2024-07-26 15:26:42 +02:00
|
|
|
// Galactic - Discord moderation bot
|
|
|
|
// Copyright (C) 2024 Navy.gif
|
|
|
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
2023-12-05 16:47:54 +01:00
|
|
|
import { LoggerClientOptions } from '@navy.gif/logger';
|
|
|
|
import {
|
|
|
|
Partials,
|
|
|
|
GatewayIntentBits,
|
|
|
|
Snowflake,
|
|
|
|
User,
|
|
|
|
ApplicationCommandType,
|
|
|
|
GuildMember,
|
|
|
|
Role,
|
|
|
|
BaseChannel,
|
|
|
|
PermissionsString,
|
|
|
|
Channel,
|
|
|
|
APIEmbed,
|
|
|
|
OverwriteType,
|
|
|
|
Awaitable,
|
|
|
|
Message,
|
|
|
|
If,
|
|
|
|
GuildBan,
|
|
|
|
ThreadChannel,
|
|
|
|
APIEmbedField,
|
|
|
|
VoiceState,
|
2023-12-08 20:20:36 +01:00
|
|
|
Invite,
|
|
|
|
GuildTextBasedChannel,
|
2023-12-05 16:47:54 +01:00
|
|
|
} from 'discord.js';
|
|
|
|
import { InvokerWrapper, MemberWrapper, UserWrapper } from '../src/client/components/wrappers/index.js';
|
|
|
|
import GuildWrapper from '../src/client/components/wrappers/GuildWrapper.js';
|
|
|
|
import DiscordClient from '../src/client/DiscordClient.js';
|
|
|
|
import { CommandOption, Inhibitor } from '../src/client/interfaces/index.js';
|
|
|
|
import { MuteType } from './Settings.js';
|
|
|
|
import { ClientEvents } from './Events.js';
|
|
|
|
import { FilterResult } from './Utils.js';
|
|
|
|
import { GuildSettingTypes } from './Guild.js';
|
|
|
|
import { StorageManagerOptions } from './Storage.js';
|
|
|
|
|
|
|
|
export type ManagerEvalOptions = {
|
|
|
|
context?: object,
|
|
|
|
debug?: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type DiscordStruct = {
|
|
|
|
id: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export type FormatParams = {
|
2023-12-05 21:22:00 +01:00
|
|
|
[key: string]: string | number | boolean | undefined | null
|
2023-12-05 16:47:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export type FormatOpts = {
|
|
|
|
code?: boolean,
|
|
|
|
language?: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ClientOptions = {
|
|
|
|
rootDir: string,
|
|
|
|
prefix?: string,
|
|
|
|
developers?: string[],
|
|
|
|
developmentMode?: boolean,
|
|
|
|
invite: string,
|
|
|
|
slashCommands?: {
|
|
|
|
developerGuilds: string[]
|
|
|
|
},
|
|
|
|
libraryOptions: {
|
|
|
|
partials?: Partials[],
|
|
|
|
intents: GatewayIntentBits[],
|
|
|
|
invalidRequestWarningInterval?: number
|
|
|
|
},
|
|
|
|
storage: StorageManagerOptions,
|
2023-12-05 19:31:53 +01:00
|
|
|
logger: LoggerClientOptions,
|
|
|
|
version: string
|
2023-12-05 16:47:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export type ComponentType = 'command' | 'module' | 'observer' | 'inhibitor' | 'setting'
|
|
|
|
export type ComponentOptions = {
|
|
|
|
id: string,
|
|
|
|
type: ComponentType,
|
2023-12-05 19:31:53 +01:00
|
|
|
moduleName?: string,
|
2023-12-05 16:47:54 +01:00
|
|
|
guarded?: boolean,
|
|
|
|
disabled?: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ModuleOptions = {
|
|
|
|
name: string
|
|
|
|
}
|
|
|
|
|
|
|
|
// type ComponentUpdate = (opts: { component: Component, type: 'ENABLE' | 'DISABLE' }) => void;
|
|
|
|
// export type EventHook = (...args: ClientEvents[keyof ClientEvents]) => Promise<void> | void // ((...args: unknown[]) => void) | ComponentUpdate;
|
|
|
|
export type EventHook<K extends keyof ClientEvents> = (...args: ClientEvents[K]) => Awaitable<unknown>;
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
export type GenericEventHook = (...args: any[]) => Awaitable<unknown>;
|
|
|
|
|
|
|
|
export type Hooks = [keyof ClientEvents, GenericEventHook][];
|
|
|
|
export type ObserverOptions = {
|
|
|
|
name?: string,
|
|
|
|
priority?: number,
|
|
|
|
hooks?: Hooks,
|
|
|
|
} & Partial<ComponentOptions>
|
|
|
|
|
|
|
|
export type InhibitorOptions = {
|
|
|
|
name?: string,
|
|
|
|
guild?: boolean
|
|
|
|
priority?: number,
|
|
|
|
silent?: boolean
|
|
|
|
} & Partial<ComponentOptions>
|
|
|
|
|
|
|
|
export type InhibitorResponse<T extends boolean = boolean> = {
|
|
|
|
error: T,
|
|
|
|
inhibitor: Inhibitor,
|
|
|
|
params: If<T, FormatParams>,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type InfractionType =
|
|
|
|
| 'ADDROLE'
|
|
|
|
| 'BAN'
|
|
|
|
| 'KICK'
|
|
|
|
| 'LOCKDOWN'
|
|
|
|
| 'MUTE'
|
|
|
|
| 'NICKNAME'
|
|
|
|
| 'NOTE'
|
|
|
|
| 'PRUNE'
|
|
|
|
| 'REMOVEROLE'
|
|
|
|
| 'SLOWMODE'
|
|
|
|
| 'SOFTBAN'
|
|
|
|
| 'UNBAN'
|
|
|
|
| 'UNLOCKDOWN'
|
|
|
|
| 'UNMUTE'
|
|
|
|
| 'VCKICK'
|
|
|
|
| 'VCMUTE'
|
|
|
|
| 'VCUNMUTE'
|
|
|
|
| 'VCBAN'
|
|
|
|
| 'VCUNBAN'
|
|
|
|
| 'WARN'
|
|
|
|
| 'DELETE';
|
|
|
|
|
|
|
|
export enum CommandOptionType {
|
2023-12-05 21:49:44 +01:00
|
|
|
SUB_COMMAND = 'SUB_COMMAND',
|
|
|
|
SUB_COMMAND_GROUP = 'SUB_COMMAND_GROUP',
|
|
|
|
ROLES = 'ROLES', // Note plurality, strings can parse users, roles, and channels.
|
|
|
|
MEMBERS = 'MEMBERS',
|
|
|
|
USERS = 'USERS',
|
|
|
|
CHANNELS = 'CHANNELS',
|
|
|
|
TEXT_CHANNELS = 'TEXT_CHANNELS',
|
|
|
|
VOICE_CHANNELS = 'VOICE_CHANNELS',
|
|
|
|
TIME = 'TIME', // timestring
|
|
|
|
DATE = 'DATE',
|
|
|
|
COMPONENT = 'COMPONENT',
|
|
|
|
COMPONENTS = 'COMPONENTS',
|
|
|
|
COMMAND = 'COMMAND',
|
|
|
|
COMMANDS = 'COMMANDS',
|
|
|
|
MODULE = 'MODULE',
|
|
|
|
STRING = 'STRING',
|
|
|
|
INTEGER = 'INTEGER',
|
|
|
|
BOOLEAN = 'BOOLEAN',
|
|
|
|
MEMBER = 'MEMBER',
|
|
|
|
USER = 'USER',
|
|
|
|
TEXT_CHANNEL = 'TEXT_CHANNEL',
|
|
|
|
VOICE_CHANNEL = 'VOICE_CHANNEL',
|
|
|
|
CHANNEL = 'CHANNEL',
|
|
|
|
ROLE = 'ROLE',
|
|
|
|
MENTIONABLE = 'MENTIONABLE',
|
|
|
|
NUMBER = 'NUMBER',
|
|
|
|
FLOAT = 'FLOAT',
|
|
|
|
POINTS = 'POINTS'
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum DiscordCommandOptionType {
|
2023-12-05 16:47:54 +01:00
|
|
|
SUB_COMMAND = 1,
|
|
|
|
SUB_COMMAND_GROUP = 2,
|
|
|
|
ROLES = 3, // Note plurality, strings can parse users, roles, and channels.
|
|
|
|
MEMBERS = 3,
|
|
|
|
USERS = 3,
|
|
|
|
CHANNELS = 3,
|
|
|
|
TEXT_CHANNELS = 3,
|
|
|
|
VOICE_CHANNELS = 3,
|
|
|
|
TIME = 3, // timestring
|
|
|
|
DATE = 3,
|
|
|
|
COMPONENT = 3,
|
|
|
|
COMPONENTS = 3,
|
|
|
|
COMMAND = 3,
|
|
|
|
COMMANDS = 3,
|
|
|
|
MODULE = 3,
|
|
|
|
STRING = 3,
|
|
|
|
INTEGER = 4,
|
|
|
|
BOOLEAN = 5,
|
|
|
|
MEMBER = 6,
|
|
|
|
USER = 6,
|
|
|
|
TEXT_CHANNEL = 7,
|
|
|
|
VOICE_CHANNEL = 7,
|
|
|
|
CHANNEL = 7,
|
|
|
|
ROLE = 8,
|
|
|
|
MENTIONABLE = 9,
|
|
|
|
NUMBER = 10,
|
|
|
|
FLOAT = 10,
|
|
|
|
POINTS = 4
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum ChannelTypes {
|
|
|
|
TEXT_CHANNEL = 0,
|
|
|
|
VOICE_CHANNEL = 3
|
|
|
|
}
|
|
|
|
|
|
|
|
type CommandOptionChoice = {
|
|
|
|
name: string,
|
|
|
|
value: unknown
|
|
|
|
}
|
|
|
|
export type CommandOptionChoices = CommandOptionChoice[]
|
|
|
|
export type DependsOnMode = 'OR' | 'AND'
|
|
|
|
|
|
|
|
export type RawCommandOption = {
|
|
|
|
name: string,
|
|
|
|
type: CommandOptionType,
|
|
|
|
options: RawCommandOption[]
|
|
|
|
}
|
|
|
|
|
|
|
|
export type CommandOptionShape = {
|
|
|
|
name: string,
|
|
|
|
description: string,
|
|
|
|
type: number,
|
|
|
|
required: boolean,
|
|
|
|
choices: CommandOptionChoice[],
|
|
|
|
options: CommandOptionShape[],
|
|
|
|
min_value?: number,
|
|
|
|
max_value?: number,
|
|
|
|
channel_types: number[] | null,
|
|
|
|
autocomplete: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type CommandOptionParams = {
|
|
|
|
client?: DiscordClient,
|
|
|
|
guild?: GuildWrapper | null,
|
|
|
|
|
|
|
|
name: string | string[],
|
2023-12-08 19:23:22 +01:00
|
|
|
aliases?: string[]
|
2023-12-05 16:47:54 +01:00
|
|
|
description?: string | string[]
|
|
|
|
type?: CommandOptionType | CommandOptionType[],
|
2023-12-08 19:23:22 +01:00
|
|
|
|
2023-12-05 16:47:54 +01:00
|
|
|
required?: boolean,
|
|
|
|
autocomplete?: boolean,
|
|
|
|
strict?: boolean,
|
|
|
|
showUsage?: boolean
|
|
|
|
|
|
|
|
choices?: CommandOptionChoices,
|
|
|
|
options?: CommandOptionParams[],
|
|
|
|
|
|
|
|
dependsOn?: string[],
|
|
|
|
dependsOnMode?: DependsOnMode,
|
2023-12-08 19:23:22 +01:00
|
|
|
|
2023-12-05 16:47:54 +01:00
|
|
|
minimum?: number,
|
|
|
|
maximum?: number,
|
|
|
|
// min?: number,
|
|
|
|
// max?: number,
|
|
|
|
|
|
|
|
slashOption?: boolean,
|
|
|
|
flag?: boolean,
|
|
|
|
valueOptional?: boolean,
|
|
|
|
valueAsAlias?: boolean,
|
|
|
|
|
|
|
|
value?: unknown,
|
|
|
|
defaultValue?: unknown,
|
|
|
|
rawValue?: string | string[] | null,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type CommandOptions = {
|
|
|
|
name?: string,
|
|
|
|
description?: string,
|
|
|
|
tags?: string[],
|
|
|
|
aliases?: string[],
|
|
|
|
restricted?: boolean,
|
|
|
|
showUsage?: boolean,
|
|
|
|
guildOnly?: boolean,
|
|
|
|
archivable?: boolean
|
|
|
|
slash?: boolean,
|
|
|
|
clientPermissions?: PermissionsString[],
|
|
|
|
memberPermissions?: PermissionsString[],
|
|
|
|
options?: CommandOptionParams[],
|
2023-12-05 19:31:53 +01:00
|
|
|
moduleName?: string
|
2023-12-05 16:47:54 +01:00
|
|
|
} & Partial<ComponentOptions>
|
|
|
|
|
|
|
|
export type CommandParams = { [key: string]: CommandOption | undefined }
|
|
|
|
|
|
|
|
export type SettingActionType = InfractionType | ''
|
|
|
|
export type SettingAction = {
|
|
|
|
[key: string]: unknown
|
|
|
|
type: SettingActionType,
|
|
|
|
duration: number | null,
|
|
|
|
points: number | null,
|
|
|
|
expiration: number | null,
|
|
|
|
force: boolean,
|
|
|
|
prune: boolean,
|
|
|
|
trigger: string | string[] | number
|
|
|
|
}
|
|
|
|
export type SettingMethod = 'add' | 'remove' | 'edit' | 'list' | 'reset'
|
|
|
|
export type SettingTypeResolve = 'USER' | 'GUILD';
|
|
|
|
export type SettingEmojiOption = {
|
|
|
|
//
|
|
|
|
}
|
|
|
|
export type SettingApiDefinitions = {
|
|
|
|
//
|
|
|
|
}
|
|
|
|
export type BaseSetting = object
|
2023-12-11 14:07:08 +01:00
|
|
|
export type SettingOptions<IsGuildSetting extends boolean> = {
|
2023-12-05 16:47:54 +01:00
|
|
|
name?: string,
|
2023-12-08 19:23:22 +01:00
|
|
|
aliases?: string[],
|
2023-12-11 14:07:08 +01:00
|
|
|
resolve?: If<IsGuildSetting, 'GUILD', 'USER'>,
|
2023-12-05 16:47:54 +01:00
|
|
|
description?: string,
|
|
|
|
display?: string,
|
|
|
|
emoji?: SettingEmojiOption,
|
|
|
|
default?: GuildSettingTypes
|
|
|
|
definitions?: SettingApiDefinitions,
|
|
|
|
commandOptions?: CommandOptionParams[],
|
|
|
|
commandType?: CommandOptionType,
|
|
|
|
clientPermissions?: PermissionsString[],
|
|
|
|
memberPermissions?: PermissionsString[],
|
|
|
|
premium?: number
|
|
|
|
} & Partial<ComponentOptions>
|
|
|
|
|
|
|
|
export type FilterSettingHelperResponse = {
|
|
|
|
error: true,
|
|
|
|
index?: string,
|
|
|
|
content?: string,
|
|
|
|
embeds?: APIEmbed[],
|
|
|
|
params?: FormatParams
|
|
|
|
} | { content: string, error?: false };
|
|
|
|
|
|
|
|
export type SlashCommandOptions = {
|
|
|
|
commandType?: ApplicationCommandType
|
|
|
|
} & CommandOptions;
|
|
|
|
|
|
|
|
export type InfractionTargetType = 'USER' | 'CHANNEL';
|
|
|
|
|
|
|
|
export type InfractionArguments = {
|
|
|
|
[key: string]: CommandOption | undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
export type AdditionalInfractionData = {
|
|
|
|
muteType?: MuteType,
|
|
|
|
roles?: Role[]
|
|
|
|
roleIds?: Snowflake[],
|
|
|
|
roleNames?: string[],
|
|
|
|
oldPermissions?: {
|
|
|
|
[key: string]: {
|
|
|
|
type: OverwriteType,
|
|
|
|
permissions: {
|
|
|
|
SendMessages: boolean | null,
|
|
|
|
AddReactions: boolean | null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
dehoist?: boolean,
|
|
|
|
oldName?: string,
|
|
|
|
name?: string,
|
|
|
|
removedRoles?: Snowflake[] | null,
|
|
|
|
muteRole?: Snowflake | null,
|
|
|
|
amount?: number,
|
|
|
|
message?: Snowflake,
|
|
|
|
seconds?: number,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type InfractionFlags = {
|
|
|
|
//
|
|
|
|
}
|
|
|
|
|
|
|
|
export type InfractionChangeType =
|
|
|
|
| 'UNRESOLVE'
|
|
|
|
| 'RESOLVE'
|
|
|
|
| 'DURATION'
|
|
|
|
| 'EXPIRATION'
|
|
|
|
| 'POINTS'
|
|
|
|
| 'REASON'
|
|
|
|
|
|
|
|
export type InfractionChange = {
|
|
|
|
type: InfractionChangeType,
|
|
|
|
staff: string,
|
|
|
|
timestamp: number,
|
|
|
|
duration?: number | null,
|
|
|
|
reason?: string,
|
|
|
|
expiration?: number | null,
|
|
|
|
points?: number | null
|
|
|
|
}
|
|
|
|
|
|
|
|
export type BaseInfractionData = {
|
|
|
|
_id?: string,
|
|
|
|
type?: InfractionType,
|
|
|
|
case?: number,
|
|
|
|
arguments?: InfractionArguments,
|
|
|
|
targetType?: InfractionTargetType,
|
|
|
|
guild: GuildWrapper,
|
2023-12-08 20:27:50 +01:00
|
|
|
channel?: GuildTextBasedChannel | null,
|
2023-12-05 16:47:54 +01:00
|
|
|
invoker?: InvokerWrapper | null,
|
2023-12-08 20:20:36 +01:00
|
|
|
target?: MemberWrapper | UserWrapper | GuildTextBasedChannel,
|
2023-12-05 16:47:54 +01:00
|
|
|
executor?: MemberWrapper | UserWrapper
|
|
|
|
duration?: number | null,
|
|
|
|
reason?: string,
|
|
|
|
silent?: boolean,
|
|
|
|
points?: number,
|
|
|
|
expiration?: number,
|
|
|
|
data?: AdditionalInfractionData,
|
|
|
|
hyperlink?: string | null,
|
2024-03-26 15:05:07 +01:00
|
|
|
// _callbacked?: boolean,
|
2023-12-05 16:47:54 +01:00
|
|
|
fetched?: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type InfractionJSON = {
|
|
|
|
id: string,
|
|
|
|
type: InfractionType,
|
|
|
|
case: number,
|
|
|
|
targetType: InfractionTargetType,
|
|
|
|
guild: Snowflake,
|
|
|
|
channel: Snowflake,
|
|
|
|
channelName: string,
|
|
|
|
target: Snowflake,
|
|
|
|
targetTag: string,
|
|
|
|
executor: Snowflake,
|
|
|
|
executorTag: string,
|
|
|
|
duration: number,
|
|
|
|
reason: string,
|
|
|
|
points: number,
|
|
|
|
expiration: number,
|
|
|
|
data: AdditionalInfractionData,
|
|
|
|
callback: number | null,
|
|
|
|
_callbacked?: boolean | null,
|
|
|
|
timestamp: number,
|
|
|
|
resolved: boolean,
|
|
|
|
changes: InfractionChange[],
|
|
|
|
flags: InfractionFlags,
|
|
|
|
modLogMessage: Snowflake,
|
|
|
|
modLogChannel: Snowflake
|
|
|
|
dmLogMessage: Snowflake,
|
|
|
|
message: Snowflake,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ModerationCallback = {
|
|
|
|
infraction: InfractionJSON,
|
|
|
|
timeout: NodeJS.Timeout
|
|
|
|
}
|
|
|
|
|
|
|
|
export type InfractionData = {
|
|
|
|
//
|
|
|
|
} & BaseInfractionData
|
|
|
|
|
|
|
|
export type MemberResolveable = Snowflake | GuildMember | MemberWrapper | User | UserWrapper | string;
|
|
|
|
export type UserResolveable = Snowflake | User | UserWrapper | MemberWrapper | GuildMember | string;
|
|
|
|
export type RoleResolveable = Snowflake | Role | string;
|
|
|
|
export type ChannelResolveable = Snowflake | BaseChannel | string;
|
|
|
|
// type Resolveable = MemberResolveable | UserResolveable | RoleResolveable | ChannelResolveable
|
|
|
|
|
|
|
|
export type MemberResolverFunction = (r: MemberResolveable, s: boolean, g: GuildWrapper | null) => Promise<GuildMember | null>
|
|
|
|
export type UserResolverFunction = (r: UserResolveable, s: boolean, g: GuildWrapper | null) => Promise<User | null>
|
|
|
|
export type RoleResolverFunction = (r: RoleResolveable, s: boolean, g: GuildWrapper | null) => Promise<Role | null>
|
|
|
|
export type ChannelResolverFunction = (r: ChannelResolveable, s: boolean, g: GuildWrapper | null) => Promise<Channel | null>
|
|
|
|
export type ResolverFunction =
|
|
|
|
| MemberResolverFunction
|
|
|
|
| UserResolverFunction
|
|
|
|
| RoleResolverFunction
|
|
|
|
| ChannelResolverFunction
|
|
|
|
|
|
|
|
export declare interface ExtendedMessage<InGuild extends boolean = boolean> extends Message<InGuild> {
|
|
|
|
guildWrapper: If<InGuild, GuildWrapper>;
|
|
|
|
filtered?: FilterResult
|
|
|
|
}
|
|
|
|
|
|
|
|
export declare interface ExtendedGuildBan extends GuildBan {
|
|
|
|
guildWrapper: GuildWrapper
|
|
|
|
}
|
|
|
|
|
|
|
|
export declare interface ExtendedGuildMember extends GuildMember {
|
|
|
|
guildWrapper: GuildWrapper
|
|
|
|
}
|
|
|
|
|
|
|
|
export declare interface ExtendedThreadChannel extends ThreadChannel {
|
|
|
|
guildWrapper: GuildWrapper
|
|
|
|
}
|
|
|
|
|
|
|
|
export declare interface ExtendedAPIEmbedField extends APIEmbedField
|
|
|
|
{
|
|
|
|
_attachment?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export declare interface ExtendedAPIEmbed extends APIEmbed {
|
|
|
|
fields: ExtendedAPIEmbedField[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export declare interface ExtendedVoiceState extends VoiceState {
|
|
|
|
guildWrapper: GuildWrapper
|
|
|
|
}
|
|
|
|
|
|
|
|
export declare interface ExtendedInvite extends Invite {
|
|
|
|
guildWrapper?: GuildWrapper
|
|
|
|
}
|