diff --git a/.vscode/launch.json b/.vscode/launch.json index 9f681f5..4c74138 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,10 +5,10 @@ "version": "0.2.0", "configurations": [ { - "command": "yarn dev", + "command": "cd bridge && yarn dev", "name": "Debug", "request": "launch", - "type": "node-terminal" + "type": "node-terminal", } ] } \ No newline at end of file diff --git a/bridge/package.json b/bridge/package.json index 3ec74e4..12e7a02 100644 --- a/bridge/package.json +++ b/bridge/package.json @@ -18,6 +18,7 @@ "discord.js": "^13.6.0", "dotenv": "^16.0.0", "log75": "^2.2.0", - "monk": "^7.3.4" + "monk": "^7.3.4", + "ulid": "^2.3.0" } } diff --git a/bridge/src/discord/events.ts b/bridge/src/discord/events.ts index 285a9b8..487ca25 100644 --- a/bridge/src/discord/events.ts +++ b/bridge/src/discord/events.ts @@ -3,12 +3,21 @@ import { client } from "./client"; import { client as revoltClient } from "../revolt/client"; import { ChannelPermission } from "@janderedev/revolt.js"; import axios from 'axios'; -import BridgedMessage from "../types/BridgedMessage"; +import { ulid } from "ulid"; client.on('messageCreate', async message => { try { + if (!message.content) return; + logger.debug(`[M] Discord: ${message.content}`); - const bridgeCfg = await BRIDGE_CONFIG.findOne({ discord: message.channelId }); + const [ bridgeCfg, bridgedReply ] = await Promise.all([ + BRIDGE_CONFIG.findOne({ discord: message.channelId }), + (message.reference?.messageId + ? BRIDGED_MESSAGES.findOne({ "discord.messageId": message.reference.messageId }) + : undefined + ), + ]); + if (message.webhookId && bridgeCfg?.discordWebhook?.id == message.webhookId) { return logger.debug(`Discord: Message has already been bridged; ignoring`); } @@ -25,38 +34,64 @@ client.on('messageCreate', async message => { return logger.debug(`Discord: Lacking Masquerade permission; refusing to send`); } - await axios.post( - `${revoltClient.apiURL}/channels/${channel._id}/messages`, + // Setting a known nonce allows us to ignore bridged + // messages while still letting other AutoMod messages pass. + const nonce = ulid(); + + await BRIDGED_MESSAGES.update( + { "discord.messageId": message.id }, { - content: message.content, // todo: parse and normalize this - //attachments: [], - //embeds: [], - //replies: [], - masquerade: { - name: message.author.username, - avatar: message.author.displayAvatarURL({ size: 128 }), + $setOnInsert: { + origin: 'discord', + discord: { + messageId: message.id, + }, + }, + $set: { + 'revolt.nonce': nonce, } }, - { - headers: { - 'x-bot-token': process.env['REVOLT_TOKEN']! + { upsert: true } + ); + + const sendBridgeMessage = async (reply?: string) => { + await axios.post( + `${revoltClient.apiURL}/channels/${channel._id}/messages`, + { + content: message.content, + //attachments: [], + //embeds: [], + nonce: nonce, + replies: reply ? [ { id: reply, mention: !!message.mentions.repliedUser } ] : undefined, + masquerade: { + name: message.author.username, + avatar: message.author.displayAvatarURL({ size: 128 }), + } + }, + { + headers: { + 'x-bot-token': process.env['REVOLT_TOKEN']! + } + } + ) + .then(async res => { + await BRIDGED_MESSAGES.update( + { "discord.messageId": message.id }, + { + $set: { "revolt.messageId": res.data._id }, + } + ); + }) + .catch(async e => { + console.error(`Failed to send message`, e.response.data); + if (reply) { + console.info('Reytring without reply'); + await sendBridgeMessage(undefined); } - } - ) - .then(async res => { - await BRIDGED_MESSAGES.insert({ - origin: 'discord', - discord: { - messageId: message.id, - }, - revolt: { - messageId: res.data._id, - }, }); - }) - .catch(e => { - console.error(`Failed to send message`, e.response.data) - }); + } + + await sendBridgeMessage(bridgedReply?.revolt?.messageId); } catch(e) { console.error(e); } diff --git a/bridge/src/index.ts b/bridge/src/index.ts index 30c99c3..c83316c 100644 --- a/bridge/src/index.ts +++ b/bridge/src/index.ts @@ -28,4 +28,4 @@ for (const v of [ 'REVOLT_TOKEN', 'DISCORD_TOKEN', 'DB_STRING' ]) { ]); })(); -export { logger, BRIDGED_MESSAGES, BRIDGE_CONFIG } +export { logger, db, BRIDGED_MESSAGES, BRIDGE_CONFIG } diff --git a/bridge/src/revolt/events.ts b/bridge/src/revolt/events.ts index a536083..66fa971 100644 --- a/bridge/src/revolt/events.ts +++ b/bridge/src/revolt/events.ts @@ -2,14 +2,17 @@ import axios from "axios"; import { BRIDGED_MESSAGES, BRIDGE_CONFIG, logger } from ".."; import { client } from "./client"; import { client as discordClient } from "../discord/client"; +import { WebhookClient } from "discord.js"; client.on('message', async message => { try { + if (!message.content || typeof message.content != 'string') return; logger.debug(`[M] Revolt: ${message.content}`); - const [ bridgedMsg, bridgeCfg ] = await Promise.all([ - BRIDGED_MESSAGES.findOne({ "revolt.messageId": message._id }), + const [ bridgeCfg, bridgedMsg, ...repliedMessages ] = await Promise.all([ BRIDGE_CONFIG.findOne({ revolt: message.channel_id }), + BRIDGED_MESSAGES.findOne({ "revolt.nonce": message.nonce }), + //...(message.reply_ids?.map(id => BRIDGED_MESSAGES.findOne({ "revolt.messageId": id })) ?? []) ]); if (bridgedMsg) return logger.debug(`Revolt: Message has already been bridged; ignoring`); @@ -19,42 +22,44 @@ client.on('message', async message => { return logger.debug(`Revolt: No Discord webhook stored`); } - await BRIDGED_MESSAGES.insert({ - origin: 'revolt', - discord: {}, - revolt: { - messageId: message._id, + await BRIDGED_MESSAGES.update( + { 'revolt.messageId': message._id }, + { + $set: { + revolt: { + messageId: message._id, + nonce: message.nonce, + }, + }, + $setOnInsert: { + discord: {}, + origin: 'revolt', + } }, + { upsert: true } + ); + + const client = new WebhookClient({ + id: bridgeCfg.discordWebhook.id, + token: bridgeCfg.discordWebhook.token, }); - axios.post( - `https://discord.com/api/v9/webhooks/${bridgeCfg.discordWebhook.id}/${bridgeCfg.discordWebhook.token}?wait=true`, - { - content: message.content, - username: message.author?.username ?? 'Unknown user', - avatar_url: message.author?.generateAvatarURL({ max_side: 128 }), - }, - { - headers: { - "Authorization": `Bot ${discordClient.token}` - } - } - ) + client.send({ + content: `${message.content}`, + username: message.author?.username ?? 'Unknown user', + avatarURL: message.author?.generateAvatarURL({ max_side: 128 }), + }) .then(async res => { await BRIDGED_MESSAGES.update({ - revolt: { - messageId: message._id - } + "revolt.messageId": message._id }, { $set: { - discord: { - messageId: res.data._id - } + "discord.messageId": res.id } }); }) - .catch(e => { - console.error('Failed to execute webhook', e.response.data); + .catch(async e => { + console.error('Failed to execute webhook', e?.response?.data ?? e); }); } catch(e) { console.error(e); diff --git a/bridge/src/types/BridgedMessage.ts b/bridge/src/types/BridgedMessage.ts index 66c25c8..f5e60ae 100644 --- a/bridge/src/types/BridgedMessage.ts +++ b/bridge/src/types/BridgedMessage.ts @@ -7,5 +7,6 @@ export default class { revolt: { messageId?: string; + nonce?: string; } }