Skip to content

Commit

Permalink
remove all "moderator id" type parameters and make the user context o…
Browse files Browse the repository at this point in the history
…verridable

fixes #487
  • Loading branch information
d-fischer committed Apr 30, 2023
1 parent 3705c18 commit f9d39af
Show file tree
Hide file tree
Showing 17 changed files with 240 additions and 207 deletions.
9 changes: 5 additions & 4 deletions packages/api/src/client/BaseApiClient.ts
Expand Up @@ -137,7 +137,7 @@ export class BaseApiClient extends EventEmitter {

if (forceUser) {
const contextUserId = options.canOverrideScopedUserContext
? this._getUserIdFromRequestContext(options)
? this._getUserIdFromRequestContext(options.userId)
: options.userId;

if (!contextUserId) {
Expand All @@ -160,7 +160,7 @@ export class BaseApiClient extends EventEmitter {
return await this._callApiUsingInitialToken<T>(options, accessToken);
}

const requestContextUserId = this._getUserIdFromRequestContext(options);
const requestContextUserId = this._getUserIdFromRequestContext(options.userId);
const accessToken =
requestContextUserId === null
? await authProvider.getAnyAccessToken()
Expand Down Expand Up @@ -388,8 +388,9 @@ export class BaseApiClient extends EventEmitter {
}

// null means app access, undefined means none specified
protected _getUserIdFromRequestContext(options: ContextApiCallOptions): string | null | undefined {
return options.userId;
/** @internal */
_getUserIdFromRequestContext(contextUserId: string | undefined): string | null | undefined {
return contextUserId;
}

private async _callApiUsingInitialToken<T = unknown>(
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/client/NoContextApiClient.ts
Expand Up @@ -4,7 +4,8 @@ import { BaseApiClient } from './BaseApiClient';
/** @private */
@rtfm('api', 'ApiClient')
export class NoContextApiClient extends BaseApiClient {
protected _getUserIdFromRequestContext(): null {
/** @internal */
_getUserIdFromRequestContext(): null {
return null;
}
}
3 changes: 2 additions & 1 deletion packages/api/src/client/UserContextApiClient.ts
Expand Up @@ -16,7 +16,8 @@ export class UserContextApiClient extends BaseApiClient {
super(config, logger, rateLimiter);
}

protected _getUserIdFromRequestContext(): string | undefined {
/** @internal */
_getUserIdFromRequestContext(): string | undefined {
return this._userId;
}
}
5 changes: 5 additions & 0 deletions packages/api/src/endpoints/BaseApi.ts
Expand Up @@ -10,4 +10,9 @@ export class BaseApi {
constructor(client: BaseApiClient) {
this._client = client;
}

/** @internal */
protected _getUserContextIdWithDefault(userId: string): string {
return this._client._getUserIdFromRequestContext(userId) ?? userId;
}
}
28 changes: 17 additions & 11 deletions packages/api/src/endpoints/channel/HelixChannelApi.ts
Expand Up @@ -291,9 +291,11 @@ export class HelixChannelApi extends BaseApi {
* Gets a list of users that follow the specified broadcaster.
* You can also use this endpoint to see whether a specific user follows the broadcaster.
*
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* @param broadcaster The broadcaster you want to get a list of followers for.
* @param moderator The broadcaster or one of the broadcaster’s moderators.
* The token of this user will be used to fetch the followers.
* @param user An optional user to determine if this user follows the broadcaster.
* If specified, the response contains this user if they follow the broadcaster.
* If not specified, the response contains all users that follow the broadcaster.
Expand All @@ -303,15 +305,15 @@ export class HelixChannelApi extends BaseApi {
*/
async getChannelFollowers(
broadcaster: UserIdResolvable,
moderator: UserIdResolvable,
user?: UserIdResolvable,
pagination?: HelixForwardPagination
): Promise<HelixPaginatedResultWithTotal<HelixChannelFollower>> {
const result = await this._client.callApi<HelixPaginatedResponseWithTotal<HelixChannelFollowerData>>({
type: 'helix',
url: 'channels/followers',
method: 'GET',
userId: extractUserId(moderator),
userId: extractUserId(broadcaster),
canOverrideScopedUserContext: true,
scopes: ['moderator:read:followers'],
query: {
...createChannelFollowerQuery(broadcaster, user),
Expand All @@ -325,21 +327,23 @@ export class HelixChannelApi extends BaseApi {
/**
* Creates a paginator for users that follow the specified broadcaster.
*
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* @param broadcaster The broadcaster for whom you are getting a list of followers.
* @param moderator The broadcaster or one of the broadcaster’s moderators.
* The token of this user will be used to fetch the followers.
*
* @expandParams
*/
getChannelFollowersPaginated(
broadcaster: UserIdResolvable,
moderator: UserIdResolvable
broadcaster: UserIdResolvable
): HelixPaginatedRequestWithTotal<HelixChannelFollowerData, HelixChannelFollower> {
return new HelixPaginatedRequestWithTotal<HelixChannelFollowerData, HelixChannelFollower>(
{
url: 'channels/followers',
method: 'GET',
userId: extractUserId(moderator),
userId: extractUserId(broadcaster),
canOverrideScopedUserContext: true,
scopes: ['moderator:read:followers'],
query: createChannelFollowerQuery(broadcaster)
},
Expand All @@ -349,9 +353,11 @@ export class HelixChannelApi extends BaseApi {
}

/**
* Gets a list of broadcasters that the specified user follows. You can also use this endpoint to see whether the user follows a specific broadcaster.
* Gets a list of broadcasters that the specified user follows.
* You can also use this endpoint to see whether the user follows a specific broadcaster.
*
* @param user The user that's getting a list of followed channels. This ID must match the user ID in the access token.
* @param user The user that's getting a list of followed channels.
* This ID must match the user ID in the access token.
* @param broadcaster An optional broadcaster to determine if the user follows this broadcaster.
* If specified, the response contains this broadcaster if the user follows them.
* If not specified, the response contains all broadcasters that the user follows.
Expand Down
95 changes: 55 additions & 40 deletions packages/api/src/endpoints/chat/HelixChatApi.ts
Expand Up @@ -55,25 +55,28 @@ export class HelixChatApi extends BaseApi {
/**
* Gets the list of users that are connected to the broadcaster’s chat session.
*
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* @param broadcaster The broadcaster whose list of chatters you want to get.
* @param moderator The broadcaster or one of the broadcaster’s moderators.
* The token of this user will be used to fetch the chatters.
* @param pagination
*
* @expandParams
*/
async getChatters(
broadcaster: UserIdResolvable,
moderator: UserIdResolvable,
pagination?: HelixForwardPagination
): Promise<HelixPaginatedResultWithTotal<HelixChatChatter>> {
const broadcasterId = extractUserId(broadcaster);
const result = await this._client.callApi<HelixPaginatedResultWithTotal<HelixChatChatterData>>({
type: 'helix',
url: 'chat/chatters',
userId: extractUserId(moderator),
userId: broadcasterId,
canOverrideScopedUserContext: true,
scopes: ['moderator:read:chatters'],
query: {
...createModeratorActionQuery(broadcaster, moderator),
...this._createModeratorActionQuery(broadcasterId),
...createPaginationQuery(pagination)
}
});
Expand All @@ -84,22 +87,25 @@ export class HelixChatApi extends BaseApi {
/**
* Creates a paginator for users that are connected to the broadcaster’s chat session.
*
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* @param broadcaster The broadcaster whose list of chatters you want to get.
* @param moderator The broadcaster or one of the broadcaster’s moderators.
* The token of this user will be used to fetch the chatters.
*
* @expandParams
*/
getChattersPaginated(
broadcaster: UserIdResolvable,
moderator: UserIdResolvable
broadcaster: UserIdResolvable
): HelixPaginatedRequestWithTotal<HelixChatChatterData, HelixChatChatter> {
const broadcasterId = extractUserId(broadcaster);
return new HelixPaginatedRequestWithTotal<HelixChatChatterData, HelixChatChatter>(
{
url: 'chat/chatters',
userId: extractUserId(moderator),
userId: broadcasterId,
canOverrideScopedUserContext: true,
scopes: ['moderator:read:chatters'],
query: createModeratorActionQuery(broadcaster, moderator)
query: this._createModeratorActionQuery(broadcasterId)
},
this._client,
data => new HelixChatChatter(data, this._client)
Expand Down Expand Up @@ -196,22 +202,21 @@ export class HelixChatApi extends BaseApi {
/**
* Gets the settings of a broadcaster's chat, including the delay settings.
*
* @param broadcaster The broadcaster the chat belongs to.
* @param moderator The moderator the request is on behalf of.
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* This is the user your user token needs to represent.
* You can get your own settings by setting `broadcaster` and `moderator` to the same user.
* @param broadcaster The broadcaster the chat belongs to.
*/
async getSettingsPrivileged(
broadcaster: UserIdResolvable,
moderator: UserIdResolvable
): Promise<HelixPrivilegedChatSettings> {
async getSettingsPrivileged(broadcaster: UserIdResolvable): Promise<HelixPrivilegedChatSettings> {
const broadcasterId = extractUserId(broadcaster);
const result = await this._client.callApi<HelixResponse<HelixPrivilegedChatSettingsData>>({
type: 'helix',
url: 'chat/settings',
userId: extractUserId(moderator),
userId: broadcasterId,
canOverrideScopedUserContext: true,
scopes: ['moderator:read:chat_settings'],
query: createModeratorActionQuery(broadcaster, moderator)
query: this._createModeratorActionQuery(broadcasterId)
});

return new HelixPrivilegedChatSettings(result.data[0]);
Expand All @@ -220,27 +225,28 @@ export class HelixChatApi extends BaseApi {
/**
* Updates the settings of a broadcaster's chat.
*
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* @expandParams
*
* @param broadcaster The broadcaster the chat belongs to.
* @param moderator The moderator the request is on behalf of.
*
* This is the user your user token needs to represent.
* You can get your own settings by setting `broadcaster` and `moderator` to the same user.
* @param settings The settings to change.
*/
async updateSettings(
broadcaster: UserIdResolvable,
moderator: UserIdResolvable,
settings: HelixUpdateChatSettingsParams
): Promise<HelixPrivilegedChatSettings> {
const broadcasterId = extractUserId(broadcaster);
const result = await this._client.callApi<HelixResponse<HelixPrivilegedChatSettingsData>>({
type: 'helix',
url: 'chat/settings',
method: 'PATCH',
userId: extractUserId(moderator),
userId: broadcasterId,
canOverrideScopedUserContext: true,
scopes: ['moderator:manage:chat_settings'],
query: createModeratorActionQuery(broadcaster, moderator),
query: this._createModeratorActionQuery(broadcasterId),
jsonBody: createChatSettingsUpdateBody(settings)
});

Expand All @@ -250,25 +256,26 @@ export class HelixChatApi extends BaseApi {
/**
* Sends an announcement to a broadcaster's chat.
*
* @param broadcaster The broadcaster the chat belongs to.
* @param moderator The moderator the request is on behalf of.
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* This is the user your user token needs to represent.
* You can send an announcement to your own chat by setting `broadcaster` and `moderator` to the same user.
* @param broadcaster The broadcaster the chat belongs to.
* @param announcement The announcement to send.
*/
async sendAnnouncement(
broadcaster: UserIdResolvable,
moderator: UserIdResolvable,
announcement: HelixSendChatAnnouncementParams
): Promise<void> {
const broadcasterId = extractUserId(broadcaster);
await this._client.callApi({
type: 'helix',
url: 'chat/announcements',
method: 'POST',
userId: extractUserId(moderator),
userId: broadcasterId,
canOverrideScopedUserContext: true,
scopes: ['moderator:manage:announcements'],
query: createModeratorActionQuery(broadcaster, moderator),
query: this._createModeratorActionQuery(broadcasterId),
jsonBody: {
message: announcement.message,
color: announcement.color
Expand Down Expand Up @@ -340,19 +347,27 @@ export class HelixChatApi extends BaseApi {
* Sends a shoutout to the specified broadcaster.
* The broadcaster may send a shoutout once every 2 minutes. They may send the same broadcaster a shoutout once every 60 minutes.
*
* This uses the token of the broadcaster by default.
* If you want to execute this in the context of another user (who has to be moderator of the channel)
* you can do so using [user context overrides](/docs/auth/concepts/context-switching).
*
* @param from The ID of the broadcaster that’s sending the shoutout.
* @param to The ID of the broadcaster that’s receiving the shoutout.
* @param moderator The ID of the broadcaster or a user that is one of the broadcaster’s moderators.
* This ID must match the user ID in the access token.
*/
async shoutoutUser(from: UserIdResolvable, to: UserIdResolvable, moderator: UserIdResolvable): Promise<void> {
async shoutoutUser(from: UserIdResolvable, to: UserIdResolvable): Promise<void> {
const fromId = extractUserId(from);
await this._client.callApi({
type: 'helix',
url: 'chat/shoutouts',
method: 'POST',
userId: extractUserId(moderator),
userId: fromId,
canOverrideScopedUserContext: true,
scopes: ['moderator:manage:shoutouts'],
query: createShoutoutQuery(from, to, moderator)
query: createShoutoutQuery(from, to, this._getUserContextIdWithDefault(fromId))
});
}

private _createModeratorActionQuery(broadcasterId: string) {
return createModeratorActionQuery(broadcasterId, this._getUserContextIdWithDefault(broadcasterId));
}
}

0 comments on commit f9d39af

Please sign in to comment.