Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: moderation v2 endpoints under client.moderation #1327

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
};
Loading