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
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;
|
||||
}
|
||||
|
||||
async getCaseNotes(caseId: number): Promise<CaseNote[]> {
|
||||
const results = await knex("case_notes")
|
||||
.where("case_id", caseId)
|
||||
.select();
|
||||
|
||||
return results.map(r => new CaseNote(r));
|
||||
return this.cases.findOne({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
case_number: caseNumber
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
async findFirstCaseNote(caseId: number): Promise<CaseNote> {
|
||||
const result = await knex("case_notes")
|
||||
.where("case_id", caseId)
|
||||
.first();
|
||||
|
||||
return result ? new CaseNote(result) : null;
|
||||
return this.cases.find({
|
||||
relations: this.getRelations(),
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async create(data): Promise<number> {
|
||||
return knex
|
||||
.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]));
|
||||
const result = await this.cases.insert({
|
||||
...data,
|
||||
guild_id: this.guildId,
|
||||
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({
|
||||
...data,
|
||||
case_id: caseId
|
||||
})
|
||||
.into("case_notes")
|
||||
.return();
|
||||
async createNote(caseId: number, data: any): Promise<number> {
|
||||
const result = await this.caseNotes.insert({
|
||||
...data,
|
||||
case_id: caseId
|
||||
});
|
||||
|
||||
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({
|
||||
guild_id: this.guildId,
|
||||
user_id: userId,
|
||||
expires_at: expiresAt
|
||||
})
|
||||
.into("mutes");
|
||||
return this.mutes.insert({
|
||||
guild_id: this.guildId,
|
||||
user_id: userId,
|
||||
expires_at: expiresAt
|
||||
});
|
||||
}
|
||||
|
||||
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) => {
|
||||
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()
|
||||
);
|
||||
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());
|
||||
|
||||
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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue