diff --git a/src/client.ts b/src/client.ts index ab3966d64..d96ace885 100644 --- a/src/client.ts +++ b/src/client.ts @@ -203,17 +203,10 @@ import { QueryMessageHistorySort, QueryMessageHistoryOptions, QueryMessageHistoryResponse, - GetUserModerationReportResponse, - ReviewQueueFilters, - ReviewQueueSort, - ReviewQueuePaginationOptions, - ReviewQueueResponse, - GetConfigResponse, - UpsertConfigResponse, - Config, } from './types'; import { InsightMetrics, postInsights } from './insights'; import { Thread } from './thread'; +import { Moderation } from './moderation'; function isString(x: unknown): x is string { return typeof x === 'string' || x instanceof String; @@ -247,6 +240,7 @@ export class StreamChat; mutedChannels: ChannelMute[]; mutedUsers: Mute[]; node: boolean; @@ -298,6 +292,8 @@ export class StreamChat>( - this.baseURL + `/api/v2/moderation/user_report`, - { - user_id: userID, - ...options, - }, - ); - } - - async queryReviewQueue( - filterConditions: ReviewQueueFilters = {}, - sort: ReviewQueueSort = [], - options: ReviewQueuePaginationOptions = {}, - ) { - return await this.post(this.baseURL + '/api/v2/moderation/review_queue', { - filter: filterConditions, - sort: normalizeQuerySort(sort), - ...options, - }); - } - - async upsertConfig(config: Config = {}) { - return await this.post(this.baseURL + '/api/v2/moderation/config', config); - } - - async getConfig(key: string) { - return await this.get(this.baseURL + '/api/v2/moderation/config/' + key); - } - - async flagUserV2(flaggedUserID: string, reason: string, options: Record = {}) { - return this.flagV2('stream:user', flaggedUserID, '', reason, options); - } - - async flagMessageV2(messageID: string, reason: string, options: Record = {}) { - return this.flagV2('stream:chat:v1:message', messageID, '', reason, options); - } - - async flagV2( - entityType: string, - entityId: string, - entityCreatorID: string, - reason: string, - options: Record = {}, - ) { - return await this.post<{ item_id: string } & APIResponse>(this.baseURL + '/api/v2/moderation/flag', { - entity_type: entityType, - entity_id: entityId, - entity_creator_id: entityCreatorID, - reason, - ...options, - }); - } - - async muteUserV2( - targetID: string, - options: { - timeout?: number; - user_id?: string; - } = {}, - ) { - return await this.post(this.baseURL + '/api/v2/moderation/mute', { - target_ids: [targetID], - ...options, - }); - } - - async unmuteUserV2( - targetID: string, - options: { - user_id?: string; - }, - ) { - return await this.post<{ item_id: string } & APIResponse>(this.baseURL + '/api/v2/moderation/unmute', { - target_ids: [targetID], - ...options, - }); - } /** * queryChannels - Query channels * diff --git a/src/index.ts b/src/index.ts index 96d641236..232af6ec0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ export * from './channel_state'; export * from './thread'; export * from './connection'; export * from './events'; +export * from './moderation'; export * from './permissions'; export * from './signing'; export * from './token_manager'; diff --git a/src/moderation.ts b/src/moderation.ts new file mode 100644 index 000000000..cefcc1e68 --- /dev/null +++ b/src/moderation.ts @@ -0,0 +1,182 @@ +import { + APIResponse, + ModerationConfig, + DefaultGenerics, + ExtendableGenerics, + GetConfigResponse, + GetUserModerationReportResponse, + MuteUserResponse, + ReviewQueueFilters, + ReviewQueuePaginationOptions, + ReviewQueueResponse, + ReviewQueueSort, + UpsertConfigResponse, + ModerationFlagOptions, + ModerationMuteOptions, + GetUserModerationReportOptions, +} from './types'; +import { StreamChat } from './client'; +import { normalizeQuerySort } from './utils'; + +export const MODERATION_ENTITY_TYPES = { + user: 'stream:user', + message: 'stream:chat:v1:message', +}; + +// Moderation class provides all the endpoints related to moderation v2. +export class Moderation { + client: StreamChat; + + constructor(client: StreamChat) { + this.client = client; + } + + /** + * Flag a user + * + * @param {string} flaggedUserID User ID to be flagged + * @param {string} reason Reason for flagging the user + * @param {Object} options Additional options for flagging the user + * @param {string} options.user_id (For server side usage) User ID of the user who is flagging the target user + * @param {Object} options.custom Additional data to be stored with the flag + * @returns + */ + async flagUser(flaggedUserID: string, reason: string, options: ModerationFlagOptions = {}) { + return this.flag(MODERATION_ENTITY_TYPES.user, flaggedUserID, '', reason, options); + } + + /** + * Flag a message + * + * @param {string} messageID Message ID to be flagged + * @param {string} reason Reason for flagging the message + * @param {Object} options Additional options for flagging the message + * @param {string} options.user_id (For server side usage) User ID of the user who is flagging the target message + * @param {Object} options.custom Additional data to be stored with the flag + * @returns + */ + async flagMessage(messageID: string, reason: string, options: ModerationFlagOptions = {}) { + return this.flag(MODERATION_ENTITY_TYPES.message, messageID, '', reason, options); + } + + /** + * Flag a user + * + * @param {string} entityType Entity type to be flagged + * @param {string} entityId Entity ID to be flagged + * @param {string} entityCreatorID User ID of the entity creator + * @param {string} reason Reason for flagging the entity + * @param {Object} options Additional options for flagging the entity + * @param {string} options.user_id (For server side usage) User ID of the user who is flagging the target entity + * @param {Object} options.moderation_payload Content to be flagged e.g., { texts: ['text1', 'text2'], images: ['image1', 'image2']} + * @param {Object} options.custom Additional data to be stored with the flag + * @returns + */ + async flag( + entityType: string, + entityId: string, + entityCreatorID: string, + reason: string, + options: ModerationFlagOptions = {}, + ) { + return await this.client.post<{ item_id: string } & APIResponse>(this.client.baseURL + '/api/v2/moderation/flag', { + entity_type: entityType, + entity_id: entityId, + entity_creator_id: entityCreatorID, + reason, + ...options, + }); + } + + /** + * Mute a user + * @param {string} targetID User ID to be muted + * @param {Object} options Additional options for muting the user + * @param {string} options.user_id (For server side usage) User ID of the user who is muting the target user + * @param {number} options.timeout Timeout for the mute in minutes + * @returns + */ + async muteUser(targetID: string, options: ModerationMuteOptions = {}) { + return await this.client.post & APIResponse>( + this.client.baseURL + '/api/v2/moderation/mute', + { + target_ids: [targetID], + ...options, + }, + ); + } + + /** + * Unmute a user + * @param {string} targetID User ID to be unmuted + * @param {Object} options Additional options for unmuting the user + * @param {string} options.user_id (For server side usage) User ID of the user who is unmuting the target user + * @returns + */ + async unmuteUser( + targetID: string, + options: { + user_id?: string; + }, + ) { + return await this.client.post<{ item_id: string } & APIResponse>( + this.client.baseURL + '/api/v2/moderation/unmute', + { + target_ids: [targetID], + ...options, + }, + ); + } + + /** + * Get moderation report for a user + * @param {string} userID User ID for which moderation report is to be fetched + * @param {Object} options Additional options for fetching the moderation report + * @param {boolean} options.create_user_if_not_exists Create user if not exists + * @param {boolean} options.include_user_blocks Include user blocks + * @param {boolean} options.include_user_mutes Include user mutes + */ + async getUserModerationReport(userID: string, options: GetUserModerationReportOptions = {}) { + return await this.client.get>( + this.client.baseURL + `/api/v2/moderation/user_report`, + { + user_id: userID, + ...options, + }, + ); + } + + /** + * Query review queue + * @param {Object} filterConditions Filter conditions for querying review queue + * @param {Object} sort Sort conditions for querying review queue + * @param {Object} options Pagination options for querying review queue + */ + async queryReviewQueue( + filterConditions: ReviewQueueFilters = {}, + sort: ReviewQueueSort = [], + options: ReviewQueuePaginationOptions = {}, + ) { + return await this.client.post(this.client.baseURL + '/api/v2/moderation/review_queue', { + filter: filterConditions, + sort: normalizeQuerySort(sort), + ...options, + }); + } + + /** + * Upsert moderation config + * @param {Object} config Moderation config to be upserted + */ + async upsertConfig(config: ModerationConfig = {}) { + return await this.client.post(this.client.baseURL + '/api/v2/moderation/config', config); + } + + /** + * Get moderation config + * @param {string} key Key for which moderation config is to be fetched + */ + async getConfig(key: string) { + return await this.client.get(this.client.baseURL + '/api/v2/moderation/config/' + key); + } +} diff --git a/src/types.ts b/src/types.ts index 864404023..0e4833e1e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3289,12 +3289,28 @@ export type ReviewQueueResponse = { prev?: string; }; -export type Config = {}; +export type ModerationConfig = {}; export type GetConfigResponse = { - config: Config; + config: ModerationConfig; }; export type UpsertConfigResponse = { - config: Config; + config: ModerationConfig; +}; + +export type ModerationFlagOptions = { + custom?: Record; + moderation_payload?: ModerationPayload; + user_id?: string; +}; + +export type ModerationMuteOptions = { + timeout?: number; + user_id?: string; +}; +export type GetUserModerationReportOptions = { + create_user_if_not_exists?: boolean; + include_user_blocks?: boolean; + include_user_mutes?: boolean; };