From 807ab899be79feab477093471029f8350dbde8ba Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 19 Sep 2023 01:45:40 +1200 Subject: [PATCH] Copy Discord emojis/stickers to Matrix --- d2m/actions/create-space.js | 23 ++++++++++ d2m/converters/expression.js | 82 ++++++++++++++++++++++++++++++++++++ d2m/discord-packets.js | 15 +++++++ d2m/event-dispatcher.js | 4 +- 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 d2m/converters/expression.js diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 8628dcb..96b074e 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -12,6 +12,8 @@ const api = sync.require("../../matrix/api") const file = sync.require("../../matrix/file") /** @type {import("./create-room")} */ const createRoom = sync.require("./create-room") +/** @type {import("../converters/expression")} */ +const expression = sync.require("../converters/expression") /** @type {import("../../matrix/kstate")} */ const ks = sync.require("../../matrix/kstate") @@ -186,8 +188,29 @@ async function syncSpaceFully(guildID) { return spaceID } +/** + * @param {import("discord-api-types/v10").GatewayGuildEmojisUpdateDispatchData | import("discord-api-types/v10").GatewayGuildStickersUpdateDispatchData} data + */ +async function syncSpaceExpressions(data) { + // No need for kstate here. Each of these maps to a single state event, which will always overwrite what was there before. I can just send the state event. + + const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(data.guild_id) + if (!spaceID) return + + if ("emojis" in data && data.emojis.length) { + const content = await expression.emojisToState(data.emojis) + api.sendState(spaceID, "im.ponies.room_emotes", "moe.cadence.ooye.pack.emojis", content) + } + + if ("stickers" in data && data.stickers.length) { + const content = await expression.stickersToState(data.stickers) + api.sendState(spaceID, "im.ponies.room_emotes", "moe.cadence.ooye.pack.stickers", content) + } +} + module.exports.createSpace = createSpace module.exports.ensureSpace = ensureSpace module.exports.syncSpace = syncSpace module.exports.syncSpaceFully = syncSpaceFully module.exports.guildToKState = guildToKState +module.exports.syncSpaceExpressions = syncSpaceExpressions diff --git a/d2m/converters/expression.js b/d2m/converters/expression.js new file mode 100644 index 0000000..146a766 --- /dev/null +++ b/d2m/converters/expression.js @@ -0,0 +1,82 @@ +// @ts-check + +const assert = require("assert").strict +const DiscordTypes = require("discord-api-types/v10") + +const passthrough = require("../../passthrough") +const {discord, sync, db, select} = passthrough +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +/** + * @param {DiscordTypes.APIEmoji[]} emojis + */ +async function emojisToState(emojis) { + const result = { + pack: { + display_name: "Discord Emojis", + usage: ["emoticon"] // we'll see... + }, + images: { + } + } + await Promise.all(emojis.map(emoji => + // the homeserver can probably cope with doing this in parallel + file.uploadDiscordFileToMxc(file.emoji(emoji.id, emoji.animated)).then(url => { + result.images[emoji.name] = { + info: { + mimetype: emoji.animated ? "image/gif" : "image/png" + }, + url + } + }).catch(e => { + if (e.data.errcode === "M_TOO_LARGE") { // Lol. + return + } + console.error(`Trying to handle emoji ${emoji.name} (${emoji.id}), but...`) + throw e + }) + )) + return result +} + +/** + * @param {DiscordTypes.APISticker[]} stickers + */ +async function stickersToState(stickers) { + const result = { + pack: { + display_name: "Discord Stickers", + usage: ["sticker"] // we'll see... + }, + images: { + } + } + const shortcodes = [] + await Promise.all(stickers.map(sticker => + // the homeserver can probably cope with doing this in parallel + file.uploadDiscordFileToMxc(file.sticker(sticker)).then(url => { + + /** @type {string | undefined} */ + let body = sticker.name + if (sticker && sticker.description) body += ` - ${sticker.description}` + if (!body) body = undefined + + let shortcode = sticker.name.toLowerCase().replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-|-$/g, "").replace(/--+/g, "-") + while (shortcodes.includes(shortcode)) shortcode = shortcode + "~" + shortcodes.push(shortcode) + + result.images[shortcodes] = { + info: { + mimetype: file.stickerFormat.get(sticker.format_type)?.mime || "image/png" + }, + body, + url + } + }) + )) + return result +} + +module.exports.emojisToState = emojisToState +module.exports.stickersToState = stickersToState diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index 0476bdd..4d6e9fe 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -56,6 +56,18 @@ const utils = { } } + } else if (message.t === "GUILD_EMOJIS_UPDATE") { + const guild = client.guilds.get(message.d.guild_id) + if (guild) { + guild.emojis = message.d.emojis + } + + } else if (message.t === "GUILD_STICKERS_UPDATE") { + const guild = client.guilds.get(message.d.guild_id) + if (guild) { + guild.stickers = message.d.stickers + } + } else if (message.t === "THREAD_CREATE") { client.channels.set(message.d.id, message.d) @@ -98,6 +110,9 @@ const utils = { if (message.t === "GUILD_UPDATE") { await eventDispatcher.onGuildUpdate(client, message.d) + } else if (message.t === "GUILD_EMOJIS_UPDATE" || message.t === "GUILD_STICKERS_UPDATE") { + await eventDispatcher.onExpressionsUpdate(client, message.d) + } else if (message.t === "CHANNEL_UPDATE") { await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index b1d420e..f7ff6a3 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -239,8 +239,6 @@ module.exports = { * @param {import("discord-api-types/v10").GatewayGuildEmojisUpdateDispatchData | import("discord-api-types/v10").GatewayGuildStickersUpdateDispatchData} data */ async onExpressionsUpdate(client, data) { - const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(guild.id) - if (!spaceID) return - await createSpace.syncSpaceExpressions(spaceID, data) + await createSpace.syncSpaceExpressions(data) } }