Compare commits
24 Commits
main
...
rejoin-log
Author | SHA1 | Date | |
---|---|---|---|
f3c0c1308c | |||
c037a8abe9 | |||
9119316438 | |||
79f7c00670 | |||
d963cf3db5 | |||
51ec1189c0 | |||
390c0ee840 | |||
8199399323 | |||
34923bee9e | |||
dc2602724c | |||
fa45aeab42 | |||
ccb5ce3000 | |||
c081cdc1bf | |||
2c4c4702b4 | |||
cc3ca2580d | |||
96b459a8aa | |||
d637a197b2 | |||
de26cdec44 | |||
2a43ad7feb | |||
a3e6793632 | |||
5f07fd3e15 | |||
c5c304d5d9 | |||
bc467e4dd9 | |||
|
f288293fd5 |
@ -26,6 +26,9 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-misused-promises": ["error", {
|
||||||
|
// "checksVoidReturn": false
|
||||||
|
}],
|
||||||
"accessor-pairs": "warn",
|
"accessor-pairs": "warn",
|
||||||
"array-callback-return": "warn",
|
"array-callback-return": "warn",
|
||||||
"array-bracket-newline": [
|
"array-bracket-newline": [
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
compressionLevel: mixed
|
||||||
|
|
||||||
|
enableGlobalCache: false
|
||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
npmRegistryServer: "https://registry.corgi.wtf"
|
npmRegistryServer: "https://registry.corgi.wtf"
|
||||||
|
@ -42,7 +42,7 @@ import { InvokerWrapper, MemberWrapper, UserWrapper } from '../src/client/compon
|
|||||||
import GuildWrapper from '../src/client/components/wrappers/GuildWrapper.js';
|
import GuildWrapper from '../src/client/components/wrappers/GuildWrapper.js';
|
||||||
import DiscordClient from '../src/client/DiscordClient.js';
|
import DiscordClient from '../src/client/DiscordClient.js';
|
||||||
import { CommandOption, Inhibitor } from '../src/client/interfaces/index.js';
|
import { CommandOption, Inhibitor } from '../src/client/interfaces/index.js';
|
||||||
import { MuteType } from './Settings.js';
|
import { MuteType, TextCommandsSettings } from './Settings.js';
|
||||||
import { ClientEvents } from './Events.js';
|
import { ClientEvents } from './Events.js';
|
||||||
import { FilterResult } from './Utils.js';
|
import { FilterResult } from './Utils.js';
|
||||||
import { GuildSettingTypes } from './Guild.js';
|
import { GuildSettingTypes } from './Guild.js';
|
||||||
@ -114,6 +114,7 @@ export type ObserverOptions = {
|
|||||||
export type InhibitorOptions = {
|
export type InhibitorOptions = {
|
||||||
name?: string,
|
name?: string,
|
||||||
guild?: boolean
|
guild?: boolean
|
||||||
|
// Higher numbers come first
|
||||||
priority?: number,
|
priority?: number,
|
||||||
silent?: boolean
|
silent?: boolean
|
||||||
} & Partial<ComponentOptions>
|
} & Partial<ComponentOptions>
|
||||||
@ -348,6 +349,7 @@ export type InfractionArguments = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type AdditionalInfractionData = {
|
export type AdditionalInfractionData = {
|
||||||
|
track?: boolean;
|
||||||
muteType?: MuteType,
|
muteType?: MuteType,
|
||||||
roles?: Role[]
|
roles?: Role[]
|
||||||
roleIds?: Snowflake[],
|
roleIds?: Snowflake[],
|
||||||
@ -502,3 +504,14 @@ export declare interface ExtendedVoiceState extends VoiceState {
|
|||||||
export declare interface ExtendedInvite extends Invite {
|
export declare interface ExtendedInvite extends Invite {
|
||||||
guildWrapper?: GuildWrapper
|
guildWrapper?: GuildWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EntitySettings = {
|
||||||
|
[key: string]: unknown,
|
||||||
|
textcommands: TextCommandsSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EntityData = {
|
||||||
|
id: Snowflake,
|
||||||
|
banned?: boolean,
|
||||||
|
settings?: EntitySettings
|
||||||
|
}
|
10
@types/Events.d.ts
vendored
10
@types/Events.d.ts
vendored
@ -14,13 +14,20 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { Component } from '../src/client/interfaces/index.ts';
|
import { Component, Infraction } from '../src/client/interfaces/index.ts';
|
||||||
import { ClientEvents as CE, InvalidRequestWarningData, RateLimitData, ResponseLike } from 'discord.js';
|
import { ClientEvents as CE, InvalidRequestWarningData, RateLimitData, ResponseLike } from 'discord.js';
|
||||||
import { ExtendedGuildBan, ExtendedMessage } from './Client.ts';
|
import { ExtendedGuildBan, ExtendedMessage } from './Client.ts';
|
||||||
import { APIRequest } from '@discordjs/rest';
|
import { APIRequest } from '@discordjs/rest';
|
||||||
import { AutomodErrorProps, LinkFilterWarnProps, LogErrorProps, MissingPermsProps, UtilityErrorProps, WordWatcherErrorProps } from './Moderation.js';
|
import { AutomodErrorProps, LinkFilterWarnProps, LogErrorProps, MissingPermsProps, UtilityErrorProps, WordWatcherErrorProps } from './Moderation.js';
|
||||||
|
|
||||||
type ComponentUpdate = { component: Component, type: 'ENABLE' | 'DISABLE' | 'LOAD' | 'UNLOAD' | 'RELOAD' }
|
type ComponentUpdate = { component: Component, type: 'ENABLE' | 'DISABLE' | 'LOAD' | 'UNLOAD' | 'RELOAD' }
|
||||||
|
|
||||||
|
export type RejoinLogEntry = {
|
||||||
|
userId: string;
|
||||||
|
guildId: string;
|
||||||
|
caseId: string;
|
||||||
|
notify: string;
|
||||||
|
}
|
||||||
export interface ClientEvents extends CE {
|
export interface ClientEvents extends CE {
|
||||||
componentUpdate: [data: ComponentUpdate],
|
componentUpdate: [data: ComponentUpdate],
|
||||||
rateLimit: [rateLimitInfo: RateLimitData];
|
rateLimit: [rateLimitInfo: RateLimitData];
|
||||||
@ -36,4 +43,5 @@ export interface ClientEvents extends CE {
|
|||||||
utilityError: [UtilityErrorProps];
|
utilityError: [UtilityErrorProps];
|
||||||
linkFilterWarn: [LinkFilterWarnProps];
|
linkFilterWarn: [LinkFilterWarnProps];
|
||||||
filterMissingPermissions: [MissingPermsProps];
|
filterMissingPermissions: [MissingPermsProps];
|
||||||
|
infraction: [Infraction];
|
||||||
}
|
}
|
8
@types/Guild.d.ts
vendored
8
@types/Guild.d.ts
vendored
@ -37,6 +37,7 @@ import {
|
|||||||
PermissionSettings,
|
PermissionSettings,
|
||||||
ProtectionSettings,
|
ProtectionSettings,
|
||||||
RaidprotectionSettings,
|
RaidprotectionSettings,
|
||||||
|
RejoinSettings,
|
||||||
SelfroleSettings,
|
SelfroleSettings,
|
||||||
SilenceSettings,
|
SilenceSettings,
|
||||||
StaffSettings,
|
StaffSettings,
|
||||||
@ -48,6 +49,7 @@ import {
|
|||||||
WordWatcherSettings
|
WordWatcherSettings
|
||||||
} from './Settings.js';
|
} from './Settings.js';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { EntityData, EntitySettings } from './Client.ts';
|
||||||
|
|
||||||
export type GuildSettingTypes =
|
export type GuildSettingTypes =
|
||||||
| AutomodSettings
|
| AutomodSettings
|
||||||
@ -79,6 +81,7 @@ export type GuildSettingTypes =
|
|||||||
| WordFilterSettings
|
| WordFilterSettings
|
||||||
| WordWatcherSettings
|
| WordWatcherSettings
|
||||||
| LocaleSettings
|
| LocaleSettings
|
||||||
|
| RejoinSettings
|
||||||
|
|
||||||
export type PartialGuildSettings = Partial<GuildSettings>
|
export type PartialGuildSettings = Partial<GuildSettings>
|
||||||
// {
|
// {
|
||||||
@ -117,7 +120,8 @@ export type GuildSettings = {
|
|||||||
invitefilter: InviteFilterSettings,
|
invitefilter: InviteFilterSettings,
|
||||||
mentionfilter: MentionFilterSettings,
|
mentionfilter: MentionFilterSettings,
|
||||||
raidprotection: RaidprotectionSettings,
|
raidprotection: RaidprotectionSettings,
|
||||||
}
|
rejoin: RejoinSettings
|
||||||
|
} & EntitySettings
|
||||||
|
|
||||||
export type PermissionSet = {
|
export type PermissionSet = {
|
||||||
global: string[],
|
global: string[],
|
||||||
@ -143,7 +147,7 @@ export type GuildData = {
|
|||||||
modlogs?: boolean,
|
modlogs?: boolean,
|
||||||
settings?: boolean
|
settings?: boolean
|
||||||
}
|
}
|
||||||
}
|
} & EntityData
|
||||||
|
|
||||||
export type CallbackData = {
|
export type CallbackData = {
|
||||||
type: string,
|
type: string,
|
||||||
|
@ -17,11 +17,6 @@
|
|||||||
import { Snowflake } from 'discord.js';
|
import { Snowflake } from 'discord.js';
|
||||||
import { InfractionType, SettingAction } from './Client.js';
|
import { InfractionType, SettingAction } from './Client.js';
|
||||||
|
|
||||||
export type UserSettings = {
|
|
||||||
prefix?: string,
|
|
||||||
locale?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Setting = {
|
export type Setting = {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
@ -213,3 +208,8 @@ export type LocaleSettings = {
|
|||||||
export type RaidprotectionSettings = {
|
export type RaidprotectionSettings = {
|
||||||
//
|
//
|
||||||
} & Setting
|
} & Setting
|
||||||
|
|
||||||
|
export type RejoinSettings = {
|
||||||
|
channel: string | null,
|
||||||
|
message: string | null
|
||||||
|
} & Setting
|
34
@types/User.d.ts
vendored
Normal file
34
@types/User.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
import { EntityData } from './Client.ts';
|
||||||
|
import { LocaleSettings, TextCommandsSettings } from './Settings.ts';
|
||||||
|
|
||||||
|
export type UserSettingTypes =
|
||||||
|
| LocaleSettings
|
||||||
|
| TextCommandsSettings
|
||||||
|
|
||||||
|
export type UserSettings = {
|
||||||
|
[key: string]: UserSettingTypes
|
||||||
|
textcommands: TextCommandsSettings,
|
||||||
|
locale: LocaleSettings
|
||||||
|
} & EntitySettings
|
||||||
|
|
||||||
|
export type UserData = {
|
||||||
|
[key: string]: unknown
|
||||||
|
settings?: UserSettings,
|
||||||
|
debug?: boolean
|
||||||
|
} & EntityData
|
@ -18,6 +18,8 @@ Internal/technical documentation will be in the source files adjacent to the cod
|
|||||||
- MariaDB (TBD)
|
- MariaDB (TBD)
|
||||||
- RabbitMQ (maybe, TBD)
|
- RabbitMQ (maybe, TBD)
|
||||||
|
|
||||||
|
A Docker compose file is available for convenience for setting up databases for development or personal deployment.
|
||||||
|
|
||||||
### Running
|
### Running
|
||||||
- Install the dependencies: `yarn install`
|
- Install the dependencies: `yarn install`
|
||||||
- Run the bot: `yarn start`
|
- Run the bot: `yarn start`
|
||||||
@ -41,3 +43,7 @@ New feature branch -> development (local bots) -> alpha (QA) -> beta (GalacticTe
|
|||||||
- Linter
|
- Linter
|
||||||
- Build test
|
- Build test
|
||||||
- Unit tests
|
- Unit tests
|
||||||
|
|
||||||
|
# Contributors
|
||||||
|
**Nolan** - https://github.com/noolaan
|
||||||
|
Major framework contributions to V3 development.
|
||||||
|
47
docker-compose.yml
Normal file
47
docker-compose.yml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Compose file for the bot's DB stack
|
||||||
|
# Modify as necessary
|
||||||
|
# TODO: Setup Traefik labels
|
||||||
|
|
||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: mongo
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- mongo-data:/data/db
|
||||||
|
ports:
|
||||||
|
- 27017:27017
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: root
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: I_am_ROOT
|
||||||
|
|
||||||
|
maria:
|
||||||
|
image: mariadb
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- maria-data:/var/lib/mysql
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
environment:
|
||||||
|
MARIADB_ROOT_PASSWORD: I_am_ROOT
|
||||||
|
|
||||||
|
# Web interfaces for interacting with the databases
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
|
||||||
|
mongo-express:
|
||||||
|
image: mongo-express
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 8081:8081
|
||||||
|
environment:
|
||||||
|
ME_CONFIG_MONGODB_ADMINUSERNAME: root
|
||||||
|
ME_CONFIG_MONGODB_ADMINPASSWORD: I_am_ROOT
|
||||||
|
ME_CONFIG_MONGODB_URL: mongodb://root:I_am_ROOT@mongo:27017/
|
||||||
|
ME_CONFIG_BASICAUTH: false
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo-data:
|
||||||
|
maria-data:
|
11
options.json
11
options.json
@ -11,7 +11,8 @@
|
|||||||
"prefix": "!",
|
"prefix": "!",
|
||||||
"developers": [
|
"developers": [
|
||||||
"132777808362471424",
|
"132777808362471424",
|
||||||
"187613017733726210"
|
"187613017733726210",
|
||||||
|
"132620781791346688"
|
||||||
],
|
],
|
||||||
"developmentMode": false,
|
"developmentMode": false,
|
||||||
"libraryOptions": {
|
"libraryOptions": {
|
||||||
@ -35,7 +36,11 @@
|
|||||||
"GuildMessageReactions",
|
"GuildMessageReactions",
|
||||||
"DirectMessages"
|
"DirectMessages"
|
||||||
],
|
],
|
||||||
"invalidRequestWarningInterval": 500
|
"invalidRequestWarningInterval": 500,
|
||||||
|
"rest": {
|
||||||
|
"timeout": 30000,
|
||||||
|
"retries": 5
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"invite": "https://discord.gg/WDCTKGp",
|
"invite": "https://discord.gg/WDCTKGp",
|
||||||
"slashCommands": {
|
"slashCommands": {
|
||||||
@ -79,7 +84,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mariadb": {
|
"mariadb": {
|
||||||
"load": false,
|
"load": true,
|
||||||
"tables": []
|
"tables": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -235,6 +235,7 @@ class DiscordClient extends Client
|
|||||||
this.#logger.error(`Unhandled rejection:\n${err?.stack || err}`);
|
this.#logger.error(`Unhandled rejection:\n${err?.stack || err}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
process.on('message', this.#handleMessage.bind(this));
|
process.on('message', this.#handleMessage.bind(this));
|
||||||
process.on('SIGINT', () => this.logger.info('Received SIGINT'));
|
process.on('SIGINT', () => this.logger.info('Received SIGINT'));
|
||||||
process.on('SIGTERM', () => this.logger.info('Received SIGTERM'));
|
process.on('SIGTERM', () => this.logger.info('Received SIGTERM'));
|
||||||
@ -524,6 +525,7 @@ class DiscordClient extends Client
|
|||||||
return this.localeLoader.format(language, index, params, code);
|
return this.localeLoader.format(language, index, params, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Combine these
|
||||||
async getGuildWrapper (id: string)
|
async getGuildWrapper (id: string)
|
||||||
{
|
{
|
||||||
if (this.#guildWrappers.has(id))
|
if (this.#guildWrappers.has(id))
|
||||||
@ -551,6 +553,11 @@ class DiscordClient extends Client
|
|||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getGuildWrappers (ids: string[])
|
||||||
|
{
|
||||||
|
return (await Promise.all(ids.map(id => this.getGuildWrapper(id)))).filter(entry => entry !== null);
|
||||||
|
}
|
||||||
|
|
||||||
getUserWrapper(resolveable: UserResolveable, fetch?: false): UserWrapper | null;
|
getUserWrapper(resolveable: UserResolveable, fetch?: false): UserWrapper | null;
|
||||||
getUserWrapper(resolveable: UserResolveable, fetch?: true): Promise<UserWrapper | null>;
|
getUserWrapper(resolveable: UserResolveable, fetch?: true): Promise<UserWrapper | null>;
|
||||||
getUserWrapper (resolveable: UserResolveable, fetch = true)
|
getUserWrapper (resolveable: UserResolveable, fetch = true)
|
||||||
@ -582,6 +589,16 @@ class DiscordClient extends Client
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUserWrappers(resolveables: UserResolveable[], fetch?: true): Promise<UserWrapper[]>;
|
||||||
|
getUserWrappers(resolveables: UserResolveable[], fetch?: false): UserWrapper[];
|
||||||
|
getUserWrappers (resolveables: UserResolveable[], fetch = true): Promise<UserWrapper[]> | UserWrapper[]
|
||||||
|
{
|
||||||
|
if (!fetch)
|
||||||
|
return resolveables.map(r => this.getUserWrapper(r, false)).filter(e => e !== null) as UserWrapper[];
|
||||||
|
return Promise.all(resolveables.map(resolveable => this.getUserWrapper(resolveable)))
|
||||||
|
.then(entries => entries.filter(e => e !== null)) as Promise<UserWrapper[]>;
|
||||||
|
}
|
||||||
|
|
||||||
async fetchInvite (invite: InviteResolvable, opts?: ClientFetchInviteOptions)
|
async fetchInvite (invite: InviteResolvable, opts?: ClientFetchInviteOptions)
|
||||||
{
|
{
|
||||||
const code = DataResolver.resolveInviteCode(invite);
|
const code = DataResolver.resolveInviteCode(invite);
|
||||||
@ -639,6 +656,11 @@ class DiscordClient extends Client
|
|||||||
return this.#storageManager.mongodb;
|
return this.#storageManager.mongodb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get mariadb ()
|
||||||
|
{
|
||||||
|
return this.#storageManager.mariadb;
|
||||||
|
}
|
||||||
|
|
||||||
get moderation ()
|
get moderation ()
|
||||||
{
|
{
|
||||||
return this.#moderationManager;
|
return this.#moderationManager;
|
||||||
|
71
src/client/components/commands/developer/Botban.ts
Normal file
71
src/client/components/commands/developer/Botban.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js';
|
||||||
|
import { Command } from '../../../interfaces/index.js';
|
||||||
|
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
|
||||||
|
import DiscordClient from '../../../DiscordClient.js';
|
||||||
|
|
||||||
|
class BotbanCommand extends Command
|
||||||
|
{
|
||||||
|
|
||||||
|
constructor (client: DiscordClient)
|
||||||
|
{
|
||||||
|
super(client, {
|
||||||
|
name: 'botban',
|
||||||
|
aliases: [ 'bban' ],
|
||||||
|
restricted: true,
|
||||||
|
moduleName: 'developer',
|
||||||
|
options: [
|
||||||
|
{ name: 'users', type: CommandOptionType.USERS },
|
||||||
|
{ name: 'guild', type: CommandOptionType.STRING },
|
||||||
|
{ name: 'unban', type: CommandOptionType.BOOLEAN, flag: true, defaultValue: true, valueOptional: true }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute (_invoker: InvokerWrapper, { users, guild, unban }: CommandParams)
|
||||||
|
{
|
||||||
|
let out = 'Revoked bot access from:\n';
|
||||||
|
if (users)
|
||||||
|
{
|
||||||
|
const ids = users.asUsers.map(u => u.id);
|
||||||
|
const wrappers = await this.client.getUserWrappers(ids);
|
||||||
|
out += '**Users**\n';
|
||||||
|
for (const wrapper of wrappers)
|
||||||
|
{
|
||||||
|
out += `\t${wrapper.displayName}\n`;
|
||||||
|
await wrapper.setBotBan(!unban?.asBool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (guild)
|
||||||
|
{
|
||||||
|
const ids = guild.asString.split(' ');
|
||||||
|
const wrappers = await this.client.getGuildWrappers(ids);
|
||||||
|
out += '**Servers**\n';
|
||||||
|
for (const wrapper of wrappers)
|
||||||
|
{
|
||||||
|
out += `\t${wrapper}`;
|
||||||
|
await wrapper.setBotBan(!unban?.asBool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BotbanCommand;
|
@ -27,20 +27,47 @@ class DebugCommand extends Command
|
|||||||
name: 'debug',
|
name: 'debug',
|
||||||
restricted: true,
|
restricted: true,
|
||||||
moduleName: 'developer',
|
moduleName: 'developer',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: [ 'enable', 'disable' ],
|
||||||
|
type: CommandOptionType.SUB_COMMAND,
|
||||||
options: [
|
options: [
|
||||||
{ name: 'guild', required: true },
|
{ name: 'guild', required: true },
|
||||||
{ name: 'enabled', required: true, type: CommandOptionType.BOOLEAN }
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'list',
|
||||||
|
type: CommandOptionType.SUB_COMMAND,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute (_invoker: InvokerWrapper, options: CommandParams)
|
async execute (invoker: InvokerWrapper, options: CommandParams)
|
||||||
|
{
|
||||||
|
const { subcommand } = invoker;
|
||||||
|
if (!subcommand)
|
||||||
|
throw new Error('Missing subcommand');
|
||||||
|
|
||||||
|
if ([ 'enable', 'disable' ].includes(subcommand.name))
|
||||||
{
|
{
|
||||||
const guildId = options.guild!.asString;
|
const guildId = options.guild!.asString;
|
||||||
return this.enableDebug(guildId, options.enabled?.asBool);
|
return this.toggleDebug(guildId, subcommand.name === 'enable');
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableDebug (guildId: string, enabled = false)
|
if (subcommand.name === 'list')
|
||||||
|
return this.listDebugGuilds();
|
||||||
|
}
|
||||||
|
|
||||||
|
async listDebugGuilds ()
|
||||||
|
{
|
||||||
|
const data = await this.client.mongodb.guilds.find({ debug: true });
|
||||||
|
const ids = data.map(entry => entry.id ?? entry.guildId);
|
||||||
|
const guilds = await this.client.getGuildWrappers(ids);
|
||||||
|
return guilds.map(guild => `**${guild.name}**: ${guild.id}`).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleDebug (guildId: string, enabled = false)
|
||||||
{
|
{
|
||||||
let output = '';
|
let output = '';
|
||||||
if (this.client.shard)
|
if (this.client.shard)
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { CommandParams } from '../../../../../@types/Client.js';
|
import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js';
|
||||||
import DiscordClient from '../../../DiscordClient.js';
|
import DiscordClient from '../../../DiscordClient.js';
|
||||||
import { Kick } from '../../../infractions/index.js';
|
import { Kick } from '../../../infractions/index.js';
|
||||||
import { ModerationCommand } from '../../../interfaces/index.js';
|
import { ModerationCommand } from '../../../interfaces/index.js';
|
||||||
@ -29,7 +29,16 @@ class KickCommand extends ModerationCommand
|
|||||||
name: 'kick',
|
name: 'kick',
|
||||||
description: 'Kick people.',
|
description: 'Kick people.',
|
||||||
moduleName: 'moderation',
|
moduleName: 'moderation',
|
||||||
options: [],
|
options: [
|
||||||
|
{
|
||||||
|
name: 'track',
|
||||||
|
description: 'Whether to ping you if the user rejoins',
|
||||||
|
type: CommandOptionType.BOOLEAN,
|
||||||
|
flag: true,
|
||||||
|
valueOptional: true,
|
||||||
|
defaultValue: false
|
||||||
|
}
|
||||||
|
],
|
||||||
guildOnly: true,
|
guildOnly: true,
|
||||||
showUsage: true,
|
showUsage: true,
|
||||||
memberPermissions: [ 'KickMembers' ],
|
memberPermissions: [ 'KickMembers' ],
|
||||||
@ -37,11 +46,14 @@ class KickCommand extends ModerationCommand
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute (invoker: InvokerWrapper<true>, { users, ...args }: CommandParams)
|
async execute (invoker: InvokerWrapper<true>, { users, track, ...args }: CommandParams)
|
||||||
{
|
{
|
||||||
const wrappers = await Promise.all(users!.asUsers.map(user => invoker.guild.memberWrapper(user)));
|
const wrappers = await Promise.all(users!.asUsers.map(user => invoker.guild.memberWrapper(user)));
|
||||||
return this.client.moderation.handleInfraction(Kick, invoker, {
|
return this.client.moderation.handleInfraction(Kick, invoker, {
|
||||||
targets: wrappers.filter(Boolean) as MemberWrapper[],
|
targets: wrappers.filter(Boolean) as MemberWrapper[],
|
||||||
|
data: {
|
||||||
|
track: track?.asBool
|
||||||
|
},
|
||||||
args
|
args
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import { SlashCommand } from '../../../interfaces/index.js';
|
import { SlashCommand } from '../../../interfaces/index.js';
|
||||||
import DiscordClient from '../../../DiscordClient.js';
|
import DiscordClient from '../../../DiscordClient.js';
|
||||||
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
|
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
|
||||||
|
import Quotes from '../../../../constants/Quotes.js';
|
||||||
|
|
||||||
class PingCommand extends SlashCommand
|
class PingCommand extends SlashCommand
|
||||||
{
|
{
|
||||||
@ -37,7 +38,10 @@ class PingCommand extends SlashCommand
|
|||||||
const { ping } = this.client.ws;
|
const { ping } = this.client.ws;
|
||||||
const number = (ping / 40);
|
const number = (ping / 40);
|
||||||
const repeat = number > 1 ? number : 1;
|
const repeat = number > 1 ? number : 1;
|
||||||
return invoker.reply(`P${'o'.repeat(repeat)}ng! \`${ping}ms\``, { emoji: 'success' });
|
|
||||||
|
const index = Math.floor(Quotes.length * Math.random());
|
||||||
|
const [ quote, author ] = Quotes[index];
|
||||||
|
return invoker.reply(`P${'o'.repeat(repeat)}ng! \`${ping}ms\`\n\n> ${quote.replaceAll('\n', '\n> ')}\n\\- *${author}*`, { emoji: 'success' });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
47
src/client/components/inhibitors/Banned.ts
Normal file
47
src/client/components/inhibitors/Banned.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
import { InhibitorResponse } from '../../../../@types/Client.js';
|
||||||
|
import Util from '../../../utilities/Util.js';
|
||||||
|
import DiscordClient from '../../DiscordClient.js';
|
||||||
|
import Inhibitor from '../../interfaces/Inhibitor.js';
|
||||||
|
import InvokerWrapper from '../wrappers/InvokerWrapper.js';
|
||||||
|
|
||||||
|
class Banned extends Inhibitor
|
||||||
|
{
|
||||||
|
constructor (client: DiscordClient)
|
||||||
|
{
|
||||||
|
super(client, {
|
||||||
|
name: 'banned',
|
||||||
|
priority: 10,
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute (invoker: InvokerWrapper): Promise<InhibitorResponse>
|
||||||
|
{
|
||||||
|
const user = await invoker.userWrapper();
|
||||||
|
const { guild } = invoker;
|
||||||
|
if (user && await user.botBanned())
|
||||||
|
return super._fail({ noun: Util.capitalise(invoker.format('NOUN_USER')) });
|
||||||
|
if (guild && await guild.botBanned())
|
||||||
|
return super._fail({ noun: Util.capitalise(invoker.format('NOUN_GUILD')) });
|
||||||
|
return super._succeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Banned;
|
@ -24,7 +24,7 @@ class ChannelIgnore extends Inhibitor
|
|||||||
{
|
{
|
||||||
super(client, {
|
super(client, {
|
||||||
name: 'channelIgnore',
|
name: 'channelIgnore',
|
||||||
priority: 9,
|
priority: 8,
|
||||||
guild: true,
|
guild: true,
|
||||||
silent: true
|
silent: true
|
||||||
});
|
});
|
||||||
|
@ -25,7 +25,7 @@ class ClientPermissions extends Inhibitor
|
|||||||
{
|
{
|
||||||
super(client, {
|
super(client, {
|
||||||
name: 'clientPermissions',
|
name: 'clientPermissions',
|
||||||
priority: 10,
|
priority: 9,
|
||||||
guarded: true,
|
guarded: true,
|
||||||
guild: true
|
guild: true
|
||||||
});
|
});
|
||||||
|
@ -287,7 +287,7 @@ class CommandHandler extends Observer
|
|||||||
{
|
{
|
||||||
if (!(invoker.command instanceof SettingsCommand))
|
if (!(invoker.command instanceof SettingsCommand))
|
||||||
invoker.command.error(now);
|
invoker.command.error(now);
|
||||||
this.logger.error(`\n[${invoker.type.toUpperCase()}] Command ${debugstr} errored:\nGuild: ${invoker.inGuild() ? invoker.guild?.name : 'dms'} (${invoker.guild?.id || ''})\nOptions:\n${Object.keys(options).map((key) => `[${key}: ${options[key].asString} (${options[key].rawValue})]`).join('\n')}\n${error.stack || error}`);
|
this.logger.error(`\n[${invoker.type.toUpperCase()}] Command "${debugstr}" errored:\nGuild: ${invoker.inGuild() ? invoker.guild?.name : 'dms'} (${invoker.guild?.id || ''})\nOptions:\n${Object.keys(options).map((key) => `[${key}: ${options[key].asString} (${options[key].rawValue})]`).join('\n')}\n${error.stack || error}`);
|
||||||
this._generateError(invoker, { type: 'commandHandler' });
|
this._generateError(invoker, { type: 'commandHandler' });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -25,6 +25,8 @@ import { AttachmentData, MessageLogEntry } from '../../../../@types/Guild.js';
|
|||||||
import { stripIndents } from 'common-tags';
|
import { stripIndents } from 'common-tags';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { inspect } from 'util';
|
import { inspect } from 'util';
|
||||||
|
import Infraction from '../../interfaces/Infraction.js';
|
||||||
|
import { RejoinLogEntry } from '../../../../@types/Events.js';
|
||||||
|
|
||||||
/* eslint-disable no-labels */
|
/* eslint-disable no-labels */
|
||||||
const CONSTANTS: {
|
const CONSTANTS: {
|
||||||
@ -81,7 +83,9 @@ class GuildLogger extends Observer
|
|||||||
[ 'guildMemberUpdate', this.memberUpdate.bind(this) ],
|
[ 'guildMemberUpdate', this.memberUpdate.bind(this) ],
|
||||||
[ 'threadCreate', this.threadCreate.bind(this) ],
|
[ 'threadCreate', this.threadCreate.bind(this) ],
|
||||||
[ 'threadDelete', this.threadDelete.bind(this) ],
|
[ 'threadDelete', this.threadDelete.bind(this) ],
|
||||||
[ 'threadUpdate', this.threadUpdate.bind(this) ]
|
[ 'threadUpdate', this.threadUpdate.bind(this) ],
|
||||||
|
[ 'infraction', this.infraction.bind(this) ],
|
||||||
|
[ 'guildMemberAdd', this.rejoinTracking.bind(this) ],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!process.env.MODERATION_WEHBHOOK_ID || !process.env.MODERATION_WEHBHOOK_TOKEN)
|
if (!process.env.MODERATION_WEHBHOOK_ID || !process.env.MODERATION_WEHBHOOK_TOKEN)
|
||||||
@ -955,6 +959,60 @@ class GuildLogger extends Observer
|
|||||||
};
|
};
|
||||||
await logChannel.send({ embeds: [ embed ] });
|
await logChannel.send({ embeds: [ embed ] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async infraction (inf: Infraction)
|
||||||
|
{
|
||||||
|
if (inf.type !== 'KICK')
|
||||||
|
return;
|
||||||
|
|
||||||
|
const { guild: wrapper } = inf;
|
||||||
|
const settings = await wrapper.settings();
|
||||||
|
const setting = settings.rejoin;
|
||||||
|
if (!setting.channel || !setting.enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!inf.targetId || !inf.guildId || !inf.id)
|
||||||
|
throw new Error('Missing ID for target, guild, infraction');
|
||||||
|
|
||||||
|
const notifyArray = [];
|
||||||
|
if (inf.data.track)
|
||||||
|
notifyArray.push(inf.executorId);
|
||||||
|
await this.client.mariadb.query('INSERT INTO `kickLog` (`userId`, `guildId`, `caseId`, `notify`) VALUES (?);', [[ inf.targetId, inf.guildId, inf.id, JSON.stringify(notifyArray) ]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async rejoinTracking (member: ExtendedGuildMember)
|
||||||
|
{
|
||||||
|
const { guildWrapper: wrapper } = member;
|
||||||
|
const settings = await wrapper.settings();
|
||||||
|
const setting = settings.rejoin;
|
||||||
|
if (!setting.channel || !setting.enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const logChannel = await wrapper.resolveChannel<TextChannel>(setting.channel);
|
||||||
|
if (!logChannel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const missing = logChannel.permissionsFor(this.client.user!)?.missing([ 'ViewChannel', 'SendMessages' ]);
|
||||||
|
if (!missing || missing.length)
|
||||||
|
return this.client.emit('logError', { guild: wrapper, logger: 'rejoinLogger', reason: 'REJOINLOG_NO_PERMS', params: { missing: missing?.join(', ') ?? 'ALL' } });
|
||||||
|
|
||||||
|
const [ result ] = await this.client.mariadb.query('DELETE FROM `kickLog` WHERE `guildId` = ? AND `userId` = ? RETURNING *;', [ member.guild.id, member.id ]) as RejoinLogEntry[];
|
||||||
|
if (!result)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const infraction = await this.client.mongodb.infractions.findOne({ id: result.caseId });
|
||||||
|
|
||||||
|
let { message } = setting;
|
||||||
|
message = this._replaceTags(message!, member);
|
||||||
|
const notifyArray = JSON.parse(result.notify);
|
||||||
|
if (notifyArray.length > 0)
|
||||||
|
{
|
||||||
|
const notifyString = notifyArray.map((id: string) => `<@${id}>`).join(', ');
|
||||||
|
message = notifyString + '\n' + message;
|
||||||
|
}
|
||||||
|
message += (infraction ? `\n**Reason:** \`${infraction.reason}\`` : '') + `\n**Case:** \`${result.caseId.split(':')[1]}\``;
|
||||||
|
await this.client.rateLimiter.queueSend(logChannel, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GuildLogger;
|
export default GuildLogger;
|
83
src/client/components/settings/logging/Rejoin.ts
Normal file
83
src/client/components/settings/logging/Rejoin.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { TextChannel } from 'discord.js';
|
||||||
|
import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js';
|
||||||
|
import { RejoinSettings } from '../../../../../@types/Settings.js';
|
||||||
|
import DiscordClient from '../../../DiscordClient.js';
|
||||||
|
import Setting from '../../../interfaces/Setting.js';
|
||||||
|
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
|
||||||
|
import CommandError from '../../../interfaces/CommandError.js';
|
||||||
|
|
||||||
|
class Rejoin extends Setting
|
||||||
|
{
|
||||||
|
constructor (client: DiscordClient)
|
||||||
|
{
|
||||||
|
super(client, {
|
||||||
|
name: 'rejoin',
|
||||||
|
description: 'Configure if and where the bot will send a message when a kicked user rejoins the server',
|
||||||
|
display: 'Rejoin Logging',
|
||||||
|
moduleName: 'logging',
|
||||||
|
default: {
|
||||||
|
enabled: false,
|
||||||
|
channel: null,
|
||||||
|
message: 'The user **{mention}** ({id}) has rejoined the server after being kicked.',
|
||||||
|
},
|
||||||
|
definitions: {
|
||||||
|
enabled: 'BOOLEAN',
|
||||||
|
channel: 'GUILD_TEXT',
|
||||||
|
message: 'STRING'
|
||||||
|
},
|
||||||
|
commandOptions: [
|
||||||
|
{
|
||||||
|
name: 'enabled',
|
||||||
|
description: 'Toggle logging on or off',
|
||||||
|
type: CommandOptionType.BOOLEAN,
|
||||||
|
flag: true,
|
||||||
|
valueOptional: true,
|
||||||
|
defaultValue: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'channel',
|
||||||
|
description: 'Channel in which to output logs',
|
||||||
|
type: CommandOptionType.TEXT_CHANNEL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'message',
|
||||||
|
description: 'Message to send when a user rejoins',
|
||||||
|
type: CommandOptionType.STRING
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute (invoker: InvokerWrapper, opts: CommandParams, setting: RejoinSettings)
|
||||||
|
{
|
||||||
|
const channel = opts.channel?.asChannel;
|
||||||
|
const message = opts.message?.asString;
|
||||||
|
|
||||||
|
const index = 'SETTING_SUCCESS_ALT';
|
||||||
|
|
||||||
|
if (opts.enabled)
|
||||||
|
setting.enabled = opts.enabled.asBool;
|
||||||
|
|
||||||
|
if (opts.channel)
|
||||||
|
{
|
||||||
|
if (!(channel instanceof TextChannel))
|
||||||
|
throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' });
|
||||||
|
const perms = channel.permissionsFor(this.client.user!);
|
||||||
|
const missingPerms = perms?.missing([ 'ViewChannel', 'SendMessages' ]);
|
||||||
|
if (!missingPerms || missingPerms.length)
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
index: 'ERR_CHANNEL_PERMS',
|
||||||
|
params: { channel: channel.name, perms: missingPerms?.join(', ') ?? 'ALL' }
|
||||||
|
};
|
||||||
|
setting.channel = channel.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.message)
|
||||||
|
setting.message = message ?? null;
|
||||||
|
|
||||||
|
return { index };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Rejoin;
|
40
src/client/components/wrappers/EntityWrapper.ts
Normal file
40
src/client/components/wrappers/EntityWrapper.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// import { LoggerClient } from '@navy.gif/logger';
|
||||||
|
// import DiscordClient from '../../DiscordClient.js';
|
||||||
|
// import { EntityData } from '../../../../@types/Client.js';
|
||||||
|
|
||||||
|
// // TODO: Consider refactoring Guild and User wrappers to have a common ancestor
|
||||||
|
// class EntityWrapper<DataType extends EntityData>
|
||||||
|
// {
|
||||||
|
// #client: DiscordClient;
|
||||||
|
// #data!: DataType;
|
||||||
|
// #logger: LoggerClient;
|
||||||
|
// constructor (client: DiscordClient)
|
||||||
|
// {
|
||||||
|
// this.#client = client;
|
||||||
|
// this.#logger = client.createLogger(this, { name: this.constructor.name });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async banned ()
|
||||||
|
// {
|
||||||
|
// return this.#data.banned;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default EntityWrapper;
|
@ -26,7 +26,7 @@ import {
|
|||||||
} from '../../../../@types/Guild.js';
|
} from '../../../../@types/Guild.js';
|
||||||
import DiscordClient from '../../DiscordClient.js';
|
import DiscordClient from '../../DiscordClient.js';
|
||||||
|
|
||||||
const configVersion = '3.slash.2';
|
const configVersion = '3.slash.3';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Guild,
|
Guild,
|
||||||
@ -45,6 +45,7 @@ import {
|
|||||||
import MemberWrapper from './MemberWrapper.js';
|
import MemberWrapper from './MemberWrapper.js';
|
||||||
import { FilterUtil, Util } from '../../../utilities/index.js';
|
import { FilterUtil, Util } from '../../../utilities/index.js';
|
||||||
import { LoggerClient } from '@navy.gif/logger';
|
import { LoggerClient } from '@navy.gif/logger';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
class GuildWrapper
|
class GuildWrapper
|
||||||
{
|
{
|
||||||
@ -104,15 +105,16 @@ class GuildWrapper
|
|||||||
{
|
{
|
||||||
if (this.#data)
|
if (this.#data)
|
||||||
return this.#data;
|
return this.#data;
|
||||||
const data = await this.#client.mongodb.guilds.findOne<GuildData>({ guildId: this.id });
|
|
||||||
|
const data = await this.#client.mongodb.guilds.findOne({ $or: [{ guildId: this.id }, { id: this.id }] });
|
||||||
if (!data)
|
if (!data)
|
||||||
{
|
{
|
||||||
this.#data = {};
|
this.#data = { id: this.id };
|
||||||
return this.#data;
|
return this.#data;
|
||||||
}
|
}
|
||||||
if (data._version === '3.slash')
|
if (data._version === '3.slash')
|
||||||
{
|
{
|
||||||
const oldSettings = data as GuildSettings;
|
const oldSettings = data as unknown as GuildSettings;
|
||||||
const keys = Object.keys(this.defaultConfig);
|
const keys = Object.keys(this.defaultConfig);
|
||||||
const settings: PartialGuildSettings = {};
|
const settings: PartialGuildSettings = {};
|
||||||
for (const key of keys)
|
for (const key of keys)
|
||||||
@ -125,6 +127,13 @@ class GuildWrapper
|
|||||||
await this.#client.mongodb.guilds.deleteOne({ guildId: this.id });
|
await this.#client.mongodb.guilds.deleteOne({ guildId: this.id });
|
||||||
await this.#client.mongodb.guilds.updateOne({ guildId: this.id }, { $set: data });
|
await this.#client.mongodb.guilds.updateOne({ guildId: this.id }, { $set: data });
|
||||||
}
|
}
|
||||||
|
if (data._version === '3.slash.2')
|
||||||
|
{
|
||||||
|
data.id = data.guildId as string;
|
||||||
|
delete data.guildId;
|
||||||
|
data._version = configVersion; // 3.slash.3
|
||||||
|
await this.#client.mongodb.guilds.updateOne({ _id: data._id as ObjectId }, { $set: data });
|
||||||
|
}
|
||||||
this.#data = data;
|
this.#data = data;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -136,10 +145,8 @@ class GuildWrapper
|
|||||||
return this.#settings;
|
return this.#settings;
|
||||||
|
|
||||||
const data = await this.fetchData();
|
const data = await this.fetchData();
|
||||||
// eslint-disable-next-line prefer-const
|
|
||||||
const {
|
const {
|
||||||
settings,
|
settings,
|
||||||
// _imported
|
|
||||||
} = data;
|
} = data;
|
||||||
const { defaultConfig } = this;
|
const { defaultConfig } = this;
|
||||||
|
|
||||||
@ -168,7 +175,7 @@ class GuildWrapper
|
|||||||
return this.#settings;
|
return this.#settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateData (data: GuildData)
|
async updateData (data: Partial<GuildData>)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -194,6 +201,22 @@ class GuildWrapper
|
|||||||
} as GuildSettings;
|
} as GuildSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if guild has been banned from using the bot
|
||||||
|
*/
|
||||||
|
async botBanned ()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBotBan (value = true)
|
||||||
|
{
|
||||||
|
if (!this.#data)
|
||||||
|
await this.fetchData();
|
||||||
|
this.#data.banned = value;
|
||||||
|
await this.updateData({ banned: value });
|
||||||
|
}
|
||||||
|
|
||||||
async permissions ()
|
async permissions ()
|
||||||
{
|
{
|
||||||
if (this.#permissions)
|
if (this.#permissions)
|
||||||
|
@ -16,47 +16,177 @@
|
|||||||
|
|
||||||
import { ImageURLOptions, MessageCreateOptions, MessagePayload, User } from 'discord.js';
|
import { ImageURLOptions, MessageCreateOptions, MessagePayload, User } from 'discord.js';
|
||||||
import DiscordClient from '../../DiscordClient.js';
|
import DiscordClient from '../../DiscordClient.js';
|
||||||
import { UserSettings } from '../../../../@types/Settings.js';
|
|
||||||
import { LoggerClient } from '@navy.gif/logger';
|
import { LoggerClient } from '@navy.gif/logger';
|
||||||
|
import { UserData, UserSettings } from '../../../../@types/User.js';
|
||||||
|
|
||||||
class UserWrapper
|
class UserWrapper
|
||||||
{
|
{
|
||||||
#client: DiscordClient;
|
#client: DiscordClient;
|
||||||
#user: User;
|
#user: User;
|
||||||
|
|
||||||
#settings: UserSettings;
|
#data!: UserData;
|
||||||
// #points: {
|
|
||||||
// [key: string]: {
|
|
||||||
// expirations: Expiry[],
|
|
||||||
// points: number
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
#logger: LoggerClient;
|
#logger: LoggerClient;
|
||||||
|
#settings: UserSettings;
|
||||||
|
|
||||||
constructor (client: DiscordClient, user: User)
|
constructor (client: DiscordClient, user: User)
|
||||||
{
|
{
|
||||||
this.#client = client;
|
this.#client = client;
|
||||||
this.#user = user;
|
this.#user = user;
|
||||||
this.#logger = client.createLogger({ name: `User: ${user.id}` });
|
this.#logger = client.createLogger({ name: `User: ${user.id}` });
|
||||||
|
}
|
||||||
|
|
||||||
this.#settings = {};
|
async fetchData ()
|
||||||
// this.#points = {};
|
{
|
||||||
|
if (this.#data)
|
||||||
|
return this.#data;
|
||||||
|
|
||||||
|
const data = await this.#client.mongodb.users.findOne({ id: this.id });
|
||||||
|
if (!data)
|
||||||
|
{
|
||||||
|
this.#data = { id: this.id };
|
||||||
|
return this.#data;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#data = data;
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async settings (forceFetch = false)
|
async settings (forceFetch = false)
|
||||||
{
|
{
|
||||||
if (this.#settings && !forceFetch)
|
if (this.#data && !forceFetch)
|
||||||
return this.#settings;
|
return this.#data.settings;
|
||||||
|
|
||||||
|
const data = await this.fetchData();
|
||||||
|
const { settings } = data;
|
||||||
|
const { defaultConfig } = this;
|
||||||
|
|
||||||
const settings = await this.#client.mongodb.users.findOne({ userId: this.id });
|
|
||||||
if (settings)
|
if (settings)
|
||||||
this.#settings = { ...this.defaultConfig, ...settings };
|
{
|
||||||
else
|
const keys = Object.keys(settings);
|
||||||
this.#settings = { userId: this.id, ...this.defaultConfig };
|
for (const key of keys)
|
||||||
|
{
|
||||||
|
defaultConfig[key] = { ...defaultConfig[key], [key]: settings[key] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#settings = defaultConfig;
|
||||||
return this.#settings;
|
return this.#settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the user is banned from using the bot
|
||||||
|
*/
|
||||||
|
async botBanned (): Promise<boolean>
|
||||||
|
{
|
||||||
|
const data = await this.fetchData();
|
||||||
|
return data?.banned ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBotBan (value = true)
|
||||||
|
{
|
||||||
|
if (!this.#data)
|
||||||
|
await this.fetchData();
|
||||||
|
this.#data!.banned = value;
|
||||||
|
await this.updateData({ banned: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSettings (settings: Partial<UserSettings>)
|
||||||
|
{
|
||||||
|
if (!this.#data?.settings)
|
||||||
|
await this.settings();
|
||||||
|
|
||||||
|
await this.updateData({ settings: settings as UserSettings });
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateData (data: Partial<UserData>)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await this.#client.mongodb.users.updateOne({ id: this.id }, { $set: data });
|
||||||
|
this.#data = { ...this.#data, ...data } as UserData;
|
||||||
|
this.#storageLog('Data update');
|
||||||
|
}
|
||||||
|
catch (err)
|
||||||
|
{
|
||||||
|
const error = err as Error;
|
||||||
|
this.#storageError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultConfig (): UserSettings
|
||||||
|
{
|
||||||
|
return JSON.parse(JSON.stringify(this.#client.defaultConfig('USER')));
|
||||||
|
}
|
||||||
|
|
||||||
|
createDM (force?: boolean)
|
||||||
|
{
|
||||||
|
return this.#user.createDM(force);
|
||||||
|
}
|
||||||
|
|
||||||
|
send (content: string | MessagePayload | MessageCreateOptions)
|
||||||
|
{
|
||||||
|
return this.#user.send(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
#storageLog (log: string)
|
||||||
|
{
|
||||||
|
this.#logger.debug(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
#storageError (error: Error)
|
||||||
|
{
|
||||||
|
this.#logger.error(`Database Error (user:${this.id}) : \n${error.stack || error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
displayAvatarURL (opts?: ImageURLOptions)
|
||||||
|
{
|
||||||
|
return this.#user.displayAvatarURL(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
get id ()
|
||||||
|
{
|
||||||
|
return this.#user.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name ()
|
||||||
|
{
|
||||||
|
return this.#user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
get username ()
|
||||||
|
{
|
||||||
|
return this.#user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tag ()
|
||||||
|
{
|
||||||
|
return this.#user.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayName ()
|
||||||
|
{
|
||||||
|
return this.#user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
get developer ()
|
||||||
|
{
|
||||||
|
return this.#client.opts.developers?.includes(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
get prefix ()
|
||||||
|
{
|
||||||
|
return this.#data?.settings.textcommands.prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
get locale ()
|
||||||
|
{
|
||||||
|
return this.#data?.settings.locale.language;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserWrapper;
|
||||||
|
|
||||||
// async fetchPoints (guild: GuildWrapper)
|
// async fetchPoints (guild: GuildWrapper)
|
||||||
// {
|
// {
|
||||||
// let index = this.#points[guild.id];
|
// let index = this.#points[guild.id];
|
||||||
@ -156,100 +286,3 @@ class UserWrapper
|
|||||||
// expirationPoints = [ 0 ];
|
// expirationPoints = [ 0 ];
|
||||||
// return expirationPoints.reduce((p, v) => p + v) + index.points;
|
// return expirationPoints.reduce((p, v) => p + v) + index.points;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async updateSettings (data: UserSettings)
|
|
||||||
{ // Update property (upsert true) - updateOne
|
|
||||||
if (!this.#settings)
|
|
||||||
await this.settings();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await this.#client.mongodb.users.updateOne(
|
|
||||||
{ guildId: this.id },
|
|
||||||
{ $set: data }
|
|
||||||
);
|
|
||||||
this.#settings = {
|
|
||||||
...this.#settings,
|
|
||||||
...data
|
|
||||||
};
|
|
||||||
this.#storageLog(`Database Update (guild:${this.id}).`);
|
|
||||||
}
|
|
||||||
catch (error)
|
|
||||||
{
|
|
||||||
this.#storageError(error as Error);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultConfig ()
|
|
||||||
{
|
|
||||||
return JSON.parse(JSON.stringify(this.#client.defaultConfig('USER')));
|
|
||||||
}
|
|
||||||
|
|
||||||
createDM (force?: boolean)
|
|
||||||
{
|
|
||||||
return this.#user.createDM(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
send (content: string | MessagePayload | MessageCreateOptions)
|
|
||||||
{
|
|
||||||
return this.#user.send(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
#storageLog (log: string)
|
|
||||||
{
|
|
||||||
this.#logger.debug(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
#storageError (error: Error)
|
|
||||||
{
|
|
||||||
this.#logger.error(`Database Error (user:${this.id}) : \n${error.stack || error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
displayAvatarURL (opts?: ImageURLOptions)
|
|
||||||
{
|
|
||||||
return this.#user.displayAvatarURL(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
get id ()
|
|
||||||
{
|
|
||||||
return this.#user.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get name ()
|
|
||||||
{
|
|
||||||
return this.#user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
get username ()
|
|
||||||
{
|
|
||||||
return this.#user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tag ()
|
|
||||||
{
|
|
||||||
return this.#user.tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayName ()
|
|
||||||
{
|
|
||||||
return this.#user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
get developer ()
|
|
||||||
{
|
|
||||||
return this.#client.opts.developers?.includes(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
get prefix ()
|
|
||||||
{
|
|
||||||
return this.#settings.prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
get locale ()
|
|
||||||
{
|
|
||||||
return this.#settings.locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserWrapper;
|
|
@ -264,12 +264,14 @@ class Infraction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the role structures as they will cause problems when serialising for database
|
||||||
|
if (this.#data.roles)
|
||||||
|
delete this.#data.roles;
|
||||||
|
|
||||||
if (this.#duration)
|
if (this.#duration)
|
||||||
await this.#client.moderation.handleTimedInfraction(this.json);
|
await this.#client.moderation.handleTimedInfraction(this.json);
|
||||||
|
|
||||||
/* LMAOOOO PLEASE DONT JUDGE ME */
|
this.client.emit('infraction', this);
|
||||||
if (this.#data.roles)
|
|
||||||
delete this.#data.roles;
|
|
||||||
|
|
||||||
return this.save();
|
return this.save();
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ class MariaDBProvider extends Provider
|
|||||||
{
|
{
|
||||||
const result = await new Promise<T[] | FieldInfo[] | undefined>((resolve, reject) =>
|
const result = await new Promise<T[] | FieldInfo[] | undefined>((resolve, reject) =>
|
||||||
{
|
{
|
||||||
const q = connection.query({ timeout, sql: query }, [ values ], (err, results, fields) =>
|
const q = connection.query({ timeout, sql: query }, values, (err, results, fields) =>
|
||||||
{
|
{
|
||||||
if (err)
|
if (err)
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
import { CallbackInfo } from '../../../../@types/CallbackManager.js';
|
import { CallbackInfo } from '../../../../@types/CallbackManager.js';
|
||||||
import { InfractionJSON } from '../../../../@types/Client.js';
|
import { InfractionJSON } from '../../../../@types/Client.js';
|
||||||
import { AttachmentData, GuildData, GuildPermissions, MessageLogEntry, RoleCacheEntry, WebhookEntry, WordWatcherEntry } from '../../../../@types/Guild.js';
|
import { AttachmentData, GuildData, GuildPermissions, MessageLogEntry, RoleCacheEntry, WebhookEntry, WordWatcherEntry } from '../../../../@types/Guild.js';
|
||||||
import { UserSettings } from '../../../../@types/Settings.js';
|
|
||||||
import { MongoDBOptions } from '../../../../@types/Storage.js';
|
import { MongoDBOptions } from '../../../../@types/Storage.js';
|
||||||
|
import { UserData } from '../../../../@types/User.js';
|
||||||
import DiscordClient from '../../DiscordClient.js';
|
import DiscordClient from '../../DiscordClient.js';
|
||||||
import { MongodbTable } from '../interfaces/index.js';
|
import { MongodbTable } from '../interfaces/index.js';
|
||||||
import Provider from '../interfaces/Provider.js';
|
import Provider from '../interfaces/Provider.js';
|
||||||
@ -142,7 +142,7 @@ class MongoDBProvider extends Provider
|
|||||||
|
|
||||||
get users ()
|
get users ()
|
||||||
{
|
{
|
||||||
return this.tables.users as MongodbTable<UserSettings>;
|
return this.tables.users as MongodbTable<UserData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
get roleCache ()
|
get roleCache ()
|
||||||
|
23
src/constants/Quotes.ts
Normal file
23
src/constants/Quotes.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export default [
|
||||||
|
['Pacifism is objectively pro-fascist. This is elementary common sense. If you hamper the war effort of one side, you automatically help out that of the other. Nor is there any real way of remaining outside such a war as the present one. In practice, \'he that is not with me is against me\'.', 'George Orwell'],
|
||||||
|
['If people are good only because they fear punishment, and hope for reward, then we are a sorry lot indeed.', 'Albert Einstein'],
|
||||||
|
['The ultimate tragedy is not the oppression and cruelty by the bad people but the silence over that by the good people.', 'Martin Luther King Jr.'],
|
||||||
|
['The Three Laws of Robotics are:\n**1.** A robot may not injure a human being or, through inaction, allow a human being to come to harm.\n**2.** A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.\n**3.** A robot must protect its own existence as long as such protection does not conflict with the First or Second Laws.', 'Isaac Asimov'],
|
||||||
|
['That\'s why they call it the American Dream, because you have to be asleep to believe it.', 'George Carlin'],
|
||||||
|
['Think of how stupid the average person is, and realize half of them are stupider than that.', 'George Carlin'],
|
||||||
|
['In another life, I would have really liked just doing laundry and taxes with you.', 'Waymond Wang (Everything Everywhere All at Once)'],
|
||||||
|
['I\'m not upset that you lied to me, I\'m upset that I can\'t trust you again.', 'Friedrich Nietzsche'],
|
||||||
|
['Religion is the impotence of the human mind to deal with occurrences it cannot understand.', 'Karl Marx'],
|
||||||
|
['He wears a mask, and his face grows to fit it.', 'George Orwell'],
|
||||||
|
['Darkness cannot drive out darkness: only light can do that. Hate cannot drive out hate: only love can do that.', 'Martin Luther King Jr.'],
|
||||||
|
['Without music, life would be a mistake.', 'Friedrich Nietzsche'],
|
||||||
|
['People don\'t get what they deserve. They just get what they get. There\'s nothing any of us can do about it.', 'Gregory House (House)'],
|
||||||
|
['Rational arguments don\'t usually work on religious people. Otherwise there would be no religious people.', 'Gregory House (House)'],
|
||||||
|
['It\'s never lupus', 'Gregory House (House)'],
|
||||||
|
['Almost dying changes nothing. Dying changes everything.', 'Gregory House (House)'],
|
||||||
|
['Do not go gentle into that good night,\nOld age should burn and rave at close of day;\nRage, rage against the dying of the light.', 'Dylan Thomas'],
|
||||||
|
['Mankind was born on Earth. It was never meant to die here.','Joseph Cooper (Interstellar)'],
|
||||||
|
['Once you\'re a parent, you\'re the ghost of your children\'s future.','Joseph Cooper (Interstellar)'],
|
||||||
|
['When we deny the EVIL within ourselves, we dehumanize ourselves, and we deprive ourselves not only of our own destiny but of any possibility of dealing with the EVIL of others.','J. Robert Oppenheimer'],
|
||||||
|
['Are you not entertained? Are you not entertained? Is this not why you are here?', 'Maximus Decimus Meridius (Gladiator)']
|
||||||
|
]
|
@ -27,3 +27,6 @@ The command **{command}** can only be run by developers.
|
|||||||
|
|
||||||
[INHIBITOR_GUILDONLY_ERROR]
|
[INHIBITOR_GUILDONLY_ERROR]
|
||||||
The command **{command}** is only available in servers.
|
The command **{command}** is only available in servers.
|
||||||
|
|
||||||
|
[INHIBITOR_BANNED_ERROR]
|
||||||
|
{noun} has been banned from using the bot.
|
5
src/localization/en_gb/general/en_gb_nouns.lang
Normal file
5
src/localization/en_gb/general/en_gb_nouns.lang
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[NOUN_USER]
|
||||||
|
user
|
||||||
|
|
||||||
|
[NOUN_GUILD]
|
||||||
|
server
|
@ -163,6 +163,11 @@ User ID: {id}
|
|||||||
Bot missing permissions in nickname log channel
|
Bot missing permissions in nickname log channel
|
||||||
Missing: {missing}
|
Missing: {missing}
|
||||||
|
|
||||||
|
//Rejoin Tracking Logs
|
||||||
|
[REJOINLOG_NO_PERMS]
|
||||||
|
Bot missing permissions in rejoin log channel
|
||||||
|
Missing: {missing}
|
||||||
|
|
||||||
//Bulk Delete Logs
|
//Bulk Delete Logs
|
||||||
[BULK_DELETE_TITLE]
|
[BULK_DELETE_TITLE]
|
||||||
Bulk message delete log [{embedNr}] in **#{channel}**
|
Bulk message delete log [{embedNr}] in **#{channel}**
|
||||||
|
@ -42,3 +42,7 @@ Configure member nickname logging for your server.
|
|||||||
|
|
||||||
[SETTING_VOICE_HELP]
|
[SETTING_VOICE_HELP]
|
||||||
Configure logging of voice joins and leaves for your server.
|
Configure logging of voice joins and leaves for your server.
|
||||||
|
|
||||||
|
//Rejoin logs
|
||||||
|
[SETTING_REJOIN_HELP]
|
||||||
|
Configure if and where a message is sent when a user who has been kicked rejoins the server.
|
Loading…
Reference in New Issue
Block a user