Switch from Knex to TypeORM. Update Knub.
This commit is contained in:
parent
e3ff4cef45
commit
f9c16263ae
49 changed files with 1192 additions and 1395 deletions
34
knexfile.js
34
knexfile.js
|
@ -1,34 +0,0 @@
|
|||
require('dotenv').config();
|
||||
|
||||
const moment = require('moment-timezone');
|
||||
moment.tz.setDefault('UTC');
|
||||
|
||||
module.exports = {
|
||||
client: 'mysql2',
|
||||
connection: {
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
charset: 'utf8mb4',
|
||||
timezone: 'UTC',
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true,
|
||||
dateStrings: true,
|
||||
typeCast(field, next) {
|
||||
if (field.type === 'DATETIME') {
|
||||
const val = field.string();
|
||||
return val != null ? moment(val).format('YYYY-MM-DD HH:mm:ss') : null;
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
},
|
||||
pool: {
|
||||
afterCreate(conn, cb) {
|
||||
conn.query('SET time_zone = "+00:00";', err => {
|
||||
cb(err, conn);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
112
migrations/1540519249973-CreatePreTypeORMTables.ts
Normal file
112
migrations/1540519249973-CreatePreTypeORMTables.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class CreatePreTypeORMTables1540519249973 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`archives\` (
|
||||
\`id\` VARCHAR(36) NOT NULL,
|
||||
\`guild_id\` VARCHAR(20) NOT NULL,
|
||||
\`body\` MEDIUMTEXT NOT NULL,
|
||||
\`created_at\` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`expires_at\` DATETIME NULL DEFAULT NULL,
|
||||
PRIMARY KEY (\`id\`)
|
||||
)
|
||||
COLLATE='utf8mb4_general_ci'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`cases\` (
|
||||
\`id\` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
\`guild_id\` BIGINT(20) UNSIGNED NOT NULL,
|
||||
\`case_number\` INT(10) UNSIGNED NOT NULL,
|
||||
\`user_id\` BIGINT(20) UNSIGNED NOT NULL,
|
||||
\`user_name\` VARCHAR(128) NOT NULL,
|
||||
\`mod_id\` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
|
||||
\`mod_name\` VARCHAR(128) NULL DEFAULT NULL,
|
||||
\`type\` INT(10) UNSIGNED NOT NULL,
|
||||
\`audit_log_id\` BIGINT(20) NULL DEFAULT NULL,
|
||||
\`created_at\` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (\`id\`),
|
||||
UNIQUE INDEX \`mod_actions_guild_id_case_number_unique\` (\`guild_id\`, \`case_number\`),
|
||||
UNIQUE INDEX \`mod_actions_audit_log_id_unique\` (\`audit_log_id\`),
|
||||
INDEX \`mod_actions_user_id_index\` (\`user_id\`),
|
||||
INDEX \`mod_actions_mod_id_index\` (\`mod_id\`),
|
||||
INDEX \`mod_actions_created_at_index\` (\`created_at\`)
|
||||
)
|
||||
COLLATE = 'utf8mb4_general_ci'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`case_notes\` (
|
||||
\`id\` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
\`case_id\` INT(10) UNSIGNED NOT NULL,
|
||||
\`mod_id\` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
|
||||
\`mod_name\` VARCHAR(128) NULL DEFAULT NULL,
|
||||
\`body\` TEXT NOT NULL,
|
||||
\`created_at\` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (\`id\`),
|
||||
INDEX \`mod_action_notes_mod_action_id_index\` (\`case_id\`),
|
||||
INDEX \`mod_action_notes_mod_id_index\` (\`mod_id\`),
|
||||
INDEX \`mod_action_notes_created_at_index\` (\`created_at\`)
|
||||
)
|
||||
COLLATE = 'utf8mb4_general_ci'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`mutes\` (
|
||||
\`guild_id\` BIGINT(20) UNSIGNED NOT NULL,
|
||||
\`user_id\` BIGINT(20) UNSIGNED NOT NULL,
|
||||
\`created_at\` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`expires_at\` DATETIME NULL DEFAULT NULL,
|
||||
\`case_id\` INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||
PRIMARY KEY (\`guild_id\`, \`user_id\`),
|
||||
INDEX \`mutes_expires_at_index\` (\`expires_at\`),
|
||||
INDEX \`mutes_case_id_foreign\` (\`case_id\`),
|
||||
CONSTRAINT \`mutes_case_id_foreign\` FOREIGN KEY (\`case_id\`) REFERENCES \`cases\` (\`id\`)
|
||||
ON DELETE SET NULL
|
||||
)
|
||||
COLLATE = 'utf8mb4_general_ci'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`persisted_data\` (
|
||||
\`guild_id\` VARCHAR(20) NOT NULL,
|
||||
\`user_id\` VARCHAR(20) NOT NULL,
|
||||
\`roles\` VARCHAR(1024) NULL DEFAULT NULL,
|
||||
\`nickname\` VARCHAR(255) NULL DEFAULT NULL,
|
||||
\`is_voice_muted\` INT(11) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (\`guild_id\`, \`user_id\`)
|
||||
)
|
||||
COLLATE = 'utf8mb4_general_ci'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`reaction_roles\` (
|
||||
\`guild_id\` VARCHAR(20) NOT NULL,
|
||||
\`channel_id\` VARCHAR(20) NOT NULL,
|
||||
\`message_id\` VARCHAR(20) NOT NULL,
|
||||
\`emoji\` VARCHAR(20) NOT NULL,
|
||||
\`role_id\` VARCHAR(20) NOT NULL,
|
||||
PRIMARY KEY (\`guild_id\`, \`channel_id\`, \`message_id\`, \`emoji\`),
|
||||
INDEX \`reaction_roles_message_id_emoji_index\` (\`message_id\`, \`emoji\`)
|
||||
)
|
||||
COLLATE = 'utf8mb4_general_ci'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`tags\` (
|
||||
\`guild_id\` BIGINT(20) UNSIGNED NOT NULL,
|
||||
\`tag\` VARCHAR(64) NOT NULL,
|
||||
\`user_id\` BIGINT(20) UNSIGNED NOT NULL,
|
||||
\`body\` TEXT NOT NULL,
|
||||
\`created_at\` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (\`guild_id\`, \`tag\`)
|
||||
)
|
||||
COLLATE = 'utf8mb4_general_ci'
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
// No down function since we're migrating (hehe) from another migration system (knex)
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
exports.up = async function(knex) {
|
||||
if (! await knex.schema.hasTable('mod_actions')) {
|
||||
await knex.schema.createTable('mod_actions', table => {
|
||||
table.increments('id');
|
||||
table.bigInteger('guild_id').unsigned().notNullable();
|
||||
table.integer('case_number').unsigned().notNullable();
|
||||
table.bigInteger('user_id').index().unsigned().notNullable();
|
||||
table.string('user_name', 128).notNullable();
|
||||
table.bigInteger('mod_id').index().unsigned().nullable().defaultTo(null);
|
||||
table.string('mod_name', 128).nullable().defaultTo(null);
|
||||
table.integer('action_type').unsigned().notNullable();
|
||||
table.bigInteger('audit_log_id').unique().nullable().defaultTo(null);
|
||||
table.dateTime('created_at').index().defaultTo(knex.raw('NOW()')).notNullable();
|
||||
|
||||
table.unique(['guild_id', 'case_number']);
|
||||
});
|
||||
}
|
||||
|
||||
if (! await knex.schema.hasTable('mod_action_notes')) {
|
||||
await knex.schema.createTable('mod_action_notes', table => {
|
||||
table.increments('id');
|
||||
table.integer('mod_action_id').unsigned().notNullable().index().references('id').inTable('mod_actions').onDelete('CASCADE');
|
||||
table.bigInteger('mod_id').index().unsigned().nullable().defaultTo(null);
|
||||
table.string('mod_name', 128).nullable().defaultTo(null);
|
||||
table.text('body').notNullable();
|
||||
table.dateTime('created_at').index().defaultTo(knex.raw('NOW()')).notNullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
await knex.schema.dropTableIfExists('mod_action_notes');
|
||||
await knex.schema.dropTableIfExists('mod_actions');
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
exports.up = async function(knex) {
|
||||
if (! await knex.schema.hasTable('mutes')) {
|
||||
await knex.schema.createTable('mutes', table => {
|
||||
table.bigInteger('guild_id').unsigned().notNullable();
|
||||
table.bigInteger('user_id').unsigned().notNullable();
|
||||
table.dateTime('created_at').defaultTo(knex.raw('NOW()'));
|
||||
table.dateTime('expires_at').nullable().defaultTo(null);
|
||||
|
||||
table.primary(['guild_id', 'user_id']);
|
||||
table.index(['expires_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
await knex.schema.dropTableIfExists('mutes');
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
exports.up = async function(knex) {
|
||||
await knex.schema.renameTable('mod_actions', 'cases');
|
||||
await knex.schema.renameTable('mod_action_notes', 'case_notes');
|
||||
await knex.schema.table('cases', table => {
|
||||
table.renameColumn('action_type', 'type');
|
||||
});
|
||||
await knex.schema.table('case_notes', table => {
|
||||
table.renameColumn('mod_action_id', 'case_id');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
await knex.schema.table('cases', table => {
|
||||
table.renameColumn('type', 'action_type');
|
||||
});
|
||||
await knex.schema.table('case_notes', table => {
|
||||
table.renameColumn('case_id', 'mod_action_id');
|
||||
});
|
||||
await knex.schema.renameTable('cases', 'mod_actions');
|
||||
await knex.schema.renameTable('case_notes', 'mod_action_notes');
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
exports.up = async function(knex, Promise) {
|
||||
if (! await knex.schema.hasTable('reaction_roles')) {
|
||||
await knex.schema.createTable('reaction_roles', table => {
|
||||
table.string('guild_id', 20).notNullable();
|
||||
table.string('channel_id', 20).notNullable();
|
||||
table.string('message_id', 20).notNullable();
|
||||
table.string('emoji', 20).notNullable();
|
||||
table.string('role_id', 20).notNullable();
|
||||
|
||||
table.primary(['guild_id', 'channel_id', 'message_id', 'emoji']);
|
||||
table.index(['message_id', 'emoji']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function(knex, Promise) {
|
||||
await knex.schema.dropTableIfExists('reaction_roles');
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
exports.up = async function(knex, Promise) {
|
||||
if (! await knex.schema.hasTable('persisted_data')) {
|
||||
await knex.schema.createTable('persisted_data', table => {
|
||||
table.string('guild_id', 20).notNullable();
|
||||
table.string('user_id', 20).notNullable();
|
||||
table.string('roles', 1024).nullable().defaultTo(null);
|
||||
table.string('nickname', 255).nullable().defaultTo(null);
|
||||
table.integer('is_voice_muted').notNullable().defaultTo(0);
|
||||
|
||||
table.primary(['guild_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function(knex, Promise) {
|
||||
await knex.schema.dropTableIfExists('persisted_data');
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
exports.up = async function(knex, Promise) {
|
||||
if (! await knex.schema.hasTable('spam_logs')) {
|
||||
await knex.schema.createTable('spam_logs', table => {
|
||||
table.string('id', 36).notNullable().primary();
|
||||
table.string('guild_id', 20).notNullable();
|
||||
table.text('body', 'mediumtext').notNullable();
|
||||
table.dateTime('created_at').defaultTo(knex.raw('NOW()')).notNullable();
|
||||
table.dateTime('expires_at').nullable();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function(knex, Promise) {
|
||||
await knex.schema.dropTableIfExists('spam_logs');
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
exports.up = async function(knex, Promise) {
|
||||
await knex.schema.table('mutes', table => {
|
||||
table.integer('case_id').unsigned().nullable().defaultTo(null).after('user_id').references('id').inTable('cases').onDelete('SET NULL');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = async function(knex, Promise) {
|
||||
await knex.schema.table('mutes', table => {
|
||||
table.dropForeign('case_id');
|
||||
table.dropColumn('case_id');
|
||||
});
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
exports.up = async function(knex) {
|
||||
if (! await knex.schema.hasTable('tags')) {
|
||||
await knex.schema.createTable('tags', table => {
|
||||
table.bigInteger('guild_id').unsigned().notNullable();
|
||||
table.string('tag', 64).notNullable();
|
||||
table.bigInteger('user_id').unsigned().notNullable();
|
||||
table.text('body').notNullable();
|
||||
table.dateTime('created_at').defaultTo(knex.raw('NOW()'));
|
||||
|
||||
table.primary(['guild_id', 'tag']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
await knex.schema.dropTableIfExists('tags');
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
exports.up = async function(knex) {
|
||||
await knex.schema.renameTable('spam_logs', 'archives');
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
await knex.schema.renameTable('archives', 'spam_logs');
|
||||
};
|
38
ormconfig.js
Normal file
38
ormconfig.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
require('dotenv').config();
|
||||
|
||||
const moment = require('moment-timezone');
|
||||
moment.tz.setDefault('UTC');
|
||||
|
||||
module.exports = {
|
||||
type: "mysql",
|
||||
host: process.env.DB_HOST,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
charset: 'utf8mb4',
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: true,
|
||||
dateStrings: true,
|
||||
synchronize: false,
|
||||
|
||||
// Entities
|
||||
entities: [`${__dirname}/src/data/entities/*.ts`],
|
||||
|
||||
// Pool options
|
||||
extra: {
|
||||
typeCast(field, next) {
|
||||
if (field.type === 'DATETIME') {
|
||||
const val = field.string();
|
||||
return val != null ? moment(val).format('YYYY-MM-DD HH:mm:ss') : null;
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
},
|
||||
|
||||
// Migrations
|
||||
migrations: ["migrations/*.ts"],
|
||||
cli: {
|
||||
migrationsDir: "migrations"
|
||||
},
|
||||
};
|
1231
package-lock.json
generated
1231
package-lock.json
generated
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
@ -10,8 +10,7 @@
|
|||
"precommit": "lint-staged",
|
||||
"postcommit": "git update-index --again",
|
||||
"format": "prettier --write \"./**/*.ts\"",
|
||||
"db-migrate": "knex migrate:latest",
|
||||
"db-rollback": "knex migrate:rollback"
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
|
@ -23,34 +22,35 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/knex": "0.0.64",
|
||||
"@types/lodash.at": "^4.6.3",
|
||||
"@types/moment-timezone": "^0.5.6",
|
||||
"@types/node": "^8.0.50",
|
||||
"dotenv": "^4.0.0",
|
||||
"emoji-regex": "^7.0.0",
|
||||
"eris": "^0.8.6",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"humanize-duration": "^3.15.0",
|
||||
"knex": "0.12.6",
|
||||
"knub": "^10.1.0",
|
||||
"js-yaml": "^3.12.0",
|
||||
"knub": "^12.1.1",
|
||||
"lodash.at": "^4.6.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.intersection": "^4.4.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"moment-timezone": "^0.5.21",
|
||||
"mysql2": "^1.6.0",
|
||||
"mysql": "^2.16.0",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
"tlds": "^1.203.1",
|
||||
"ts-node": "^3.3.0",
|
||||
"typescript": "^2.9.2",
|
||||
"typeorm": "^0.2.8",
|
||||
"typescript": "^3.1.3",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.17.5",
|
||||
"lint-staged": "^7.2.0",
|
||||
"prettier": "^1.8.2",
|
||||
"@types/node": "^10.12.0",
|
||||
"husky": "^0.14.3",
|
||||
"lint-staged": "^7.2.0",
|
||||
"nodemon": "^1.17.5",
|
||||
"prettier": "^1.8.2",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-config-prettier": "^1.6.0"
|
||||
}
|
||||
|
|
13
src/SimpleError.ts
Normal file
13
src/SimpleError.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import util from "util";
|
||||
|
||||
export class SimpleError {
|
||||
public message: string;
|
||||
|
||||
constructor(message: string) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
[util.inspect.custom](depth, options) {
|
||||
return `Error: ${this.message}`;
|
||||
}
|
||||
}
|
50
src/data/BaseRepository.ts
Normal file
50
src/data/BaseRepository.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
export class BaseRepository {
|
||||
private static guildInstances: Map<string, any>;
|
||||
private nextRelations: string[];
|
||||
|
||||
protected guildId: string;
|
||||
|
||||
constructor(guildId: string) {
|
||||
this.guildId = guildId;
|
||||
this.nextRelations = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cached instance of the inheriting class for the specified guildId,
|
||||
* or creates a new instance if one doesn't exist yet
|
||||
*/
|
||||
public static getInstance<T extends typeof BaseRepository>(this: T, guildId: string): InstanceType<T> {
|
||||
if (!this.guildInstances) {
|
||||
this.guildInstances = new Map();
|
||||
}
|
||||
|
||||
if (!this.guildInstances.has(guildId)) {
|
||||
this.guildInstances.set(guildId, new this(guildId));
|
||||
}
|
||||
|
||||
return this.guildInstances.get(guildId) as InstanceType<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primes the specified relation(s) to be used in the next database operation.
|
||||
* Can be chained.
|
||||
*/
|
||||
public with(relations: string | string[]): this {
|
||||
if (Array.isArray(relations)) {
|
||||
this.nextRelations.push(...relations);
|
||||
} else {
|
||||
this.nextRelations.push(relations);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and resets the relations primed using with()
|
||||
*/
|
||||
protected getRelations(): string[] {
|
||||
const relations = this.nextRelations || [];
|
||||
this.nextRelations = [];
|
||||
return relations;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { CaseType } from "./CaseType";
|
||||
import { CaseTypes } from "./CaseTypes";
|
||||
|
||||
export const CaseTypeColors = {
|
||||
[CaseType.Note]: 0x3498db,
|
||||
[CaseType.Warn]: 0xdae622,
|
||||
[CaseType.Mute]: 0xe6b122,
|
||||
[CaseType.Unmute]: 0xa175b3,
|
||||
[CaseType.Kick]: 0xe67e22,
|
||||
[CaseType.Softban]: 0xe67e22,
|
||||
[CaseType.Ban]: 0xcb4314,
|
||||
[CaseType.Unban]: 0x9b59b6
|
||||
[CaseTypes.Note]: 0x3498db,
|
||||
[CaseTypes.Warn]: 0xdae622,
|
||||
[CaseTypes.Mute]: 0xe6b122,
|
||||
[CaseTypes.Unmute]: 0xa175b3,
|
||||
[CaseTypes.Kick]: 0xe67e22,
|
||||
[CaseTypes.Softban]: 0xe67e22,
|
||||
[CaseTypes.Ban]: 0xcb4314,
|
||||
[CaseTypes.Unban]: 0x9b59b6
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export enum CaseType {
|
||||
export enum CaseTypes {
|
||||
Ban = 1,
|
||||
Unban,
|
||||
Note,
|
|
@ -1,51 +1,52 @@
|
|||
import uuid from "uuid/v4"; // tslint:disable-line
|
||||
import moment from "moment-timezone";
|
||||
import knex from "../knex";
|
||||
import SpamLog from "../models/SpamLog";
|
||||
import { ArchiveEntry } from "./entities/ArchiveEntry";
|
||||
import { getRepository, Repository } from "typeorm";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
|
||||
const DEFAULT_EXPIRY_DAYS = 30;
|
||||
|
||||
function deleteExpiredArchives() {
|
||||
knex("archives")
|
||||
.where("expires_at", "<=", knex.raw("NOW()"))
|
||||
.delete();
|
||||
}
|
||||
|
||||
deleteExpiredArchives();
|
||||
setInterval(deleteExpiredArchives, 1000 * 60 * 60); // Clean expired archives every hour
|
||||
|
||||
export class GuildArchives {
|
||||
protected guildId: string;
|
||||
export class GuildArchives extends BaseRepository {
|
||||
protected archives: Repository<ArchiveEntry>;
|
||||
|
||||
constructor(guildId) {
|
||||
this.guildId = guildId;
|
||||
super(guildId);
|
||||
this.archives = getRepository(ArchiveEntry);
|
||||
|
||||
// Clean expired archives at start and then every hour
|
||||
this.deleteExpiredArchives();
|
||||
setInterval(() => this.deleteExpiredArchives(), 1000 * 60 * 60);
|
||||
}
|
||||
|
||||
generateNewLogId() {
|
||||
return uuid();
|
||||
private deleteExpiredArchives() {
|
||||
this.archives
|
||||
.createQueryBuilder()
|
||||
.where("expires_at <= NOW()")
|
||||
.delete()
|
||||
.execute();
|
||||
}
|
||||
|
||||
async find(id: string): Promise<SpamLog> {
|
||||
const result = await knex("archives")
|
||||
.where("id", id)
|
||||
.first();
|
||||
|
||||
return result ? new SpamLog(result) : null;
|
||||
async find(id: string): Promise<ArchiveEntry> {
|
||||
return this.archives.findOne({
|
||||
where: { id },
|
||||
relations: this.getRelations()
|
||||
});
|
||||
}
|
||||
|
||||
async create(body: string, expiresAt: moment.Moment = null) {
|
||||
const id = this.generateNewLogId();
|
||||
/**
|
||||
* @returns ID of the created entry
|
||||
*/
|
||||
async create(body: string, expiresAt: moment.Moment = null): Promise<string> {
|
||||
if (!expiresAt) {
|
||||
expiresAt = moment().add(DEFAULT_EXPIRY_DAYS, "days");
|
||||
}
|
||||
|
||||
await knex("archives").insert({
|
||||
id,
|
||||
const result = await this.archives.insert({
|
||||
guild_id: this.guildId,
|
||||
body,
|
||||
expires_at: expiresAt.format("YYYY-MM-DD HH:mm:ss")
|
||||
});
|
||||
|
||||
return id;
|
||||
return result.identifiers[0].id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,93 +1,78 @@
|
|||
import knex from "../knex";
|
||||
import Case from "../models/Case";
|
||||
import CaseNote from "../models/CaseNote";
|
||||
import { Case } from "./entities/Case";
|
||||
import { CaseNote } from "./entities/CaseNote";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
import { getRepository, In, Repository } from "typeorm";
|
||||
|
||||
export class GuildCases {
|
||||
protected guildId: string;
|
||||
export class GuildCases extends BaseRepository {
|
||||
private cases: Repository<Case>;
|
||||
private caseNotes: Repository<CaseNote>;
|
||||
|
||||
constructor(guildId) {
|
||||
this.guildId = guildId;
|
||||
super(guildId);
|
||||
this.cases = getRepository(Case);
|
||||
this.caseNotes = getRepository(CaseNote);
|
||||
}
|
||||
|
||||
async get(ids: number[]): Promise<Case[]> {
|
||||
const result = await knex("cases")
|
||||
.whereIn("id", ids)
|
||||
.select();
|
||||
|
||||
return result.map(r => new Case(r));
|
||||
return this.cases.find({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
id: In(ids)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async find(id: number): Promise<Case> {
|
||||
const result = await knex("cases")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("id", id)
|
||||
.first();
|
||||
|
||||
return result ? new Case(result) : null;
|
||||
return this.cases.findOne({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async findByCaseNumber(caseNumber: number): Promise<Case> {
|
||||
const result = await knex("cases")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("case_number", caseNumber)
|
||||
.first();
|
||||
|
||||
return result ? new Case(result) : null;
|
||||
return this.cases.findOne({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
case_number: caseNumber
|
||||
}
|
||||
|
||||
async getCaseNotes(caseId: number): Promise<CaseNote[]> {
|
||||
const results = await knex("case_notes")
|
||||
.where("case_id", caseId)
|
||||
.select();
|
||||
|
||||
return results.map(r => new CaseNote(r));
|
||||
});
|
||||
}
|
||||
|
||||
async getByUserId(userId: string): Promise<Case[]> {
|
||||
const results = await knex("cases")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.select();
|
||||
|
||||
return results.map(r => new Case(r));
|
||||
return this.cases.find({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
}
|
||||
|
||||
async findFirstCaseNote(caseId: number): Promise<CaseNote> {
|
||||
const result = await knex("case_notes")
|
||||
.where("case_id", caseId)
|
||||
.first();
|
||||
|
||||
return result ? new CaseNote(result) : null;
|
||||
});
|
||||
}
|
||||
|
||||
async create(data): Promise<number> {
|
||||
return knex
|
||||
.insert({
|
||||
const result = await this.cases.insert({
|
||||
...data,
|
||||
guild_id: this.guildId,
|
||||
case_number: knex.raw(
|
||||
"(SELECT IFNULL(MAX(case_number)+1, 1) FROM cases AS ma2 WHERE guild_id = ?)",
|
||||
this.guildId
|
||||
)
|
||||
})
|
||||
.returning("id")
|
||||
.into("cases")
|
||||
.then(ids => Number(ids[0]));
|
||||
case_number: () => `(SELECT IFNULL(MAX(case_number)+1, 1) FROM cases AS ma2 WHERE guild_id = ${this.guildId})`
|
||||
});
|
||||
|
||||
return result.identifiers[0].id;
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
return knex("cases")
|
||||
.where("id", id)
|
||||
.update(data);
|
||||
return this.cases.update(id, data);
|
||||
}
|
||||
|
||||
createNote(caseId: number, data: any) {
|
||||
return knex
|
||||
.insert({
|
||||
async createNote(caseId: number, data: any): Promise<number> {
|
||||
const result = await this.caseNotes.insert({
|
||||
...data,
|
||||
case_id: caseId
|
||||
})
|
||||
.into("case_notes")
|
||||
.return();
|
||||
});
|
||||
|
||||
return result.identifiers[0].id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
import knex from "../knex";
|
||||
import moment from "moment-timezone";
|
||||
import Mute from "../models/Mute";
|
||||
import { Mute } from "./entities/Mute";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
import { getRepository, Repository, Brackets } from "typeorm";
|
||||
|
||||
export class GuildMutes {
|
||||
protected guildId: string;
|
||||
export class GuildMutes extends BaseRepository {
|
||||
private mutes: Repository<Mute>;
|
||||
|
||||
constructor(guildId) {
|
||||
this.guildId = guildId;
|
||||
super(guildId);
|
||||
this.mutes = getRepository(Mute);
|
||||
}
|
||||
|
||||
async getExpiredMutes(): Promise<Mute[]> {
|
||||
const result = await knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.whereNotNull("expires_at")
|
||||
.whereRaw("expires_at <= NOW()")
|
||||
.select();
|
||||
|
||||
return result.map(r => new Mute(r));
|
||||
return this.mutes
|
||||
.createQueryBuilder("mutes")
|
||||
.where("guild_id = :guild_id", { guild_id: this.guildId })
|
||||
.where("expires_at IS NOT NULL")
|
||||
.where("expires_at <= NOW()")
|
||||
.getMany();
|
||||
}
|
||||
|
||||
async findExistingMuteForUserId(userId: string): Promise<Mute> {
|
||||
const result = await knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.first();
|
||||
|
||||
return result ? new Mute(result) : null;
|
||||
return this.mutes.findOne({
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async addMute(userId, expiryTime) {
|
||||
|
@ -35,13 +36,11 @@ export class GuildMutes {
|
|||
.format("YYYY-MM-DD HH:mm:ss")
|
||||
: null;
|
||||
|
||||
return knex
|
||||
.insert({
|
||||
return this.mutes.insert({
|
||||
guild_id: this.guildId,
|
||||
user_id: userId,
|
||||
expires_at: expiresAt
|
||||
})
|
||||
.into("mutes");
|
||||
});
|
||||
}
|
||||
|
||||
async updateExpiryTime(userId, newExpiryTime) {
|
||||
|
@ -51,12 +50,15 @@ export class GuildMutes {
|
|||
.format("YYYY-MM-DD HH:mm:ss")
|
||||
: null;
|
||||
|
||||
return knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.update({
|
||||
return this.mutes.update(
|
||||
{
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
},
|
||||
{
|
||||
expires_at: expiresAt
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async addOrUpdateMute(userId, expiryTime) {
|
||||
|
@ -70,25 +72,33 @@ export class GuildMutes {
|
|||
}
|
||||
|
||||
async getActiveMutes(): Promise<Mute[]> {
|
||||
const result = await knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.where(q => q.whereRaw("expires_at > NOW()").orWhereNull("expires_at"))
|
||||
.select();
|
||||
|
||||
return result.map(r => new Mute(r));
|
||||
return this.mutes
|
||||
.createQueryBuilder("mutes")
|
||||
.where("guild_id = :guild_id", { guild_id: this.guildId })
|
||||
.andWhere(
|
||||
new Brackets(qb => {
|
||||
qb.where("expires_at > NOW()").orWhere("expires_at IS NULL");
|
||||
})
|
||||
)
|
||||
.getMany();
|
||||
}
|
||||
|
||||
async setCaseId(userId, caseId) {
|
||||
await knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.update({ case_id: caseId });
|
||||
await this.mutes.update(
|
||||
{
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
},
|
||||
{
|
||||
case_id: caseId
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async clear(userId) {
|
||||
return knex("mutes")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.delete();
|
||||
await this.mutes.delete({
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import knex from "../knex";
|
||||
import PersistedData from "../models/PersistedData";
|
||||
import { PersistedData } from "./entities/PersistedData";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
import { getRepository, Repository } from "typeorm";
|
||||
|
||||
export interface IPartialPersistData {
|
||||
roles?: string[];
|
||||
|
@ -7,20 +8,21 @@ export interface IPartialPersistData {
|
|||
is_voice_muted?: boolean;
|
||||
}
|
||||
|
||||
export class GuildPersistedData {
|
||||
protected guildId: string;
|
||||
export class GuildPersistedData extends BaseRepository {
|
||||
private persistedData: Repository<PersistedData>;
|
||||
|
||||
constructor(guildId) {
|
||||
this.guildId = guildId;
|
||||
super(guildId);
|
||||
this.persistedData = getRepository(PersistedData);
|
||||
}
|
||||
|
||||
async find(userId: string) {
|
||||
const result = await knex("persisted_data")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.first();
|
||||
|
||||
return result ? new PersistedData(result) : null;
|
||||
return this.persistedData.findOne({
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async set(userId: string, data: IPartialPersistData = {}) {
|
||||
|
@ -31,12 +33,15 @@ export class GuildPersistedData {
|
|||
|
||||
const existing = await this.find(userId);
|
||||
if (existing) {
|
||||
await knex("persisted_data")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.update(finalData);
|
||||
await this.persistedData.update(
|
||||
{
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
},
|
||||
finalData
|
||||
);
|
||||
} else {
|
||||
await knex("persisted_data").insert({
|
||||
await this.persistedData.insert({
|
||||
...finalData,
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
|
@ -45,9 +50,9 @@ export class GuildPersistedData {
|
|||
}
|
||||
|
||||
async clear(userId: string) {
|
||||
await knex("persisted_data")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("user_id", userId)
|
||||
.delete();
|
||||
await this.persistedData.delete({
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,57 @@
|
|||
import knex from "../knex";
|
||||
import ReactionRole from "../models/ReactionRole";
|
||||
import { ReactionRole } from "./entities/ReactionRole";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
import { getRepository, Repository } from "typeorm";
|
||||
|
||||
export class GuildReactionRoles {
|
||||
protected guildId: string;
|
||||
export class GuildReactionRoles extends BaseRepository {
|
||||
private reactionRoles: Repository<ReactionRole>;
|
||||
|
||||
constructor(guildId) {
|
||||
this.guildId = guildId;
|
||||
super(guildId);
|
||||
this.reactionRoles = getRepository(ReactionRole);
|
||||
}
|
||||
|
||||
async all(): Promise<ReactionRole[]> {
|
||||
const results = await knex("reaction_roles")
|
||||
.where("guild_id", this.guildId)
|
||||
.select();
|
||||
|
||||
return results.map(r => new ReactionRole(r));
|
||||
return this.reactionRoles.find({
|
||||
where: {
|
||||
guild_id: this.guildId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getForMessage(messageId: string): Promise<ReactionRole[]> {
|
||||
const results = await knex("reaction_roles")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("message_id", messageId)
|
||||
.select();
|
||||
|
||||
return results.map(r => new ReactionRole(r));
|
||||
return this.reactionRoles.find({
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
message_id: messageId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getByMessageAndEmoji(messageId: string, emoji: string): Promise<ReactionRole> {
|
||||
const result = await knex("reaction_roles")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("message_id", messageId)
|
||||
.where("emoji", emoji)
|
||||
.first();
|
||||
|
||||
return result ? new ReactionRole(result) : null;
|
||||
return this.reactionRoles.findOne({
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
message_id: messageId,
|
||||
emoji
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async removeFromMessage(messageId: string, emoji: string = null) {
|
||||
let query = knex("reaction_roles")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("message_id", messageId);
|
||||
const criteria: any = {
|
||||
guild_id: this.guildId,
|
||||
message_id: messageId
|
||||
};
|
||||
|
||||
if (emoji) {
|
||||
query = query.where("emoji", emoji);
|
||||
criteria.emoji = emoji;
|
||||
}
|
||||
|
||||
await query.delete();
|
||||
await this.reactionRoles.delete(criteria);
|
||||
}
|
||||
|
||||
async add(channelId: string, messageId: string, emoji: string, roleId: string) {
|
||||
await knex("reaction_roles").insert({
|
||||
await this.reactionRoles.insert({
|
||||
guild_id: this.guildId,
|
||||
channel_id: channelId,
|
||||
message_id: messageId,
|
||||
|
|
|
@ -1,36 +1,40 @@
|
|||
import knex from "../knex";
|
||||
import moment from "moment-timezone";
|
||||
import Tag from "../models/Tag";
|
||||
import { Tag } from "./entities/Tag";
|
||||
import { getRepository, Repository } from "typeorm";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
|
||||
export class GuildTags {
|
||||
protected guildId: string;
|
||||
export class GuildTags extends BaseRepository {
|
||||
private tags: Repository<Tag>;
|
||||
|
||||
constructor(guildId) {
|
||||
this.guildId = guildId;
|
||||
super(guildId);
|
||||
this.tags = getRepository(Tag);
|
||||
}
|
||||
|
||||
async find(tag): Promise<Tag> {
|
||||
const result = await knex("tags")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("tag", tag)
|
||||
.first();
|
||||
|
||||
return result ? new Tag(result) : null;
|
||||
return this.tags.findOne({
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async createOrUpdate(tag, body, userId) {
|
||||
const existingTag = await this.find(tag);
|
||||
if (existingTag) {
|
||||
await knex("tags")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("tag", tag)
|
||||
.update({
|
||||
await this.tags
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.set({
|
||||
body,
|
||||
user_id: userId,
|
||||
created_at: knex.raw("NOW()")
|
||||
});
|
||||
created_at: () => "NOW()"
|
||||
})
|
||||
.where("guild_id = :guildId", { guildId: this.guildId })
|
||||
.where("tag = :tag", { tag })
|
||||
.execute();
|
||||
} else {
|
||||
await knex("tags").insert({
|
||||
await this.tags.insert({
|
||||
guild_id: this.guildId,
|
||||
user_id: userId,
|
||||
tag,
|
||||
|
@ -40,9 +44,9 @@ export class GuildTags {
|
|||
}
|
||||
|
||||
async delete(tag) {
|
||||
await knex("tags")
|
||||
.where("guild_id", this.guildId)
|
||||
.where("tag", tag)
|
||||
.delete();
|
||||
await this.tags.delete({
|
||||
guild_id: this.guildId,
|
||||
tag
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
23
src/data/db.ts
Normal file
23
src/data/db.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { SimpleError } from "../SimpleError";
|
||||
import { Connection, createConnection } from "typeorm";
|
||||
|
||||
let connectionPromise: Promise<Connection>;
|
||||
|
||||
export let connection: Connection;
|
||||
|
||||
export function connect() {
|
||||
if (!connectionPromise) {
|
||||
connectionPromise = createConnection().then(newConnection => {
|
||||
return newConnection.query("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP) AS tz").then(r => {
|
||||
if (r[0].tz !== "00:00:00") {
|
||||
throw new SimpleError(`Database timezone must be UTC (detected ${r[0].tz})`);
|
||||
}
|
||||
|
||||
connection = newConnection;
|
||||
return newConnection;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return connectionPromise;
|
||||
}
|
16
src/data/entities/ArchiveEntry.ts
Normal file
16
src/data/entities/ArchiveEntry.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity("archives")
|
||||
export class ArchiveEntry {
|
||||
@Column()
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column() guild_id: string;
|
||||
|
||||
@Column() body: string;
|
||||
|
||||
@Column() created_at: string;
|
||||
|
||||
@Column() expires_at: string;
|
||||
}
|
28
src/data/entities/Case.ts
Normal file
28
src/data/entities/Case.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
|
||||
import { CaseNote } from "./CaseNote";
|
||||
|
||||
@Entity("cases")
|
||||
export class Case {
|
||||
@PrimaryGeneratedColumn() id: number;
|
||||
|
||||
@Column() guild_id: string;
|
||||
|
||||
@Column() case_number: number;
|
||||
|
||||
@Column() user_id: string;
|
||||
|
||||
@Column() user_name: string;
|
||||
|
||||
@Column() mod_id: string;
|
||||
|
||||
@Column() mod_name: string;
|
||||
|
||||
@Column() type: number;
|
||||
|
||||
@Column() audit_log_id: string;
|
||||
|
||||
@Column() created_at: string;
|
||||
|
||||
@OneToMany(type => CaseNote, note => note.case)
|
||||
notes: CaseNote[];
|
||||
}
|
21
src/data/entities/CaseNote.ts
Normal file
21
src/data/entities/CaseNote.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from "typeorm";
|
||||
import { Case } from "./Case";
|
||||
|
||||
@Entity("case_notes")
|
||||
export class CaseNote {
|
||||
@PrimaryGeneratedColumn() id: number;
|
||||
|
||||
@Column() case_id: number;
|
||||
|
||||
@Column() mod_id: string;
|
||||
|
||||
@Column() mod_name: string;
|
||||
|
||||
@Column() body: string;
|
||||
|
||||
@Column() created_at: string;
|
||||
|
||||
@ManyToOne(type => Case, theCase => theCase.notes)
|
||||
@JoinColumn({ name: "case_id" })
|
||||
case: Case;
|
||||
}
|
18
src/data/entities/Mute.ts
Normal file
18
src/data/entities/Mute.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Entity, Column, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity("mutes")
|
||||
export class Mute {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
guild_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
user_id: string;
|
||||
|
||||
@Column() created_at: string;
|
||||
|
||||
@Column() expires_at: string;
|
||||
|
||||
@Column() case_id: number;
|
||||
}
|
18
src/data/entities/PersistedData.ts
Normal file
18
src/data/entities/PersistedData.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Entity, Column, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity("persisted_data")
|
||||
export class PersistedData {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
guild_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
user_id: string;
|
||||
|
||||
@Column() roles: string;
|
||||
|
||||
@Column() nickname: string;
|
||||
|
||||
@Column() is_voice_muted: number;
|
||||
}
|
22
src/data/entities/ReactionRole.ts
Normal file
22
src/data/entities/ReactionRole.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Entity, Column, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity("reaction_roles")
|
||||
export class ReactionRole {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
guild_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
channel_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
message_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
emoji: string;
|
||||
|
||||
@Column() role_id: string;
|
||||
}
|
18
src/data/entities/Tag.ts
Normal file
18
src/data/entities/Tag.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Entity, Column, PrimaryColumn, CreateDateColumn } from "typeorm";
|
||||
|
||||
@Entity("tags")
|
||||
export class Tag {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
guild_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
tag: string;
|
||||
|
||||
@Column() user_id: string;
|
||||
|
||||
@Column() body: string;
|
||||
|
||||
@Column() created_at: string;
|
||||
}
|
32
src/index.ts
32
src/index.ts
|
@ -1,3 +1,9 @@
|
|||
import path from "path";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
import _fs from "fs";
|
||||
const fs = _fs.promises;
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
process.on("unhandledRejection", (reason, p) => {
|
||||
|
@ -17,13 +23,12 @@ process.on("uncaughtException", err => {
|
|||
});
|
||||
|
||||
// Always use UTC
|
||||
// This is also set for the database in knexfile
|
||||
import moment from "moment-timezone";
|
||||
moment.tz.setDefault("UTC");
|
||||
|
||||
import { Client } from "eris";
|
||||
import { Knub, logger } from "knub";
|
||||
import knex from "./knex";
|
||||
import { connect } from "./data/db";
|
||||
|
||||
// Global plugins
|
||||
import { BotControlPlugin } from "./plugins/BotControl";
|
||||
|
@ -42,7 +47,9 @@ import { TagsPlugin } from "./plugins/Tags";
|
|||
|
||||
// Run latest database migrations
|
||||
logger.info("Running database migrations");
|
||||
knex.migrate.latest().then(() => {
|
||||
connect().then(async conn => {
|
||||
await conn.runMigrations();
|
||||
|
||||
const client = new Client(process.env.TOKEN, {
|
||||
getAllUsers: true
|
||||
});
|
||||
|
@ -72,6 +79,25 @@ knex.migrate.latest().then(() => {
|
|||
return keys.filter(pluginName => {
|
||||
return plugins[pluginName] && plugins[pluginName].enabled !== false;
|
||||
});
|
||||
},
|
||||
|
||||
async getConfig(id) {
|
||||
const configFile = id ? `${id}.yml` : "global.yml";
|
||||
const configPath = path.join("config", configFile);
|
||||
|
||||
try {
|
||||
await fs.access(configPath);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const yamlString = await fs.readFile(configPath, { encoding: "utf8" });
|
||||
return yaml.safeLoad(yamlString);
|
||||
},
|
||||
|
||||
logFn: (level, msg) => {
|
||||
if (level === "debug") return;
|
||||
console.log(`[${level.toUpperCase()}] ${msg}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
const knexfile = require("../knexfile");
|
||||
import knex from "knex";
|
||||
|
||||
const db = knex(knexfile);
|
||||
|
||||
export default db;
|
|
@ -1,14 +0,0 @@
|
|||
import Model from "./Model";
|
||||
|
||||
export default class Case extends Model {
|
||||
public id: number;
|
||||
public guild_id: string;
|
||||
public case_number: number;
|
||||
public user_id: string;
|
||||
public user_name: string;
|
||||
public mod_id: string;
|
||||
public mod_name: string;
|
||||
public type: number;
|
||||
public audit_log_id: string;
|
||||
public created_at: string;
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import Model from "./Model";
|
||||
|
||||
export default class CaseNote extends Model {
|
||||
public id: number;
|
||||
public case_id: number;
|
||||
public mod_id: string;
|
||||
public mod_name: string;
|
||||
public body: string;
|
||||
public created_at: string;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export default class Model {
|
||||
constructor(props) {
|
||||
for (const key in props) {
|
||||
this[key] = props[key];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import Model from "./Model";
|
||||
|
||||
export default class Mute extends Model {
|
||||
public guild_id: string;
|
||||
public user_id: string;
|
||||
public case_id: number;
|
||||
public created_at: string;
|
||||
public expires_at: string;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import Model from "./Model";
|
||||
|
||||
export default class PersistedData extends Model {
|
||||
private _roles;
|
||||
private _isVoiceMuted;
|
||||
|
||||
public guild_id: string;
|
||||
public user_id: string;
|
||||
public nickname: string;
|
||||
|
||||
set roles(v) {
|
||||
this._roles = v ? v.split(",") : [];
|
||||
}
|
||||
|
||||
get roles() {
|
||||
return this._roles;
|
||||
}
|
||||
|
||||
set is_voice_muted(v) {
|
||||
this._isVoiceMuted = v === 1;
|
||||
}
|
||||
|
||||
get is_voice_muted() {
|
||||
return this._isVoiceMuted;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import Model from "./Model";
|
||||
|
||||
export default class ReactionRole extends Model {
|
||||
public guild_id: string;
|
||||
public channel_id: string;
|
||||
public message_id: string;
|
||||
public emoji: string;
|
||||
public role_id: string;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import Model from "./Model";
|
||||
|
||||
export default class SpamLog extends Model {
|
||||
public id: string;
|
||||
public guild_id: string;
|
||||
public body: string;
|
||||
public created_at: string;
|
||||
public expires_at: string;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import Model from "./Model";
|
||||
|
||||
export default class Tag extends Model {
|
||||
public guild_id: string;
|
||||
public tag: string;
|
||||
public user_id: string;
|
||||
public body: string;
|
||||
public created_at: string;
|
||||
}
|
|
@ -11,14 +11,13 @@ import {
|
|||
errorMessage,
|
||||
findRelevantAuditLogEntry,
|
||||
formatTemplateString,
|
||||
sleep,
|
||||
stripObjectToScalars,
|
||||
successMessage,
|
||||
trimLines
|
||||
} from "../utils";
|
||||
import { GuildMutes } from "../data/GuildMutes";
|
||||
import Case from "../models/Case";
|
||||
import { CaseType } from "../data/CaseType";
|
||||
import { Case } from "../data/entities/Case";
|
||||
import { CaseTypes } from "../data/CaseTypes";
|
||||
import { GuildLogs } from "../data/GuildLogs";
|
||||
import { LogType } from "../data/LogType";
|
||||
import Timer = NodeJS.Timer;
|
||||
|
@ -47,8 +46,8 @@ export class ModActionsPlugin extends Plugin {
|
|||
protected ignoredEvents: IIgnoredEvent[];
|
||||
|
||||
async onLoad() {
|
||||
this.cases = new GuildCases(this.guildId);
|
||||
this.mutes = new GuildMutes(this.guildId);
|
||||
this.cases = GuildCases.getInstance(this.guildId);
|
||||
this.mutes = GuildMutes.getInstance(this.guildId);
|
||||
this.serverLogs = new GuildLogs(this.guildId);
|
||||
|
||||
this.ignoredEvents = [];
|
||||
|
@ -156,9 +155,9 @@ export class ModActionsPlugin extends Plugin {
|
|||
const modId = relevantAuditLogEntry.user.id;
|
||||
const auditLogId = relevantAuditLogEntry.id;
|
||||
|
||||
await this.createCase(user.id, modId, CaseType.Ban, auditLogId, relevantAuditLogEntry.reason, true);
|
||||
await this.createCase(user.id, modId, CaseTypes.Ban, auditLogId, relevantAuditLogEntry.reason, true);
|
||||
} else {
|
||||
await this.createCase(user.id, null, CaseType.Ban);
|
||||
await this.createCase(user.id, null, CaseTypes.Ban);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,9 +182,9 @@ export class ModActionsPlugin extends Plugin {
|
|||
const modId = relevantAuditLogEntry.user.id;
|
||||
const auditLogId = relevantAuditLogEntry.id;
|
||||
|
||||
await this.createCase(user.id, modId, CaseType.Unban, auditLogId, null, true);
|
||||
await this.createCase(user.id, modId, CaseTypes.Unban, auditLogId, null, true);
|
||||
} else {
|
||||
await this.createCase(user.id, null, CaseType.Unban);
|
||||
await this.createCase(user.id, null, CaseTypes.Unban);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,7 +227,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
this.createCase(
|
||||
member.id,
|
||||
kickAuditLogEntry.user.id,
|
||||
CaseType.Kick,
|
||||
CaseTypes.Kick,
|
||||
kickAuditLogEntry.id,
|
||||
kickAuditLogEntry.reason,
|
||||
true
|
||||
|
@ -274,12 +273,13 @@ export class ModActionsPlugin extends Plugin {
|
|||
const user = await this.bot.users.get(args.userId);
|
||||
const userName = user ? `${user.username}#${user.discriminator}` : "member";
|
||||
|
||||
await this.createCase(args.userId, msg.author.id, CaseType.Note, null, args.note);
|
||||
await this.createCase(args.userId, msg.author.id, CaseTypes.Note, null, args.note);
|
||||
msg.channel.createMessage(successMessage(`Note added on ${userName}`));
|
||||
}
|
||||
|
||||
@d.command("warn", "<member:Member> <reason:string$>")
|
||||
@d.permission("warn")
|
||||
@d.nonBlocking()
|
||||
async warnCmd(msg: Message, args: any) {
|
||||
// Make sure we're allowed to warn this member
|
||||
if (!this.canActOn(msg.member, args.member)) {
|
||||
|
@ -307,7 +307,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
await this.createCase(args.member.id, msg.author.id, CaseType.Warn, null, args.reason);
|
||||
await this.createCase(args.member.id, msg.author.id, CaseTypes.Warn, null, args.reason);
|
||||
|
||||
msg.channel.createMessage(
|
||||
successMessage(`Warned **${args.member.user.username}#${args.member.user.discriminator}**`)
|
||||
|
@ -363,7 +363,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
}
|
||||
} else {
|
||||
// Create a case
|
||||
const caseId = await this.createCase(args.member.id, msg.author.id, CaseType.Mute, null, args.reason);
|
||||
const caseId = await this.createCase(args.member.id, msg.author.id, CaseTypes.Mute, null, args.reason);
|
||||
await this.mutes.setCaseId(args.member.id, caseId);
|
||||
}
|
||||
|
||||
|
@ -458,7 +458,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
}
|
||||
|
||||
// Create a case
|
||||
await this.createCase(args.member.id, msg.author.id, CaseType.Unmute, null, args.reason);
|
||||
await this.createCase(args.member.id, msg.author.id, CaseTypes.Unmute, null, args.reason);
|
||||
|
||||
// Log the action
|
||||
this.serverLogs.log(LogType.MEMBER_UNMUTE, {
|
||||
|
@ -568,7 +568,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
args.member.kick(args.reason);
|
||||
|
||||
// Create a case for this action
|
||||
await this.createCase(args.member.id, msg.author.id, CaseType.Kick, null, args.reason);
|
||||
await this.createCase(args.member.id, msg.author.id, CaseTypes.Kick, null, args.reason);
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = `Kicked **${args.member.user.username}#${args.member.user.discriminator}**`;
|
||||
|
@ -613,7 +613,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
args.member.ban(1, args.reason);
|
||||
|
||||
// Create a case for this action
|
||||
await this.createCase(args.member.id, msg.author.id, CaseType.Ban, null, args.reason);
|
||||
await this.createCase(args.member.id, msg.author.id, CaseTypes.Ban, null, args.reason);
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = `Banned **${args.member.user.username}#${args.member.user.discriminator}**`;
|
||||
|
@ -646,7 +646,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
await this.guild.unbanMember(args.member.id);
|
||||
|
||||
// Create a case for this action
|
||||
await this.createCase(args.member.id, msg.author.id, CaseType.Softban, null, args.reason);
|
||||
await this.createCase(args.member.id, msg.author.id, CaseTypes.Softban, null, args.reason);
|
||||
|
||||
// Confirm the action to the moderator
|
||||
msg.channel.createMessage(
|
||||
|
@ -677,7 +677,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
msg.channel.createMessage(successMessage("Member unbanned!"));
|
||||
|
||||
// Create a case
|
||||
this.createCase(args.userId, msg.author.id, CaseType.Unban, null, args.reason);
|
||||
this.createCase(args.userId, msg.author.id, CaseTypes.Unban, null, args.reason);
|
||||
|
||||
// Log the action
|
||||
this.serverLogs.log(LogType.MEMBER_UNBAN, {
|
||||
|
@ -710,7 +710,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
msg.channel.createMessage(successMessage("Member forcebanned!"));
|
||||
|
||||
// Create a case
|
||||
this.createCase(args.userId, msg.author.id, CaseType.Ban, null, args.reason);
|
||||
this.createCase(args.userId, msg.author.id, CaseTypes.Ban, null, args.reason);
|
||||
|
||||
// Log the action
|
||||
this.serverLogs.log(LogType.MEMBER_FORCEBAN, {
|
||||
|
@ -721,6 +721,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
|
||||
@d.command("massban", "<userIds:string...>")
|
||||
@d.permission("massban")
|
||||
@d.nonBlocking()
|
||||
async massbanCmd(msg: Message, args: { userIds: string[] }) {
|
||||
// Limit to 100 users at once (arbitrary?)
|
||||
if (args.userIds.length > 100) {
|
||||
|
@ -763,7 +764,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
for (const userId of args.userIds) {
|
||||
try {
|
||||
await this.guild.banMember(userId);
|
||||
await this.createCase(userId, msg.author.id, CaseType.Ban, null, `Mass ban: ${banReason}`, false, false);
|
||||
await this.createCase(userId, msg.author.id, CaseTypes.Ban, null, `Mass ban: ${banReason}`, false, false);
|
||||
} catch (e) {
|
||||
failedBans.push(userId);
|
||||
}
|
||||
|
@ -811,13 +812,13 @@ export class ModActionsPlugin extends Plugin {
|
|||
|
||||
// Verify the case type is valid
|
||||
const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase();
|
||||
if (!CaseType[type]) {
|
||||
if (!CaseTypes[type]) {
|
||||
msg.channel.createMessage(errorMessage("Cannot add case: invalid case type"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the case
|
||||
const caseId = await this.createCase(args.target, msg.author.id, CaseType[type], null, args.reason);
|
||||
const caseId = await this.createCase(args.target, msg.author.id, CaseTypes[type], null, args.reason);
|
||||
const theCase = await this.cases.find(caseId);
|
||||
|
||||
// Log the action
|
||||
|
@ -852,7 +853,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
@d.command(/cases|usercases/, "<userId:userId> [expanded:string]")
|
||||
@d.permission("view")
|
||||
async usercasesCmd(msg: Message, args: { userId: string; expanded?: string }) {
|
||||
const cases = await this.cases.getByUserId(args.userId);
|
||||
const cases = await this.cases.with("notes").getByUserId(args.userId);
|
||||
const user = this.bot.users.get(args.userId);
|
||||
const userName = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
|
||||
const prefix = this.knub.getGuildData(this.guildId).config.prefix;
|
||||
|
@ -869,7 +870,8 @@ export class ModActionsPlugin extends Plugin {
|
|||
// Compact view (= regular message with a preview of each case)
|
||||
const lines = [];
|
||||
for (const theCase of cases) {
|
||||
const firstNote = await this.cases.findFirstCaseNote(theCase.id);
|
||||
theCase.notes.sort((a, b) => (a.created_at > b.created_at ? 1 : -1));
|
||||
const firstNote = theCase.notes[0];
|
||||
let reason = firstNote ? firstNote.body : "";
|
||||
|
||||
if (reason.length > CASE_LIST_REASON_MAX_LENGTH) {
|
||||
|
@ -882,7 +884,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
|
||||
reason = disableLinkPreviews(reason);
|
||||
|
||||
lines.push(`Case \`#${theCase.case_number}\` __${CaseType[theCase.type]}__ ${reason}`);
|
||||
lines.push(`Case \`#${theCase.case_number}\` __${CaseTypes[theCase.type]}__ ${reason}`);
|
||||
}
|
||||
|
||||
const finalMessage = trimLines(`
|
||||
|
@ -945,7 +947,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
protected async displayCase(caseOrCaseId: Case | number, channelId: string) {
|
||||
let theCase: Case;
|
||||
if (typeof caseOrCaseId === "number") {
|
||||
theCase = await this.cases.find(caseOrCaseId);
|
||||
theCase = await this.cases.with("notes").find(caseOrCaseId);
|
||||
} else {
|
||||
theCase = caseOrCaseId;
|
||||
}
|
||||
|
@ -953,10 +955,8 @@ export class ModActionsPlugin extends Plugin {
|
|||
if (!theCase) return;
|
||||
if (!this.guild.channels.get(channelId)) return;
|
||||
|
||||
const notes = await this.cases.getCaseNotes(theCase.id);
|
||||
|
||||
const createdAt = moment(theCase.created_at);
|
||||
const actionTypeStr = CaseType[theCase.type].toUpperCase();
|
||||
const actionTypeStr = CaseTypes[theCase.type].toUpperCase();
|
||||
|
||||
const embed: any = {
|
||||
title: `${actionTypeStr} - Case #${theCase.case_number}`,
|
||||
|
@ -981,8 +981,8 @@ export class ModActionsPlugin extends Plugin {
|
|||
embed.color = CaseTypeColors[theCase.type];
|
||||
}
|
||||
|
||||
if (notes.length) {
|
||||
notes.forEach((note: any) => {
|
||||
if (theCase.notes.length) {
|
||||
theCase.notes.forEach((note: any) => {
|
||||
const noteDate = moment(note.created_at);
|
||||
embed.fields.push({
|
||||
name: `${note.mod_name} at ${noteDate.format("YYYY-MM-DD [at] HH:mm")}:`,
|
||||
|
@ -1014,7 +1014,7 @@ export class ModActionsPlugin extends Plugin {
|
|||
public async createCase(
|
||||
userId: string,
|
||||
modId: string,
|
||||
caseType: CaseType,
|
||||
caseType: CaseTypes,
|
||||
auditLogId: string = null,
|
||||
reason: string = null,
|
||||
automatic = false,
|
||||
|
|
|
@ -21,7 +21,7 @@ export class PersistPlugin extends Plugin {
|
|||
}
|
||||
|
||||
onLoad() {
|
||||
this.persistedData = new GuildPersistedData(this.guildId);
|
||||
this.persistedData = GuildPersistedData.getInstance(this.guildId);
|
||||
this.logs = new GuildLogs(this.guildId);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,20 +30,17 @@ export class ReactionRolesPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async onLoad() {
|
||||
this.reactionRoles = new GuildReactionRoles(this.guildId);
|
||||
this.reactionRoles = GuildReactionRoles.getInstance(this.guildId);
|
||||
return;
|
||||
|
||||
// Pre-fetch all messages with reaction roles so we get their events
|
||||
const reactionRoles = await this.reactionRoles.all();
|
||||
|
||||
const channelMessages: Map<string, Set<string>> = reactionRoles.reduce(
|
||||
(map: Map<string, Set<string>>, row) => {
|
||||
const channelMessages: Map<string, Set<string>> = reactionRoles.reduce((map: Map<string, Set<string>>, row) => {
|
||||
if (!map.has(row.channel_id)) map.set(row.channel_id, new Set());
|
||||
map.get(row.channel_id).add(row.message_id);
|
||||
return map;
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
}, new Map());
|
||||
|
||||
const msgLoadPromises = [];
|
||||
|
||||
|
@ -62,10 +59,7 @@ export class ReactionRolesPlugin extends Plugin {
|
|||
|
||||
@d.command("reaction_roles", "<channel:channel> <messageId:string> <reactionRolePairs:string$>")
|
||||
@d.permission("manage")
|
||||
async reactionRolesCmd(
|
||||
msg: Message,
|
||||
args: { channel: Channel; messageId: string; reactionRolePairs: string }
|
||||
) {
|
||||
async reactionRolesCmd(msg: Message, args: { channel: Channel; messageId: string; reactionRolePairs: string }) {
|
||||
if (!(args.channel instanceof TextChannel)) {
|
||||
msg.channel.createMessage(errorMessage("Channel must be a text channel!"));
|
||||
return;
|
||||
|
@ -100,9 +94,7 @@ export class ReactionRolesPlugin extends Plugin {
|
|||
// Verify the specified emojis and roles are valid
|
||||
for (const pair of newRolePairs) {
|
||||
if (isSnowflake(pair[0]) && !guildEmojiIds.includes(pair[0])) {
|
||||
msg.channel.createMessage(
|
||||
errorMessage("I can only use regular emojis and custom emojis from this server")
|
||||
);
|
||||
msg.channel.createMessage(errorMessage("I can only use regular emojis and custom emojis from this server"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -113,9 +105,7 @@ export class ReactionRolesPlugin extends Plugin {
|
|||
}
|
||||
|
||||
const oldReactionRoles = await this.reactionRoles.getForMessage(targetMessage.id);
|
||||
const oldRolePairs: ReactionRolePair[] = oldReactionRoles.map(
|
||||
r => [r.emoji, r.role_id] as ReactionRolePair
|
||||
);
|
||||
const oldRolePairs: ReactionRolePair[] = oldReactionRoles.map(r => [r.emoji, r.role_id] as ReactionRolePair);
|
||||
|
||||
// Remove old reaction/role pairs that weren't included in the new pairs or were changed in some way
|
||||
const toRemove = oldRolePairs.filter(
|
||||
|
@ -154,10 +144,7 @@ export class ReactionRolesPlugin extends Plugin {
|
|||
|
||||
@d.event("messageReactionAdd")
|
||||
async onAddReaction(msg: Message, emoji: CustomEmoji, userId: string) {
|
||||
const matchingReactionRole = await this.reactionRoles.getByMessageAndEmoji(
|
||||
msg.id,
|
||||
emoji.id || emoji.name
|
||||
);
|
||||
const matchingReactionRole = await this.reactionRoles.getByMessageAndEmoji(msg.id, emoji.id || emoji.name);
|
||||
if (!matchingReactionRole) return;
|
||||
|
||||
const member = this.guild.members.get(userId);
|
||||
|
@ -168,10 +155,7 @@ export class ReactionRolesPlugin extends Plugin {
|
|||
|
||||
@d.event("messageReactionRemove")
|
||||
async onRemoveReaction(msg: Message, emoji: CustomEmoji, userId: string) {
|
||||
const matchingReactionRole = await this.reactionRoles.getByMessageAndEmoji(
|
||||
msg.id,
|
||||
emoji.id || emoji.name
|
||||
);
|
||||
const matchingReactionRole = await this.reactionRoles.getByMessageAndEmoji(msg.id, emoji.id || emoji.name);
|
||||
if (!matchingReactionRole) return;
|
||||
|
||||
const member = this.guild.members.get(userId);
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import { decorators as d, Plugin } from "knub";
|
||||
import { Channel, Message, TextChannel, User } from "eris";
|
||||
import { Channel, Message, User } from "eris";
|
||||
import {
|
||||
formatTemplateString,
|
||||
getEmojiInString,
|
||||
getRoleMentions,
|
||||
getUrlsInString,
|
||||
getUserMentions,
|
||||
sleep,
|
||||
stripObjectToScalars,
|
||||
trimLines
|
||||
} from "../utils";
|
||||
import { LogType } from "../data/LogType";
|
||||
import { GuildLogs } from "../data/GuildLogs";
|
||||
import { ModActionsPlugin } from "./ModActions";
|
||||
import { CaseType } from "../data/CaseType";
|
||||
import { CaseTypes } from "../data/CaseTypes";
|
||||
import { GuildArchives } from "../data/GuildArchives";
|
||||
import moment from "moment-timezone";
|
||||
|
||||
|
@ -23,7 +22,8 @@ enum RecentActionType {
|
|||
Link,
|
||||
Attachment,
|
||||
Emoji,
|
||||
Newline
|
||||
Newline,
|
||||
Censor
|
||||
}
|
||||
|
||||
interface IRecentAction {
|
||||
|
@ -96,7 +96,7 @@ export class SpamPlugin extends Plugin {
|
|||
|
||||
onLoad() {
|
||||
this.logs = new GuildLogs(this.guildId);
|
||||
this.archives = new GuildArchives(this.guildId);
|
||||
this.archives = GuildArchives.getInstance(this.guildId);
|
||||
|
||||
this.recentActions = [];
|
||||
this.expiryInterval = setInterval(() => this.clearOldRecentActions(), 1000 * 60);
|
||||
|
@ -260,7 +260,7 @@ export class SpamPlugin extends Plugin {
|
|||
const logUrl = await this.saveSpamArchives(uniqueMessages, msg.channel, msg.author);
|
||||
|
||||
// Create a case and log the actions taken above
|
||||
const caseType = spamConfig.mute ? CaseType.Mute : CaseType.Note;
|
||||
const caseType = spamConfig.mute ? CaseTypes.Mute : CaseTypes.Note;
|
||||
const caseText = trimLines(`
|
||||
Automatic spam detection: ${description} (over ${spamConfig.count} in ${spamConfig.interval}s)
|
||||
${logUrl}
|
||||
|
@ -297,6 +297,14 @@ export class SpamPlugin extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
// For interoperability with the Censor plugin
|
||||
async logCensor(msg: Message) {
|
||||
const spamConfig = this.configValueForMsg(msg, "max_censor");
|
||||
if (spamConfig) {
|
||||
this.logAndDetectSpam(msg, RecentActionType.Censor, spamConfig, 1, "too many censored messages");
|
||||
}
|
||||
}
|
||||
|
||||
@d.event("messageCreate")
|
||||
async onMessageCreate(msg: Message) {
|
||||
if (msg.author.bot) return;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Plugin, decorators as d } from "knub";
|
||||
import { Channel, Message, TextChannel } from "eris";
|
||||
import { Message } from "eris";
|
||||
import { errorMessage, successMessage } from "../utils";
|
||||
import { GuildTags } from "../data/GuildTags";
|
||||
|
||||
|
@ -29,7 +29,7 @@ export class TagsPlugin extends Plugin {
|
|||
}
|
||||
|
||||
onLoad() {
|
||||
this.tags = new GuildTags(this.guildId);
|
||||
this.tags = GuildTags.getInstance(this.guildId);
|
||||
}
|
||||
|
||||
@d.command("tag", "<tag:string> <body:string$>")
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
import { Plugin, decorators as d, reply } from "knub";
|
||||
import { Channel, EmbedOptions, Message, TextChannel, User, VoiceChannel } from "eris";
|
||||
import {
|
||||
embedPadding,
|
||||
errorMessage,
|
||||
getMessages,
|
||||
stripObjectToScalars,
|
||||
successMessage,
|
||||
trimLines
|
||||
} from "../utils";
|
||||
import { embedPadding, errorMessage, getMessages, stripObjectToScalars, successMessage, trimLines } from "../utils";
|
||||
import { GuildLogs } from "../data/GuildLogs";
|
||||
import { LogType } from "../data/LogType";
|
||||
import moment from "moment-timezone";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import { GuildCases } from "../data/GuildCases";
|
||||
import { CaseType } from "../data/CaseType";
|
||||
import { CaseTypes } from "../data/CaseTypes";
|
||||
|
||||
const MAX_SEARCH_RESULTS = 15;
|
||||
const MAX_CLEAN_COUNT = 50;
|
||||
|
@ -54,7 +47,7 @@ export class UtilityPlugin extends Plugin {
|
|||
|
||||
onLoad() {
|
||||
this.logs = new GuildLogs(this.guildId);
|
||||
this.cases = new GuildCases(this.guildId);
|
||||
this.cases = GuildCases.getInstance(this.guildId);
|
||||
|
||||
if (activeReloads && activeReloads.has(this.guildId)) {
|
||||
activeReloads.get(this.guildId).createMessage(successMessage("Reloaded!"));
|
||||
|
@ -80,9 +73,7 @@ export class UtilityPlugin extends Plugin {
|
|||
}
|
||||
|
||||
const level = this.getMemberLevel(member);
|
||||
msg.channel.createMessage(
|
||||
`The permission level of ${member.username}#${member.discriminator} is **${level}**`
|
||||
);
|
||||
msg.channel.createMessage(`The permission level of ${member.username}#${member.discriminator} is **${level}**`);
|
||||
}
|
||||
|
||||
@d.command("search", "<query:string$>")
|
||||
|
@ -126,9 +117,7 @@ export class UtilityPlugin extends Plugin {
|
|||
});
|
||||
lines = lines.slice(from, to);
|
||||
|
||||
const footer = paginated
|
||||
? "Add a page number to the end of the command to browse results"
|
||||
: "";
|
||||
const footer = paginated ? "Add a page number to the end of the command to browse results" : "";
|
||||
|
||||
msg.channel.createMessage(`${header}\n\`\`\`${lines.join("\n")}\`\`\`${footer}`);
|
||||
} else {
|
||||
|
@ -152,25 +141,17 @@ export class UtilityPlugin extends Plugin {
|
|||
@d.permission("clean")
|
||||
async cleanAllCmd(msg: Message, args: { count: number }) {
|
||||
if (args.count > MAX_CLEAN_COUNT || args.count <= 0) {
|
||||
msg.channel.createMessage(
|
||||
errorMessage(`Clean count must be between 1 and ${MAX_CLEAN_COUNT}`)
|
||||
);
|
||||
msg.channel.createMessage(errorMessage(`Clean count must be between 1 and ${MAX_CLEAN_COUNT}`));
|
||||
return;
|
||||
}
|
||||
|
||||
const messagesToClean = await getMessages(
|
||||
msg.channel as TextChannel,
|
||||
m => m.id !== msg.id,
|
||||
args.count
|
||||
);
|
||||
const messagesToClean = await getMessages(msg.channel as TextChannel, m => m.id !== msg.id, args.count);
|
||||
if (messagesToClean.length > 0) {
|
||||
await this.cleanMessages(msg.channel, messagesToClean.map(m => m.id), msg.author);
|
||||
}
|
||||
|
||||
msg.channel.createMessage(
|
||||
successMessage(
|
||||
`Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`
|
||||
)
|
||||
successMessage(`Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -178,9 +159,7 @@ export class UtilityPlugin extends Plugin {
|
|||
@d.permission("clean")
|
||||
async cleanUserCmd(msg: Message, args: { userId: string; count: number }) {
|
||||
if (args.count > MAX_CLEAN_COUNT || args.count <= 0) {
|
||||
msg.channel.createMessage(
|
||||
errorMessage(`Clean count must be between 1 and ${MAX_CLEAN_COUNT}`)
|
||||
);
|
||||
msg.channel.createMessage(errorMessage(`Clean count must be between 1 and ${MAX_CLEAN_COUNT}`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -194,9 +173,7 @@ export class UtilityPlugin extends Plugin {
|
|||
}
|
||||
|
||||
msg.channel.createMessage(
|
||||
successMessage(
|
||||
`Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`
|
||||
)
|
||||
successMessage(`Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -204,9 +181,7 @@ export class UtilityPlugin extends Plugin {
|
|||
@d.permission("clean")
|
||||
async cleanBotCmd(msg: Message, args: { count: number }) {
|
||||
if (args.count > MAX_CLEAN_COUNT || args.count <= 0) {
|
||||
msg.channel.createMessage(
|
||||
errorMessage(`Clean count must be between 1 and ${MAX_CLEAN_COUNT}`)
|
||||
);
|
||||
msg.channel.createMessage(errorMessage(`Clean count must be between 1 and ${MAX_CLEAN_COUNT}`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -220,9 +195,7 @@ export class UtilityPlugin extends Plugin {
|
|||
}
|
||||
|
||||
msg.channel.createMessage(
|
||||
successMessage(
|
||||
`Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`
|
||||
)
|
||||
successMessage(`Cleaned ${messagesToClean.length} ${messagesToClean.length === 1 ? "message" : "messages"}`)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -283,7 +256,7 @@ export class UtilityPlugin extends Plugin {
|
|||
});
|
||||
|
||||
const caseSummaries = cases.map(c => {
|
||||
return `${CaseType[c.type]} (#${c.case_number})`;
|
||||
return `${CaseTypes[c.type]} (#${c.case_number})`;
|
||||
});
|
||||
|
||||
embed.fields.push({
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"noImplicitAny": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "ES2017",
|
||||
"emitDecoratorMetadata": true,
|
||||
"target": "ES6",
|
||||
"lib": [
|
||||
"es6",
|
||||
"es7",
|
||||
|
|
Loading…
Add table
Reference in a new issue