mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-21 08:45:03 +00:00
Merge upstream/djs into origin/feat/mime-checking
This commit is contained in:
commit
0f1819421a
96 changed files with 6868 additions and 458 deletions
6516
backend/package-lock.json
generated
6516
backend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -29,7 +29,7 @@
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"deep-diff": "^1.0.2",
|
"deep-diff": "^1.0.2",
|
||||||
"discord.js": "^13.0.0-dev.4886ae2.1627214570",
|
"discord.js": "^13.0.0-dev.d310e4f.1627560163",
|
||||||
"dotenv": "^4.0.0",
|
"dotenv": "^4.0.0",
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
"erlpack": "github:discord/erlpack",
|
"erlpack": "github:discord/erlpack",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import util from "util";
|
import util from "util";
|
||||||
|
|
||||||
export class ErisError extends Error {
|
export class DiscordJSError extends Error {
|
||||||
code: number | string | undefined;
|
code: number | string | undefined;
|
||||||
shardId: number;
|
shardId: number;
|
||||||
|
|
||||||
|
@ -11,6 +11,6 @@ export class ErisError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
[util.inspect.custom]() {
|
[util.inspect.custom]() {
|
||||||
return `[ERIS] [ERROR CODE ${this.code || "?"}] [SHARD ${this.shardId}] ${this.message}`;
|
return `[DISCORDJS] [ERROR CODE ${this.code ?? "?"}] [SHARD ${this.shardId}] ${this.message}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,9 @@
|
||||||
import { GuildChannel, GuildMember, User } from "discord.js";
|
import { GuildChannel, GuildMember, Snowflake, Util, User } from "discord.js";
|
||||||
import { baseCommandParameterTypeHelpers, baseTypeConverters, CommandContext, TypeConversionError } from "knub";
|
import { baseCommandParameterTypeHelpers, baseTypeConverters, CommandContext, TypeConversionError } from "knub";
|
||||||
import { createTypeHelper } from "knub-command-manager";
|
import { createTypeHelper } from "knub-command-manager";
|
||||||
import {
|
import {
|
||||||
channelMentionRegex,
|
channelMentionRegex,
|
||||||
convertDelayStringToMS,
|
convertDelayStringToMS,
|
||||||
disableCodeBlocks,
|
|
||||||
disableInlineCode,
|
|
||||||
isValidSnowflake,
|
isValidSnowflake,
|
||||||
resolveMember,
|
resolveMember,
|
||||||
resolveUser,
|
resolveUser,
|
||||||
|
@ -32,7 +30,7 @@ export const commandTypes = {
|
||||||
async resolvedUser(value, context: CommandContext<any>) {
|
async resolvedUser(value, context: CommandContext<any>) {
|
||||||
const result = await resolveUser(context.pluginData.client, value);
|
const result = await resolveUser(context.pluginData.client, value);
|
||||||
if (result == null || result instanceof UnknownUser) {
|
if (result == null || result instanceof UnknownUser) {
|
||||||
throw new TypeConversionError(`User \`${disableCodeBlocks(value)}\` was not found`);
|
throw new TypeConversionError(`User \`${Util.escapeCodeBlock(value)}\` was not found`);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
@ -40,7 +38,7 @@ export const commandTypes = {
|
||||||
async resolvedUserLoose(value, context: CommandContext<any>) {
|
async resolvedUserLoose(value, context: CommandContext<any>) {
|
||||||
const result = await resolveUser(context.pluginData.client, value);
|
const result = await resolveUser(context.pluginData.client, value);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
throw new TypeConversionError(`Invalid user: \`${disableCodeBlocks(value)}\``);
|
throw new TypeConversionError(`Invalid user: \`${Util.escapeCodeBlock(value)}\``);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
@ -53,7 +51,7 @@ export const commandTypes = {
|
||||||
const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value);
|
const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
throw new TypeConversionError(
|
throw new TypeConversionError(
|
||||||
`Member \`${disableCodeBlocks(value)}\` was not found or they have left the server`,
|
`Member \`${Util.escapeCodeBlock(value)}\` was not found or they have left the server`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -64,7 +62,7 @@ export const commandTypes = {
|
||||||
|
|
||||||
const result = await resolveMessageTarget(context.pluginData, value);
|
const result = await resolveMessageTarget(context.pluginData, value);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new TypeConversionError(`Unknown message \`${disableInlineCode(value)}\``);
|
throw new TypeConversionError(`Unknown message \`${Util.escapeInlineCode(value)}\``);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -72,32 +70,32 @@ export const commandTypes = {
|
||||||
|
|
||||||
async anyId(value: string, context: CommandContext<any>) {
|
async anyId(value: string, context: CommandContext<any>) {
|
||||||
const userId = resolveUserId(context.pluginData.client, value);
|
const userId = resolveUserId(context.pluginData.client, value);
|
||||||
if (userId) return userId;
|
if (userId) return userId as Snowflake;
|
||||||
|
|
||||||
const channelIdMatch = value.match(channelMentionRegex);
|
const channelIdMatch = value.match(channelMentionRegex);
|
||||||
if (channelIdMatch) return channelIdMatch[1];
|
if (channelIdMatch) return channelIdMatch[1] as Snowflake;
|
||||||
|
|
||||||
const roleIdMatch = value.match(roleMentionRegex);
|
const roleIdMatch = value.match(roleMentionRegex);
|
||||||
if (roleIdMatch) return roleIdMatch[1];
|
if (roleIdMatch) return roleIdMatch[1] as Snowflake;
|
||||||
|
|
||||||
if (isValidSnowflake(value)) {
|
if (isValidSnowflake(value)) {
|
||||||
return value;
|
return value as Snowflake;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeConversionError(`Could not parse ID: \`${disableInlineCode(value)}\``);
|
throw new TypeConversionError(`Could not parse ID: \`${Util.escapeInlineCode(value)}\``);
|
||||||
},
|
},
|
||||||
|
|
||||||
regex(value: string, context: CommandContext<any>): RegExp {
|
regex(value: string, context: CommandContext<any>): RegExp {
|
||||||
try {
|
try {
|
||||||
return inputPatternToRegExp(value);
|
return inputPatternToRegExp(value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TypeConversionError(`Could not parse RegExp: \`${disableInlineCode(e.message)}\``);
|
throw new TypeConversionError(`Could not parse RegExp: \`${Util.escapeInlineCode(e.message)}\``);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
timezone(value: string) {
|
timezone(value: string) {
|
||||||
if (!isValidTimezone(value)) {
|
if (!isValidTimezone(value)) {
|
||||||
throw new TypeConversionError(`Invalid timezone: ${disableInlineCode(value)}`);
|
throw new TypeConversionError(`Invalid timezone: ${Util.escapeInlineCode(value)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
@ -112,7 +110,7 @@ export const commandTypeHelpers = {
|
||||||
resolvedUserLoose: createTypeHelper<Promise<User | UnknownUser>>(commandTypes.resolvedUserLoose),
|
resolvedUserLoose: createTypeHelper<Promise<User | UnknownUser>>(commandTypes.resolvedUserLoose),
|
||||||
resolvedMember: createTypeHelper<Promise<GuildMember>>(commandTypes.resolvedMember),
|
resolvedMember: createTypeHelper<Promise<GuildMember>>(commandTypes.resolvedMember),
|
||||||
messageTarget: createTypeHelper<Promise<MessageTarget>>(commandTypes.messageTarget),
|
messageTarget: createTypeHelper<Promise<MessageTarget>>(commandTypes.messageTarget),
|
||||||
anyId: createTypeHelper<Promise<string>>(commandTypes.anyId),
|
anyId: createTypeHelper<Promise<Snowflake>>(commandTypes.anyId),
|
||||||
regex: createTypeHelper<RegExp>(commandTypes.regex),
|
regex: createTypeHelper<RegExp>(commandTypes.regex),
|
||||||
timezone: createTypeHelper<string>(commandTypes.timezone),
|
timezone: createTypeHelper<string>(commandTypes.timezone),
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
"CHANNEL_DELETE": "🗑 Channel {channelMention(channel)} was deleted",
|
"CHANNEL_DELETE": "🗑 Channel {channelMention(channel)} was deleted",
|
||||||
"CHANNEL_UPDATE": "✏ Channel {channelMention(newChannel)} was edited. Changes:\n{differenceString}",
|
"CHANNEL_UPDATE": "✏ Channel {channelMention(newChannel)} was edited. Changes:\n{differenceString}",
|
||||||
|
|
||||||
"THREAD_CREATE": "🖊 Thread {channelMention(thread)} was created in channel <#{thread.parentID}>",
|
"THREAD_CREATE": "🖊 Thread {channelMention(thread)} was created in channel <#{thread.parentId}>",
|
||||||
"THREAD_DELETE": "🗑 Thread {channelMention(thread)} was deleted/archived from channel <#{thread.parentID}>",
|
"THREAD_DELETE": "🗑 Thread {channelMention(thread)} was deleted/archived from channel <#{thread.parentId}>",
|
||||||
"THREAD_UPDATE": "✏ Thread {channelMention(newThread)} was edited. Changes:\n{differenceString}",
|
"THREAD_UPDATE": "✏ Thread {channelMention(newThread)} was edited. Changes:\n{differenceString}",
|
||||||
|
|
||||||
"ROLE_CREATE": "🖊 Role **{role.name}** (`{role.id}`) was created",
|
"ROLE_CREATE": "🖊 Role **{role.name}** (`{role.id}`) was created",
|
||||||
|
@ -50,6 +50,14 @@
|
||||||
"STAGE_INSTANCE_DELETE": "📣 Stage Instance `{stageInstance.topic}` was deleted in Stage Channel <#{stageChannel.id}>",
|
"STAGE_INSTANCE_DELETE": "📣 Stage Instance `{stageInstance.topic}` was deleted in Stage Channel <#{stageChannel.id}>",
|
||||||
"STAGE_INSTANCE_UPDATE": "📣 Stage Instance `{newStageInstance.topic}` was edited in Stage Channel <#{stageChannel.id}>. Changes:\n{differenceString}",
|
"STAGE_INSTANCE_UPDATE": "📣 Stage Instance `{newStageInstance.topic}` was edited in Stage Channel <#{stageChannel.id}>. Changes:\n{differenceString}",
|
||||||
|
|
||||||
|
"EMOJI_CREATE": "<{emoji.identifier}> Emoji `{emoji.name} ({emoji.id})` was created",
|
||||||
|
"EMOJI_DELETE": "👋 Emoji `{emoji.name} ({emoji.id})` was deleted",
|
||||||
|
"EMOJI_UPDATE": "<{newEmoji.identifier}> Emoji `{newEmoji.name} ({newEmoji.id})` was updated. Changes:\n{differenceString}",
|
||||||
|
|
||||||
|
"STICKER_CREATE": "🖼️ Sticker `{sticker.name} ({sticker.id})` was created. Description: `{sticker.description}` Format: {emoji.format}",
|
||||||
|
"STICKER_DELETE": "🖼️ Sticker `{sticker.name} ({sticker.id})` was deleted.",
|
||||||
|
"STICKER_UPDATE": "🖼️ Sticker `{newSticker.name} ({sticker.id})` was updated. Changes:\n{differenceString}",
|
||||||
|
|
||||||
"COMMAND": "🤖 {userMention(member)} used command in {channelMention(channel)}:\n`{command}`",
|
"COMMAND": "🤖 {userMention(member)} used command in {channelMention(channel)}:\n`{command}`",
|
||||||
|
|
||||||
"MESSAGE_SPAM_DETECTED": "🛑 {userMention(member)} spam detected in {channelMention(channel)}: {description} (more than {limit} in {interval}s)\n{archiveUrl}",
|
"MESSAGE_SPAM_DETECTED": "🛑 {userMention(member)} spam detected in {channelMention(channel)}: {description} (more than {limit} in {interval}s)\n{archiveUrl}",
|
||||||
|
|
|
@ -41,6 +41,14 @@ export enum LogType {
|
||||||
STAGE_INSTANCE_DELETE,
|
STAGE_INSTANCE_DELETE,
|
||||||
STAGE_INSTANCE_UPDATE,
|
STAGE_INSTANCE_UPDATE,
|
||||||
|
|
||||||
|
EMOJI_CREATE,
|
||||||
|
EMOJI_DELETE,
|
||||||
|
EMOJI_UPDATE,
|
||||||
|
|
||||||
|
STICKER_CREATE,
|
||||||
|
STICKER_DELETE,
|
||||||
|
STICKER_UPDATE,
|
||||||
|
|
||||||
COMMAND,
|
COMMAND,
|
||||||
|
|
||||||
MESSAGE_SPAM_DETECTED,
|
MESSAGE_SPAM_DETECTED,
|
||||||
|
|
|
@ -17,7 +17,7 @@ export function getReverseCounterComparisonOp(op: TriggerComparisonOp): TriggerC
|
||||||
return REVERSE_OPS[op];
|
return REVERSE_OPS[op];
|
||||||
}
|
}
|
||||||
|
|
||||||
const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})([1-9]\\d*)$`);
|
const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})(\\d*)$`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Parsed comparison op and value, or null if the comparison string was invalid
|
* @return Parsed comparison op and value, or null if the comparison string was invalid
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Client, Intents, TextChannel } from "discord.js";
|
import { Client, Intents, TextChannel } from "discord.js";
|
||||||
import fs from "fs";
|
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { Knub, PluginError } from "knub";
|
import { Knub, PluginError } from "knub";
|
||||||
import { PluginLoadError } from "knub/dist/plugins/PluginLoadError";
|
import { PluginLoadError } from "knub/dist/plugins/PluginLoadError";
|
||||||
|
@ -11,7 +10,7 @@ import { Configs } from "./data/Configs";
|
||||||
import { connect } from "./data/db";
|
import { connect } from "./data/db";
|
||||||
import { GuildLogs } from "./data/GuildLogs";
|
import { GuildLogs } from "./data/GuildLogs";
|
||||||
import { LogType } from "./data/LogType";
|
import { LogType } from "./data/LogType";
|
||||||
import { ErisError } from "./ErisError";
|
import { DiscordJSError } from "./DiscordJSError";
|
||||||
import "./loadEnv";
|
import "./loadEnv";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins";
|
import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins";
|
||||||
|
@ -21,8 +20,6 @@ import { ZeppelinGlobalConfig, ZeppelinGuildConfig } from "./types";
|
||||||
import { startUptimeCounter } from "./uptime";
|
import { startUptimeCounter } from "./uptime";
|
||||||
import { errorMessage, isDiscordAPIError, isDiscordHTTPError, successMessage } from "./utils";
|
import { errorMessage, isDiscordAPIError, isDiscordHTTPError, successMessage } from "./utils";
|
||||||
|
|
||||||
const fsp = fs.promises;
|
|
||||||
|
|
||||||
if (!process.env.KEY) {
|
if (!process.env.KEY) {
|
||||||
// tslint:disable-next-line:no-console
|
// tslint:disable-next-line:no-console
|
||||||
console.error("Project root .env with KEY is required!");
|
console.error("Project root .env with KEY is required!");
|
||||||
|
@ -81,7 +78,7 @@ function errorHandler(err) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err instanceof ErisError) {
|
if (err instanceof DiscordJSError) {
|
||||||
if (err.code && SAFE_TO_IGNORE_ERIS_ERROR_CODES.includes(err.code)) {
|
if (err.code && SAFE_TO_IGNORE_ERIS_ERROR_CODES.includes(err.code)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -176,14 +173,12 @@ connect().then(async () => {
|
||||||
});
|
});
|
||||||
client.setMaxListeners(200);
|
client.setMaxListeners(200);
|
||||||
|
|
||||||
client.on("debug", message => {
|
client.on("rateLimit", rateLimitData => {
|
||||||
if (message.includes(" 429 ")) {
|
logger.info(`[429] ${JSON.stringify(rateLimitData)}`);
|
||||||
logger.info(`[429] ${message}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("error", err => {
|
client.on("error", err => {
|
||||||
errorHandler(new ErisError(err.message, (err as any).code, 0));
|
errorHandler(new DiscordJSError(err.message, (err as any).code, 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
const allowedGuilds = new AllowedGuilds();
|
const allowedGuilds = new AllowedGuilds();
|
||||||
|
@ -266,5 +261,5 @@ connect().then(async () => {
|
||||||
bot.initialize();
|
bot.initialize();
|
||||||
logger.info("Bot Initialized");
|
logger.info("Bot Initialized");
|
||||||
logger.info("Logging in...");
|
logger.info("Logging in...");
|
||||||
await client.login(process.env.token);
|
await client.login(process.env.TOKEN);
|
||||||
});
|
});
|
||||||
|
|
|
@ -198,7 +198,7 @@ export async function sendSuccessMessage(
|
||||||
return channel
|
return channel
|
||||||
.send({ ...content }) // Force line break
|
.send({ ...content }) // Force line break
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : `${channel.id}`;
|
const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id;
|
||||||
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
|
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
@ -219,7 +219,7 @@ export async function sendErrorMessage(
|
||||||
return channel
|
return channel
|
||||||
.send({ ...content }) // Force line break
|
.send({ ...content }) // Force line break
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : `${channel.id}`;
|
const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id;
|
||||||
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
|
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function runAutomodOnMessage(
|
||||||
message: SavedMessage,
|
message: SavedMessage,
|
||||||
isEdit: boolean,
|
isEdit: boolean,
|
||||||
) {
|
) {
|
||||||
const user = pluginData.client.users.cache!.get(message.user_id as Snowflake);
|
const user = pluginData.client.users.cache.get(message.user_id as Snowflake);
|
||||||
const member = pluginData.guild.members.cache.get(message.user_id as Snowflake);
|
const member = pluginData.guild.members.cache.get(message.user_id as Snowflake);
|
||||||
|
|
||||||
const context: AutomodContext = {
|
const context: AutomodContext = {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Snowflake, TextChannel } from "discord.js";
|
import { Snowflake, TextChannel, Util } from "discord.js";
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
import { asSingleLine, disableInlineCode, messageSummary, verboseChannelMention } from "../../../utils";
|
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
|
||||||
import { automodTrigger } from "../helpers";
|
import { automodTrigger } from "../helpers";
|
||||||
|
|
||||||
interface MatchResultType {
|
interface MatchResultType {
|
||||||
|
@ -73,7 +73,7 @@ export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
asSingleLine(`
|
asSingleLine(`
|
||||||
Matched attachment type \`${disableInlineCode(matchResult.extra.matchedType)}\`
|
Matched attachment type \`${Util.escapeInlineCode(matchResult.extra.matchedType)}\`
|
||||||
(${matchResult.extra.mode === "blacklist" ? "(blacklisted)" : "(not in whitelist)"})
|
(${matchResult.extra.mode === "blacklist" ? "(blacklisted)" : "(not in whitelist)"})
|
||||||
in message (\`${contexts[0].message!.id}\`) in ${prettyChannel}:
|
in message (\`${contexts[0].message!.id}\`) in ${prettyChannel}:
|
||||||
`) + messageSummary(contexts[0].message!)
|
`) + messageSummary(contexts[0].message!)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { Util } from "discord.js";
|
||||||
import escapeStringRegexp from "escape-string-regexp";
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
import { allowTimeout } from "../../../RegExpRunner";
|
import { allowTimeout } from "../../../RegExpRunner";
|
||||||
import { disableInlineCode, getUrlsInString, tNullable } from "../../../utils";
|
import { getUrlsInString, tNullable } from "../../../utils";
|
||||||
import { TRegex } from "../../../validatorUtils";
|
import { TRegex } from "../../../validatorUtils";
|
||||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||||
|
@ -129,6 +130,6 @@ export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
||||||
|
|
||||||
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||||
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
|
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
|
||||||
return `Matched link \`${disableInlineCode(matchResult.extra.link)}\` in ${partialSummary}`;
|
return `Matched link \`${Util.escapeInlineCode(matchResult.extra.link)}\` in ${partialSummary}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { Util } from "discord.js";
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
import { allowTimeout } from "../../../RegExpRunner";
|
import { allowTimeout } from "../../../RegExpRunner";
|
||||||
import { disableInlineCode } from "../../../utils";
|
|
||||||
import { normalizeText } from "../../../utils/normalizeText";
|
import { normalizeText } from "../../../utils/normalizeText";
|
||||||
import { stripMarkdown } from "../../../utils/stripMarkdown";
|
import { stripMarkdown } from "../../../utils/stripMarkdown";
|
||||||
import { TRegex } from "../../../validatorUtils";
|
import { TRegex } from "../../../validatorUtils";
|
||||||
|
@ -72,6 +72,6 @@ export const MatchRegexTrigger = automodTrigger<MatchResultType>()({
|
||||||
|
|
||||||
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||||
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
|
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
|
||||||
return `Matched regex \`${disableInlineCode(matchResult.extra.pattern)}\` in ${partialSummary}`;
|
return `Matched regex \`${Util.escapeInlineCode(matchResult.extra.pattern)}\` in ${partialSummary}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { Util } from "discord.js";
|
||||||
import escapeStringRegexp from "escape-string-regexp";
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
import { disableInlineCode } from "../../../utils";
|
|
||||||
import { normalizeText } from "../../../utils/normalizeText";
|
import { normalizeText } from "../../../utils/normalizeText";
|
||||||
import { stripMarkdown } from "../../../utils/stripMarkdown";
|
import { stripMarkdown } from "../../../utils/stripMarkdown";
|
||||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||||
|
@ -89,6 +89,6 @@ export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
|
||||||
|
|
||||||
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||||
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
|
const partialSummary = getTextMatchPartialSummary(pluginData, matchResult.extra.type, contexts[0]);
|
||||||
return `Matched word \`${disableInlineCode(matchResult.extra.word)}\` in ${partialSummary}`;
|
return `Matched word \`${Util.escapeInlineCode(matchResult.extra.word)}\` in ${partialSummary}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||||
const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake);
|
const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake);
|
||||||
const roleName = role?.name || "Unknown";
|
const roleName = role?.name || "Unknown";
|
||||||
const member = contexts[0].member!;
|
const member = contexts[0].member!;
|
||||||
const memberName = `**${member.user.username}#${member.user.discriminator}** (\`${member.id}\`)`;
|
const memberName = `**${member.user.tag}** (\`${member.id}\`)`;
|
||||||
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was added to ${memberName}`;
|
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was added to ${memberName}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||||
const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake);
|
const role = pluginData.guild.roles.cache.get(matchResult.extra.matchedRoleId as Snowflake);
|
||||||
const roleName = role?.name || "Unknown";
|
const roleName = role?.name || "Unknown";
|
||||||
const member = contexts[0].member!;
|
const member = contexts[0].member!;
|
||||||
const memberName = `**${member.user.username}#${member.user.discriminator}** (\`${member.id}\`)`;
|
const memberName = `**${member.user.tag}** (\`${member.id}\`)`;
|
||||||
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was removed from ${memberName}`;
|
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was removed from ${memberName}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,9 +35,7 @@ export const AddDashboardUserCmd = botControlCmd({
|
||||||
await pluginData.state.apiPermissionAssignments.addUser(args.guildId, user.id, [ApiPermissions.EditConfig]);
|
await pluginData.state.apiPermissionAssignments.addUser(args.guildId, user.id, [ApiPermissions.EditConfig]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userNameList = args.users.map(
|
const userNameList = args.users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`);
|
||||||
user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`,
|
|
||||||
);
|
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel as TextChannel,
|
msg.channel as TextChannel,
|
||||||
|
|
|
@ -46,7 +46,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
||||||
|
|
||||||
// If we have user, always display which guilds they have permissions in (or only specified guild permissions)
|
// If we have user, always display which guilds they have permissions in (or only specified guild permissions)
|
||||||
if (args.user) {
|
if (args.user) {
|
||||||
const userInfo = `**${args.user.username}#${args.user.discriminator}** (\`${args.user.id}\`)`;
|
const userInfo = `**${args.user.tag}** (\`${args.user.id}\`)`;
|
||||||
|
|
||||||
for (const assignment of existingUserAssignment!) {
|
for (const assignment of existingUserAssignment!) {
|
||||||
if (guild != null && assignment.guild_id !== args.guildId) continue;
|
if (guild != null && assignment.guild_id !== args.guildId) continue;
|
||||||
|
@ -82,9 +82,7 @@ export const ListDashboardPermsCmd = botControlCmd({
|
||||||
finalMessage += `The server ${guildInfo} has the following assigned permissions:\n`; // Double \n for consistency with AddDashboardUserCmd
|
finalMessage += `The server ${guildInfo} has the following assigned permissions:\n`; // Double \n for consistency with AddDashboardUserCmd
|
||||||
for (const assignment of existingGuildAssignment) {
|
for (const assignment of existingGuildAssignment) {
|
||||||
const user = await resolveUser(pluginData.client, assignment.target_id);
|
const user = await resolveUser(pluginData.client, assignment.target_id);
|
||||||
finalMessage += `\n**${user.username}#${user.discriminator}**, \`${
|
finalMessage += `\n**${user.tag}**, \`${assignment.target_id}\`: ${assignment.permissions.join(", ")}`;
|
||||||
assignment.target_id
|
|
||||||
}\`: ${assignment.permissions.join(", ")}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,7 @@ export const ListDashboardUsersCmd = botControlCmd({
|
||||||
|
|
||||||
const dashboardUsers = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id);
|
const dashboardUsers = await pluginData.state.apiPermissionAssignments.getByGuildId(guild.id);
|
||||||
const users = await Promise.all(dashboardUsers.map(perm => resolveUser(pluginData.client, perm.target_id)));
|
const users = await Promise.all(dashboardUsers.map(perm => resolveUser(pluginData.client, perm.target_id)));
|
||||||
const userNameList = users.map(
|
const userNameList = users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`);
|
||||||
user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`,
|
|
||||||
);
|
|
||||||
|
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(
|
||||||
pluginData,
|
pluginData,
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const ReloadServerCmd = botControlCmd({
|
||||||
},
|
},
|
||||||
|
|
||||||
signature: {
|
signature: {
|
||||||
guildId: ct.string(),
|
guildId: ct.anyId(),
|
||||||
},
|
},
|
||||||
|
|
||||||
async run({ pluginData, message: msg, args }) {
|
async run({ pluginData, message: msg, args }) {
|
||||||
|
|
|
@ -34,9 +34,7 @@ export const RemoveDashboardUserCmd = botControlCmd({
|
||||||
await pluginData.state.apiPermissionAssignments.removeUser(args.guildId, user.id);
|
await pluginData.state.apiPermissionAssignments.removeUser(args.guildId, user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userNameList = args.users.map(
|
const userNameList = args.users.map(user => `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`);
|
||||||
user => `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`,
|
|
||||||
);
|
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel as TextChannel,
|
msg.channel as TextChannel,
|
||||||
|
|
|
@ -49,7 +49,7 @@ export const ServersCmd = botControlCmd({
|
||||||
const lines = filteredGuilds.map(g => {
|
const lines = filteredGuilds.map(g => {
|
||||||
const paddedId = g.id.padEnd(longestId, " ");
|
const paddedId = g.id.padEnd(longestId, " ");
|
||||||
const owner = getUser(pluginData.client, g.ownerId);
|
const owner = getUser(pluginData.client, g.ownerId);
|
||||||
return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${owner.username}#${owner.discriminator}** \`${owner.id}\`)`;
|
return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${owner.tag}** \`${owner.id}\`)`;
|
||||||
});
|
});
|
||||||
createChunkedMessage(msg.channel as TextChannel, lines.join("\n"));
|
createChunkedMessage(msg.channel as TextChannel, lines.join("\n"));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,15 +7,15 @@ import { postCaseToCaseLogChannel } from "./postToCaseLogChannel";
|
||||||
|
|
||||||
export async function createCase(pluginData: GuildPluginData<CasesPluginType>, args: CaseArgs) {
|
export async function createCase(pluginData: GuildPluginData<CasesPluginType>, args: CaseArgs) {
|
||||||
const user = await resolveUser(pluginData.client, args.userId);
|
const user = await resolveUser(pluginData.client, args.userId);
|
||||||
const userName = `${user.username}#${user.discriminator}`;
|
const userName = user.tag;
|
||||||
|
|
||||||
const mod = await resolveUser(pluginData.client, args.modId);
|
const mod = await resolveUser(pluginData.client, args.modId);
|
||||||
const modName = `${mod.username}#${mod.discriminator}`;
|
const modName = mod.tag;
|
||||||
|
|
||||||
let ppName: string | null = null;
|
let ppName: string | null = null;
|
||||||
if (args.ppId) {
|
if (args.ppId) {
|
||||||
const pp = await resolveUser(pluginData.client, args.ppId);
|
const pp = await resolveUser(pluginData.client, args.ppId);
|
||||||
ppName = `${pp.username}#${pp.discriminator}`;
|
ppName = pp.tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.auditLogId) {
|
if (args.auditLogId) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ export async function createCaseNote(pluginData: GuildPluginData<CasesPluginType
|
||||||
throw new RecoverablePluginError(ERRORS.INVALID_USER);
|
throw new RecoverablePluginError(ERRORS.INVALID_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modName = `${mod.username}#${mod.discriminator}`;
|
const modName = mod.tag;
|
||||||
|
|
||||||
let body = args.body;
|
let body = args.body;
|
||||||
|
|
||||||
|
|
|
@ -60,10 +60,10 @@ export const ArchiveChannelCmd = channelArchiverCmd({
|
||||||
|
|
||||||
while (archivedMessages < maxMessagesToArchive) {
|
while (archivedMessages < maxMessagesToArchive) {
|
||||||
const messagesToFetch = Math.min(MAX_MESSAGES_PER_FETCH, maxMessagesToArchive - archivedMessages);
|
const messagesToFetch = Math.min(MAX_MESSAGES_PER_FETCH, maxMessagesToArchive - archivedMessages);
|
||||||
const messages = (await args.channel.messages.fetch({
|
const messages = await args.channel.messages.fetch({
|
||||||
limit: messagesToFetch,
|
limit: messagesToFetch,
|
||||||
before: previousId as Snowflake,
|
before: previousId as Snowflake,
|
||||||
})) as Collection<Snowflake, Message>;
|
});
|
||||||
if (messages.size === 0) break;
|
if (messages.size === 0) break;
|
||||||
|
|
||||||
for (const message of messages.values()) {
|
for (const message of messages.values()) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Snowflake } from "discord.js";
|
||||||
import { GlobalPluginData } from "knub";
|
import { GlobalPluginData } from "knub";
|
||||||
import { SECONDS } from "../../../utils";
|
import { SECONDS } from "../../../utils";
|
||||||
import { GuildConfigReloaderPluginType } from "../types";
|
import { GuildConfigReloaderPluginType } from "../types";
|
||||||
|
@ -11,7 +12,7 @@ export async function reloadChangedGuilds(pluginData: GlobalPluginData<GuildConf
|
||||||
for (const item of changedConfigs) {
|
for (const item of changedConfigs) {
|
||||||
if (!item.key.startsWith("guild-")) continue;
|
if (!item.key.startsWith("guild-")) continue;
|
||||||
|
|
||||||
const guildId = item.key.slice("guild-".length);
|
const guildId = item.key.slice("guild-".length) as Snowflake;
|
||||||
console.log(`Config changed, reloading guild ${guildId}`);
|
console.log(`Config changed, reloading guild ${guildId}`);
|
||||||
await pluginData.getKnubInstance().reloadGuild(guildId);
|
await pluginData.getKnubInstance().reloadGuild(guildId);
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,10 @@ export const VoiceStateUpdateAlertEvt = locateUserEvt({
|
||||||
|
|
||||||
triggeredAlerts.forEach(alert => {
|
triggeredAlerts.forEach(alert => {
|
||||||
const txtChannel = meta.pluginData.guild.channels.resolve(alert.channel_id as Snowflake) as TextChannel;
|
const txtChannel = meta.pluginData.guild.channels.resolve(alert.channel_id as Snowflake) as TextChannel;
|
||||||
txtChannel.send(
|
txtChannel.send({
|
||||||
`🔴 <@!${alert.requestor_id}> the user <@!${alert.user_id}> disconnected out of \`<#!${voiceChannel.id}>\``,
|
content: `🔴 <@!${alert.requestor_id}> the user <@!${alert.user_id}> disconnected out of \`${voiceChannel.name}\``,
|
||||||
);
|
allowedMentions: { users: [alert.requestor_id as Snowflake] },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ export async function createOrReuseInvite(vc: VoiceChannel) {
|
||||||
const existingInvites = await vc.fetchInvites();
|
const existingInvites = await vc.fetchInvites();
|
||||||
|
|
||||||
if (existingInvites.size !== 0) {
|
if (existingInvites.size !== 0) {
|
||||||
return existingInvites[0];
|
return existingInvites.first()!;
|
||||||
} else {
|
} else {
|
||||||
return vc.createInvite();
|
return vc.createInvite();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||||
import { LogType } from "../../data/LogType";
|
import { LogType } from "../../data/LogType";
|
||||||
import { logger } from "../../logger";
|
import { logger } from "../../logger";
|
||||||
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
||||||
import { disableCodeBlocks } from "../../utils";
|
|
||||||
import { CasesPlugin } from "../Cases/CasesPlugin";
|
import { CasesPlugin } from "../Cases/CasesPlugin";
|
||||||
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
|
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
|
||||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||||
|
@ -20,6 +19,14 @@ import {
|
||||||
LogsStageInstanceDeleteEvt,
|
LogsStageInstanceDeleteEvt,
|
||||||
LogsStageInstanceUpdateEvt,
|
LogsStageInstanceUpdateEvt,
|
||||||
} from "./events/LogsStageInstanceModifyEvts";
|
} from "./events/LogsStageInstanceModifyEvts";
|
||||||
|
import {
|
||||||
|
LogsEmojiCreateEvt,
|
||||||
|
LogsEmojiDeleteEvt,
|
||||||
|
LogsEmojiUpdateEvt,
|
||||||
|
LogsStickerCreateEvt,
|
||||||
|
LogsStickerDeleteEvt,
|
||||||
|
LogsStickerUpdateEvt,
|
||||||
|
} from "./events/LogsEmojiAndStickerModifyEvts";
|
||||||
import { LogsThreadCreateEvt, LogsThreadDeleteEvt, LogsThreadUpdateEvt } from "./events/LogsThreadModifyEvts";
|
import { LogsThreadCreateEvt, LogsThreadDeleteEvt, LogsThreadUpdateEvt } from "./events/LogsThreadModifyEvts";
|
||||||
import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts";
|
import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts";
|
||||||
import { LogsVoiceStateUpdateEvt } from "./events/LogsVoiceChannelEvts";
|
import { LogsVoiceStateUpdateEvt } from "./events/LogsVoiceChannelEvts";
|
||||||
|
@ -29,6 +36,7 @@ import { log } from "./util/log";
|
||||||
import { onMessageDelete } from "./util/onMessageDelete";
|
import { onMessageDelete } from "./util/onMessageDelete";
|
||||||
import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk";
|
import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk";
|
||||||
import { onMessageUpdate } from "./util/onMessageUpdate";
|
import { onMessageUpdate } from "./util/onMessageUpdate";
|
||||||
|
import { Util } from "discord.js";
|
||||||
|
|
||||||
const defaultOptions: PluginOptions<LogsPluginType> = {
|
const defaultOptions: PluginOptions<LogsPluginType> = {
|
||||||
config: {
|
config: {
|
||||||
|
@ -81,6 +89,12 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()({
|
||||||
LogsThreadCreateEvt,
|
LogsThreadCreateEvt,
|
||||||
LogsThreadDeleteEvt,
|
LogsThreadDeleteEvt,
|
||||||
LogsThreadUpdateEvt,
|
LogsThreadUpdateEvt,
|
||||||
|
LogsEmojiCreateEvt,
|
||||||
|
LogsEmojiDeleteEvt,
|
||||||
|
LogsEmojiUpdateEvt,
|
||||||
|
LogsStickerCreateEvt,
|
||||||
|
LogsStickerDeleteEvt,
|
||||||
|
LogsStickerUpdateEvt,
|
||||||
],
|
],
|
||||||
|
|
||||||
public: {
|
public: {
|
||||||
|
@ -133,7 +147,7 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()({
|
||||||
The following regex has taken longer than ${timeoutMs}ms for ${failedTimes} times and has been temporarily disabled:
|
The following regex has taken longer than ${timeoutMs}ms for ${failedTimes} times and has been temporarily disabled:
|
||||||
`.trim() +
|
`.trim() +
|
||||||
"\n```" +
|
"\n```" +
|
||||||
disableCodeBlocks(regexSource) +
|
Util.escapeCodeBlock(regexSource) +
|
||||||
"```",
|
"```",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { LogType } from "../../../data/LogType";
|
||||||
|
import { differenceToString, getScalarDifference } from "../../../utils";
|
||||||
|
import {
|
||||||
|
channelToConfigAccessibleChannel,
|
||||||
|
emojiToConfigAccessibleEmoji,
|
||||||
|
stickerToConfigAccessibleSticker,
|
||||||
|
} from "../../../utils/configAccessibleObjects";
|
||||||
|
import { logsEvt } from "../types";
|
||||||
|
|
||||||
|
export const LogsEmojiCreateEvt = logsEvt({
|
||||||
|
event: "emojiCreate",
|
||||||
|
|
||||||
|
async listener(meta) {
|
||||||
|
meta.pluginData.state.guildLogs.log(LogType.EMOJI_CREATE, {
|
||||||
|
emoji: emojiToConfigAccessibleEmoji(meta.args.emoji),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LogsEmojiDeleteEvt = logsEvt({
|
||||||
|
event: "emojiDelete",
|
||||||
|
|
||||||
|
async listener(meta) {
|
||||||
|
meta.pluginData.state.guildLogs.log(LogType.EMOJI_DELETE, {
|
||||||
|
emoji: emojiToConfigAccessibleEmoji(meta.args.emoji),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LogsEmojiUpdateEvt = logsEvt({
|
||||||
|
event: "emojiUpdate",
|
||||||
|
|
||||||
|
async listener(meta) {
|
||||||
|
const diff = getScalarDifference(meta.args.oldEmoji, meta.args.newEmoji);
|
||||||
|
const differenceString = differenceToString(diff);
|
||||||
|
|
||||||
|
meta.pluginData.state.guildLogs.log(LogType.EMOJI_UPDATE, {
|
||||||
|
oldEmoji: emojiToConfigAccessibleEmoji(meta.args.oldEmoji),
|
||||||
|
newEmoji: emojiToConfigAccessibleEmoji(meta.args.newEmoji),
|
||||||
|
differenceString,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LogsStickerCreateEvt = logsEvt({
|
||||||
|
event: "stickerCreate",
|
||||||
|
|
||||||
|
async listener(meta) {
|
||||||
|
meta.pluginData.state.guildLogs.log(LogType.STICKER_CREATE, {
|
||||||
|
sticker: stickerToConfigAccessibleSticker(meta.args.sticker),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LogsStickerDeleteEvt = logsEvt({
|
||||||
|
event: "stickerDelete",
|
||||||
|
|
||||||
|
async listener(meta) {
|
||||||
|
meta.pluginData.state.guildLogs.log(LogType.STICKER_DELETE, {
|
||||||
|
sticker: stickerToConfigAccessibleSticker(meta.args.sticker),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LogsStickerUpdateEvt = logsEvt({
|
||||||
|
event: "stickerUpdate",
|
||||||
|
|
||||||
|
async listener(meta) {
|
||||||
|
const diff = getScalarDifference(meta.args.oldSticker, meta.args.newSticker);
|
||||||
|
const differenceString = differenceToString(diff);
|
||||||
|
|
||||||
|
meta.pluginData.state.guildLogs.log(
|
||||||
|
LogType.STICKER_UPDATE,
|
||||||
|
{
|
||||||
|
oldSticker: stickerToConfigAccessibleSticker(meta.args.oldSticker),
|
||||||
|
newSticker: stickerToConfigAccessibleSticker(meta.args.newSticker),
|
||||||
|
differenceString,
|
||||||
|
},
|
||||||
|
meta.args.newSticker.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -16,7 +16,7 @@ export const LogsGuildBanAddEvt = logsEvt({
|
||||||
GuildAuditLogs.Actions.MEMBER_BAN_ADD as number,
|
GuildAuditLogs.Actions.MEMBER_BAN_ADD as number,
|
||||||
user.id,
|
user.id,
|
||||||
);
|
);
|
||||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.executor : null;
|
const mod = relevantAuditLogEntry?.executor ?? null;
|
||||||
|
|
||||||
pluginData.state.guildLogs.log(
|
pluginData.state.guildLogs.log(
|
||||||
LogType.MEMBER_BAN,
|
LogType.MEMBER_BAN,
|
||||||
|
@ -41,7 +41,7 @@ export const LogsGuildBanRemoveEvt = logsEvt({
|
||||||
GuildAuditLogs.Actions.MEMBER_BAN_REMOVE as number,
|
GuildAuditLogs.Actions.MEMBER_BAN_REMOVE as number,
|
||||||
user.id,
|
user.id,
|
||||||
);
|
);
|
||||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.executor : null;
|
const mod = relevantAuditLogEntry?.executor ?? null;
|
||||||
|
|
||||||
pluginData.state.guildLogs.log(
|
pluginData.state.guildLogs.log(
|
||||||
LogType.MEMBER_UNBAN,
|
LogType.MEMBER_UNBAN,
|
||||||
|
|
|
@ -52,7 +52,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
|
||||||
GuildAuditLogs.Actions.MEMBER_ROLE_UPDATE as number,
|
GuildAuditLogs.Actions.MEMBER_ROLE_UPDATE as number,
|
||||||
member.id,
|
member.id,
|
||||||
);
|
);
|
||||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.executor : null;
|
const mod = relevantAuditLogEntry?.executor ?? null;
|
||||||
|
|
||||||
if (addedRoles.length && removedRoles.length) {
|
if (addedRoles.length && removedRoles.length) {
|
||||||
// Roles added *and* removed
|
// Roles added *and* removed
|
||||||
|
@ -61,11 +61,11 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
|
||||||
{
|
{
|
||||||
member: logMember,
|
member: logMember,
|
||||||
addedRoles: addedRoles
|
addedRoles: addedRoles
|
||||||
.map(roleId => pluginData.guild.roles.cache.get(roleId) || { id: roleId, name: `Unknown (${roleId})` })
|
.map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` })
|
||||||
.map(r => r.name)
|
.map(r => r.name)
|
||||||
.join(", "),
|
.join(", "),
|
||||||
removedRoles: removedRoles
|
removedRoles: removedRoles
|
||||||
.map(roleId => pluginData.guild.roles.cache.get(roleId) || { id: roleId, name: `Unknown (${roleId})` })
|
.map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` })
|
||||||
.map(r => r.name)
|
.map(r => r.name)
|
||||||
.join(", "),
|
.join(", "),
|
||||||
mod: mod ? userToConfigAccessibleUser(mod) : {},
|
mod: mod ? userToConfigAccessibleUser(mod) : {},
|
||||||
|
@ -79,7 +79,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
|
||||||
{
|
{
|
||||||
member: logMember,
|
member: logMember,
|
||||||
roles: addedRoles
|
roles: addedRoles
|
||||||
.map(roleId => pluginData.guild.roles.cache.get(roleId) || { id: roleId, name: `Unknown (${roleId})` })
|
.map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` })
|
||||||
.map(r => r.name)
|
.map(r => r.name)
|
||||||
.join(", "),
|
.join(", "),
|
||||||
mod: mod ? userToConfigAccessibleUser(mod) : {},
|
mod: mod ? userToConfigAccessibleUser(mod) : {},
|
||||||
|
@ -93,7 +93,7 @@ export const LogsGuildMemberUpdateEvt = logsEvt({
|
||||||
{
|
{
|
||||||
member: logMember,
|
member: logMember,
|
||||||
roles: removedRoles
|
roles: removedRoles
|
||||||
.map(roleId => pluginData.guild.roles.cache.get(roleId) || { id: roleId, name: `Unknown (${roleId})` })
|
.map(roleId => pluginData.guild.roles.cache.get(roleId) ?? { id: roleId, name: `Unknown (${roleId})` })
|
||||||
.map(r => r.name)
|
.map(r => r.name)
|
||||||
.join(", "),
|
.join(", "),
|
||||||
mod: mod ? userToConfigAccessibleUser(mod) : {},
|
mod: mod ? userToConfigAccessibleUser(mod) : {},
|
||||||
|
|
|
@ -17,13 +17,13 @@ export const LogsVoiceStateUpdateEvt = logsEvt({
|
||||||
// Leave evt
|
// Leave evt
|
||||||
meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_LEAVE, {
|
meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_LEAVE, {
|
||||||
member: memberToConfigAccessibleMember(member),
|
member: memberToConfigAccessibleMember(member),
|
||||||
oldChannel: channelToConfigAccessibleChannel(oldChannel!),
|
channel: channelToConfigAccessibleChannel(oldChannel!),
|
||||||
});
|
});
|
||||||
} else if (!oldChannel && newChannel) {
|
} else if (!oldChannel && newChannel) {
|
||||||
// Join Evt
|
// Join Evt
|
||||||
meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_JOIN, {
|
meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_JOIN, {
|
||||||
member: memberToConfigAccessibleMember(member),
|
member: memberToConfigAccessibleMember(member),
|
||||||
newChannel: channelToConfigAccessibleChannel(newChannel),
|
channel: channelToConfigAccessibleChannel(newChannel),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_MOVE, {
|
meta.pluginData.state.guildLogs.log(LogType.VOICE_CHANNEL_MOVE, {
|
||||||
|
|
|
@ -94,7 +94,7 @@ export async function log(pluginData: GuildPluginData<LogsPluginType>, type: Log
|
||||||
type === LogType.CENSOR ||
|
type === LogType.CENSOR ||
|
||||||
type === LogType.CLEAN
|
type === LogType.CLEAN
|
||||||
) {
|
) {
|
||||||
if (data.channel.parentID && opts.excluded_categories.includes(data.channel.parentID)) {
|
if (data.channel.parentId && opts.excluded_categories.includes(data.channel.parentId)) {
|
||||||
continue logChannelLoop;
|
continue logChannelLoop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,11 +73,7 @@ export const AddCaseCmd = modActionsCmd({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created for **${user.tag}**`);
|
||||||
pluginData,
|
|
||||||
msg.channel,
|
|
||||||
`Case #${theCase.case_number} created for **${user.username}#${user.discriminator}**`,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created`);
|
sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ export const BanCmd = modActionsCmd({
|
||||||
// Confirm the action to the moderator
|
// Confirm the action to the moderator
|
||||||
let response = "";
|
let response = "";
|
||||||
if (!forceban) {
|
if (!forceban) {
|
||||||
response = `Banned **${user.username}#${user.discriminator}** ${forTime}(Case #${banResult.case.case_number})`;
|
response = `Banned **${user.tag}** ${forTime}(Case #${banResult.case.case_number})`;
|
||||||
if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`;
|
if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`;
|
||||||
} else {
|
} else {
|
||||||
response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`;
|
response = `Member forcebanned ${forTime}(Case #${banResult.case.case_number})`;
|
||||||
|
|
|
@ -29,7 +29,7 @@ export const CasesModCmd = modActionsCmd({
|
||||||
async run({ pluginData, message: msg, args }) {
|
async run({ pluginData, message: msg, args }) {
|
||||||
const modId = args.mod || msg.author.id;
|
const modId = args.mod || msg.author.id;
|
||||||
const mod = await resolveUser(pluginData.client, modId);
|
const mod = await resolveUser(pluginData.client, modId);
|
||||||
const modName = mod instanceof User ? `${mod.username}#${mod.discriminator}` : modId;
|
const modName = mod instanceof User ? mod.tag : modId;
|
||||||
|
|
||||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||||
const totalCases = await casesPlugin.getTotalCasesByMod(modId);
|
const totalCases = await casesPlugin.getTotalCasesByMod(modId);
|
||||||
|
@ -57,7 +57,7 @@ export const CasesModCmd = modActionsCmd({
|
||||||
const embed: MessageEmbedOptions = {
|
const embed: MessageEmbedOptions = {
|
||||||
author: {
|
author: {
|
||||||
name: title,
|
name: title,
|
||||||
iconURL: mod instanceof User ? mod.avatarURL() || mod.defaultAvatarURL : undefined,
|
iconURL: mod instanceof User ? mod.displayAvatarURL() : undefined,
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
...getChunkedEmbedFields(emptyEmbedValue, lines.join("\n")),
|
...getChunkedEmbedFields(emptyEmbedValue, lines.join("\n")),
|
||||||
|
|
|
@ -61,10 +61,7 @@ export const CasesUserCmd = modActionsCmd({
|
||||||
const normalCases = cases.filter(c => !c.is_hidden);
|
const normalCases = cases.filter(c => !c.is_hidden);
|
||||||
const hiddenCases = cases.filter(c => c.is_hidden);
|
const hiddenCases = cases.filter(c => c.is_hidden);
|
||||||
|
|
||||||
const userName =
|
const userName = user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : user.tag;
|
||||||
user instanceof UnknownUser && cases.length
|
|
||||||
? cases[cases.length - 1].user_name
|
|
||||||
: `${user.username}#${user.discriminator}`;
|
|
||||||
|
|
||||||
if (cases.length === 0) {
|
if (cases.length === 0) {
|
||||||
msg.channel.send(`No cases found for **${userName}**`);
|
msg.channel.send(`No cases found for **${userName}**`);
|
||||||
|
@ -119,7 +116,7 @@ export const CasesUserCmd = modActionsCmd({
|
||||||
lineChunks.length === 1
|
lineChunks.length === 1
|
||||||
? `Cases for ${userName} (${lines.length} total)`
|
? `Cases for ${userName} (${lines.length} total)`
|
||||||
: `Cases ${chunkStart}–${chunkEnd} of ${lines.length} for ${userName}`,
|
: `Cases ${chunkStart}–${chunkEnd} of ${lines.length} for ${userName}`,
|
||||||
icon_url: user instanceof User ? user.avatarURL() || user.defaultAvatarURL : undefined,
|
icon_url: user instanceof User ? user.displayAvatarURL() : undefined,
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
...getChunkedEmbedFields(emptyEmbedValue, linesInChunk.join("\n")),
|
...getChunkedEmbedFields(emptyEmbedValue, linesInChunk.join("\n")),
|
||||||
|
|
|
@ -68,7 +68,7 @@ export const DeleteCaseCmd = modActionsCmd({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedByName = `${message.author.username}#${message.author.discriminator}`;
|
const deletedByName = message.author.tag;
|
||||||
|
|
||||||
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
||||||
const deletedAt = timeAndDate.inGuildTz().format(timeAndDate.getDateFormat("pretty_datetime"));
|
const deletedAt = timeAndDate.inGuildTz().format(timeAndDate.getDateFormat("pretty_datetime"));
|
||||||
|
|
|
@ -70,7 +70,7 @@ export const ForcebanCmd = modActionsCmd({
|
||||||
// FIXME: Use banUserId()?
|
// FIXME: Use banUserId()?
|
||||||
await pluginData.guild.bans.create(user.id as Snowflake, {
|
await pluginData.guild.bans.create(user.id as Snowflake, {
|
||||||
days: 1,
|
days: 1,
|
||||||
reason: reason != null ? encodeURIComponent(reason) : undefined,
|
reason: reason ?? undefined,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Failed to forceban member");
|
sendErrorMessage(pluginData, msg.channel, "Failed to forceban member");
|
||||||
|
|
|
@ -95,7 +95,7 @@ export const MassbanCmd = modActionsCmd({
|
||||||
|
|
||||||
await pluginData.guild.bans.create(userId as Snowflake, {
|
await pluginData.guild.bans.create(userId as Snowflake, {
|
||||||
days: deleteDays,
|
days: deleteDays,
|
||||||
reason: banReason != null ? encodeURIComponent(banReason) : undefined,
|
reason: banReason ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
await casesPlugin.createCase({
|
await casesPlugin.createCase({
|
||||||
|
|
|
@ -60,10 +60,7 @@ export const MassunbanCmd = modActionsCmd({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pluginData.guild.bans.remove(
|
await pluginData.guild.bans.remove(userId as Snowflake, unbanReason ?? undefined);
|
||||||
userId as Snowflake,
|
|
||||||
unbanReason != null ? encodeURIComponent(unbanReason) : undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
await casesPlugin.createCase({
|
await casesPlugin.createCase({
|
||||||
userId,
|
userId,
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const NoteCmd = modActionsCmd({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userName = `${user.username}#${user.discriminator}`;
|
const userName = user.tag;
|
||||||
const reason = formatReasonWithAttachments(args.note, msg.attachments.array());
|
const reason = formatReasonWithAttachments(args.note, msg.attachments.array());
|
||||||
|
|
||||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const UnbanCmd = modActionsCmd({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ignoreEvent(pluginData, IgnoredEventType.Unban, user.id);
|
ignoreEvent(pluginData, IgnoredEventType.Unban, user.id);
|
||||||
await pluginData.guild.bans.remove(user.id as Snowflake, reason != null ? encodeURIComponent(reason) : undefined);
|
await pluginData.guild.bans.remove(user.id as Snowflake, reason ?? undefined);
|
||||||
} catch {
|
} catch {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Failed to unban member; are you sure they're banned?");
|
sendErrorMessage(pluginData, msg.channel, "Failed to unban member; are you sure they're banned?");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -107,7 +107,7 @@ export const WarnCmd = modActionsCmd({
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel,
|
msg.channel,
|
||||||
`Warned **${memberToWarn.user.username}#${memberToWarn.user.discriminator}** (Case #${warnResult.case.case_number})${messageResultText}`,
|
`Warned **${memberToWarn.user.tag}** (Case #${warnResult.case.case_number})${messageResultText}`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,7 +44,7 @@ export const CreateBanCaseOnManualBanEvt = modActionsEvt({
|
||||||
const config = mod instanceof UnknownUser ? pluginData.config.get() : await pluginData.config.getForUser(mod);
|
const config = mod instanceof UnknownUser ? pluginData.config.get() : await pluginData.config.getForUser(mod);
|
||||||
|
|
||||||
if (config.create_cases_for_manual_actions) {
|
if (config.create_cases_for_manual_actions) {
|
||||||
reason = relevantAuditLogEntry.reason || "";
|
reason = relevantAuditLogEntry.reason ?? "";
|
||||||
createdCase = await casesPlugin.createCase({
|
createdCase = await casesPlugin.createCase({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
modId,
|
modId,
|
||||||
|
|
|
@ -47,7 +47,7 @@ export const PostAlertOnMemberJoinEvt = modActionsEvt({
|
||||||
}
|
}
|
||||||
|
|
||||||
await alertChannel.send(
|
await alertChannel.send(
|
||||||
`<@!${member.id}> (${member.user.username}#${member.user.discriminator} \`${member.id}\`) joined with ${actions.length} prior record(s)`,
|
`<@!${member.id}> (${member.user.tag} \`${member.id}\`) joined with ${actions.length} prior record(s)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -82,7 +82,7 @@ export async function actualKickMemberCmd(
|
||||||
ignoreEvent(pluginData, IgnoredEventType.Ban, memberToKick.id);
|
ignoreEvent(pluginData, IgnoredEventType.Ban, memberToKick.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await memberToKick.ban({ days: 1, reason: encodeURIComponent("kick -clean") });
|
await memberToKick.ban({ days: 1, reason: "kick -clean" });
|
||||||
} catch {
|
} catch {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Failed to ban the user to clean messages (-clean)");
|
sendErrorMessage(pluginData, msg.channel, "Failed to ban the user to clean messages (-clean)");
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ export async function actualKickMemberCmd(
|
||||||
ignoreEvent(pluginData, IgnoredEventType.Unban, memberToKick.id);
|
ignoreEvent(pluginData, IgnoredEventType.Unban, memberToKick.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pluginData.guild.bans.remove(memberToKick.id, encodeURIComponent("kick -clean"));
|
await pluginData.guild.bans.remove(memberToKick.id, "kick -clean");
|
||||||
} catch {
|
} catch {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Failed to unban the user after banning them (-clean)");
|
sendErrorMessage(pluginData, msg.channel, "Failed to unban the user after banning them (-clean)");
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ export async function actualKickMemberCmd(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the action to the moderator
|
// Confirm the action to the moderator
|
||||||
let response = `Kicked **${memberToKick.user.username}#${memberToKick.user.discriminator}** (Case #${kickResult.case.case_number})`;
|
let response = `Kicked **${memberToKick.user.tag}** (Case #${kickResult.case.case_number})`;
|
||||||
|
|
||||||
if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`;
|
if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`;
|
||||||
sendSuccessMessage(pluginData, msg.channel, response);
|
sendSuccessMessage(pluginData, msg.channel, response);
|
||||||
|
|
|
@ -85,24 +85,24 @@ export async function actualMuteUserCmd(
|
||||||
if (args.time) {
|
if (args.time) {
|
||||||
if (muteResult.updatedExistingMute) {
|
if (muteResult.updatedExistingMute) {
|
||||||
response = asSingleLine(`
|
response = asSingleLine(`
|
||||||
Updated **${user.username}#${user.discriminator}**'s
|
Updated **${user.tag}**'s
|
||||||
mute to ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
mute to ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
||||||
`);
|
`);
|
||||||
} else {
|
} else {
|
||||||
response = asSingleLine(`
|
response = asSingleLine(`
|
||||||
Muted **${user.username}#${user.discriminator}**
|
Muted **${user.tag}**
|
||||||
for ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
for ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (muteResult.updatedExistingMute) {
|
if (muteResult.updatedExistingMute) {
|
||||||
response = asSingleLine(`
|
response = asSingleLine(`
|
||||||
Updated **${user.username}#${user.discriminator}**'s
|
Updated **${user.tag}**'s
|
||||||
mute to indefinite (Case #${muteResult.case.case_number})
|
mute to indefinite (Case #${muteResult.case.case_number})
|
||||||
`);
|
`);
|
||||||
} else {
|
} else {
|
||||||
response = asSingleLine(`
|
response = asSingleLine(`
|
||||||
Muted **${user.username}#${user.discriminator}**
|
Muted **${user.tag}**
|
||||||
indefinitely (Case #${muteResult.case.case_number})
|
indefinitely (Case #${muteResult.case.case_number})
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export async function actualUnmuteCmd(
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel as TextChannel,
|
msg.channel as TextChannel,
|
||||||
asSingleLine(`
|
asSingleLine(`
|
||||||
Unmuting **${user.username}#${user.discriminator}**
|
Unmuting **${user.tag}**
|
||||||
in ${timeUntilUnmute} (Case #${result.case.case_number})
|
in ${timeUntilUnmute} (Case #${result.case.case_number})
|
||||||
`),
|
`),
|
||||||
);
|
);
|
||||||
|
@ -57,7 +57,7 @@ export async function actualUnmuteCmd(
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel as TextChannel,
|
msg.channel as TextChannel,
|
||||||
asSingleLine(`
|
asSingleLine(`
|
||||||
Unmuted **${user.username}#${user.discriminator}**
|
Unmuted **${user.tag}**
|
||||||
(Case #${result.case.case_number})
|
(Case #${result.case.case_number})
|
||||||
`),
|
`),
|
||||||
);
|
);
|
||||||
|
|
|
@ -80,7 +80,7 @@ export async function banUserId(
|
||||||
const deleteMessageDays = Math.min(30, Math.max(0, banOptions.deleteMessageDays ?? 1));
|
const deleteMessageDays = Math.min(30, Math.max(0, banOptions.deleteMessageDays ?? 1));
|
||||||
await pluginData.guild.bans.create(userId as Snowflake, {
|
await pluginData.guild.bans.create(userId as Snowflake, {
|
||||||
days: deleteMessageDays,
|
days: deleteMessageDays,
|
||||||
reason: reason != null ? encodeURIComponent(reason) : undefined,
|
reason: reason ?? undefined,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let errorMessage;
|
let errorMessage;
|
||||||
|
|
|
@ -49,7 +49,7 @@ export async function kickMember(
|
||||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_KICK, member.id);
|
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_KICK, member.id);
|
||||||
ignoreEvent(pluginData, IgnoredEventType.Kick, member.id);
|
ignoreEvent(pluginData, IgnoredEventType.Kick, member.id);
|
||||||
try {
|
try {
|
||||||
await member.kick(reason != null ? encodeURIComponent(reason) : undefined);
|
await member.kick(reason ?? undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
status: "failed",
|
status: "failed",
|
||||||
|
|
|
@ -32,10 +32,7 @@ export async function outdatedTempbansLoop(pluginData: GuildPluginData<ModAction
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
ignoreEvent(pluginData, IgnoredEventType.Unban, tempban.user_id);
|
ignoreEvent(pluginData, IgnoredEventType.Unban, tempban.user_id);
|
||||||
await pluginData.guild.bans.remove(
|
await pluginData.guild.bans.remove(tempban.user_id as Snowflake, reason ?? undefined);
|
||||||
tempban.user_id as Snowflake,
|
|
||||||
reason != null ? encodeURIComponent(reason) : undefined,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
pluginData.state.serverLogs.log(LogType.BOT_ALERT, {
|
pluginData.state.serverLogs.log(LogType.BOT_ALERT, {
|
||||||
body: `Encountered an error trying to automatically unban ${tempban.user_id} after tempban timeout`,
|
body: `Encountered an error trying to automatically unban ${tempban.user_id} after tempban timeout`,
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const ClearBannedMutesCmd = mutesCmd({
|
||||||
|
|
||||||
const activeMutes = await pluginData.state.mutes.getActiveMutes();
|
const activeMutes = await pluginData.state.mutes.getActiveMutes();
|
||||||
|
|
||||||
const bans: Array<{ reason: string; user: User }> = (await pluginData.guild.bans.fetch({ cache: true })) as any;
|
const bans = await pluginData.guild.bans.fetch({ cache: true });
|
||||||
const bannedIds = bans.map(b => b.user.id);
|
const bannedIds = bans.map(b => b.user.id);
|
||||||
|
|
||||||
await msg.channel.send(`Found ${activeMutes.length} mutes and ${bannedIds.length} bans, cross-referencing...`);
|
await msg.channel.send(`Found ${activeMutes.length} mutes and ${bannedIds.length} bans, cross-referencing...`);
|
||||||
|
|
|
@ -67,7 +67,7 @@ export const MutesCmd = mutesCmd({
|
||||||
totalMutes = manuallyMutedMembers.length;
|
totalMutes = manuallyMutedMembers.length;
|
||||||
|
|
||||||
lines = manuallyMutedMembers.map(member => {
|
lines = manuallyMutedMembers.map(member => {
|
||||||
return `<@!${member.id}> (**${member.user.username}#${member.user.discriminator}**, \`${member.id}\`) 🔧 Manual mute`;
|
return `<@!${member.id}> (**${member.user.tag}**, \`${member.id}\`) 🔧 Manual mute`;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Show filtered active mutes (but not manual mutes)
|
// Show filtered active mutes (but not manual mutes)
|
||||||
|
@ -119,7 +119,7 @@ export const MutesCmd = mutesCmd({
|
||||||
|
|
||||||
lines = filteredMutes.map(mute => {
|
lines = filteredMutes.map(mute => {
|
||||||
const user = pluginData.client.users.resolve(mute.user_id as Snowflake);
|
const user = pluginData.client.users.resolve(mute.user_id as Snowflake);
|
||||||
const username = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
|
const username = user ? user.tag : "Unknown#0000";
|
||||||
const theCase = muteCasesById.get(mute.case_id);
|
const theCase = muteCasesById.get(mute.case_id);
|
||||||
const caseName = theCase ? `Case #${theCase.case_number}` : "No case";
|
const caseName = theCase ? `Case #${theCase.case_number}` : "No case";
|
||||||
|
|
||||||
|
@ -214,25 +214,18 @@ export const MutesCmd = mutesCmd({
|
||||||
const row = new MessageActionRow().addComponents(buttons);
|
const row = new MessageActionRow().addComponents(buttons);
|
||||||
await listMessage.edit({ components: [row] });
|
await listMessage.edit({ components: [row] });
|
||||||
|
|
||||||
const filter = (iac: MessageComponentInteraction) => iac.message.id === listMessage.id;
|
const collector = listMessage.createMessageComponentCollector({ time: stopCollectionDebounce });
|
||||||
const collector = listMessage.createMessageComponentCollector({
|
|
||||||
filter,
|
|
||||||
time: stopCollectionDebounce,
|
|
||||||
});
|
|
||||||
|
|
||||||
collector.on("collect", async (interaction: MessageComponentInteraction) => {
|
collector.on("collect", async (interaction: MessageComponentInteraction) => {
|
||||||
if (msg.author.id !== interaction.user.id) {
|
if (msg.author.id !== interaction.user.id) {
|
||||||
interaction.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true });
|
interaction.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true });
|
||||||
} else {
|
} else {
|
||||||
collector.resetTimer();
|
collector.resetTimer();
|
||||||
|
await interaction.deferUpdate();
|
||||||
if (interaction.customId === `previousButton:${idMod}` && currentPage > 1) {
|
if (interaction.customId === `previousButton:${idMod}` && currentPage > 1) {
|
||||||
await interaction.deferUpdate();
|
|
||||||
await drawListPage(currentPage - 1);
|
await drawListPage(currentPage - 1);
|
||||||
} else if (interaction.customId === `nextButton:${idMod}` && currentPage < totalPages) {
|
} else if (interaction.customId === `nextButton:${idMod}` && currentPage < totalPages) {
|
||||||
await interaction.deferUpdate();
|
|
||||||
await drawListPage(currentPage + 1);
|
await drawListPage(currentPage + 1);
|
||||||
} else {
|
|
||||||
await interaction.deferUpdate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -90,7 +90,7 @@ export async function muteUser(
|
||||||
try {
|
try {
|
||||||
await member.roles.add(muteRole as Snowflake);
|
await member.roles.add(muteRole as Snowflake);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const actualMuteRole = pluginData.guild.roles.cache.find(x => x.id === muteRole);
|
const actualMuteRole = pluginData.guild.roles.cache.get(muteRole as Snowflake);
|
||||||
if (!actualMuteRole) {
|
if (!actualMuteRole) {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
logs.log(LogType.BOT_ALERT, {
|
logs.log(LogType.BOT_ALERT, {
|
||||||
|
|
|
@ -30,8 +30,8 @@ export const NamesCmd = nameHistoryCmd({
|
||||||
);
|
);
|
||||||
const usernameRows = usernames.map(r => `\`[${r.timestamp}]\` **${disableCodeBlocks(r.username)}**`);
|
const usernameRows = usernames.map(r => `\`[${r.timestamp}]\` **${disableCodeBlocks(r.username)}**`);
|
||||||
|
|
||||||
const user = await pluginData.client.users.fetch(args.userId as Snowflake);
|
const user = await pluginData.client.users.fetch(args.userId as Snowflake).catch(() => null);
|
||||||
const currentUsername = user ? `${user.username}#${user.discriminator}` : args.userId;
|
const currentUsername = user ? user.tag : args.userId;
|
||||||
|
|
||||||
const nicknameDays = Math.round(NICKNAME_RETENTION_PERIOD / DAYS);
|
const nicknameDays = Math.round(NICKNAME_RETENTION_PERIOD / DAYS);
|
||||||
const usernameDays = Math.round(NICKNAME_RETENTION_PERIOD / DAYS);
|
const usernameDays = Math.round(NICKNAME_RETENTION_PERIOD / DAYS);
|
||||||
|
|
|
@ -6,10 +6,7 @@ export const ChannelJoinEvt = nameHistoryEvt({
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
meta.pluginData.state.updateQueue.add(() =>
|
meta.pluginData.state.updateQueue.add(() =>
|
||||||
updateNickname(
|
updateNickname(meta.pluginData, meta.args.newState.member ?? meta.args.oldState.member!),
|
||||||
meta.pluginData,
|
|
||||||
meta.args.newState.member ? meta.args.newState.member : meta.args.oldState.member!,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
|
import { Util } from "discord.js";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import {
|
import { createChunkedMessage, DBDateFormat, deactivateMentions, sorter, trimLines } from "../../../utils";
|
||||||
createChunkedMessage,
|
|
||||||
DBDateFormat,
|
|
||||||
deactivateMentions,
|
|
||||||
disableCodeBlocks,
|
|
||||||
sorter,
|
|
||||||
trimLines,
|
|
||||||
} from "../../../utils";
|
|
||||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
||||||
import { postCmd } from "../types";
|
import { postCmd } from "../types";
|
||||||
|
|
||||||
|
@ -33,7 +27,7 @@ export const ScheduledPostsListCmd = postCmd({
|
||||||
|
|
||||||
const isTruncated = previewText.length > SCHEDULED_POST_PREVIEW_TEXT_LENGTH;
|
const isTruncated = previewText.length > SCHEDULED_POST_PREVIEW_TEXT_LENGTH;
|
||||||
|
|
||||||
previewText = disableCodeBlocks(deactivateMentions(previewText))
|
previewText = Util.escapeCodeBlock(deactivateMentions(previewText))
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
.slice(0, SCHEDULED_POST_PREVIEW_TEXT_LENGTH);
|
.slice(0, SCHEDULED_POST_PREVIEW_TEXT_LENGTH);
|
||||||
|
|
||||||
|
@ -61,7 +55,7 @@ export const ScheduledPostsListCmd = postCmd({
|
||||||
|
|
||||||
const finalMessage = trimLines(`
|
const finalMessage = trimLines(`
|
||||||
${postLines.join("\n")}
|
${postLines.join("\n")}
|
||||||
|
|
||||||
Use \`scheduled_posts <num>\` to view a scheduled post in full
|
Use \`scheduled_posts <num>\` to view a scheduled post in full
|
||||||
Use \`scheduled_posts delete <num>\` to delete a scheduled post
|
Use \`scheduled_posts delete <num>\` to delete a scheduled post
|
||||||
`);
|
`);
|
||||||
|
|
|
@ -138,7 +138,7 @@ export async function actualPostCmd(
|
||||||
|
|
||||||
await pluginData.state.scheduledPosts.create({
|
await pluginData.state.scheduledPosts.create({
|
||||||
author_id: msg.author.id,
|
author_id: msg.author.id,
|
||||||
author_name: `${msg.author.username}#${msg.author.discriminator}`,
|
author_name: msg.author.tag,
|
||||||
channel_id: targetChannel.id,
|
channel_id: targetChannel.id,
|
||||||
content,
|
content,
|
||||||
attachments: msg.attachments.array(),
|
attachments: msg.attachments.array(),
|
||||||
|
|
|
@ -17,10 +17,8 @@ export const ButtonInteractionEvt = reactionRolesEvt({
|
||||||
event: "interactionCreate",
|
event: "interactionCreate",
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
const int = meta.args.interaction.isMessageComponent()
|
const int = meta.args.interaction;
|
||||||
? (meta.args.interaction as MessageComponentInteraction)
|
if (!int.isMessageComponent()) return;
|
||||||
: null;
|
|
||||||
if (!int) return;
|
|
||||||
|
|
||||||
const cfg = meta.pluginData.config.get();
|
const cfg = meta.pluginData.config.get();
|
||||||
const split = int.customId.split(BUTTON_CONTEXT_SEPARATOR);
|
const split = int.customId.split(BUTTON_CONTEXT_SEPARATOR);
|
||||||
|
@ -32,6 +30,10 @@ export const ButtonInteractionEvt = reactionRolesEvt({
|
||||||
};
|
};
|
||||||
|
|
||||||
if (context.stateless) {
|
if (context.stateless) {
|
||||||
|
if (context.roleOrMenu == null) {
|
||||||
|
// Not reaction from this plugin
|
||||||
|
return;
|
||||||
|
}
|
||||||
const timeSinceCreation = moment.utc().valueOf() - idToTimestamp(int.message.id)!;
|
const timeSinceCreation = moment.utc().valueOf() - idToTimestamp(int.message.id)!;
|
||||||
if (timeSinceCreation >= BUTTON_INVALIDATION_TIME) {
|
if (timeSinceCreation >= BUTTON_INVALIDATION_TIME) {
|
||||||
sendEphemeralReply(
|
sendEphemeralReply(
|
||||||
|
@ -51,7 +53,7 @@ export const ButtonInteractionEvt = reactionRolesEvt({
|
||||||
.getPlugin(LogsPlugin)
|
.getPlugin(LogsPlugin)
|
||||||
.log(
|
.log(
|
||||||
LogType.BOT_ALERT,
|
LogType.BOT_ALERT,
|
||||||
`**A configuration error occured** on buttons for message ${int.message.id}, group **${context.groupName}** not found in config`,
|
`**A configuration error occurred** on buttons for message ${int.message.id}, group **${context.groupName}** not found in config`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +65,7 @@ export const ButtonInteractionEvt = reactionRolesEvt({
|
||||||
.getPlugin(LogsPlugin)
|
.getPlugin(LogsPlugin)
|
||||||
.log(
|
.log(
|
||||||
LogType.BOT_ALERT,
|
LogType.BOT_ALERT,
|
||||||
`**A internal error occured** on buttons for message ${int.message.id}, action **${context.action}** is not known`,
|
`**A internal error occurred** on buttons for message ${int.message.id}, action **${context.action}** is not known`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,7 @@ export async function addMemberPendingRoleChange(
|
||||||
"Reaction roles",
|
"Reaction roles",
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(
|
logger.warn(`Failed to apply role changes to ${member.user.tag} (${member.id}): ${e.message}`);
|
||||||
`Failed to apply role changes to ${member.user.username}#${member.user.discriminator} (${member.id}): ${e.message}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
|
@ -22,7 +22,7 @@ export async function handleOpenMenu(
|
||||||
.getPlugin(LogsPlugin)
|
.getPlugin(LogsPlugin)
|
||||||
.log(
|
.log(
|
||||||
LogType.BOT_ALERT,
|
LogType.BOT_ALERT,
|
||||||
`**A configuration error occured** on buttons for message ${int.message.id}, no menus found in config`,
|
`**A configuration error occurred** on buttons for message ${int.message.id}, no menus found in config`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -52,14 +52,13 @@ export async function handleOpenMenu(
|
||||||
.getPlugin(LogsPlugin)
|
.getPlugin(LogsPlugin)
|
||||||
.log(
|
.log(
|
||||||
LogType.BOT_ALERT,
|
LogType.BOT_ALERT,
|
||||||
`**A configuration error occured** on buttons for message ${int.message.id}, menu **${context.roleOrMenu}** not found in config`,
|
`**A configuration error occurred** on buttons for message ${int.message.id}, menu **${context.roleOrMenu}** not found in config`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rows = splitButtonsIntoRows(menuButtons, Object.values(group.button_menus[context.roleOrMenu])); // new MessageActionRow().addComponents(menuButtons);
|
const rows = splitButtonsIntoRows(menuButtons, Object.values(group.button_menus[context.roleOrMenu])); // new MessageActionRow().addComponents(menuButtons);
|
||||||
|
|
||||||
int.reply({ content: `Click to add/remove a role`, components: rows, ephemeral: true });
|
int.reply({ content: `Click to add/remove a role`, components: rows, ephemeral: true });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleModifyRole(
|
export async function handleModifyRole(
|
||||||
|
@ -78,7 +77,7 @@ export async function handleModifyRole(
|
||||||
.getPlugin(LogsPlugin)
|
.getPlugin(LogsPlugin)
|
||||||
.log(
|
.log(
|
||||||
LogType.BOT_ALERT,
|
LogType.BOT_ALERT,
|
||||||
`**A configuration error occured** on buttons for message ${int.message.id}, role **${context.roleOrMenu}** not found on server`,
|
`**A configuration error occurred** on buttons for message ${int.message.id}, role **${context.roleOrMenu}** not found on server`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -101,10 +100,7 @@ export async function handleModifyRole(
|
||||||
.getPlugin(LogsPlugin)
|
.getPlugin(LogsPlugin)
|
||||||
.log(
|
.log(
|
||||||
LogType.BOT_ALERT,
|
LogType.BOT_ALERT,
|
||||||
`**A configuration error occured** on buttons for message ${int.message.id}, error: ${e}. We might be missing permissions!`,
|
`**A configuration error occurred** on buttons for message ${int.message.id}, error: ${e}. We might be missing permissions!`,
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { GuildPluginData } from "knub";
|
||||||
import { ReactionRolesPluginType } from "../types";
|
import { ReactionRolesPluginType } from "../types";
|
||||||
import { ButtonMenuActions } from "./buttonMenuActions";
|
import { ButtonMenuActions } from "./buttonMenuActions";
|
||||||
|
|
||||||
export const BUTTON_CONTEXT_SEPARATOR = "::";
|
export const BUTTON_CONTEXT_SEPARATOR = ":rb:";
|
||||||
|
|
||||||
export async function getButtonAction(pluginData: GuildPluginData<ReactionRolesPluginType>, roleOrMenu: string) {
|
export async function getButtonAction(pluginData: GuildPluginData<ReactionRolesPluginType>, roleOrMenu: string) {
|
||||||
if (await pluginData.guild.roles.fetch(roleOrMenu as Snowflake).catch(() => false)) {
|
if (await pluginData.guild.roles.fetch(roleOrMenu as Snowflake).catch(() => false)) {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { Util } from "discord.js";
|
||||||
import { ChannelTypeStrings } from "src/types";
|
import { ChannelTypeStrings } from "src/types";
|
||||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||||
import { asSingleLine, disableInlineCode } from "../../../utils";
|
import { asSingleLine } from "../../../utils";
|
||||||
import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions";
|
import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions";
|
||||||
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
||||||
import { BOT_SLOWMODE_CLEAR_PERMISSIONS } from "../requiredPermissions";
|
import { BOT_SLOWMODE_CLEAR_PERMISSIONS } from "../requiredPermissions";
|
||||||
|
@ -45,7 +46,7 @@ export const SlowmodeClearCmd = slowmodeCmd({
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel,
|
msg.channel,
|
||||||
asSingleLine(`
|
asSingleLine(`
|
||||||
Failed to clear slowmode from **${args.user.username}#${args.user.discriminator}** in <#${args.channel.id}>:
|
Failed to clear slowmode from **${args.user.tag}** in <#${args.channel.id}>:
|
||||||
Threads cannot have Bot Slowmode
|
Threads cannot have Bot Slowmode
|
||||||
`),
|
`),
|
||||||
);
|
);
|
||||||
|
@ -56,17 +57,13 @@ export const SlowmodeClearCmd = slowmodeCmd({
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel,
|
msg.channel,
|
||||||
asSingleLine(`
|
asSingleLine(`
|
||||||
Failed to clear slowmode from **${args.user.username}#${args.user.discriminator}** in <#${args.channel.id}>:
|
Failed to clear slowmode from **${args.user.tag}** in <#${args.channel.id}>:
|
||||||
\`${disableInlineCode(e.message)}\`
|
\`${Util.escapeInlineCode(e.message)}\`
|
||||||
`),
|
`),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(pluginData, msg.channel, `Slowmode cleared from **${args.user.tag}** in <#${args.channel.id}>`);
|
||||||
pluginData,
|
|
||||||
msg.channel,
|
|
||||||
`Slowmode cleared from **${args.user.username}#${args.user.discriminator}** in <#${args.channel.id}>`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const SlowmodeGetCmd = slowmodeCmd({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSlowmode) {
|
if (currentSlowmode) {
|
||||||
const humanized = humanizeDuration(channel.rateLimitPerUser * 1000);
|
const humanized = humanizeDuration(currentSlowmode * 1000);
|
||||||
const slowmodeType = isNative ? "native" : "bot-maintained";
|
const slowmodeType = isNative ? "native" : "bot-maintained";
|
||||||
msg.channel.send(`The current slowmode of <#${channel.id}> is **${humanized}** (${slowmodeType})`);
|
msg.channel.send(`The current slowmode of <#${channel.id}> is **${humanized}** (${slowmodeType})`);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Permissions, TextChannel, ThreadChannel } from "discord.js";
|
import { Permissions, TextChannel, ThreadChannel, Util } from "discord.js";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
import { ChannelTypeStrings } from "src/types";
|
import { ChannelTypeStrings } from "src/types";
|
||||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||||
import { asSingleLine, DAYS, disableInlineCode, HOURS, MINUTES } from "../../../utils";
|
import { asSingleLine, DAYS, HOURS, MINUTES } from "../../../utils";
|
||||||
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
||||||
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
||||||
import { BOT_SLOWMODE_PERMISSIONS, NATIVE_SLOWMODE_PERMISSIONS } from "../requiredPermissions";
|
import { BOT_SLOWMODE_PERMISSIONS, NATIVE_SLOWMODE_PERMISSIONS } from "../requiredPermissions";
|
||||||
|
@ -89,7 +89,7 @@ export const SlowmodeSetCmd = slowmodeCmd({
|
||||||
|
|
||||||
if (mode === "native") {
|
if (mode === "native") {
|
||||||
const missingPermissions = getMissingPermissions(
|
const missingPermissions = getMissingPermissions(
|
||||||
channelPermissions ? channelPermissions : new Permissions(),
|
channelPermissions ?? new Permissions(),
|
||||||
NATIVE_SLOWMODE_PERMISSIONS,
|
NATIVE_SLOWMODE_PERMISSIONS,
|
||||||
);
|
);
|
||||||
if (missingPermissions) {
|
if (missingPermissions) {
|
||||||
|
@ -104,7 +104,7 @@ export const SlowmodeSetCmd = slowmodeCmd({
|
||||||
|
|
||||||
if (mode === "bot") {
|
if (mode === "bot") {
|
||||||
const missingPermissions = getMissingPermissions(
|
const missingPermissions = getMissingPermissions(
|
||||||
channelPermissions ? channelPermissions : new Permissions(),
|
channelPermissions ?? new Permissions(),
|
||||||
BOT_SLOWMODE_PERMISSIONS,
|
BOT_SLOWMODE_PERMISSIONS,
|
||||||
);
|
);
|
||||||
if (missingPermissions) {
|
if (missingPermissions) {
|
||||||
|
@ -133,7 +133,7 @@ export const SlowmodeSetCmd = slowmodeCmd({
|
||||||
rateLimitPerUser: rateLimitSeconds,
|
rateLimitPerUser: rateLimitSeconds,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendErrorMessage(pluginData, msg.channel, `Failed to set native slowmode: ${disableInlineCode(e.message)}`);
|
sendErrorMessage(pluginData, msg.channel, `Failed to set native slowmode: ${Util.escapeInlineCode(e.message)}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -21,7 +21,7 @@ export async function applyBotSlowmodeToUserId(
|
||||||
await channel.permissionOverwrites.create(userId as Snowflake, { SEND_MESSAGES: false }, { type: 1 });
|
await channel.permissionOverwrites.create(userId as Snowflake, { SEND_MESSAGES: false }, { type: 1 });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const user = (await pluginData.client.users.fetch(userId as Snowflake)) || new UnknownUser({ id: userId });
|
const user = await pluginData.client.users.fetch(userId as Snowflake).catch(() => new UnknownUser({ id: userId }));
|
||||||
|
|
||||||
if (isDiscordAPIError(e) && e.code === 50013) {
|
if (isDiscordAPIError(e) && e.code === 50013) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
|
|
@ -21,8 +21,9 @@ export async function clearExpiredSlowmodes(pluginData: GuildPluginData<Slowmode
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
const realUser =
|
const realUser = await pluginData.client
|
||||||
pluginData.client.users!.fetch(user.user_id as Snowflake) || new UnknownUser({ id: user.user_id });
|
.users!.fetch(user.user_id as Snowflake)
|
||||||
|
.catch(() => new UnknownUser({ id: user.user_id }));
|
||||||
|
|
||||||
pluginData.state.logs.log(LogType.BOT_ALERT, {
|
pluginData.state.logs.log(LogType.BOT_ALERT, {
|
||||||
body: `Failed to clear slowmode permissions from {userMention(user)} in {channelMention(channel)}`,
|
body: `Failed to clear slowmode permissions from {userMention(user)} in {channelMention(channel)}`,
|
||||||
|
|
|
@ -120,8 +120,8 @@ export async function logAndDetectMessageSpam(
|
||||||
// Then, if enabled, remove the spam messages
|
// Then, if enabled, remove the spam messages
|
||||||
if (spamConfig.clean !== false) {
|
if (spamConfig.clean !== false) {
|
||||||
msgIds.forEach(id => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id));
|
msgIds.forEach(id => pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id));
|
||||||
(pluginData.guild.channels.cache.get(savedMessage.channel_id as Snowflake)! as TextChannel)
|
(pluginData.guild.channels.cache.get(savedMessage.channel_id as Snowflake)! as TextChannel | undefined)
|
||||||
.bulkDelete(msgIds as Snowflake[])
|
?.bulkDelete(msgIds as Snowflake[])
|
||||||
.catch(noop);
|
.catch(noop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,19 +18,17 @@ export function createStarboardEmbedFromMessage(
|
||||||
text: `#${(msg.channel as GuildChannel).name}`,
|
text: `#${(msg.channel as GuildChannel).name}`,
|
||||||
},
|
},
|
||||||
author: {
|
author: {
|
||||||
name: `${msg.author.username}#${msg.author.discriminator}`,
|
name: msg.author.tag,
|
||||||
},
|
},
|
||||||
fields: [],
|
fields: [],
|
||||||
timestamp: msg.createdAt,
|
timestamp: msg.createdTimestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
embed.color = color;
|
embed.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.author.avatarURL()) {
|
embed.author.icon_url = msg.author.displayAvatarURL({ dynamic: true });
|
||||||
embed.author.icon_url = msg.author.avatarURL()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The second condition here checks for messages with only an image link that is then embedded.
|
// The second condition here checks for messages with only an image link that is then embedded.
|
||||||
// The message content in that case is hidden by the Discord client, so we hide it here too.
|
// The message content in that case is hidden by the Discord client, so we hide it here too.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Message, MessageEmbed, Snowflake, TextChannel } from "discord.js";
|
import { Message, MessageEmbedOptions, Snowflake, TextChannel } from "discord.js";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import { StarboardPluginType, TStarboardOpts } from "../types";
|
import { StarboardPluginType, TStarboardOpts } from "../types";
|
||||||
import { createStarboardEmbedFromMessage } from "./createStarboardEmbedFromMessage";
|
import { createStarboardEmbedFromMessage } from "./createStarboardEmbedFromMessage";
|
||||||
|
@ -16,6 +16,6 @@ export async function saveMessageToStarboard(
|
||||||
const embed = createStarboardEmbedFromMessage(msg, Boolean(starboard.copy_full_embed), starboard.color);
|
const embed = createStarboardEmbedFromMessage(msg, Boolean(starboard.copy_full_embed), starboard.color);
|
||||||
embed.fields!.push(createStarboardPseudoFooterForMessage(starboard, msg, starboard.star_emoji![0], starCount));
|
embed.fields!.push(createStarboardPseudoFooterForMessage(starboard, msg, starboard.star_emoji![0], starCount));
|
||||||
|
|
||||||
const starboardMessage = await (channel as TextChannel).send({ embeds: [embed as MessageEmbed] });
|
const starboardMessage = await (channel as TextChannel).send({ embeds: [embed as MessageEmbedOptions] });
|
||||||
await pluginData.state.starboardMessages.createStarboardMessage(channel.id, msg.id, starboardMessage.id);
|
await pluginData.state.starboardMessages.createStarboardMessage(channel.id, msg.id, starboardMessage.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { Util } from "discord.js";
|
||||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||||
import { disableInlineCode, trimLines } from "../../../utils";
|
import { trimLines } from "../../../utils";
|
||||||
import { parseFuzzyTimezone } from "../../../utils/parseFuzzyTimezone";
|
import { parseFuzzyTimezone } from "../../../utils/parseFuzzyTimezone";
|
||||||
import { timeAndDateCmd } from "../types";
|
import { timeAndDateCmd } from "../types";
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ export const SetTimezoneCmd = timeAndDateCmd({
|
||||||
pluginData,
|
pluginData,
|
||||||
message.channel,
|
message.channel,
|
||||||
trimLines(`
|
trimLines(`
|
||||||
Invalid timezone: \`${disableInlineCode(args.timezone)}\`
|
Invalid timezone: \`${Util.escapeInlineCode(args.timezone)}\`
|
||||||
Zeppelin uses timezone locations rather than specific timezone names.
|
Zeppelin uses timezone locations rather than specific timezone names.
|
||||||
See the **TZ database name** column at <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones> for a list of valid options.
|
See the **TZ database name** column at <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones> for a list of valid options.
|
||||||
`),
|
`),
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { UsernameSaverPluginType } from "./types";
|
||||||
|
|
||||||
export async function updateUsername(pluginData: GuildPluginData<UsernameSaverPluginType>, user: User) {
|
export async function updateUsername(pluginData: GuildPluginData<UsernameSaverPluginType>, user: User) {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
const newUsername = `${user.username}#${user.discriminator}`;
|
const newUsername = user.tag;
|
||||||
const latestEntry = await pluginData.state.usernameHistory.getLastEntry(user.id);
|
const latestEntry = await pluginData.state.usernameHistory.getLastEntry(user.id);
|
||||||
if (!latestEntry || newUsername !== latestEntry.username) {
|
if (!latestEntry || newUsername !== latestEntry.username) {
|
||||||
await pluginData.state.usernameHistory.addEntry(user.id, newUsername);
|
await pluginData.state.usernameHistory.addEntry(user.id, newUsername);
|
||||||
|
|
|
@ -39,8 +39,6 @@ export const AboutCmd = utilityCmd({
|
||||||
version = "?";
|
version = "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
// const shard = pluginData.client.shards.get(pluginData.client.guildShardMap[pluginData.guild.id])!; FIXME Sharding stuff
|
|
||||||
|
|
||||||
const lastReload = humanizeDuration(Date.now() - pluginData.state.lastReload, {
|
const lastReload = humanizeDuration(Date.now() - pluginData.state.lastReload, {
|
||||||
largest: 2,
|
largest: 2,
|
||||||
round: true,
|
round: true,
|
||||||
|
@ -51,7 +49,7 @@ export const AboutCmd = utilityCmd({
|
||||||
["Last reload", `${lastReload} ago`],
|
["Last reload", `${lastReload} ago`],
|
||||||
["Last update", lastUpdate],
|
["Last update", lastUpdate],
|
||||||
["Version", version],
|
["Version", version],
|
||||||
// ["API latency", `${shard.latency}ms`],
|
["API latency", `${pluginData.client.ws.ping}ms`],
|
||||||
["Server timezone", timeAndDate.getGuildTz()],
|
["Server timezone", timeAndDate.getGuildTz()],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -70,11 +68,7 @@ export const AboutCmd = utilityCmd({
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: "Status",
|
name: "Status",
|
||||||
value: basicInfoRows
|
value: basicInfoRows.map(([label, value]) => `${label}: **${value}**`).join("\n"),
|
||||||
.map(([label, value]) => {
|
|
||||||
return `${label}: **${value}**`;
|
|
||||||
})
|
|
||||||
.join("\n"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Loaded plugins on this server (${loadedPlugins.length})`,
|
name: `Loaded plugins on this server (${loadedPlugins.length})`,
|
||||||
|
|
|
@ -16,15 +16,12 @@ export const AvatarCmd = utilityCmd({
|
||||||
async run({ message: msg, args, pluginData }) {
|
async run({ message: msg, args, pluginData }) {
|
||||||
const user = args.user || msg.author;
|
const user = args.user || msg.author;
|
||||||
if (!(user instanceof UnknownUser)) {
|
if (!(user instanceof UnknownUser)) {
|
||||||
const avatar = user.avatarURL() || user.defaultAvatarURL;
|
|
||||||
let extension = avatar.slice(avatar.lastIndexOf("."), avatar.lastIndexOf("?"));
|
|
||||||
// Some pngs can have the .jpg extention for some reason, so we always use .png for static images
|
|
||||||
extension = extension === ".gif" ? extension : ".png";
|
|
||||||
const avatarUrl = avatar.slice(0, avatar.lastIndexOf("."));
|
|
||||||
const embed: MessageEmbedOptions = {
|
const embed: MessageEmbedOptions = {
|
||||||
image: { url: avatarUrl + `${extension}?size=2048` },
|
image: {
|
||||||
|
url: user.displayAvatarURL({ dynamic: true, format: "png", size: 2048 }),
|
||||||
|
},
|
||||||
|
title: `Avatar of ${user.tag}:`,
|
||||||
};
|
};
|
||||||
embed.title = `Avatar of ${user.username}#${user.discriminator}:`;
|
|
||||||
msg.channel.send({ embeds: [embed] });
|
msg.channel.send({ embeds: [embed] });
|
||||||
} else {
|
} else {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Invalid user ID");
|
sendErrorMessage(pluginData, msg.channel, "Invalid user ID");
|
||||||
|
|
|
@ -80,7 +80,7 @@ export const HelpCmd = utilityCmd({
|
||||||
? `Results (${totalResults} total, showing first ${limitedResults.length}):\n\n`
|
? `Results (${totalResults} total, showing first ${limitedResults.length}):\n\n`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
message += `${commandSnippets.join("\n\n")}`;
|
message += commandSnippets.join("\n\n");
|
||||||
createChunkedMessage(msg.channel, message);
|
createChunkedMessage(msg.channel, message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { MessageAttachment } from "discord.js";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import twemoji from "twemoji";
|
import twemoji from "twemoji";
|
||||||
|
@ -39,8 +40,8 @@ export const JumboCmd = utilityCmd({
|
||||||
const size = config.jumbo_size > 2048 ? 2048 : config.jumbo_size;
|
const size = config.jumbo_size > 2048 ? 2048 : config.jumbo_size;
|
||||||
const emojiRegex = new RegExp(`(<.*:).*:(\\d+)`);
|
const emojiRegex = new RegExp(`(<.*:).*:(\\d+)`);
|
||||||
const results = emojiRegex.exec(args.emoji);
|
const results = emojiRegex.exec(args.emoji);
|
||||||
let extention = ".png";
|
let extension = ".png";
|
||||||
let file;
|
let file: MessageAttachment | undefined;
|
||||||
|
|
||||||
if (!isEmoji(args.emoji)) {
|
if (!isEmoji(args.emoji)) {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Invalid emoji");
|
sendErrorMessage(pluginData, msg.channel, "Invalid emoji");
|
||||||
|
@ -50,25 +51,19 @@ export const JumboCmd = utilityCmd({
|
||||||
if (results) {
|
if (results) {
|
||||||
let url = "https://cdn.discordapp.com/emojis/";
|
let url = "https://cdn.discordapp.com/emojis/";
|
||||||
if (results[1] === "<a:") {
|
if (results[1] === "<a:") {
|
||||||
extention = ".gif";
|
extension = ".gif";
|
||||||
}
|
}
|
||||||
url += `${results[2]}${extention}`;
|
url += `${results[2]}${extension}`;
|
||||||
if (extention === ".png") {
|
if (extension === ".png") {
|
||||||
const image = await resizeBuffer(await getBufferFromUrl(url), size, size);
|
const image = await resizeBuffer(await getBufferFromUrl(url), size, size);
|
||||||
file = {
|
file = new MessageAttachment(image, `emoji${extension}`);
|
||||||
name: `emoji${extention}`,
|
|
||||||
file: image,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
const image = await getBufferFromUrl(url);
|
const image = await getBufferFromUrl(url);
|
||||||
file = {
|
file = new MessageAttachment(image, `emoji${extension}`);
|
||||||
name: `emoji${extention}`,
|
|
||||||
file: image,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let url = CDN_URL + `/${twemoji.convert.toCodePoint(args.emoji)}.svg`;
|
let url = CDN_URL + `/${twemoji.convert.toCodePoint(args.emoji)}.svg`;
|
||||||
let image;
|
let image: Buffer | undefined;
|
||||||
try {
|
try {
|
||||||
image = await resizeBuffer(await getBufferFromUrl(url), size, size);
|
image = await resizeBuffer(await getBufferFromUrl(url), size, size);
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -77,12 +72,14 @@ export const JumboCmd = utilityCmd({
|
||||||
image = await resizeBuffer(await getBufferFromUrl(url), size, size);
|
image = await resizeBuffer(await getBufferFromUrl(url), size, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file = {
|
if (!image) {
|
||||||
name: `emoji.png`,
|
sendErrorMessage(pluginData, msg.channel, "Invalid emoji");
|
||||||
file: image,
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
file = new MessageAttachment(image, "emoji.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.channel.send({ content: "", files: [file] });
|
msg.channel.send({ files: [file] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,8 +17,6 @@ export const LevelCmd = utilityCmd({
|
||||||
run({ message, args, pluginData }) {
|
run({ message, args, pluginData }) {
|
||||||
const member = args.member || message.member;
|
const member = args.member || message.member;
|
||||||
const level = getMemberLevel(pluginData, member);
|
const level = getMemberLevel(pluginData, member);
|
||||||
message.channel.send(
|
message.channel.send(`The permission level of ${member.user.tag} is **${level}**`);
|
||||||
`The permission level of ${member.user.username}#${member.user.discriminator} is **${level}**`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,8 +29,6 @@ export const PingCmd = utilityCmd({
|
||||||
const lowest = Math.round(Math.min(...times));
|
const lowest = Math.round(Math.min(...times));
|
||||||
const mean = Math.round(times.reduce((total, ms) => total + ms, 0) / times.length);
|
const mean = Math.round(times.reduce((total, ms) => total + ms, 0) / times.length);
|
||||||
|
|
||||||
// const shard = pluginData.client.shards.get(pluginData.client.guildShardMap[pluginData.guild.id])!; FIXME sharding stuff
|
|
||||||
|
|
||||||
msg.channel.send(
|
msg.channel.send(
|
||||||
trimLines(`
|
trimLines(`
|
||||||
**Ping:**
|
**Ping:**
|
||||||
|
@ -38,7 +36,8 @@ export const PingCmd = utilityCmd({
|
||||||
Highest: **${highest}ms**
|
Highest: **${highest}ms**
|
||||||
Mean: **${mean}ms**
|
Mean: **${mean}ms**
|
||||||
Time between ping command and first reply: **${msgToMsgDelay!}ms**
|
Time between ping command and first reply: **${msgToMsgDelay!}ms**
|
||||||
`), // Omitted line: Shard latency: **${shard.latency}ms**
|
Shard latency: **${pluginData.client.ws.ping}ms**
|
||||||
|
`),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Clean up test messages
|
// Clean up test messages
|
||||||
|
|
|
@ -34,13 +34,9 @@ export const RolesCmd = utilityCmd({
|
||||||
if (args.counts) {
|
if (args.counts) {
|
||||||
await refreshMembersIfNeeded(guild);
|
await refreshMembersIfNeeded(guild);
|
||||||
|
|
||||||
// If the user requested role member counts as well, calculate them and sort the roles by their member count
|
// If the user requested role member counts as well, fetch them and sort the roles by their member count
|
||||||
const roleCounts: Map<string, number> = Array.from(guild.members.cache.values()).reduce((map, member) => {
|
const roleCounts: Map<string, number> = Array.from(guild.roles.cache.values()).reduce((map, role) => {
|
||||||
for (const roleId of member.roles.cache) {
|
map.set(role.id, role.members.size);
|
||||||
if (!map.has(roleId)) map.set(roleId, 0);
|
|
||||||
map.set(roleId, map.get(roleId) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}, new Map());
|
}, new Map());
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const VcdisconnectCmd = utilityCmd({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args.member.voice || !args.member.voice.channelId) {
|
if (!args.member.voice?.channelId) {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel");
|
sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,6 @@ export const VcdisconnectCmd = utilityCmd({
|
||||||
oldChannel: channelToConfigAccessibleChannel(channel),
|
oldChannel: channelToConfigAccessibleChannel(channel),
|
||||||
});
|
});
|
||||||
|
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(pluginData, msg.channel, `**${args.member.user.tag}** disconnected from **${channel.name}**`);
|
||||||
pluginData,
|
|
||||||
msg.channel,
|
|
||||||
`**${args.member.user.username}#${args.member.user.discriminator}** disconnected from **${channel.name}**`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,7 +57,7 @@ export const VcmoveCmd = utilityCmd({
|
||||||
channel = closestMatch;
|
channel = closestMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args.member.voice || !args.member.voice.channelId) {
|
if (!args.member.voice?.channelId) {
|
||||||
sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel");
|
sendErrorMessage(pluginData, msg.channel, "Member is not in a voice channel");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -85,11 +85,7 @@ export const VcmoveCmd = utilityCmd({
|
||||||
newChannel: channelToConfigAccessibleChannel(channel),
|
newChannel: channelToConfigAccessibleChannel(channel),
|
||||||
});
|
});
|
||||||
|
|
||||||
sendSuccessMessage(
|
sendSuccessMessage(pluginData, msg.channel, `**${args.member.user.tag}** moved to **${channel.name}**`);
|
||||||
pluginData,
|
|
||||||
msg.channel,
|
|
||||||
`**${args.member.user.username}#${args.member.user.discriminator}** moved to **${channel.name}**`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -162,7 +158,7 @@ export const VcmoveAllCmd = utilityCmd({
|
||||||
sendErrorMessage(
|
sendErrorMessage(
|
||||||
pluginData,
|
pluginData,
|
||||||
msg.channel,
|
msg.channel,
|
||||||
`Failed to move ${currMember.user.username}#${currMember.user.discriminator} (${currMember.id}): You cannot act on this member`,
|
`Failed to move ${currMember.user.tag} (${currMember.id}): You cannot act on this member`,
|
||||||
);
|
);
|
||||||
errAmt++;
|
errAmt++;
|
||||||
continue;
|
continue;
|
||||||
|
@ -177,11 +173,7 @@ export const VcmoveAllCmd = utilityCmd({
|
||||||
sendErrorMessage(pluginData, msg.channel, "Unknown error when trying to move members");
|
sendErrorMessage(pluginData, msg.channel, "Unknown error when trying to move members");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendErrorMessage(
|
sendErrorMessage(pluginData, msg.channel, `Failed to move ${currMember.user.tag} (${currMember.id})`);
|
||||||
pluginData,
|
|
||||||
msg.channel,
|
|
||||||
`Failed to move ${currMember.user.username}#${currMember.user.discriminator} (${currMember.id})`,
|
|
||||||
);
|
|
||||||
errAmt++;
|
errAmt++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const AutoJoinThreadEvt = utilityEvt({
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
const config = meta.pluginData.config.get();
|
const config = meta.pluginData.config.get();
|
||||||
if (config.autojoin_threads && meta.args.thread.joinable && !meta.args.thread.joined) {
|
if (config.autojoin_threads && meta.args.thread.joinable) {
|
||||||
await meta.args.thread.join();
|
await meta.args.thread.join();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -16,11 +16,10 @@ export const AutoJoinThreadSyncEvt = utilityEvt({
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
const config = meta.pluginData.config.get();
|
const config = meta.pluginData.config.get();
|
||||||
if (config.autojoin_threads) {
|
if (!config.autojoin_threads) return;
|
||||||
for (const thread of meta.args.threads.values()) {
|
for (const thread of meta.args.threads.values()) {
|
||||||
if (!thread.joined && thread.joinable) {
|
if (!thread.joined && thread.joinable) {
|
||||||
await thread.join();
|
await thread.join();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { MessageEmbedOptions, Snowflake, StageChannel, VoiceChannel } from "discord.js";
|
import { MessageEmbedOptions, Snowflake, StageChannel, ThreadChannel, VoiceChannel } from "discord.js";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { ChannelTypeStrings } from "src/types";
|
import { ChannelTypeStrings } from "src/types";
|
||||||
import { EmbedWith, formatNumber, preEmbedPadding, trimLines } from "../../../utils";
|
import { EmbedWith, formatNumber, MINUTES, preEmbedPadding, trimLines, verboseUserMention } from "../../../utils";
|
||||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
||||||
import { UtilityPluginType } from "../types";
|
import { UtilityPluginType } from "../types";
|
||||||
|
|
||||||
|
@ -15,6 +15,10 @@ const ANNOUNCEMENT_CHANNEL_ICON =
|
||||||
"https://cdn.discordapp.com/attachments/740650744830623756/740656841687564348/announcement-channel.png";
|
"https://cdn.discordapp.com/attachments/740650744830623756/740656841687564348/announcement-channel.png";
|
||||||
const STAGE_CHANNEL_ICON =
|
const STAGE_CHANNEL_ICON =
|
||||||
"https://cdn.discordapp.com/attachments/740650744830623756/839930647711186995/stage-channel.png";
|
"https://cdn.discordapp.com/attachments/740650744830623756/839930647711186995/stage-channel.png";
|
||||||
|
const PUBLIC_THREAD_ICON =
|
||||||
|
"https://cdn.discordapp.com/attachments/740650744830623756/870343055855738921/public-thread.png";
|
||||||
|
const PRIVATE_THREAD_UCON =
|
||||||
|
"https://cdn.discordapp.com/attachments/740650744830623756/870343402447839242/private-thread.png";
|
||||||
|
|
||||||
export async function getChannelInfoEmbed(
|
export async function getChannelInfoEmbed(
|
||||||
pluginData: GuildPluginData<UtilityPluginType>,
|
pluginData: GuildPluginData<UtilityPluginType>,
|
||||||
|
@ -30,14 +34,14 @@ export async function getChannelInfoEmbed(
|
||||||
fields: [],
|
fields: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon = TEXT_CHANNEL_ICON;
|
const icon =
|
||||||
if (channel.type === ChannelTypeStrings.VOICE) {
|
{
|
||||||
icon = VOICE_CHANNEL_ICON;
|
[ChannelTypeStrings.VOICE]: VOICE_CHANNEL_ICON,
|
||||||
} else if (channel.type === ChannelTypeStrings.NEWS) {
|
[ChannelTypeStrings.NEWS]: ANNOUNCEMENT_CHANNEL_ICON,
|
||||||
icon = ANNOUNCEMENT_CHANNEL_ICON;
|
[ChannelTypeStrings.STAGE]: STAGE_CHANNEL_ICON,
|
||||||
} else if (channel.type === ChannelTypeStrings.STAGE) {
|
[ChannelTypeStrings.PUBLIC_THREAD]: PUBLIC_THREAD_ICON,
|
||||||
icon = STAGE_CHANNEL_ICON;
|
[ChannelTypeStrings.PRIVATE_THREAD]: PRIVATE_THREAD_UCON,
|
||||||
}
|
}[channel.type] || TEXT_CHANNEL_ICON;
|
||||||
|
|
||||||
const channelType =
|
const channelType =
|
||||||
{
|
{
|
||||||
|
@ -47,6 +51,9 @@ export async function getChannelInfoEmbed(
|
||||||
[ChannelTypeStrings.NEWS]: "Announcement channel",
|
[ChannelTypeStrings.NEWS]: "Announcement channel",
|
||||||
[ChannelTypeStrings.STORE]: "Store channel",
|
[ChannelTypeStrings.STORE]: "Store channel",
|
||||||
[ChannelTypeStrings.STAGE]: "Stage channel",
|
[ChannelTypeStrings.STAGE]: "Stage channel",
|
||||||
|
[ChannelTypeStrings.PUBLIC_THREAD]: "Public Thread channel",
|
||||||
|
[ChannelTypeStrings.PRIVATE_THREAD]: "Private Thread channel",
|
||||||
|
[ChannelTypeStrings.NEWS_THREAD]: "News Thread channel",
|
||||||
}[channel.type] || "Channel";
|
}[channel.type] || "Channel";
|
||||||
|
|
||||||
embed.author = {
|
embed.author = {
|
||||||
|
@ -121,5 +128,23 @@ export async function getChannelInfoEmbed(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (channel.type === ChannelTypeStrings.PRIVATE_THREAD || channel.type === ChannelTypeStrings.PUBLIC_THREAD) {
|
||||||
|
const thread = channel as ThreadChannel;
|
||||||
|
const parentChannelName = thread.parent?.name ? thread.parent.name : `<#${thread.parentId}>`;
|
||||||
|
const memberCount = thread.memberCount ?? thread.members.cache.size;
|
||||||
|
const owner = await pluginData.guild.members.fetch(thread.ownerId).catch(() => null);
|
||||||
|
const ownerMention = owner ? verboseUserMention(owner.user) : "Unknown#0000";
|
||||||
|
const humanizedArchiveTime = `Archive duration: **${humanizeDuration(thread.autoArchiveDuration * MINUTES)}**`;
|
||||||
|
|
||||||
|
embed.fields.push({
|
||||||
|
name: preEmbedPadding + "Thread information",
|
||||||
|
value: trimLines(`
|
||||||
|
Parent channel: **#${parentChannelName}**
|
||||||
|
Member count: **${memberCount}**
|
||||||
|
Thread creator: ${ownerMention}
|
||||||
|
${thread.archived ? "Archived: **True**" : humanizedArchiveTime}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return embed;
|
return embed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ export async function getInviteInfoEmbed(
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "Invite creator",
|
name: preEmbedPadding + "Invite creator",
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
Name: **${invite.inviter.username}#${invite.inviter.discriminator}**
|
Name: **${invite.inviter.tag}**
|
||||||
ID: \`${invite.inviter.id}\`
|
ID: \`${invite.inviter.id}\`
|
||||||
Mention: <@!${invite.inviter.id}>
|
Mention: <@!${invite.inviter.id}>
|
||||||
`),
|
`),
|
||||||
|
@ -143,7 +143,7 @@ export async function getInviteInfoEmbed(
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "Invite creator",
|
name: preEmbedPadding + "Invite creator",
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
Name: **${invite.inviter.username}#${invite.inviter.discriminator}**
|
Name: **${invite.inviter.tag}**
|
||||||
ID: \`${invite.inviter.id}\`
|
ID: \`${invite.inviter.id}\`
|
||||||
Mention: <@!${invite.inviter.id}>
|
Mention: <@!${invite.inviter.id}>
|
||||||
`),
|
`),
|
||||||
|
|
|
@ -112,7 +112,7 @@ export async function getMessageInfoEmbed(
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "Author information",
|
name: preEmbedPadding + "Author information",
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
Name: **${message.author.username}#${message.author.discriminator}**
|
Name: **${message.author.tag}**
|
||||||
ID: \`${message.author.id}\`
|
ID: \`${message.author.id}\`
|
||||||
Created: **${authorAccountAge} ago** (\`${prettyAuthorCreatedAt}\`)
|
Created: **${authorAccountAge} ago** (\`${prettyAuthorCreatedAt}\`)
|
||||||
${authorJoinedAt ? `Joined: **${authorServerAge} ago** (\`${prettyAuthorJoinedAt}\`)` : ""}
|
${authorJoinedAt ? `Joined: **${authorServerAge} ago** (\`${prettyAuthorJoinedAt}\`)` : ""}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { CategoryChannel, MessageEmbedOptions, Snowflake, TextChannel, VoiceChannel } from "discord.js";
|
import { MessageEmbedOptions, Snowflake } from "discord.js";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
|
import { ChannelTypeStrings } from "../../../types";
|
||||||
import {
|
import {
|
||||||
EmbedWith,
|
EmbedWith,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
@ -13,6 +14,7 @@ import {
|
||||||
resolveUser,
|
resolveUser,
|
||||||
trimLines,
|
trimLines,
|
||||||
} from "../../../utils";
|
} from "../../../utils";
|
||||||
|
import { idToTimestamp } from "../../../utils/idToTimestamp";
|
||||||
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
|
||||||
import { UtilityPluginType } from "../types";
|
import { UtilityPluginType } from "../types";
|
||||||
import { getGuildPreview } from "./getGuildPreview";
|
import { getGuildPreview } from "./getGuildPreview";
|
||||||
|
@ -50,7 +52,7 @@ export async function getServerInfoEmbed(
|
||||||
|
|
||||||
// BASIC INFORMATION
|
// BASIC INFORMATION
|
||||||
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
||||||
const createdAt = moment.utc((guildPreview || restGuild)!.id, "x"); // FIXME ID -> Timestamp
|
const createdAt = moment.utc(idToTimestamp((guildPreview || restGuild)!.id)!, "x");
|
||||||
const tzCreatedAt = requestMemberId
|
const tzCreatedAt = requestMemberId
|
||||||
? await timeAndDate.inMemberTz(requestMemberId, createdAt)
|
? await timeAndDate.inMemberTz(requestMemberId, createdAt)
|
||||||
: timeAndDate.inGuildTz(createdAt);
|
: timeAndDate.inGuildTz(createdAt);
|
||||||
|
@ -65,7 +67,7 @@ export async function getServerInfoEmbed(
|
||||||
|
|
||||||
if (thisServer) {
|
if (thisServer) {
|
||||||
const owner = await resolveUser(pluginData.client, thisServer.ownerId);
|
const owner = await resolveUser(pluginData.client, thisServer.ownerId);
|
||||||
const ownerName = `${owner.username}#${owner.discriminator}`;
|
const ownerName = owner.tag;
|
||||||
|
|
||||||
basicInformation.push(`Owner: **${ownerName}** (\`${thisServer.ownerId}\`)`);
|
basicInformation.push(`Owner: **${ownerName}** (\`${thisServer.ownerId}\`)`);
|
||||||
// basicInformation.push(`Voice region: **${thisServer.region}**`); Outdated, as automatic voice regions are fully live
|
// basicInformation.push(`Voice region: **${thisServer.region}**`); Outdated, as automatic voice regions are fully live
|
||||||
|
@ -81,12 +83,11 @@ export async function getServerInfoEmbed(
|
||||||
});
|
});
|
||||||
|
|
||||||
// IMAGE LINKS
|
// IMAGE LINKS
|
||||||
const iconUrl = `[Link](${(restGuild || guildPreview)!.iconURL()})`;
|
const iconUrl = `[Link](${(restGuild || guildPreview)!.iconURL({ dynamic: true, format: "png", size: 2048 })})`;
|
||||||
const bannerUrl = restGuild?.bannerURL() ? `[Link](${restGuild.bannerURL()})` : "None";
|
const bannerUrl = restGuild?.banner ? `[Link](${restGuild.bannerURL({ format: "png", size: 2048 })})` : "None";
|
||||||
const splashUrl =
|
const splashUrl = (restGuild || guildPreview)!.splash
|
||||||
(restGuild || guildPreview)!.splashURL() != null
|
? `[Link](${(restGuild || guildPreview)!.splashURL({ format: "png", size: 2048 })})`
|
||||||
? `[Link](${(restGuild || guildPreview)!.splashURL()?.replace("size=128", "size=2048")})`
|
: "None";
|
||||||
: "None";
|
|
||||||
|
|
||||||
embed.fields.push(
|
embed.fields.push(
|
||||||
{
|
{
|
||||||
|
@ -154,9 +155,9 @@ export async function getServerInfoEmbed(
|
||||||
// CHANNEL COUNTS
|
// CHANNEL COUNTS
|
||||||
if (thisServer) {
|
if (thisServer) {
|
||||||
const totalChannels = thisServer.channels.cache.size;
|
const totalChannels = thisServer.channels.cache.size;
|
||||||
const categories = thisServer.channels.cache.filter(channel => channel instanceof CategoryChannel);
|
const categories = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.CATEGORY);
|
||||||
const textChannels = thisServer.channels.cache.filter(channel => channel instanceof TextChannel);
|
const textChannels = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.TEXT);
|
||||||
const voiceChannels = thisServer.channels.cache.filter(channel => channel instanceof VoiceChannel);
|
const voiceChannels = thisServer.channels.cache.filter(channel => channel.type === ChannelTypeStrings.VOICE);
|
||||||
|
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "Channels",
|
name: preEmbedPadding + "Channels",
|
||||||
|
@ -194,7 +195,7 @@ export async function getServerInfoEmbed(
|
||||||
}[restGuild.premiumTier] || 0;
|
}[restGuild.premiumTier] || 0;
|
||||||
|
|
||||||
otherStats.push(`Emojis: **${restGuild.emojis.cache.size}** / ${maxEmojis * 2}`);
|
otherStats.push(`Emojis: **${restGuild.emojis.cache.size}** / ${maxEmojis * 2}`);
|
||||||
otherStats.push(`Stickers: ? / ${maxStickers}`); // Wait on DJS: **${restGuild.stickers.cache.size}**
|
otherStats.push(`Stickers: **${restGuild.stickers.cache.size}** / ${maxStickers}`);
|
||||||
} else {
|
} else {
|
||||||
otherStats.push(`Emojis: **${guildPreview!.emojis.size}**`);
|
otherStats.push(`Emojis: **${guildPreview!.emojis.size}**`);
|
||||||
// otherStats.push(`Stickers: **${guildPreview!.stickers.size}**`); Wait on DJS
|
// otherStats.push(`Stickers: **${guildPreview!.stickers.size}**`); Wait on DJS
|
||||||
|
|
|
@ -36,10 +36,10 @@ export async function getUserInfoEmbed(
|
||||||
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
|
||||||
|
|
||||||
embed.author = {
|
embed.author = {
|
||||||
name: `User: ${user.username}#${user.discriminator}`,
|
name: `User: ${user.tag}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const avatarURL = user.avatarURL() || user.defaultAvatarURL;
|
const avatarURL = user.displayAvatarURL();
|
||||||
embed.author.icon_url = avatarURL;
|
embed.author.icon_url = avatarURL;
|
||||||
|
|
||||||
const createdAt = moment.utc(user.createdAt, "x");
|
const createdAt = moment.utc(user.createdAt, "x");
|
||||||
|
@ -84,7 +84,7 @@ export async function getUserInfoEmbed(
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "User information",
|
name: preEmbedPadding + "User information",
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
Name: **${user.username}#${user.discriminator}**
|
Name: **${user.tag}**
|
||||||
ID: \`${user.id}\`
|
ID: \`${user.id}\`
|
||||||
Created: **${accountAge} ago** (\`${prettyCreatedAt}\`)
|
Created: **${accountAge} ago** (\`${prettyCreatedAt}\`)
|
||||||
Mention: <@!${user.id}>
|
Mention: <@!${user.id}>
|
||||||
|
@ -103,7 +103,7 @@ export async function getUserInfoEmbed(
|
||||||
});
|
});
|
||||||
const roles = member.roles.cache
|
const roles = member.roles.cache
|
||||||
.map(role => pluginData.guild.roles.cache.get(role.id))
|
.map(role => pluginData.guild.roles.cache.get(role.id))
|
||||||
.filter(r => r != null) as Role[];
|
.filter((r): r is Role => !!r);
|
||||||
roles.sort(sorter("position", "DESC"));
|
roles.sort(sorter("position", "DESC"));
|
||||||
|
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
|
@ -119,7 +119,7 @@ export async function getUserInfoEmbed(
|
||||||
embed.fields.push({
|
embed.fields.push({
|
||||||
name: preEmbedPadding + "Voice information",
|
name: preEmbedPadding + "Voice information",
|
||||||
value: trimLines(`
|
value: trimLines(`
|
||||||
${voiceChannel ? `Current voice channel: **${voiceChannel ? voiceChannel.name : "None"}**` : ""}
|
${voiceChannel ? `Current voice channel: **${voiceChannel.name ?? "None"}**` : ""}
|
||||||
${member.voice.mute ? "Server voice muted: **Yes**" : ""}
|
${member.voice.mute ? "Server voice muted: **Yes**" : ""}
|
||||||
${member.voice.deaf ? "Server voice deafened: **Yes**" : ""}
|
${member.voice.deaf ? "Server voice deafened: **Yes**" : ""}
|
||||||
`),
|
`),
|
||||||
|
|
|
@ -177,17 +177,11 @@ export async function displaySearch(
|
||||||
.setEmoji("⬅")
|
.setEmoji("⬅")
|
||||||
.setCustomId(`previousButton:${idMod}`)
|
.setCustomId(`previousButton:${idMod}`)
|
||||||
.setDisabled(currentPage === 1),
|
.setDisabled(currentPage === 1),
|
||||||
);
|
|
||||||
|
|
||||||
buttons.push(
|
|
||||||
new MessageButton()
|
new MessageButton()
|
||||||
.setStyle("SECONDARY")
|
.setStyle("SECONDARY")
|
||||||
.setEmoji("➡")
|
.setEmoji("➡")
|
||||||
.setCustomId(`nextButton:${idMod}`)
|
.setCustomId(`nextButton:${idMod}`)
|
||||||
.setDisabled(currentPage === searchResult.lastPage),
|
.setDisabled(currentPage === searchResult.lastPage),
|
||||||
);
|
|
||||||
|
|
||||||
buttons.push(
|
|
||||||
new MessageButton()
|
new MessageButton()
|
||||||
.setStyle("SECONDARY")
|
.setStyle("SECONDARY")
|
||||||
.setEmoji("🔄")
|
.setEmoji("🔄")
|
||||||
|
@ -197,8 +191,7 @@ export async function displaySearch(
|
||||||
const row = new MessageActionRow().addComponents(buttons);
|
const row = new MessageActionRow().addComponents(buttons);
|
||||||
await searchMsg.edit({ content: result, components: [row] });
|
await searchMsg.edit({ content: result, components: [row] });
|
||||||
|
|
||||||
const filter = (iac: MessageComponentInteraction) => iac.message.id === searchMsg.id;
|
const collector = searchMsg.createMessageComponentCollector({ time: 2 * MINUTES });
|
||||||
const collector = searchMsg.createMessageComponentCollector({ filter, time: 2 * MINUTES });
|
|
||||||
|
|
||||||
collector.on("collect", async (interaction: MessageComponentInteraction) => {
|
collector.on("collect", async (interaction: MessageComponentInteraction) => {
|
||||||
if (msg.author.id !== interaction.user.id) {
|
if (msg.author.id !== interaction.user.id) {
|
||||||
|
@ -325,7 +318,7 @@ async function performMemberSearch(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.voice) {
|
if (args.voice) {
|
||||||
matchingMembers = matchingMembers.filter(m => m.voice.channelId != null);
|
matchingMembers = matchingMembers.filter(m => m.voice.channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.bot) {
|
if (args.bot) {
|
||||||
|
@ -391,7 +384,7 @@ async function performMemberSearch(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullUsername = `${member.user.username}#${member.user.discriminator}`;
|
const fullUsername = member.user.tag;
|
||||||
if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -458,7 +451,7 @@ async function performBanSearch(
|
||||||
|
|
||||||
const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex);
|
const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex);
|
||||||
matchingBans = await asyncFilter(matchingBans, async user => {
|
matchingBans = await asyncFilter(matchingBans, async user => {
|
||||||
const fullUsername = `${user.username}#${user.discriminator}`;
|
const fullUsername = user.tag;
|
||||||
if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -502,10 +495,10 @@ function formatSearchResultList(members: Array<GuildMember | User>): string {
|
||||||
const paddedId = member.id.padEnd(longestId, " ");
|
const paddedId = member.id.padEnd(longestId, " ");
|
||||||
let line;
|
let line;
|
||||||
if (member instanceof GuildMember) {
|
if (member instanceof GuildMember) {
|
||||||
line = `${paddedId} ${member.user.username}#${member.user.discriminator}`;
|
line = `${paddedId} ${member.user.tag}`;
|
||||||
if (member.nickname) line += ` (${member.nickname})`;
|
if (member.nickname) line += ` (${member.nickname})`;
|
||||||
} else {
|
} else {
|
||||||
line = `${paddedId} ${member.username}#${member.discriminator}`;
|
line = `${paddedId} ${member.tag}`;
|
||||||
}
|
}
|
||||||
return line;
|
return line;
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
TextChannel,
|
TextChannel,
|
||||||
ThreadChannel,
|
ThreadChannel,
|
||||||
User,
|
User,
|
||||||
|
Util,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import emojiRegex from "emoji-regex";
|
import emojiRegex from "emoji-regex";
|
||||||
import { either } from "fp-ts/lib/Either";
|
import { either } from "fp-ts/lib/Either";
|
||||||
|
@ -212,7 +213,7 @@ export function differenceToString(diff: Map<string, { was: any; is: any }>): st
|
||||||
let toReturn = "";
|
let toReturn = "";
|
||||||
diff = prettyDifference(diff);
|
diff = prettyDifference(diff);
|
||||||
for (const [key, difference] of diff) {
|
for (const [key, difference] of diff) {
|
||||||
toReturn += `${key[0].toUpperCase() + key.slice(1)}: \`${difference.was}\` ➜ \`${difference.is}\`\n`;
|
toReturn += `**${key[0].toUpperCase() + key.slice(1)}**: \`${difference.was}\` ➜ \`${difference.is}\`\n`;
|
||||||
}
|
}
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
@ -785,21 +786,6 @@ export function deactivateMentions(content: string): string {
|
||||||
return content.replace(/@/g, "@\u200b");
|
return content.replace(/@/g, "@\u200b");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable inline code in the given string by replacing backticks/grave accents with acute accents
|
|
||||||
* FIXME: Find a better way that keeps the grave accents? Can't use the code block approach here since it's just 1 character.
|
|
||||||
*/
|
|
||||||
export function disableInlineCode(content: string): string {
|
|
||||||
return content.replace(/`/g, "\u00b4");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable code blocks in the given string by adding invisible unicode characters between backticks
|
|
||||||
*/
|
|
||||||
export function disableCodeBlocks(content: string): string {
|
|
||||||
return content.replace(/`/g, "`\u200b");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMediaUrls(content: string): string {
|
export function useMediaUrls(content: string): string {
|
||||||
return content.replace(/cdn\.discord(app)?\.com/g, "media.discordapp.net");
|
return content.replace(/cdn\.discord(app)?\.com/g, "media.discordapp.net");
|
||||||
}
|
}
|
||||||
|
@ -1099,6 +1085,7 @@ export class UnknownUser {
|
||||||
public id: string;
|
public id: string;
|
||||||
public username = "Unknown";
|
public username = "Unknown";
|
||||||
public discriminator = "0000";
|
public discriminator = "0000";
|
||||||
|
public tag = "Unknown#0000";
|
||||||
|
|
||||||
constructor(props = {}) {
|
constructor(props = {}) {
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
|
@ -1281,7 +1268,7 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Role name
|
// Role name
|
||||||
const roleList = await (await bot.guilds.fetch(guildId as Snowflake)).roles.cache;
|
const roleList = (await bot.guilds.fetch(guildId as Snowflake)).roles.cache;
|
||||||
const role = roleList.filter(x => x.name.toLocaleLowerCase() === value.toLocaleLowerCase());
|
const role = roleList.filter(x => x.name.toLocaleLowerCase() === value.toLocaleLowerCase());
|
||||||
if (role[0]) {
|
if (role[0]) {
|
||||||
return role[0].id;
|
return role[0].id;
|
||||||
|
@ -1309,7 +1296,6 @@ export async function resolveInvite<T extends boolean>(
|
||||||
return inviteCache.get(key) as ResolveInviteReturnType<T>;
|
return inviteCache.get(key) as ResolveInviteReturnType<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore: the getInvite() withCounts typings are blergh
|
|
||||||
const promise = client.fetchInvite(code).catch(() => null);
|
const promise = client.fetchInvite(code).catch(() => null);
|
||||||
inviteCache.set(key, promise);
|
inviteCache.set(key, promise);
|
||||||
|
|
||||||
|
@ -1318,12 +1304,14 @@ export async function resolveInvite<T extends boolean>(
|
||||||
|
|
||||||
const internalStickerCache: LimitedCollection<Snowflake, Sticker> = new LimitedCollection(500);
|
const internalStickerCache: LimitedCollection<Snowflake, Sticker> = new LimitedCollection(500);
|
||||||
|
|
||||||
export async function resolveStickerId(bot: Client, id: Snowflake): Promise<Sticker> {
|
export async function resolveStickerId(bot: Client, id: Snowflake): Promise<Sticker | null> {
|
||||||
const cachedSticker = internalStickerCache.get(id);
|
const cachedSticker = internalStickerCache.get(id);
|
||||||
if (cachedSticker) return cachedSticker;
|
if (cachedSticker) return cachedSticker;
|
||||||
|
|
||||||
const fetchedSticker = await bot.fetchSticker(id).catch(undefined);
|
const fetchedSticker = await bot.fetchSticker(id).catch(() => null);
|
||||||
internalStickerCache.set(id, fetchedSticker);
|
if (fetchedSticker) {
|
||||||
|
internalStickerCache.set(id, fetchedSticker);
|
||||||
|
}
|
||||||
|
|
||||||
return fetchedSticker;
|
return fetchedSticker;
|
||||||
}
|
}
|
||||||
|
@ -1334,11 +1322,11 @@ export async function confirm(channel: TextChannel, userId: string, content: Mes
|
||||||
|
|
||||||
export function messageSummary(msg: SavedMessage) {
|
export function messageSummary(msg: SavedMessage) {
|
||||||
// Regular text content
|
// Regular text content
|
||||||
let result = "```\n" + (msg.data.content ? disableCodeBlocks(msg.data.content) : "<no text content>") + "```";
|
let result = "```\n" + (msg.data.content ? Util.escapeCodeBlock(msg.data.content) : "<no text content>") + "```";
|
||||||
|
|
||||||
// Rich embed
|
// Rich embed
|
||||||
const richEmbed = (msg.data.embeds || []).find(e => (e as MessageEmbed).type === "rich");
|
const richEmbed = (msg.data.embeds || []).find(e => (e as MessageEmbed).type === "rich");
|
||||||
if (richEmbed) result += "Embed:```" + disableCodeBlocks(JSON.stringify(richEmbed)) + "```";
|
if (richEmbed) result += "Embed:```" + Util.escapeCodeBlock(JSON.stringify(richEmbed)) + "```";
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
if (msg.data.attachments) {
|
if (msg.data.attachments) {
|
||||||
|
@ -1353,18 +1341,18 @@ export function messageSummary(msg: SavedMessage) {
|
||||||
|
|
||||||
export function verboseUserMention(user: User | UnknownUser): string {
|
export function verboseUserMention(user: User | UnknownUser): string {
|
||||||
if (user.id == null) {
|
if (user.id == null) {
|
||||||
return `**${user.username}#${user.discriminator}**`;
|
return `**${user.tag}**`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<@!${user.id}> (**${user.username}#${user.discriminator}**, \`${user.id}\`)`;
|
return `<@!${user.id}> (**${user.tag}**, \`${user.id}\`)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verboseUserName(user: User | UnknownUser): string {
|
export function verboseUserName(user: User | UnknownUser): string {
|
||||||
if (user.id == null) {
|
if (user.id == null) {
|
||||||
return `**${user.username}#${user.discriminator}**`;
|
return `**${user.tag}**`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `**${user.username}#${user.discriminator}** (\`${user.id}\`)`;
|
return `**${user.tag}** (\`${user.id}\`)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verboseChannelMention(channel: GuildChannel): string {
|
export function verboseChannelMention(channel: GuildChannel): string {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import {
|
import {
|
||||||
|
Emoji,
|
||||||
GuildChannel,
|
GuildChannel,
|
||||||
GuildMember,
|
GuildMember,
|
||||||
PartialGuildMember,
|
PartialGuildMember,
|
||||||
Role,
|
Role,
|
||||||
Snowflake,
|
Snowflake,
|
||||||
StageInstance,
|
StageInstance,
|
||||||
|
Sticker,
|
||||||
ThreadChannel,
|
ThreadChannel,
|
||||||
User,
|
User,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
|
@ -15,6 +17,7 @@ export interface IConfigAccessibleUser {
|
||||||
username: string;
|
username: string;
|
||||||
discriminator: string;
|
discriminator: string;
|
||||||
mention: string;
|
mention: string;
|
||||||
|
tag: string;
|
||||||
avatarURL?: string;
|
avatarURL?: string;
|
||||||
bot?: boolean;
|
bot?: boolean;
|
||||||
createdAt?: number;
|
createdAt?: number;
|
||||||
|
@ -38,12 +41,13 @@ export interface IConfigAccessibleMember extends IConfigAccessibleUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function userToConfigAccessibleUser(user: User | UnknownUser): IConfigAccessibleUser {
|
export function userToConfigAccessibleUser(user: User | UnknownUser): IConfigAccessibleUser {
|
||||||
if (`${user.username}#${user.discriminator}` === "Unknown#0000") {
|
if (user.tag === "Unknown#0000") {
|
||||||
const toReturnPartial: IConfigAccessibleUser = {
|
const toReturnPartial: IConfigAccessibleUser = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: "Unknown",
|
username: "Unknown",
|
||||||
discriminator: "0000",
|
discriminator: "0000",
|
||||||
mention: `<@${user.id}>`,
|
mention: `<@${user.id}>`,
|
||||||
|
tag: "Unknown#0000",
|
||||||
};
|
};
|
||||||
|
|
||||||
return toReturnPartial;
|
return toReturnPartial;
|
||||||
|
@ -55,6 +59,7 @@ export function userToConfigAccessibleUser(user: User | UnknownUser): IConfigAcc
|
||||||
username: properUser.username,
|
username: properUser.username,
|
||||||
discriminator: properUser.discriminator,
|
discriminator: properUser.discriminator,
|
||||||
mention: `<@${properUser.id}>`,
|
mention: `<@${properUser.id}>`,
|
||||||
|
tag: properUser.tag,
|
||||||
avatarURL: properUser.displayAvatarURL({ dynamic: true }),
|
avatarURL: properUser.displayAvatarURL({ dynamic: true }),
|
||||||
bot: properUser.bot,
|
bot: properUser.bot,
|
||||||
createdAt: properUser.createdTimestamp,
|
createdAt: properUser.createdTimestamp,
|
||||||
|
@ -127,3 +132,51 @@ export function stageToConfigAccessibleStage(stage: StageInstance): IConfigAcces
|
||||||
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IConfigAccessibleEmoji {
|
||||||
|
id: Snowflake;
|
||||||
|
name: string;
|
||||||
|
createdAt?: number;
|
||||||
|
animated: boolean;
|
||||||
|
identifier: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emojiToConfigAccessibleEmoji(emoji: Emoji): IConfigAccessibleEmoji {
|
||||||
|
const toReturn: IConfigAccessibleEmoji = {
|
||||||
|
id: emoji.id!,
|
||||||
|
name: emoji.name!,
|
||||||
|
createdAt: emoji.createdTimestamp ?? undefined,
|
||||||
|
animated: emoji.animated ?? false,
|
||||||
|
identifier: emoji.identifier,
|
||||||
|
};
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConfigAccessibleSticker {
|
||||||
|
id: Snowflake;
|
||||||
|
guildId?: Snowflake;
|
||||||
|
packId?: Snowflake;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
tags: string;
|
||||||
|
format: string;
|
||||||
|
animated: boolean;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stickerToConfigAccessibleSticker(sticker: Sticker): IConfigAccessibleSticker {
|
||||||
|
const toReturn: IConfigAccessibleSticker = {
|
||||||
|
id: sticker.id,
|
||||||
|
guildId: sticker.guildId ?? undefined,
|
||||||
|
packId: sticker.packId ?? undefined,
|
||||||
|
name: sticker.name,
|
||||||
|
description: sticker.description ?? "",
|
||||||
|
tags: sticker.tags?.join(", ") ?? "",
|
||||||
|
format: sticker.format,
|
||||||
|
animated: sticker.format === "PNG" ? false : true,
|
||||||
|
url: sticker.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import { Permissions } from "discord.js";
|
import { Permissions } from "discord.js";
|
||||||
|
|
||||||
const camelCaseToTitleCase = str =>
|
|
||||||
str
|
|
||||||
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
||||||
.split(" ")
|
|
||||||
.map(w => w[0].toUpperCase() + w.slice(1))
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
const permissionNumberToName: Map<bigint, string> = new Map();
|
const permissionNumberToName: Map<bigint, string> = new Map();
|
||||||
const ignoredPermissionConstants = ["all", "allGuild", "allText", "allVoice"];
|
const ignoredPermissionConstants = ["all", "allGuild", "allText", "allVoice"];
|
||||||
|
|
||||||
for (const key in Permissions.FLAGS) {
|
for (const key in Permissions.FLAGS) {
|
||||||
if (ignoredPermissionConstants.includes(key)) continue;
|
if (ignoredPermissionConstants.includes(key)) continue;
|
||||||
permissionNumberToName.set(BigInt(Permissions.FLAGS[key]), camelCaseToTitleCase(key));
|
permissionNumberToName.set(BigInt(Permissions.FLAGS[key]), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import moment from "moment";
|
import { Snowflake, SnowflakeUtil } from "discord.js";
|
||||||
|
|
||||||
const EPOCH = 1420070400000;
|
|
||||||
|
|
||||||
export function idToTimestamp(id: string) {
|
export function idToTimestamp(id: string) {
|
||||||
if (typeof id === "number") return null;
|
if (typeof id === "number") return null;
|
||||||
return moment(+id / 4194304 + EPOCH)
|
return SnowflakeUtil.deconstruct(id as Snowflake).timestamp;
|
||||||
.utc()
|
|
||||||
.valueOf();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,7 @@ export async function waitForButtonConfirm(
|
||||||
]);
|
]);
|
||||||
const message = await channel.send({ ...toPost, components: [row] });
|
const message = await channel.send({ ...toPost, components: [row] });
|
||||||
|
|
||||||
const filter = (iac: MessageComponentInteraction) => iac.message.id === message.id;
|
const collector = message.createMessageComponentCollector({ time: 10000 });
|
||||||
const collector = message.createMessageComponentCollector({ filter, time: 10000 });
|
|
||||||
|
|
||||||
collector.on("collect", (interaction: MessageComponentInteraction) => {
|
collector.on("collect", (interaction: MessageComponentInteraction) => {
|
||||||
if (options?.restrictToId && options.restrictToId !== interaction.user.id) {
|
if (options?.restrictToId && options.restrictToId !== interaction.user.id) {
|
||||||
|
|
|
@ -37,6 +37,12 @@ const LOG_TYPES = {
|
||||||
"STAGE_INSTANCE_CREATE": "Stage created",
|
"STAGE_INSTANCE_CREATE": "Stage created",
|
||||||
"STAGE_INSTANCE_DELETE": "Stage deleted",
|
"STAGE_INSTANCE_DELETE": "Stage deleted",
|
||||||
"STAGE_INSTANCE_UPDATE": "Stage updated",
|
"STAGE_INSTANCE_UPDATE": "Stage updated",
|
||||||
|
"EMOJI_CREATE": "Emoji created",
|
||||||
|
"EMOJI_DELETE": "Emoji deleted",
|
||||||
|
"EMOJI_UPDATE": "Emoji updated",
|
||||||
|
"STICKER_CREATE": "Sticker created",
|
||||||
|
"STICKER_DELETE": "Sticker deleted",
|
||||||
|
"STICKER_UPDATE": "Sticker updated",
|
||||||
"COMMAND": "Command used",
|
"COMMAND": "Command used",
|
||||||
"MESSAGE_SPAM_DETECTED": "Message spam detected",
|
"MESSAGE_SPAM_DETECTED": "Message spam detected",
|
||||||
"CENSOR": "Message censored",
|
"CENSOR": "Message censored",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue