From db3d9391fd29139992e3fe840c4621f67e9bbb0e Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 9 Apr 2022 22:43:36 +0200 Subject: [PATCH] global blacklist --- api/src/routes/stats.ts | 17 ++- bot/src/bot/commands/botadm.ts | 137 ++++++++++++++++++++++++- bot/src/bot/commands/botctl.ts | 21 +++- bot/src/bot/commands/warns.ts | 22 ++-- bot/src/bot/modules/command_handler.ts | 9 +- bot/src/bot/modules/event_handler.ts | 31 +++++- bot/src/bot/util.ts | 1 - bot/src/struct/ServerConfig.ts | 1 + 8 files changed, 224 insertions(+), 15 deletions(-) diff --git a/api/src/routes/stats.ts b/api/src/routes/stats.ts index d43fab5..db9de35 100644 --- a/api/src/routes/stats.ts +++ b/api/src/routes/stats.ts @@ -1,4 +1,4 @@ -import { app, logger } from '..'; +import { app, db, logger } from '..'; import { Request, Response } from 'express'; import { botReq } from './internal/ws'; @@ -21,4 +21,17 @@ app.get('/stats', async (req: Request, res: Response) => { res.send({ servers: SERVER_COUNT, }); -}); \ No newline at end of file +}); + +app.get('/stats/global_blacklist', async (req: Request, res: Response) => { + try { + const users = await db.get('users').find({ globalBlacklist: true }); + + res.send({ + total: users.length, + blacklist: users.map(u => ({ id: u.id?.toUpperCase() })), + }); + } catch(e) { + console.error(''+e); + } +}); diff --git a/bot/src/bot/commands/botadm.ts b/bot/src/bot/commands/botadm.ts index 67e37f7..cbead95 100644 --- a/bot/src/bot/commands/botadm.ts +++ b/bot/src/bot/commands/botadm.ts @@ -1,6 +1,6 @@ import SimpleCommand from "../../struct/commands/SimpleCommand"; import MessageCommandContext from "../../struct/MessageCommandContext"; -import { client } from "../.."; +import { client, dbs } from "../.."; import { commands, DEFAULT_PREFIX, ownerIDs } from "../modules/command_handler"; import child_process from 'child_process'; import fs from 'fs'; @@ -9,6 +9,11 @@ import { wordlist } from "../modules/user_scan"; import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { adminBotLog } from "../logging"; import CommandCategory from "../../struct/commands/CommandCategory"; +import { parseUserOrId } from "../util"; +import { ChannelPermission, ServerPermission } from "@janderedev/revolt.js"; + +const BLACKLIST_BAN_REASON = `This user is globally blacklisted and has been banned automatically. If you wish to opt out of the global blacklist, run '/botctl ignore_blacklist yes'.`; +const BLACKLIST_MESSAGE = (username: string) => `\`@${username}\` has been banned automatically. Check the ban reason for more info.`; // id: expireDate const sudoOverrides: { [key: string]: number|null } = {} @@ -30,6 +35,11 @@ const getCommitHash = (): Promise => new Promise((resolve) => { const SUBCOMMANDS: string[] = [ 'stats', 'sudo', + 'userinfo', + 'blacklist', + 'unblacklist', + 'ignore', + 'unignore', ]; export default { @@ -126,6 +136,129 @@ export default { break; } + case 'userinfo': { + const target = await parseUserOrId(args.shift() || ''); + if (!target) return message.reply('Specified user could not be found.'); + + const res = await dbs.USERS.findOne({ id: target._id }); + + if (!res) await message.reply(`Nothing stored about this user.`); + else await message.reply(`\`\`\`json\n${JSON.stringify(res, null, 4)}\n\`\`\``); + + break; + } + + case 'blacklist': { + const target = await parseUserOrId(args.shift() || ''); + if (!target) return message.reply('Specified user could not be found.'); + if (target._id == message.author_id) return message.reply(`no`); + + await dbs.USERS.update({ + id: target._id, + }, { + $setOnInsert: { id: target._id }, + $set: { globalBlacklist: true } + }, { upsert: true }); + + try { + // Ban the user from all shared servers (unless those who opted out) + if (target instanceof User) { + const msg = await message.reply(`User update stored.`); + let bannedServers = 0; + + const mutuals = await target.fetchMutual(); + for (const serverid of mutuals.servers) { + const server = client.servers.get(serverid); + if (!server) continue; + + if (server.permission & ServerPermission.BanMembers) { + const config = await dbs.SERVERS.findOne({ id: server._id }); + if (config?.allowBlacklistedUsers) continue; + + try { + await server.banUser(target._id, { + reason: BLACKLIST_BAN_REASON, + }); + bannedServers++; + + if (server.system_messages?.user_banned) { + const channel = server.channels.find(c => c!._id == server.system_messages!.user_banned); + if (channel && channel.permission & ChannelPermission.SendMessage) { + await channel.sendMessage(BLACKLIST_MESSAGE(target.username)); + } + } + } catch(e) { + console.error(`Failed to ban in ${serverid}: ${e}`); + } + } + } + + if (bannedServers) { + msg?.edit({ content: `User update stored. User has been banned from ${bannedServers} servers.` }); + } + } else await message.reply(`User update stored. No servers are currently shared with this user.`); + } catch(e) { + console.error(''+e); + await message.reply(`Failed to ban target from mutual servers: ${e}\n`); + } + + break; + } + + case 'unblacklist': { + const target = await parseUserOrId(args.shift() || ''); + if (!target) return message.reply('Specified user could not be found.'); + + await dbs.USERS.update({ + id: target._id, + }, { + $setOnInsert: { id: target._id }, + $set: { globalBlacklist: false } + }, { upsert: true }); + + await message.reply(`User update stored. Existing bans will not be lifted automatically.`); + + break; + } + + case 'ignore': { + const target = await parseUserOrId(args.shift() || ''); + if (!target) return message.reply('Specified user could not be found.'); + if (target._id == message.author_id) return message.reply(`no`); + + await dbs.USERS.update( + { id: target._id }, + { + $setOnInsert: { id: target._id }, + $set: { ignore: true }, + }, + { upsert: true } + ); + + await message.reply(`User update stored.`); + + break; + } + + case 'unignore': { + const target = await parseUserOrId(args.shift() || ''); + if (!target) return message.reply('Specified user could not be found.'); + if (target._id == message.author_id) return message.reply(`no`); + + await dbs.USERS.update( + { id: target._id }, + { + $setOnInsert: { id: target._id }, + $set: { ignore: false }, + }, + { upsert: true } + ); + + await message.reply(`User update stored.`); + + break; + } + default: message.reply('Unknown subcommand. Available subcommands: ' + SUBCOMMANDS.join(', ')); } @@ -133,4 +266,4 @@ export default { } } as SimpleCommand; -export { isSudo, updateSudoTimeout } +export { isSudo, updateSudoTimeout, BLACKLIST_BAN_REASON, BLACKLIST_MESSAGE } diff --git a/bot/src/bot/commands/botctl.ts b/bot/src/bot/commands/botctl.ts index 61b832e..8905128 100644 --- a/bot/src/bot/commands/botctl.ts +++ b/bot/src/bot/commands/botctl.ts @@ -47,10 +47,29 @@ export default { userscans = userscans.filter(s => s != message.serverContext._id); } break; + + case 'ignore_blacklist': + try { + if (args[0] == 'yes') { + await dbs.SERVERS.update({ id: message.serverContext._id }, { $set: { allowBlacklistedUsers: true } }); + await message.reply('Globally blacklisted users will no longer get banned in this server. Previously banned users will need to be unbanned manually.'); + } else if (args[0] == 'no') { + await dbs.SERVERS.update({ id: message.serverContext._id }, { $set: { allowBlacklistedUsers: false } }); + await message.reply('Globally blacklisted users will now get banned in this server.'); + } else { + await message.reply(`Please specify either 'yes' or 'no' to toggle this setting.`); + } + } catch(e) { + console.error(''+e); + message.reply('Something went wrong: ' + e); + } + break; + case undefined: case '': message.reply(`### Available subcommands\n` - + `- \`scan_userlist\` - If user scanning is enabled, this will scan the entire user list.`); + + `- \`scan_userlist\` - If user scanning is enabled, this will scan the entire user list.\n` + + `- \`ignore_blacklist\` - Ignore the bot's global blacklist.`); break default: message.reply(`Unknown option`); diff --git a/bot/src/bot/commands/warns.ts b/bot/src/bot/commands/warns.ts index 1799316..5c9dc73 100644 --- a/bot/src/bot/commands/warns.ts +++ b/bot/src/bot/commands/warns.ts @@ -12,6 +12,8 @@ import CommandCategory from "../../struct/commands/CommandCategory"; Day.extend(RelativeTime); +const GLOBAL_BLACKLIST_TEXT = `> :warning: This user has been flagged and is globally blacklisted. [Learn more.](https://github.com/janderedev/automod/wiki/Global-Blacklist)\n\n`; + export default { name: 'warns', aliases: [ 'warnings', 'infractions', 'infraction' ], @@ -66,10 +68,18 @@ export default { let user = await parseUserOrId(args[0]); if (!user?._id) return message.reply('I can\'t find this user.'); - let infs = userInfractions.get(user._id); - if (!infs) return message.reply(`There are no infractions stored for \`${await fetchUsername(user._id)}\`.`); + const infs = userInfractions.get(user._id); + const userConfig = await dbs.USERS.findOne({ id: user._id }); + + if (!infs) return message.reply(`There are no infractions stored for \`${await fetchUsername(user._id)}\`.` + + (userConfig?.globalBlacklist ? '\n' + GLOBAL_BLACKLIST_TEXT : ''), false); else { - let msg = `## ${infs.length} infractions stored for ${await fetchUsername(user._id)}\n\u200b\n`; + let msg = `## ${infs.length} infractions stored for ${await fetchUsername(user._id)}\n`; + + if (userConfig?.globalBlacklist) { + msg += GLOBAL_BLACKLIST_TEXT; + } else msg += '\u200b\n'; + let attachSpreadsheet = false; for (const i in infs) { let inf = infs[i]; @@ -111,12 +121,12 @@ export default { let sheet = Xlsx.utils.aoa_to_sheet(csv_data); let csv = Xlsx.utils.sheet_to_csv(sheet); - message.reply({ content: msg, attachments: [ await uploadFile(csv, `${user._id}.csv`) ] }); + message.reply({ content: msg, attachments: [ await uploadFile(csv, `${user._id}.csv`) ] }, false); } catch(e) { console.error(e); - message.reply(msg); + message.reply(msg, false); } - } else message.reply(msg); + } else message.reply(msg, false); } break; } diff --git a/bot/src/bot/modules/command_handler.ts b/bot/src/bot/modules/command_handler.ts index 6f91d24..5e401b0 100644 --- a/bot/src/bot/modules/command_handler.ts +++ b/bot/src/bot/modules/command_handler.ts @@ -61,10 +61,15 @@ let commands: SimpleCommand[]; if (!await antispam(msg)) return; checkCustomRules(msg); + let [ config, userConfig ] = await Promise.all([ + dbs.SERVERS.findOne({ id: msg.channel!.server_id! }), + dbs.USERS.findOne({ id: msg.author_id }), + ]); + + if (userConfig?.ignore) return; + let args = msg.content.split(' '); let cmdName = args.shift() ?? ''; - - let config = await dbs.SERVERS.findOne({ id: msg.channel!.server_id! }); let guildPrefix = config?.prefix ?? DEFAULT_PREFIX; if (cmdName.startsWith(`<@${client.user?._id}>`)) { diff --git a/bot/src/bot/modules/event_handler.ts b/bot/src/bot/modules/event_handler.ts index 3d04cb6..2dc75a9 100644 --- a/bot/src/bot/modules/event_handler.ts +++ b/bot/src/bot/modules/event_handler.ts @@ -1,7 +1,9 @@ +import { ChannelPermission, ServerPermission } from "@janderedev/revolt.js"; import { ulid } from "ulid"; import { client, dbs } from "../.."; import Infraction from "../../struct/antispam/Infraction"; import InfractionType from "../../struct/antispam/InfractionType"; +import { BLACKLIST_BAN_REASON, BLACKLIST_MESSAGE } from "../commands/botadm"; import logger from "../logger"; import { hasPermForChannel, storeInfraction } from "../util"; import { DEFAULT_PREFIX } from "./command_handler"; @@ -48,7 +50,34 @@ client.on('message', async message => { } as Infraction).catch(console.warn); } catch(e) { console.error(e) } break; - case 'user_joined': break; + case 'user_joined': { + if (!sysMsg.user) break; + + try { + const [ serverConfig, userConfig ] = await Promise.all([ + dbs.SERVERS.findOne({ id: message.channel!.server_id! }), + dbs.USERS.findOne({ id: sysMsg.user._id }), + ]); + + if (userConfig?.globalBlacklist && !serverConfig?.allowBlacklistedUsers) { + const server = message.channel?.server; + if (server && (server?.permission ?? 0) & ServerPermission.BanMembers) { + await server.banUser(sysMsg.user._id, { reason: BLACKLIST_BAN_REASON }); + + if (server.system_messages?.user_banned) { + const channel = server.channels.find(c => c?._id == server.system_messages?.user_banned); + if (channel && channel.permission & ChannelPermission.SendMessage) { + await channel.sendMessage(BLACKLIST_MESSAGE(sysMsg.user.username)); + } + } + } + } + } catch(e) { + console.error(''+e); + } + + break; + }; case 'user_left' : break; } }); diff --git a/bot/src/bot/util.ts b/bot/src/bot/util.ts index 6c7fb4c..086d18d 100644 --- a/bot/src/bot/util.ts +++ b/bot/src/bot/util.ts @@ -2,7 +2,6 @@ import { Member } from "@janderedev/revolt.js/dist/maps/Members"; import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { client, dbs } from ".."; import Infraction from "../struct/antispam/Infraction"; -import ServerConfig from "../struct/ServerConfig"; import FormData from 'form-data'; import axios from 'axios'; import { Server } from "@janderedev/revolt.js/dist/maps/Servers"; diff --git a/bot/src/struct/ServerConfig.ts b/bot/src/struct/ServerConfig.ts index c478680..601fad5 100644 --- a/bot/src/struct/ServerConfig.ts +++ b/bot/src/struct/ServerConfig.ts @@ -26,6 +26,7 @@ class ServerConfig { userScan?: LogConfig // User profile matched word list }; enableUserScan?: boolean; + allowBlacklistedUsers?: boolean; // Whether the server explicitly allows users that are globally blacklisted } export default ServerConfig;