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": {
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-misused-promises": ["error", {
|
||||
// "checksVoidReturn": false
|
||||
}],
|
||||
"accessor-pairs": "warn",
|
||||
"array-callback-return": "warn",
|
||||
"array-bracket-newline": [
|
||||
|
@ -1,3 +1,7 @@
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
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 DiscordClient from '../src/client/DiscordClient.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 { FilterResult } from './Utils.js';
|
||||
import { GuildSettingTypes } from './Guild.js';
|
||||
@ -114,6 +114,7 @@ export type ObserverOptions = {
|
||||
export type InhibitorOptions = {
|
||||
name?: string,
|
||||
guild?: boolean
|
||||
// Higher numbers come first
|
||||
priority?: number,
|
||||
silent?: boolean
|
||||
} & Partial<ComponentOptions>
|
||||
@ -348,6 +349,7 @@ export type InfractionArguments = {
|
||||
}
|
||||
|
||||
export type AdditionalInfractionData = {
|
||||
track?: boolean;
|
||||
muteType?: MuteType,
|
||||
roles?: Role[]
|
||||
roleIds?: Snowflake[],
|
||||
@ -501,4 +503,15 @@ export declare interface ExtendedVoiceState extends VoiceState {
|
||||
|
||||
export declare interface ExtendedInvite extends Invite {
|
||||
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
|
||||
// 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 { ExtendedGuildBan, ExtendedMessage } from './Client.ts';
|
||||
import { APIRequest } from '@discordjs/rest';
|
||||
import { AutomodErrorProps, LinkFilterWarnProps, LogErrorProps, MissingPermsProps, UtilityErrorProps, WordWatcherErrorProps } from './Moderation.js';
|
||||
|
||||
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 {
|
||||
componentUpdate: [data: ComponentUpdate],
|
||||
rateLimit: [rateLimitInfo: RateLimitData];
|
||||
@ -36,4 +43,5 @@ export interface ClientEvents extends CE {
|
||||
utilityError: [UtilityErrorProps];
|
||||
linkFilterWarn: [LinkFilterWarnProps];
|
||||
filterMissingPermissions: [MissingPermsProps];
|
||||
infraction: [Infraction];
|
||||
}
|
8
@types/Guild.d.ts
vendored
8
@types/Guild.d.ts
vendored
@ -37,6 +37,7 @@ import {
|
||||
PermissionSettings,
|
||||
ProtectionSettings,
|
||||
RaidprotectionSettings,
|
||||
RejoinSettings,
|
||||
SelfroleSettings,
|
||||
SilenceSettings,
|
||||
StaffSettings,
|
||||
@ -48,6 +49,7 @@ import {
|
||||
WordWatcherSettings
|
||||
} from './Settings.js';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import { EntityData, EntitySettings } from './Client.ts';
|
||||
|
||||
export type GuildSettingTypes =
|
||||
| AutomodSettings
|
||||
@ -79,6 +81,7 @@ export type GuildSettingTypes =
|
||||
| WordFilterSettings
|
||||
| WordWatcherSettings
|
||||
| LocaleSettings
|
||||
| RejoinSettings
|
||||
|
||||
export type PartialGuildSettings = Partial<GuildSettings>
|
||||
// {
|
||||
@ -117,7 +120,8 @@ export type GuildSettings = {
|
||||
invitefilter: InviteFilterSettings,
|
||||
mentionfilter: MentionFilterSettings,
|
||||
raidprotection: RaidprotectionSettings,
|
||||
}
|
||||
rejoin: RejoinSettings
|
||||
} & EntitySettings
|
||||
|
||||
export type PermissionSet = {
|
||||
global: string[],
|
||||
@ -143,7 +147,7 @@ export type GuildData = {
|
||||
modlogs?: boolean,
|
||||
settings?: boolean
|
||||
}
|
||||
}
|
||||
} & EntityData
|
||||
|
||||
export type CallbackData = {
|
||||
type: string,
|
||||
|
@ -17,11 +17,6 @@
|
||||
import { Snowflake } from 'discord.js';
|
||||
import { InfractionType, SettingAction } from './Client.js';
|
||||
|
||||
export type UserSettings = {
|
||||
prefix?: string,
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export type Setting = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any
|
||||
@ -212,4 +207,9 @@ export type LocaleSettings = {
|
||||
|
||||
export type RaidprotectionSettings = {
|
||||
//
|
||||
} & 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)
|
||||
- RabbitMQ (maybe, TBD)
|
||||
|
||||
A Docker compose file is available for convenience for setting up databases for development or personal deployment.
|
||||
|
||||
### Running
|
||||
- Install the dependencies: `yarn install`
|
||||
- Run the bot: `yarn start`
|
||||
@ -40,4 +42,8 @@ New feature branch -> development (local bots) -> alpha (QA) -> beta (GalacticTe
|
||||
- Proper workflows with gitea actions.
|
||||
- Linter
|
||||
- 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": "!",
|
||||
"developers": [
|
||||
"132777808362471424",
|
||||
"187613017733726210"
|
||||
"187613017733726210",
|
||||
"132620781791346688"
|
||||
],
|
||||
"developmentMode": false,
|
||||
"libraryOptions": {
|
||||
@ -35,7 +36,11 @@
|
||||
"GuildMessageReactions",
|
||||
"DirectMessages"
|
||||
],
|
||||
"invalidRequestWarningInterval": 500
|
||||
"invalidRequestWarningInterval": 500,
|
||||
"rest": {
|
||||
"timeout": 30000,
|
||||
"retries": 5
|
||||
}
|
||||
},
|
||||
"invite": "https://discord.gg/WDCTKGp",
|
||||
"slashCommands": {
|
||||
@ -79,7 +84,7 @@
|
||||
]
|
||||
},
|
||||
"mariadb": {
|
||||
"load": false,
|
||||
"load": true,
|
||||
"tables": []
|
||||
}
|
||||
},
|
||||
|
@ -235,6 +235,7 @@ class DiscordClient extends Client
|
||||
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('SIGINT', () => this.logger.info('Received SIGINT'));
|
||||
process.on('SIGTERM', () => this.logger.info('Received SIGTERM'));
|
||||
@ -524,6 +525,7 @@ class DiscordClient extends Client
|
||||
return this.localeLoader.format(language, index, params, code);
|
||||
}
|
||||
|
||||
// TODO: Combine these
|
||||
async getGuildWrapper (id: string)
|
||||
{
|
||||
if (this.#guildWrappers.has(id))
|
||||
@ -551,6 +553,11 @@ class DiscordClient extends Client
|
||||
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?: true): Promise<UserWrapper | null>;
|
||||
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)
|
||||
{
|
||||
const code = DataResolver.resolveInviteCode(invite);
|
||||
@ -639,6 +656,11 @@ class DiscordClient extends Client
|
||||
return this.#storageManager.mongodb;
|
||||
}
|
||||
|
||||
get mariadb ()
|
||||
{
|
||||
return this.#storageManager.mariadb;
|
||||
}
|
||||
|
||||
get moderation ()
|
||||
{
|
||||
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;
|
@ -28,19 +28,46 @@ class DebugCommand extends Command
|
||||
restricted: true,
|
||||
moduleName: 'developer',
|
||||
options: [
|
||||
{ name: 'guild', required: true },
|
||||
{ name: 'enabled', required: true, type: CommandOptionType.BOOLEAN }
|
||||
{
|
||||
name: [ 'enable', 'disable' ],
|
||||
type: CommandOptionType.SUB_COMMAND,
|
||||
options: [
|
||||
{ name: 'guild', required: true },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'list',
|
||||
type: CommandOptionType.SUB_COMMAND,
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async execute (_invoker: InvokerWrapper, options: CommandParams)
|
||||
async execute (invoker: InvokerWrapper, options: CommandParams)
|
||||
{
|
||||
const guildId = options.guild!.asString;
|
||||
return this.enableDebug(guildId, options.enabled?.asBool);
|
||||
const { subcommand } = invoker;
|
||||
if (!subcommand)
|
||||
throw new Error('Missing subcommand');
|
||||
|
||||
if ([ 'enable', 'disable' ].includes(subcommand.name))
|
||||
{
|
||||
const guildId = options.guild!.asString;
|
||||
return this.toggleDebug(guildId, subcommand.name === 'enable');
|
||||
}
|
||||
|
||||
if (subcommand.name === 'list')
|
||||
return this.listDebugGuilds();
|
||||
}
|
||||
|
||||
async enableDebug (guildId: string, enabled = false)
|
||||
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 = '';
|
||||
if (this.client.shard)
|
||||
|
@ -14,7 +14,7 @@
|
||||
// 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 { CommandParams } from '../../../../../@types/Client.js';
|
||||
import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js';
|
||||
import DiscordClient from '../../../DiscordClient.js';
|
||||
import { Kick } from '../../../infractions/index.js';
|
||||
import { ModerationCommand } from '../../../interfaces/index.js';
|
||||
@ -29,7 +29,16 @@ class KickCommand extends ModerationCommand
|
||||
name: 'kick',
|
||||
description: 'Kick people.',
|
||||
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,
|
||||
showUsage: true,
|
||||
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)));
|
||||
return this.client.moderation.handleInfraction(Kick, invoker, {
|
||||
targets: wrappers.filter(Boolean) as MemberWrapper[],
|
||||
data: {
|
||||
track: track?.asBool
|
||||
},
|
||||
args
|
||||
});
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
import { SlashCommand } from '../../../interfaces/index.js';
|
||||
import DiscordClient from '../../../DiscordClient.js';
|
||||
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
|
||||
import Quotes from '../../../../constants/Quotes.js';
|
||||
|
||||
class PingCommand extends SlashCommand
|
||||
{
|
||||
@ -37,7 +38,10 @@ class PingCommand extends SlashCommand
|
||||
const { ping } = this.client.ws;
|
||||
const number = (ping / 40);
|
||||
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, {
|
||||
name: 'channelIgnore',
|
||||
priority: 9,
|
||||
priority: 8,
|
||||
guild: true,
|
||||
silent: true
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ class ClientPermissions extends Inhibitor
|
||||
{
|
||||
super(client, {
|
||||
name: 'clientPermissions',
|
||||
priority: 10,
|
||||
priority: 9,
|
||||
guarded: true,
|
||||
guild: true
|
||||
});
|
||||
|
@ -287,7 +287,7 @@ class CommandHandler extends Observer
|
||||
{
|
||||
if (!(invoker.command instanceof SettingsCommand))
|
||||
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' });
|
||||
}
|
||||
return;
|
||||
|
@ -25,6 +25,8 @@ import { AttachmentData, MessageLogEntry } from '../../../../@types/Guild.js';
|
||||
import { stripIndents } from 'common-tags';
|
||||
import moment from 'moment';
|
||||
import { inspect } from 'util';
|
||||
import Infraction from '../../interfaces/Infraction.js';
|
||||
import { RejoinLogEntry } from '../../../../@types/Events.js';
|
||||
|
||||
/* eslint-disable no-labels */
|
||||
const CONSTANTS: {
|
||||
@ -81,7 +83,9 @@ class GuildLogger extends Observer
|
||||
[ 'guildMemberUpdate', this.memberUpdate.bind(this) ],
|
||||
[ 'threadCreate', this.threadCreate.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)
|
||||
@ -955,6 +959,60 @@ class GuildLogger extends Observer
|
||||
};
|
||||
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;
|
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';
|
||||
import DiscordClient from '../../DiscordClient.js';
|
||||
|
||||
const configVersion = '3.slash.2';
|
||||
const configVersion = '3.slash.3';
|
||||
|
||||
import {
|
||||
Guild,
|
||||
@ -45,6 +45,7 @@ import {
|
||||
import MemberWrapper from './MemberWrapper.js';
|
||||
import { FilterUtil, Util } from '../../../utilities/index.js';
|
||||
import { LoggerClient } from '@navy.gif/logger';
|
||||
import { ObjectId } from 'mongodb';
|
||||
|
||||
class GuildWrapper
|
||||
{
|
||||
@ -104,15 +105,16 @@ class GuildWrapper
|
||||
{
|
||||
if (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)
|
||||
{
|
||||
this.#data = {};
|
||||
this.#data = { id: this.id };
|
||||
return this.#data;
|
||||
}
|
||||
if (data._version === '3.slash')
|
||||
{
|
||||
const oldSettings = data as GuildSettings;
|
||||
const oldSettings = data as unknown as GuildSettings;
|
||||
const keys = Object.keys(this.defaultConfig);
|
||||
const settings: PartialGuildSettings = {};
|
||||
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.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;
|
||||
return data;
|
||||
}
|
||||
@ -136,10 +145,8 @@ class GuildWrapper
|
||||
return this.#settings;
|
||||
|
||||
const data = await this.fetchData();
|
||||
// eslint-disable-next-line prefer-const
|
||||
const {
|
||||
settings,
|
||||
// _imported
|
||||
} = data;
|
||||
const { defaultConfig } = this;
|
||||
|
||||
@ -168,7 +175,7 @@ class GuildWrapper
|
||||
return this.#settings;
|
||||
}
|
||||
|
||||
async updateData (data: GuildData)
|
||||
async updateData (data: Partial<GuildData>)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -194,6 +201,22 @@ class GuildWrapper
|
||||
} 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 ()
|
||||
{
|
||||
if (this.#permissions)
|
||||
|
@ -16,171 +16,104 @@
|
||||
|
||||
import { ImageURLOptions, MessageCreateOptions, MessagePayload, User } from 'discord.js';
|
||||
import DiscordClient from '../../DiscordClient.js';
|
||||
import { UserSettings } from '../../../../@types/Settings.js';
|
||||
import { LoggerClient } from '@navy.gif/logger';
|
||||
import { UserData, UserSettings } from '../../../../@types/User.js';
|
||||
|
||||
class UserWrapper
|
||||
{
|
||||
#client: DiscordClient;
|
||||
#user: User;
|
||||
|
||||
#settings: UserSettings;
|
||||
// #points: {
|
||||
// [key: string]: {
|
||||
// expirations: Expiry[],
|
||||
// points: number
|
||||
// }
|
||||
// };
|
||||
|
||||
#data!: UserData;
|
||||
#logger: LoggerClient;
|
||||
#settings: UserSettings;
|
||||
|
||||
constructor (client: DiscordClient, user: User)
|
||||
{
|
||||
this.#client = client;
|
||||
this.#user = user;
|
||||
this.#logger = client.createLogger({ name: `User: ${user.id}` });
|
||||
}
|
||||
|
||||
this.#settings = {};
|
||||
// this.#points = {};
|
||||
async fetchData ()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (this.#settings && !forceFetch)
|
||||
return this.#settings;
|
||||
if (this.#data && !forceFetch)
|
||||
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)
|
||||
this.#settings = { ...this.defaultConfig, ...settings };
|
||||
else
|
||||
this.#settings = { userId: this.id, ...this.defaultConfig };
|
||||
{
|
||||
const keys = Object.keys(settings);
|
||||
for (const key of keys)
|
||||
{
|
||||
defaultConfig[key] = { ...defaultConfig[key], [key]: settings[key] };
|
||||
}
|
||||
}
|
||||
|
||||
this.#settings = defaultConfig;
|
||||
return this.#settings;
|
||||
}
|
||||
|
||||
// async fetchPoints (guild: GuildWrapper)
|
||||
// {
|
||||
// let index = this.#points[guild.id];
|
||||
// if (index)
|
||||
// return Promise.resolve(index);
|
||||
// this.#points[guild.id] = {
|
||||
// expirations: [],
|
||||
// points: 0
|
||||
// };
|
||||
// index = this.#points[guild.id];
|
||||
|
||||
// const filter = {
|
||||
// guild: guild.id,
|
||||
// target: this.id,
|
||||
// resolved: false,
|
||||
// // points: { $gte: 0 },
|
||||
// // $or: [{ expiration: 0 }, { expiration: { $gte: now } }]
|
||||
// };
|
||||
// const find = await this.#client.mongodb.infractions.find(
|
||||
// filter,
|
||||
// { projection: { id: 1, points: 1, expiration: 1 } }
|
||||
// );
|
||||
|
||||
// if (find.length)
|
||||
// {
|
||||
// for (const { points: p, expiration, id } of find)
|
||||
// {
|
||||
// let points = p;
|
||||
// // Imported cases may have false or null
|
||||
// if (typeof points !== 'number')
|
||||
// points = 0;
|
||||
// if (expiration > 0)
|
||||
// {
|
||||
// index.expirations.push({ points, expiration, id });
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// index.points += points;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return index;
|
||||
// }
|
||||
|
||||
// async editPoints (guild: GuildWrapper, { id, diff, expiration }: {id: string, diff: number, expiration: unknown })
|
||||
// {
|
||||
// const points = await this.fetchPoints(guild);
|
||||
// if (expiration)
|
||||
// {
|
||||
// const expiry = points.expirations.find((exp) => exp.id === id) as Expiry;
|
||||
// expiry.points += diff;
|
||||
// }
|
||||
// else
|
||||
// points.points += diff;
|
||||
// return this.totalPoints(guild);
|
||||
// }
|
||||
|
||||
// async editExpiration (guild: GuildWrapper, { id, expiration, points }: { id: string, expiration: number, points: number })
|
||||
// {
|
||||
// const index = await this.fetchPoints(guild);
|
||||
// const i = index.expirations.findIndex((exp) => exp.id === id);
|
||||
// if (i > -1)
|
||||
// index.expirations[i].expiration = expiration;
|
||||
// else
|
||||
// {
|
||||
// index.points -= points;
|
||||
// index.expirations.push({ id, points, expiration });
|
||||
// }
|
||||
// return this.totalPoints(guild);
|
||||
// }
|
||||
|
||||
// async totalPoints (guild: GuildWrapper, point?: { id: string, points: number, expiration: number, timestamp: number })
|
||||
// { // point = { points: x, expiration: x, timestamp: x}
|
||||
// const index = await this.fetchPoints(guild);
|
||||
// const now = Date.now();
|
||||
|
||||
// if (point)
|
||||
// {
|
||||
// if (point.expiration > 0)
|
||||
// {
|
||||
// index.expirations.push({ id: point.id, points: point.points, expiration: point.expiration + point.timestamp });
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// index.points += point.points;
|
||||
// }
|
||||
// }
|
||||
|
||||
// let expirationPoints = index.expirations.map((e) =>
|
||||
// {
|
||||
// if (e.expiration >= now)
|
||||
// return e.points;
|
||||
// return 0;
|
||||
// });
|
||||
|
||||
// if (expirationPoints.length === 0)
|
||||
// expirationPoints = [ 0 ];
|
||||
// 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;
|
||||
/**
|
||||
* Check whether the user is banned from using the bot
|
||||
*/
|
||||
async botBanned (): Promise<boolean>
|
||||
{
|
||||
const data = await this.fetchData();
|
||||
return data?.banned ?? false;
|
||||
}
|
||||
|
||||
get defaultConfig ()
|
||||
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')));
|
||||
}
|
||||
@ -242,14 +175,114 @@ class UserWrapper
|
||||
|
||||
get prefix ()
|
||||
{
|
||||
return this.#settings.prefix;
|
||||
return this.#data?.settings.textcommands.prefix;
|
||||
}
|
||||
|
||||
get locale ()
|
||||
{
|
||||
return this.#settings.locale;
|
||||
return this.#data?.settings.locale.language;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default UserWrapper;
|
||||
export default UserWrapper;
|
||||
|
||||
// async fetchPoints (guild: GuildWrapper)
|
||||
// {
|
||||
// let index = this.#points[guild.id];
|
||||
// if (index)
|
||||
// return Promise.resolve(index);
|
||||
// this.#points[guild.id] = {
|
||||
// expirations: [],
|
||||
// points: 0
|
||||
// };
|
||||
// index = this.#points[guild.id];
|
||||
|
||||
// const filter = {
|
||||
// guild: guild.id,
|
||||
// target: this.id,
|
||||
// resolved: false,
|
||||
// // points: { $gte: 0 },
|
||||
// // $or: [{ expiration: 0 }, { expiration: { $gte: now } }]
|
||||
// };
|
||||
// const find = await this.#client.mongodb.infractions.find(
|
||||
// filter,
|
||||
// { projection: { id: 1, points: 1, expiration: 1 } }
|
||||
// );
|
||||
|
||||
// if (find.length)
|
||||
// {
|
||||
// for (const { points: p, expiration, id } of find)
|
||||
// {
|
||||
// let points = p;
|
||||
// // Imported cases may have false or null
|
||||
// if (typeof points !== 'number')
|
||||
// points = 0;
|
||||
// if (expiration > 0)
|
||||
// {
|
||||
// index.expirations.push({ points, expiration, id });
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// index.points += points;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return index;
|
||||
// }
|
||||
|
||||
// async editPoints (guild: GuildWrapper, { id, diff, expiration }: {id: string, diff: number, expiration: unknown })
|
||||
// {
|
||||
// const points = await this.fetchPoints(guild);
|
||||
// if (expiration)
|
||||
// {
|
||||
// const expiry = points.expirations.find((exp) => exp.id === id) as Expiry;
|
||||
// expiry.points += diff;
|
||||
// }
|
||||
// else
|
||||
// points.points += diff;
|
||||
// return this.totalPoints(guild);
|
||||
// }
|
||||
|
||||
// async editExpiration (guild: GuildWrapper, { id, expiration, points }: { id: string, expiration: number, points: number })
|
||||
// {
|
||||
// const index = await this.fetchPoints(guild);
|
||||
// const i = index.expirations.findIndex((exp) => exp.id === id);
|
||||
// if (i > -1)
|
||||
// index.expirations[i].expiration = expiration;
|
||||
// else
|
||||
// {
|
||||
// index.points -= points;
|
||||
// index.expirations.push({ id, points, expiration });
|
||||
// }
|
||||
// return this.totalPoints(guild);
|
||||
// }
|
||||
|
||||
// async totalPoints (guild: GuildWrapper, point?: { id: string, points: number, expiration: number, timestamp: number })
|
||||
// { // point = { points: x, expiration: x, timestamp: x}
|
||||
// const index = await this.fetchPoints(guild);
|
||||
// const now = Date.now();
|
||||
|
||||
// if (point)
|
||||
// {
|
||||
// if (point.expiration > 0)
|
||||
// {
|
||||
// index.expirations.push({ id: point.id, points: point.points, expiration: point.expiration + point.timestamp });
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// index.points += point.points;
|
||||
// }
|
||||
// }
|
||||
|
||||
// let expirationPoints = index.expirations.map((e) =>
|
||||
// {
|
||||
// if (e.expiration >= now)
|
||||
// return e.points;
|
||||
// return 0;
|
||||
// });
|
||||
|
||||
// if (expirationPoints.length === 0)
|
||||
// expirationPoints = [ 0 ];
|
||||
// return expirationPoints.reduce((p, v) => p + v) + index.points;
|
||||
// }
|
@ -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)
|
||||
await this.#client.moderation.handleTimedInfraction(this.json);
|
||||
|
||||
/* LMAOOOO PLEASE DONT JUDGE ME */
|
||||
if (this.#data.roles)
|
||||
delete this.#data.roles;
|
||||
this.client.emit('infraction', this);
|
||||
|
||||
return this.save();
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ class MariaDBProvider extends Provider
|
||||
{
|
||||
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)
|
||||
reject(err);
|
||||
|
@ -17,8 +17,8 @@
|
||||
import { CallbackInfo } from '../../../../@types/CallbackManager.js';
|
||||
import { InfractionJSON } from '../../../../@types/Client.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 { UserData } from '../../../../@types/User.js';
|
||||
import DiscordClient from '../../DiscordClient.js';
|
||||
import { MongodbTable } from '../interfaces/index.js';
|
||||
import Provider from '../interfaces/Provider.js';
|
||||
@ -142,7 +142,7 @@ class MongoDBProvider extends Provider
|
||||
|
||||
get users ()
|
||||
{
|
||||
return this.tables.users as MongodbTable<UserSettings>;
|
||||
return this.tables.users as MongodbTable<UserData>;
|
||||
}
|
||||
|
||||
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)']
|
||||
]
|
@ -26,4 +26,7 @@ The command **{command}** requires the __bot__ to have permissions to use.
|
||||
The command **{command}** can only be run by developers.
|
||||
|
||||
[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
|
@ -1,180 +1,185 @@
|
||||
// Message logs
|
||||
[MSGLOG_DELETE_TITLE]
|
||||
{emoji_trash} {author}'s message was deleted in #{channel}
|
||||
|
||||
[MSGLOG_NOCONTENT]
|
||||
**__NO TEXT CONTENT__**
|
||||
|
||||
[MSGLOG_REPLY]
|
||||
Message was in reply to user {tag} ({id}):
|
||||
|
||||
[MSGLOG_REPLY_VALUE]
|
||||
**[Jump]({link})**
|
||||
{content}
|
||||
|
||||
[MSGLOG_REPLY_NOCONTENT]
|
||||
**__Missing content.__**
|
||||
|
||||
[MSGLOG_FILTERED]
|
||||
The message was filtered:
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_WORD]
|
||||
Filter: **{matcher}**
|
||||
Matched: **{match}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_INVITE]
|
||||
Filter: **{matcher}**
|
||||
Matched: **{match}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_LINK]
|
||||
Filter: **{matcher}**
|
||||
Matched: **{match}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_MENTION]
|
||||
Filter: **{filter}**
|
||||
Amount: **{amount}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_WORDWATCHER]
|
||||
Filter: **{filter}**
|
||||
Action: **{action}**
|
||||
|
||||
[MSGLOG_FILTERED_SANCTIONED]
|
||||
__User was sanctioned.__ {emoji_hammer}
|
||||
|
||||
[MSGLOG_DELETE_FOOTER]
|
||||
Message ID: {msgID} | User ID: {userID}
|
||||
|
||||
[MSGLOG_EDIT_TITLE]
|
||||
{emoji_note} {author} edited their message in #{channel}
|
||||
|
||||
[MSGLOG_EDIT_FOOTER]
|
||||
Message ID: {msgID} | User ID: {userID}
|
||||
|
||||
[MSGLOG_EDIT_OLD]
|
||||
Old message
|
||||
|
||||
[MSGLOG_EDIT_NEW]
|
||||
New message
|
||||
|
||||
[MSGLOG_EDIT_OLD_CUTOFF]
|
||||
The original message was cut off at 1024 characters.
|
||||
|
||||
[MSGLOG_EDIT_NEW_CUTOFF]
|
||||
The new message is cut off at 1024 characters.
|
||||
|
||||
[MSGLOG_EDIT_JUMP]
|
||||
**[Jump to message](https://discord.com/channels/{guild}/{channel}/{message})**
|
||||
|
||||
[MSGLOG_PINNED_TITLE]
|
||||
{emoji_pin} {author}'s message was {pinned} in #{channel}
|
||||
|
||||
[PIN_TOGGLE]
|
||||
switch({toggle}) {
|
||||
case true:
|
||||
'pinned';
|
||||
break;
|
||||
default:
|
||||
'unpinned';
|
||||
break;
|
||||
}
|
||||
|
||||
[THREAD_SWITCH]
|
||||
switch('{type}') {
|
||||
case 'CREATE':
|
||||
'created';
|
||||
break;
|
||||
case 'DELETE':
|
||||
'deleted';
|
||||
break;
|
||||
case 'ARCHIVE':
|
||||
'archived';
|
||||
break;
|
||||
case 'UNARCHIVE':
|
||||
'unarchived';
|
||||
break;
|
||||
}
|
||||
|
||||
[MSGLOG_THREAD_TITLE]
|
||||
A thread was {action} in #{channel}
|
||||
|
||||
[MSGLOG_THREAD_DESC_DELETE]
|
||||
**Thread:** {name} ({id})
|
||||
**Owner:** {owner}
|
||||
**Deleted by:** {actor}
|
||||
|
||||
[MSGLOG_THREAD_DESC_CREATE]
|
||||
**Thread:** {name} <#{id}>
|
||||
**Owner:** {owner}
|
||||
|
||||
[MSGLOG_THREAD_DESC_ARCHIVE]
|
||||
**Thread:** {name} <#{id}>
|
||||
**Owner:** {owner}
|
||||
**Archived by:** {actor}
|
||||
|
||||
[MSGLOG_THREAD_DESC_UNARCHIVE]
|
||||
**Thread:** {name} <#{id}>
|
||||
**Owner:** {owner}
|
||||
**Unarchived by:** {actor}
|
||||
|
||||
[MSGLOG_THREAD_FOOTER]
|
||||
Channel ID: {channelId} • Thread ID: {threadId} • Owner ID: {ownerId}
|
||||
|
||||
[MSGLOG_THREAD_LOCKED]
|
||||
**Thread was locked**
|
||||
|
||||
[MSGLOG_NO_PERMS]
|
||||
Bot missing permissions in message log channel
|
||||
Missing: {missing}
|
||||
|
||||
[MSGLOG_NO_HOOK]
|
||||
Webhook missing in message log channel.
|
||||
Reassign the message log channel to have the bot recreate the webhook.
|
||||
|
||||
//Voice Logs
|
||||
[VCLOG_JOIN]
|
||||
{nickname} **{tag}** ({id}) joined channel {emoji_voice-channel} **{newChannel}**.
|
||||
|
||||
[VCLOG_SWITCH]
|
||||
{nickname} **{tag}** ({id}) switched from {emoji_voice-channel} **{oldChannel}** to {emoji_voice-channel} **{newChannel}**.
|
||||
|
||||
[VCLOG_LEAVE]
|
||||
{nickname} **{tag}** ({id}) left channel {emoji_voice-channel} **{oldChannel}**
|
||||
|
||||
[VCLOG_NO_PERMS]
|
||||
Bot missing permissions in voice join/leave log channel
|
||||
Missing: {missing}
|
||||
|
||||
[MEMBERLOG_NO_PERMS]
|
||||
Bot missing permissions in member log channel
|
||||
Missing: {missing}
|
||||
|
||||
//Nickname Logs
|
||||
[NICKLOG_TITLE]
|
||||
{user} updated their nickname
|
||||
|
||||
[NICKLOG_DESCRIPTION]
|
||||
**Old nickname:** `{oldNick}`
|
||||
**New nickname:** `{newNick}`
|
||||
|
||||
[NICKLOG_FOOTER]
|
||||
User ID: {id}
|
||||
|
||||
[NICKLOG_NO_PERMS]
|
||||
Bot missing permissions in nickname log channel
|
||||
Missing: {missing}
|
||||
|
||||
//Bulk Delete Logs
|
||||
[BULK_DELETE_TITLE]
|
||||
Bulk message delete log [{embedNr}] in **#{channel}**
|
||||
|
||||
[BULK_DELETE_FOOTER]
|
||||
Showing messages {rangeStart}-{rangeEnd} of {total} messages
|
||||
|
||||
[BULK_DELETE_NO_CONTENT]
|
||||
No content.
|
||||
|
||||
[BULK_DELETE_ATTACHMENTS]
|
||||
Message attachments:
|
||||
|
||||
[BULK_DELETE_ATTACHMENTS_VALUE]
|
||||
// Message logs
|
||||
[MSGLOG_DELETE_TITLE]
|
||||
{emoji_trash} {author}'s message was deleted in #{channel}
|
||||
|
||||
[MSGLOG_NOCONTENT]
|
||||
**__NO TEXT CONTENT__**
|
||||
|
||||
[MSGLOG_REPLY]
|
||||
Message was in reply to user {tag} ({id}):
|
||||
|
||||
[MSGLOG_REPLY_VALUE]
|
||||
**[Jump]({link})**
|
||||
{content}
|
||||
|
||||
[MSGLOG_REPLY_NOCONTENT]
|
||||
**__Missing content.__**
|
||||
|
||||
[MSGLOG_FILTERED]
|
||||
The message was filtered:
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_WORD]
|
||||
Filter: **{matcher}**
|
||||
Matched: **{match}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_INVITE]
|
||||
Filter: **{matcher}**
|
||||
Matched: **{match}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_LINK]
|
||||
Filter: **{matcher}**
|
||||
Matched: **{match}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_MENTION]
|
||||
Filter: **{filter}**
|
||||
Amount: **{amount}**
|
||||
|
||||
[MSGLOG_FILTERED_VALUE_WORDWATCHER]
|
||||
Filter: **{filter}**
|
||||
Action: **{action}**
|
||||
|
||||
[MSGLOG_FILTERED_SANCTIONED]
|
||||
__User was sanctioned.__ {emoji_hammer}
|
||||
|
||||
[MSGLOG_DELETE_FOOTER]
|
||||
Message ID: {msgID} | User ID: {userID}
|
||||
|
||||
[MSGLOG_EDIT_TITLE]
|
||||
{emoji_note} {author} edited their message in #{channel}
|
||||
|
||||
[MSGLOG_EDIT_FOOTER]
|
||||
Message ID: {msgID} | User ID: {userID}
|
||||
|
||||
[MSGLOG_EDIT_OLD]
|
||||
Old message
|
||||
|
||||
[MSGLOG_EDIT_NEW]
|
||||
New message
|
||||
|
||||
[MSGLOG_EDIT_OLD_CUTOFF]
|
||||
The original message was cut off at 1024 characters.
|
||||
|
||||
[MSGLOG_EDIT_NEW_CUTOFF]
|
||||
The new message is cut off at 1024 characters.
|
||||
|
||||
[MSGLOG_EDIT_JUMP]
|
||||
**[Jump to message](https://discord.com/channels/{guild}/{channel}/{message})**
|
||||
|
||||
[MSGLOG_PINNED_TITLE]
|
||||
{emoji_pin} {author}'s message was {pinned} in #{channel}
|
||||
|
||||
[PIN_TOGGLE]
|
||||
switch({toggle}) {
|
||||
case true:
|
||||
'pinned';
|
||||
break;
|
||||
default:
|
||||
'unpinned';
|
||||
break;
|
||||
}
|
||||
|
||||
[THREAD_SWITCH]
|
||||
switch('{type}') {
|
||||
case 'CREATE':
|
||||
'created';
|
||||
break;
|
||||
case 'DELETE':
|
||||
'deleted';
|
||||
break;
|
||||
case 'ARCHIVE':
|
||||
'archived';
|
||||
break;
|
||||
case 'UNARCHIVE':
|
||||
'unarchived';
|
||||
break;
|
||||
}
|
||||
|
||||
[MSGLOG_THREAD_TITLE]
|
||||
A thread was {action} in #{channel}
|
||||
|
||||
[MSGLOG_THREAD_DESC_DELETE]
|
||||
**Thread:** {name} ({id})
|
||||
**Owner:** {owner}
|
||||
**Deleted by:** {actor}
|
||||
|
||||
[MSGLOG_THREAD_DESC_CREATE]
|
||||
**Thread:** {name} <#{id}>
|
||||
**Owner:** {owner}
|
||||
|
||||
[MSGLOG_THREAD_DESC_ARCHIVE]
|
||||
**Thread:** {name} <#{id}>
|
||||
**Owner:** {owner}
|
||||
**Archived by:** {actor}
|
||||
|
||||
[MSGLOG_THREAD_DESC_UNARCHIVE]
|
||||
**Thread:** {name} <#{id}>
|
||||
**Owner:** {owner}
|
||||
**Unarchived by:** {actor}
|
||||
|
||||
[MSGLOG_THREAD_FOOTER]
|
||||
Channel ID: {channelId} • Thread ID: {threadId} • Owner ID: {ownerId}
|
||||
|
||||
[MSGLOG_THREAD_LOCKED]
|
||||
**Thread was locked**
|
||||
|
||||
[MSGLOG_NO_PERMS]
|
||||
Bot missing permissions in message log channel
|
||||
Missing: {missing}
|
||||
|
||||
[MSGLOG_NO_HOOK]
|
||||
Webhook missing in message log channel.
|
||||
Reassign the message log channel to have the bot recreate the webhook.
|
||||
|
||||
//Voice Logs
|
||||
[VCLOG_JOIN]
|
||||
{nickname} **{tag}** ({id}) joined channel {emoji_voice-channel} **{newChannel}**.
|
||||
|
||||
[VCLOG_SWITCH]
|
||||
{nickname} **{tag}** ({id}) switched from {emoji_voice-channel} **{oldChannel}** to {emoji_voice-channel} **{newChannel}**.
|
||||
|
||||
[VCLOG_LEAVE]
|
||||
{nickname} **{tag}** ({id}) left channel {emoji_voice-channel} **{oldChannel}**
|
||||
|
||||
[VCLOG_NO_PERMS]
|
||||
Bot missing permissions in voice join/leave log channel
|
||||
Missing: {missing}
|
||||
|
||||
[MEMBERLOG_NO_PERMS]
|
||||
Bot missing permissions in member log channel
|
||||
Missing: {missing}
|
||||
|
||||
//Nickname Logs
|
||||
[NICKLOG_TITLE]
|
||||
{user} updated their nickname
|
||||
|
||||
[NICKLOG_DESCRIPTION]
|
||||
**Old nickname:** `{oldNick}`
|
||||
**New nickname:** `{newNick}`
|
||||
|
||||
[NICKLOG_FOOTER]
|
||||
User ID: {id}
|
||||
|
||||
[NICKLOG_NO_PERMS]
|
||||
Bot missing permissions in nickname log channel
|
||||
Missing: {missing}
|
||||
|
||||
//Rejoin Tracking Logs
|
||||
[REJOINLOG_NO_PERMS]
|
||||
Bot missing permissions in rejoin log channel
|
||||
Missing: {missing}
|
||||
|
||||
//Bulk Delete Logs
|
||||
[BULK_DELETE_TITLE]
|
||||
Bulk message delete log [{embedNr}] in **#{channel}**
|
||||
|
||||
[BULK_DELETE_FOOTER]
|
||||
Showing messages {rangeStart}-{rangeEnd} of {total} messages
|
||||
|
||||
[BULK_DELETE_NO_CONTENT]
|
||||
No content.
|
||||
|
||||
[BULK_DELETE_ATTACHMENTS]
|
||||
Message attachments:
|
||||
|
||||
[BULK_DELETE_ATTACHMENTS_VALUE]
|
||||
{links}
|
@ -1,44 +1,48 @@
|
||||
[SETTING_MESSAGES_HELP]
|
||||
Configure message logging for your server.
|
||||
Message logging utilizes webhooks for bulk message deletions, so if you wish to log those, make sure the bot has the `ManageWebhooks` permission in the logging channel!
|
||||
If you've given the permission retroactively, make sure to reconfigure the channel to ensure the bot creates the webhook.
|
||||
|
||||
[SETTING_MEMBERS_HELP]
|
||||
Configure member logging for your server.
|
||||
|
||||
**Usable tags:**
|
||||
`{mention}` - mentions the user
|
||||
`{tag}` - username#discriminator
|
||||
`{username}` - username
|
||||
`{guildsize}` - member count of the server
|
||||
`{guildname}` - name of the server
|
||||
`{accage}` - age of the account
|
||||
`{id}` - ID of the account
|
||||
|
||||
[SETTING_DMINFRACTION_HELP]
|
||||
Configure if messages get sent to users after they're infracted.
|
||||
Setup custom messages and customize what infractions you want to send to the user.
|
||||
|
||||
[SETTING_DMINFRACTION_VALID]
|
||||
Valid choices are **{valid}**.
|
||||
|
||||
// Memberlogs
|
||||
[SETTING_MEMBERLOG_JOIN]
|
||||
》 Join Message
|
||||
|
||||
[SETTING_MEMBERLOG_LEAVE]
|
||||
》 Leave Message
|
||||
|
||||
[SETTING_ERRORS_HELP]
|
||||
Log issues that arise due to configuration.
|
||||
Most common outputs will be from automod.
|
||||
|
||||
[SETTING_MODERATION_HELP]
|
||||
Define the channel to which moderation logs are sent.
|
||||
The setting also allows you to exclude/include types of actions from being logged in the channel.
|
||||
|
||||
[SETTING_NICKNAMES_HELP]
|
||||
Configure member nickname logging for your server.
|
||||
|
||||
[SETTING_VOICE_HELP]
|
||||
Configure logging of voice joins and leaves for your server.
|
||||
[SETTING_MESSAGES_HELP]
|
||||
Configure message logging for your server.
|
||||
Message logging utilizes webhooks for bulk message deletions, so if you wish to log those, make sure the bot has the `ManageWebhooks` permission in the logging channel!
|
||||
If you've given the permission retroactively, make sure to reconfigure the channel to ensure the bot creates the webhook.
|
||||
|
||||
[SETTING_MEMBERS_HELP]
|
||||
Configure member logging for your server.
|
||||
|
||||
**Usable tags:**
|
||||
`{mention}` - mentions the user
|
||||
`{tag}` - username#discriminator
|
||||
`{username}` - username
|
||||
`{guildsize}` - member count of the server
|
||||
`{guildname}` - name of the server
|
||||
`{accage}` - age of the account
|
||||
`{id}` - ID of the account
|
||||
|
||||
[SETTING_DMINFRACTION_HELP]
|
||||
Configure if messages get sent to users after they're infracted.
|
||||
Setup custom messages and customize what infractions you want to send to the user.
|
||||
|
||||
[SETTING_DMINFRACTION_VALID]
|
||||
Valid choices are **{valid}**.
|
||||
|
||||
// Memberlogs
|
||||
[SETTING_MEMBERLOG_JOIN]
|
||||
》 Join Message
|
||||
|
||||
[SETTING_MEMBERLOG_LEAVE]
|
||||
》 Leave Message
|
||||
|
||||
[SETTING_ERRORS_HELP]
|
||||
Log issues that arise due to configuration.
|
||||
Most common outputs will be from automod.
|
||||
|
||||
[SETTING_MODERATION_HELP]
|
||||
Define the channel to which moderation logs are sent.
|
||||
The setting also allows you to exclude/include types of actions from being logged in the channel.
|
||||
|
||||
[SETTING_NICKNAMES_HELP]
|
||||
Configure member nickname logging for your server.
|
||||
|
||||
[SETTING_VOICE_HELP]
|
||||
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