diff --git a/src/moderation.ts b/src/moderation.ts index 230f68482..71e828e19 100644 --- a/src/moderation.ts +++ b/src/moderation.ts @@ -1,6 +1,12 @@ import type { APIResponse, + AppealOptions, + AppealRequest, + AppealResponse, + AppealsSort, CustomCheckFlag, + DecideAppealRequest, + GetAppealResponse, GetConfigResponse, GetUserModerationReportOptions, GetUserModerationReportResponse, @@ -11,6 +17,9 @@ import type { ModerationRuleRequest, MuteUserResponse, Pager, + QueryAppealsFilters, + QueryAppealsPaginationOptions, + QueryAppealsResponse, QueryConfigsResponse, QueryModerationConfigsFilters, QueryModerationConfigsSort, @@ -186,6 +195,108 @@ export class Moderation { ); } + /** + * Appeal against the moderation decision + * @param {AppealRequest} appealRequest Appeal request to be appealed against + */ + async appeal(appealRequest: AppealRequest, options: AppealOptions = {}) { + return await this.client.post( + this.client.baseURL + '/api/v2/moderation/appeal', + { + text: appealRequest.text, + entity_id: appealRequest.entityID, + entity_type: appealRequest.entityType, + attachments: appealRequest.attachments, + ...options, + }, + ); + } + + /** + * Decide on an appeal + * @param {DecideAppealRequest} decideAppealRequest Request to decide on an appeal + */ + async decideAppeal( + decideAppealRequest: DecideAppealRequest, + options: AppealOptions = {}, + ) { + return await this.client.post( + this.client.baseURL + '/api/v2/moderation/decide_appeal', + { + appeal_id: decideAppealRequest.appealID, + status: decideAppealRequest.status, + decision_reason: decideAppealRequest.decisionReason, + ...(decideAppealRequest.channelCIDs + ? { channel_cids: decideAppealRequest.channelCIDs } + : {}), + ...options, + }, + ); + } + + /** + * Accept an appeal + * @param {appealID} appealID ID of appeal + * @param {decisionReason} decisionReason Reason for accepting an appeal + */ + async acceptAppeal( + appealID: string, + decisionReason: string, + options: AppealOptions = {}, + ) { + return await this.decideAppeal( + { appealID, decisionReason, status: 'accepted' }, + options, + ); + } + + /** + * Reject an appeal + * @param {appealID} appealID ID of appeal + * @param {decisionReason} decisionReason Reason for rejecting an appeal + */ + async rejectAppeal( + appealID: string, + decisionReason: string, + options: AppealOptions = {}, + ) { + return await this.decideAppeal( + { appealID, decisionReason, status: 'rejected' }, + options, + ); + } + + /** + * Get Appeal Item + * @param {string} appealID ID of the appeal to be fetched + */ + async getAppeal(appealID: string) { + return await this.client.get( + this.client.baseURL + '/api/v2/moderation/appeal/' + appealID, + ); + } + + /** + * Query appeals + * @param {Object} filterConditions Filter conditions for querying appeals + * @param {Object} sort Sort conditions for querying appeals + * @param {Object} options Pagination options for querying appeals + */ + async queryAppeals( + filterConditions: QueryAppealsFilters = {}, + sort: AppealsSort = [], + options: QueryAppealsPaginationOptions = {}, + ) { + return await this.client.post( + this.client.baseURL + '/api/v2/moderation/appeals', + { + filter: filterConditions, + sort: normalizeQuerySort(sort), + ...options, + }, + ); + } + /** * Upsert moderation config * @param {Object} config Moderation config to be upserted diff --git a/src/types.ts b/src/types.ts index a999bea76..9246ca0ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3581,6 +3581,19 @@ export type ModerationFlag = { moderation_payload_hash?: string; }; +export type AppealItem = { + attachments: string[]; + created_at: string; + updated_at: string; + decision_reason: string; + entity_id: string; + entity_type: string; + status: string; + text: string; + user: UserResponse; + id: string; +}; + export type ReviewQueueItem = { // eslint-disable-next-line @typescript-eslint/no-explicit-any actions_taken: any[]; @@ -3609,6 +3622,7 @@ export type ReviewQueueItem = { reviewed_at: string; status: string; updated_at: string; + appeal?: AppealItem; }; export type CustomCheckFlag = { @@ -3633,19 +3647,25 @@ export type SubmitActionOptions = { channel_ban_only?: boolean; reason?: string; timeout?: number; + decision_reason?: string; delete_messages?: MessageDeletionStrategy; }; delete_message?: { hard_delete?: boolean; + decision_reason?: string; }; delete_user?: { delete_conversation_channels?: boolean; hard_delete?: boolean; mark_messages_deleted?: boolean; + decision_reason?: string; + }; + restore?: { + decision_reason?: string; }; - restore?: {}; unban?: { channel_cid?: string; + decision_reason?: string; }; user_id?: string; }; @@ -3790,6 +3810,12 @@ export type ReviewQueueFilters = QueryFilters< date_range?: RequireOnlyOne<{ $eq?: string; // Format: "date1_date2" }>; + } & { + appeal?: boolean; + } & { + appeal_status?: RequireOnlyOne<{ + $eq?: 'submitted' | 'accepted' | 'rejected'; + }>; } >; @@ -3797,6 +3823,60 @@ export type ReviewQueueSort = | Sort> | Array>>; +export type AppealsSort = + | Sort> + | Array>>; + +export type QueryAppealsFilters = QueryFilters< + { + entity_type?: + | RequireOnlyOne, '$eq' | '$in'>> + | PrimitiveFilter; + } & { + created_at?: + | RequireOnlyOne< + Pick< + QueryFilter, + '$eq' | '$gt' | '$lt' | '$gte' | '$lte' + > + > + | PrimitiveFilter; + } & { + id?: + | RequireOnlyOne, '$eq' | '$in'>> + | PrimitiveFilter; + } & { + entity_id?: + | RequireOnlyOne, '$eq' | '$in'>> + | PrimitiveFilter; + } & { + status?: + | RequireOnlyOne, '$eq' | '$in'>> + | PrimitiveFilter; + } & { + updated_at?: + | RequireOnlyOne< + Pick< + QueryFilter, + '$eq' | '$gt' | '$lt' | '$gte' | '$lte' + > + > + | PrimitiveFilter; + } & { + text?: RequireOnlyOne<{ + $eq?: string; + }>; + } & { + decision_reason?: RequireOnlyOne<{ + $eq?: string; + }>; + } & { + review_queue_item_id?: RequireOnlyOne<{ + $eq?: string; + }>; + } +>; + export type QueryModerationConfigsSort = Array>; export type ReviewQueuePaginationOptions = Pager; @@ -3807,6 +3887,14 @@ export type ReviewQueueResponse = { prev?: string; }; +export type QueryAppealsResponse = APIResponse & { + items: AppealItem[]; + next?: string; + prev?: string; +}; + +export type QueryAppealsPaginationOptions = Pager; + export type ModerationConfig = { key: string; ai_image_config?: AIImageConfig; @@ -3839,6 +3927,14 @@ export type UpsertConfigResponse = { config: ModerationConfigResponse; }; +export type AppealResponse = APIResponse & { + appeal_id: string; +}; + +export type GetAppealResponse = APIResponse & { + item: AppealItem; +}; + // Moderation Rule Builder Types export type ModerationRule = { id: string; @@ -3861,6 +3957,20 @@ export type ModerationRuleRequest = { enabled: boolean; }; +export type AppealRequest = { + text: string; + entityID: string; + entityType: string; + attachments: string[]; +}; + +export type DecideAppealRequest = { + appealID: string; + status: 'accepted' | 'rejected'; + decisionReason: string; + channelCIDs?: string[]; +}; + export type RuleBuilderRule = { id: string; rule_type: 'user' | 'content'; @@ -3995,6 +4105,11 @@ export type ModerationFlagOptions = { user_id?: string; }; +export type AppealOptions = { + custom?: Record; + user_id?: string; +}; + export type ModerationMuteOptions = { timeout?: number; user_id?: string;