diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js new file mode 100644 index 00000000..e69de29b diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js new file mode 100644 index 00000000..c47e8cbf --- /dev/null +++ b/d2m/converters/message-to-event.js @@ -0,0 +1,26 @@ +// @ts-check + +const markdown = require("discord-markdown") + +/** + * @param {import("discord-api-types/v10").APIMessage} message + * @returns {import("../../types").M_Room_Message_content} + */ +module.exports = function(message) { + const body = message.content + const html = markdown.toHTML(body, { + discordCallback: { + user: Function, + channel: Function, + role: Function, + everyone: Function, + here: Function + } + }, null, null) + return { + msgtype: "m.text", + body: body, + format: "m.custom.html", + formatted_body: html + } +} diff --git a/modules/DiscordClient.js b/d2m/discord-client.js similarity index 87% rename from modules/DiscordClient.js rename to d2m/discord-client.js index 714db1e6..20927180 100644 --- a/modules/DiscordClient.js +++ b/d2m/discord-client.js @@ -1,11 +1,13 @@ +// @ts-check + const { SnowTransfer } = require("snowtransfer") const { Client: CloudStorm } = require("cloudstorm") const passthrough = require("../passthrough") const { sync } = passthrough -/** @type {typeof import("./DiscordUtils")} */ -const dUtils = sync.require("./DiscordUtils") +/** @type {typeof import("./discord-packets")} */ +const discordPackets = sync.require("./discord-packets") class DiscordClient { /** @@ -15,7 +17,7 @@ class DiscordClient { this.discordToken = discordToken this.snow = new SnowTransfer(discordToken) this.cloud = new CloudStorm(discordToken, { - shards: "auto", + shards: [0], reconnect: true, snowtransferInstance: this.snow, intents: [ @@ -41,7 +43,7 @@ class DiscordClient { this.guilds = new Map() /** @type {Map>} */ this.guildChannelMap = new Map() - this.cloud.on("event", message => dUtils.onPacket(this, message)) + this.cloud.on("event", message => discordPackets.onPacket(this, message)) this.cloud.on("error", console.error) } } diff --git a/modules/DiscordUtils.js b/d2m/discord-packets.js similarity index 83% rename from modules/DiscordUtils.js rename to d2m/discord-packets.js index 46ee4fdb..3939b81f 100644 --- a/modules/DiscordUtils.js +++ b/d2m/discord-packets.js @@ -1,12 +1,14 @@ +// @ts-check + const passthrough = require("../passthrough") const { sync } = passthrough -/** @type {typeof import("./DiscordEvents")} */ -const DiscordEvents = sync.require("./DiscordEvents") +/** @type {typeof import("./event-dispatcher")} */ +const eventDispatcher = sync.require("./event-dispatcher") const utils = { /** - * @param {import("./DiscordClient")} client + * @param {import("./discord-client")} client * @param {import("cloudstorm").IGatewayMessage} message */ onPacket(client, message) { @@ -56,7 +58,13 @@ const utils = { } - } else if (message.t === "MESSAGE_CREATE") DiscordEvents.onMessageCreate(client, message.d) + } else if (message.t === "MESSAGE_CREATE") { + eventDispatcher.onMessageCreate(client, message.d) + + + } else if (message.t === "MESSAGE_REACTION_ADD") { + eventDispatcher.onReactionAdd(client, message.d) + } } } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js new file mode 100644 index 00000000..714ac934 --- /dev/null +++ b/d2m/event-dispatcher.js @@ -0,0 +1,23 @@ +// @ts-check + +module.exports = { + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message + */ + onMessageCreate(client, message) { + console.log(message) + console.log(message.guild_id) + console.log(message.member) + return {} + }, + + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data + */ + onReactionAdd(client, data) { + console.log(data) + return {} + } +} diff --git a/index.js b/index.js index 44bfa002..c6340f85 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +// @ts-check + const HeatSync = require("heatsync") const config = require("./config") @@ -7,7 +9,7 @@ const sync = new HeatSync() Object.assign(passthrough, { config, sync }) -const DiscordClient = require("./modules/DiscordClient") +const DiscordClient = require("./d2m/discord-client") const discord = new DiscordClient(config.discordToken) passthrough.discord = discord @@ -19,5 +21,5 @@ passthrough.discord = discord require("./stdin") })() -process.on("unhandledRejection", console.error) -process.on("uncaughtException", console.error) +// process.on("unhandledRejection", console.error) +// process.on("uncaughtException", console.error) diff --git a/modules/DiscordEvents.js b/modules/DiscordEvents.js deleted file mode 100644 index a33eb13e..00000000 --- a/modules/DiscordEvents.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - /** - * Process Discord messages and convert to a message Matrix can understand - * - * @param {import("./DiscordClient")} client - * @param {import("discord-api-types/v10").APIMessage} message - * @returns {import("../types").MatrixMessage} - */ - onMessageCreate(client, message) { - return {} - } -} diff --git a/notes.md b/notes.md new file mode 100644 index 00000000..fcbffa15 --- /dev/null +++ b/notes.md @@ -0,0 +1,63 @@ +# d2m + +Remember that a discord message may be transformed to multiple matrix messages. + +A database will be used to store the discord id to matrix event id mapping. Table columns: +- discord id +- matrix id +- the "type" of the matrix id, used to update things properly next time. for example, whether it is the message text or an attachment + +There needs to be a way to easily manually trigger something later. For example, it should be easy to manually retry sending a message, or check all members for changes, etc. + +## Transforming content + +1. Upload attachments to mxc if they are small enough. +2. Convert discord message text and embeds to matrix event. + 1. Convert discord mentions, names, channel links, message links, and emojis to intermediate formats. + 2. Convert discord text to body. + 3. Convert discord text to formatted_body using custom discord-markdown npm module. + 4. Convert discord embeds to quotes. +3. Gather relevant reply data. +4. Send reply+message. +5. Send attachments. +6. Store in database. + +## Message sent + +1. Transform content. +2. Send to matrix. + +## Message deleted + +1. Look up equivalents on matrix. +2. Delete on matrix. + +## Message edited / embeds added + +1. Look up equivalents on matrix. +2. Replace content on matrix. + +## Reaction added + +1. Add reaction on matrix. + +## Reaction removed + +1. Remove reaction on matrix. + +## Member data changed + +1. Compare current member against cached version in database. +2. Update member on matrix. +3. Update cached version in database. + +## Channel created / updated + +(but I should be able to manually call this function at any time to run the same code on any given channel) + +1. Compare current channel against cached version in database. +2. If channel does not yet exist in database: + 1. Create the corresponding room. + 2. Add to database. +3. Update room details to match. +4. Add to space. diff --git a/package.json b/package.json index 0d6caedb..24fd3433 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,16 @@ "author": "Cadence, PapiOphidian", "license": "MIT", "dependencies": { + "better-sqlite3": "^8.3.0", "cloudstorm": "^0.7.0", + "discord-markdown": "git+https://git.sr.ht/~cadence/nodejs-discord-markdown#24508e701e91d5a00fa5e773ced874d9ee8c889b", "heatsync": "^2.4.0", - "snowtransfer": "^0.7.0" + "matrix-appservice": "^2.0.0", + "matrix-js-sdk": "^24.1.0", + "snowtransfer": "^0.7.0", + "supertape": "^8.3.0" + }, + "devDependencies": { + "@types/node": "^18.16.0" } } diff --git a/passthrough.js b/passthrough.js index 77b9c8fb..b6bc56fa 100644 --- a/passthrough.js +++ b/passthrough.js @@ -1,10 +1,13 @@ +// @ts-check + /** * @typedef {Object} Passthrough * @property {import("repl").REPLServer} repl * @property {typeof import("./config")} config - * @property {import("./modules/DiscordClient")} discord + * @property {import("./d2m/discord-client")} discord * @property {import("heatsync")} sync */ /** @type {Passthrough} */ +// @ts-ignore const pt = {} module.exports = pt diff --git a/stdin.js b/stdin.js index 95c7a738..be38b06e 100644 --- a/stdin.js +++ b/stdin.js @@ -1,3 +1,5 @@ +// @ts-check + const repl = require("repl") const util = require("util") diff --git a/types.d.ts b/types.d.ts index 491622c5..180a559f 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1 +1,6 @@ -export type MatrixMessage = {} +export type M_Room_Message_content = { + msgtype: "m.text" + body: string + formatted_body?: "org.matrix.custom.html" + format?: string +}