Skip to content

Commit

Permalink
feat: moderation v2 endpoints under client.moderation (#1327)
Browse files Browse the repository at this point in the history
  • Loading branch information
vishalnarkhede authored Jul 5, 2024
1 parent b1f47f0 commit 2276b85
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 96 deletions.
97 changes: 4 additions & 93 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -247,6 +240,7 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
* manually calling queryChannels endpoint.
*/
recoverStateOnReconnect?: boolean;
moderation: Moderation<StreamChatGenerics>;
mutedChannels: ChannelMute<StreamChatGenerics>[];
mutedUsers: Mute<StreamChatGenerics>[];
node: boolean;
Expand Down Expand Up @@ -298,6 +292,8 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
this.mutedChannels = [];
this.mutedUsers = [];

this.moderation = new Moderation(this);

// set the secret
if (secretOrOptions && isString(secretOrOptions)) {
this.secret = secretOrOptions;
Expand Down Expand Up @@ -1557,91 +1553,6 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
});
}

async getUserModerationReport(
userID: string,
options: {
create_user_if_not_exists?: boolean;
include_user_blocks?: boolean;
include_user_mutes?: boolean;
} = {},
) {
return await this.get<GetUserModerationReportResponse<StreamChatGenerics>>(
this.baseURL + `/api/v2/moderation/user_report`,
{
user_id: userID,
...options,
},
);
}

async queryReviewQueue(
filterConditions: ReviewQueueFilters = {},
sort: ReviewQueueSort = [],
options: ReviewQueuePaginationOptions = {},
) {
return await this.post<ReviewQueueResponse>(this.baseURL + '/api/v2/moderation/review_queue', {
filter: filterConditions,
sort: normalizeQuerySort(sort),
...options,
});
}

async upsertConfig(config: Config = {}) {
return await this.post<UpsertConfigResponse>(this.baseURL + '/api/v2/moderation/config', config);
}

async getConfig(key: string) {
return await this.get<GetConfigResponse>(this.baseURL + '/api/v2/moderation/config/' + key);
}

async flagUserV2(flaggedUserID: string, reason: string, options: Record<string, unknown> = {}) {
return this.flagV2('stream:user', flaggedUserID, '', reason, options);
}

async flagMessageV2(messageID: string, reason: string, options: Record<string, unknown> = {}) {
return this.flagV2('stream:chat:v1:message', messageID, '', reason, options);
}

async flagV2(
entityType: string,
entityId: string,
entityCreatorID: string,
reason: string,
options: Record<string, unknown> = {},
) {
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<MuteUserResponse & APIResponse>(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
*
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
182 changes: 182 additions & 0 deletions src/moderation.ts
Original file line number Diff line number Diff line change
@@ -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<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> {
client: StreamChat<StreamChatGenerics>;

constructor(client: StreamChat<StreamChatGenerics>) {
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<MuteUserResponse<StreamChatGenerics> & 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<StreamChatGenerics>>(
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) {
return await this.client.get<GetConfigResponse>(this.client.baseURL + '/api/v2/moderation/config/' + key);
}
}
22 changes: 19 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;
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;
};

0 comments on commit 2276b85

Please sign in to comment.