From 55504e3071f3538ea66b1229c33ae65fe29f2855 Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 30 Apr 2022 11:25:24 +0200 Subject: [PATCH 1/7] allow changing api url --- .env.example | 4 ++++ bot/src/index.ts | 3 ++- docker-compose.yml.example | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index c0e2f61..7df25e6 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,10 @@ # after initializing the database. DB_PASS= +# Base URL of the Revolt API to connect to. +# Defaults to https://api.revolt.chat +API_URL= + # Your bot account's token. BOT_TOKEN= diff --git a/bot/src/index.ts b/bot/src/index.ts index 48657e0..0771f66 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -21,7 +21,8 @@ let client = new AutomodClient({ // pongTimeout: 10, // onPongTimeout: 'RECONNECT', fixReplyCrash: true, - messageTimeoutFix: true + messageTimeoutFix: true, + apiURL: process.env.API_URL, }, db); login(client); diff --git a/docker-compose.yml.example b/docker-compose.yml.example index 0c71ec8..2f6b878 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -23,6 +23,7 @@ services: - BOT_METRICS_MSG_PING_CHANNEL - BOT_STATUS - BOT_STATUS_INTERVAL + - API_URL # Uncomment if you enabled Prometheus metrics #ports: # - 127.0.0.1:${BOT_METRICS_PORT}:${BOT_METRICS_PORT} @@ -53,6 +54,7 @@ services: - DB_STRING=mongodb://mogus:${DB_PASS}@mongo:27017/admin - NODE_ENV=production - BRIDGE_METRICS_PORT + - API_URL # Uncomment if you enabled Prometheus metrics #ports: # - 127.0.0.1:${BRIDGE_METRICS_PORT}:${BRIDGE_METRICS_PORT} From 0917a476fcf7cbaa239cf0e2ce759501550f03d8 Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 30 Apr 2022 15:57:37 +0200 Subject: [PATCH 2/7] compat with revolt.js 6.0 --- bot/package.json | 2 +- bot/src/bot/commands/ban.ts | 3 + bot/src/bot/commands/botadm.ts | 6 +- bot/src/bot/commands/bridge.ts | 2 - bot/src/bot/commands/kick.ts | 7 +- bot/src/bot/commands/test.ts | 1 - bot/src/bot/commands/unban.ts | 3 + bot/src/bot/modules/event_handler.ts | 8 +- bot/src/bot/modules/tempbans.ts | 1 + bot/src/bot/modules/user_scan.ts | 4 +- bot/src/bot/util.ts | 23 ++--- bot/src/struct/AutomodClient.ts | 7 ++ bot/yarn.lock | 127 ++++++++++++++++++++++++--- 13 files changed, 154 insertions(+), 40 deletions(-) diff --git a/bot/package.json b/bot/package.json index 7d6311e..ecc45d7 100644 --- a/bot/package.json +++ b/bot/package.json @@ -13,7 +13,7 @@ "author": "", "license": "ISC", "dependencies": { - "@janderedev/revolt.js": "^5.2.8-patch.2", + "@janderedev/revolt.js": "^6.0.0-rc.21", "@types/monk": "^6.0.0", "axios": "^0.22.0", "dayjs": "^1.10.7", diff --git a/bot/src/bot/commands/ban.ts b/bot/src/bot/commands/ban.ts index d435625..d99e1db 100644 --- a/bot/src/bot/commands/ban.ts +++ b/bot/src/bot/commands/ban.ts @@ -24,6 +24,9 @@ export default { run: async (message: MessageCommandContext, args: string[]) => { if (!await isModerator(message)) return message.reply(NO_MANAGER_MSG); + if (!message.serverContext.havePermission('BanMembers')) { + return await message.reply(`Sorry, I do not have \`BanMembers\` permission.`); + } if (args.length == 0) return message.reply(`You need to provide a target user!`); diff --git a/bot/src/bot/commands/botadm.ts b/bot/src/bot/commands/botadm.ts index 0acfae4..f2dce12 100644 --- a/bot/src/bot/commands/botadm.ts +++ b/bot/src/bot/commands/botadm.ts @@ -10,7 +10,7 @@ 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"; +import { Permission } from "@janderedev/revolt.js/dist/permissions/definitions"; 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.`; @@ -172,7 +172,7 @@ export default { const server = client.servers.get(serverid); if (!server) continue; - if (server.permission & ServerPermission.BanMembers) { + if (server.havePermission('BanMembers')) { const config = await dbs.SERVERS.findOne({ id: server._id }); if (config?.allowBlacklistedUsers) continue; @@ -184,7 +184,7 @@ export default { 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) { + if (channel && channel.havePermission('SendMessage')) { await channel.sendMessage(BLACKLIST_MESSAGE(target.username)); } } diff --git a/bot/src/bot/commands/bridge.ts b/bot/src/bot/commands/bridge.ts index 5a604aa..7016f08 100644 --- a/bot/src/bot/commands/bridge.ts +++ b/bot/src/bot/commands/bridge.ts @@ -71,7 +71,6 @@ export default { content: '#', embeds: [ { - type: 'Text', title: `Bridges in ${message.channel?.server?.name}`, description: `**${links.length}** bridged channels found.\n\n` + links.map(l => `<#${l.revolt}> **->** ${l.discord}`).join('\n'), @@ -85,7 +84,6 @@ export default { content: '#', embeds: [ { - type: 'Text', title: 'Discord Bridge', description: `Bridges allow you to link your Revolt server to a Discord server ` + `by relaying all messages.\n\n` diff --git a/bot/src/bot/commands/kick.ts b/bot/src/bot/commands/kick.ts index a7785ed..ddccc32 100644 --- a/bot/src/bot/commands/kick.ts +++ b/bot/src/bot/commands/kick.ts @@ -19,10 +19,13 @@ export default { run: async (message: MessageCommandContext, args: string[]) => { if (!await isModerator(message)) return message.reply(NO_MANAGER_MSG); - + if (!message.serverContext.havePermission('KickMembers')) { + return await message.reply(`Sorry, I do not have \`KickMembers\` permission.`); + } + if (args.length == 0) return message.reply(`You need to provide a target user!`); - + let targetUser = await parseUser(args.shift()!); if (!targetUser) return message.reply('Sorry, I can\'t find that user.'); diff --git a/bot/src/bot/commands/test.ts b/bot/src/bot/commands/test.ts index 4aadd60..a43a8bf 100644 --- a/bot/src/bot/commands/test.ts +++ b/bot/src/bot/commands/test.ts @@ -12,7 +12,6 @@ export default { content: 'Beep boop.', embeds: [ { - type: "Text", colour: "#ff0000", description: "embed description", title: "embed title", diff --git a/bot/src/bot/commands/unban.ts b/bot/src/bot/commands/unban.ts index 39b4f74..245a604 100644 --- a/bot/src/bot/commands/unban.ts +++ b/bot/src/bot/commands/unban.ts @@ -15,6 +15,9 @@ export default { category: CommandCategory.Moderation, run: async (message: MessageCommandContext, args: string[]) => { if (!await isModerator(message)) return message.reply(NO_MANAGER_MSG); + if (!message.serverContext.havePermission('BanMembers')) { + return await message.reply(`Sorry, I do not have \`BanMembers\` permission.`); + } let checkTempBans = async (id: string): Promise => { let tempbans = await dbs.TEMPBANS.find({ bannedUser: id, server: message.serverContext._id }); diff --git a/bot/src/bot/modules/event_handler.ts b/bot/src/bot/modules/event_handler.ts index 2dc75a9..6e10d92 100644 --- a/bot/src/bot/modules/event_handler.ts +++ b/bot/src/bot/modules/event_handler.ts @@ -1,4 +1,4 @@ -import { ChannelPermission, ServerPermission } from "@janderedev/revolt.js"; +import { Permission } from "@janderedev/revolt.js/dist/permissions/definitions"; import { ulid } from "ulid"; import { client, dbs } from "../.."; import Infraction from "../../struct/antispam/Infraction"; @@ -61,12 +61,12 @@ client.on('message', async message => { if (userConfig?.globalBlacklist && !serverConfig?.allowBlacklistedUsers) { const server = message.channel?.server; - if (server && (server?.permission ?? 0) & ServerPermission.BanMembers) { + if (server && server.havePermission('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) { + if (channel && channel.havePermission('SendMessage')) { await channel.sendMessage(BLACKLIST_MESSAGE(sysMsg.user.username)); } } @@ -102,7 +102,7 @@ client.on('member/join', (member) => { c => c && c.channel_type == 'TextChannel' && hasPermForChannel(member, c, 'SendMessage') - && hasPermForChannel(member, c, 'EmbedLinks') + && hasPermForChannel(member, c, 'SendEmbeds') ); // Attempt to find an appropriate channel, otherwise use the first one available diff --git a/bot/src/bot/modules/tempbans.ts b/bot/src/bot/modules/tempbans.ts index eb88ab7..3401d68 100644 --- a/bot/src/bot/modules/tempbans.ts +++ b/bot/src/bot/modules/tempbans.ts @@ -29,6 +29,7 @@ async function processUnban(ban: TempBan) { if (expired.includes(ban.id)) return; let server = client.servers.get(ban.server) || await client.servers.fetch(ban.server); + if (!server.havePermission('BanMembers')) return logger.debug(`No permission to process unbans in ${server._id}, skipping`); let serverBans = await server.fetchBans(); if (serverBans.bans.find(b => b._id.user == ban.bannedUser)) { diff --git a/bot/src/bot/modules/user_scan.ts b/bot/src/bot/modules/user_scan.ts index d63a5c0..70bcf5d 100644 --- a/bot/src/bot/modules/user_scan.ts +++ b/bot/src/bot/modules/user_scan.ts @@ -73,8 +73,8 @@ async function scanUser(member: Member) { lastLoggedProfile: { username: user.username, nickname: member.nickname || undefined, - profile: profile.content, - status: user.status?.text, + profile: profile.content || undefined, + status: user.status?.text || undefined, } } }); diff --git a/bot/src/bot/util.ts b/bot/src/bot/util.ts index 086d18d..69547c0 100644 --- a/bot/src/bot/util.ts +++ b/bot/src/bot/util.ts @@ -11,11 +11,10 @@ import { ColorResolvable, MessageEmbed } from "discord.js"; import logger from "./logger"; import { ulid } from "ulid"; import { Channel } from "@janderedev/revolt.js/dist/maps/Channels"; -import { ChannelPermission, ServerPermission } from "@janderedev/revolt.js"; +import { Permission } from "@janderedev/revolt.js/dist/permissions/definitions"; import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { isSudo } from "./commands/botadm"; - const NO_MANAGER_MSG = '🔒 Missing permission'; const ULID_REGEX = /^[0-9A-HJ-KM-NP-TV-Z]{26}$/i; const USER_MENTION_REGEX = /^<@[0-9A-HJ-KM-NP-TV-Z]{26}>$/i; @@ -135,20 +134,22 @@ function getPermissionBasedOnRole(member: Member): 0|1|2|3 { return 0; } -function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean { - let p = ServerPermission[perm]; +/** + * @deprecated Unnecessary + */ +function hasPerm(member: Member, perm: keyof typeof Permission): boolean { + let p = Permission[perm]; if (member.server?.owner == member.user?._id) return true; - // this should work but im not 100% certain - let userPerm = member.roles?.map(id => member.server?.roles?.[id]?.permissions?.[0]) - .reduce((sum?: number, cur?: number) => sum! | cur!, member.server?.default_permissions[0]) ?? 0; - - return !!(userPerm & p); + return member.hasPermission(member.server!, perm); } -function hasPermForChannel(member: Member, channel: Channel, perm: keyof typeof ChannelPermission): boolean { +/** + * @deprecated Unnecessary + */ +function hasPermForChannel(member: Member, channel: Channel, perm: keyof typeof Permission): boolean { if (!member.server) throw 'hasPermForChannel(): Server is undefined'; - return !!(channel.permission & ChannelPermission[perm]); + return member.hasPermission(channel, perm); } async function getOwnMemberInServer(server: Server): Promise { diff --git a/bot/src/struct/AutomodClient.ts b/bot/src/struct/AutomodClient.ts index 282703b..2e99604 100644 --- a/bot/src/struct/AutomodClient.ts +++ b/bot/src/struct/AutomodClient.ts @@ -29,6 +29,13 @@ let login = (client: Revolt.Client): Promise => new Promise((resolve, reje adminBotLog({ message: 'Bot logged in', type: 'INFO' }); resolve(); }); + + client.on('packet', packet => { + if (packet.type == 'InvalidSession' as any) { + logger.error('Authentication failed: ' + JSON.stringify(packet)); + process.exit(99); + } + }); }); export default AutomodClient; diff --git a/bot/yarn.lock b/bot/yarn.lock index e2ad57d..8988d54 100644 --- a/bot/yarn.lock +++ b/bot/yarn.lock @@ -27,30 +27,41 @@ combined-stream "^1.0.8" mime-types "^2.1.12" -"@insertish/exponential-backoff@3.1.0-patch.0": - version "3.1.0-patch.0" - resolved "https://registry.yarnpkg.com/@insertish/exponential-backoff/-/exponential-backoff-3.1.0-patch.0.tgz#1fff134f70fc0906d11d09069d51183b542e42cf" - integrity sha512-1qhW81s3GDbssyaxRWdpBAn1cIw5s393HnrdYFjfa2i6rq3SEjQDK6U80g6dq/K0or6JRr2eCn75Wfc1IrGM0g== +"@insertish/exponential-backoff@3.1.0-patch.2": + version "3.1.0-patch.2" + resolved "https://registry.yarnpkg.com/@insertish/exponential-backoff/-/exponential-backoff-3.1.0-patch.2.tgz#08310c856b795c783a832f3f608c0feaff262c0d" + integrity sha512-0lsMVexkZ7dHpQlPrTZDRsm42hZ4/CzKxP1hbAEJH8IaEwrIEhhTKJuFxPuLOi2TwPImuocncjYHpGpwVNOemQ== "@insertish/isomorphic-ws@^4.0.1": version "4.0.1" 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.2": - version "5.2.8-patch.2" - resolved "https://registry.yarnpkg.com/@janderedev/revolt.js/-/revolt.js-5.2.8-patch.2.tgz#d74a6500e217a4cb94d139027dec2acb71f73a0f" - integrity sha512-2nucMuToKC5F4jsajmTauBaV9Q7oyl1KWKF0ysYYPzEKxK9LfPL560v9bQR/ff1g8GkEv4biAN/A17MCcddAuw== +"@insertish/oapi@0.1.15": + version "0.1.15" + resolved "https://registry.yarnpkg.com/@insertish/oapi/-/oapi-0.1.15.tgz#ee58b82d879ed5af54db846699941b8ad0262e7b" + integrity sha512-2G8eFxrojF651tBoRutQ8CJOw0u8UjYSlw/LQU+xycw5KpXslhWp/KSR86uIFyDIPddwfMCNA91vwHGCFRA63w== dependencies: - "@insertish/exponential-backoff" "3.1.0-patch.0" + typescript "^4.6.2" + optionalDependencies: + axios "^0.26.1" + openapi-typescript "^5.2.0" + +"@janderedev/revolt.js@^6.0.0-rc.21": + version "6.0.0-rc.21" + resolved "https://registry.yarnpkg.com/@janderedev/revolt.js/-/revolt.js-6.0.0-rc.21.tgz#0629700cd78d0f7db5f4707dc7a8f5ba67482e1a" + integrity sha512-uLFXrv6ZjLJ+e9keevv0sUeZY8gDw3RfSg07LNYVUfqi1T64WwmZypz3zc2kTacbfthIgI368K2e6f3q4toc4Q== + dependencies: + "@insertish/exponential-backoff" "3.1.0-patch.2" "@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" + long "^5.2.0" mobx "^6.3.2" - revolt-api "0.5.3-alpha.12" + revolt-api "0.5.3-rc.15" ulid "^2.3.0" ws "^8.2.2" @@ -126,6 +137,11 @@ ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -145,6 +161,13 @@ axios@^0.22.0: dependencies: follow-redirects "^1.14.4" +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -301,6 +324,11 @@ follow-redirects@^1.14.0, follow-redirects@^1.14.4: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== +follow-redirects@^1.14.8: + version "1.14.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== + form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -324,6 +352,16 @@ frac@~1.1.2: resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b" integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== +globalyzer@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" + integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -344,6 +382,13 @@ isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + lodash.defaultsdeep@^4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" @@ -366,6 +411,11 @@ log75@^2.2.0: dependencies: ansi-colors "^4.1.1" +long@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" + integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" @@ -383,6 +433,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.50.0" +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mobx@^6.3.2: version "6.3.13" resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.13.tgz#93e56a57ee72369f850cf3d6398fd36ee8ef062e" @@ -464,6 +519,18 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +openapi-typescript@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-5.2.0.tgz#c0712d7a17e4502ac083162c42f946c0e966a505" + integrity sha512-EGoPTmxrpiN40R6An5Wqol2l74sRb773pv/KXWYCQaMCXNtMGN7Jv+y/jY4B1Bd4hsIW2j9GFmQXxqfGmOXaxA== + dependencies: + js-yaml "^4.1.0" + mime "^3.0.0" + prettier "^2.5.1" + tiny-glob "^0.2.9" + undici "^4.14.1" + yargs-parser "^21.0.0" + optional-require@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.1.8.tgz#16364d76261b75d964c482b2406cb824d8ec44b7" @@ -483,6 +550,11 @@ ow@^0.27.0: type-fest "^1.2.1" vali-date "^1.0.0" +prettier@^2.5.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== + printj@~1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" @@ -528,10 +600,14 @@ require-at@^1.0.6: resolved "https://registry.yarnpkg.com/require-at/-/require-at-1.0.6.tgz#9eb7e3c5e00727f5a4744070a7f560d4de4f6e6a" integrity sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g== -revolt-api@0.5.3-alpha.12: - version "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-api@0.5.3-rc.15: + version "0.5.3-rc.15" + resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-rc.15.tgz#abd08dd8109d0ca31be118461eabbeb6c3b7792e" + integrity sha512-MYin3U+KoObNkILPf2cz+FPperynExkUu7CjzurMJCRvBncpnhb2czvWDvnhLDKBHlpo8W597xNqzQnaklV4ug== + dependencies: + "@insertish/oapi" "0.1.15" + axios "^0.26.1" + lodash.defaultsdeep "^4.6.1" safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.2.1" @@ -578,6 +654,14 @@ tdigest@^0.1.1: dependencies: bintrees "1.0.1" +tiny-glob@^0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" + integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== + dependencies: + globalyzer "0.1.0" + globrex "^0.1.2" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -603,11 +687,21 @@ typescript@^4.4.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== +typescript@^4.6.2: + version "4.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" + integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== + ulid@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/ulid/-/ulid-2.3.0.tgz#93063522771a9774121a84d126ecd3eb9804071f" integrity sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw== +undici@^4.14.1: + version "4.16.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-4.16.0.tgz#469bb87b3b918818d3d7843d91a1d08da357d5ff" + integrity sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw== + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -666,3 +760,8 @@ xlsx@^0.17.3: ssf "~0.11.2" wmf "~1.0.1" word "~0.3.0" + +yargs-parser@^21.0.0: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== From 9d035c4e2b6cee37e57d72163f1880c78b150b56 Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 30 Apr 2022 16:17:46 +0200 Subject: [PATCH 3/7] port bridge to revolt.js 6.0 --- bridge/package.json | 2 +- bridge/src/discord/events.ts | 9 ++++--- bridge/src/revolt/client.ts | 6 +++-- bridge/src/revolt/events.ts | 2 +- bridge/yarn.lock | 48 +++++++++++++++++++++++++----------- docker-compose.yml.example | 2 +- 6 files changed, 47 insertions(+), 22 deletions(-) diff --git a/bridge/package.json b/bridge/package.json index 99aa2a6..e6a54a9 100644 --- a/bridge/package.json +++ b/bridge/package.json @@ -14,7 +14,7 @@ "license": "ISC", "dependencies": { "@discordjs/rest": "^0.4.1", - "@janderedev/revolt.js": "^5.2.8-patch.2", + "@janderedev/revolt.js": "^6.0.0-rc.21", "axios": "^0.26.1", "discord-api-types": "^0.31.2", "discord.js": "^13.6.0", diff --git a/bridge/src/discord/events.ts b/bridge/src/discord/events.ts index 20760e3..e46854e 100644 --- a/bridge/src/discord/events.ts +++ b/bridge/src/discord/events.ts @@ -1,7 +1,6 @@ import { BRIDGED_MESSAGES, BRIDGE_CONFIG, logger } from ".."; import { client } from "./client"; import { AUTUMN_URL, client as revoltClient } from "../revolt/client"; -import { ChannelPermission } from "@janderedev/revolt.js"; import axios from 'axios'; import { ulid } from "ulid"; import GenericEmbed from "../types/GenericEmbed"; @@ -84,11 +83,15 @@ client.on('messageCreate', async message => { const channel = revoltClient.channels.get(bridgeCfg.revolt); if (!channel) return logger.debug(`Discord: Cannot find associated channel`); - if (!(channel.permission & ChannelPermission.SendMessage)) { + if (!(channel.havePermission('SendMessage'))) { return logger.debug(`Discord: Lacking SendMessage permission; refusing to send`); } - if (!(channel.permission & ChannelPermission.Masquerade)) { + if (!(channel.havePermission('SendEmbeds'))) { + return logger.debug(`Discord: Lacking SendEmbeds permission; refusing to send`); + } + + if (!(channel.havePermission('Masquerade'))) { return logger.debug(`Discord: Lacking Masquerade permission; refusing to send`); } diff --git a/bridge/src/revolt/client.ts b/bridge/src/revolt/client.ts index 7ee2d81..f1a7402 100644 --- a/bridge/src/revolt/client.ts +++ b/bridge/src/revolt/client.ts @@ -4,13 +4,15 @@ import { logger } from '..'; let AUTUMN_URL = `http://autumnUrl`; -const client = new Client({ }); +const client = new Client({ + apiURL: process.env.REVOLT_API_URL, +}); const login = () => new Promise((resolve: (value: Client) => void) => { client.loginBot(process.env['REVOLT_TOKEN']!); client.once('ready', async () => { logger.info(`Revolt: ${client.user?.username} ready - ${client.servers.size} servers`); - + const apiConfig = await axios.get(client.apiURL); AUTUMN_URL = apiConfig.data?.features?.autumn?.url; diff --git a/bridge/src/revolt/events.ts b/bridge/src/revolt/events.ts index 817b51e..5d30d99 100644 --- a/bridge/src/revolt/events.ts +++ b/bridge/src/revolt/events.ts @@ -43,7 +43,7 @@ client.on('message/delete', async id => { }); client.on('message/update', async message => { - if (message.content && typeof message.content != 'string') return; + if (!message.content || typeof message.content != 'string') return; if (message.author_id == client.user?._id) return; try { diff --git a/bridge/yarn.lock b/bridge/yarn.lock index 857076e..fb130f2 100644 --- a/bridge/yarn.lock +++ b/bridge/yarn.lock @@ -37,10 +37,10 @@ node-fetch "^2.6.7" tslib "^2.3.1" -"@insertish/exponential-backoff@3.1.0-patch.0": - version "3.1.0-patch.0" - resolved "https://registry.yarnpkg.com/@insertish/exponential-backoff/-/exponential-backoff-3.1.0-patch.0.tgz#1fff134f70fc0906d11d09069d51183b542e42cf" - integrity sha512-1qhW81s3GDbssyaxRWdpBAn1cIw5s393HnrdYFjfa2i6rq3SEjQDK6U80g6dq/K0or6JRr2eCn75Wfc1IrGM0g== +"@insertish/exponential-backoff@3.1.0-patch.2": + version "3.1.0-patch.2" + resolved "https://registry.yarnpkg.com/@insertish/exponential-backoff/-/exponential-backoff-3.1.0-patch.2.tgz#08310c856b795c783a832f3f608c0feaff262c0d" + integrity sha512-0lsMVexkZ7dHpQlPrTZDRsm42hZ4/CzKxP1hbAEJH8IaEwrIEhhTKJuFxPuLOi2TwPImuocncjYHpGpwVNOemQ== "@insertish/isomorphic-ws@^4.0.1": version "4.0.1" @@ -57,20 +57,31 @@ axios "^0.26.1" openapi-typescript "^5.2.0" -"@janderedev/revolt.js@^5.2.8-patch.2": - version "5.2.8-patch.2" - resolved "https://registry.yarnpkg.com/@janderedev/revolt.js/-/revolt.js-5.2.8-patch.2.tgz#d74a6500e217a4cb94d139027dec2acb71f73a0f" - integrity sha512-2nucMuToKC5F4jsajmTauBaV9Q7oyl1KWKF0ysYYPzEKxK9LfPL560v9bQR/ff1g8GkEv4biAN/A17MCcddAuw== +"@insertish/oapi@0.1.15": + version "0.1.15" + resolved "https://registry.yarnpkg.com/@insertish/oapi/-/oapi-0.1.15.tgz#ee58b82d879ed5af54db846699941b8ad0262e7b" + integrity sha512-2G8eFxrojF651tBoRutQ8CJOw0u8UjYSlw/LQU+xycw5KpXslhWp/KSR86uIFyDIPddwfMCNA91vwHGCFRA63w== dependencies: - "@insertish/exponential-backoff" "3.1.0-patch.0" + typescript "^4.6.2" + optionalDependencies: + axios "^0.26.1" + openapi-typescript "^5.2.0" + +"@janderedev/revolt.js@^6.0.0-rc.21": + version "6.0.0-rc.21" + resolved "https://registry.yarnpkg.com/@janderedev/revolt.js/-/revolt.js-6.0.0-rc.21.tgz#0629700cd78d0f7db5f4707dc7a8f5ba67482e1a" + integrity sha512-uLFXrv6ZjLJ+e9keevv0sUeZY8gDw3RfSg07LNYVUfqi1T64WwmZypz3zc2kTacbfthIgI368K2e6f3q4toc4Q== + dependencies: + "@insertish/exponential-backoff" "3.1.0-patch.2" "@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" + long "^5.2.0" mobx "^6.3.2" - revolt-api "0.5.3-alpha.12" + revolt-api "0.5.3-rc.15" ulid "^2.3.0" ws "^8.2.2" @@ -337,6 +348,11 @@ log75@^2.2.0: dependencies: ansi-colors "^4.1.1" +long@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" + integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" @@ -494,10 +510,14 @@ require-at@^1.0.6: resolved "https://registry.yarnpkg.com/require-at/-/require-at-1.0.6.tgz#9eb7e3c5e00727f5a4744070a7f560d4de4f6e6a" integrity sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g== -revolt-api@0.5.3-alpha.12: - version "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-api@0.5.3-rc.15: + version "0.5.3-rc.15" + resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-rc.15.tgz#abd08dd8109d0ca31be118461eabbeb6c3b7792e" + integrity sha512-MYin3U+KoObNkILPf2cz+FPperynExkUu7CjzurMJCRvBncpnhb2czvWDvnhLDKBHlpo8W597xNqzQnaklV4ug== + dependencies: + "@insertish/oapi" "0.1.15" + axios "^0.26.1" + lodash.defaultsdeep "^4.6.1" revolt-api@^0.5.3-rc.8: version "0.5.3-rc.8" diff --git a/docker-compose.yml.example b/docker-compose.yml.example index 2f6b878..cea776c 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -54,7 +54,7 @@ services: - DB_STRING=mongodb://mogus:${DB_PASS}@mongo:27017/admin - NODE_ENV=production - BRIDGE_METRICS_PORT - - API_URL + - REVOLT_API_URL=${API_URL} # Uncomment if you enabled Prometheus metrics #ports: # - 127.0.0.1:${BRIDGE_METRICS_PORT}:${BRIDGE_METRICS_PORT} From ccb652f737e151299488e31dd425d6eeb64ecc9f Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 30 Apr 2022 21:01:37 +0200 Subject: [PATCH 4/7] replace tenor URLs --- bridge/src/discord/events.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/bridge/src/discord/events.ts b/bridge/src/discord/events.ts index e46854e..57b7175 100644 --- a/bridge/src/discord/events.ts +++ b/bridge/src/discord/events.ts @@ -14,6 +14,8 @@ const MAX_BRIDGED_FILE_SIZE = 8_000_000; // 8 MB const RE_MENTION_USER = /<@!*[0-9]+>/g; const RE_MENTION_CHANNEL = /<#[0-9]+>/g; const RE_EMOJI = /<(a?)?:\w+:\d{18}?>/g; +const RE_TENOR = /^https:\/\/tenor.com\/view\/[^\s]+$/g; +const RE_TENOR_META = //g client.on('messageDelete', async message => { try { @@ -208,6 +210,33 @@ client.on('messageCreate', async message => { // Replaces @mentions and #channel mentions async function renderMessageBody(message: string): Promise { + + // Replace Tenor URLs so they render properly. + // We have to download the page first, then extract + // the `c.tenor.com` URL from the meta tags. + // Could query autumn but that's too much effort and I already wrote this. + if (RE_TENOR.test(message)) { + try { + logger.debug('Replacing tenor URL'); + + const res = await axios.get( + message, + { + headers: { + 'User-Agent': 'AutoMod/1.0; https://github.com/janderedev/automod', + } + } + ); + + const metaTag = RE_TENOR_META.exec(res.data as string)?.[0]; + if (metaTag) { + return metaTag + .replace('', ''); + } + } catch(e) { logger.warn(`Replacing tenor URL failed: ${e}`) } + } + // @mentions message = await smartReplace(message, RE_MENTION_USER, async (match: string) => { const id = match.replace('<@!', '').replace('<@', '').replace('>', ''); From 755609901c54cb8e55ae9f98031b2dabe58097a3 Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 30 Apr 2022 22:23:30 +0200 Subject: [PATCH 5/7] add discord 'Message Info' context menu entry --- bridge/src/discord/commands.ts | 184 ++++++++++++++++++++++++--------- 1 file changed, 134 insertions(+), 50 deletions(-) diff --git a/bridge/src/discord/commands.ts b/bridge/src/discord/commands.ts index 5d26c58..3a80634 100644 --- a/bridge/src/discord/commands.ts +++ b/bridge/src/discord/commands.ts @@ -3,13 +3,16 @@ import { client } from "./client"; import { REST } from '@discordjs/rest'; import { Routes } from 'discord-api-types/v9'; -import { BRIDGE_CONFIG, BRIDGE_REQUESTS, logger } from ".."; -import { TextChannel } from "discord.js"; +import { BRIDGED_MESSAGES, BRIDGE_CONFIG, BRIDGE_REQUESTS, logger } from ".."; +import { MessageEmbed, TextChannel } from "discord.js"; +import { revoltFetchMessage, revoltFetchUser } from "../util"; +import { client as revoltClient } from "../revolt/client"; const COMMANDS: any[] = [ { name: 'bridge', description: 'Confirm or delete Revolt bridges', + type: 1, // Slash command options: [ { name: 'confirm', @@ -30,6 +33,11 @@ const COMMANDS: any[] = [ type: 1, }, ], + }, + { + name: 'Message Info', + description: '', + type: 3, // Message context menu } ]; @@ -59,63 +67,139 @@ client.once('ready', async () => { client.on('interactionCreate', async interaction => { try { - if (!interaction.isCommand()) return; + if (interaction.isCommand()) { + logger.debug(`Command received: /${interaction.commandName}`); - logger.debug(`Command received: /${interaction.commandName}`); + // The revolutionary Jan command handler + switch(interaction.commandName) { + case 'bridge': + if (!interaction.memberPermissions?.has('MANAGE_GUILD')) { + return await interaction.reply(`\`MANAGE_GUILD\` permission is required for this.`); + } - // The revolutionary Jan command handler - switch(interaction.commandName) { - case 'bridge': - if (!interaction.memberPermissions?.has('MANAGE_GUILD')) { - return await interaction.reply(`\`MANAGE_GUILD\` permission is required for this.`); - } + const ownPerms = (interaction.channel as TextChannel).permissionsFor(client.user!)!; + switch(interaction.options.getSubcommand(true)) { + case 'confirm': + if (!ownPerms.has('MANAGE_WEBHOOKS')) + return interaction.reply('Sorry, I lack permission to manage webhooks in this channel.'); - const ownPerms = (interaction.channel as TextChannel).permissionsFor(client.user!)!; - switch(interaction.options.getSubcommand(true)) { - case 'confirm': - if (!ownPerms.has('MANAGE_WEBHOOKS')) - return interaction.reply('Sorry, I lack permission to manage webhooks in this channel.'); + const id = interaction.options.getString('id', true); + const request = await BRIDGE_REQUESTS.findOne({ id: id }); + if (!request || request.expires < Date.now()) return await interaction.reply('Unknown ID.'); - const id = interaction.options.getString('id', true); - const request = await BRIDGE_REQUESTS.findOne({ id: id }); - if (!request || request.expires < Date.now()) return await interaction.reply('Unknown ID.'); + const bridgedCount = await BRIDGE_CONFIG.count({ discord: interaction.channelId }); + if (bridgedCount > 0) return await interaction.reply('This channel is already bridged.'); - const bridgedCount = await BRIDGE_CONFIG.count({ discord: interaction.channelId }); - if (bridgedCount > 0) return await interaction.reply('This channel is already bridged.'); + const webhook = await (interaction.channel as TextChannel) + .createWebhook('AutoMod Bridge', { avatar: client.user?.avatarURL() }); - const webhook = await (interaction.channel as TextChannel) - .createWebhook('AutoMod Bridge', { avatar: client.user?.avatarURL() }); + await BRIDGE_REQUESTS.remove({ id: id }); + await BRIDGE_CONFIG.insert({ + discord: interaction.channelId, + revolt: request.revolt, + discordWebhook: { + id: webhook.id, + token: webhook.token || '', + } + }); - await BRIDGE_REQUESTS.remove({ id: id }); - await BRIDGE_CONFIG.insert({ - discord: interaction.channelId, - revolt: request.revolt, - discordWebhook: { - id: webhook.id, - token: webhook.token || '', + return await interaction.reply(`✅ Channel bridged!`); + case 'unlink': + const res = await BRIDGE_CONFIG.findOneAndDelete({ discord: interaction.channelId }); + if (res?._id) { + await interaction.reply('Channel unbridged.'); + if (ownPerms.has('MANAGE_WEBHOOKS') && res.discordWebhook) { + try { + const hooks = await (interaction.channel as TextChannel).fetchWebhooks(); + if (hooks.get(res?.discordWebhook?.id)) await hooks.get(res?.discordWebhook?.id) + ?.delete('Channel has been unbridged'); + } catch(_) {} } + } + else await interaction.reply('This channel is not bridged.'); + + break; + default: await interaction.reply('Unknown subcommand'); + } + + break; + } + } + else if (interaction.isMessageContextMenu()) { + logger.debug(`Received context menu: ${interaction.targetMessage.id}`); + + switch(interaction.commandName) { + case 'Message Info': + const message = interaction.targetMessage; + const bridgeInfo = await BRIDGED_MESSAGES.findOne({ "discord.messageId": message.id }); + const messageUrl = `https://discord.com/channels/${interaction.guildId}/${interaction.channelId}/${message.id}`; + + if (!bridgeInfo) return await interaction.reply({ + ephemeral: true, + embeds: [ + new MessageEmbed() + .setAuthor({ name: 'Message info', url: messageUrl }) + .setDescription('This message has not been bridged.') + .setColor('#7e96ff'), + ], + }); + else { + const embed = new MessageEmbed(); + + embed.setColor('#7e96ff'); + embed.setAuthor({ name: 'Message info', url: messageUrl }); + + embed.addField('Origin', bridgeInfo.origin == 'discord' ? 'Discord' : 'Revolt', true); + + if (bridgeInfo.origin == 'discord') { + embed.addField( + 'Bridge Status', + bridgeInfo.revolt.messageId + ? 'Bridged' + : bridgeInfo.revolt.nonce + ? 'ID unknown' + : 'Unbridged', + true + ); + } else { + embed.addField( + 'Bridge Status', + bridgeInfo.discord.messageId + ? 'Bridged' + : 'Unbridged', + true + ); + + if (bridgeInfo.channels?.revolt) { + const channel = await revoltClient.channels.get(bridgeInfo.channels.revolt); + const revoltMsg = await revoltFetchMessage(bridgeInfo.revolt.messageId, channel); + + if (revoltMsg) { + const author = await revoltFetchUser(revoltMsg.author_id); + embed.addField( + 'Message Author', + `**@${author?.username}** (${revoltMsg.author_id})`, + ); + } + } + } + + embed.addField( + 'Bridge Data', + `Origin: \`${bridgeInfo.origin}\`\n` + + `Discord ID: \`${bridgeInfo.discord.messageId}\`\n` + + `Revolt ID: \`${bridgeInfo.revolt.messageId}\`\n` + + `Revolt Nonce: \`${bridgeInfo.revolt.nonce}\`\n` + + `Discord Channel: \`${bridgeInfo.channels?.discord}\`\n` + + `Revolt Channel: \`${bridgeInfo.channels?.revolt}\`` + ); + + return await interaction.reply({ + ephemeral: true, + embeds: [ embed ], }); - - return await interaction.reply(`✅ Channel bridged!`); - case 'unlink': - const res = await BRIDGE_CONFIG.findOneAndDelete({ discord: interaction.channelId }); - if (res?._id) { - await interaction.reply('Channel unbridged.'); - if (ownPerms.has('MANAGE_WEBHOOKS') && res.discordWebhook) { - try { - const hooks = await (interaction.channel as TextChannel).fetchWebhooks(); - if (hooks.get(res?.discordWebhook?.id)) await hooks.get(res?.discordWebhook?.id) - ?.delete('Channel has been unbridged'); - } catch(_) {} - } - } - else await interaction.reply('This channel is not bridged.'); - - break; - default: await interaction.reply('Unknown subcommand'); - } - - break; + } + } } } catch(e) { console.error(e); From b65a1bd70c01602f13b1ec92fd7f3fe5e73b4379 Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 30 Apr 2022 22:38:16 +0200 Subject: [PATCH 6/7] add missing permission warning to bridge setup --- bot/src/bot/commands/bridge.ts | 20 +++++++++++++++----- bridge/src/discord/events.ts | 4 ++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/bot/src/bot/commands/bridge.ts b/bot/src/bot/commands/bridge.ts index 7016f08..7943d15 100644 --- a/bot/src/bot/commands/bridge.ts +++ b/bot/src/bot/commands/bridge.ts @@ -31,11 +31,21 @@ export default { expires: Date.now() + (1000 * 60 * 15), }); - await message.reply(`### Link request created.\n` - + `Request ID: \`${reqId}\`\n\n` - + `[Invite the bridge bot to your Discord server](<${DISCORD_INVITE_URL}>) ` - + `and run \`/bridge confirm ${reqId}\` in the channel you wish to link.\n` - + `This request expires in 15 minutes.`); + let text = `### Link request created.\n` + + `Request ID: \`${reqId}\`\n\n` + + `[Invite the bridge bot to your Discord server](<${DISCORD_INVITE_URL}>) ` + + `and run \`/bridge confirm ${reqId}\` in the channel you wish to link.\n` + + `This request expires in 15 minutes.`; + + if (!message.channel!.havePermission('Masquerade') + || !message.channel!.havePermission('SendEmbeds') + || !message.channel!.havePermission('UploadFiles')) { + text += '\n\n> :warning: I currently don\'t have all required permissions in this ' + + 'channel for the bridge to work. Please make sure to grant the "Masquerade", ' + + '"Upload Files" and "Send Embeds" permission.' + } + + await message.reply(text, false); break; } diff --git a/bridge/src/discord/events.ts b/bridge/src/discord/events.ts index 57b7175..9814439 100644 --- a/bridge/src/discord/events.ts +++ b/bridge/src/discord/events.ts @@ -93,6 +93,10 @@ client.on('messageCreate', async message => { return logger.debug(`Discord: Lacking SendEmbeds permission; refusing to send`); } + if (!(channel.havePermission('UploadFiles'))) { + return logger.debug(`Discord: Lacking UploadFiles permission; refusing to send`); + } + if (!(channel.havePermission('Masquerade'))) { return logger.debug(`Discord: Lacking Masquerade permission; refusing to send`); } From a18a0f9c6595aa5391c303cb5880c0ebdcc00227 Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 30 Apr 2022 23:14:48 +0200 Subject: [PATCH 7/7] add `/bridge info` command --- bot/package.json | 1 + bot/src/bot/commands/bridge.ts | 79 ++++++++++++++++++++++++++++++-- bot/src/index.ts | 2 + bot/src/struct/BridgedMessage.ts | 18 ++++++++ bot/yarn.lock | 2 +- 5 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 bot/src/struct/BridgedMessage.ts diff --git a/bot/package.json b/bot/package.json index ecc45d7..ea4e398 100644 --- a/bot/package.json +++ b/bot/package.json @@ -23,6 +23,7 @@ "log75": "^2.2.0", "monk": "^7.3.4", "prom-client": "^14.0.1", + "revolt-api": "^0.5.3-rc.15", "ulid": "^2.3.0", "xlsx": "^0.17.3" }, diff --git a/bot/src/bot/commands/bridge.ts b/bot/src/bot/commands/bridge.ts index 7943d15..8195472 100644 --- a/bot/src/bot/commands/bridge.ts +++ b/bot/src/bot/commands/bridge.ts @@ -1,10 +1,12 @@ +import { Message } from "@janderedev/revolt.js"; import { ulid } from "ulid"; +import { SendableEmbed } from "revolt-api"; import { dbs } from "../.."; import CommandCategory from "../../struct/commands/CommandCategory"; import SimpleCommand from "../../struct/commands/SimpleCommand"; import MessageCommandContext from "../../struct/MessageCommandContext"; import { DEFAULT_PREFIX } from "../modules/command_handler"; -import { isBotManager, NO_MANAGER_MSG } from "../util"; +import { isBotManager, isModerator, NO_MANAGER_MSG } from "../util"; const DISCORD_INVITE_URL = 'https://discord.com/api/oauth2/authorize?client_id=965692929643524136&permissions=536996864&scope=bot%20applications.commands'; // todo: read this from env or smth @@ -14,10 +16,10 @@ export default { description: 'Bridge a channel with Discord', category: CommandCategory.Misc, run: async (message: MessageCommandContext, args: string[]) => { - if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG); - switch(args[0]?.toLowerCase()) { case 'link': { + if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG); + const count = await dbs.BRIDGE_CONFIG.count({ revolt: message.channel_id }); if (count) return message.reply(`This channel is already bridged.`); @@ -50,12 +52,16 @@ export default { break; } case 'unlink': { + if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG); + const res = await dbs.BRIDGE_CONFIG.remove({ revolt: message.channel_id }); if (res.deletedCount) await message.reply(`Channel unlinked!`); else await message.reply(`Unable to unlink; no channel linked.`); break; } case 'unlink_all': { + if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG); + const query = { revolt: { $in: message.channel?.server?.channel_ids || [] } }; if (args[1] == 'CONFIRM') { const res = await dbs.BRIDGE_CONFIG.remove(query); @@ -75,6 +81,8 @@ export default { break; } case 'list': { + if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG); + const links = await dbs.BRIDGE_CONFIG.find({ revolt: { $in: message.channel?.server?.channel_ids || [] } }); await message.reply({ @@ -89,6 +97,67 @@ export default { }); break; } + case 'info': { + try { + if (!message.reply_ids) { + return await message.reply('Please run this command again while replying to a message.'); + } + + if (message.reply_ids.length > 1 && !await isModerator(message, false)) { + return await message.reply( + 'To avoid spam, only moderators are allowed to query bridge info for more than one message at a time.' + ); + } + + const messages = (await Promise.allSettled( + message.reply_ids?.map(m => message.channel!.fetchMessage(m)) || [] + )) + .filter(m => m.status == 'fulfilled') + .map(m => (m as PromiseFulfilledResult).value); + + if (!messages.length) { + return await message.reply('Something went wrong; could not fetch the target message(s).'); + } + + const embeds: SendableEmbed[] = await Promise.all(messages.map(async msg => { + const bridgeData = await dbs.BRIDGED_MESSAGES.findOne({ + 'revolt.messageId': msg._id, + }); + + const embed: SendableEmbed = bridgeData ? { + url: msg.url, + title: `Message ${bridgeData?.origin == 'revolt' ? `by ${msg.author?.username}` : 'from Discord'}`, + colour: '#7e96ff', + description: `**Origin:** ${bridgeData.origin == 'revolt' ? 'Revolt' : 'Discord'}\n` + + `**Bridge Status:** ${ + bridgeData.origin == 'revolt' + ? (bridgeData.discord.messageId ? 'Bridged' : 'Unbridged') + : (bridgeData.revolt.messageId ? 'Bridged' : (bridgeData.revolt.nonce ? 'ID unknown' : 'Unbridged')) + }\n` + + `### Bridge Data\n` + + `Origin: \`${bridgeData.origin}\`\n` + + `Discord ID: \`${bridgeData.discord.messageId}\`\n` + + `Revolt ID: \`${bridgeData.revolt.messageId}\`\n` + + `Revolt Nonce: \`${bridgeData.revolt.nonce}\`\n` + + `Discord Channel: \`${bridgeData.channels?.discord}\`\n` + + `Revolt Channel: \`${bridgeData.channels?.revolt}\``, + } : { + url: msg.url, + title: `Message by ${msg.author?.username}`, + description: 'This message has not been bridged.', + colour: '#7e96ff', + } + + return embed; + })); + + await message.reply({ embeds }, false); + } catch(e) { + console.error(e); + message.reply(''+e)?.catch(() => {}); + } + break; + } case 'help': { await message.reply({ content: '#', @@ -103,7 +172,9 @@ export default { + `then run the command: \`/bridge confirm [ID]\`.\n\n` + `You can list all bridges in a Revolt server by running \`${DEFAULT_PREFIX}bridge list\`\n\n` + `To unlink a channel, run \`/bridge unlink\` from either Discord or Revolt. If you wish to ` - + `unbridge all channels in a Revolt server, run \`${DEFAULT_PREFIX}bridge unlink_all\`.` + + `unbridge all channels in a Revolt server, run \`${DEFAULT_PREFIX}bridge unlink_all\`.\n` + + `To view bridge info about a particular message, run \`${DEFAULT_PREFIX}bridge info\` ` + + `while replying to the message.` } ] }); diff --git a/bot/src/index.ts b/bot/src/index.ts index 0771f66..f165816 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -13,6 +13,7 @@ import { VoteEntry } from './bot/commands/votekick'; import ScannedUser from './struct/ScannedUser'; import BridgeRequest from './struct/BridgeRequest'; import BridgeConfig from './struct/BridgeConfig'; +import BridgedMessage from './struct/BridgedMessage'; logger.info('Initializing client'); @@ -36,6 +37,7 @@ const dbs = { VOTEKICKS: db.get('votekicks'), SCANNED_USERS: db.get('scanned_users'), BRIDGE_CONFIG: db.get('bridge_config'), + BRIDGED_MESSAGES: db.get('bridged_messages'), BRIDGE_REQUESTS: db.get('bridge_requests'), } diff --git a/bot/src/struct/BridgedMessage.ts b/bot/src/struct/BridgedMessage.ts new file mode 100644 index 0000000..698d542 --- /dev/null +++ b/bot/src/struct/BridgedMessage.ts @@ -0,0 +1,18 @@ +export default class { + origin: 'discord'|'revolt'; + + discord: { + messageId?: string; + } + + revolt: { + messageId?: string; + nonce?: string; + } + + // Required to sync message deletions + channels?: { + discord: string; + revolt: string; + } +} diff --git a/bot/yarn.lock b/bot/yarn.lock index 8988d54..b3d587c 100644 --- a/bot/yarn.lock +++ b/bot/yarn.lock @@ -600,7 +600,7 @@ require-at@^1.0.6: resolved "https://registry.yarnpkg.com/require-at/-/require-at-1.0.6.tgz#9eb7e3c5e00727f5a4744070a7f560d4de4f6e6a" integrity sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g== -revolt-api@0.5.3-rc.15: +revolt-api@0.5.3-rc.15, revolt-api@^0.5.3-rc.15: version "0.5.3-rc.15" resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-rc.15.tgz#abd08dd8109d0ca31be118461eabbeb6c3b7792e" integrity sha512-MYin3U+KoObNkILPf2cz+FPperynExkUu7CjzurMJCRvBncpnhb2czvWDvnhLDKBHlpo8W597xNqzQnaklV4ug==