Merge pull request #238 from DarkView/djs

This commit is contained in:
Miikka 2021-08-14 14:35:45 +03:00 committed by GitHub
commit 09f7cbbd25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
517 changed files with 8840 additions and 4185 deletions

2
.gitignore vendored
View file

@ -77,3 +77,5 @@ npm-audit.txt
# Debug files
*.debug.ts
*.debug.js
.vscode/

2
.nvmrc
View file

@ -1 +1 @@
14
16.6

1413
backend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -23,24 +23,23 @@
"test-watch": "tsc-watch --onSuccess \"npx ava\""
},
"dependencies": {
"@types/sharp": "^0.23.1",
"@types/twemoji": "^12.1.0",
"bufferutil": "^4.0.1",
"bufferutil": "^4.0.3",
"cors": "^2.8.5",
"cross-env": "^5.2.0",
"deep-diff": "^1.0.2",
"discord-api-types": "^0.22.0",
"discord.js": "^13.1.0",
"dotenv": "^4.0.0",
"emoji-regex": "^8.0.0",
"eris": "^0.15.1",
"erlpack": "github:abalabahaha/erlpack",
"erlpack": "github:almeidx/erlpack#f0c535f73817fd914806d6ca26a7730c14e0fb7c",
"escape-string-regexp": "^1.0.5",
"express": "^4.17.0",
"fp-ts": "^2.0.1",
"humanize-duration": "^3.15.0",
"io-ts": "^2.0.0",
"js-yaml": "^3.13.1",
"knub": "^30.0.0-beta.37",
"knub-command-manager": "^8.1.2",
"knub": "file:../../Knub",
"knub-command-manager": "^9.1.0",
"last-commit-log": "^2.1.0",
"lodash.chunk": "^4.2.0",
"lodash.clonedeep": "^4.5.0",
@ -59,13 +58,14 @@
"regexp-worker": "^1.1.0",
"safe-regex": "^2.0.2",
"seedrandom": "^3.0.1",
"sharp": "^0.23.4",
"sharp": "github:almeidx/sharp#68b4f387ae2ee1ee2dd8f289f5ec5fcf722fd3d3",
"strip-combining-marks": "^1.0.0",
"tlds": "^1.203.1",
"tmp": "0.0.33",
"tsconfig-paths": "^3.9.0",
"twemoji": "^12.1.4",
"typeorm": "^0.2.31",
"utf-8-validate": "^5.0.5",
"uuid": "^3.3.2",
"yawn-yaml": "github:dragory/yawn-yaml#string-number-fix-build",
"zlib-sync": "^0.1.7"
@ -82,12 +82,14 @@
"@types/passport-oauth2": "^1.4.8",
"@types/passport-strategy": "^0.2.35",
"@types/safe-regex": "^1.1.2",
"@types/sharp": "^0.23.1",
"@types/tmp": "0.0.33",
"@types/twemoji": "^12.1.0",
"ava": "^3.10.0",
"rimraf": "^2.6.2",
"source-map-support": "^0.5.16",
"tsc-watch": "^4.0.0",
"typescript": "^4.1.3"
"typescript": "^4.3.4"
},
"ava": {
"files": [

View file

@ -1,6 +1,6 @@
import util from "util";
export class ErisError extends Error {
export class DiscordJSError extends Error {
code: number | string | undefined;
shardId: number;
@ -11,6 +11,6 @@ export class ErisError extends Error {
}
[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}`;
}
}

View file

@ -1,4 +1,4 @@
import { Guild } from "eris";
import { Guild } from "discord.js";
export enum ERRORS {
NO_MUTE_ROLE_IN_CONFIG = 1,

View file

@ -1,7 +1,7 @@
import { RegExpWorker, TimeoutError } from "regexp-worker";
import { CooldownManager } from "knub";
import { MINUTES, SECONDS } from "./utils";
import { EventEmitter } from "events";
import { CooldownManager } from "knub";
import { RegExpWorker, TimeoutError } from "regexp-worker";
import { MINUTES, SECONDS } from "./utils";
import Timeout = NodeJS.Timeout;
const isTimeoutError = (a): a is TimeoutError => {

View file

@ -1,7 +1,7 @@
import express, { Request, Response } from "express";
import moment from "moment-timezone";
import { GuildArchives } from "../data/GuildArchives";
import { notFound } from "./responses";
import moment from "moment-timezone";
export function initArchives(app: express.Express) {
const archives = new GuildArchives(null);

View file

@ -1,13 +1,13 @@
import express, { Request, Response } from "express";
import passport from "passport";
import OAuth2Strategy from "passport-oauth2";
import { Strategy as CustomStrategy } from "passport-custom";
import { ApiLogins } from "../data/ApiLogins";
import pick from "lodash.pick";
import https from "https";
import pick from "lodash.pick";
import passport from "passport";
import { Strategy as CustomStrategy } from "passport-custom";
import OAuth2Strategy from "passport-oauth2";
import { ApiLogins } from "../data/ApiLogins";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
import { ApiUserInfo } from "../data/ApiUserInfo";
import { ApiUserInfoData } from "../data/entities/ApiUserInfo";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
import { ok } from "./responses";
interface IPassportApiUser {

View file

@ -1,7 +1,7 @@
import express from "express";
import { guildPlugins } from "../plugins/availablePlugins";
import { notFound } from "./responses";
import { indentLines } from "../utils";
import { notFound } from "./responses";
function formatConfigSchema(schema) {
if (schema._tag === "InterfaceType" || schema._tag === "PartialType") {

View file

@ -1,13 +1,13 @@
import express, { Request, Response } from "express";
import { AllowedGuilds } from "../data/AllowedGuilds";
import { clientError, ok, serverError, unauthorized } from "./responses";
import { Configs } from "../data/Configs";
import { validateGuildConfig } from "../configValidator";
import yaml, { YAMLException } from "js-yaml";
import { apiTokenAuthHandlers } from "./auth";
import { ApiPermissions } from "@shared/apiPermissions";
import { hasGuildPermission, requireGuildPermission } from "./permissions";
import express, { Request, Response } from "express";
import yaml, { YAMLException } from "js-yaml";
import { validateGuildConfig } from "../configValidator";
import { AllowedGuilds } from "../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
import { Configs } from "../data/Configs";
import { apiTokenAuthHandlers } from "./auth";
import { hasGuildPermission, requireGuildPermission } from "./permissions";
import { clientError, ok, serverError, unauthorized } from "./responses";
const apiPermissionAssignments = new ApiPermissionAssignments();

View file

@ -1,8 +1,6 @@
import "./loadEnv";
import { connect } from "../data/db";
import path from "path";
import { setIsAPI } from "../globals";
import "./loadEnv";
if (!process.env.KEY) {
// tslint:disable-next-line:no-console

View file

@ -1,7 +1,7 @@
import { ApiPermissions, hasPermission, permissionArrToSet } from "@shared/apiPermissions";
import { isStaff } from "../staff";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
import { Request, Response } from "express";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
import { isStaff } from "../staff";
import { unauthorized } from "./responses";
const apiPermissionAssignments = new ApiPermissionAssignments();

View file

@ -1,6 +1,6 @@
import express, { Request, Response } from "express";
import { apiTokenAuthHandlers } from "./auth";
import { isStaff } from "../staff";
import { apiTokenAuthHandlers } from "./auth";
export function initStaff(app: express.Express) {
const staffRouter = express.Router();

View file

@ -1,11 +1,11 @@
import { clientError, error, notFound } from "./responses";
import express from "express";
import cors from "cors";
import { initAuth } from "./auth";
import { initGuildsAPI } from "./guilds";
import { initArchives } from "./archives";
import { initDocs } from "./docs";
import express from "express";
import { TokenError } from "passport-oauth2";
import { initArchives } from "./archives";
import { initAuth } from "./auth";
import { initDocs } from "./docs";
import { initGuildsAPI } from "./guilds";
import { clientError, error, notFound } from "./responses";
const app = express();

View file

@ -1,9 +1,9 @@
import { GuildChannel, GuildMember, Snowflake, Util, User } from "discord.js";
import { baseCommandParameterTypeHelpers, baseTypeConverters, CommandContext, TypeConversionError } from "knub";
import { createTypeHelper } from "knub-command-manager";
import {
channelMentionRegex,
convertDelayStringToMS,
disableCodeBlocks,
disableInlineCode,
isSnowflake,
isValidSnowflake,
resolveMember,
resolveUser,
@ -11,13 +11,9 @@ import {
roleMentionRegex,
UnknownUser,
} from "./utils";
import { GuildChannel, Member, TextChannel, User } from "eris";
import { baseTypeConverters, baseCommandParameterTypeHelpers, CommandContext, TypeConversionError } from "knub";
import { createTypeHelper } from "knub-command-manager";
import { getChannelIdFromMessageId } from "./data/getChannelIdFromMessageId";
import { isValidTimezone } from "./utils/isValidTimezone";
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget";
import { inputPatternToRegExp } from "./validatorUtils";
import { isValidTimezone } from "./utils/isValidTimezone";
export const commandTypes = {
...baseTypeConverters,
@ -34,7 +30,7 @@ export const commandTypes = {
async resolvedUser(value, context: CommandContext<any>) {
const result = await resolveUser(context.pluginData.client, value);
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;
},
@ -42,7 +38,7 @@ export const commandTypes = {
async resolvedUserLoose(value, context: CommandContext<any>) {
const result = await resolveUser(context.pluginData.client, value);
if (result == null) {
throw new TypeConversionError(`Invalid user: \`${disableCodeBlocks(value)}\``);
throw new TypeConversionError(`Invalid user: \`${Util.escapeCodeBlock(value)}\``);
}
return result;
},
@ -55,7 +51,7 @@ export const commandTypes = {
const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value);
if (result == null) {
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;
@ -66,7 +62,7 @@ export const commandTypes = {
const result = await resolveMessageTarget(context.pluginData, value);
if (!result) {
throw new TypeConversionError(`Unknown message \`${disableInlineCode(value)}\``);
throw new TypeConversionError(`Unknown message \`${Util.escapeInlineCode(value)}\``);
}
return result;
@ -74,32 +70,32 @@ export const commandTypes = {
async anyId(value: string, context: CommandContext<any>) {
const userId = resolveUserId(context.pluginData.client, value);
if (userId) return userId;
if (userId) return userId as Snowflake;
const channelIdMatch = value.match(channelMentionRegex);
if (channelIdMatch) return channelIdMatch[1];
if (channelIdMatch) return channelIdMatch[1] as Snowflake;
const roleIdMatch = value.match(roleMentionRegex);
if (roleIdMatch) return roleIdMatch[1];
if (roleIdMatch) return roleIdMatch[1] as Snowflake;
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 {
try {
return inputPatternToRegExp(value);
} 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) {
if (!isValidTimezone(value)) {
throw new TypeConversionError(`Invalid timezone: ${disableInlineCode(value)}`);
throw new TypeConversionError(`Invalid timezone: ${Util.escapeInlineCode(value)}`);
}
return value;
@ -112,9 +108,9 @@ export const commandTypeHelpers = {
delay: createTypeHelper<number>(commandTypes.delay),
resolvedUser: createTypeHelper<Promise<User>>(commandTypes.resolvedUser),
resolvedUserLoose: createTypeHelper<Promise<User | UnknownUser>>(commandTypes.resolvedUserLoose),
resolvedMember: createTypeHelper<Promise<Member>>(commandTypes.resolvedMember),
resolvedMember: createTypeHelper<Promise<GuildMember>>(commandTypes.resolvedMember),
messageTarget: createTypeHelper<Promise<MessageTarget>>(commandTypes.messageTarget),
anyId: createTypeHelper<Promise<string>>(commandTypes.anyId),
anyId: createTypeHelper<Promise<Snowflake>>(commandTypes.anyId),
regex: createTypeHelper<RegExp>(commandTypes.regex),
timezone: createTypeHelper<string>(commandTypes.timezone),
};

View file

@ -1,10 +1,9 @@
import * as t from "io-ts";
import { guildPlugins } from "./plugins/availablePlugins";
import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { PartialZeppelinGuildConfigSchema, ZeppelinGuildConfig } from "./types";
import { configUtils, ConfigValidationError, PluginOptions } from "knub";
import moment from "moment-timezone";
import { guildPlugins } from "./plugins/availablePlugins";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { PartialZeppelinGuildConfigSchema, ZeppelinGuildConfig } from "./types";
import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils";
const pluginNameToPlugin = new Map<string, ZeppelinPlugin>();
for (const plugin of guildPlugins) {

View file

@ -1,7 +1,7 @@
import { AllowedGuild } from "./entities/AllowedGuild";
import { getRepository, Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository";
import { ApiPermissionTypes } from "./ApiPermissionAssignments";
import { BaseRepository } from "./BaseRepository";
import { AllowedGuild } from "./entities/AllowedGuild";
export class AllowedGuilds extends BaseRepository {
private allowedGuilds: Repository<AllowedGuild>;

View file

@ -1,11 +1,11 @@
import { getRepository, Repository } from "typeorm";
import { ApiLogin } from "./entities/ApiLogin";
import { BaseRepository } from "./BaseRepository";
import crypto from "crypto";
import moment from "moment-timezone";
import { getRepository, Repository } from "typeorm";
// tslint:disable-next-line:no-submodule-imports
import uuidv4 from "uuid/v4";
import { DAYS, DBDateFormat } from "../utils";
import { BaseRepository } from "./BaseRepository";
import { ApiLogin } from "./entities/ApiLogin";
const LOGIN_EXPIRY_TIME = 1 * DAYS;

View file

@ -1,7 +1,7 @@
import { getRepository, Repository } from "typeorm";
import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment";
import { BaseRepository } from "./BaseRepository";
import { ApiPermissions } from "@shared/apiPermissions";
import { getRepository, Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository";
import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment";
export enum ApiPermissionTypes {
User = "USER",

View file

@ -1,9 +1,9 @@
import moment from "moment-timezone";
import { getRepository, Repository } from "typeorm";
import { ApiUserInfo as ApiUserInfoEntity, ApiUserInfoData } from "./entities/ApiUserInfo";
import { DBDateFormat } from "../utils";
import { BaseRepository } from "./BaseRepository";
import { connection } from "./db";
import moment from "moment-timezone";
import { DBDateFormat } from "../utils";
import { ApiUserInfo as ApiUserInfoEntity, ApiUserInfoData } from "./entities/ApiUserInfo";
export class ApiUserInfo extends BaseRepository {
private apiUserInfo: Repository<ApiUserInfoEntity>;

View file

@ -1,10 +1,10 @@
import { Config } from "./entities/Config";
import { getRepository, Repository } from "typeorm";
import { connection } from "./db";
import { BaseRepository } from "./BaseRepository";
import { isAPI } from "../globals";
import { HOURS, SECONDS } from "../utils";
import { BaseRepository } from "./BaseRepository";
import { cleanupConfigs } from "./cleanup/configs";
import { connection } from "./db";
import { Config } from "./entities/Config";
if (isAPI()) {
const CLEANUP_INTERVAL = 1 * HOURS;

View file

@ -24,11 +24,15 @@
"CHANNEL_CREATE": "🖊 Channel {channelMention(channel)} was created",
"CHANNEL_DELETE": "🗑 Channel {channelMention(channel)} was deleted",
"CHANNEL_EDIT": "✏ Channel {channelMention(channel)} was edited",
"CHANNEL_UPDATE": "✏ Channel {channelMention(newChannel)} was edited. Changes:\n{differenceString}",
"THREAD_CREATE": "🖊 Thread {channelMention(thread)} was created in 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}",
"ROLE_CREATE": "🖊 Role **{role.name}** (`{role.id}`) was created",
"ROLE_DELETE": "🖊 Role **{role.name}** (`{role.id}`) was deleted",
"ROLE_EDIT": "🖊 Role **{role.name}** (`{role.id}`) was edited",
"ROLE_UPDATE": "🖊 Role **{newRole.name}** (`{newRole.id}`) was edited. Changes:\n{differenceString}",
"MESSAGE_EDIT": "✏ {userMention(user)} edited their message (`{after.id}`) in {channelMention(channel)}:\n**Before:**{messageSummary(before)}**After:**{messageSummary(after)}",
"MESSAGE_DELETE": "🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}",
@ -42,6 +46,18 @@
"VOICE_CHANNEL_FORCE_MOVE": "\uD83C\uDF99 ✍ {userMention(member)} was moved from **{oldChannel.name}** to **{newChannel.name}** by {userMention(mod)}",
"VOICE_CHANNEL_FORCE_DISCONNECT": "\uD83C\uDF99 🚫 {userMention(member)} was forcefully disconnected from **{oldChannel.name}** by {userMention(mod)}",
"STAGE_INSTANCE_CREATE": "📣 Stage Instance `{stageInstance.topic}` was created 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}",
"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}`",
"MESSAGE_SPAM_DETECTED": "🛑 {userMention(member)} spam detected in {channelMention(channel)}: {description} (more than {limit} in {interval}s)\n{archiveUrl}",

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { AntiraidLevel } from "./entities/AntiraidLevel";
export class GuildAntiraidLevels extends BaseGuildRepository {

View file

@ -1,11 +1,12 @@
import { Guild, Snowflake } from "discord.js";
import moment from "moment-timezone";
import { ArchiveEntry } from "./entities/ArchiveEntry";
import { isDefaultSticker } from "src/utils/isDefaultSticker";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { trimLines } from "../utils";
import { SavedMessage } from "./entities/SavedMessage";
import { Guild } from "eris";
import { renderTemplate } from "../templateFormatter";
import { trimLines } from "../utils";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { ArchiveEntry } from "./entities/ArchiveEntry";
import { SavedMessage } from "./entities/SavedMessage";
const DEFAULT_EXPIRY_DAYS = 30;
@ -13,7 +14,7 @@ const MESSAGE_ARCHIVE_HEADER_FORMAT = trimLines(`
Server: {guild.name} ({guild.id})
`);
const MESSAGE_ARCHIVE_MESSAGE_FORMAT =
"[#{channel.name}] [{user.id}] [{timestamp}] {user.username}#{user.discriminator}: {content}{attachments}";
"[#{channel.name}] [{user.id}] [{timestamp}] {user.username}#{user.discriminator}: {content}{attachments}{stickers}";
export class GuildArchives extends BaseGuildRepository {
protected archives: Repository<ArchiveEntry>;
@ -73,13 +74,19 @@ export class GuildArchives extends BaseGuildRepository {
protected async renderLinesFromSavedMessages(savedMessages: SavedMessage[], guild: Guild) {
const msgLines: string[] = [];
for (const msg of savedMessages) {
const channel = guild.channels.get(msg.channel_id);
const channel = guild.channels.cache.get(msg.channel_id as Snowflake);
const user = { ...msg.data.author, id: msg.user_id };
const line = await renderTemplate(MESSAGE_ARCHIVE_MESSAGE_FORMAT, {
id: msg.id,
timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"),
content: msg.data.content,
attachments: msg.data.attachments?.map(att => {
return JSON.stringify({ name: att.name, url: att.url, type: att.contentType });
}),
stickers: msg.data.stickers?.map(sti => {
return JSON.stringify({ name: sti.name, id: sti.id, isDefault: isDefaultSticker(sti.id) });
}),
user,
channel,
});

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { AutoReaction } from "./entities/AutoReaction";
export class GuildAutoReactions extends BaseGuildRepository {

View file

@ -0,0 +1,58 @@
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { ButtonRole } from "./entities/ButtonRole";
export class GuildButtonRoles extends BaseGuildRepository {
private buttonRoles: Repository<ButtonRole>;
constructor(guildId) {
super(guildId);
this.buttonRoles = getRepository(ButtonRole);
}
async getForButtonId(buttonId: string) {
return this.buttonRoles.findOne({
guild_id: this.guildId,
button_id: buttonId,
});
}
async getAllForMessageId(messageId: string) {
return this.buttonRoles.find({
guild_id: this.guildId,
message_id: messageId,
});
}
async removeForButtonId(buttonId: string) {
return this.buttonRoles.delete({
guild_id: this.guildId,
button_id: buttonId,
});
}
async removeAllForMessageId(messageId: string) {
return this.buttonRoles.delete({
guild_id: this.guildId,
message_id: messageId,
});
}
async getForButtonGroup(buttonGroup: string) {
return this.buttonRoles.find({
guild_id: this.guildId,
button_group: buttonGroup,
});
}
async add(channelId: string, messageId: string, buttonId: string, buttonGroup: string, buttonName: string) {
await this.buttonRoles.insert({
guild_id: this.guildId,
channel_id: channelId,
message_id: messageId,
button_id: buttonId,
button_group: buttonGroup,
button_name: buttonName,
});
}
}

View file

@ -1,11 +1,10 @@
import { getRepository, In, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { CaseTypes } from "./CaseTypes";
import { connection } from "./db";
import { Case } from "./entities/Case";
import { CaseNote } from "./entities/CaseNote";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, In, Repository } from "typeorm";
import { DBDateFormat, disableLinkPreviews } from "../utils";
import { CaseTypes } from "./CaseTypes";
import moment = require("moment-timezone");
import { connection } from "./db";
const CASE_SUMMARY_REASON_MAX_LENGTH = 300;

View file

@ -0,0 +1,35 @@
import { DeleteResult, getRepository, InsertResult, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { ContextMenuLink } from "./entities/ContextMenuLink";
export class GuildContextMenuLinks extends BaseGuildRepository {
private contextLinks: Repository<ContextMenuLink>;
constructor(guildId) {
super(guildId);
this.contextLinks = getRepository(ContextMenuLink);
}
async get(id: string): Promise<ContextMenuLink | undefined> {
return this.contextLinks.findOne({
where: {
guild_id: this.guildId,
context_id: id,
},
});
}
async create(contextId: string, contextAction: string): Promise<InsertResult> {
return this.contextLinks.insert({
guild_id: this.guildId,
context_id: contextId,
action_name: contextAction,
});
}
async deleteAll(): Promise<DeleteResult> {
return this.contextLinks.delete({
guild_id: this.guildId,
});
}
}

View file

@ -1,18 +1,13 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { FindConditions, getRepository, In, IsNull, LessThan, Not, Repository } from "typeorm";
import { Counter } from "./entities/Counter";
import { CounterValue } from "./entities/CounterValue";
import {
CounterTrigger,
isValidCounterComparisonOp,
TRIGGER_COMPARISON_OPS,
TriggerComparisonOp,
} from "./entities/CounterTrigger";
import { CounterTriggerState } from "./entities/CounterTriggerState";
import moment from "moment-timezone";
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils";
import { connection } from "./db";
import { FindConditions, getRepository, In, IsNull, Not, Repository } from "typeorm";
import { Queue } from "../Queue";
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { connection } from "./db";
import { Counter } from "./entities/Counter";
import { CounterTrigger, isValidCounterComparisonOp, TriggerComparisonOp } from "./entities/CounterTrigger";
import { CounterTriggerState } from "./entities/CounterTriggerState";
import { CounterValue } from "./entities/CounterValue";
const DELETE_UNUSED_COUNTERS_AFTER = 1 * DAYS;
const DELETE_UNUSED_COUNTER_TRIGGERS_AFTER = 1 * DAYS;

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { QueuedEventEmitter } from "../QueuedEventEmitter";
import { BaseGuildRepository } from "./BaseGuildRepository";
export class GuildEvents extends BaseGuildRepository {
private queuedEventEmitter: QueuedEventEmitter;

View file

@ -1,7 +1,7 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { MemberTimezone } from "./entities/MemberTimezone";
import { getRepository, Repository } from "typeorm/index";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { connection } from "./db";
import { MemberTimezone } from "./entities/MemberTimezone";
export class GuildMemberTimezones extends BaseGuildRepository {
protected memberTimezones: Repository<MemberTimezone>;

View file

@ -1,7 +1,7 @@
import moment from "moment-timezone";
import { Mute } from "./entities/Mute";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { Brackets, getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { Mute } from "./entities/Mute";
export class GuildMutes extends BaseGuildRepository {
private mutes: Repository<Mute>;

View file

@ -1,9 +1,9 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, In, Repository } from "typeorm";
import { NicknameHistoryEntry } from "./entities/NicknameHistoryEntry";
import { MINUTES, SECONDS } from "../utils";
import { isAPI } from "../globals";
import { MINUTES, SECONDS } from "../utils";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { cleanupNicknames } from "./cleanup/nicknames";
import { NicknameHistoryEntry } from "./entities/NicknameHistoryEntry";
if (!isAPI()) {
const CLEANUP_INTERVAL = 5 * MINUTES;

View file

@ -1,6 +1,6 @@
import { PersistedData } from "./entities/PersistedData";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { PersistedData } from "./entities/PersistedData";
export interface IPartialPersistData {
roles?: string[];

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { PingableRole } from "./entities/PingableRole";
export class GuildPingableRoles extends BaseGuildRepository {

View file

@ -1,6 +1,6 @@
import { ReactionRole } from "./entities/ReactionRole";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { ReactionRole } from "./entities/ReactionRole";
export class GuildReactionRoles extends BaseGuildRepository {
private reactionRoles: Repository<ReactionRole>;
@ -24,6 +24,9 @@ export class GuildReactionRoles extends BaseGuildRepository {
guild_id: this.guildId,
message_id: messageId,
},
order: {
order: "ASC",
},
});
}
@ -50,7 +53,14 @@ export class GuildReactionRoles extends BaseGuildRepository {
await this.reactionRoles.delete(criteria);
}
async add(channelId: string, messageId: string, emoji: string, roleId: string, exclusive?: boolean) {
async add(
channelId: string,
messageId: string,
emoji: string,
roleId: string,
exclusive?: boolean,
position?: number,
) {
await this.reactionRoles.insert({
guild_id: this.guildId,
channel_id: channelId,
@ -58,6 +68,7 @@ export class GuildReactionRoles extends BaseGuildRepository {
emoji,
role_id: roleId,
is_exclusive: Boolean(exclusive),
order: position,
});
}
}

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { Reminder } from "./entities/Reminder";
export class GuildReminders extends BaseGuildRepository {

View file

@ -1,12 +1,13 @@
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage";
import { QueuedEventEmitter } from "../QueuedEventEmitter";
import { GuildChannel, Message, PossiblyUncachedTextableChannel } from "eris";
import { GuildChannel, Message } from "discord.js";
import moment from "moment-timezone";
import { MINUTES, SECONDS } from "../utils";
import { getRepository, Repository } from "typeorm";
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
import { isAPI } from "../globals";
import { QueuedEventEmitter } from "../QueuedEventEmitter";
import { MINUTES, SECONDS } from "../utils";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { cleanupMessages } from "./cleanup/messages";
import { ISavedMessageData, SavedMessage } from "./entities/SavedMessage";
if (!isAPI()) {
const CLEANUP_INTERVAL = 5 * MINUTES;
@ -34,19 +35,19 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.toBePermanent = new Set();
}
public msgToSavedMessageData(msg: Message<PossiblyUncachedTextableChannel>): ISavedMessageData {
public msgToSavedMessageData(msg: Message): ISavedMessageData {
const data: ISavedMessageData = {
author: {
username: msg.author.username,
discriminator: msg.author.discriminator,
},
content: msg.content,
timestamp: msg.timestamp,
timestamp: msg.createdTimestamp,
};
if (msg.attachments.length) data.attachments = msg.attachments;
if (msg.attachments.size) data.attachments = [...msg.attachments.values()];
if (msg.embeds.length) data.embeds = msg.embeds;
if (msg.stickers?.length) data.stickers = msg.stickers;
if (msg.stickers?.size) data.stickers = [...msg.stickers.values()];
return data;
}
@ -139,12 +140,12 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.events.emit(`create:${data.id}`, [inserted]);
}
async createFromMsg(msg: Message<PossiblyUncachedTextableChannel>, overrides = {}) {
async createFromMsg(msg: Message, overrides = {}) {
const existingSavedMsg = await this.find(msg.id);
if (existingSavedMsg) return;
const savedMessageData = this.msgToSavedMessageData(msg);
const postedAt = moment.utc(msg.timestamp, "x").format("YYYY-MM-DD HH:mm:ss");
const postedAt = moment.utc(msg.createdTimestamp, "x").format("YYYY-MM-DD HH:mm:ss");
const data = {
id: msg.id,
@ -159,6 +160,12 @@ export class GuildSavedMessages extends BaseGuildRepository {
return this.create({ ...data, ...overrides });
}
async createFromMessages(messages: Message[], overrides = {}) {
for (const msg of messages) {
await this.createFromMsg(msg, overrides);
}
}
async markAsDeleted(id) {
await this.messages
.createQueryBuilder("messages")
@ -211,10 +218,12 @@ export class GuildSavedMessages extends BaseGuildRepository {
const newMessage = { ...oldMessage, data: newData };
// @ts-ignore
await this.messages.update(
// FIXME?
{ id },
{
data: newData,
data: newData as QueryDeepPartialEntity<ISavedMessageData>,
},
);
@ -222,7 +231,7 @@ export class GuildSavedMessages extends BaseGuildRepository {
this.events.emit(`update:${id}`, [newMessage, oldMessage]);
}
async saveEditFromMsg(msg: Message<PossiblyUncachedTextableChannel>) {
async saveEditFromMsg(msg: Message) {
const newData = this.msgToSavedMessageData(msg);
return this.saveEdit(msg.id, newData);
}

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { ScheduledPost } from "./entities/ScheduledPost";
export class GuildScheduledPosts extends BaseGuildRepository {

View file

@ -1,8 +1,8 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import moment from "moment-timezone";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { SlowmodeChannel } from "./entities/SlowmodeChannel";
import { SlowmodeUser } from "./entities/SlowmodeUser";
import moment from "moment-timezone";
export class GuildSlowmodes extends BaseGuildRepository {
private slowmodeChannels: Repository<SlowmodeChannel>;

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { StarboardMessage } from "./entities/StarboardMessage";
export class GuildStarboardMessages extends BaseGuildRepository {

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { StarboardReaction } from "./entities/StarboardReaction";
export class GuildStarboardReactions extends BaseGuildRepository {

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { StatValue } from "./entities/StatValue";
export class GuildStats extends BaseGuildRepository {

View file

@ -1,6 +1,6 @@
import { Tag } from "./entities/Tag";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { Tag } from "./entities/Tag";
import { TagResponse } from "./entities/TagResponse";
export class GuildTags extends BaseGuildRepository {

View file

@ -1,7 +1,6 @@
import moment from "moment-timezone";
import { Mute } from "./entities/Mute";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { Brackets, getRepository, Repository } from "typeorm";
import { Tempban } from "./entities/Tempban";
export class GuildTempbans extends BaseGuildRepository {

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { VCAlert } from "./entities/VCAlert";
export class GuildVCAlerts extends BaseGuildRepository {

View file

@ -18,9 +18,15 @@ export enum LogType {
CHANNEL_CREATE,
CHANNEL_DELETE,
CHANNEL_UPDATE,
THREAD_CREATE,
THREAD_DELETE,
THREAD_UPDATE,
ROLE_CREATE,
ROLE_DELETE,
ROLE_UPDATE,
MESSAGE_EDIT,
MESSAGE_DELETE,
@ -31,6 +37,18 @@ export enum LogType {
VOICE_CHANNEL_LEAVE,
VOICE_CHANNEL_MOVE,
STAGE_INSTANCE_CREATE,
STAGE_INSTANCE_DELETE,
STAGE_INSTANCE_UPDATE,
EMOJI_CREATE,
EMOJI_DELETE,
EMOJI_UPDATE,
STICKER_CREATE,
STICKER_DELETE,
STICKER_UPDATE,
COMMAND,
MESSAGE_SPAM_DETECTED,

View file

@ -1,5 +1,5 @@
import { BaseRepository } from "./BaseRepository";
import { getRepository, Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository";
import { Supporter } from "./entities/Supporter";
export class Supporters extends BaseRepository {

View file

@ -1,9 +1,9 @@
import { getRepository, In, Repository } from "typeorm";
import { UsernameHistoryEntry } from "./entities/UsernameHistoryEntry";
import { isAPI } from "../globals";
import { MINUTES, SECONDS } from "../utils";
import { BaseRepository } from "./BaseRepository";
import { isAPI } from "../globals";
import { cleanupUsernames } from "./cleanup/usernames";
import { UsernameHistoryEntry } from "./entities/UsernameHistoryEntry";
if (!isAPI()) {
const CLEANUP_INTERVAL = 5 * MINUTES;

View file

@ -1,8 +1,8 @@
import { connection } from "../db";
import { getRepository, In } from "typeorm";
import { Config } from "../entities/Config";
import moment from "moment-timezone";
import { getRepository, In } from "typeorm";
import { DBDateFormat } from "../../utils";
import { connection } from "../db";
import { Config } from "../entities/Config";
const CLEAN_PER_LOOP = 50;

View file

@ -1,8 +1,8 @@
import { DAYS, DBDateFormat, MINUTES } from "../../utils";
import { getRepository, In } from "typeorm";
import { SavedMessage } from "../entities/SavedMessage";
import moment from "moment-timezone";
import { getRepository, In } from "typeorm";
import { DAYS, DBDateFormat, MINUTES } from "../../utils";
import { connection } from "../db";
import { SavedMessage } from "../entities/SavedMessage";
/**
* How long message edits, deletions, etc. will include the original message content.

View file

@ -1,8 +1,8 @@
import { getRepository, In } from "typeorm";
import moment from "moment-timezone";
import { NicknameHistoryEntry } from "../entities/NicknameHistoryEntry";
import { getRepository, In } from "typeorm";
import { DAYS, DBDateFormat } from "../../utils";
import { connection } from "../db";
import { NicknameHistoryEntry } from "../entities/NicknameHistoryEntry";
export const NICKNAME_RETENTION_PERIOD = 30 * DAYS;
const CLEAN_PER_LOOP = 500;

View file

@ -1,8 +1,8 @@
import { getRepository, In } from "typeorm";
import moment from "moment-timezone";
import { UsernameHistoryEntry } from "../entities/UsernameHistoryEntry";
import { getRepository, In } from "typeorm";
import { DAYS, DBDateFormat } from "../../utils";
import { connection } from "../db";
import { UsernameHistoryEntry } from "../entities/UsernameHistoryEntry";
export const USERNAME_RETENTION_PERIOD = 30 * DAYS;
const CLEAN_PER_LOOP = 500;

View file

@ -1,5 +1,5 @@
import { SimpleError } from "../SimpleError";
import { Connection, createConnection } from "typeorm";
import { SimpleError } from "../SimpleError";
let connectionPromise: Promise<Connection>;

View file

@ -1,5 +1,5 @@
import { decrypt, encrypt } from "../utils/crypt";
import { ValueTransformer } from "typeorm";
import { decrypt, encrypt } from "../utils/crypt";
interface EncryptedJsonTransformer<T> extends ValueTransformer {
from(dbValue: any): T;

View file

@ -1,5 +1,5 @@
import { decrypt, encrypt } from "../utils/crypt";
import { ValueTransformer } from "typeorm";
import { decrypt, encrypt } from "../utils/crypt";
interface EncryptedTextTransformer extends ValueTransformer {
from(dbValue: any): string;

View file

@ -0,0 +1,24 @@
import { Column, Entity, PrimaryColumn } from "typeorm";
@Entity("button_roles")
export class ButtonRole {
@Column()
@PrimaryColumn()
guild_id: string;
@Column()
@PrimaryColumn()
channel_id: string;
@Column()
@PrimaryColumn()
message_id: string;
@Column()
@PrimaryColumn()
button_id: string;
@Column() button_group: string;
@Column() button_name: string;
}

View file

@ -0,0 +1,10 @@
import { Column, Entity, PrimaryColumn } from "typeorm";
@Entity("context_menus")
export class ContextMenuLink {
@Column() guild_id: string;
@Column() @PrimaryColumn() context_id: string;
@Column() action_name: string;
}

View file

@ -17,7 +17,7 @@ export function getReverseCounterComparisonOp(op: TriggerComparisonOp): TriggerC
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

View file

@ -21,4 +21,6 @@ export class ReactionRole {
@Column() role_id: string;
@Column() is_exclusive: boolean;
@Column() order: number;
}

View file

@ -1,9 +1,9 @@
import { MessageAttachment, Sticker } from "discord.js";
import { Column, Entity, PrimaryColumn } from "typeorm";
import { createEncryptedJsonTransformer } from "../encryptedJsonTransformer";
import { Attachment, Sticker } from "eris";
export interface ISavedMessageData {
attachments?: Attachment[];
attachments?: MessageAttachment[];
author: {
username: string;
discriminator: string;

View file

@ -1,5 +1,5 @@
import { MessageAttachment } from "discord.js";
import { Column, Entity, PrimaryColumn } from "typeorm";
import { Attachment } from "eris";
import { StrictMessageContent } from "../../utils";
@Entity("scheduled_posts")
@ -18,7 +18,7 @@ export class ScheduledPost {
@Column("simple-json") content: StrictMessageContent;
@Column("simple-json") attachments: Attachment[];
@Column("simple-json") attachments: MessageAttachment[];
@Column({ type: String, nullable: true }) post_at: string | null;

View file

@ -1,5 +1,5 @@
import { getRepository, Repository } from "typeorm";
import { SavedMessage } from "./entities/SavedMessage";
import { Repository, getRepository } from "typeorm";
let repository: Repository<SavedMessage>;

View file

@ -1,32 +1,24 @@
import "./loadEnv";
import path from "path";
import { Client, Intents, TextChannel } from "discord.js";
import yaml from "js-yaml";
import fs from "fs";
import { Knub, PluginError } from "knub";
import { SimpleError } from "./SimpleError";
import { Configs } from "./data/Configs";
import { PluginLoadError } from "knub/dist/plugins/PluginLoadError";
// Always use UTC internally
// This is also enforced for the database in data/db.ts
import moment from "moment-timezone";
import { Client, DiscordHTTPError, TextChannel } from "eris";
import { connect } from "./data/db";
import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins";
import { errorMessage, isDiscordHTTPError, isDiscordRESTError, MINUTES, successMessage } from "./utils";
import { startUptimeCounter } from "./uptime";
import { AllowedGuilds } from "./data/AllowedGuilds";
import { ZeppelinGlobalConfig, ZeppelinGuildConfig } from "./types";
import { RecoverablePluginError } from "./RecoverablePluginError";
import { Configs } from "./data/Configs";
import { connect } from "./data/db";
import { GuildLogs } from "./data/GuildLogs";
import { LogType } from "./data/LogType";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { DiscordJSError } from "./DiscordJSError";
import "./loadEnv";
import { logger } from "./logger";
import { PluginLoadError } from "knub/dist/plugins/PluginLoadError";
import { ErisError } from "./ErisError";
const fsp = fs.promises;
import { baseGuildPlugins, globalPlugins, guildPlugins } from "./plugins/availablePlugins";
import { RecoverablePluginError } from "./RecoverablePluginError";
import { SimpleError } from "./SimpleError";
import { ZeppelinGlobalConfig, ZeppelinGuildConfig } from "./types";
import { startUptimeCounter } from "./uptime";
import { errorMessage, isDiscordAPIError, isDiscordHTTPError, successMessage } from "./utils";
if (!process.env.KEY) {
// tslint:disable-next-line:no-console
@ -86,7 +78,7 @@ function errorHandler(err) {
return;
}
if (err instanceof ErisError) {
if (err instanceof DiscordJSError) {
if (err.code && SAFE_TO_IGNORE_ERIS_ERROR_CODES.includes(err.code)) {
return;
}
@ -96,7 +88,7 @@ function errorHandler(err) {
}
}
if (err instanceof DiscordHTTPError && err.code >= 500) {
if (isDiscordHTTPError(err) && err.code >= 500) {
// Don't need stack traces on HTTP 500 errors
// These also shouldn't count towards RECENT_DISCORD_ERROR_EXIT_THRESHOLD because they don't indicate an error in our code
console.error(err.message);
@ -118,7 +110,7 @@ function errorHandler(err) {
console.error(`Exiting after ${RECENT_PLUGIN_ERROR_EXIT_THRESHOLD} plugin errors`);
process.exit(1);
}
} else if (isDiscordRESTError(err) || isDiscordHTTPError(err)) {
} else if (isDiscordAPIError(err) || isDiscordHTTPError(err)) {
// Discord API errors, usually safe to just log instead of crash
// We still bail if we get a ton of them in a short amount of time
if (++recentDiscordErrors >= RECENT_DISCORD_ERROR_EXIT_THRESHOLD) {
@ -151,48 +143,42 @@ moment.tz.setDefault("UTC");
logger.info("Connecting to database");
connect().then(async () => {
const client = new Client(`Bot ${process.env.TOKEN}`, {
getAllUsers: false,
restMode: true,
compress: false,
guildCreateTimeout: 0,
rest: {
ratelimiterOffset: 150,
},
const client = new Client({
partials: ["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"],
restTimeOffset: 150,
restGlobalRateLimit: 50,
// Disable mentions by default
allowedMentions: {
everyone: false,
users: false,
roles: false,
parse: [],
users: [],
roles: [],
repliedUser: false,
},
intents: [
// Privileged
"guildMembers",
// "guildPresences",
"guildMessageTyping",
Intents.FLAGS.GUILD_MEMBERS,
// Intents.FLAGS.GUILD_PRESENCES,
Intents.FLAGS.GUILD_MESSAGE_TYPING,
// Regular
"directMessages",
"guildBans",
"guildEmojis",
"guildInvites",
"guildMessageReactions",
"guildMessages",
"guilds",
"guildVoiceStates",
Intents.FLAGS.DIRECT_MESSAGES,
Intents.FLAGS.GUILD_BANS,
Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS,
Intents.FLAGS.GUILD_INVITES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_VOICE_STATES,
],
});
client.setMaxListeners(200);
client.on("debug", message => {
if (message.includes(" 429 ")) {
logger.info(`[429] ${message}`);
}
client.on("rateLimit", rateLimitData => {
logger.info(`[429] ${JSON.stringify(rateLimitData)}`);
});
client.on("error", (err, shardId) => {
errorHandler(new ErisError(err.message, (err as any).code, shardId));
client.on("error", err => {
errorHandler(new DiscordJSError(err.message, (err as any).code, 0));
});
const allowedGuilds = new AllowedGuilds();
@ -257,13 +243,13 @@ connect().then(async () => {
sendSuccessMessageFn(channel, body) {
const guildId = channel instanceof TextChannel ? channel.guild.id : undefined;
const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.success_emoji : undefined;
channel.createMessage(successMessage(body, emoji));
channel.send(successMessage(body, emoji));
},
sendErrorMessageFn(channel, body) {
const guildId = channel instanceof TextChannel ? channel.guild.id : undefined;
const emoji = guildId ? bot.getLoadedGuild(guildId)!.config.error_emoji : undefined;
channel.createMessage(errorMessage(body, emoji));
channel.send(errorMessage(body, emoji));
},
},
});
@ -272,6 +258,8 @@ connect().then(async () => {
startUptimeCounter();
});
logger.info("Starting the bot");
bot.run();
bot.initialize();
logger.info("Bot Initialized");
logger.info("Logging in...");
await client.login(process.env.TOKEN);
});

View file

@ -1,8 +1,8 @@
// tslint:disable:no-console
import { connect } from "./data/db";
import { Configs } from "./data/Configs";
import path from "path";
import * as _fs from "fs";
import path from "path";
import { Configs } from "./data/Configs";
import { connect } from "./data/db";
const fs = _fs.promises;

View file

@ -1,4 +1,4 @@
import { MigrationInterface, QueryRunner, TableIndex, TableUnique } from "typeorm";
import { MigrationInterface, QueryRunner, TableIndex } from "typeorm";
export class FixStarboardReactionsIndices1608692857722 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {

View file

@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class OrderReactionRoles1622939525343 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"reaction_roles",
new TableColumn({
name: "order",
type: "int",
isNullable: true,
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("reaction_roles", "order");
}
}

View file

@ -0,0 +1,49 @@
import { MigrationInterface, QueryRunner, Table } from "typeorm";
export class CreateButtonRolesTable1623018101018 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "button_roles",
columns: [
{
name: "guild_id",
type: "bigint",
isPrimary: true,
},
{
name: "channel_id",
type: "bigint",
isPrimary: true,
},
{
name: "message_id",
type: "bigint",
isPrimary: true,
},
{
name: "button_id",
type: "varchar",
length: "100",
isPrimary: true,
isUnique: true,
},
{
name: "button_group",
type: "varchar",
length: "100",
},
{
name: "button_name",
type: "varchar",
length: "100",
},
],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("button_roles");
}
}

View file

@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner, Table } from "typeorm";
export class CreateContextMenuTable1628809879962 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "context_menus",
columns: [
{
name: "guild_id",
type: "bigint",
},
{
name: "context_id",
type: "bigint",
isPrimary: true,
isUnique: true,
},
{
name: "action_name",
type: "varchar",
length: "100",
},
],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("context_menus");
}
}

View file

@ -2,23 +2,28 @@
* @file Utility functions that are plugin-instance-specific (i.e. use PluginData)
*/
import { AdvancedMessageContent, AllowedMentions, GuildTextableChannel, Member, Message, TextableChannel } from "eris";
import { CommandContext, configUtils, ConfigValidationError, GuildPluginData, helpers, PluginOptions } from "knub";
import { decodeAndValidateStrict, StrictValidationError, validate } from "./validatorUtils";
import { deepKeyIntersect, errorMessage, successMessage, tDeepPartial, tNullable } from "./utils";
import { TZeppelinKnub } from "./types";
import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; // TODO: Export from Knub index
import { GuildMember, Message, MessageMentionOptions, MessageOptions, TextChannel } from "discord.js";
import * as t from "io-ts";
import { CommandContext, configUtils, ConfigValidationError, GuildPluginData, helpers, PluginOptions } from "knub";
import { PluginOverrideCriteria } from "knub/dist/config/configTypes";
import { Tail } from "./utils/typeUtils";
import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; // TODO: Export from Knub index
import { AnyPluginData } from "knub/dist/plugins/PluginData";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { logger } from "./logger";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { TZeppelinKnub } from "./types";
import { deepKeyIntersect, errorMessage, successMessage, tDeepPartial, tNullable } from "./utils";
import { Tail } from "./utils/typeUtils";
import { decodeAndValidateStrict, StrictValidationError, validate } from "./validatorUtils";
const { getMemberLevel } = helpers;
export function canActOn(pluginData: GuildPluginData<any>, member1: Member, member2: Member, allowSameLevel = false) {
if (member2.id === pluginData.client.user.id) {
export function canActOn(
pluginData: GuildPluginData<any>,
member1: GuildMember,
member2: GuildMember,
allowSameLevel = false,
) {
if (member2.id === pluginData.client.user!.id) {
return false;
}
@ -178,45 +183,43 @@ export function getPluginConfigPreprocessor(
};
}
export function sendSuccessMessage(
export async function sendSuccessMessage(
pluginData: AnyPluginData<any>,
channel: TextableChannel,
channel: TextChannel,
body: string,
allowedMentions?: AllowedMentions,
allowedMentions?: MessageMentionOptions,
): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.success_emoji || undefined;
const formattedBody = successMessage(body, emoji);
const content: AdvancedMessageContent = allowedMentions
const content: MessageOptions = allowedMentions
? { content: formattedBody, allowedMentions }
: { content: formattedBody };
return channel
.createMessage(content) // Force line break
.send({ ...content }) // Force line break
.catch(err => {
const channelInfo = (channel as GuildTextableChannel).guild
? `${channel.id} (${(channel as GuildTextableChannel).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}`);
return undefined;
});
}
export function sendErrorMessage(
export async function sendErrorMessage(
pluginData: AnyPluginData<any>,
channel: TextableChannel,
channel: TextChannel,
body: string,
allowedMentions?: AllowedMentions,
allowedMentions?: MessageMentionOptions,
): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.error_emoji || undefined;
const formattedBody = errorMessage(body, emoji);
const content: AdvancedMessageContent = allowedMentions
const content: MessageOptions = allowedMentions
? { content: formattedBody, allowedMentions }
: { content: formattedBody };
return channel
.createMessage(content) // Force line break
.send({ ...content }) // Force line break
.catch(err => {
const channelInfo = (channel as GuildTextableChannel).guild
? `${channel.id} (${(channel as GuildTextableChannel).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}`);
return undefined;
});

View file

@ -1,13 +1,13 @@
import { PluginOptions } from "knub";
import { AutoDeletePluginType, ConfigSchema } from "./types";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { LogsPlugin } from "../Logs/LogsPlugin";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { AutoDeletePluginType, ConfigSchema } from "./types";
import { onMessageCreate } from "./util/onMessageCreate";
import { onMessageDelete } from "./util/onMessageDelete";
import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
import { LogsPlugin } from "../Logs/LogsPlugin";
const defaultOptions: PluginOptions<AutoDeletePluginType> = {
config: {

View file

@ -1,9 +1,9 @@
import * as t from "io-ts";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { tDelayString, MINUTES } from "../../utils";
import { BasePluginType } from "knub";
import { SavedMessage } from "../../data/entities/SavedMessage";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { SavedMessage } from "../../data/entities/SavedMessage";
import { MINUTES, tDelayString } from "../../utils";
import Timeout = NodeJS.Timeout;
export const MAX_DELAY = 5 * MINUTES;

View file

@ -1,8 +1,8 @@
import { GuildPluginData } from "knub";
import { AutoDeletePluginType } from "../types";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { scheduleNextDeletion } from "./scheduleNextDeletion";
import { sorter } from "../../../utils";
import { AutoDeletePluginType } from "../types";
import { scheduleNextDeletion } from "./scheduleNextDeletion";
export function addMessageToDeletionQueue(
pluginData: GuildPluginData<AutoDeletePluginType>,

View file

@ -1,14 +1,15 @@
import { Permissions, Snowflake, TextChannel } from "discord.js";
import { GuildPluginData } from "knub";
import { AutoDeletePluginType } from "../types";
import moment from "moment-timezone";
import { channelToConfigAccessibleChannel, userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects";
import { LogType } from "../../../data/LogType";
import { resolveUser, stripObjectToScalars, verboseChannelMention } from "../../../utils";
import { logger } from "../../../logger";
import { scheduleNextDeletion } from "./scheduleNextDeletion";
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
import { resolveUser, verboseChannelMention } from "../../../utils";
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
import { Constants } from "eris";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
import { AutoDeletePluginType } from "../types";
import { scheduleNextDeletion } from "./scheduleNextDeletion";
export async function deleteNextItem(pluginData: GuildPluginData<AutoDeletePluginType>) {
const [itemToDelete] = pluginData.state.deletionQueue.splice(0, 1);
@ -16,16 +17,16 @@ export async function deleteNextItem(pluginData: GuildPluginData<AutoDeletePlugi
scheduleNextDeletion(pluginData);
const channel = pluginData.guild.channels.get(itemToDelete.message.channel_id);
const channel = pluginData.guild.channels.cache.get(itemToDelete.message.channel_id as Snowflake) as TextChannel;
if (!channel) {
// Channel was deleted, ignore
return;
}
const logs = pluginData.getPlugin(LogsPlugin);
const perms = channel.permissionsOf(pluginData.client.user.id);
const perms = channel.permissionsFor(pluginData.client.user!.id);
if (!hasDiscordPermissions(perms, Constants.Permissions.readMessages | Constants.Permissions.readMessageHistory)) {
if (!hasDiscordPermissions(perms, Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.READ_MESSAGE_HISTORY)) {
logs.log(LogType.BOT_ALERT, {
body: `Missing permissions to read messages or message history in auto-delete channel ${verboseChannelMention(
channel,
@ -34,7 +35,7 @@ export async function deleteNextItem(pluginData: GuildPluginData<AutoDeletePlugi
return;
}
if (!hasDiscordPermissions(perms, Constants.Permissions.manageMessages)) {
if (!hasDiscordPermissions(perms, Permissions.FLAGS.MANAGE_MESSAGES)) {
logs.log(LogType.BOT_ALERT, {
body: `Missing permissions to delete messages in auto-delete channel ${verboseChannelMention(channel)}`,
});
@ -44,7 +45,7 @@ export async function deleteNextItem(pluginData: GuildPluginData<AutoDeletePlugi
const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
pluginData.state.guildLogs.ignoreLog(LogType.MESSAGE_DELETE, itemToDelete.message.id);
pluginData.client.deleteMessage(itemToDelete.message.channel_id, itemToDelete.message.id).catch(err => {
(channel as TextChannel).messages.delete(itemToDelete.message.id as Snowflake).catch(err => {
if (err.code === 10008) {
// "Unknown Message", probably already deleted by automod or another bot, ignore
return;
@ -60,8 +61,8 @@ export async function deleteNextItem(pluginData: GuildPluginData<AutoDeletePlugi
pluginData.state.guildLogs.log(LogType.MESSAGE_DELETE_AUTO, {
message: itemToDelete.message,
user: stripObjectToScalars(user),
channel: stripObjectToScalars(channel),
user: userToConfigAccessibleUser(user),
channel: channelToConfigAccessibleChannel(channel),
messageDate,
});
}

View file

@ -1,8 +1,8 @@
import { AutoDeletePluginType, MAX_DELAY } from "../types";
import { GuildPluginData } from "knub";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { convertDelayStringToMS, resolveMember } from "../../../utils";
import { LogType } from "../../../data/LogType";
import { convertDelayStringToMS, resolveMember } from "../../../utils";
import { AutoDeletePluginType, MAX_DELAY } from "../types";
import { addMessageToDeletionQueue } from "./addMessageToDeletionQueue";
export async function onMessageCreate(pluginData: GuildPluginData<AutoDeletePluginType>, msg: SavedMessage) {

View file

@ -1,6 +1,6 @@
import { GuildPluginData } from "knub";
import { AutoDeletePluginType } from "../types";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { AutoDeletePluginType } from "../types";
import { scheduleNextDeletion } from "./scheduleNextDeletion";
export function onMessageDelete(pluginData: GuildPluginData<AutoDeletePluginType>, msg: SavedMessage) {

View file

@ -1,6 +1,6 @@
import { AutoDeletePluginType } from "../types";
import { GuildPluginData } from "knub";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { AutoDeletePluginType } from "../types";
import { onMessageDelete } from "./onMessageDelete";
export function onMessageDeleteBulk(pluginData: GuildPluginData<AutoDeletePluginType>, messages: SavedMessage[]) {

View file

@ -1,13 +1,13 @@
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { AutoReactionsPluginType, ConfigSchema } from "./types";
import { PluginOptions } from "knub";
import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd";
import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildAutoReactions } from "../../data/GuildAutoReactions";
import { AddReactionsEvt } from "./events/AddReactionsEvt";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { trimPluginDescription } from "../../utils";
import { LogsPlugin } from "../Logs/LogsPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd";
import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd";
import { AddReactionsEvt } from "./events/AddReactionsEvt";
import { AutoReactionsPluginType, ConfigSchema } from "./types";
const defaultOptions: PluginOptions<AutoReactionsPluginType> = {
config: {

View file

@ -1,6 +1,6 @@
import { autoReactionsCmd } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { autoReactionsCmd } from "../types";
export const DisableAutoReactionsCmd = autoReactionsCmd({
trigger: "auto_reactions disable",

View file

@ -1,13 +1,13 @@
import { autoReactionsCmd } from "../types";
import { GuildChannel, Permissions } from "discord.js";
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils";
import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions";
import { Constants, GuildChannel } from "eris";
import { readChannelPermissions } from "../../../utils/readChannelPermissions";
import { missingPermissionError } from "../../../utils/missingPermissionError";
import { readChannelPermissions } from "../../../utils/readChannelPermissions";
import { autoReactionsCmd } from "../types";
const requiredPermissions = readChannelPermissions | Constants.Permissions.addReactions;
const requiredPermissions = readChannelPermissions | Permissions.FLAGS.ADD_REACTIONS;
export const NewAutoReactionsCmd = autoReactionsCmd({
trigger: "auto_reactions",
@ -22,7 +22,7 @@ export const NewAutoReactionsCmd = autoReactionsCmd({
async run({ message: msg, args, pluginData }) {
const finalReactions: string[] = [];
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!;
const missingPermissions = getMissingChannelPermissions(me, args.channel as GuildChannel, requiredPermissions);
if (missingPermissions) {
sendErrorMessage(

View file

@ -1,14 +1,13 @@
import { autoReactionsEvt } from "../types";
import { isDiscordRESTError } from "../../../utils";
import { GuildChannel, Permissions } from "discord.js";
import { LogType } from "../../../data/LogType";
import { logger } from "../../../logger";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { Constants, GuildChannel } from "eris";
import { isDiscordAPIError } from "../../../utils";
import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions";
import { readChannelPermissions } from "../../../utils/readChannelPermissions";
import { missingPermissionError } from "../../../utils/missingPermissionError";
import { readChannelPermissions } from "../../../utils/readChannelPermissions";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { autoReactionsEvt } from "../types";
const p = Constants.Permissions;
const p = Permissions.FLAGS;
export const AddReactionsEvt = autoReactionsEvt({
event: "messageCreate",
@ -19,11 +18,11 @@ export const AddReactionsEvt = autoReactionsEvt({
const autoReaction = await pluginData.state.autoReactions.getForChannel(message.channel.id);
if (!autoReaction) return;
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!;
const missingPermissions = getMissingChannelPermissions(
me,
message.channel as GuildChannel,
readChannelPermissions | p.addReactions,
readChannelPermissions | p.ADD_REACTIONS,
);
if (missingPermissions) {
const logs = pluginData.getPlugin(LogsPlugin);
@ -35,9 +34,9 @@ export const AddReactionsEvt = autoReactionsEvt({
for (const reaction of autoReaction.reactions) {
try {
await message.addReaction(reaction);
await message.react(reaction);
} catch (e) {
if (isDiscordRESTError(e)) {
if (isDiscordAPIError(e)) {
const logs = pluginData.getPlugin(LogsPlugin);
if (e.code === 10008) {
logs.log(LogType.BOT_ALERT, {

View file

@ -1,8 +1,8 @@
import * as t from "io-ts";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildAutoReactions } from "../../data/GuildAutoReactions";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildAutoReactions } from "../../data/GuildAutoReactions";
export const ConfigSchema = t.type({
can_manage: t.boolean,

View file

@ -1,38 +1,35 @@
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { AutomodPluginType, ConfigSchema } from "./types";
import { RunAutomodOnJoinEvt } from "./events/RunAutomodOnJoinEvt";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { runAutomodOnMessage } from "./events/runAutomodOnMessage";
import { Queue } from "../../Queue";
import { configUtils, CooldownManager } from "knub";
import { availableTriggers } from "./triggers/availableTriggers";
import { StrictValidationError } from "../../validatorUtils";
import { ConfigPreprocessorFn } from "knub/dist/config/configTypes";
import { availableActions } from "./actions/availableActions";
import { clearOldRecentActions } from "./functions/clearOldRecentActions";
import { disableCodeBlocks, MINUTES, SECONDS } from "../../utils";
import { clearOldRecentSpam } from "./functions/clearOldRecentSpam";
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
import { GuildArchives } from "../../data/GuildArchives";
import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChanges";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { Queue } from "../../Queue";
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
import { MINUTES, SECONDS } from "../../utils";
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap";
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap";
import { StrictValidationError } from "../../validatorUtils";
import { CountersPlugin } from "../Counters/CountersPlugin";
import { LogsPlugin } from "../Logs/LogsPlugin";
import { ModActionsPlugin } from "../ModActions/ModActionsPlugin";
import { MutesPlugin } from "../Mutes/MutesPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { availableActions } from "./actions/availableActions";
import { AntiraidClearCmd } from "./commands/AntiraidClearCmd";
import { SetAntiraidCmd } from "./commands/SetAntiraidCmd";
import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd";
import { pluginInfo } from "./info";
import { RegExpRunner } from "../../RegExpRunner";
import { LogType } from "../../data/LogType";
import { logger } from "../../logger";
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate";
import { CountersPlugin } from "../Counters/CountersPlugin";
import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger";
import { RunAutomodOnJoinEvt, RunAutomodOnLeaveEvt } from "./events/RunAutomodOnJoinLeaveEvt";
import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate";
import { runAutomodOnMessage } from "./events/runAutomodOnMessage";
import { runAutomodOnModAction } from "./events/runAutomodOnModAction";
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap";
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap";
import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChanges";
import { clearOldRecentActions } from "./functions/clearOldRecentActions";
import { clearOldRecentSpam } from "./functions/clearOldRecentSpam";
import { pluginInfo } from "./info";
import { availableTriggers } from "./triggers/availableTriggers";
import { AutomodPluginType, ConfigSchema } from "./types";
const defaultOptions = {
config: {
@ -179,6 +176,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
events: [
RunAutomodOnJoinEvt,
RunAutomodOnMemberUpdate,
RunAutomodOnLeaveEvt,
// Messages use message events from SavedMessages, see onLoad below
],

View file

@ -1,17 +1,16 @@
import { Permissions, Snowflake } from "discord.js";
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { nonNullish, unique } from "../../../utils";
import { Constants } from "eris";
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
import { canAssignRole } from "../../../utils/canAssignRole";
import { missingPermissionError } from "../../../utils/missingPermissionError";
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
import { memberRolesLock } from "../../../utils/lockNameHelpers";
import { missingPermissionError } from "../../../utils/missingPermissionError";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
import { automodAction } from "../helpers";
const p = Constants.Permissions;
const p = Permissions.FLAGS;
export const AddRolesAction = automodAction({
configType: t.array(t.string),
@ -19,9 +18,9 @@ export const AddRolesAction = automodAction({
async apply({ pluginData, contexts, actionConfig, ruleName }) {
const members = unique(contexts.map(c => c.member).filter(nonNullish));
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!;
const missingPermissions = getMissingPermissions(me.permission, p.manageRoles);
const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES);
if (missingPermissions) {
const logs = pluginData.getPlugin(LogsPlugin);
logs.log(LogType.BOT_ALERT, {
@ -42,7 +41,7 @@ export const AddRolesAction = automodAction({
if (rolesWeCannotAssign.length) {
const roleNamesWeCannotAssign = rolesWeCannotAssign.map(
roleId => pluginData.guild.roles.get(roleId)?.name || roleId,
roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId,
);
const logs = pluginData.getPlugin(LogsPlugin);
logs.log(LogType.BOT_ALERT, {
@ -54,13 +53,13 @@ export const AddRolesAction = automodAction({
await Promise.all(
members.map(async member => {
const memberRoles = new Set(member.roles);
const memberRoles = new Set(member.roles.cache.keys());
for (const roleId of rolesToAssign) {
memberRoles.add(roleId);
memberRoles.add(roleId as Snowflake);
ignoreRoleChange(pluginData, member.id, roleId);
}
if (memberRoles.size === member.roles.length) {
if (memberRoles.size === member.roles.cache.size) {
// No role changes
return;
}
@ -71,7 +70,6 @@ export const AddRolesAction = automodAction({
await member.edit({
roles: rolesArr,
});
member.roles = rolesArr; // Make sure we know of the new roles internally as well
memberRoleLock.unlock();
}),

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { CountersPlugin } from "../../Counters/CountersPlugin";
import { LogType } from "../../../data/LogType";
import { CountersPlugin } from "../../Counters/CountersPlugin";
import { automodAction } from "../helpers";
export const AddToCounterAction = automodAction({
configType: t.type({

View file

@ -1,23 +1,18 @@
import { Snowflake, TextChannel } from "discord.js";
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
import { LogType } from "../../../data/LogType";
import { renderTemplate, TemplateParseError } from "../../../templateFormatter";
import {
asyncMap,
createChunkedMessage,
isDiscordRESTError,
messageLink,
resolveMember,
stripObjectToScalars,
tAllowedMentions,
tNormalizedNullOptional,
tNullable,
verboseChannelMention,
} from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { TextChannel } from "eris";
import { renderTemplate, TemplateParseError } from "../../../templateFormatter";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
export const AlertAction = automodAction({
configType: t.type({
@ -29,7 +24,7 @@ export const AlertAction = automodAction({
defaultConfig: {},
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
const channel = pluginData.guild.channels.get(actionConfig.channel);
const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake);
const logs = pluginData.getPlugin(LogsPlugin);
if (channel && channel instanceof TextChannel) {
@ -73,7 +68,11 @@ export const AlertAction = automodAction({
}
try {
await createChunkedMessage(channel, rendered, actionConfig.allowed_mentions);
await createChunkedMessage(
channel,
rendered,
erisAllowedMentionsToDjsMentionOptions(actionConfig.allowed_mentions),
);
} catch (err) {
if (err.code === 50001) {
logs.log(LogType.BOT_ALERT, {

View file

@ -1,20 +1,20 @@
import * as t from "io-ts";
import { CleanAction } from "./clean";
import { AutomodActionBlueprint } from "../helpers";
import { WarnAction } from "./warn";
import { MuteAction } from "./mute";
import { KickAction } from "./kick";
import { BanAction } from "./ban";
import { AlertAction } from "./alert";
import { ChangeNicknameAction } from "./changeNickname";
import { LogAction } from "./log";
import { AddRolesAction } from "./addRoles";
import { RemoveRolesAction } from "./removeRoles";
import { SetAntiraidLevelAction } from "./setAntiraidLevel";
import { ReplyAction } from "./reply";
import { AddToCounterAction } from "./addToCounter";
import { AlertAction } from "./alert";
import { BanAction } from "./ban";
import { ChangeNicknameAction } from "./changeNickname";
import { CleanAction } from "./clean";
import { KickAction } from "./kick";
import { LogAction } from "./log";
import { MuteAction } from "./mute";
import { RemoveRolesAction } from "./removeRoles";
import { ReplyAction } from "./reply";
import { SetAntiraidLevelAction } from "./setAntiraidLevel";
import { SetCounterAction } from "./setCounter";
import { SetSlowmodeAction } from "./setSlowmode";
import { WarnAction } from "./warn";
export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
clean: CleanAction,

View file

@ -1,18 +1,9 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import {
asyncMap,
convertDelayStringToMS,
nonNullish,
resolveMember,
tDelayString,
tNullable,
unique,
} from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils";
import { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers";
export const BanAction = automodAction({
configType: t.type({
@ -37,7 +28,7 @@ export const BanAction = automodAction({
const deleteMessageDays = actionConfig.deleteMessageDays || undefined;
const caseArgs: Partial<CaseArgs> = {
modId: pluginData.client.user.id,
modId: pluginData.client.user!.id,
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
automatic: true,
postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined,

View file

@ -1,8 +1,8 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { nonNullish, unique } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
export const ChangeNicknameAction = automodAction({
configType: t.union([

View file

@ -1,7 +1,8 @@
import { Snowflake, TextChannel } from "discord.js";
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { noop } from "../../../utils";
import { automodAction } from "../helpers";
export const CleanAction = automodAction({
configType: t.boolean,
@ -29,7 +30,8 @@ export const CleanAction = automodAction({
pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id);
}
await pluginData.client.deleteMessages(channelId, messageIds).catch(noop);
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel;
await channel.bulkDelete(messageIds as Snowflake[]).catch(noop);
}
},
});

View file

@ -1,10 +1,9 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers";
export const KickAction = automodAction({
configType: t.type({
@ -25,7 +24,7 @@ export const KickAction = automodAction({
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;
const caseArgs: Partial<CaseArgs> = {
modId: pluginData.client.user.id,
modId: pluginData.client.user!.id,
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
automatic: true,
postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined,

View file

@ -1,8 +1,8 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { LogType } from "../../../data/LogType";
import { stripObjectToScalars, unique } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers";
export const LogAction = automodAction({
configType: t.boolean,

View file

@ -1,21 +1,12 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import {
asyncMap,
convertDelayStringToMS,
nonNullish,
resolveMember,
tDelayString,
tNullable,
unique,
} from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils";
import { CaseArgs } from "../../Cases/types";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers";
export const MuteAction = automodAction({
configType: t.type({
@ -42,7 +33,7 @@ export const MuteAction = automodAction({
const rolesToRestore = actionConfig.restore_roles_on_mute;
const caseArgs: Partial<CaseArgs> = {
modId: pluginData.client.user.id,
modId: pluginData.client.user!.id,
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
automatic: true,
postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined,

View file

@ -1,18 +1,16 @@
import { Permissions, Snowflake } from "discord.js";
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { missingPermissionError } from "../../../utils/missingPermissionError";
import { nonNullish, unique } from "../../../utils";
import { canAssignRole } from "../../../utils/canAssignRole";
import { Constants } from "eris";
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
import { memberRolesLock } from "../../../utils/lockNameHelpers";
import { missingPermissionError } from "../../../utils/missingPermissionError";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
import { automodAction } from "../helpers";
const p = Constants.Permissions;
const p = Permissions.FLAGS;
export const RemoveRolesAction = automodAction({
configType: t.array(t.string),
@ -21,9 +19,9 @@ export const RemoveRolesAction = automodAction({
async apply({ pluginData, contexts, actionConfig, ruleName }) {
const members = unique(contexts.map(c => c.member).filter(nonNullish));
const me = pluginData.guild.members.get(pluginData.client.user.id)!;
const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!;
const missingPermissions = getMissingPermissions(me.permission, p.manageRoles);
const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES);
if (missingPermissions) {
const logs = pluginData.getPlugin(LogsPlugin);
logs.log(LogType.BOT_ALERT, {
@ -44,7 +42,7 @@ export const RemoveRolesAction = automodAction({
if (rolesWeCannotRemove.length) {
const roleNamesWeCannotRemove = rolesWeCannotRemove.map(
roleId => pluginData.guild.roles.get(roleId)?.name || roleId,
roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId,
);
const logs = pluginData.getPlugin(LogsPlugin);
logs.log(LogType.BOT_ALERT, {
@ -56,13 +54,13 @@ export const RemoveRolesAction = automodAction({
await Promise.all(
members.map(async member => {
const memberRoles = new Set(member.roles);
const memberRoles = new Set(member.roles.cache.keys());
for (const roleId of rolesToRemove) {
memberRoles.delete(roleId);
memberRoles.delete(roleId as Snowflake);
ignoreRoleChange(pluginData, member.id, roleId);
}
if (memberRoles.size === member.roles.length) {
if (memberRoles.size === member.roles.cache.size) {
// No role changes
return;
}
@ -73,7 +71,6 @@ export const RemoveRolesAction = automodAction({
await member.edit({
roles: rolesArr,
});
member.roles = rolesArr; // Make sure we know of the new roles internally as well
memberRoleLock.unlock();
}),

View file

@ -1,22 +1,21 @@
import { MessageOptions, Permissions, Snowflake, TextChannel, User } from "discord.js";
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { userToConfigAccessibleUser } from "../../../utils/configAccessibleObjects";
import { LogType } from "../../../data/LogType";
import { renderTemplate } from "../../../templateFormatter";
import {
convertDelayStringToMS,
noop,
renderRecursively,
StrictMessageContent,
stripObjectToScalars,
tDelayString,
tMessageContent,
tNullable,
unique,
verboseChannelMention,
} from "../../../utils";
import { AdvancedMessageContent, Constants, MessageContent, TextChannel, User } from "eris";
import { AutomodContext } from "../types";
import { renderTemplate } from "../../../templateFormatter";
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
import { LogType } from "../../../data/LogType";
import { automodAction } from "../helpers";
import { AutomodContext } from "../types";
export const ReplyAction = automodAction({
configType: t.union([
@ -32,7 +31,7 @@ export const ReplyAction = automodAction({
async apply({ pluginData, contexts, actionConfig, ruleName }) {
const contextsWithTextChannels = contexts
.filter(c => c.message?.channel_id)
.filter(c => pluginData.guild.channels.get(c.message!.channel_id) instanceof TextChannel);
.filter(c => pluginData.guild.channels.cache.get(c.message!.channel_id as Snowflake) instanceof TextChannel);
const contextsByChannelId = contextsWithTextChannels.reduce((map: Map<string, AutomodContext[]>, context) => {
if (!map.has(context.message!.channel_id)) {
@ -49,21 +48,21 @@ export const ReplyAction = automodAction({
const renderReplyText = async str =>
renderTemplate(str, {
user: stripObjectToScalars(user),
user: userToConfigAccessibleUser(user),
});
const formatted =
typeof actionConfig === "string"
? await renderReplyText(actionConfig)
: ((await renderRecursively(actionConfig.text, renderReplyText)) as AdvancedMessageContent);
: ((await renderRecursively(actionConfig.text, renderReplyText)) as MessageOptions);
if (formatted) {
const channel = pluginData.guild.channels.get(channelId) as TextChannel;
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake) as TextChannel;
// Check for basic Send Messages and View Channel permissions
if (
!hasDiscordPermissions(
channel.permissionsOf(pluginData.client.user.id),
Constants.Permissions.sendMessages | Constants.Permissions.readMessages,
channel.permissionsFor(pluginData.client.user!.id),
Permissions.FLAGS.SEND_MESSAGES | Permissions.FLAGS.VIEW_CHANNEL,
)
) {
pluginData.state.logs.log(LogType.BOT_ALERT, {
@ -75,7 +74,7 @@ export const ReplyAction = automodAction({
// If the message is an embed, check for embed permissions
if (
typeof formatted !== "string" &&
!hasDiscordPermissions(channel.permissionsOf(pluginData.client.user.id), Constants.Permissions.embedLinks)
!hasDiscordPermissions(channel.permissionsFor(pluginData.client.user!.id), Permissions.FLAGS.EMBED_LINKS)
) {
pluginData.state.logs.log(LogType.BOT_ALERT, {
body: `Missing permissions to reply **with an embed** in ${verboseChannelMention(
@ -85,8 +84,8 @@ export const ReplyAction = automodAction({
continue;
}
const messageContent: StrictMessageContent = typeof formatted === "string" ? { content: formatted } : formatted;
const replyMsg = await channel.createMessage({
const messageContent: MessageOptions = typeof formatted === "string" ? { content: formatted } : formatted;
const replyMsg = await channel.send({
...messageContent,
allowedMentions: {
users: [user.id],

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { tNullable } from "../../../utils";
import { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { automodAction } from "../helpers";
export const SetAntiraidLevelAction = automodAction({
configType: tNullable(t.string),

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { CountersPlugin } from "../../Counters/CountersPlugin";
import { LogType } from "../../../data/LogType";
import { CountersPlugin } from "../../Counters/CountersPlugin";
import { automodAction } from "../helpers";
export const SetCounterAction = automodAction({
configType: t.type({

View file

@ -1,8 +1,9 @@
import { Snowflake, TextChannel } from "discord.js";
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { convertDelayStringToMS, isDiscordRESTError, tDelayString, tNullable } from "../../../utils";
import { ChannelTypeStrings } from "src/types";
import { LogType } from "../../../data/LogType";
import { Constants, TextChannel } from "eris";
import { convertDelayStringToMS, isDiscordAPIError, tDelayString, tNullable } from "../../../utils";
import { automodAction } from "../helpers";
export const SetSlowmodeAction = automodAction({
configType: t.type({
@ -18,24 +19,23 @@ export const SetSlowmodeAction = automodAction({
const slowmodeMs = Math.max(actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : 0, 0);
for (const channelId of actionConfig.channels) {
const channel = pluginData.guild.channels.get(channelId);
const channel = pluginData.guild.channels.cache.get(channelId as Snowflake);
// Only text channels and text channels within categories support slowmodes
if (
!channel ||
!(channel.type === Constants.ChannelTypes.GUILD_TEXT || channel.type === Constants.ChannelTypes.GUILD_CATEGORY)
) {
if (!channel || !(channel.type === ChannelTypeStrings.TEXT || ChannelTypeStrings.CATEGORY)) {
continue;
}
let channelsToSlowmode: TextChannel[] = [];
if (channel.type === Constants.ChannelTypes.GUILD_CATEGORY) {
const channelsToSlowmode: TextChannel[] = [];
if (channel.type === ChannelTypeStrings.CATEGORY) {
// Find all text channels within the category
channelsToSlowmode = pluginData.guild.channels.filter(
ch => ch.parentID === channel.id && ch.type === Constants.ChannelTypes.GUILD_TEXT,
) as TextChannel[];
for (const ch of pluginData.guild.channels.cache.values()) {
if (ch.parentId === channel.id && ch.type === ChannelTypeStrings.TEXT) {
channelsToSlowmode.push(ch as TextChannel);
}
}
} else {
channelsToSlowmode.push(channel);
channelsToSlowmode.push(channel as TextChannel);
}
const slowmodeSeconds = Math.ceil(slowmodeMs / 1000);
@ -49,7 +49,7 @@ export const SetSlowmodeAction = automodAction({
} catch (e) {
// Check for invalid form body -> indicates duration was too large
const errorMessage =
isDiscordRESTError(e) && e.code === 50035
isDiscordAPIError(e) && e.code === 50035
? `Duration is greater than maximum native slowmode duration`
: e.message;

View file

@ -1,10 +1,9 @@
import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers";
export const WarnAction = automodAction({
configType: t.type({
@ -25,7 +24,7 @@ export const WarnAction = automodAction({
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;
const caseArgs: Partial<CaseArgs> = {
modId: pluginData.client.user.id,
modId: pluginData.client.user!.id,
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
automatic: true,
postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined,

Some files were not shown because too many files have changed in this diff Show more