diff --git a/api/src/index.ts b/api/src/index.ts index 306d9fa..c30ff2d 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -18,7 +18,7 @@ app.use(Express.json()); export { logger, app, db, PORT, SESSION_LIFETIME } (async () => { - await Promise.all([ + const promises = [ import('./middlewares/log'), import('./middlewares/updateTokenExpiry'), import('./middlewares/cors'), @@ -27,7 +27,12 @@ export { logger, app, db, PORT, SESSION_LIFETIME } import('./routes/login'), import('./routes/dash/servers'), import('./routes/dash/server'), - ]); + import('./routes/dash/server-automod'), + ]; + + for (const p of promises) await p; + + logger.done('All routes and middlewares loaded'); })(); diff --git a/api/src/middlewares/cors.ts b/api/src/middlewares/cors.ts index ea94c37..13972ab 100644 --- a/api/src/middlewares/cors.ts +++ b/api/src/middlewares/cors.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import { app, logger } from ".."; +import { app } from ".."; app.use('*', (req: Request, res: Response, next: () => void) => { res.header('Access-Control-Allow-Origin', '*'); diff --git a/api/src/routes/dash/server-automod.ts b/api/src/routes/dash/server-automod.ts new file mode 100644 index 0000000..2a1f6f8 --- /dev/null +++ b/api/src/routes/dash/server-automod.ts @@ -0,0 +1,92 @@ +import { app, db } from '../..'; +import { Request, Response } from 'express'; +import { badRequest, isAuthenticated, unauthorized } from '../../utils'; +import { botReq } from '../internal/ws'; +import { FindOneResult } from 'monk'; + +type AntispamRule = { + id: string; + max_msg: number; + timeframe: number; + action: 0|1|2|3|4; + channels: string[] | null; + message: string | null; +} + +app.get('/dash/server/:server/automod', async (req: Request, res: Response) => { + const user = await isAuthenticated(req, res, true); + if (!user) return; + + const { server } = req.params; + if (!server || typeof server != 'string') return badRequest(res); + + const response = await botReq('getUserServerDetails', { user, server }); + if (!response.success) { + return res.status(response.statusCode ?? 500).send({ error: response.error }); + } + + if (!response.server) return res.status(404).send({ error: 'Server not found' }); + + const permissionLevel: 0|1|2|3 = response.perms; + if (permissionLevel < 1) return unauthorized(res, `Only moderators and bot managers may view this.`); + + const serverConfig: FindOneResult = await db.get('servers').findOne({ id: server }); + + const result = { + antispam: (serverConfig.automodSettings?.spam as AntispamRule[]|undefined) + ?.map(r => ({ // Removing unwanted fields from response + action: r.action, + channels: r.channels, + id: r.id, + max_msg: r.max_msg, + message: r.message, + timeframe: r.timeframe, + } as AntispamRule)) + ?? [] + } + + res.send(result); +}); + +app.patch('/dash/server/:server/automod/:ruleid', async (req: Request, res: Response) => { + const user = await isAuthenticated(req, res, true); + if (!user) return; + + const { server, ruleid } = req.params; + const body = req.body; + if (!server || !ruleid) return badRequest(res); + + const response = await botReq('getUserServerDetails', { user, server }); + if (!response.success) { + return res.status(response.statusCode ?? 500).send({ error: response.error }); + } + + if (!response.server) return res.status(404).send({ error: 'Server not found' }); + + const permissionLevel: 0|1|2|3 = response.perms; + if (permissionLevel < 2) return unauthorized(res, `Only bot managers can manage moderation rules.`); + + const serverConfig: FindOneResult = await db.get('servers').findOne({ id: server }); + const antiSpamRules: AntispamRule[] = serverConfig.automodSettings?.spam ?? []; + + const rule = antiSpamRules.find(r => r.id == ruleid); + if (!rule) return res.status(404).send({ error: 'No rule with this ID could be found.' }); + + await db.get('servers').update({ + id: server + }, { + $set: { + "automodSettings.spam.$[rulefilter]": { + ...rule, + action: body.action ?? rule.action, + channels: body.channels ?? rule.channels, + message: body.message ?? rule.message, + max_msg: body.max_msg ?? rule.max_msg, + timeframe: body.timeframe ?? rule.timeframe, + + } as AntispamRule + } + }, { arrayFilters: [ { "rulefilter.id": ruleid } ] }); + + return res.send({ success: true }); +}); diff --git a/api/src/routes/dash/server.ts b/api/src/routes/dash/server.ts index 2f90eae..85c264a 100644 --- a/api/src/routes/dash/server.ts +++ b/api/src/routes/dash/server.ts @@ -4,6 +4,7 @@ import { badRequest, getPermissionLevel, isAuthenticated, unauthorized } from '. import { botReq } from '../internal/ws'; type User = { id: string, username?: string, avatarURL?: string } +type Channel = { id: string, name: string, icon?: string, type: 'VOICE'|'TEXT', nsfw: boolean } type ServerDetails = { id: string, @@ -14,11 +15,12 @@ type ServerDetails = { bannerURL?: string, serverConfig: any, users: User[], + channels: Channel[], } app.get('/dash/server/:server', async (req: Request, res: Response) => { const user = await isAuthenticated(req, res, true); - if (!user) return unauthorized(res); + if (!user) return; const { server } = req.params; if (!server || typeof server != 'string') return badRequest(res); diff --git a/api/src/utils.ts b/api/src/utils.ts index de42593..1c278fa 100644 --- a/api/src/utils.ts +++ b/api/src/utils.ts @@ -37,12 +37,12 @@ async function getSessionInfo(user: string, token: string): Promise return { exists: !!session, valid: !!(session && !session.invalid && session.expires > Date.now()), nonce: session?.nonce } } -function badRequest(res: Response) { - res.status(400).send(JSON.stringify({ "error": "Invalid request body" }, null, 4)); +function badRequest(res: Response, infoText?: string) { + res.status(400).send(JSON.stringify({ "error": "Invalid request body", "info": infoText || undefined }, null, 4)); } -function unauthorized(res: Response) { - res.status(401).send(JSON.stringify({ "error": "Unauthorized" }, null, 4)); +function unauthorized(res: Response, infoText?: string) { + res.status(401).send(JSON.stringify({ "error": "Unauthorized", "info": infoText || undefined }, null, 4)); } async function getPermissionLevel(user: string, server: string) { diff --git a/bot/package.json b/bot/package.json index ceb06c9..85f4188 100644 --- a/bot/package.json +++ b/bot/package.json @@ -13,6 +13,7 @@ "author": "", "license": "ISC", "dependencies": { + "@janderedev/revolt.js": "^5.2.8-patch.1", "@types/monk": "^6.0.0", "axios": "^0.22.0", "dayjs": "^1.10.7", @@ -21,7 +22,6 @@ "form-data": "^4.0.0", "log75": "^2.2.0", "monk": "^7.3.4", - "revolt.js": "^5.2.7", "ulid": "^2.3.0", "xlsx": "^0.17.3" }, diff --git a/bot/src/bot/commands/bot_managers.ts b/bot/src/bot/commands/bot_managers.ts index dc5f39e..c1d4e05 100644 --- a/bot/src/bot/commands/bot_managers.ts +++ b/bot/src/bot/commands/bot_managers.ts @@ -2,7 +2,7 @@ import Command from "../../struct/Command"; import { hasPerm, parseUser } from "../util"; import ServerConfig from "../../struct/ServerConfig"; import { client } from "../.."; -import { User } from "revolt.js/dist/maps/Users"; +import { User } from "@janderedev/revolt.js/dist/maps/Users"; import MessageCommandContext from "../../struct/MessageCommandContext"; const SYNTAX = '/admin add @user; /admin remove @user; /admin list'; diff --git a/bot/src/bot/commands/botadm.ts b/bot/src/bot/commands/botadm.ts index 1ec1031..416d287 100644 --- a/bot/src/bot/commands/botadm.ts +++ b/bot/src/bot/commands/botadm.ts @@ -6,7 +6,7 @@ import child_process from 'child_process'; import fs from 'fs'; import path from 'path'; import { wordlist } from "../modules/user_scan"; -import { User } from "revolt.js/dist/maps/Users"; +import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { adminBotLog } from "../logging"; // id: expireDate diff --git a/bot/src/bot/commands/eval.ts b/bot/src/bot/commands/eval.ts index b5ec43d..4eba7b3 100644 --- a/bot/src/bot/commands/eval.ts +++ b/bot/src/bot/commands/eval.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { inspect } from 'util'; import { client } from "../.."; import MessageCommandContext from "../../struct/MessageCommandContext"; diff --git a/bot/src/bot/commands/help.ts b/bot/src/bot/commands/help.ts index cc65701..68defc1 100644 --- a/bot/src/bot/commands/help.ts +++ b/bot/src/bot/commands/help.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { commands, DEFAULT_PREFIX, ownerIDs } from "../modules/command_handler"; import CommandCategory from "../../struct/CommandCategory"; import MessageCommandContext from "../../struct/MessageCommandContext"; diff --git a/bot/src/bot/commands/kick.ts b/bot/src/bot/commands/kick.ts index 2cf43ab..a15ceac 100644 --- a/bot/src/bot/commands/kick.ts +++ b/bot/src/bot/commands/kick.ts @@ -1,4 +1,4 @@ -import { Member } from "revolt.js/dist/maps/Members"; +import { Member } from "@janderedev/revolt.js/dist/maps/Members"; import { ulid } from "ulid"; import { client } from "../.."; import Infraction from "../../struct/antispam/Infraction"; diff --git a/bot/src/bot/commands/moderator.ts b/bot/src/bot/commands/moderator.ts index 9279768..b7158ce 100644 --- a/bot/src/bot/commands/moderator.ts +++ b/bot/src/bot/commands/moderator.ts @@ -1,9 +1,9 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { isBotManager, NO_MANAGER_MSG, parseUser } from "../util"; import ServerConfig from "../../struct/ServerConfig"; import { client } from "../.."; -import { User } from "revolt.js/dist/maps/Users"; +import { User } from "@janderedev/revolt.js/dist/maps/Users"; import MessageCommandContext from "../../struct/MessageCommandContext"; const SYNTAX = '/mod add @user; /mod remove @user; /mod list'; diff --git a/bot/src/bot/commands/ping.ts b/bot/src/bot/commands/ping.ts index 426ba4b..5f3031a 100644 --- a/bot/src/bot/commands/ping.ts +++ b/bot/src/bot/commands/ping.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { client } from "../.."; import MessageCommandContext from "../../struct/MessageCommandContext"; diff --git a/bot/src/bot/commands/prefix.ts b/bot/src/bot/commands/prefix.ts index fd6539f..d45d736 100644 --- a/bot/src/bot/commands/prefix.ts +++ b/bot/src/bot/commands/prefix.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { client } from "../.."; import ServerConfig from "../../struct/ServerConfig"; import { DEFAULT_PREFIX } from "../modules/command_handler"; diff --git a/bot/src/bot/commands/purge.ts b/bot/src/bot/commands/purge.ts index 0ede4f6..cfe4ead 100644 --- a/bot/src/bot/commands/purge.ts +++ b/bot/src/bot/commands/purge.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { decodeTime } from 'ulid'; import { isModerator, parseUser } from "../util"; import MessageCommandContext from "../../struct/MessageCommandContext"; diff --git a/bot/src/bot/commands/settings.ts b/bot/src/bot/commands/settings.ts index c9963cf..301ae5d 100644 --- a/bot/src/bot/commands/settings.ts +++ b/bot/src/bot/commands/settings.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { client } from "../.."; import AutomodSettings from "../../struct/antispam/AutomodSettings"; import AntispamRule from "../../struct/antispam/AntispamRule"; diff --git a/bot/src/bot/commands/shell_eval.ts b/bot/src/bot/commands/shell_eval.ts index 466309b..d46b3d9 100644 --- a/bot/src/bot/commands/shell_eval.ts +++ b/bot/src/bot/commands/shell_eval.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { exec } from 'child_process'; import MessageCommandContext from "../../struct/MessageCommandContext"; diff --git a/bot/src/bot/commands/test.ts b/bot/src/bot/commands/test.ts index 03077e6..e62788f 100644 --- a/bot/src/bot/commands/test.ts +++ b/bot/src/bot/commands/test.ts @@ -1,5 +1,5 @@ import Command from "../../struct/Command"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import MessageCommandContext from "../../struct/MessageCommandContext"; export default { @@ -8,6 +8,6 @@ export default { description: 'Test command', category: 'misc', run: (message: MessageCommandContext, args: string[]) => { - message.reply('Beep boop.'); + setTimeout(() => message.reply('Beep boop.'), 1000); } } as Command; diff --git a/bot/src/bot/commands/whitelist.ts b/bot/src/bot/commands/whitelist.ts index 9f224cc..0230632 100644 --- a/bot/src/bot/commands/whitelist.ts +++ b/bot/src/bot/commands/whitelist.ts @@ -1,5 +1,5 @@ -import { Message } from "revolt.js/dist/maps/Messages"; -import { User } from "revolt.js/dist/maps/Users"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; +import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { client } from "../.."; import Command from "../../struct/Command"; import MessageCommandContext from "../../struct/MessageCommandContext"; diff --git a/bot/src/bot/modules/antispam.ts b/bot/src/bot/modules/antispam.ts index d0cda7a..7ddb375 100644 --- a/bot/src/bot/modules/antispam.ts +++ b/bot/src/bot/modules/antispam.ts @@ -1,4 +1,4 @@ -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { ulid } from "ulid"; import { client } from "../.."; import AntispamRule from "../../struct/antispam/AntispamRule"; diff --git a/bot/src/bot/modules/api/server_details.ts b/bot/src/bot/modules/api/server_details.ts index 7d2545b..a5bb3d2 100644 --- a/bot/src/bot/modules/api/server_details.ts +++ b/bot/src/bot/modules/api/server_details.ts @@ -1,5 +1,5 @@ -import { Member } from "revolt.js/dist/maps/Members"; -import { User } from "revolt.js/dist/maps/Users"; +import { Member } from "@janderedev/revolt.js/dist/maps/Members"; +import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { client } from "../../.."; import ServerConfig from "../../../struct/ServerConfig"; import { getPermissionLevel } from "../../util"; @@ -7,6 +7,7 @@ import { wsEvents, WSResponse } from "../api_communication"; type ReqData = { user: string, server: string } type APIUser = { id: string, username?: string, avatarURL?: string } +type APIChannel = { id: string, name: string, icon?: string, type: 'VOICE'|'TEXT', nsfw: boolean } type ServerDetails = { id: string, @@ -17,6 +18,7 @@ type ServerDetails = { bannerURL?: string, serverConfig?: ServerConfig, users: APIUser[], + channels: APIChannel[], } wsEvents.on('req:getUserServerDetails', async (data: ReqData, cb: (data: WSResponse) => void) => { @@ -71,6 +73,13 @@ wsEvents.on('req:getUserServerDetails', async (data: ReqData, cb: (data: WSRespo ? { id: u.value._id, avatarURL: u.value.generateAvatarURL(), username: u.value.username } : { id: u.reason } ), + channels: server.channels.filter(c => c != undefined).map(c => ({ + id: c!._id, + name: c!.name ?? '', + nsfw: c!.nsfw ?? false, + type: c!.channel_type == 'VoiceChannel' ? 'VOICE' : 'TEXT', + icon: c!.generateIconURL(), + })), } cb({ success: true, server: response }); diff --git a/bot/src/bot/modules/api/servers.ts b/bot/src/bot/modules/api/servers.ts index 84651ed..6aab4d6 100644 --- a/bot/src/bot/modules/api/servers.ts +++ b/bot/src/bot/modules/api/servers.ts @@ -1,4 +1,4 @@ -import { User } from 'revolt.js/dist/maps/Users'; +import { User } from '@janderedev/revolt.js/dist/maps/Users'; import { client } from '../../..'; import { getPermissionLevel, isBotManager } from '../../util'; import { wsEvents, WSResponse } from '../api_communication'; diff --git a/bot/src/bot/modules/api/users.ts b/bot/src/bot/modules/api/users.ts index 4501427..0a4ced6 100644 --- a/bot/src/bot/modules/api/users.ts +++ b/bot/src/bot/modules/api/users.ts @@ -1,4 +1,4 @@ -import { User } from "revolt.js/dist/maps/Users"; +import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { client } from "../../.."; import { getPermissionLevel, parseUser } from "../../util"; import { wsEvents, WSResponse } from "../api_communication"; diff --git a/bot/src/bot/modules/command_handler.ts b/bot/src/bot/modules/command_handler.ts index 9c4a5f8..3f3d180 100644 --- a/bot/src/bot/modules/command_handler.ts +++ b/bot/src/bot/modules/command_handler.ts @@ -9,7 +9,6 @@ import checkCustomRules from "./custom_rules/custom_rules"; import MessageCommandContext from "../../struct/MessageCommandContext"; import { fileURLToPath } from 'url'; import { getOwnMemberInServer, hasPermForChannel } from "../util"; -import { prepareMessage } from "./prepare_message"; import { isSudo, updateSudoTimeout } from "../commands/botadm"; // thanks a lot esm @@ -96,7 +95,6 @@ let commands: Command[]; let message: MessageCommandContext = msg as MessageCommandContext; message.serverContext = serverCtx; - prepareMessage(message); logger.info(`Command: ${message.author?.username} (${message.author?._id}) in ${message.channel?.server?.name} (${message.channel?.server?._id}): ${message.content}`); diff --git a/bot/src/bot/modules/custom_rules/actions/delete.ts b/bot/src/bot/modules/custom_rules/actions/delete.ts index b721089..ea065aa 100644 --- a/bot/src/bot/modules/custom_rules/actions/delete.ts +++ b/bot/src/bot/modules/custom_rules/actions/delete.ts @@ -1,4 +1,4 @@ -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import CustomRuleAction from "../../../../struct/antispam/CustomRuleAction"; async function execute(message: Message, action: CustomRuleAction) { diff --git a/bot/src/bot/modules/custom_rules/actions/sendMessage.ts b/bot/src/bot/modules/custom_rules/actions/sendMessage.ts index 48962ec..b1172cf 100644 --- a/bot/src/bot/modules/custom_rules/actions/sendMessage.ts +++ b/bot/src/bot/modules/custom_rules/actions/sendMessage.ts @@ -1,4 +1,4 @@ -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { client } from "../../../.."; import CustomRuleAction from "../../../../struct/antispam/CustomRuleAction"; diff --git a/bot/src/bot/modules/custom_rules/actions/warn.ts b/bot/src/bot/modules/custom_rules/actions/warn.ts index 805d531..56fa028 100644 --- a/bot/src/bot/modules/custom_rules/actions/warn.ts +++ b/bot/src/bot/modules/custom_rules/actions/warn.ts @@ -1,4 +1,4 @@ -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import CustomRuleAction from "../../../../struct/antispam/CustomRuleAction"; import { storeInfraction } from '../../../util'; import Infraction from "../../../../struct/antispam/Infraction"; diff --git a/bot/src/bot/modules/custom_rules/custom_rules.ts b/bot/src/bot/modules/custom_rules/custom_rules.ts index b39aaad..e5e18aa 100644 --- a/bot/src/bot/modules/custom_rules/custom_rules.ts +++ b/bot/src/bot/modules/custom_rules/custom_rules.ts @@ -1,4 +1,4 @@ -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { client } from "../../.."; import ServerConfig from "../../../struct/ServerConfig"; import logger from "../../logger"; diff --git a/bot/src/bot/modules/custom_rules/message_content_trigger.ts b/bot/src/bot/modules/custom_rules/message_content_trigger.ts index 4321b39..5bf5c9d 100644 --- a/bot/src/bot/modules/custom_rules/message_content_trigger.ts +++ b/bot/src/bot/modules/custom_rules/message_content_trigger.ts @@ -1,4 +1,4 @@ -import { Message } from "revolt.js/dist/maps/Messages"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { client } from "../../.."; import CustomRuleTrigger from "../../../struct/antispam/CustomRuleTrigger"; import VM from 'vm'; diff --git a/bot/src/bot/modules/mod_logs.ts b/bot/src/bot/modules/mod_logs.ts index 5ba5a8f..6d36bef 100644 --- a/bot/src/bot/modules/mod_logs.ts +++ b/bot/src/bot/modules/mod_logs.ts @@ -1,5 +1,5 @@ -import { Member } from "revolt.js/dist/maps/Members"; -import { Server } from "revolt.js/dist/maps/Servers"; +import { Member } from "@janderedev/revolt.js/dist/maps/Members"; +import { Server } from "@janderedev/revolt.js/dist/maps/Servers"; import { client } from "../.."; import Infraction from "../../struct/antispam/Infraction"; import LogMessage from "../../struct/LogMessage"; diff --git a/bot/src/bot/modules/prepare_message.ts b/bot/src/bot/modules/prepare_message.ts deleted file mode 100644 index 54c5236..0000000 --- a/bot/src/bot/modules/prepare_message.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Message } from "revolt.js/dist/maps/Messages"; -import logger from "../logger"; - -// We modify the way `reply()` works to make sure we -// don't crash if the original message was deleted. - -export function prepareMessage(message: Message) { - message.reply = (...args: Parameters) => { - return new Promise((resolve, reject) => { - message.channel?.sendMessage({ - content: typeof args[0] == 'string' ? args[0] : args[0].content, - replies: [ { id: message._id, mention: args[1] ?? true } ], - }) - ?.then(m => resolve(m)) - .catch(e => { - if (e?.response?.status == 404) { - logger.warn("Replying to message gave 404, trying again without reply"); - if (!message.channel) return reject("Channel does not exist"); - message.channel?.sendMessage(typeof args[0] == 'string' ? { content: args[0] } : args[0]) - .then(resolve) - .catch(reject); - } else reject(e); - }); - }); - } -} diff --git a/bot/src/bot/modules/user_scan.ts b/bot/src/bot/modules/user_scan.ts index 440eeb6..64475ec 100644 --- a/bot/src/bot/modules/user_scan.ts +++ b/bot/src/bot/modules/user_scan.ts @@ -2,7 +2,7 @@ import { client } from "../.."; import fs from 'fs'; import { FindOneResult } from "monk"; import ScannedUser from "../../struct/ScannedUser"; -import { Member } from "revolt.js/dist/maps/Members"; +import { Member } from "@janderedev/revolt.js/dist/maps/Members"; import ServerConfig from "../../struct/ServerConfig"; import logger from "../logger"; import { sendLogMessage } from "../util"; diff --git a/bot/src/bot/util.ts b/bot/src/bot/util.ts index 3e97c0f..24691f5 100644 --- a/bot/src/bot/util.ts +++ b/bot/src/bot/util.ts @@ -1,19 +1,19 @@ -import { Member } from "revolt.js/dist/maps/Members"; -import { User } from "revolt.js/dist/maps/Users"; +import { Member } from "@janderedev/revolt.js/dist/maps/Members"; +import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { client } from ".."; import Infraction from "../struct/antispam/Infraction"; import ServerConfig from "../struct/ServerConfig"; import FormData from 'form-data'; import axios from 'axios'; -import { Server } from "revolt.js/dist/maps/Servers"; +import { Server } from "@janderedev/revolt.js/dist/maps/Servers"; import LogConfig from "../struct/LogConfig"; import LogMessage from "../struct/LogMessage"; import { ColorResolvable, MessageEmbed } from "discord.js"; import logger from "./logger"; import { ulid } from "ulid"; -import { Channel } from "revolt.js/dist/maps/Channels"; -import { ChannelPermission, ServerPermission } from "revolt.js"; -import { Message } from "revolt.js/dist/maps/Messages"; +import { Channel } from "@janderedev/revolt.js/dist/maps/Channels"; +import { ChannelPermission, ServerPermission } from "@janderedev/revolt.js"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { isSudo } from "./commands/botadm"; diff --git a/bot/src/index.ts b/bot/src/index.ts index c0e0d6f..c6fd57a 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -8,7 +8,12 @@ import MongoDB from './bot/db'; logger.info('Initializing client'); let db = MongoDB(); -let client = new AutomodClient({ pongTimeout: 10, onPongTimeout: 'RECONNECT' }, db); +let client = new AutomodClient({ + pongTimeout: 10, + onPongTimeout: 'RECONNECT', + fixReplyCrash: true, + messageTimeoutFix: true +}, db); login(client); export { client } diff --git a/bot/src/struct/AutomodClient.ts b/bot/src/struct/AutomodClient.ts index ed34e7e..282703b 100644 --- a/bot/src/struct/AutomodClient.ts +++ b/bot/src/struct/AutomodClient.ts @@ -1,4 +1,4 @@ -import * as Revolt from "revolt.js"; +import * as Revolt from "@janderedev/revolt.js"; import { IMonkManager } from 'monk'; import logger from '../bot/logger'; import { adminBotLog } from "../bot/logging"; diff --git a/bot/src/struct/Command.ts b/bot/src/struct/Command.ts index 3a75f0e..f201f9d 100644 --- a/bot/src/struct/Command.ts +++ b/bot/src/struct/Command.ts @@ -1,4 +1,4 @@ -import { ChannelPermission, ServerPermission } from "revolt.js"; +import { ChannelPermission, ServerPermission } from "@janderedev/revolt.js"; class Command { name: string; diff --git a/bot/src/struct/MessageCommandContext.ts b/bot/src/struct/MessageCommandContext.ts index 557564e..6af8d30 100644 --- a/bot/src/struct/MessageCommandContext.ts +++ b/bot/src/struct/MessageCommandContext.ts @@ -1,5 +1,5 @@ -import { Message } from "revolt.js/dist/maps/Messages"; -import { Server } from "revolt.js/dist/maps/Servers"; +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; +import { Server } from "@janderedev/revolt.js/dist/maps/Servers"; import logger from "../bot/logger"; class MessageCommandContext extends Message { diff --git a/bot/yarn.lock b/bot/yarn.lock index da93bb2..36275ac 100644 --- a/bot/yarn.lock +++ b/bot/yarn.lock @@ -37,6 +37,23 @@ resolved "https://registry.yarnpkg.com/@insertish/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#5bcd6f73b93efa9ccdb6abf887ae808d40827169" integrity sha512-kFD/p8T4Hkqr992QrdkbW/cQ/W/q2d9MPCobwzBv2PwTKLkCD9RaYDy6m17qRnSLQQ5PU0kHCG8kaOwAqzj1vQ== +"@janderedev/revolt.js@^5.2.8-patch.1": + version "5.2.8-patch.1" + resolved "https://registry.yarnpkg.com/@janderedev/revolt.js/-/revolt.js-5.2.8-patch.1.tgz#e8570090612cb9e0f399f8bc75feed3cbbdfcd2a" + integrity sha512-rUjpp+Nk7/aPdFrSNBorSyvJwIb4fkwRzLB2OODWLesYvlxddJG2PtFujWU4dk3fnQMxpMaQVLq95T2GtsOkdg== + dependencies: + "@insertish/exponential-backoff" "3.1.0-patch.0" + "@insertish/isomorphic-ws" "^4.0.1" + axios "^0.21.4" + eventemitter3 "^4.0.7" + lodash.defaultsdeep "^4.6.1" + lodash.flatten "^4.4.0" + lodash.isequal "^4.5.0" + mobx "^6.3.2" + revolt-api "0.5.3-alpha.12" + ulid "^2.3.0" + ws "^8.2.2" + "@sapphire/async-queue@^1.1.8": version "1.1.9" resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.1.9.tgz#ce69611c8753c4affd905a7ef43061c7eb95c01b" @@ -362,9 +379,9 @@ mime-types@^2.1.12: mime-db "1.50.0" mobx@^6.3.2: - version "6.3.10" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.10.tgz#c3bc715c8f03717b9a2329f9697d42b7998d42e0" - integrity sha512-lfuIN5TGXBNy/5s3ggr1L+IbD+LvfZVlj5q1ZuqyV9AfMtunYQvE8G0WfewS9tgIR3I1q8HJEEbcAOsxEgLwRw== + version "6.3.13" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.13.tgz#93e56a57ee72369f850cf3d6398fd36ee8ef062e" + integrity sha512-zDDKDhYUk9QCHQUdLG+wb4Jv/nXutSLt/P8kkwHyjdbrJO4OZS6QTEsrOnrKM39puqXSrJZHdB6+yRys2NBFFA== mongodb@^3.2.3: version "3.7.2" @@ -504,23 +521,6 @@ revolt-api@0.5.3-alpha.12: resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.12.tgz#78f25b567b840c1fd072595526592a422cb01f25" integrity sha512-MM42oI5+5JJMnAs3JiOwSQOy/SUYzYs3M8YRC5QI4G6HU7CfyB2HNWh5jFsyRlcLdSi13dGazHm31FUPHsxOzw== -revolt.js@^5.2.7: - version "5.2.7" - resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.2.7.tgz#7b887329913494a2caf02c9828685d63551890db" - integrity sha512-KNoQqLrdd/B8zryu2fhWim9rO5OEkouhCZj4nU+upwrekz30DjxqWgZCup/apKXE8PSmrhSgWdKT8SHCBXOxFQ== - dependencies: - "@insertish/exponential-backoff" "3.1.0-patch.0" - "@insertish/isomorphic-ws" "^4.0.1" - axios "^0.21.4" - eventemitter3 "^4.0.7" - lodash.defaultsdeep "^4.6.1" - lodash.flatten "^4.4.0" - lodash.isequal "^4.5.0" - mobx "^6.3.2" - revolt-api "0.5.3-alpha.12" - ulid "^2.3.0" - ws "^8.2.2" - safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -623,9 +623,9 @@ word@~0.3.0: integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== ws@^8.2.2: - version "8.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" - integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== ws@^8.2.3: version "8.3.0" diff --git a/web/src/assets/channel-default-icon.svg b/web/src/assets/channel-default-icon.svg new file mode 100644 index 0000000..fd6abab --- /dev/null +++ b/web/src/assets/channel-default-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/pages/ServerDashboard.tsx b/web/src/pages/ServerDashboard.tsx index 5f4747c..e4b9b8c 100644 --- a/web/src/pages/ServerDashboard.tsx +++ b/web/src/pages/ServerDashboard.tsx @@ -1,19 +1,23 @@ import axios from 'axios'; -import { FunctionComponent, useCallback, useEffect, useState } from "react"; +import React, { FunctionComponent, useCallback, useEffect, useState } from "react"; import { Button } from '@revoltchat/ui/lib/components/atoms/inputs/Button'; import { InputBox } from '@revoltchat/ui/lib/components/atoms/inputs/InputBox'; import { Checkbox } from '@revoltchat/ui/lib/components/atoms/inputs/Checkbox'; +import { ComboBox } from '@revoltchat/ui/lib/components/atoms/inputs/ComboBox'; import { LineDivider } from '@revoltchat/ui/lib/components/atoms/layout/LineDivider'; import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1'; import { H3 } from '@revoltchat/ui/lib/components/atoms/heading/H3'; import { H4 } from '@revoltchat/ui/lib/components/atoms/heading/H4'; +import { H5 } from '@revoltchat/ui/lib/components/atoms/heading/H5'; import { Icon } from '@mdi/react'; import { mdiCloseBox } from '@mdi/js'; import { API_URL } from "../App"; import { getAuthHeaders } from "../utils"; import { useParams } from "react-router-dom"; +import defaultChannelIcon from '../assets/channel-default-icon.svg'; type User = { id: string, username?: string, avatarURL?: string } +type Channel = { id: string, name: string, icon?: string, type: 'VOICE'|'TEXT', nsfw: boolean } type Server = { id?: string, @@ -24,6 +28,16 @@ type Server = { bannerURL?: string, serverConfig?: { [key: string]: any }, users: User[], + channels: Channel[], +} + +type AntispamRule = { + id: string; + max_msg: number; + timeframe: number; + action: 0|1|2|3|4; + channels: string[] | null; + message: string | null; } const ServerDashboard: FunctionComponent = () => { @@ -37,6 +51,8 @@ const ServerDashboard: FunctionComponent = () => { const [botManagers, setBotManagers] = useState([] as string[]); const [moderators, setModerators] = useState([] as string[]); + const [automodSettings, setAutomodSettings] = useState(null as { antispam: AntispamRule[] }|null); + const { serverid } = useParams(); const saveConfig = useCallback(async () => { @@ -71,13 +87,25 @@ const ServerDashboard: FunctionComponent = () => { setBotManagers(server.serverConfig?.botManagers ?? []); setModerators(server.serverConfig?.moderators ?? []); + + loadAutomodInfo(server); } catch(e: any) { console.error(e); setStatus(`${e?.message ?? e}`); } }, [serverInfo]); - useEffect(() => { loadInfo() }, []); + const loadAutomodInfo = useCallback(async (server: Server) => { + if ((server.perms ?? 0) > 0) { + const res = await axios.get(API_URL + `/dash/server/${serverid}/automod`, { headers: await getAuthHeaders() }); + setAutomodSettings(res.data); + console.log(res.data); + } + }, []); + + useEffect(() => { + loadInfo(); + }, []); return ( <> @@ -87,69 +115,115 @@ const ServerDashboard: FunctionComponent = () => {

{serverInfo.description ?? No server description set}


-

Prefix

- { - setPrefix(e.currentTarget.value); - setChanged({ ...changed, prefix: true }); - }} - /> - { - setPrefixAllowSpace(!prefixAllowSpace); - setChanged({ ...changed, prefixAllowSpace: true }); - }} - title="Allow space after prefix" - description={'Whether the bot recognizes a command if the prefix is followed by a space. Enable if your prefix is a word.'} - /> - + <> +

Prefix

+ { + setPrefix(e.currentTarget.value); + setChanged({ ...changed, prefix: true }); + }} + /> + { + setPrefixAllowSpace(!prefixAllowSpace); + setChanged({ ...changed, prefixAllowSpace: true }); + }} + title="Allow space after prefix" + description={'Whether the bot recognizes a command if the prefix is followed by a space. Enable if your prefix is a word.'} + /> + + -

Bot Managers

-

- Only users with "Manage Server" permission are allowed to add/remove other - bot managers and are automatically considered bot manager. -

- - - {botManagers.map((uid: string) => { - const user = serverInfo.users.find(u => u.id == uid) || { id: uid } - return ( - - )})} - - - + <> +

Bot Managers

+

+ Only users with "Manage Server" permission are allowed to add/remove other + bot managers and are automatically considered bot manager. +

+ + + {botManagers.map((uid: string) => { + const user = serverInfo.users.find(u => u.id == uid) || { id: uid } + return ( + + )})} + + + -

Moderators

-

- Only bot managers are allowed to add/remove moderators. - All bot managers are also moderators. -

- - - {moderators.map((uid: string) => { - const user = serverInfo.users.find(u => u.id == uid) || { id: uid } - return ( - - )})} - - - +

Moderators

+

+ Only bot managers are allowed to add/remove moderators. + All bot managers are also moderators. +

+ + + {moderators.map((uid: string) => { + const user = serverInfo.users.find(u => u.id == uid) || { id: uid } + return ( + + )})} + + + + + + + + <> +

Antispam Rules

+ {serverInfo.perms != null && automodSettings && ( + serverInfo.perms > 0 + ? ( + <> + {automodSettings.antispam.map(r => )} + + ) + : ( +
+

+ You do not have access to this. +

+
+ ) + ) + } +
); + function RemoveButton(props: { onClick: () => void }) { + return ( +
+ +
+ ) + } + function UserListEntry(props: { user: User, type: 'MANAGER'|'MOD' }) { return (
{ display: 'inline-block', }} >{props.user.username ?? 'Unknown'} -
{ const res = await axios.delete( `${API_URL}/dash/server/${serverid}/${props.type == 'MANAGER' ? 'managers' : 'mods'}/${props.user.id}`, { headers: await getAuthHeaders() } ); - + if (props.type == 'MANAGER') { setBotManagers(res.data.managers); } @@ -202,13 +270,7 @@ const ServerDashboard: FunctionComponent = () => { setModerators(res.data.mods); } }} - > - -
+ />
); } @@ -248,7 +310,7 @@ const ServerDashboard: FunctionComponent = () => { function UserListAddField(props: { type: 'MANAGER'|'MOD' }) { const [content, setContent] = useState(''); - const onConfirm = useCallback(async () => { + const onConfirm = useCallback(async () => {0 if (content.length) { const res = await axios.put( `${API_URL}/dash/server/${serverid}/${props.type == 'MANAGER' ? 'managers' : 'mods'}`, @@ -291,11 +353,217 @@ const ServerDashboard: FunctionComponent = () => { width: '40px', height: '38px', margin: '4px 8px', + opacity: content.length > 0 ? '1' : '0', }} onClick={onConfirm} >Ok ); } + + function ChannelListAddField(props: { onInput: (channel: Channel) => void }) { + const [content, setContent] = useState(''); + + const onConfirm = useCallback(async () => { + if (content.length) { + const channel = serverInfo.channels + .find(c => c.id == content.toUpperCase()) + || serverInfo.channels + .find(c => c.name == content) + || serverInfo.channels // Prefer channel with same capitalization, + .find(c => c.name.toLowerCase() == content.toLowerCase()); // otherwise search case insensitive + + if (channel && channel.type == 'TEXT') { + props.onInput(channel); + setContent(''); + } + } + }, [content]); + + return ( +
+ setContent(e.currentTarget.value)} + style={{ + float: 'left', + width: '180px', + height: '38px', + margin: '4px 8px', + }} + onKeyDown={e => e.key == 'Enter' && onConfirm()} + /> + +
+ ); + } + + function AntispamRule(props: { rule: AntispamRule }) { + const [maxMsg, setMaxMsg] = useState(props.rule.max_msg); + const [timeframe, setTimeframe] = useState(props.rule.timeframe); + const [action, setAction] = useState(props.rule.action); + const [message, setMessage] = useState(props.rule.message || ''); + const [channels, setChannels] = useState(props.rule.channels ?? []); + const [channelsChanged, setChannelsChanged] = useState(false); + + const save = useCallback(async () => { + await axios.patch( + `${API_URL}/dash/server/${serverid}/automod/${props.rule.id}`, + { + action: action != props.rule.action ? action : undefined, + channels: channelsChanged ? channels : undefined, + max_msg: maxMsg != props.rule.max_msg ? maxMsg : undefined, + message: message != props.rule.message ? message : undefined, + timeframe: timeframe != props.rule.timeframe ? timeframe : undefined, + } as AntispamRule, + { headers: await getAuthHeaders() } + ); + + await loadAutomodInfo(serverInfo); + }, [maxMsg, timeframe, action, message, channels, channelsChanged]); + + const reset = useCallback(() => { + setMaxMsg(props.rule.max_msg); + setTimeframe(props.rule.timeframe); + setAction(props.rule.action); + setMessage(props.rule.message || ''); + setChannels(props.rule.channels ?? []); + setChannelsChanged(false); + }, []); + + const inputStyle: React.CSSProperties = { + maxWidth: '100px', + margin: '8px 8px 0px 8px', + } + + const messagePlaceholders = { + 0: '', + 1: 'Message content...', + 2: '(Optional) Warn reason...', + 3: '', + 4: '', + } + + return ( +
+ +
+ If user sends more than + { + const val = e.currentTarget.value; + if (!isNaN(Number(val)) && val.length <= 4 && Number(val) >= 0) setMaxMsg(Number(val)); + }} /> + messages in + { + const val = e.currentTarget.value; + if (!isNaN(Number(val)) && val.length <= 4 && Number(val) >= 0) setTimeframe(Number(val)); + }} /> + seconds, + setAction(ev.currentTarget.value as any)} + > + + + + + + + = 3 || action == 0 ? 'none' : 'unset' }} + value={message} + placeholder={messagePlaceholders[action] || ''} + onChange={ev => setMessage(ev.currentTarget.value)} + /> + = 3 ? 'unset' : 'none'}}> +
+ "Kick" and "Ban" actions are currently placeholders, they do not have any functionality yet. +
+ +

+ You can specify channels here that this rule will run in. + If left empty, it will run in all channels. +

+ + { + channels.map(cid => { + const channel: Channel = serverInfo.channels.find(c => c.id == cid && c.type == 'TEXT') + || { id: cid, name: 'Unknown channel', nsfw: false, type: 'TEXT' }; + return ( +
+ + {channel.name} + { + setChannels(channels.filter(c => c != cid)); + setChannelsChanged(true); + }} /> +
+ ) + }) + } + { + if (!channels.includes(channel.id)) { + setChannels([ ...channels, channel.id ]); + setChannelsChanged(true); + } + }} /> +
+
+
+
+ + +
+
+
+ ) + } } export default ServerDashboard;