From 63e5ae3e0e86b1e10d14a15e1ebb356ea4fe6027 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 12 May 2023 17:35:37 +1200 Subject: [PATCH] begin converting discord attachments and stickers --- d2m/actions/send-message.js | 23 +++++- d2m/converters/message-to-event.js | 94 ++++++++++++++++++++---- d2m/converters/message-to-event.test.js | 28 +++++++ db/ooye.db | Bin 94208 -> 94208 bytes matrix/file.js | 7 ++ test/data.js | 74 +++++++++++++++---- test/test.js | 1 + 7 files changed, 194 insertions(+), 33 deletions(-) create mode 100644 d2m/converters/message-to-event.test.js diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 2630430..738c59a 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -19,16 +19,31 @@ const createRoom = sync.require("../actions/create-room") async function sendMessage(message) { assert.ok(message.member) - const event = messageToEvent.messageToEvent(message) const roomID = await createRoom.ensureRoom(message.channel_id) + let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const eventID = await api.sendEvent(roomID, "m.room.message", event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, 0) // 0 is primary, 1 is supporting - return eventID + + const events = await messageToEvent.messageToEvent(message) + const eventIDs = [] + let eventPart = 0 // 0 is primary, 1 is supporting + for (const event of events) { + const eventType = event.$type + /** @type {Pick> & { $type?: string }} */ + const eventWithoutType = {...event} + delete eventWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, eventPart) + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventIDs.push(eventID) + } + + return eventIDs } module.exports.sendMessage = sendMessage diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index dd6aabc..86be8ac 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -2,27 +2,93 @@ const markdown = require("discord-markdown") +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + /** * @param {import("discord-api-types/v10").APIMessage} message - * @returns {import("../../types").Event.M_Room_Message} */ -function messageToEvent(message) { +async function messageToEvent(message) { + const events = [] + + // Text content appears first const body = message.content const html = markdown.toHTML(body, { - /* discordCallback: { - user: Function, - channel: Function, - role: Function, - everyone: Function, - here: Function - } */ + discordCallback: { + user: node => { + const mxid = db.prepare("SELECT mxid FROM sim WHERE discord_id = ?").pluck().get(node.id) + if (mxid) { + return "https://matrix.to/#/" + mxid + } else { + return "@" + node.id + } + }, + channel: node => { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(node.id) + if (roomID) { + return "https://matrix.to/#/" + roomID + } else { + return "#" + node.id + } + }, + role: node => + "@&" + node.id, + everyone: node => + "@room", + here: node => + "@here" + } }, null, null) - return { - msgtype: "m.text", - body: body, - format: "org.matrix.custom.html", - formatted_body: html + const isPlaintext = body === html + if (isPlaintext) { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body + }) + } else { + events.push({ + $type: "m.room.message", + msgtype: "m.text", + body: body, + format: "org.matrix.custom.html", + formatted_body: html + }) } + + // Then attachments + const attachmentEvents = await Promise.all(message.attachments.map(async attachment => { + // TODO: handle large files differently - link them instead of uploading + if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { + return { + $type: "m.room.message", + msgtype: "m.image", + url: await file.uploadDiscordFileToMxc(attachment.url), + external_url: attachment.url, + body: attachment.filename, + // TODO: filename: attachment.filename and then use body as the caption + info: { + mimetype: attachment.content_type, + w: attachment.width, + h: attachment.height, + size: attachment.size + } + } + } else { + return { + $type: "m.room.message", + msgtype: "m.text", + body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) + } + } + })) + events.push(...attachmentEvents) + + // Then stickers + + return events } module.exports.messageToEvent = messageToEvent diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js new file mode 100644 index 0000000..07a45f7 --- /dev/null +++ b/d2m/converters/message-to-event.test.js @@ -0,0 +1,28 @@ +const {test} = require("supertape") +const assert = require("assert") +const {messageToEvent} = require("./message-to-event") +const data = require("../../test/data") + +test("message2event: stickers", async t => { + const events = await messageToEvent(data.message.sticker) + t.deepEqual(events, [{ + $type: "m.room.message", + msgtype: "m.text", + body: "can have attachments too" + }, { + $type: "m.room.message", + msgtype: "m.image", + url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", + body: "image.png", + external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + info: { + mimetype: "image/png", + w: 333, + h: 287, + size: 127373, + }, + }, { + $type: "m.sticker", + todo: "todo" + }]) +}) diff --git a/db/ooye.db b/db/ooye.db index 064c32b6606e9b33b2d031f1b55017cf70430d0a..924e5995e795ae11ae409e01ad15cfcfed6e76d8 100644 GIT binary patch delta 329 zcmZp8z}oPDb%HeG=7}=SjGH$mEXfz+e(f|Me delta 94 zcmV-k0HObY;01u-1&|v7vXLA^0kW}Rq;C`m3x@y?Zw}fHLk+GC84QFA?+b^s5ioBH zvxj?q1O^BU-vA8XvkZWC43V&3v)_y*0|p2Vi2x5pvkZ_<4w0}ilUkk@v!9+}(ffxX AS^xk5 diff --git a/matrix/file.js b/matrix/file.js index 4578d4a..62a4550 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -74,7 +74,14 @@ function memberAvatar(guildID, user, member) { return `/guilds/${guildID}/users/${user.id}/avatars/${member.avatar}.png?size=${IMAGE_SIZE}` } +function emoji(emojiID, animated) { + const base = `/emojis/${emojiID}` + if (animated) return base + ".gif" + else return base + ".png" +} + module.exports.guildIcon = guildIcon module.exports.userAvatar = userAvatar module.exports.memberAvatar = memberAvatar +module.exports.emoji = emoji module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc diff --git a/test/data.js b/test/data.js index e6a49c8..c94d132 100644 --- a/test/data.js +++ b/test/data.js @@ -6,18 +6,18 @@ module.exports = { channel: { general: { type: 0, - topic: 'https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:', + topic: "https://docs.google.com/document/d/blah/edit | I spread, pipe, and whip because it is my will. :headstone:", rate_limit_per_user: 0, position: 0, permission_overwrites: [], parent_id: null, nsfw: false, - name: 'collective-unconscious' , - last_pin_timestamp: '2023-04-06T09:51:57+00:00', - last_message_id: '1103832925784514580', - id: '112760669178241024', + name: "collective-unconscious" , + last_pin_timestamp: "2023-04-06T09:51:57+00:00", + last_message_id: "1103832925784514580", + id: "112760669178241024", default_thread_rate_limit_per_user: 0, - guild_id: '112760669178241024' + guild_id: "112760669178241024" } }, room: { @@ -45,41 +45,85 @@ module.exports = { }, guild: { general: { - owner_id: '112760500130975744', + owner_id: "112760500130975744", premium_tier: 3, stickers: [], max_members: 500000, - splash: '86a34ed02524b972918bef810087f8e7', + splash: "86a34ed02524b972918bef810087f8e7", explicit_content_filter: 0, afk_channel_id: null, nsfw_level: 0, description: null, - preferred_locale: 'en-US', - system_channel_id: '112760669178241024', + preferred_locale: "en-US", + system_channel_id: "112760669178241024", mfa_level: 0, /** @type {300} */ afk_timeout: 300, - id: '112760669178241024', - icon: 'a_f83622e09ead74f0c5c527fe241f8f8c', + id: "112760669178241024", + icon: "a_f83622e09ead74f0c5c527fe241f8f8c", emojis: [], premium_subscription_count: 14, roles: [], discovery_splash: null, default_message_notifications: 1, - region: 'deprecated', + region: "deprecated", max_video_channel_users: 25, verification_level: 0, application_id: null, premium_progress_bar_enabled: false, - banner: 'a_a666ae551605a2d8cda0afd591c0af3a', + banner: "a_a666ae551605a2d8cda0afd591c0af3a", features: [], vanity_url_code: null, hub_type: null, public_updates_channel_id: null, rules_channel_id: null, - name: 'Psychonauts 3', + name: "Psychonauts 3", max_stage_video_channel_users: 300, system_channel_flags: 0|0 } + }, + message: { + // Display order is text content, attachments, then stickers + sticker: { + id: "1106366167788044450", + type: 0, + content: "can have attachments too", + channel_id: "122155380120748034", + author: { + id: "113340068197859328", + username: "Cookie 🍪", + global_name: null, + display_name: null, + avatar: "b48302623a12bc7c59a71328f72ccb39", + discriminator: "7766", + public_flags: 128, + avatar_decoration: null + }, + attachments: [{ + id: "1106366167486038016", + filename: "image.png", + size: 127373, + url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + proxy_url: "https://media.discordapp.net/attachments/122155380120748034/1106366167486038016/image.png", + width: 333, + height: 287, + content_type: "image/png" + }], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-05-11T23:44:09.690000+00:00", + edited_timestamp: null, + flags: 0, + components: [], + sticker_items: [{ + id: "1106323941183717586", + format_type: 1, + name: "pomu puff" + }] + } } } diff --git a/test/test.js b/test/test.js index 2c44bb3..ae6aea6 100644 --- a/test/test.js +++ b/test/test.js @@ -14,5 +14,6 @@ Object.assign(passthrough, { config, sync, db }) require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/read-registration.test") +require("../d2m/converters/message-to-event.test") require("../d2m/actions/create-room.test") require("../d2m/converters/user-to-mxid.test")