|
|
|
|
import {
|
|
|
|
|
ButtonStyle,
|
|
|
|
|
CommandInteraction,
|
|
|
|
|
ComponentType,
|
|
|
|
|
GuildMember,
|
|
|
|
|
Message,
|
|
|
|
|
MessageComponentInteraction
|
|
|
|
|
} from "discord.js";
|
|
|
|
|
import type Discord from "discord.js";
|
|
|
|
|
import { Collection, MongoClient } from "mongodb";
|
|
|
|
|
import config from "../config/main.js";
|
|
|
|
|
import client from "../utils/client.js";
|
|
|
|
|
import * as crypto from "crypto";
|
|
|
|
|
import _ from "lodash";
|
|
|
|
|
import defaultData from "../config/default.js";
|
|
|
|
|
|
|
|
|
|
let username, password;
|
|
|
|
|
|
|
|
|
|
if ("username" in config.mongoOptions) username = encodeURIComponent(config.mongoOptions.username as string);
|
|
|
|
|
if ("password" in config.mongoOptions) password = encodeURIComponent(config.mongoOptions.password as string);
|
|
|
|
|
|
|
|
|
|
const mongoClient = new MongoClient(
|
|
|
|
|
username
|
|
|
|
|
? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT&authSource=${config.mongoOptions.authSource}`
|
|
|
|
|
: `mongodb://${config.mongoOptions.host}`
|
|
|
|
|
);
|
|
|
|
|
await mongoClient.connect();
|
|
|
|
|
export const database = mongoClient.db();
|
|
|
|
|
|
|
|
|
|
const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" };
|
|
|
|
|
const getIV = () => crypto.randomBytes(16);
|
|
|
|
|
|
|
|
|
|
export class Guilds {
|
|
|
|
|
guilds: Collection<GuildConfig>;
|
|
|
|
|
oldGuilds: Collection<GuildConfig>;
|
|
|
|
|
defaultData: GuildConfig;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.guilds = database.collection<GuildConfig>("guilds");
|
|
|
|
|
this.defaultData = defaultData;
|
|
|
|
|
this.oldGuilds = database.collection<GuildConfig>("oldGuilds");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async readOld(guild: string): Promise<Partial<GuildConfig>> {
|
|
|
|
|
// console.log("Guild read")
|
|
|
|
|
const entry = await this.oldGuilds.findOne({ id: guild });
|
|
|
|
|
return entry ?? {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updateAllGuilds() {
|
|
|
|
|
const guilds = await this.guilds.find().toArray();
|
|
|
|
|
for (const guild of guilds) {
|
|
|
|
|
let guildObj;
|
|
|
|
|
try {
|
|
|
|
|
guildObj = await client.guilds.fetch(guild.id);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
guildObj = null;
|
|
|
|
|
}
|
|
|
|
|
if (!guildObj) await this.delete(guild.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async read(guild: string): Promise<GuildConfig> {
|
|
|
|
|
// console.log("Guild read")
|
|
|
|
|
const entry = await this.guilds.findOne({ id: guild });
|
|
|
|
|
const data = _.cloneDeep(this.defaultData);
|
|
|
|
|
return _.merge(data, entry ?? {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async write(guild: string, set: object | null, unset: string[] | string = []) {
|
|
|
|
|
// console.log("Guild write")
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
const uo: Record<string, any> = {};
|
|
|
|
|
if (!Array.isArray(unset)) unset = [unset];
|
|
|
|
|
for (const key of unset) {
|
|
|
|
|
uo[key] = null;
|
|
|
|
|
}
|
|
|
|
|
const out = { $set: {}, $unset: {} };
|
|
|
|
|
if (set) out.$set = set;
|
|
|
|
|
if (unset.length) out.$unset = uo;
|
|
|
|
|
await this.guilds.updateOne({ id: guild }, out, { upsert: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
async append(guild: string, key: string, value: any) {
|
|
|
|
|
// console.log("Guild append")
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
await this.guilds.updateOne(
|
|
|
|
|
{ id: guild },
|
|
|
|
|
{
|
|
|
|
|
$addToSet: { [key]: { $each: value } }
|
|
|
|
|
},
|
|
|
|
|
{ upsert: true }
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
await this.guilds.updateOne(
|
|
|
|
|
{ id: guild },
|
|
|
|
|
{
|
|
|
|
|
$addToSet: { [key]: value }
|
|
|
|
|
},
|
|
|
|
|
{ upsert: true }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async remove(
|
|
|
|
|
guild: string,
|
|
|
|
|
key: string,
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
value: any,
|
|
|
|
|
innerKey?: string | null
|
|
|
|
|
) {
|
|
|
|
|
// console.log("Guild remove")
|
|
|
|
|
if (innerKey) {
|
|
|
|
|
await this.guilds.updateOne(
|
|
|
|
|
{ id: guild },
|
|
|
|
|
{
|
|
|
|
|
$pull: { [key]: { [innerKey]: { $eq: value } } }
|
|
|
|
|
},
|
|
|
|
|
{ upsert: true }
|
|
|
|
|
);
|
|
|
|
|
} else if (Array.isArray(value)) {
|
|
|
|
|
await this.guilds.updateOne(
|
|
|
|
|
{ id: guild },
|
|
|
|
|
{
|
|
|
|
|
$pullAll: { [key]: value }
|
|
|
|
|
},
|
|
|
|
|
{ upsert: true }
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
await this.guilds.updateOne(
|
|
|
|
|
{ id: guild },
|
|
|
|
|
{
|
|
|
|
|
$pullAll: { [key]: [value] }
|
|
|
|
|
},
|
|
|
|
|
{ upsert: true }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async delete(guild: string) {
|
|
|
|
|
// console.log("Guild delete")
|
|
|
|
|
await this.guilds.deleteOne({ id: guild });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async staffChannels(): Promise<string[]> {
|
|
|
|
|
const entries = (
|
|
|
|
|
await this.guilds
|
|
|
|
|
.find(
|
|
|
|
|
{ "logging.staff.channel": { $exists: true } },
|
|
|
|
|
{ projection: { "logging.staff.channel": 1, _id: 0 } }
|
|
|
|
|
)
|
|
|
|
|
.toArray()
|
|
|
|
|
).map((e) => e.logging.staff.channel);
|
|
|
|
|
const out: string[] = [];
|
|
|
|
|
for (const entry of entries) {
|
|
|
|
|
if (entry) out.push(entry);
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TranscriptEmbed {
|
|
|
|
|
title?: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
fields?: {
|
|
|
|
|
name: string;
|
|
|
|
|
value: string;
|
|
|
|
|
inline: boolean;
|
|
|
|
|
}[];
|
|
|
|
|
footer?: {
|
|
|
|
|
text: string;
|
|
|
|
|
iconURL?: string;
|
|
|
|
|
};
|
|
|
|
|
color?: number;
|
|
|
|
|
timestamp?: string;
|
|
|
|
|
author?: {
|
|
|
|
|
name: string;
|
|
|
|
|
iconURL?: string;
|
|
|
|
|
url?: string;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TranscriptComponent {
|
|
|
|
|
type: number;
|
|
|
|
|
style?: ButtonStyle;
|
|
|
|
|
label?: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
placeholder?: string;
|
|
|
|
|
emojiURL?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TranscriptAuthor {
|
|
|
|
|
username: string;
|
|
|
|
|
discriminator: number;
|
|
|
|
|
nickname?: string;
|
|
|
|
|
id: string;
|
|
|
|
|
iconURL?: string;
|
|
|
|
|
topRole: {
|
|
|
|
|
color: number;
|
|
|
|
|
badgeURL?: string;
|
|
|
|
|
};
|
|
|
|
|
bot: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TranscriptAttachment {
|
|
|
|
|
url: string;
|
|
|
|
|
filename: string;
|
|
|
|
|
size: number;
|
|
|
|
|
log?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TranscriptMessage {
|
|
|
|
|
id: string;
|
|
|
|
|
author: TranscriptAuthor;
|
|
|
|
|
content?: string;
|
|
|
|
|
embeds?: TranscriptEmbed[];
|
|
|
|
|
components?: TranscriptComponent[][];
|
|
|
|
|
editedTimestamp?: number;
|
|
|
|
|
createdTimestamp: number;
|
|
|
|
|
flags?: string[];
|
|
|
|
|
attachments?: TranscriptAttachment[];
|
|
|
|
|
stickerURLs?: string[];
|
|
|
|
|
referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TranscriptSchema {
|
|
|
|
|
code: string;
|
|
|
|
|
for: TranscriptAuthor;
|
|
|
|
|
type: "ticket" | "purge";
|
|
|
|
|
guild: string;
|
|
|
|
|
channel: string;
|
|
|
|
|
messages: TranscriptMessage[];
|
|
|
|
|
createdTimestamp: number;
|
|
|
|
|
createdBy: TranscriptAuthor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface findDocSchema {
|
|
|
|
|
channelID: string;
|
|
|
|
|
messageID: string;
|
|
|
|
|
code: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class Transcript {
|
|
|
|
|
transcripts: Collection<TranscriptSchema>;
|
|
|
|
|
messageToTranscript: Collection<findDocSchema>;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.transcripts = database.collection<TranscriptSchema>("transcripts");
|
|
|
|
|
this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async upload(data: findDocSchema) {
|
|
|
|
|
// console.log("Transcript upload")
|
|
|
|
|
await this.messageToTranscript.insertOne(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async create(transcript: Omit<TranscriptSchema, "code">) {
|
|
|
|
|
// console.log("Transcript create")
|
|
|
|
|
let code;
|
|
|
|
|
do {
|
|
|
|
|
code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
|
|
|
|
|
} while (await this.transcripts.findOne({ code: code }));
|
|
|
|
|
const key = crypto
|
|
|
|
|
.randomBytes(32 ** 2)
|
|
|
|
|
.toString("base64")
|
|
|
|
|
.replace(/=/g, "")
|
|
|
|
|
.replace(/\//g, "_")
|
|
|
|
|
.replace(/\+/g, "-")
|
|
|
|
|
.substring(0, 32);
|
|
|
|
|
const iv = getIV()
|
|
|
|
|
.toString("base64")
|
|
|
|
|
.substring(0, 16)
|
|
|
|
|
.replace(/=/g, "")
|
|
|
|
|
.replace(/\//g, "_")
|
|
|
|
|
.replace(/\+/g, "-");
|
|
|
|
|
for (const message of transcript.messages) {
|
|
|
|
|
if (message.content) {
|
|
|
|
|
const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
|
|
|
|
|
message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
|
|
|
|
|
if (doc.acknowledged) {
|
|
|
|
|
await client.database.eventScheduler.schedule(
|
|
|
|
|
"deleteTranscript",
|
|
|
|
|
(Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
|
|
|
|
|
{ guild: transcript.guild, code: code, iv: iv, key: key }
|
|
|
|
|
);
|
|
|
|
|
return [code, key, iv];
|
|
|
|
|
} else return [null, null, null];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async delete(code: string) {
|
|
|
|
|
// console.log("Transcript delete")
|
|
|
|
|
await this.transcripts.deleteOne({ code: code });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async deleteAll(guild: string) {
|
|
|
|
|
// console.log("Transcript delete")
|
|
|
|
|
const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
|
|
|
|
|
for (const doc of filteredDocs) {
|
|
|
|
|
await this.transcripts.deleteOne({ code: doc.code });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async readEncrypted(code: string) {
|
|
|
|
|
// console.log("Transcript read")
|
|
|
|
|
let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
|
|
|
|
|
let findDoc: findDocSchema | null = null;
|
|
|
|
|
if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
|
|
|
|
|
if (findDoc) {
|
|
|
|
|
const message = await (
|
|
|
|
|
client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
|
|
|
|
|
)?.messages.fetch(findDoc.messageID);
|
|
|
|
|
if (!message) return null;
|
|
|
|
|
const attachment = message.attachments.first();
|
|
|
|
|
if (!attachment) return null;
|
|
|
|
|
const transcript = (await fetch(attachment.url)).body;
|
|
|
|
|
if (!transcript) return null;
|
|
|
|
|
const reader = transcript.getReader();
|
|
|
|
|
let data: Uint8Array | null = null;
|
|
|
|
|
let allPacketsReceived = false;
|
|
|
|
|
while (!allPacketsReceived) {
|
|
|
|
|
const { value, done } = await reader.read();
|
|
|
|
|
if (done) {
|
|
|
|
|
allPacketsReceived = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!data) {
|
|
|
|
|
data = value;
|
|
|
|
|
} else {
|
|
|
|
|
data = new Uint8Array(Buffer.concat([data, value]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!data) return null;
|
|
|
|
|
doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
|
|
|
|
|
}
|
|
|
|
|
if (!doc) return null;
|
|
|
|
|
return doc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async read(code: string, key: string, iv: string) {
|
|
|
|
|
let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
|
|
|
|
|
let findDoc: findDocSchema | null = null;
|
|
|
|
|
if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
|
|
|
|
|
if (findDoc) {
|
|
|
|
|
const message = await (
|
|
|
|
|
client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
|
|
|
|
|
)?.messages.fetch(findDoc.messageID);
|
|
|
|
|
if (!message) return null;
|
|
|
|
|
const attachment = message.attachments.first();
|
|
|
|
|
if (!attachment) return null;
|
|
|
|
|
const transcript = (await fetch(attachment.url)).body;
|
|
|
|
|
if (!transcript) return null;
|
|
|
|
|
const reader = transcript.getReader();
|
|
|
|
|
let data: Uint8Array | null = null;
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
|
|
|
|
|
while (true) {
|
|
|
|
|
const { value, done } = await reader.read();
|
|
|
|
|
if (done) break;
|
|
|
|
|
if (!data) {
|
|
|
|
|
data = value;
|
|
|
|
|
} else {
|
|
|
|
|
data = new Uint8Array(Buffer.concat([data, value]));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!data) return null;
|
|
|
|
|
doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
|
|
|
|
|
}
|
|
|
|
|
if (!doc) return null;
|
|
|
|
|
for (const message of doc.messages) {
|
|
|
|
|
if (message.content) {
|
|
|
|
|
const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
|
|
|
|
|
message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return doc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async createTranscript(
|
|
|
|
|
type: "ticket" | "purge",
|
|
|
|
|
messages: Message[],
|
|
|
|
|
interaction: MessageComponentInteraction | CommandInteraction,
|
|
|
|
|
member: GuildMember
|
|
|
|
|
) {
|
|
|
|
|
const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
|
|
|
|
|
const newOut: Omit<TranscriptSchema, "code"> = {
|
|
|
|
|
type: type,
|
|
|
|
|
for: {
|
|
|
|
|
username: member!.user.username,
|
|
|
|
|
discriminator: parseInt(member!.user.discriminator),
|
|
|
|
|
id: member!.user.id,
|
|
|
|
|
topRole: {
|
|
|
|
|
color: member!.roles.highest.color
|
|
|
|
|
},
|
|
|
|
|
iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
|
|
|
|
|
bot: member!.user.bot
|
|
|
|
|
},
|
|
|
|
|
guild: interaction.guild!.id,
|
|
|
|
|
channel: interaction.channel!.id,
|
|
|
|
|
messages: [],
|
|
|
|
|
createdTimestamp: Date.now(),
|
|
|
|
|
createdBy: {
|
|
|
|
|
username: interaction.user.username,
|
|
|
|
|
discriminator: parseInt(interaction.user.discriminator),
|
|
|
|
|
id: interaction.user.id,
|
|
|
|
|
topRole: {
|
|
|
|
|
color: interactionMember?.roles.highest.color ?? 0x000000
|
|
|
|
|
},
|
|
|
|
|
iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
|
|
|
|
|
bot: interaction.user.bot
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (member.nickname) newOut.for.nickname = member.nickname;
|
|
|
|
|
if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
|
|
|
|
|
messages.reverse().forEach((message) => {
|
|
|
|
|
const msg: TranscriptMessage = {
|
|
|
|
|
id: message.id,
|
|
|
|
|
author: {
|
|
|
|
|
username: message.author.username,
|
|
|
|
|
discriminator: parseInt(message.author.discriminator),
|
|
|
|
|
id: message.author.id,
|
|
|
|
|
topRole: {
|
|
|
|
|
color: message.member ? message.member.roles.highest.color : 0x000000
|
|
|
|
|
},
|
|
|
|
|
iconURL: (message.member?.user ?? message.author).displayAvatarURL({ forceStatic: true }),
|
|
|
|
|
bot: message.author.bot || false
|
|
|
|
|
},
|
|
|
|
|
createdTimestamp: message.createdTimestamp
|
|
|
|
|
};
|
|
|
|
|
if (message.member?.nickname) msg.author.nickname = message.member.nickname;
|
|
|
|
|
if (message.member?.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
|
|
|
|
|
if (message.content) msg.content = message.content;
|
|
|
|
|
if (message.embeds.length > 0)
|
|
|
|
|
msg.embeds = message.embeds.map((embed) => {
|
|
|
|
|
const obj: TranscriptEmbed = {};
|
|
|
|
|
if (embed.title) obj.title = embed.title;
|
|
|
|
|
if (embed.description) obj.description = embed.description;
|
|
|
|
|
if (embed.fields.length > 0)
|
|
|
|
|
obj.fields = embed.fields.map((field) => {
|
|
|
|
|
return {
|
|
|
|
|
name: field.name,
|
|
|
|
|
value: field.value,
|
|
|
|
|
inline: field.inline ?? false
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
if (embed.color) obj.color = embed.color;
|
|
|
|
|
if (embed.timestamp) obj.timestamp = embed.timestamp;
|
|
|
|
|
if (embed.footer)
|
|
|
|
|
obj.footer = {
|
|
|
|
|
text: embed.footer.text
|
|
|
|
|
};
|
|
|
|
|
if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
|
|
|
|
|
if (embed.author)
|
|
|
|
|
obj.author = {
|
|
|
|
|
name: embed.author.name
|
|
|
|
|
};
|
|
|
|
|
if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
|
|
|
|
|
if (embed.author?.url) obj.author!.url = embed.author.url;
|
|
|
|
|
return obj;
|
|
|
|
|
});
|
|
|
|
|
if (message.components.length > 0)
|
|
|
|
|
msg.components = message.components.map((component) =>
|
|
|
|
|
component.components.map((child) => {
|
|
|
|
|
const obj: TranscriptComponent = {
|
|
|
|
|
type: child.type
|
|
|
|
|
};
|
|
|
|
|
if (child.type === ComponentType.Button) {
|
|
|
|
|
obj.style = child.style;
|
|
|
|
|
obj.label = child.label ?? "";
|
|
|
|
|
} else if (child.type > 2) {
|
|
|
|
|
obj.placeholder = child.placeholder ?? "";
|
|
|
|
|
}
|
|
|
|
|
return obj;
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
|
|
|
|
|
msg.flags = message.flags.toArray();
|
|
|
|
|
|
|
|
|
|
if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
|
|
|
|
|
if (message.reference)
|
|
|
|
|
msg.referencedMessage = [
|
|
|
|
|
message.reference.guildId ?? "",
|
|
|
|
|
message.reference.channelId,
|
|
|
|
|
message.reference.messageId ?? ""
|
|
|
|
|
];
|
|
|
|
|
newOut.messages.push(msg);
|
|
|
|
|
});
|
|
|
|
|
return newOut;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
|
|
|
|
|
let out = "";
|
|
|
|
|
for (const message of transcript.messages) {
|
|
|
|
|
if (message.referencedMessage) {
|
|
|
|
|
if (Array.isArray(message.referencedMessage)) {
|
|
|
|
|
out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
|
|
|
|
|
} else out += `> [Reply To] ${message.referencedMessage}\n`;
|
|
|
|
|
}
|
|
|
|
|
out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
|
|
|
|
|
message.author.id
|
|
|
|
|
}) (${message.id})`;
|
|
|
|
|
out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
|
|
|
|
|
if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
|
|
|
|
|
out += "\n";
|
|
|
|
|
if (message.content) out += `[Content]\n${message.content}\n\n`;
|
|
|
|
|
if (message.embeds) {
|
|
|
|
|
for (const embed of message.embeds) {
|
|
|
|
|
out += `[Embed]\n`;
|
|
|
|
|
if (embed.title) out += `| Title: ${embed.title}\n`;
|
|
|
|
|
if (embed.description) out += `| Description: ${embed.description}\n`;
|
|
|
|
|
if (embed.fields) {
|
|
|
|
|
for (const field of embed.fields) {
|
|
|
|
|
out += `| Field: ${field.name} - ${field.value}\n`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (embed.footer) {
|
|
|
|
|
out += `|Footer: ${embed.footer.text}\n`;
|
|
|
|
|
}
|
|
|
|
|
out += "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (message.components) {
|
|
|
|
|
for (const component of message.components) {
|
|
|
|
|
out += `[Component]\n`;
|
|
|
|
|
for (const button of component) {
|
|
|
|
|
out += `| Button: ${button.label ?? button.description}\n`;
|
|
|
|
|
}
|
|
|
|
|
out += "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (message.attachments) {
|
|
|
|
|
for (const attachment of message.attachments) {
|
|
|
|
|
out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out += "\n\n";
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class History {
|
|
|
|
|
histories: Collection<HistorySchema>;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.histories = database.collection<HistorySchema>("history");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async create(
|
|
|
|
|
type: string,
|
|
|
|
|
guild: string,
|
|
|
|
|
user: Discord.User,
|
|
|
|
|
moderator: Discord.User | null,
|
|
|
|
|
reason: string | null,
|
|
|
|
|
before?: string | null,
|
|
|
|
|
after?: string | null,
|
|
|
|
|
amount?: string | null
|
|
|
|
|
) {
|
|
|
|
|
// console.log("History create");
|
|
|
|
|
await this.histories.insertOne(
|
|
|
|
|
{
|
|
|
|
|
type: type,
|
|
|
|
|
guild: guild,
|
|
|
|
|
user: user.id,
|
|
|
|
|
moderator: moderator ? moderator.id : null,
|
|
|
|
|
reason: reason,
|
|
|
|
|
occurredAt: new Date(),
|
|
|
|
|
before: before ?? null,
|
|
|
|
|
after: after ?? null,
|
|
|
|
|
amount: amount ?? null
|
|
|
|
|
},
|
|
|
|
|
collectionOptions
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async read(guild: string, user: string, year: number) {
|
|
|
|
|
// console.log("History read");
|
|
|
|
|
const entry = (await this.histories
|
|
|
|
|
.find({
|
|
|
|
|
guild: guild,
|
|
|
|
|
user: user,
|
|
|
|
|
occurredAt: {
|
|
|
|
|
$gte: new Date(year - 1, 11, 31, 23, 59, 59),
|
|
|
|
|
$lt: new Date(year + 1, 0, 1, 0, 0, 0)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.toArray()) as HistorySchema[];
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async delete(guild: string) {
|
|
|
|
|
// console.log("History delete");
|
|
|
|
|
await this.histories.deleteMany({ guild: guild });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ScanCacheSchema {
|
|
|
|
|
addedAt: Date;
|
|
|
|
|
hash: string;
|
|
|
|
|
nsfw?: boolean;
|
|
|
|
|
malware?: boolean;
|
|
|
|
|
bad_link?: boolean;
|
|
|
|
|
tags?: string[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class ScanCache {
|
|
|
|
|
scanCache: Collection<ScanCacheSchema>;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.scanCache = database.collection<ScanCacheSchema>("scanCache");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async read(hash: string) {
|
|
|
|
|
return await this.scanCache.findOne({ hash: hash });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async write(hash: string, type: "nsfw" | "malware" | "bad_link", data: boolean, tags?: string[]) {
|
|
|
|
|
// await this.scanCache.insertOne(
|
|
|
|
|
// { hash: hash, [type]: data, tags: tags ?? [], addedAt: new Date() },
|
|
|
|
|
// collectionOptions
|
|
|
|
|
// );
|
|
|
|
|
await this.scanCache.updateOne(
|
|
|
|
|
{ hash: hash },
|
|
|
|
|
{
|
|
|
|
|
$set: (() => {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case "nsfw": {
|
|
|
|
|
return { nsfw: data, addedAt: new Date() };
|
|
|
|
|
}
|
|
|
|
|
case "malware": {
|
|
|
|
|
return { malware: data, addedAt: new Date() };
|
|
|
|
|
}
|
|
|
|
|
case "bad_link": {
|
|
|
|
|
return { bad_link: data, tags: tags ?? [], addedAt: new Date() };
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
throw new Error("Invalid type");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
// No you can't just do { [type]: data }, yes it's a typescript error, no I don't know how to fix it
|
|
|
|
|
// cleanly, yes it would be marginally more elegant, no it's not essential, yes I'd be happy to review
|
|
|
|
|
// PRs that did improve this snippet
|
|
|
|
|
// Made an attempt... Gave up... Just Leave It
|
|
|
|
|
// Counter: 2
|
|
|
|
|
},
|
|
|
|
|
Object.assign({ upsert: true }, collectionOptions)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async cleanup() {
|
|
|
|
|
// console.log("ScanCache cleanup");
|
|
|
|
|
await this.scanCache.deleteMany({
|
|
|
|
|
addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
|
|
|
|
|
hash: { $not$text: "http" }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class PerformanceTest {
|
|
|
|
|
performanceData: Collection<PerformanceDataSchema>;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.performanceData = database.collection<PerformanceDataSchema>("performance");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async record(data: PerformanceDataSchema) {
|
|
|
|
|
// console.log("PerformanceTest record");
|
|
|
|
|
data.timestamp = new Date();
|
|
|
|
|
await this.performanceData.insertOne(data, collectionOptions);
|
|
|
|
|
}
|
|
|
|
|
async read() {
|
|
|
|
|
// console.log("PerformanceTest read");
|
|
|
|
|
return await this.performanceData.find({}).toArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PerformanceDataSchema {
|
|
|
|
|
timestamp?: Date;
|
|
|
|
|
discord: number;
|
|
|
|
|
databaseRead: number;
|
|
|
|
|
resources: {
|
|
|
|
|
cpu: number;
|
|
|
|
|
memory: number;
|
|
|
|
|
temperature: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class ModNotes {
|
|
|
|
|
modNotes: Collection<ModNoteSchema>;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.modNotes = database.collection<ModNoteSchema>("modNotes");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async flag(guild: string, user: string, flag: FlagColors | null) {
|
|
|
|
|
const modNote = await this.modNotes.findOne({ guild: guild, user: user });
|
|
|
|
|
modNote
|
|
|
|
|
? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { flag: flag } }, collectionOptions)
|
|
|
|
|
: await this.modNotes.insertOne({ guild: guild, user: user, note: null, flag: flag }, collectionOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async create(guild: string, user: string, note: string | null) {
|
|
|
|
|
// console.log("ModNotes create");
|
|
|
|
|
const modNote = await this.modNotes.findOne({ guild: guild, user: user });
|
|
|
|
|
modNote
|
|
|
|
|
? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, collectionOptions)
|
|
|
|
|
: await this.modNotes.insertOne({ guild: guild, user: user, note: note, flag: null }, collectionOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async read(guild: string, user: string) {
|
|
|
|
|
// console.log("ModNotes read");
|
|
|
|
|
const entry = await this.modNotes.findOne({ guild: guild, user: user });
|
|
|
|
|
return entry ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async delete(guild: string) {
|
|
|
|
|
// console.log("ModNotes delete");
|
|
|
|
|
await this.modNotes.deleteMany({ guild: guild });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class Premium {
|
|
|
|
|
premium: Collection<PremiumSchema>;
|
|
|
|
|
cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
|
|
|
|
|
cacheTimeout = 1000 * 60 * 60; // 1 hour
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.premium = database.collection<PremiumSchema>("premium");
|
|
|
|
|
this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updateUser(user: string, level: number) {
|
|
|
|
|
// console.log("Premium updateUser");
|
|
|
|
|
if (!(await this.userExists(user))) await this.createUser(user, level);
|
|
|
|
|
await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async userExists(user: string): Promise<boolean> {
|
|
|
|
|
// console.log("Premium userExists");
|
|
|
|
|
const entry = await this.premium.findOne({ user: user });
|
|
|
|
|
return entry ? true : false;
|
|
|
|
|
}
|
|
|
|
|
async createUser(user: string, level: number) {
|
|
|
|
|
// console.log("Premium createUser");
|
|
|
|
|
await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
|
|
|
|
|
// console.log("Premium hasPremium");
|
|
|
|
|
// [Has premium, user giving premium, level, is mod: if given automatically]
|
|
|
|
|
const cached = this.cache.get(guild);
|
|
|
|
|
if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
|
|
|
|
|
const entries = await this.premium.find({}).toArray();
|
|
|
|
|
const members = (await client.guilds.fetch(guild)).members.cache;
|
|
|
|
|
for (const { user } of entries) {
|
|
|
|
|
const member = members.get(user);
|
|
|
|
|
if (member) {
|
|
|
|
|
//TODO: Notify user if they've given premium to a server that has since gotten premium via a mod.
|
|
|
|
|
const modPerms = //TODO: Create list in config for perms
|
|
|
|
|
member.permissions.has("Administrator") ||
|
|
|
|
|
member.permissions.has("ManageChannels") ||
|
|
|
|
|
member.permissions.has("ManageRoles") ||
|
|
|
|
|
member.permissions.has("ManageEmojisAndStickers") ||
|
|
|
|
|
member.permissions.has("ManageWebhooks") ||
|
|
|
|
|
member.permissions.has("ManageGuild") ||
|
|
|
|
|
member.permissions.has("KickMembers") ||
|
|
|
|
|
member.permissions.has("BanMembers") ||
|
|
|
|
|
member.permissions.has("ManageEvents") ||
|
|
|
|
|
member.permissions.has("ManageMessages") ||
|
|
|
|
|
member.permissions.has("ManageThreads");
|
|
|
|
|
const entry = entries.find((e) => e.user === member.id);
|
|
|
|
|
if (entry && entry.level === 3 && modPerms) {
|
|
|
|
|
this.cache.set(guild, [
|
|
|
|
|
true,
|
|
|
|
|
member.id,
|
|
|
|
|
entry.level,
|
|
|
|
|
true,
|
|
|
|
|
new Date(Date.now() + this.cacheTimeout)
|
|
|
|
|
]);
|
|
|
|
|
return [true, member.id, entry.level, true];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const entry = await this.premium.findOne({
|
|
|
|
|
appliesTo: {
|
|
|
|
|
$elemMatch: {
|
|
|
|
|
$eq: guild
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.cache.set(guild, [
|
|
|
|
|
entry ? true : false,
|
|
|
|
|
entry?.user ?? "",
|
|
|
|
|
entry?.level ?? 0,
|
|
|
|
|
false,
|
|
|
|
|
new Date(Date.now() + this.cacheTimeout)
|
|
|
|
|
]);
|
|
|
|
|
return entry ? [true, entry.user, entry.level, false] : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fetchUser(user: string): Promise<PremiumSchema | null> {
|
|
|
|
|
// console.log("Premium fetchUser");
|
|
|
|
|
const entry = await this.premium.findOne({ user: user });
|
|
|
|
|
if (!entry) return null;
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async checkAllPremium(member?: GuildMember) {
|
|
|
|
|
// console.log("Premium checkAllPremium");
|
|
|
|
|
const entries = await this.premium.find({}).toArray();
|
|
|
|
|
if (member) {
|
|
|
|
|
const entry = entries.find((e) => e.user === member.id);
|
|
|
|
|
if (entry) {
|
|
|
|
|
const expiresAt = entry.expiresAt;
|
|
|
|
|
if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
|
|
|
|
|
}
|
|
|
|
|
const roles = member.roles;
|
|
|
|
|
let level = 0;
|
|
|
|
|
if (roles.cache.has("1066468879309750313")) {
|
|
|
|
|
level = 99;
|
|
|
|
|
} else if (roles.cache.has("1066465491713003520")) {
|
|
|
|
|
level = 1;
|
|
|
|
|
} else if (roles.cache.has("1066439526496604194")) {
|
|
|
|
|
level = 2;
|
|
|
|
|
} else if (roles.cache.has("1066464134322978912")) {
|
|
|
|
|
level = 3;
|
|
|
|
|
}
|
|
|
|
|
await this.updateUser(member.id, level);
|
|
|
|
|
if (level > 0) {
|
|
|
|
|
await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
|
|
|
|
|
} else {
|
|
|
|
|
await this.premium.updateOne(
|
|
|
|
|
{ user: member.id },
|
|
|
|
|
{ $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
|
|
|
|
|
for (const { roles, id } of members.values()) {
|
|
|
|
|
const entry = entries.find((e) => e.user === id);
|
|
|
|
|
if (entry) {
|
|
|
|
|
const expiresAt = entry.expiresAt;
|
|
|
|
|
if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
|
|
|
|
|
}
|
|
|
|
|
let level: number = 0;
|
|
|
|
|
if (roles.cache.has("1066468879309750313")) {
|
|
|
|
|
level = 99;
|
|
|
|
|
} else if (roles.cache.has("1066465491713003520")) {
|
|
|
|
|
level = 1;
|
|
|
|
|
} else if (roles.cache.has("1066439526496604194")) {
|
|
|
|
|
level = 2;
|
|
|
|
|
} else if (roles.cache.has("1066464134322978912")) {
|
|
|
|
|
level = 3;
|
|
|
|
|
}
|
|
|
|
|
await this.updateUser(id, level);
|
|
|
|
|
if (level > 0) {
|
|
|
|
|
await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
|
|
|
|
|
} else {
|
|
|
|
|
await this.premium.updateOne(
|
|
|
|
|
{ user: id },
|
|
|
|
|
{ $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async addPremium(user: string, guild: string) {
|
|
|
|
|
// console.log("Premium addPremium");
|
|
|
|
|
const { level } = (await this.fetchUser(user))!;
|
|
|
|
|
this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
|
|
|
|
|
return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async removePremium(user: string, guild: string) {
|
|
|
|
|
// console.log("Premium removePremium");
|
|
|
|
|
this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
|
|
|
|
|
return await this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// export class Plugins {}
|
|
|
|
|
|
|
|
|
|
export interface GuildConfig {
|
|
|
|
|
id: string;
|
|
|
|
|
version: number;
|
|
|
|
|
singleEventNotifications: Record<string, boolean>;
|
|
|
|
|
filters: {
|
|
|
|
|
images: {
|
|
|
|
|
NSFW: boolean;
|
|
|
|
|
size: boolean;
|
|
|
|
|
};
|
|
|
|
|
malware: boolean;
|
|
|
|
|
wordFilter: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
words: {
|
|
|
|
|
strict: string[];
|
|
|
|
|
loose: string[];
|
|
|
|
|
};
|
|
|
|
|
allowed: {
|
|
|
|
|
users: string[];
|
|
|
|
|
roles: string[];
|
|
|
|
|
channels: string[];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
invite: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
allowed: {
|
|
|
|
|
channels: string[];
|
|
|
|
|
roles: string[];
|
|
|
|
|
users: string[];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
pings: {
|
|
|
|
|
mass: number;
|
|
|
|
|
everyone: boolean;
|
|
|
|
|
roles: boolean;
|
|
|
|
|
allowed: {
|
|
|
|
|
roles: string[];
|
|
|
|
|
rolesToMention: string[];
|
|
|
|
|
users: string[];
|
|
|
|
|
channels: string[];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
clean: {
|
|
|
|
|
channels: string[];
|
|
|
|
|
allowed: {
|
|
|
|
|
users: string[];
|
|
|
|
|
roles: string[];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
autoPublish: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
channels: string[];
|
|
|
|
|
};
|
|
|
|
|
welcome: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
role: string | null;
|
|
|
|
|
ping: string | null;
|
|
|
|
|
channel: string | null;
|
|
|
|
|
message: string | null;
|
|
|
|
|
};
|
|
|
|
|
stats: Record<string, { name: string; enabled: boolean }>;
|
|
|
|
|
logging: {
|
|
|
|
|
logs: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
channel: string | null;
|
|
|
|
|
toLog: string;
|
|
|
|
|
};
|
|
|
|
|
staff: {
|
|
|
|
|
channel: string | null;
|
|
|
|
|
};
|
|
|
|
|
attachments: {
|
|
|
|
|
channel: string | null;
|
|
|
|
|
saved: Record<string, string>;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
verify: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
role: string | null;
|
|
|
|
|
};
|
|
|
|
|
tickets: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
category: string | null;
|
|
|
|
|
types: string;
|
|
|
|
|
customTypes: string[] | null;
|
|
|
|
|
useCustom: boolean;
|
|
|
|
|
supportRole: string | null;
|
|
|
|
|
maxTickets: number;
|
|
|
|
|
};
|
|
|
|
|
moderation: {
|
|
|
|
|
mute: {
|
|
|
|
|
timeout: boolean;
|
|
|
|
|
role: string | null;
|
|
|
|
|
text: string | null;
|
|
|
|
|
link: string | null;
|
|
|
|
|
};
|
|
|
|
|
kick: {
|
|
|
|
|
text: string | null;
|
|
|
|
|
link: string | null;
|
|
|
|
|
};
|
|
|
|
|
ban: {
|
|
|
|
|
text: string | null;
|
|
|
|
|
link: string | null;
|
|
|
|
|
};
|
|
|
|
|
softban: {
|
|
|
|
|
text: string | null;
|
|
|
|
|
link: string | null;
|
|
|
|
|
};
|
|
|
|
|
warn: {
|
|
|
|
|
text: string | null;
|
|
|
|
|
link: string | null;
|
|
|
|
|
};
|
|
|
|
|
role: {
|
|
|
|
|
role: string | null;
|
|
|
|
|
text: null;
|
|
|
|
|
link: null;
|
|
|
|
|
};
|
|
|
|
|
nick: {
|
|
|
|
|
text: string | null;
|
|
|
|
|
link: string | null;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
tracks: {
|
|
|
|
|
name: string;
|
|
|
|
|
retainPrevious: boolean;
|
|
|
|
|
nullable: boolean;
|
|
|
|
|
track: string[];
|
|
|
|
|
manageableBy: string[];
|
|
|
|
|
}[];
|
|
|
|
|
roleMenu: {
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
allowWebUI: boolean;
|
|
|
|
|
options: {
|
|
|
|
|
name: string;
|
|
|
|
|
description: string;
|
|
|
|
|
min: number;
|
|
|
|
|
max: number;
|
|
|
|
|
options: {
|
|
|
|
|
name: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
role: string;
|
|
|
|
|
}[];
|
|
|
|
|
}[];
|
|
|
|
|
};
|
|
|
|
|
tags: Record<string, string>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface HistorySchema {
|
|
|
|
|
type: string;
|
|
|
|
|
guild: string;
|
|
|
|
|
user: string;
|
|
|
|
|
moderator: string | null;
|
|
|
|
|
reason: string | null;
|
|
|
|
|
occurredAt: Date;
|
|
|
|
|
before: string | null;
|
|
|
|
|
after: string | null;
|
|
|
|
|
amount: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type FlagColors = "red" | "yellow" | "green" | "blue" | "purple" | "gray";
|
|
|
|
|
|
|
|
|
|
export interface ModNoteSchema {
|
|
|
|
|
guild: string;
|
|
|
|
|
user: string;
|
|
|
|
|
note: string | null;
|
|
|
|
|
flag: FlagColors | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PremiumSchema {
|
|
|
|
|
user: string;
|
|
|
|
|
level: number;
|
|
|
|
|
appliesTo: string[];
|
|
|
|
|
expiresAt?: number;
|
|
|
|
|
}
|