stream-chat
Version:
JS SDK for the Stream Chat API
454 lines (428 loc) • 14.5 kB
text/typescript
import type {
APIResponse,
CustomCheckFlag,
GetConfigResponse,
GetUserModerationReportOptions,
GetUserModerationReportResponse,
ModerationConfig,
ModerationFlagOptions,
ModerationMuteOptions,
ModerationRule,
ModerationRuleRequest,
MuteUserResponse,
Pager,
QueryConfigsResponse,
QueryModerationConfigsFilters,
QueryModerationConfigsSort,
QueryModerationRulesFilters,
QueryModerationRulesResponse,
QueryModerationRulesSort,
RequireAtLeastOne,
ReviewQueueFilters,
ReviewQueueItem,
ReviewQueuePaginationOptions,
ReviewQueueResponse,
ReviewQueueSort,
SubmitActionOptions,
UpsertConfigResponse,
UpsertModerationRuleResponse,
} from './types';
import type { StreamChat } from './client';
import { normalizeQuerySort } from './utils';
export const MODERATION_ENTITY_TYPES = {
user: 'stream:user',
message: 'stream:chat:v1:message',
userprofile: 'stream:v1:user_profile',
};
// 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
*/
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
*/
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<MuteUserResponse & 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<GetUserModerationReportResponse>(
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<ReviewQueueResponse>(
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<UpsertConfigResponse>(
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, data?: { team?: string }) {
return await this.client.get<GetConfigResponse>(
this.client.baseURL + '/api/v2/moderation/config/' + key,
data,
);
}
async deleteConfig(key: string, data?: { team?: string }) {
return await this.client.delete(
this.client.baseURL + '/api/v2/moderation/config/' + key,
data,
);
}
/**
* Query moderation configs
* @param {Object} filterConditions Filter conditions for querying moderation configs
* @param {Object} sort Sort conditions for querying moderation configs
* @param {Object} options Additional options for querying moderation configs
*/
async queryConfigs(
filterConditions: QueryModerationConfigsFilters,
sort: QueryModerationConfigsSort,
options: Pager = {},
) {
return await this.client.post<QueryConfigsResponse>(
this.client.baseURL + '/api/v2/moderation/configs',
{
filter: filterConditions,
sort,
...options,
},
);
}
async submitAction(
actionType: string,
itemID: string,
options: SubmitActionOptions = {},
) {
return await this.client.post<{ item_id: string } & APIResponse>(
this.client.baseURL + '/api/v2/moderation/submit_action',
{
action_type: actionType,
item_id: itemID,
...options,
},
);
}
/**
*
* @param {string} entityType string Type of entity to be checked E.g., stream:user, stream:chat:v1:message, or any custom string
* @param {string} entityID string ID of the entity to be checked. This is mainly for tracking purposes
* @param {string} entityCreatorID string ID of the entity creator
* @param {object} moderationPayload object Content to be checked for moderation. E.g., { texts: ['text1', 'text2'], images: ['image1', 'image2']}
* @param {Array} moderationPayload.texts array Array of texts to be checked for moderation
* @param {Array} moderationPayload.images array Array of images to be checked for moderation
* @param {Array} moderationPayload.videos array Array of videos to be checked for moderation
* @param configKey
* @param options
* @returns
*/
async check(
entityType: string,
entityID: string,
entityCreatorID: string,
moderationPayload: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
custom?: Record<string, any>;
images?: string[];
texts?: string[];
videos?: string[];
},
configKey: string,
options?: {
force_sync?: boolean;
test_mode?: boolean;
},
) {
return await this.client.post(this.client.baseURL + `/api/v2/moderation/check`, {
entity_type: entityType,
entity_id: entityID,
entity_creator_id: entityCreatorID,
moderation_payload: moderationPayload,
config_key: configKey,
options,
});
}
/**
* Experimental: Check user profile
*
* Warning: This is an experimental feature and the API is subject to change.
*
* This function is used to check a user profile for moderation.
* This will not create any review queue items for the user profile.
* You can just use this to check whether to allow a certain user profile to be created or not.
*
* Example:
*
* ```ts
* const res = await client.moderation.checkUserProfile(userId, { username: "fuck_boy_001", image: "https://example.com/profile.jpg" });
* if (res.recommended_action === "remove") {
* // Block the user profile from being created
* } else {
* // Allow the user profile to be created
* }
* ```
*
* @param userId
* @param profile.username
* @param profile.image
* @returns
*/
async checkUserProfile(
userId: string,
profile: RequireAtLeastOne<{ image?: string; username?: string }>,
) {
if (!profile.username && !profile.image) {
throw new Error('Either username or image must be provided');
}
const moderationPayload: { images?: string[]; texts?: string[] } = {};
if (profile.username) {
moderationPayload.texts = [profile.username];
}
if (profile.image) {
moderationPayload.images = [profile.image];
}
return await this.check(
MODERATION_ENTITY_TYPES.userprofile,
userId,
userId,
moderationPayload,
'user_profile:default',
{
force_sync: true,
test_mode: true,
},
);
}
/**
*
* @param {string} entityType string Type of entity to be checked E.g., stream:user, stream:chat:v1:message, or any custom string
* @param {string} entityID string ID of the entity to be checked. This is mainly for tracking purposes
* @param {string} entityCreatorID string ID of the entity creator
* @param {object} moderationPayload object Content to be checked for moderation. E.g., { texts: ['text1', 'text2'], images: ['image1', 'image2']}
* @param {Array} moderationPayload.texts array Array of texts to be checked for moderation
* @param {Array} moderationPayload.images array Array of images to be checked for moderation
* @param {Array} moderationPayload.videos array Array of videos to be checked for moderation
* @param {Array<CustomCheckFlag>} flags Array of CustomCheckFlag to be passed to flag the entity
* @returns
*/
async addCustomFlags(
entityType: string,
entityID: string,
entityCreatorID: string,
moderationPayload: {
images?: string[];
texts?: string[];
videos?: string[];
},
flags: CustomCheckFlag[],
) {
return await this.client.post<
{ id: string; item: ReviewQueueItem; status: string } & APIResponse
>(this.client.baseURL + `/api/v2/moderation/custom_check`, {
entity_type: entityType,
entity_id: entityID,
entity_creator_id: entityCreatorID,
moderation_payload: moderationPayload,
flags,
});
}
/**
* Add custom flags to a message
* @param {string} messageID Message ID to be flagged
* @param {Array<CustomCheckFlag>} flags Array of CustomCheckFlag to be passed to flag the message
* @returns
*/
async addCustomMessageFlags(messageID: string, flags: CustomCheckFlag[]) {
return await this.addCustomFlags(
MODERATION_ENTITY_TYPES.message,
messageID,
'',
{},
flags,
);
}
/**
* Create or update a moderation rule
* @param {ModerationRuleRequest} rule Rule configuration to be upserted
* @returns
*/
async upsertModerationRule(rule: ModerationRuleRequest) {
return await this.client.post<UpsertModerationRuleResponse>(
this.client.baseURL + '/api/v2/moderation/moderation_rule',
rule,
);
}
/**
* Query moderation rules
* @param {QueryModerationRulesFilters} filterConditions Filter conditions for querying moderation rules
* @param {QueryModerationRulesSort} sort Sort conditions for querying moderation rules
* @param {Pager} options Pagination options for querying moderation rules
* @returns
*/
async queryModerationRules(
filterConditions: QueryModerationRulesFilters = {},
sort: QueryModerationRulesSort = [],
options: Pager = {},
) {
return await this.client.post<QueryModerationRulesResponse>(
this.client.baseURL + '/api/v2/moderation/moderation_rules',
{
filter: filterConditions,
sort,
...options,
},
);
}
/**
* Get a specific moderation rule by ID
* @param {string} id ID of the moderation rule to fetch
* @returns
*/
async getModerationRule(id: string) {
return await this.client.get<{ rule: ModerationRule }>(
this.client.baseURL + '/api/v2/moderation/moderation_rule/' + id,
);
}
/**
* Delete a moderation rule by ID
* @param {string} id ID of the moderation rule to delete
* @returns
*/
async deleteModerationRule(id: string) {
return await this.client.delete(
this.client.baseURL + '/api/v2/moderation/moderation_rule/' + id,
);
}
}