From b6b45704829c1439557409a5e1935b856e76326e Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 6 Dec 2021 08:57:12 +0100 Subject: [PATCH] types: improve audit logs' typings (#6647) Co-authored-by: Vlad Frangu Co-authored-by: Noel Co-authored-by: Suneet Tipirneni <77477100+suneettipirneni@users.noreply.github.com> --- typings/index.d.ts | 263 ++++++++++++++++++++++++++-------------- typings/index.test-d.ts | 86 +++++++++++++ 2 files changed, 258 insertions(+), 91 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 04ba5c1f8..4c19c2952 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -890,7 +890,9 @@ export class Guild extends AnonymousGuild { public edit(data: GuildEditData, reason?: string): Promise; public editWelcomeScreen(data: WelcomeScreenEditData): Promise; public equals(guild: Guild): boolean; - public fetchAuditLogs(options?: GuildAuditLogsFetchOptions): Promise; + public fetchAuditLogs( + options?: GuildAuditLogsFetchOptions, + ): Promise>; public fetchIntegrations(): Promise>; public fetchOwner(options?: BaseFetchOptions): Promise; public fetchPreview(): Promise; @@ -935,12 +937,12 @@ export class Guild extends AnonymousGuild { public toJSON(): unknown; } -export class GuildAuditLogs { +export class GuildAuditLogs { private constructor(guild: Guild, data: RawGuildAuditLogData); private webhooks: Collection; private integrations: Collection; - public entries: Collection; + public entries: Collection>; public static Actions: GuildAuditLogsActions; public static Targets: GuildAuditLogsTargets; @@ -951,33 +953,34 @@ export class GuildAuditLogs { public toJSON(): unknown; } -export class GuildAuditLogsEntry { +export class GuildAuditLogsEntry< + TActionRaw extends GuildAuditLogsResolvable = 'ALL', + TAction = TActionRaw extends keyof GuildAuditLogsIds + ? GuildAuditLogsIds[TActionRaw] + : TActionRaw extends null + ? 'ALL' + : TActionRaw, + TActionType extends GuildAuditLogsActionType = TAction extends keyof GuildAuditLogsTypes + ? GuildAuditLogsTypes[TAction][1] + : 'ALL', + TTargetType extends GuildAuditLogsTarget = TAction extends keyof GuildAuditLogsTypes + ? GuildAuditLogsTypes[TAction][0] + : 'UNKNOWN', +> { private constructor(logs: GuildAuditLogs, guild: Guild, data: RawGuildAuditLogEntryData); - public action: GuildAuditLogsAction; - public actionType: GuildAuditLogsActionType; + public action: TAction; + public actionType: TActionType; public changes: AuditLogChange[] | null; public readonly createdAt: Date; public readonly createdTimestamp: number; public executor: User | null; - public extra: unknown | Role | GuildMember | null; + public extra: TAction extends keyof GuildAuditLogsEntryExtraField ? GuildAuditLogsEntryExtraField[TAction] : null; public id: Snowflake; public reason: string | null; - public target: - | Guild - | GuildChannel - | User - | Role - | GuildEmoji - | Invite - | Webhook - | Message - | Integration - | StageInstance - | Sticker - | ThreadChannel - | { id: Snowflake } - | null; - public targetType: GuildAuditLogsTarget; + public target: TTargetType extends keyof GuildAuditLogsEntryTargetField + ? GuildAuditLogsEntryTargetField[TTargetType] + : Role | GuildEmoji | { id: Snowflake } | null; + public targetType: TTargetType; public toJSON(): unknown; } @@ -4178,83 +4181,161 @@ export interface GuildApplicationCommandPermissionData { permissions: ApplicationCommandPermissionData[]; } -export type GuildAuditLogsAction = keyof GuildAuditLogsActions; - -export interface GuildAuditLogsActions { - ALL?: null; - GUILD_UPDATE?: number; - CHANNEL_CREATE?: number; - CHANNEL_UPDATE?: number; - CHANNEL_DELETE?: number; - CHANNEL_OVERWRITE_CREATE?: number; - CHANNEL_OVERWRITE_UPDATE?: number; - CHANNEL_OVERWRITE_DELETE?: number; - MEMBER_KICK?: number; - MEMBER_PRUNE?: number; - MEMBER_BAN_ADD?: number; - MEMBER_BAN_REMOVE?: number; - MEMBER_UPDATE?: number; - MEMBER_ROLE_UPDATE?: number; - MEMBER_MOVE?: number; - MEMBER_DISCONNECT?: number; - BOT_ADD?: number; - ROLE_CREATE?: number; - ROLE_UPDATE?: number; - ROLE_DELETE?: number; - INVITE_CREATE?: number; - INVITE_UPDATE?: number; - INVITE_DELETE?: number; - WEBHOOK_CREATE?: number; - WEBHOOK_UPDATE?: number; - WEBHOOK_DELETE?: number; - EMOJI_CREATE?: number; - EMOJI_UPDATE?: number; - EMOJI_DELETE?: number; - MESSAGE_DELETE?: number; - MESSAGE_BULK_DELETE?: number; - MESSAGE_PIN?: number; - MESSAGE_UNPIN?: number; - INTEGRATION_CREATE?: number; - INTEGRATION_UPDATE?: number; - INTEGRATION_DELETE?: number; - STAGE_INSTANCE_CREATE?: number; - STAGE_INSTANCE_UPDATE?: number; - STAGE_INSTANCE_DELETE?: number; - STICKER_CREATE?: number; - STICKER_UPDATE?: number; - STICKER_DELETE?: number; - THREAD_CREATE?: number; - THREAD_UPDATE?: number; - THREAD_DELETE?: number; +interface GuildAuditLogsTypes { + GUILD_UPDATE: ['GUILD', 'UPDATE']; + CHANNEL_CREATE: ['CHANNEL', 'CREATE']; + CHANNEL_UPDATE: ['CHANNEL', 'UPDATE']; + CHANNEL_DELETE: ['CHANNEL', 'DELETE']; + CHANNEL_OVERWRITE_CREATE: ['CHANNEL', 'CREATE']; + CHANNEL_OVERWRITE_UPDATE: ['CHANNEL', 'UPDATE']; + CHANNEL_OVERWRITE_DELETE: ['CHANNEL', 'DELETE']; + MEMBER_KICK: ['USER', 'DELETE']; + MEMBER_PRUNE: ['USER', 'DELETE']; + MEMBER_BAN_ADD: ['USER', 'DELETE']; + MEMBER_BAN_REMOVE: ['USER', 'CREATE']; + MEMBER_UPDATE: ['USER', 'UPDATE']; + MEMBER_ROLE_UPDATE: ['USER', 'UPDATE']; + MEMBER_MOVE: ['USER', 'UPDATE']; + MEMBER_DISCONNECT: ['USER', 'DELETE']; + BOT_ADD: ['USER', 'CREATE']; + ROLE_CREATE: ['ROLE', 'CREATE']; + ROLE_UPDATE: ['ROLE', 'UPDATE']; + ROLE_DELETE: ['ROLE', 'DELETE']; + INVITE_CREATE: ['INVITE', 'CREATE']; + INVITE_UPDATE: ['INVITE', 'UPDATE']; + INVITE_DELETE: ['INVITE', 'DELETE']; + WEBHOOK_CREATE: ['WEBHOOK', 'CREATE']; + WEBHOOK_UPDATE: ['WEBHOOK', 'UPDATE']; + WEBHOOK_DELETE: ['WEBHOOK', 'DELETE']; + EMOJI_CREATE: ['EMOJI', 'CREATE']; + EMOJI_UPDATE: ['EMOJI', 'UPDATE']; + EMOJI_DELETE: ['EMOJI', 'DELETE']; + MESSAGE_DELETE: ['MESSAGE', 'DELETE']; + MESSAGE_BULK_DELETE: ['MESSAGE', 'DELETE']; + MESSAGE_PIN: ['MESSAGE', 'CREATE']; + MESSAGE_UNPIN: ['MESSAGE', 'DELETE']; + INTEGRATION_CREATE: ['INTEGRATION', 'CREATE']; + INTEGRATION_UPDATE: ['INTEGRATION', 'UPDATE']; + INTEGRATION_DELETE: ['INTEGRATION', 'DELETE']; + STAGE_INSTANCE_CREATE: ['STAGE_INSTANCE', 'CREATE']; + STAGE_INSTANCE_UPDATE: ['STAGE_INSTANCE', 'UPDATE']; + STAGE_INSTANCE_DELETE: ['STAGE_INSTANCE', 'DELETE']; + STICKER_CREATE: ['STICKER', 'CREATE']; + STICKER_UPDATE: ['STICKER', 'UPDATE']; + STICKER_DELETE: ['STICKER', 'DELETE']; + THREAD_CREATE: ['THREAD', 'CREATE']; + THREAD_UPDATE: ['THREAD', 'UPDATE']; + THREAD_DELETE: ['THREAD', 'DELETE']; } -export type GuildAuditLogsActionType = 'CREATE' | 'DELETE' | 'UPDATE' | 'ALL'; +export interface GuildAuditLogsIds { + 1: 'GUILD_UPDATE'; + 10: 'CHANNEL_CREATE'; + 11: 'CHANNEL_UPDATE'; + 12: 'CHANNEL_DELETE'; + 13: 'CHANNEL_OVERWRITE_CREATE'; + 14: 'CHANNEL_OVERWRITE_UPDATE'; + 15: 'CHANNEL_OVERWRITE_DELETE'; + 20: 'MEMBER_KICK'; + 21: 'MEMBER_PRUNE'; + 22: 'MEMBER_BAN_ADD'; + 23: 'MEMBER_BAN_REMOVE'; + 24: 'MEMBER_UPDATE'; + 25: 'MEMBER_ROLE_UPDATE'; + 26: 'MEMBER_MOVE'; + 27: 'MEMBER_DISCONNECT'; + 28: 'BOT_ADD'; + 30: 'ROLE_CREATE'; + 31: 'ROLE_UPDATE'; + 32: 'ROLE_DELETE'; + 40: 'INVITE_CREATE'; + 41: 'INVITE_UPDATE'; + 42: 'INVITE_DELETE'; + 50: 'WEBHOOK_CREATE'; + 51: 'WEBHOOK_UPDATE'; + 52: 'WEBHOOK_DELETE'; + 60: 'EMOJI_CREATE'; + 61: 'EMOJI_UPDATE'; + 62: 'EMOJI_DELETE'; + 72: 'MESSAGE_DELETE'; + 73: 'MESSAGE_BULK_DELETE'; + 74: 'MESSAGE_PIN'; + 75: 'MESSAGE_UNPIN'; + 80: 'INTEGRATION_CREATE'; + 81: 'INTEGRATION_UPDATE'; + 82: 'INTEGRATION_DELETE'; + 83: 'STAGE_INSTANCE_CREATE'; + 84: 'STAGE_INSTANCE_UPDATE'; + 85: 'STAGE_INSTANCE_DELETE'; + 90: 'STICKER_CREATE'; + 91: 'STICKER_UPDATE'; + 92: 'STICKER_DELETE'; + 110: 'THREAD_CREATE'; + 111: 'THREAD_UPDATE'; + 112: 'THREAD_DELETE'; +} -export interface GuildAuditLogsFetchOptions { +export type GuildAuditLogsActions = { [Key in keyof GuildAuditLogsIds as GuildAuditLogsIds[Key]]: Key } & { ALL: null }; + +export type GuildAuditLogsAction = keyof GuildAuditLogsActions; + +export type GuildAuditLogsActionType = GuildAuditLogsTypes[keyof GuildAuditLogsTypes][1] | 'ALL'; + +export interface GuildAuditLogsEntryExtraField { + MEMBER_PRUNE: { removed: number; days: number }; + MEMBER_MOVE: { channel: GuildChannel | { id: Snowflake }; count: number }; + MESSAGE_DELETE: { channel: GuildChannel | { id: Snowflake }; count: number }; + MESSAGE_BULK_DELETE: { channel: GuildChannel | { id: Snowflake }; count: number }; + MESSAGE_PIN: { channel: GuildChannel | { id: Snowflake }; messageId: Snowflake }; + MESSAGE_UNPIN: { channel: GuildChannel | { id: Snowflake }; messageId: Snowflake }; + MEMBER_DISCONNECT: { count: number }; + CHANNEL_OVERWRITE_CREATE: + | Role + | GuildMember + | { id: Snowflake; name: string; type: OverwriteTypes.role } + | { id: Snowflake; type: OverwriteTypes.member }; + CHANNEL_OVERWRITE_UPDATE: + | Role + | GuildMember + | { id: Snowflake; name: string; type: OverwriteTypes.role } + | { id: Snowflake; type: OverwriteTypes.member }; + CHANNEL_OVERWRITE_DELETE: + | Role + | GuildMember + | { id: Snowflake; name: string; type: OverwriteTypes.role } + | { id: Snowflake; type: OverwriteTypes.member }; + STAGE_INSTANCE_CREATE: GuildChannel | { id: Snowflake }; + STAGE_INSTANCE_DELETE: GuildChannel | { id: Snowflake }; + STAGE_INSTANCE_UPDATE: GuildChannel | { id: Snowflake }; +} + +export interface GuildAuditLogsEntryTargetField { + USER: User | null; + GUILD: Guild; + WEBHOOK: Webhook; + INVITE: Invite | { [x: string]: unknown }; + MESSAGE: TActionType extends 'MESSAGE_BULK_DELETE' ? Guild | { id: Snowflake } : User; + INTEGRATION: Integration; + CHANNEL: GuildChannel | ThreadChannel | { id: Snowflake; [x: string]: unknown }; + THREAD: GuildChannel | ThreadChannel | { id: Snowflake; [x: string]: unknown }; + STAGE_INSTANCE: StageInstance; + STICKER: Sticker; +} + +export interface GuildAuditLogsFetchOptions { before?: Snowflake | GuildAuditLogsEntry; limit?: number; user?: UserResolvable; - type?: GuildAuditLogsAction | number; + type?: T; } -export type GuildAuditLogsTarget = keyof GuildAuditLogsTargets; +export type GuildAuditLogsResolvable = keyof GuildAuditLogsIds | GuildAuditLogsAction | null; -export interface GuildAuditLogsTargets { - ALL?: string; - GUILD?: string; - CHANNEL?: string; - USER?: string; - ROLE?: string; - INVITE?: string; - WEBHOOK?: string; - EMOJI?: string; - MESSAGE?: string; - INTEGRATION?: string; - STAGE_INSTANCE?: string; - STICKER?: string; - THREAD?: string; - UNKNOWN?: string; -} +export type GuildAuditLogsTarget = GuildAuditLogsTypes[keyof GuildAuditLogsTypes][0] | 'ALL' | 'UNKNOWN'; + +export type GuildAuditLogsTargets = { + [key in GuildAuditLogsTarget]?: string; +}; export type GuildBanResolvable = GuildBan | UserResolvable; diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index 9eb72d8e9..754776f77 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -8,6 +8,7 @@ import type { APIInteractionDataResolvedChannel, APIRole, } from 'discord-api-types/v9'; +import { AuditLogEvent } from 'discord-api-types/v9'; import { ApplicationCommand, ApplicationCommandData, @@ -79,6 +80,9 @@ import { Shard, WebSocketShard, Collector, + GuildAuditLogsEntry, + GuildAuditLogs, + StageInstance, } from '.'; import type { ApplicationCommandOptionTypes } from './enums'; import { expectAssignable, expectDeprecated, expectNotAssignable, expectNotType, expectType } from 'tsd'; @@ -1062,3 +1066,85 @@ collector.on('end', (collection, reason) => { }); expectType>(shard.eval(c => c.readyTimestamp)); + +// Test audit logs +declare const guild: Guild; +expectType>>(guild.fetchAuditLogs({ type: 'MEMBER_KICK' })); +expectAssignable>>( + guild.fetchAuditLogs({ type: GuildAuditLogs.Actions.MEMBER_KICK }), +); +expectType>>(guild.fetchAuditLogs({ type: AuditLogEvent.MemberKick })); + +expectType>>(guild.fetchAuditLogs({ type: 'CHANNEL_CREATE' })); +expectAssignable>>( + guild.fetchAuditLogs({ type: GuildAuditLogs.Actions.CHANNEL_CREATE }), +); +expectType>>( + guild.fetchAuditLogs({ type: AuditLogEvent.ChannelCreate }), +); + +expectType>>(guild.fetchAuditLogs({ type: 'INTEGRATION_UPDATE' })); +expectAssignable>>( + guild.fetchAuditLogs({ type: GuildAuditLogs.Actions.INTEGRATION_UPDATE }), +); +expectType>>( + guild.fetchAuditLogs({ type: AuditLogEvent.IntegrationUpdate }), +); + +expectType>>(guild.fetchAuditLogs({ type: 'ALL' })); +expectType>>(guild.fetchAuditLogs({ type: GuildAuditLogs.Actions.ALL })); +expectType>>(guild.fetchAuditLogs()); + +expectType | undefined>>( + guild.fetchAuditLogs({ type: 'MEMBER_KICK' }).then(al => al.entries.first()), +); +expectType | undefined>>( + guild.fetchAuditLogs({ type: GuildAuditLogs.Actions.MEMBER_KICK }).then(al => al.entries.first()), +); +expectAssignable | undefined>>( + guild.fetchAuditLogs({ type: AuditLogEvent.MemberKick }).then(al => al.entries.first()), +); + +expectType | undefined>>( + guild.fetchAuditLogs({ type: 'ALL' }).then(al => al.entries.first()), +); +expectType | undefined>>( + guild.fetchAuditLogs({ type: GuildAuditLogs.Actions.ALL }).then(al => al.entries.first()), +); +expectType | undefined>>( + guild.fetchAuditLogs({ type: null }).then(al => al.entries.first()), +); +expectType | undefined>>( + guild.fetchAuditLogs().then(al => al.entries.first()), +); + +expectType>( + guild.fetchAuditLogs({ type: 'MEMBER_KICK' }).then(al => al.entries.first()?.extra), +); +expectType>( + guild.fetchAuditLogs({ type: AuditLogEvent.MemberKick }).then(al => al.entries.first()?.extra), +); +expectType>( + guild.fetchAuditLogs({ type: 'STAGE_INSTANCE_CREATE' }).then(al => al.entries.first()?.extra), +); +expectType>( + guild.fetchAuditLogs({ type: 'MESSAGE_DELETE' }).then(al => al.entries.first()?.extra), +); + +expectType>( + guild.fetchAuditLogs({ type: 'MEMBER_KICK' }).then(al => al.entries.first()?.target), +); +expectType>( + guild.fetchAuditLogs({ type: AuditLogEvent.MemberKick }).then(al => al.entries.first()?.target), +); +expectType>( + guild.fetchAuditLogs({ type: 'STAGE_INSTANCE_CREATE' }).then(al => al.entries.first()?.target), +); +expectType>( + guild.fetchAuditLogs({ type: 'MESSAGE_DELETE' }).then(al => al.entries.first()?.target), +); + +expectType>( + // @ts-expect-error Invalid audit log ID + guild.fetchAuditLogs({ type: 2000 }).then(al => al.entries.first()?.target), +);