From 30cd5413c4d3154b3421ce92aeaa1cc02974552b Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 20 Dec 2024 02:24:32 +0000 Subject: [PATCH] feat: guild bans --- src/client.js | 82 +++++++++++++++++++++++++---------- src/http.js | 8 ++++ src/index.js | 37 ++++++++++------ src/listeners/client/ready.js | 2 + src/stdin/reload.js | 21 +++++++++ src/user/banned-guilds.txt | 0 6 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 src/stdin/reload.js create mode 100644 src/user/banned-guilds.txt diff --git a/src/client.js b/src/client.js index 98f4f6d..09e9af3 100644 --- a/src/client.js +++ b/src/client.js @@ -3,6 +3,7 @@ const { GatewayIntentBits, Partials, } = require('discord.js'); +const logger = require('./lib/logger'); const { PrismaClient } = require('@prisma/client'); const Keyv = require('keyv'); const I18n = require('@eartharoid/i18n'); @@ -14,7 +15,7 @@ const sqliteMiddleware = require('./lib/middleware/prisma-sqlite'); const ms = require('ms'); module.exports = class Client extends FrameworkClient { - constructor(config, log) { + constructor() { super( { intents: [ @@ -39,6 +40,12 @@ module.exports = class Client extends FrameworkClient { { baseDir: __dirname }, ); + this.config = {}; + this.log = {}; + this.init(); + } + + async init(reload = false) { const locales = {}; fs.readdirSync(join(__dirname, 'i18n')) .filter(file => file.endsWith('.yml')) @@ -48,30 +55,57 @@ module.exports = class Client extends FrameworkClient { locales[name] = YAML.parse(data); }); - this.keyv = new Keyv(); /** @type {I18n} */ this.i18n = new I18n('en-GB', locales); - /** @type {TicketManager} */ - this.tickets = new TicketManager(this); - this.config = config; - this.log = log; - this.supers = (process.env.SUPER ?? '').split(','); - /** @param {import('discord.js/typings').Interaction} interaction */ - this.commands.interceptor = async interaction => { - if (!interaction.inGuild()) return; - const id = interaction.guildId; - const cacheKey = `cache/known/guild:${id}`; - if (await this.keyv.has(cacheKey)) return; - await this.prisma.guild.upsert({ - create: { - id, - locale: this.i18n.locales.find(locale => locale === interaction.guild.preferredLocale), // undefined if not supported - }, - update: {}, - where: { id }, - }); - await this.keyv.set(cacheKey, true); - }; + + // to maintain references, these shouldn't be reassigned + Object.assign(this.config, YAML.parse(fs.readFileSync('./user/config.yml', 'utf8'))); + Object.assign(this.log, logger(this.config)); + + this.banned_guilds = new Set( + (() => { + let array = fs.readFileSync('./user/banned-guilds.txt', 'utf8').trim().split(/\r?\n/); + if (array[0] === '') array = []; + return array; + })(), + ); + this.log.info(`${this.banned_guilds.size} guilds are banned`); + + if (reload) { + await this.initAfterLogin(); + } else { + this.keyv = new Keyv(); + + this.tickets = new TicketManager(this); + + this.supers = (process.env.SUPER ?? '').split(','); + + /** @param {import('discord.js/typings').Interaction} interaction */ + this.commands.interceptor = async interaction => { + if (!interaction.inGuild()) return; + const id = interaction.guildId; + const cacheKey = `cache/known/guild:${id}`; + if (await this.keyv.has(cacheKey)) return; + await this.prisma.guild.upsert({ + create: { + id, + locale: this.i18n.locales.find(locale => locale === interaction.guild.preferredLocale), // undefined if not supported + }, + update: {}, + where: { id }, + }); + await this.keyv.set(cacheKey, true); + }; + } + } + + async initAfterLogin() { + for (const id of this.banned_guilds) { + if (this.guilds.cache.has(id)) { + this.log.info(`Leaving banned guild ${id}`); + await this.guilds.cache.get(id).leave(); + } + } } async login(token) { @@ -86,7 +120,7 @@ module.exports = class Client extends FrameworkClient { }; if (process.env.DB_PROVIDER === 'sqlite' && !process.env.DB_CONNECTION_URL) { - prisma_options.datasources = { db: { url:'file:' + join(process.cwd(), './user/database.db') } }; + prisma_options.datasources = { db: { url: 'file:' + join(process.cwd(), './user/database.db') } }; } /** @type {PrismaClient} */ diff --git a/src/http.js b/src/http.js index 688795f..a158fd0 100644 --- a/src/http.js +++ b/src/http.js @@ -105,6 +105,14 @@ module.exports = async client => { }); } + if (client.banned_guilds.has(guildId)) { + return res.code(451).send({ + error: 'Unavailable For Legal Reasons', + message: 'This guild has been banned for breaking the terms of service.', + statusCode: 451, + + }); + } const guildMember = await guild.members.fetch(userId); const isAdmin = await getPrivilegeLevel(guildMember) >= 2; if (!isAdmin) { diff --git a/src/index.js b/src/index.js index 6554f04..90e43b0 100644 --- a/src/index.js +++ b/src/index.js @@ -37,6 +37,7 @@ if (!semver.satisfies(process.versions.node, pkg.engines.node)) { process.exit(1); } +// check cwd const base_dir = path.resolve(path.join(__dirname, '../')); const cwd = path.resolve(process.cwd()); if (base_dir !== cwd) { @@ -49,26 +50,17 @@ if (base_dir !== cwd) { console.log(colours.blueBright(' Learn more at https://lnk.earth/dt-cwd.')); } -// this could be done first, but then there would be no banner :( process.env.NODE_ENV ??= 'production'; // make sure NODE_ENV is set require('./env').load(); // load and check environment variables const fs = require('fs'); const YAML = require('yaml'); const logger = require('./lib/logger'); -const Client = require('./client'); -const http = require('./http'); -// user directory may or may not exist depending on if sqlite is being used -// so the config file could be missing even though the directory exists -if (!fs.existsSync('./user/config.yml')) { - console.log('The config file does not exist, copying defaults...'); - fs.cpSync(path.join(__dirname, 'user'), './user', { recursive: true }); - console.log('Created user directory at', path.join(cwd, 'user')); -} - -const config = YAML.parse(fs.readFileSync('./user/config.yml', 'utf8')); -const log = logger(config); +// create a Logger using the default config +// and set listeners as early as possible. +let config = YAML.parse(fs.readFileSync(path.join(__dirname, 'user/config.yml'), 'utf8')); +let log = logger(config); process.on('uncaughtException', (error, origin) => { log.notice(`Discord Tickets v${pkg.version} on Node.js ${process.version} (${process.platform})`); @@ -78,7 +70,26 @@ process.on('uncaughtException', (error, origin) => { process.on('warning', warning => log.warn(warning.stack || warning)); +const Client = require('./client'); +const http = require('./http'); + +// the `user` directory may or may not exist depending on if sqlite is being used. +// copy any files that don't already exist +fs.cpSync(path.join(__dirname, 'user'), './user', { + force: false, + recursive: true, +}); + +// initialise the framework and client, +// which also loads the custom config and creates a new Logger. const client = new Client(config, log); + +// allow any config changes to affect the above listeners +// as long as these `client` properties are not reassigned. +config = client.config; +log = client.log; + +// start the bot and then the web server client.login().then(() => { http(client); }); diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 11aaec7..d8060ea 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -33,6 +33,8 @@ module.exports = class extends Listener { process.title = 'tickets'; client.log.success('Connected to Discord as "%s" over %d shards', client.user.tag, client.ws.shards.size); + await client.initAfterLogin(); + // fill cache await sync(client); diff --git a/src/stdin/reload.js b/src/stdin/reload.js new file mode 100644 index 0000000..0d854a4 --- /dev/null +++ b/src/stdin/reload.js @@ -0,0 +1,21 @@ +const { StdinCommand } = require('@eartharoid/dbf'); + +module.exports = class extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'reload', + }); + } + + async run() { + this.client.log.warn('Reloading is not the same as restarting!'); + this.client.log.info('Reinitialising client...'); + await this.client.init(true); + this.client.log.success('Client reinitialised'); + // TODO: fix this + // this.client.log.info('Reloading module components...'); + // this.client.mods.forEach(mod => mod.components.forEach(component => component.reload())); + // this.client.log.success('Components reloaded'); + } +}; diff --git a/src/user/banned-guilds.txt b/src/user/banned-guilds.txt new file mode 100644 index 0000000..e69de29