Skip to content

Commit b1f47f0

Browse files
vishalnarkhedeJimmyPettersson85gumuz
authored
feat: moderation v2 api endpoints (#1312)
Co-authored-by: Jimmy Pettersson <[email protected]> Co-authored-by: Guyon Morée <[email protected]>
1 parent dd7b1fe commit b1f47f0

File tree

2 files changed

+244
-2
lines changed

2 files changed

+244
-2
lines changed

src/client.ts

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ import {
203203
QueryMessageHistorySort,
204204
QueryMessageHistoryOptions,
205205
QueryMessageHistoryResponse,
206+
GetUserModerationReportResponse,
207+
ReviewQueueFilters,
208+
ReviewQueueSort,
209+
ReviewQueuePaginationOptions,
210+
ReviewQueueResponse,
211+
GetConfigResponse,
212+
UpsertConfigResponse,
213+
Config,
206214
} from './types';
207215
import { InsightMetrics, postInsights } from './insights';
208216
import { Thread } from './thread';
@@ -1549,6 +1557,91 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
15491557
});
15501558
}
15511559

1560+
async getUserModerationReport(
1561+
userID: string,
1562+
options: {
1563+
create_user_if_not_exists?: boolean;
1564+
include_user_blocks?: boolean;
1565+
include_user_mutes?: boolean;
1566+
} = {},
1567+
) {
1568+
return await this.get<GetUserModerationReportResponse<StreamChatGenerics>>(
1569+
this.baseURL + `/api/v2/moderation/user_report`,
1570+
{
1571+
user_id: userID,
1572+
...options,
1573+
},
1574+
);
1575+
}
1576+
1577+
async queryReviewQueue(
1578+
filterConditions: ReviewQueueFilters = {},
1579+
sort: ReviewQueueSort = [],
1580+
options: ReviewQueuePaginationOptions = {},
1581+
) {
1582+
return await this.post<ReviewQueueResponse>(this.baseURL + '/api/v2/moderation/review_queue', {
1583+
filter: filterConditions,
1584+
sort: normalizeQuerySort(sort),
1585+
...options,
1586+
});
1587+
}
1588+
1589+
async upsertConfig(config: Config = {}) {
1590+
return await this.post<UpsertConfigResponse>(this.baseURL + '/api/v2/moderation/config', config);
1591+
}
1592+
1593+
async getConfig(key: string) {
1594+
return await this.get<GetConfigResponse>(this.baseURL + '/api/v2/moderation/config/' + key);
1595+
}
1596+
1597+
async flagUserV2(flaggedUserID: string, reason: string, options: Record<string, unknown> = {}) {
1598+
return this.flagV2('stream:user', flaggedUserID, '', reason, options);
1599+
}
1600+
1601+
async flagMessageV2(messageID: string, reason: string, options: Record<string, unknown> = {}) {
1602+
return this.flagV2('stream:chat:v1:message', messageID, '', reason, options);
1603+
}
1604+
1605+
async flagV2(
1606+
entityType: string,
1607+
entityId: string,
1608+
entityCreatorID: string,
1609+
reason: string,
1610+
options: Record<string, unknown> = {},
1611+
) {
1612+
return await this.post<{ item_id: string } & APIResponse>(this.baseURL + '/api/v2/moderation/flag', {
1613+
entity_type: entityType,
1614+
entity_id: entityId,
1615+
entity_creator_id: entityCreatorID,
1616+
reason,
1617+
...options,
1618+
});
1619+
}
1620+
1621+
async muteUserV2(
1622+
targetID: string,
1623+
options: {
1624+
timeout?: number;
1625+
user_id?: string;
1626+
} = {},
1627+
) {
1628+
return await this.post<MuteUserResponse & APIResponse>(this.baseURL + '/api/v2/moderation/mute', {
1629+
target_ids: [targetID],
1630+
...options,
1631+
});
1632+
}
1633+
1634+
async unmuteUserV2(
1635+
targetID: string,
1636+
options: {
1637+
user_id?: string;
1638+
},
1639+
) {
1640+
return await this.post<{ item_id: string } & APIResponse>(this.baseURL + '/api/v2/moderation/unmute', {
1641+
target_ids: [targetID],
1642+
...options,
1643+
});
1644+
}
15521645
/**
15531646
* queryChannels - Query channels
15541647
*
@@ -2258,7 +2351,7 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
22582351
* @param {string} [options.user_id] currentUserID, only used with serverside auth
22592352
* @returns {Promise<APIResponse>}
22602353
*/
2261-
async flagMessage(targetMessageID: string, options: { user_id?: string } = {}) {
2354+
async flagMessage(targetMessageID: string, options: { reason?: string; user_id?: string } = {}) {
22622355
return await this.post<FlagMessageResponse<StreamChatGenerics>>(this.baseURL + '/moderation/flag', {
22632356
target_message_id: targetMessageID,
22642357
...options,
@@ -2271,7 +2364,7 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
22712364
* @param {string} [options.user_id] currentUserID, only used with serverside auth
22722365
* @returns {Promise<APIResponse>}
22732366
*/
2274-
async flagUser(targetID: string, options: { user_id?: string } = {}) {
2367+
async flagUser(targetID: string, options: { reason?: string; user_id?: string } = {}) {
22752368
return await this.post<FlagUserResponse<StreamChatGenerics>>(this.baseURL + '/moderation/flag', {
22762369
target_user_id: targetID,
22772370
...options,

src/types.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ export type FlagMessageResponse<StreamChatGenerics extends ExtendableGenerics =
442442
reviewed_at?: string;
443443
reviewed_by?: string;
444444
};
445+
review_queue_item_id?: string;
445446
};
446447

447448
export type FlagUserResponse<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = APIResponse & {
@@ -457,6 +458,7 @@ export type FlagUserResponse<StreamChatGenerics extends ExtendableGenerics = Def
457458
reviewed_at?: string;
458459
reviewed_by?: string;
459460
};
461+
review_queue_item_id?: string;
460462
};
461463

462464
export type FormatMessageResponse<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = Omit<
@@ -1899,6 +1901,7 @@ export type AsyncModerationOptions = {
18991901

19001902
export type AppSettings = {
19011903
agora_options?: AgoraOptions | null;
1904+
allowed_flag_reasons?: string[];
19021905
apn_config?: {
19031906
auth_key?: string;
19041907
auth_type?: string;
@@ -2006,6 +2009,8 @@ export type OGAttachment = {
20062009
export type BlockList = {
20072010
name: string;
20082011
words: string[];
2012+
type?: string;
2013+
validate?: boolean;
20092014
};
20102015

20112016
export type ChannelConfig<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = ChannelConfigFields &
@@ -3149,3 +3154,147 @@ export type QueryMessageHistoryResponse<StreamChatGenerics extends ExtendableGen
31493154
next?: string;
31503155
prev?: string;
31513156
};
3157+
3158+
// Moderation v2
3159+
export type ModerationPayload = {
3160+
created_at: string;
3161+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3162+
custom?: Record<string, any>;
3163+
images?: string[];
3164+
texts?: string[];
3165+
videos?: string[];
3166+
};
3167+
3168+
export type ModV2ReviewStatus = 'complete' | 'flagged' | 'partial';
3169+
3170+
export type ModerationFlag = {
3171+
created_at: string;
3172+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3173+
custom: Record<string, any>;
3174+
entity_creator_id: string;
3175+
entity_id: string;
3176+
entity_type: string;
3177+
id: string;
3178+
reason: string;
3179+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3180+
result: Record<string, any>[];
3181+
review_queue_item_id: string;
3182+
updated_at: string;
3183+
user: UserResponse;
3184+
moderation_payload?: ModerationPayload;
3185+
moderation_payload_hash?: string;
3186+
};
3187+
3188+
export type ReviewQueueItem = {
3189+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3190+
actions_taken: any[];
3191+
appealed_by: string;
3192+
assigned_to: string;
3193+
completed_at: string;
3194+
config_key: string;
3195+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3196+
context: any[];
3197+
created_at: string;
3198+
created_by: string;
3199+
entity_id: string;
3200+
entity_type: string;
3201+
flags: ModerationFlag[];
3202+
has_image: boolean;
3203+
has_text: boolean;
3204+
has_video: boolean;
3205+
id: string;
3206+
moderation_payload: ModerationPayload;
3207+
moderation_payload_hash: string;
3208+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3209+
options: any;
3210+
recommended_action: string;
3211+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3212+
results: any;
3213+
reviewed_at: string;
3214+
status: string;
3215+
updated_at: string;
3216+
};
3217+
3218+
export type GetUserModerationReportResponse<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = {
3219+
user: UserResponse<StreamChatGenerics>;
3220+
user_blocks?: Array<{
3221+
blocked_at: string;
3222+
blocked_by_user_id: string;
3223+
blocked_user_id: string;
3224+
}>;
3225+
user_mutes?: Mute<StreamChatGenerics>[];
3226+
};
3227+
3228+
export type ReviewQueueFilters = QueryFilters<
3229+
{
3230+
assigned_to?:
3231+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['assigned_to']>, '$eq' | '$in'>>
3232+
| PrimitiveFilter<ReviewQueueItem['assigned_to']>;
3233+
} & {
3234+
completed_at?:
3235+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['completed_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>>
3236+
| PrimitiveFilter<ReviewQueueItem['completed_at']>;
3237+
} & {
3238+
config_key?:
3239+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['config_key']>, '$eq' | '$in'>>
3240+
| PrimitiveFilter<ReviewQueueItem['config_key']>;
3241+
} & {
3242+
entity_type?:
3243+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['entity_type']>, '$eq' | '$in'>>
3244+
| PrimitiveFilter<ReviewQueueItem['entity_type']>;
3245+
} & {
3246+
created_at?:
3247+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['created_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>>
3248+
| PrimitiveFilter<ReviewQueueItem['created_at']>;
3249+
} & {
3250+
id?:
3251+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['id']>, '$eq' | '$in'>>
3252+
| PrimitiveFilter<ReviewQueueItem['id']>;
3253+
} & {
3254+
entity_id?:
3255+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['entity_id']>, '$eq' | '$in'>>
3256+
| PrimitiveFilter<ReviewQueueItem['entity_id']>;
3257+
} & {
3258+
reviewed?: boolean;
3259+
} & {
3260+
reviewed_at?:
3261+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['reviewed_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>>
3262+
| PrimitiveFilter<ReviewQueueItem['reviewed_at']>;
3263+
} & {
3264+
status?:
3265+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['status']>, '$eq' | '$in'>>
3266+
| PrimitiveFilter<ReviewQueueItem['status']>;
3267+
} & {
3268+
updated_at?:
3269+
| RequireOnlyOne<Pick<QueryFilter<ReviewQueueItem['updated_at']>, '$eq' | '$gt' | '$lt' | '$gte' | '$lte'>>
3270+
| PrimitiveFilter<ReviewQueueItem['updated_at']>;
3271+
} & {
3272+
has_image?: boolean;
3273+
} & {
3274+
has_text?: boolean;
3275+
} & {
3276+
has_video?: boolean;
3277+
}
3278+
>;
3279+
3280+
export type ReviewQueueSort =
3281+
| Sort<Pick<ReviewQueueItem, 'id' | 'created_at' | 'updated_at'>>
3282+
| Array<Sort<Pick<ReviewQueueItem, 'id' | 'created_at' | 'updated_at'>>>;
3283+
3284+
export type ReviewQueuePaginationOptions = Pager;
3285+
3286+
export type ReviewQueueResponse = {
3287+
items: ReviewQueueItem[];
3288+
next?: string;
3289+
prev?: string;
3290+
};
3291+
3292+
export type Config = {};
3293+
3294+
export type GetConfigResponse = {
3295+
config: Config;
3296+
};
3297+
3298+
export type UpsertConfigResponse = {
3299+
config: Config;
3300+
};

0 commit comments

Comments
 (0)