3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-16 14:11:50 +00:00

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

View file

@ -1,6 +1,6 @@
import util from "util"; import util from "util";
export class ErisError extends Error { export class DiscordJSError extends Error {
code: number | string | undefined; code: number | string | undefined;
shardId: number; shardId: number;
@ -11,6 +11,6 @@ export class ErisError extends Error {
} }
[util.inspect.custom]() { [util.inspect.custom]() {
return `[ERIS] [ERROR CODE ${this.code || "?"}] [SHARD ${this.shardId}] ${this.message}`; return `[DISCORDJS] [ERROR CODE ${this.code ?? "?"}] [SHARD ${this.shardId}] ${this.message}`;
} }
} }

View file

@ -1,4 +1,4 @@
import { Guild } from "eris"; import { Guild } from "discord.js";
export enum ERRORS { export enum ERRORS {
NO_MUTE_ROLE_IN_CONFIG = 1, 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 { EventEmitter } from "events";
import { CooldownManager } from "knub";
import { RegExpWorker, TimeoutError } from "regexp-worker";
import { MINUTES, SECONDS } from "./utils";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
const isTimeoutError = (a): a is TimeoutError => { const isTimeoutError = (a): a is TimeoutError => {

View file

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

View file

@ -1,13 +1,13 @@
import express, { Request, Response } from "express"; 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 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 { ApiUserInfo } from "../data/ApiUserInfo";
import { ApiUserInfoData } from "../data/entities/ApiUserInfo"; import { ApiUserInfoData } from "../data/entities/ApiUserInfo";
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
import { ok } from "./responses"; import { ok } from "./responses";
interface IPassportApiUser { interface IPassportApiUser {

View file

@ -1,7 +1,7 @@
import express from "express"; import express from "express";
import { guildPlugins } from "../plugins/availablePlugins"; import { guildPlugins } from "../plugins/availablePlugins";
import { notFound } from "./responses";
import { indentLines } from "../utils"; import { indentLines } from "../utils";
import { notFound } from "./responses";
function formatConfigSchema(schema) { function formatConfigSchema(schema) {
if (schema._tag === "InterfaceType" || schema._tag === "PartialType") { 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 { 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 { 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(); const apiPermissionAssignments = new ApiPermissionAssignments();

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import express, { Request, Response } from "express"; import express, { Request, Response } from "express";
import { apiTokenAuthHandlers } from "./auth";
import { isStaff } from "../staff"; import { isStaff } from "../staff";
import { apiTokenAuthHandlers } from "./auth";
export function initStaff(app: express.Express) { export function initStaff(app: express.Express) {
const staffRouter = express.Router(); 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 cors from "cors";
import { initAuth } from "./auth"; import express from "express";
import { initGuildsAPI } from "./guilds";
import { initArchives } from "./archives";
import { initDocs } from "./docs";
import { TokenError } from "passport-oauth2"; 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(); 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 { import {
channelMentionRegex, channelMentionRegex,
convertDelayStringToMS, convertDelayStringToMS,
disableCodeBlocks,
disableInlineCode,
isSnowflake,
isValidSnowflake, isValidSnowflake,
resolveMember, resolveMember,
resolveUser, resolveUser,
@ -11,13 +11,9 @@ import {
roleMentionRegex, roleMentionRegex,
UnknownUser, UnknownUser,
} from "./utils"; } from "./utils";
import { GuildChannel, Member, TextChannel, User } from "eris"; import { isValidTimezone } from "./utils/isValidTimezone";
import { baseTypeConverters, baseCommandParameterTypeHelpers, CommandContext, TypeConversionError } from "knub";
import { createTypeHelper } from "knub-command-manager";
import { getChannelIdFromMessageId } from "./data/getChannelIdFromMessageId";
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget"; import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget";
import { inputPatternToRegExp } from "./validatorUtils"; import { inputPatternToRegExp } from "./validatorUtils";
import { isValidTimezone } from "./utils/isValidTimezone";
export const commandTypes = { export const commandTypes = {
...baseTypeConverters, ...baseTypeConverters,
@ -34,7 +30,7 @@ export const commandTypes = {
async resolvedUser(value, context: CommandContext<any>) { async resolvedUser(value, context: CommandContext<any>) {
const result = await resolveUser(context.pluginData.client, value); const result = await resolveUser(context.pluginData.client, value);
if (result == null || result instanceof UnknownUser) { if (result == null || result instanceof UnknownUser) {
throw new TypeConversionError(`User \`${disableCodeBlocks(value)}\` was not found`); throw new TypeConversionError(`User \`${Util.escapeCodeBlock(value)}\` was not found`);
} }
return result; return result;
}, },
@ -42,7 +38,7 @@ export const commandTypes = {
async resolvedUserLoose(value, context: CommandContext<any>) { async resolvedUserLoose(value, context: CommandContext<any>) {
const result = await resolveUser(context.pluginData.client, value); const result = await resolveUser(context.pluginData.client, value);
if (result == null) { if (result == null) {
throw new TypeConversionError(`Invalid user: \`${disableCodeBlocks(value)}\``); throw new TypeConversionError(`Invalid user: \`${Util.escapeCodeBlock(value)}\``);
} }
return result; return result;
}, },
@ -55,7 +51,7 @@ export const commandTypes = {
const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value); const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value);
if (result == null) { if (result == null) {
throw new TypeConversionError( throw new TypeConversionError(
`Member \`${disableCodeBlocks(value)}\` was not found or they have left the server`, `Member \`${Util.escapeCodeBlock(value)}\` was not found or they have left the server`,
); );
} }
return result; return result;
@ -66,7 +62,7 @@ export const commandTypes = {
const result = await resolveMessageTarget(context.pluginData, value); const result = await resolveMessageTarget(context.pluginData, value);
if (!result) { if (!result) {
throw new TypeConversionError(`Unknown message \`${disableInlineCode(value)}\``); throw new TypeConversionError(`Unknown message \`${Util.escapeInlineCode(value)}\``);
} }
return result; return result;
@ -74,32 +70,32 @@ export const commandTypes = {
async anyId(value: string, context: CommandContext<any>) { async anyId(value: string, context: CommandContext<any>) {
const userId = resolveUserId(context.pluginData.client, value); const userId = resolveUserId(context.pluginData.client, value);
if (userId) return userId; if (userId) return userId as Snowflake;
const channelIdMatch = value.match(channelMentionRegex); const channelIdMatch = value.match(channelMentionRegex);
if (channelIdMatch) return channelIdMatch[1]; if (channelIdMatch) return channelIdMatch[1] as Snowflake;
const roleIdMatch = value.match(roleMentionRegex); const roleIdMatch = value.match(roleMentionRegex);
if (roleIdMatch) return roleIdMatch[1]; if (roleIdMatch) return roleIdMatch[1] as Snowflake;
if (isValidSnowflake(value)) { if (isValidSnowflake(value)) {
return value; return value as Snowflake;
} }
throw new TypeConversionError(`Could not parse ID: \`${disableInlineCode(value)}\``); throw new TypeConversionError(`Could not parse ID: \`${Util.escapeInlineCode(value)}\``);
}, },
regex(value: string, context: CommandContext<any>): RegExp { regex(value: string, context: CommandContext<any>): RegExp {
try { try {
return inputPatternToRegExp(value); return inputPatternToRegExp(value);
} catch (e) { } catch (e) {
throw new TypeConversionError(`Could not parse RegExp: \`${disableInlineCode(e.message)}\``); throw new TypeConversionError(`Could not parse RegExp: \`${Util.escapeInlineCode(e.message)}\``);
} }
}, },
timezone(value: string) { timezone(value: string) {
if (!isValidTimezone(value)) { if (!isValidTimezone(value)) {
throw new TypeConversionError(`Invalid timezone: ${disableInlineCode(value)}`); throw new TypeConversionError(`Invalid timezone: ${Util.escapeInlineCode(value)}`);
} }
return value; return value;
@ -112,9 +108,9 @@ export const commandTypeHelpers = {
delay: createTypeHelper<number>(commandTypes.delay), delay: createTypeHelper<number>(commandTypes.delay),
resolvedUser: createTypeHelper<Promise<User>>(commandTypes.resolvedUser), resolvedUser: createTypeHelper<Promise<User>>(commandTypes.resolvedUser),
resolvedUserLoose: createTypeHelper<Promise<User | UnknownUser>>(commandTypes.resolvedUserLoose), 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), messageTarget: createTypeHelper<Promise<MessageTarget>>(commandTypes.messageTarget),
anyId: createTypeHelper<Promise<string>>(commandTypes.anyId), anyId: createTypeHelper<Promise<Snowflake>>(commandTypes.anyId),
regex: createTypeHelper<RegExp>(commandTypes.regex), regex: createTypeHelper<RegExp>(commandTypes.regex),
timezone: createTypeHelper<string>(commandTypes.timezone), timezone: createTypeHelper<string>(commandTypes.timezone),
}; };

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 { configUtils, ConfigValidationError, PluginOptions } from "knub";
import moment from "moment-timezone"; 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>(); const pluginNameToPlugin = new Map<string, ZeppelinPlugin>();
for (const plugin of guildPlugins) { for (const plugin of guildPlugins) {

View file

@ -1,7 +1,7 @@
import { AllowedGuild } from "./entities/AllowedGuild";
import { getRepository, Repository } from "typeorm"; import { getRepository, Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository";
import { ApiPermissionTypes } from "./ApiPermissionAssignments"; import { ApiPermissionTypes } from "./ApiPermissionAssignments";
import { BaseRepository } from "./BaseRepository";
import { AllowedGuild } from "./entities/AllowedGuild";
export class AllowedGuilds extends BaseRepository { export class AllowedGuilds extends BaseRepository {
private allowedGuilds: Repository<AllowedGuild>; 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 crypto from "crypto";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { getRepository, Repository } from "typeorm";
// tslint:disable-next-line:no-submodule-imports // tslint:disable-next-line:no-submodule-imports
import uuidv4 from "uuid/v4"; import uuidv4 from "uuid/v4";
import { DAYS, DBDateFormat } from "../utils"; import { DAYS, DBDateFormat } from "../utils";
import { BaseRepository } from "./BaseRepository";
import { ApiLogin } from "./entities/ApiLogin";
const LOGIN_EXPIRY_TIME = 1 * DAYS; 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 { ApiPermissions } from "@shared/apiPermissions";
import { getRepository, Repository } from "typeorm";
import { BaseRepository } from "./BaseRepository";
import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment";
export enum ApiPermissionTypes { export enum ApiPermissionTypes {
User = "USER", User = "USER",

View file

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

View file

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

View file

@ -24,11 +24,15 @@
"CHANNEL_CREATE": "🖊 Channel {channelMention(channel)} was created", "CHANNEL_CREATE": "🖊 Channel {channelMention(channel)} was created",
"CHANNEL_DELETE": "🗑 Channel {channelMention(channel)} was deleted", "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_CREATE": "🖊 Role **{role.name}** (`{role.id}`) was created",
"ROLE_DELETE": "🖊 Role **{role.name}** (`{role.id}`) was deleted", "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_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)}", "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_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)}", "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}`", "COMMAND": "🤖 {userMention(member)} used command in {channelMention(channel)}:\n`{command}`",
"MESSAGE_SPAM_DETECTED": "🛑 {userMention(member)} spam detected in {channelMention(channel)}: {description} (more than {limit} in {interval}s)\n{archiveUrl}", "MESSAGE_SPAM_DETECTED": "🛑 {userMention(member)} spam detected in {channelMention(channel)}: {description} (more than {limit} in {interval}s)\n{archiveUrl}",

View file

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

View file

@ -1,11 +1,12 @@
import { Guild, Snowflake } from "discord.js";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { ArchiveEntry } from "./entities/ArchiveEntry"; import { isDefaultSticker } from "src/utils/isDefaultSticker";
import { getRepository, Repository } from "typeorm"; 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 { 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; const DEFAULT_EXPIRY_DAYS = 30;
@ -13,7 +14,7 @@ const MESSAGE_ARCHIVE_HEADER_FORMAT = trimLines(`
Server: {guild.name} ({guild.id}) Server: {guild.name} ({guild.id})
`); `);
const MESSAGE_ARCHIVE_MESSAGE_FORMAT = 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 { export class GuildArchives extends BaseGuildRepository {
protected archives: Repository<ArchiveEntry>; protected archives: Repository<ArchiveEntry>;
@ -73,13 +74,19 @@ export class GuildArchives extends BaseGuildRepository {
protected async renderLinesFromSavedMessages(savedMessages: SavedMessage[], guild: Guild) { protected async renderLinesFromSavedMessages(savedMessages: SavedMessage[], guild: Guild) {
const msgLines: string[] = []; const msgLines: string[] = [];
for (const msg of savedMessages) { 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 user = { ...msg.data.author, id: msg.user_id };
const line = await renderTemplate(MESSAGE_ARCHIVE_MESSAGE_FORMAT, { const line = await renderTemplate(MESSAGE_ARCHIVE_MESSAGE_FORMAT, {
id: msg.id, id: msg.id,
timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"), timestamp: moment.utc(msg.posted_at).format("YYYY-MM-DD HH:mm:ss"),
content: msg.data.content, 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, user,
channel, channel,
}); });

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm"; import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { AutoReaction } from "./entities/AutoReaction"; import { AutoReaction } from "./entities/AutoReaction";
export class GuildAutoReactions extends BaseGuildRepository { 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 { Case } from "./entities/Case";
import { CaseNote } from "./entities/CaseNote"; 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 moment = require("moment-timezone");
import { connection } from "./db";
const CASE_SUMMARY_REASON_MAX_LENGTH = 300; 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 moment from "moment-timezone";
import { DAYS, DBDateFormat, HOURS, MINUTES } from "../utils"; import { FindConditions, getRepository, In, IsNull, Not, Repository } from "typeorm";
import { connection } from "./db";
import { Queue } from "../Queue"; 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_COUNTERS_AFTER = 1 * DAYS;
const DELETE_UNUSED_COUNTER_TRIGGERS_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 { QueuedEventEmitter } from "../QueuedEventEmitter";
import { BaseGuildRepository } from "./BaseGuildRepository";
export class GuildEvents extends BaseGuildRepository { export class GuildEvents extends BaseGuildRepository {
private queuedEventEmitter: QueuedEventEmitter; 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 { getRepository, Repository } from "typeorm/index";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { connection } from "./db"; import { connection } from "./db";
import { MemberTimezone } from "./entities/MemberTimezone";
export class GuildMemberTimezones extends BaseGuildRepository { export class GuildMemberTimezones extends BaseGuildRepository {
protected memberTimezones: Repository<MemberTimezone>; protected memberTimezones: Repository<MemberTimezone>;

View file

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

View file

@ -1,9 +1,9 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, In, Repository } from "typeorm"; import { getRepository, In, Repository } from "typeorm";
import { NicknameHistoryEntry } from "./entities/NicknameHistoryEntry";
import { MINUTES, SECONDS } from "../utils";
import { isAPI } from "../globals"; import { isAPI } from "../globals";
import { MINUTES, SECONDS } from "../utils";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { cleanupNicknames } from "./cleanup/nicknames"; import { cleanupNicknames } from "./cleanup/nicknames";
import { NicknameHistoryEntry } from "./entities/NicknameHistoryEntry";
if (!isAPI()) { if (!isAPI()) {
const CLEANUP_INTERVAL = 5 * MINUTES; 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 { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { PersistedData } from "./entities/PersistedData";
export interface IPartialPersistData { export interface IPartialPersistData {
roles?: string[]; roles?: string[];

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { BaseGuildRepository } from "./BaseGuildRepository";
import { getRepository, Repository } from "typeorm"; import { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { ScheduledPost } from "./entities/ScheduledPost"; import { ScheduledPost } from "./entities/ScheduledPost";
export class GuildScheduledPosts extends BaseGuildRepository { 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 { getRepository, Repository } from "typeorm";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { SlowmodeChannel } from "./entities/SlowmodeChannel"; import { SlowmodeChannel } from "./entities/SlowmodeChannel";
import { SlowmodeUser } from "./entities/SlowmodeUser"; import { SlowmodeUser } from "./entities/SlowmodeUser";
import moment from "moment-timezone";
export class GuildSlowmodes extends BaseGuildRepository { export class GuildSlowmodes extends BaseGuildRepository {
private slowmodeChannels: Repository<SlowmodeChannel>; private slowmodeChannels: Repository<SlowmodeChannel>;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,9 +18,15 @@ export enum LogType {
CHANNEL_CREATE, CHANNEL_CREATE,
CHANNEL_DELETE, CHANNEL_DELETE,
CHANNEL_UPDATE,
THREAD_CREATE,
THREAD_DELETE,
THREAD_UPDATE,
ROLE_CREATE, ROLE_CREATE,
ROLE_DELETE, ROLE_DELETE,
ROLE_UPDATE,
MESSAGE_EDIT, MESSAGE_EDIT,
MESSAGE_DELETE, MESSAGE_DELETE,
@ -31,6 +37,18 @@ export enum LogType {
VOICE_CHANNEL_LEAVE, VOICE_CHANNEL_LEAVE,
VOICE_CHANNEL_MOVE, 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, COMMAND,
MESSAGE_SPAM_DETECTED, MESSAGE_SPAM_DETECTED,

View file

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

View file

@ -1,9 +1,9 @@
import { getRepository, In, Repository } from "typeorm"; import { getRepository, In, Repository } from "typeorm";
import { UsernameHistoryEntry } from "./entities/UsernameHistoryEntry"; import { isAPI } from "../globals";
import { MINUTES, SECONDS } from "../utils"; import { MINUTES, SECONDS } from "../utils";
import { BaseRepository } from "./BaseRepository"; import { BaseRepository } from "./BaseRepository";
import { isAPI } from "../globals";
import { cleanupUsernames } from "./cleanup/usernames"; import { cleanupUsernames } from "./cleanup/usernames";
import { UsernameHistoryEntry } from "./entities/UsernameHistoryEntry";
if (!isAPI()) { if (!isAPI()) {
const CLEANUP_INTERVAL = 5 * MINUTES; 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 moment from "moment-timezone";
import { getRepository, In } from "typeorm";
import { DBDateFormat } from "../../utils"; import { DBDateFormat } from "../../utils";
import { connection } from "../db";
import { Config } from "../entities/Config";
const CLEAN_PER_LOOP = 50; 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 moment from "moment-timezone";
import { getRepository, In } from "typeorm";
import { DAYS, DBDateFormat, MINUTES } from "../../utils";
import { connection } from "../db"; import { connection } from "../db";
import { SavedMessage } from "../entities/SavedMessage";
/** /**
* How long message edits, deletions, etc. will include the original message content. * 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 moment from "moment-timezone";
import { NicknameHistoryEntry } from "../entities/NicknameHistoryEntry"; import { getRepository, In } from "typeorm";
import { DAYS, DBDateFormat } from "../../utils"; import { DAYS, DBDateFormat } from "../../utils";
import { connection } from "../db"; import { connection } from "../db";
import { NicknameHistoryEntry } from "../entities/NicknameHistoryEntry";
export const NICKNAME_RETENTION_PERIOD = 30 * DAYS; export const NICKNAME_RETENTION_PERIOD = 30 * DAYS;
const CLEAN_PER_LOOP = 500; const CLEAN_PER_LOOP = 500;

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { decrypt, encrypt } from "../utils/crypt";
import { ValueTransformer } from "typeorm"; import { ValueTransformer } from "typeorm";
import { decrypt, encrypt } from "../utils/crypt";
interface EncryptedTextTransformer extends ValueTransformer { interface EncryptedTextTransformer extends ValueTransformer {
from(dbValue: any): string; 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]; return REVERSE_OPS[op];
} }
const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})([1-9]\\d*)$`); const comparisonStringRegex = new RegExp(`^(${TRIGGER_COMPARISON_OPS.join("|")})(\\d*)$`);
/** /**
* @return Parsed comparison op and value, or null if the comparison string was invalid * @return Parsed comparison op and value, or null if the comparison string was invalid

View file

@ -21,4 +21,6 @@ export class ReactionRole {
@Column() role_id: string; @Column() role_id: string;
@Column() is_exclusive: boolean; @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 { Column, Entity, PrimaryColumn } from "typeorm";
import { createEncryptedJsonTransformer } from "../encryptedJsonTransformer"; import { createEncryptedJsonTransformer } from "../encryptedJsonTransformer";
import { Attachment, Sticker } from "eris";
export interface ISavedMessageData { export interface ISavedMessageData {
attachments?: Attachment[]; attachments?: MessageAttachment[];
author: { author: {
username: string; username: string;
discriminator: string; discriminator: string;

View file

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

View file

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

View file

@ -1,8 +1,8 @@
// tslint:disable:no-console // tslint:disable:no-console
import { connect } from "./data/db";
import { Configs } from "./data/Configs";
import path from "path";
import * as _fs from "fs"; import * as _fs from "fs";
import path from "path";
import { Configs } from "./data/Configs";
import { connect } from "./data/db";
const fs = _fs.promises; 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 { export class FixStarboardReactionsIndices1608692857722 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> { 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) * @file Utility functions that are plugin-instance-specific (i.e. use PluginData)
*/ */
import { AdvancedMessageContent, AllowedMentions, GuildTextableChannel, Member, Message, TextableChannel } from "eris"; import { GuildMember, Message, MessageMentionOptions, MessageOptions, TextChannel } from "discord.js";
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 * as t from "io-ts"; import * as t from "io-ts";
import { CommandContext, configUtils, ConfigValidationError, GuildPluginData, helpers, PluginOptions } from "knub";
import { PluginOverrideCriteria } from "knub/dist/config/configTypes"; 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 { AnyPluginData } from "knub/dist/plugins/PluginData";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { logger } from "./logger"; 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; const { getMemberLevel } = helpers;
export function canActOn(pluginData: GuildPluginData<any>, member1: Member, member2: Member, allowSameLevel = false) { export function canActOn(
if (member2.id === pluginData.client.user.id) { pluginData: GuildPluginData<any>,
member1: GuildMember,
member2: GuildMember,
allowSameLevel = false,
) {
if (member2.id === pluginData.client.user!.id) {
return false; return false;
} }
@ -178,45 +183,43 @@ export function getPluginConfigPreprocessor(
}; };
} }
export function sendSuccessMessage( export async function sendSuccessMessage(
pluginData: AnyPluginData<any>, pluginData: AnyPluginData<any>,
channel: TextableChannel, channel: TextChannel,
body: string, body: string,
allowedMentions?: AllowedMentions, allowedMentions?: MessageMentionOptions,
): Promise<Message | undefined> { ): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.success_emoji || undefined; const emoji = pluginData.fullConfig.success_emoji || undefined;
const formattedBody = successMessage(body, emoji); const formattedBody = successMessage(body, emoji);
const content: AdvancedMessageContent = allowedMentions const content: MessageOptions = allowedMentions
? { content: formattedBody, allowedMentions } ? { content: formattedBody, allowedMentions }
: { content: formattedBody }; : { content: formattedBody };
return channel return channel
.createMessage(content) // Force line break .send({ ...content }) // Force line break
.catch(err => { .catch(err => {
const channelInfo = (channel as GuildTextableChannel).guild const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id;
? `${channel.id} (${(channel as GuildTextableChannel).guild.id})`
: `${channel.id}`;
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
return undefined; return undefined;
}); });
} }
export function sendErrorMessage( export async function sendErrorMessage(
pluginData: AnyPluginData<any>, pluginData: AnyPluginData<any>,
channel: TextableChannel, channel: TextChannel,
body: string, body: string,
allowedMentions?: AllowedMentions, allowedMentions?: MessageMentionOptions,
): Promise<Message | undefined> { ): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.error_emoji || undefined; const emoji = pluginData.fullConfig.error_emoji || undefined;
const formattedBody = errorMessage(body, emoji); const formattedBody = errorMessage(body, emoji);
const content: AdvancedMessageContent = allowedMentions const content: MessageOptions = allowedMentions
? { content: formattedBody, allowedMentions } ? { content: formattedBody, allowedMentions }
: { content: formattedBody }; : { content: formattedBody };
return channel return channel
.createMessage(content) // Force line break .send({ ...content }) // Force line break
.catch(err => { .catch(err => {
const channelInfo = (channel as GuildTextableChannel).guild const channelInfo = channel.guild ? `${channel.id} (${channel.guild.id})` : channel.id;
? `${channel.id} (${(channel as GuildTextableChannel).guild.id})`
: `${channel.id}`;
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
return undefined; return undefined;
}); });

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import { AutoDeletePluginType } from "../types";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { SavedMessage } from "../../../data/entities/SavedMessage"; import { SavedMessage } from "../../../data/entities/SavedMessage";
import { AutoDeletePluginType } from "../types";
import { onMessageDelete } from "./onMessageDelete"; import { onMessageDelete } from "./onMessageDelete";
export function onMessageDeleteBulk(pluginData: GuildPluginData<AutoDeletePluginType>, messages: SavedMessage[]) { 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 { PluginOptions } from "knub";
import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd";
import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildAutoReactions } from "../../data/GuildAutoReactions"; import { GuildAutoReactions } from "../../data/GuildAutoReactions";
import { AddReactionsEvt } from "./events/AddReactionsEvt"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { trimPluginDescription } from "../../utils"; import { trimPluginDescription } from "../../utils";
import { LogsPlugin } from "../Logs/LogsPlugin"; 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> = { const defaultOptions: PluginOptions<AutoReactionsPluginType> = {
config: { config: {

View file

@ -1,6 +1,6 @@
import { autoReactionsCmd } from "../types";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { autoReactionsCmd } from "../types";
export const DisableAutoReactionsCmd = autoReactionsCmd({ export const DisableAutoReactionsCmd = autoReactionsCmd({
trigger: "auto_reactions disable", 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 { commandTypeHelpers as ct } from "../../../commandTypes";
import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils"; import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { canUseEmoji, customEmojiRegex, isEmoji } from "../../../utils";
import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions"; import { getMissingChannelPermissions } from "../../../utils/getMissingChannelPermissions";
import { Constants, GuildChannel } from "eris";
import { readChannelPermissions } from "../../../utils/readChannelPermissions";
import { missingPermissionError } from "../../../utils/missingPermissionError"; 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({ export const NewAutoReactionsCmd = autoReactionsCmd({
trigger: "auto_reactions", trigger: "auto_reactions",
@ -22,7 +22,7 @@ export const NewAutoReactionsCmd = autoReactionsCmd({
async run({ message: msg, args, pluginData }) { async run({ message: msg, args, pluginData }) {
const finalReactions: string[] = []; 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); const missingPermissions = getMissingChannelPermissions(me, args.channel as GuildChannel, requiredPermissions);
if (missingPermissions) { if (missingPermissions) {
sendErrorMessage( sendErrorMessage(

View file

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

View file

@ -1,8 +1,8 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub"; import { BasePluginType, typedGuildCommand, typedGuildEventListener } from "knub";
import { GuildAutoReactions } from "../../data/GuildAutoReactions";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { GuildAutoReactions } from "../../data/GuildAutoReactions";
export const ConfigSchema = t.type({ export const ConfigSchema = t.type({
can_manage: t.boolean, 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 { configUtils, CooldownManager } from "knub";
import { availableTriggers } from "./triggers/availableTriggers";
import { StrictValidationError } from "../../validatorUtils";
import { ConfigPreprocessorFn } from "knub/dist/config/configTypes"; 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 { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
import { GuildArchives } from "../../data/GuildArchives"; 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 { LogsPlugin } from "../Logs/LogsPlugin";
import { ModActionsPlugin } from "../ModActions/ModActionsPlugin"; import { ModActionsPlugin } from "../ModActions/ModActionsPlugin";
import { MutesPlugin } from "../Mutes/MutesPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { availableActions } from "./actions/availableActions";
import { AntiraidClearCmd } from "./commands/AntiraidClearCmd"; import { AntiraidClearCmd } from "./commands/AntiraidClearCmd";
import { SetAntiraidCmd } from "./commands/SetAntiraidCmd"; import { SetAntiraidCmd } from "./commands/SetAntiraidCmd";
import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd"; 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 { 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 { runAutomodOnModAction } from "./events/runAutomodOnModAction";
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap"; import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChanges";
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap"; 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 = { const defaultOptions = {
config: { config: {
@ -179,6 +176,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
events: [ events: [
RunAutomodOnJoinEvt, RunAutomodOnJoinEvt,
RunAutomodOnMemberUpdate, RunAutomodOnMemberUpdate,
RunAutomodOnLeaveEvt,
// Messages use message events from SavedMessages, see onLoad below // 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 * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { nonNullish, unique } from "../../../utils"; 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 { canAssignRole } from "../../../utils/canAssignRole";
import { missingPermissionError } from "../../../utils/missingPermissionError"; import { getMissingPermissions } from "../../../utils/getMissingPermissions";
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
import { memberRolesLock } from "../../../utils/lockNameHelpers"; 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({ export const AddRolesAction = automodAction({
configType: t.array(t.string), configType: t.array(t.string),
@ -19,9 +18,9 @@ export const AddRolesAction = automodAction({
async apply({ pluginData, contexts, actionConfig, ruleName }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
const members = unique(contexts.map(c => c.member).filter(nonNullish)); 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) { if (missingPermissions) {
const logs = pluginData.getPlugin(LogsPlugin); const logs = pluginData.getPlugin(LogsPlugin);
logs.log(LogType.BOT_ALERT, { logs.log(LogType.BOT_ALERT, {
@ -42,7 +41,7 @@ export const AddRolesAction = automodAction({
if (rolesWeCannotAssign.length) { if (rolesWeCannotAssign.length) {
const roleNamesWeCannotAssign = rolesWeCannotAssign.map( 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); const logs = pluginData.getPlugin(LogsPlugin);
logs.log(LogType.BOT_ALERT, { logs.log(LogType.BOT_ALERT, {
@ -54,13 +53,13 @@ export const AddRolesAction = automodAction({
await Promise.all( await Promise.all(
members.map(async member => { members.map(async member => {
const memberRoles = new Set(member.roles); const memberRoles = new Set(member.roles.cache.keys());
for (const roleId of rolesToAssign) { for (const roleId of rolesToAssign) {
memberRoles.add(roleId); memberRoles.add(roleId as Snowflake);
ignoreRoleChange(pluginData, member.id, roleId); ignoreRoleChange(pluginData, member.id, roleId);
} }
if (memberRoles.size === member.roles.length) { if (memberRoles.size === member.roles.cache.size) {
// No role changes // No role changes
return; return;
} }
@ -71,7 +70,6 @@ export const AddRolesAction = automodAction({
await member.edit({ await member.edit({
roles: rolesArr, roles: rolesArr,
}); });
member.roles = rolesArr; // Make sure we know of the new roles internally as well
memberRoleLock.unlock(); memberRoleLock.unlock();
}), }),

View file

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

View file

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

View file

@ -1,20 +1,20 @@
import * as t from "io-ts"; import * as t from "io-ts";
import { CleanAction } from "./clean";
import { AutomodActionBlueprint } from "../helpers"; 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 { AddRolesAction } from "./addRoles";
import { RemoveRolesAction } from "./removeRoles";
import { SetAntiraidLevelAction } from "./setAntiraidLevel";
import { ReplyAction } from "./reply";
import { AddToCounterAction } from "./addToCounter"; 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 { SetCounterAction } from "./setCounter";
import { SetSlowmodeAction } from "./setSlowmode"; import { SetSlowmodeAction } from "./setSlowmode";
import { WarnAction } from "./warn";
export const availableActions: Record<string, AutomodActionBlueprint<any>> = { export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
clean: CleanAction, clean: CleanAction,

View file

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

View file

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

View file

@ -1,7 +1,8 @@
import { Snowflake, TextChannel } from "discord.js";
import * as t from "io-ts"; import * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { noop } from "../../../utils"; import { noop } from "../../../utils";
import { automodAction } from "../helpers";
export const CleanAction = automodAction({ export const CleanAction = automodAction({
configType: t.boolean, configType: t.boolean,
@ -29,7 +30,8 @@ export const CleanAction = automodAction({
pluginData.state.logs.ignoreLog(LogType.MESSAGE_DELETE, id); 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 * as t from "io-ts";
import { automodAction } from "../helpers";
import { LogType } from "../../../data/LogType";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils"; import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { CaseArgs } from "../../Cases/types"; import { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers";
export const KickAction = automodAction({ export const KickAction = automodAction({
configType: t.type({ configType: t.type({
@ -25,7 +24,7 @@ export const KickAction = automodAction({
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined; const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;
const caseArgs: Partial<CaseArgs> = { const caseArgs: Partial<CaseArgs> = {
modId: pluginData.client.user.id, modId: pluginData.client.user!.id,
extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [], extraNotes: matchResult.fullSummary ? [matchResult.fullSummary] : [],
automatic: true, automatic: true,
postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined, postInCaseLogOverride: actionConfig.postInCaseLog ?? undefined,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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