mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
fix(GuildAuditLogEntry)!: Fix some incorrect types and runtime logic (#10591)
BREAKING CHANGE: It also doesn't have a `options.channel_id`, so I stopped `.extra.channel` from being set to `{ id: undefined }`
BREAKING CHANGE: Fixed both types and runtime logic here, it previously created a broken `AutoModerationRule`
BREAKING CHANGE: Removed `Targets.GuildOnboarding`, it will fallback to `Targets.Unknown` and generate a placeholder `target` from the `changes`
---------
Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com>
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Denis-Adrian Cristea <didinele.dev@gmail.com>
Co-authored-by: Almeida <github@almeidx.dev>
This commit is contained in:
@@ -29,11 +29,12 @@ const Targets = {
|
|||||||
Thread: 'Thread',
|
Thread: 'Thread',
|
||||||
ApplicationCommand: 'ApplicationCommand',
|
ApplicationCommand: 'ApplicationCommand',
|
||||||
AutoModeration: 'AutoModeration',
|
AutoModeration: 'AutoModeration',
|
||||||
GuildOnboarding: 'GuildOnboarding',
|
|
||||||
GuildOnboardingPrompt: 'GuildOnboardingPrompt',
|
GuildOnboardingPrompt: 'GuildOnboardingPrompt',
|
||||||
|
SoundboardSound: 'SoundboardSound',
|
||||||
Unknown: 'Unknown',
|
Unknown: 'Unknown',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Add soundboard sounds when https://github.com/discordjs/discord.js/pull/10590 is merged
|
||||||
/**
|
/**
|
||||||
* The target of a guild audit log entry. It can be one of:
|
* The target of a guild audit log entry. It can be one of:
|
||||||
* * A guild
|
* * A guild
|
||||||
@@ -42,8 +43,7 @@ const Targets = {
|
|||||||
* * A role
|
* * A role
|
||||||
* * An invite
|
* * An invite
|
||||||
* * A webhook
|
* * A webhook
|
||||||
* * An emoji
|
* * A guild emoji
|
||||||
* * A message
|
|
||||||
* * An integration
|
* * An integration
|
||||||
* * A stage instance
|
* * A stage instance
|
||||||
* * A sticker
|
* * A sticker
|
||||||
@@ -54,7 +54,7 @@ const Targets = {
|
|||||||
* * A guild onboarding prompt
|
* * A guild onboarding prompt
|
||||||
* * An object with an id key if target was deleted or fake entity
|
* * An object with an id key if target was deleted or fake entity
|
||||||
* * An object where the keys represent either the new value or the old value
|
* * An object where the keys represent either the new value or the old value
|
||||||
* @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker|
|
* @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Integration|StageInstance|Sticker|
|
||||||
* GuildScheduledEvent|ApplicationCommand|AutoModerationRule|GuildOnboardingPrompt)} AuditLogEntryTarget
|
* GuildScheduledEvent|ApplicationCommand|AutoModerationRule|GuildOnboardingPrompt)} AuditLogEntryTarget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -82,9 +82,10 @@ const Targets = {
|
|||||||
* * Sticker
|
* * Sticker
|
||||||
* * Thread
|
* * Thread
|
||||||
* * GuildScheduledEvent
|
* * GuildScheduledEvent
|
||||||
* * ApplicationCommandPermission
|
* * ApplicationCommand
|
||||||
* * GuildOnboarding
|
|
||||||
* * GuildOnboardingPrompt
|
* * GuildOnboardingPrompt
|
||||||
|
* * SoundboardSound
|
||||||
|
* * AutoModeration
|
||||||
* * Unknown
|
* * Unknown
|
||||||
* @typedef {string} AuditLogTargetType
|
* @typedef {string} AuditLogTargetType
|
||||||
*/
|
*/
|
||||||
@@ -198,7 +199,6 @@ class GuildAuditLogsEntry {
|
|||||||
|
|
||||||
case AuditLogEvent.MemberMove:
|
case AuditLogEvent.MemberMove:
|
||||||
case AuditLogEvent.MessageDelete:
|
case AuditLogEvent.MessageDelete:
|
||||||
case AuditLogEvent.MessageBulkDelete:
|
|
||||||
this.extra = {
|
this.extra = {
|
||||||
channel: guild.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id },
|
channel: guild.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id },
|
||||||
count: Number(data.options.count),
|
count: Number(data.options.count),
|
||||||
@@ -213,6 +213,7 @@ class GuildAuditLogsEntry {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case AuditLogEvent.MessageBulkDelete:
|
||||||
case AuditLogEvent.MemberDisconnect:
|
case AuditLogEvent.MemberDisconnect:
|
||||||
this.extra = {
|
this.extra = {
|
||||||
count: Number(data.options.count),
|
count: Number(data.options.count),
|
||||||
@@ -364,10 +365,15 @@ class GuildAuditLogsEntry {
|
|||||||
data.action_type === AuditLogEvent.OnboardingPromptCreate
|
data.action_type === AuditLogEvent.OnboardingPromptCreate
|
||||||
? new GuildOnboardingPrompt(guild.client, changesReduce(this.changes, { id: data.target_id }), guild.id)
|
? new GuildOnboardingPrompt(guild.client, changesReduce(this.changes, { id: data.target_id }), guild.id)
|
||||||
: changesReduce(this.changes, { id: data.target_id });
|
: changesReduce(this.changes, { id: data.target_id });
|
||||||
} else if (targetType === Targets.GuildOnboarding) {
|
} else if (targetType === Targets.Role) {
|
||||||
this.target = changesReduce(this.changes, { id: data.target_id });
|
this.target = guild.roles.cache.get(data.target_id) ?? { id: data.target_id };
|
||||||
|
} else if (targetType === Targets.Emoji) {
|
||||||
|
this.target = guild.emojis.cache.get(data.target_id) ?? { id: data.target_id };
|
||||||
|
// TODO: Uncomment after https://github.com/discordjs/discord.js/pull/10590 is merged
|
||||||
|
// } else if (targetType === Targets.SoundboardSound) {
|
||||||
|
// this.target = guild.soundboardSounds.cache.get(data.target_id) ?? { id: data.target_id };
|
||||||
} else if (data.target_id) {
|
} else if (data.target_id) {
|
||||||
this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id };
|
this.target = { id: data.target_id };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,9 +397,10 @@ class GuildAuditLogsEntry {
|
|||||||
if (target < 110) return Targets.GuildScheduledEvent;
|
if (target < 110) return Targets.GuildScheduledEvent;
|
||||||
if (target < 120) return Targets.Thread;
|
if (target < 120) return Targets.Thread;
|
||||||
if (target < 130) return Targets.ApplicationCommand;
|
if (target < 130) return Targets.ApplicationCommand;
|
||||||
if (target >= 140 && target < 150) return Targets.AutoModeration;
|
if (target < 140) return Targets.SoundboardSound;
|
||||||
|
if (target < 143) return Targets.AutoModeration;
|
||||||
|
if (target < 146) return Targets.User;
|
||||||
if (target >= 163 && target <= 165) return Targets.GuildOnboardingPrompt;
|
if (target >= 163 && target <= 165) return Targets.GuildOnboardingPrompt;
|
||||||
if (target >= 160 && target < 170) return Targets.GuildOnboarding;
|
|
||||||
return Targets.Unknown;
|
return Targets.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,10 +426,9 @@ class GuildAuditLogsEntry {
|
|||||||
AuditLogEvent.StickerCreate,
|
AuditLogEvent.StickerCreate,
|
||||||
AuditLogEvent.GuildScheduledEventCreate,
|
AuditLogEvent.GuildScheduledEventCreate,
|
||||||
AuditLogEvent.ThreadCreate,
|
AuditLogEvent.ThreadCreate,
|
||||||
|
AuditLogEvent.SoundboardSoundCreate,
|
||||||
AuditLogEvent.AutoModerationRuleCreate,
|
AuditLogEvent.AutoModerationRuleCreate,
|
||||||
AuditLogEvent.AutoModerationBlockMessage,
|
|
||||||
AuditLogEvent.OnboardingPromptCreate,
|
AuditLogEvent.OnboardingPromptCreate,
|
||||||
AuditLogEvent.OnboardingCreate,
|
|
||||||
].includes(action)
|
].includes(action)
|
||||||
) {
|
) {
|
||||||
return 'Create';
|
return 'Create';
|
||||||
@@ -448,6 +454,7 @@ class GuildAuditLogsEntry {
|
|||||||
AuditLogEvent.StickerDelete,
|
AuditLogEvent.StickerDelete,
|
||||||
AuditLogEvent.GuildScheduledEventDelete,
|
AuditLogEvent.GuildScheduledEventDelete,
|
||||||
AuditLogEvent.ThreadDelete,
|
AuditLogEvent.ThreadDelete,
|
||||||
|
AuditLogEvent.SoundboardSoundDelete,
|
||||||
AuditLogEvent.AutoModerationRuleDelete,
|
AuditLogEvent.AutoModerationRuleDelete,
|
||||||
AuditLogEvent.OnboardingPromptDelete,
|
AuditLogEvent.OnboardingPromptDelete,
|
||||||
].includes(action)
|
].includes(action)
|
||||||
@@ -473,9 +480,12 @@ class GuildAuditLogsEntry {
|
|||||||
AuditLogEvent.GuildScheduledEventUpdate,
|
AuditLogEvent.GuildScheduledEventUpdate,
|
||||||
AuditLogEvent.ThreadUpdate,
|
AuditLogEvent.ThreadUpdate,
|
||||||
AuditLogEvent.ApplicationCommandPermissionUpdate,
|
AuditLogEvent.ApplicationCommandPermissionUpdate,
|
||||||
|
AuditLogEvent.SoundboardSoundUpdate,
|
||||||
AuditLogEvent.AutoModerationRuleUpdate,
|
AuditLogEvent.AutoModerationRuleUpdate,
|
||||||
|
AuditLogEvent.AutoModerationBlockMessage,
|
||||||
|
AuditLogEvent.AutoModerationFlagToChannel,
|
||||||
|
AuditLogEvent.AutoModerationUserCommunicationDisabled,
|
||||||
AuditLogEvent.OnboardingPromptUpdate,
|
AuditLogEvent.OnboardingPromptUpdate,
|
||||||
AuditLogEvent.OnboardingUpdate,
|
|
||||||
].includes(action)
|
].includes(action)
|
||||||
) {
|
) {
|
||||||
return 'Update';
|
return 'Update';
|
||||||
|
|||||||
34
packages/discord.js/typings/index.d.ts
vendored
34
packages/discord.js/typings/index.d.ts
vendored
@@ -1506,14 +1506,14 @@ export class GuildAuditLogsEntry<
|
|||||||
public get createdAt(): Date;
|
public get createdAt(): Date;
|
||||||
public get createdTimestamp(): number;
|
public get createdTimestamp(): number;
|
||||||
public executorId: Snowflake | null;
|
public executorId: Snowflake | null;
|
||||||
public executor: User | null;
|
public executor: User | PartialUser | null;
|
||||||
public extra: TAction extends keyof GuildAuditLogsEntryExtraField ? GuildAuditLogsEntryExtraField[TAction] : null;
|
public extra: TAction extends keyof GuildAuditLogsEntryExtraField ? GuildAuditLogsEntryExtraField[TAction] : null;
|
||||||
public id: Snowflake;
|
public id: Snowflake;
|
||||||
public reason: string | null;
|
public reason: string | null;
|
||||||
public targetId: Snowflake | null;
|
public targetId: Snowflake | null;
|
||||||
public target: TTargetType extends keyof GuildAuditLogsEntryTargetField<TAction>
|
public target: TTargetType extends keyof GuildAuditLogsEntryTargetField<TAction>
|
||||||
? GuildAuditLogsEntryTargetField<TAction>[TTargetType]
|
? GuildAuditLogsEntryTargetField<TAction>[TTargetType]
|
||||||
: Role | GuildEmoji | { id: Snowflake } | null;
|
: { id: Snowflake | undefined; [x: string]: unknown } | null;
|
||||||
public targetType: TTargetType;
|
public targetType: TTargetType;
|
||||||
public static actionType(action: AuditLogEvent): GuildAuditLogsActionType;
|
public static actionType(action: AuditLogEvent): GuildAuditLogsActionType;
|
||||||
public static targetType(target: AuditLogEvent): GuildAuditLogsTargetType;
|
public static targetType(target: AuditLogEvent): GuildAuditLogsTargetType;
|
||||||
@@ -5659,17 +5659,19 @@ interface GuildAuditLogsTypes {
|
|||||||
[AuditLogEvent.ThreadUpdate]: ['Thread', 'Update'];
|
[AuditLogEvent.ThreadUpdate]: ['Thread', 'Update'];
|
||||||
[AuditLogEvent.ThreadDelete]: ['Thread', 'Delete'];
|
[AuditLogEvent.ThreadDelete]: ['Thread', 'Delete'];
|
||||||
[AuditLogEvent.ApplicationCommandPermissionUpdate]: ['ApplicationCommand', 'Update'];
|
[AuditLogEvent.ApplicationCommandPermissionUpdate]: ['ApplicationCommand', 'Update'];
|
||||||
|
[AuditLogEvent.SoundboardSoundCreate]: ['SoundboardSound', 'Create'];
|
||||||
|
[AuditLogEvent.SoundboardSoundUpdate]: ['SoundboardSound', 'Update'];
|
||||||
|
[AuditLogEvent.SoundboardSoundDelete]: ['SoundboardSound', 'Delete'];
|
||||||
[AuditLogEvent.AutoModerationRuleCreate]: ['AutoModeration', 'Create'];
|
[AuditLogEvent.AutoModerationRuleCreate]: ['AutoModeration', 'Create'];
|
||||||
[AuditLogEvent.AutoModerationRuleUpdate]: ['AutoModeration', 'Update'];
|
[AuditLogEvent.AutoModerationRuleUpdate]: ['AutoModeration', 'Update'];
|
||||||
[AuditLogEvent.AutoModerationRuleDelete]: ['AutoModeration', 'Delete'];
|
[AuditLogEvent.AutoModerationRuleDelete]: ['AutoModeration', 'Delete'];
|
||||||
[AuditLogEvent.AutoModerationBlockMessage]: ['AutoModeration', 'Create'];
|
[AuditLogEvent.AutoModerationBlockMessage]: ['User', 'Update'];
|
||||||
[AuditLogEvent.AutoModerationFlagToChannel]: ['AutoModeration', 'Create'];
|
[AuditLogEvent.AutoModerationFlagToChannel]: ['User', 'Update'];
|
||||||
[AuditLogEvent.AutoModerationUserCommunicationDisabled]: ['AutoModeration', 'Create'];
|
[AuditLogEvent.AutoModerationUserCommunicationDisabled]: ['User', 'Update'];
|
||||||
[AuditLogEvent.OnboardingPromptCreate]: ['GuildOnboardingPrompt', 'Create'];
|
[AuditLogEvent.OnboardingPromptCreate]: ['GuildOnboardingPrompt', 'Create'];
|
||||||
[AuditLogEvent.OnboardingPromptUpdate]: ['GuildOnboardingPrompt', 'Update'];
|
[AuditLogEvent.OnboardingPromptUpdate]: ['GuildOnboardingPrompt', 'Update'];
|
||||||
[AuditLogEvent.OnboardingPromptDelete]: ['GuildOnboardingPrompt', 'Delete'];
|
[AuditLogEvent.OnboardingPromptDelete]: ['GuildOnboardingPrompt', 'Delete'];
|
||||||
[AuditLogEvent.OnboardingCreate]: ['GuildOnboarding', 'Create'];
|
// Never have a target: CreatorMonetizationRequestCreated, CreatorMonetizationTermsAccepted, OnboardingCreate, OnboardingUpdate, HomeSettingsCreate, HomeSettingsUpdate
|
||||||
[AuditLogEvent.OnboardingUpdate]: ['GuildOnboarding', 'Update'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GuildAuditLogsActionType = GuildAuditLogsTypes[keyof GuildAuditLogsTypes][1] | 'All';
|
export type GuildAuditLogsActionType = GuildAuditLogsTypes[keyof GuildAuditLogsTypes][1] | 'All';
|
||||||
@@ -5680,7 +5682,7 @@ export interface GuildAuditLogsEntryExtraField {
|
|||||||
[AuditLogEvent.MemberPrune]: { removed: number; days: number };
|
[AuditLogEvent.MemberPrune]: { removed: number; days: number };
|
||||||
[AuditLogEvent.MemberMove]: { channel: VoiceBasedChannel | { id: Snowflake }; count: number };
|
[AuditLogEvent.MemberMove]: { channel: VoiceBasedChannel | { id: Snowflake }; count: number };
|
||||||
[AuditLogEvent.MessageDelete]: { channel: GuildTextBasedChannel | { id: Snowflake }; count: number };
|
[AuditLogEvent.MessageDelete]: { channel: GuildTextBasedChannel | { id: Snowflake }; count: number };
|
||||||
[AuditLogEvent.MessageBulkDelete]: { channel: GuildTextBasedChannel | { id: Snowflake }; count: number };
|
[AuditLogEvent.MessageBulkDelete]: { count: number };
|
||||||
[AuditLogEvent.MessagePin]: { channel: GuildTextBasedChannel | { id: Snowflake }; messageId: Snowflake };
|
[AuditLogEvent.MessagePin]: { channel: GuildTextBasedChannel | { id: Snowflake }; messageId: Snowflake };
|
||||||
[AuditLogEvent.MessageUnpin]: { channel: GuildTextBasedChannel | { id: Snowflake }; messageId: Snowflake };
|
[AuditLogEvent.MessageUnpin]: { channel: GuildTextBasedChannel | { id: Snowflake }; messageId: Snowflake };
|
||||||
[AuditLogEvent.MemberDisconnect]: { count: number };
|
[AuditLogEvent.MemberDisconnect]: { count: number };
|
||||||
@@ -5721,13 +5723,13 @@ export interface GuildAuditLogsEntryExtraField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GuildAuditLogsEntryTargetField<TAction extends AuditLogEvent> {
|
export interface GuildAuditLogsEntryTargetField<TAction extends AuditLogEvent> {
|
||||||
User: User | null;
|
User: User | PartialUser | null;
|
||||||
Guild: Guild;
|
Guild: Guild;
|
||||||
Webhook: Webhook<WebhookType.ChannelFollower | WebhookType.Incoming>;
|
Webhook: Webhook<WebhookType.ChannelFollower | WebhookType.Incoming>;
|
||||||
Invite: Invite;
|
Invite: Invite;
|
||||||
Emoji: GuildEmoji;
|
Emoji: GuildEmoji | { id: Snowflake };
|
||||||
Role: Role;
|
Role: Role | { id: Snowflake };
|
||||||
Message: TAction extends AuditLogEvent.MessageBulkDelete ? Guild | { id: Snowflake } : User;
|
Message: TAction extends AuditLogEvent.MessageBulkDelete ? GuildTextBasedChannel | { id: Snowflake } : User | null;
|
||||||
Integration: Integration;
|
Integration: Integration;
|
||||||
Channel: NonThreadGuildBasedChannel | { id: Snowflake; [x: string]: unknown };
|
Channel: NonThreadGuildBasedChannel | { id: Snowflake; [x: string]: unknown };
|
||||||
Thread: AnyThreadChannel | { id: Snowflake; [x: string]: unknown };
|
Thread: AnyThreadChannel | { id: Snowflake; [x: string]: unknown };
|
||||||
@@ -5735,8 +5737,10 @@ export interface GuildAuditLogsEntryTargetField<TAction extends AuditLogEvent> {
|
|||||||
Sticker: Sticker;
|
Sticker: Sticker;
|
||||||
GuildScheduledEvent: GuildScheduledEvent;
|
GuildScheduledEvent: GuildScheduledEvent;
|
||||||
ApplicationCommand: ApplicationCommand | { id: Snowflake };
|
ApplicationCommand: ApplicationCommand | { id: Snowflake };
|
||||||
AutoModerationRule: AutoModerationRule;
|
AutoModeration: AutoModerationRule;
|
||||||
GuildOnboardingPrompt: GuildOnboardingPrompt;
|
GuildOnboardingPrompt: GuildOnboardingPrompt | { id: Snowflake; [x: string]: unknown };
|
||||||
|
// TODO: Update when https://github.com/discordjs/discord.js/pull/10590 is merged
|
||||||
|
SoundboardSound: { id: Snowflake };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GuildAuditLogsFetchOptions<Event extends GuildAuditLogsResolvable> {
|
export interface GuildAuditLogsFetchOptions<Event extends GuildAuditLogsResolvable> {
|
||||||
@@ -5752,7 +5756,7 @@ export type GuildAuditLogsResolvable = AuditLogEvent | null;
|
|||||||
export type GuildAuditLogsTargetType = GuildAuditLogsTypes[keyof GuildAuditLogsTypes][0] | 'Unknown';
|
export type GuildAuditLogsTargetType = GuildAuditLogsTypes[keyof GuildAuditLogsTypes][0] | 'Unknown';
|
||||||
|
|
||||||
export type GuildAuditLogsTargets = {
|
export type GuildAuditLogsTargets = {
|
||||||
[Key in GuildAuditLogsTargetType]: GuildAuditLogsTargetType;
|
[Key in GuildAuditLogsTargetType]: Key;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GuildBanResolvable = GuildBan | UserResolvable;
|
export type GuildBanResolvable = GuildBan | UserResolvable;
|
||||||
|
|||||||
@@ -2258,15 +2258,18 @@ expectType<Promise<{ channel: GuildTextBasedChannel | { id: Snowflake }; count:
|
|||||||
guild.fetchAuditLogs({ type: AuditLogEvent.MessageDelete }).then(al => al.entries.first()?.extra),
|
guild.fetchAuditLogs({ type: AuditLogEvent.MessageDelete }).then(al => al.entries.first()?.extra),
|
||||||
);
|
);
|
||||||
|
|
||||||
expectType<Promise<User | null | undefined>>(
|
expectType<Promise<User | PartialUser | null | undefined>>(
|
||||||
guild.fetchAuditLogs({ type: AuditLogEvent.MemberKick }).then(al => al.entries.first()?.target),
|
guild.fetchAuditLogs({ type: AuditLogEvent.MemberKick }).then(al => al.entries.first()?.target),
|
||||||
);
|
);
|
||||||
expectType<Promise<StageInstance | undefined>>(
|
expectType<Promise<StageInstance | undefined>>(
|
||||||
guild.fetchAuditLogs({ type: AuditLogEvent.StageInstanceCreate }).then(al => al.entries.first()?.target),
|
guild.fetchAuditLogs({ type: AuditLogEvent.StageInstanceCreate }).then(al => al.entries.first()?.target),
|
||||||
);
|
);
|
||||||
expectType<Promise<User | undefined>>(
|
expectType<Promise<User | null | undefined>>(
|
||||||
guild.fetchAuditLogs({ type: AuditLogEvent.MessageDelete }).then(al => al.entries.first()?.target),
|
guild.fetchAuditLogs({ type: AuditLogEvent.MessageDelete }).then(al => al.entries.first()?.target),
|
||||||
);
|
);
|
||||||
|
expectType<Promise<GuildTextBasedChannel | { id: string } | undefined>>(
|
||||||
|
guild.fetchAuditLogs({ type: AuditLogEvent.MessageBulkDelete }).then(al => al.entries.first()?.target),
|
||||||
|
);
|
||||||
|
|
||||||
declare const AuditLogChange: AuditLogChange;
|
declare const AuditLogChange: AuditLogChange;
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
|
|||||||
Reference in New Issue
Block a user