From 95b6fddaa8e451b0321901586493bbfaaf3441a3 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 27 Sep 2023 23:23:46 +1300 Subject: [PATCH 1/5] fix emoji sheet partial letterboxing --- m2d/converters/emoji-sheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m2d/converters/emoji-sheet.js b/m2d/converters/emoji-sheet.js index 7e698c5..c271567 100644 --- a/m2d/converters/emoji-sheet.js +++ b/m2d/converters/emoji-sheet.js @@ -37,7 +37,7 @@ async function compositeMatrixEmojis(mxcs) { /** @type {{info: sharp.OutputInfo, buffer: Buffer}} */ const result = await new Promise((resolve, reject) => { const transformer = sharp() - .resize(SIZE, SIZE, {fit: "contain"}) + .resize(SIZE, SIZE, {fit: "contain", background: {r: 0, g: 0, b: 0, alpha: 0}}) .png({compressionLevel: 0}) .toBuffer((err, buffer, info) => { if (err) return reject(err) From ca988af2e0ea5a1654f1ae842de599527a38b9f6 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 27 Sep 2023 23:24:31 +1300 Subject: [PATCH 2/5] d->m accept emojis with longer names --- d2m/converters/message-to-event.js | 2 +- d2m/converters/message-to-event.test.js | 15 +++++++++++++ db/ooye-test-data.sql | 6 +++-- test/data.js | 30 +++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index 15eaf34..69d1619 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -158,7 +158,7 @@ async function messageToEvent(message, guild, options = {}, di) { // Handling emojis that we don't know about. The emoji has to be present in the DB for it to be picked up in the emoji markdown converter. // So we scan the message ahead of time for all its emojis and ensure they are in the DB. - const emojiMatches = [...content.matchAll(/<(a?):([^:>]{2,20}):([0-9]+)>/g)] + const emojiMatches = [...content.matchAll(/<(a?):([^:>]{2,64}):([0-9]+)>/g)] const emojiDownloads = [] for (const match of emojiMatches) { const id = match[3] diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index c09708d..34f376f 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -403,3 +403,18 @@ test("message2event: mid-message small bridged emoji", async t => { formatted_body: 'h is for :hippo:!' }]) }) + +test("message2event: emoji triple long name", async t => { + const events = await messageToEvent(data.message.emoji_triple_long_name, data.guild.general, {}) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: ":brillillillilliant_move::brillillillilliant_move::brillillillilliant_move:", + format: "org.matrix.custom.html", + formatted_body: + ':brillillillilliant_move:' + + ':brillillillilliant_move:' + + ':brillillillilliant_move:' + }]) +}) diff --git a/db/ooye-test-data.sql b/db/ooye-test-data.sql index 68c6d00..2a56381 100644 --- a/db/ooye-test-data.sql +++ b/db/ooye-test-data.sql @@ -74,12 +74,14 @@ INSERT INTO emoji (id, name, animated, mxc_url) VALUES ('230201364309868544', 'hippo', 0, 'mxc://cadence.moe/qWmbXeRspZRLPcjseyLmeyXC'), ('393635038903926784', 'hipposcope', 1, 'mxc://cadence.moe/WbYqNlACRuicynBfdnPYtmvc'), ('362741439211503616', 'bn_re', 0, 'mxc://cadence.moe/OIpqpfxTnHKokcsYqDusxkBT'), -('551636841284108289', 'ae_botrac4r', 0, 'mxc://cadence.moe/skqfuItqxNmBYekzmVKyoLzs'); +('551636841284108289', 'ae_botrac4r', 0, 'mxc://cadence.moe/skqfuItqxNmBYekzmVKyoLzs'), +('975572106295259148', 'brillillillilliant_move', 0, 'mxc://cadence.moe/scfRIDOGKWFDEBjVXocWYQHik'); INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES ('!kLRqKKUQXcibIMtOpl:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL), ('!BpMdOUkWWhFxmTrENV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'malformed mxc'), ('!fGgIymcYWOqjbSRUdV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'), -('!PnyBKvUBOhjuCucEfk:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'); +('!PnyBKvUBOhjuCucEfk:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'), +('!maggESguZBqGBZtSnr:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'); COMMIT; diff --git a/test/data.js b/test/data.js index a0e59d3..8cfa717 100644 --- a/test/data.js +++ b/test/data.js @@ -1038,6 +1038,36 @@ module.exports = { flags: 0, components: [] }, + emoji_triple_long_name: { + id: "1156394116540805170", + type: 0, + content: "<:brillillillilliant_move:975572106295259148><:brillillillilliant_move:975572106295259148><:brillillillilliant_move:975572106295259148>", + channel_id: "112760669178241024", + author: { + id: "111604486476181504", + username: "kyuugryphon", + avatar: "e4ce31267ca524d19be80e684d4cafa1", + discriminator: "0", + public_flags: 0, + flags: 0, + banner: null, + accent_color: null, + global_name: "KyuuGryphon", + avatar_decoration_data: null, + banner_color: null + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "2023-09-27T00:57:22.147000+00:00", + edited_timestamp: null, + flags: 0, + components: [] + } }, message_with_embeds: { nothing_but_a_field: { From 56f16901cab17f344512cfe55dd158bc3f15fb6c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 27 Sep 2023 23:24:53 +1300 Subject: [PATCH 3/5] silence the bot in unbridged rooms --- m2d/actions/send-event.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index ac4ef60..d5797b9 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -58,9 +58,8 @@ async function resolvePendingFiles(message) { /** @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File | Ty.Event.Outer_M_Sticker} event */ async function sendEvent(event) { - // TODO: we just assume the bridge has already been created, is that really ok? const row = select("channel_room", ["channel_id", "thread_parent"], "WHERE room_id = ?").get(event.room_id) - assert(row) + if (!row) return // allow the bot to exist in unbridged rooms, just don't do anything with it let channelID = row.channel_id let threadID = undefined if (row.thread_parent) { From 60f3b67d2db9f59b2fef1bf34b65ef58581b64bb Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 27 Sep 2023 23:25:46 +1300 Subject: [PATCH 4/5] m->d fix for memorised emojis from other servers --- m2d/converters/event-to-message.js | 45 ++++++++++++------------- m2d/converters/event-to-message.test.js | 26 +++++++++++++- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 4c6e9e2..b028dc5 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -120,32 +120,31 @@ turndownService.addRule("emoji", { replacement: function (content, node) { const mxcUrl = node.getAttribute("src") - let row = select("emoji", ["id", "name", "animated"], "WHERE mxc_url = ?").get(mxcUrl) - if (!row) { - // We don't know what this is... but maybe we can guess based on the name? - const guessedName = node.getAttribute("title").replace(/^:|:$/g, "") - for (const guild of discord?.guilds.values() || []) { - /** @type {{name: string, id: string, animated: number}[]} */ - // @ts-ignore - const emojis = guild.emojis - const match = emojis.find(e => e.name === guessedName) || emojis.find(e => e.name?.toLowerCase() === guessedName.toLowerCase()) - if (match) { - row = match - break - } + // Get the known emoji from the database. (We may not be able to actually use this if it was from another server.) + const row = select("emoji", ["id", "name", "animated"], "WHERE mxc_url = ?").get(mxcUrl) + // Also guess a suitable emoji based on the ID (if available) or name + let guess = null + const guessedName = node.getAttribute("title").replace(/^:|:$/g, "") + for (const guild of discord?.guilds.values() || []) { + /** @type {{name: string, id: string, animated: number}[]} */ + // @ts-ignore + const emojis = guild.emojis + const match = emojis.find(e => e.id === row?.id) || emojis.find(e => e.name === guessedName) || emojis.find(e => e.name?.toLowerCase() === guessedName.toLowerCase()) + if (match) { + guess = match + break } } - if (row) { - const animatedChar = row.animated ? "a" : "" - return `<${animatedChar}:${row.name}:${row.id}>` + if (guess) { + // We know an emoji, and we can use it + const animatedChar = guess.animated ? "a" : "" + return `<${animatedChar}:${guess.name}:${guess.id}>` + } else if (endOfMessageEmojis.includes(mxcUrl)) { + // We can't locate or use a suitable emoji. After control returns, it will rewind over this, delete this section, and upload the emojis as a sprite sheet. + return `<::>` } else { - if (endOfMessageEmojis.includes(mxcUrl)) { - // After control returns to the main converter, it will rewind over this, delete this section, and upload the emojis as a sprite sheet. - return `<::>` - } else { - // This emoji is not at the end of the message, it is in the middle. We don't upload middle emojis as a sprite sheet. - return `[${node.getAttribute("title")}](${utils.getPublicUrlForMxc(mxcUrl)})` - } + // We prefer not to upload this as a sprite sheet because the emoji is not at the end of the message, it is in the middle. + return `[${node.getAttribute("title")}](${utils.getPublicUrlForMxc(mxcUrl)})` } } }) diff --git a/m2d/converters/event-to-message.test.js b/m2d/converters/event-to-message.test.js index 438334b..a65f747 100644 --- a/m2d/converters/event-to-message.test.js +++ b/m2d/converters/event-to-message.test.js @@ -1805,7 +1805,6 @@ slow()("event2message: unknown emoji in the end is reuploaded as a sprite sheet" }) slow()("event2message: known and unknown emojis in the end are reuploaded as a sprite sheet", async t => { - t.comment("SKIPPED") const messages = await eventToMessage({ type: "m.room.message", sender: "@cadence:cadence.moe", @@ -1829,3 +1828,28 @@ slow()("event2message: known and unknown emojis in the end are reuploaded as a s fileContentStart: "iVBORw0KGgoAAAANSUhEUgAAAGAAAAAwCAYAAADuFn/PAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAT5UlEQVR4nOVbCXSVRZauR9gMsoYlvKwvARKSkPUlJOyL" }) }) + +slow()("event2message: all unknown chess emojis are reuploaded as a sprite sheet", async t => { + const messages = await eventToMessage({ + type: "m.room.message", + sender: "@cadence:cadence.moe", + content: { + msgtype: "m.text", + body: "testing :chess_good_move::chess_incorrect::chess_blund::chess_brilliant_move::chess_blundest::chess_draw_black:", + format: "org.matrix.custom.html", + formatted_body: "testing \":chess_good_move:\"\":chess_incorrect:\"\":chess_blund:\"\":chess_brilliant_move:\"\":chess_blundest:\"\":chess_draw_black:\"" + }, + event_id: "$Me6iE8C8CZyrDEOYYrXKSYRuuh_25Jj9kZaNrf7LKr4", + room_id: "!maggESguZBqGBZtSnr:cadence.moe" + }) + const testResult = { + content: messages.messagesToSend[0].content, + fileName: messages.messagesToSend[0].pendingFiles[0].name, + fileContentStart: messages.messagesToSend[0].pendingFiles[0].buffer.subarray(0, 90).toString("base64") + } + t.deepEqual(testResult, { + content: "testing", + fileName: "emojis.png", + fileContentStart: "iVBORw0KGgoAAAANSUhEUgAAASAAAAAwCAYAAACxIqevAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAgAElEQVR4nOV9B1xUV9r3JMbEGBQLbRodhukDg2jWZP02" + }) +}) From 39f41e7d53055be26dfa8309884bd651fc55b32c Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 28 Sep 2023 00:10:24 +1300 Subject: [PATCH 5/5] /emoji works consistently and can bulk upload --- matrix/matrix-command-handler.js | 136 +++++++++++++++++++------------ 1 file changed, 84 insertions(+), 52 deletions(-) diff --git a/matrix/matrix-command-handler.js b/matrix/matrix-command-handler.js index e2d7f87..0f90bc6 100644 --- a/matrix/matrix-command-handler.js +++ b/matrix/matrix-command-handler.js @@ -12,6 +12,9 @@ const api = sync.require("./api") const mxUtils = sync.require("../m2d/converters/utils") /** @type {import("../discord/utils")} */ const dUtils = sync.require("../discord/utils") +/** @type {import("./kstate")} */ +const ks = sync.require("./kstate") +const reg = require("./read-registration") const PREFIXES = ["//", "/"] @@ -69,6 +72,7 @@ function onReactionAdd(event) { /** * @callback CommandExecute * @param {Ty.Event.Outer_M_Room_Message} event + * @param {string} realBody * @param {any} [ctx] */ @@ -81,13 +85,13 @@ function onReactionAdd(event) { /** @param {CommandExecute} execute */ function replyctx(execute) { /** @type {CommandExecute} */ - return function(event, ctx = {}) { + return function(event, realBody, ctx = {}) { ctx["m.relates_to"] = { "m.in_reply_to": { event_id: event.event_id } } - return execute(event, ctx) + return execute(event, realBody, ctx) } } @@ -106,7 +110,7 @@ class MatrixStringBuilder { */ add(body, formattedBody, condition = true) { if (condition) { - if (!formattedBody) formattedBody = body + if (formattedBody == undefined) formattedBody = body this.body += body this.formattedBody += formattedBody } @@ -120,7 +124,7 @@ class MatrixStringBuilder { */ addLine(body, formattedBody, condition = true) { if (condition) { - if (!formattedBody) formattedBody = body + if (formattedBody == undefined) formattedBody = body if (this.body.length && this.body.slice(-1) !== "\n") this.body += "\n" this.body += body const match = this.formattedBody.match(/<\/?([a-zA-Z]+[a-zA-Z0-9]*)[^>]*>\s*$/) @@ -144,13 +148,14 @@ class MatrixStringBuilder { const commands = [{ aliases: ["emoji"], execute: replyctx( - async (event, ctx) => { + async (event, realBody, ctx) => { // Guard /** @type {string} */ // @ts-ignore const channelID = select("channel_room", "channel_id", "WHERE room_id = ?").pluck().get(event.room_id) const guildID = discord.channels.get(channelID)?.["guild_id"] let matrixOnlyReason = null const matrixOnlyConclusion = "So the emoji will be uploaded on Matrix-side only. It will still be usable over the bridge, but may have degraded functionality." + // Check if we can/should upload to Discord, for various causes if (!guildID) { matrixOnlyReason = "NOT_BRIDGED" } else { @@ -164,47 +169,68 @@ const commands = [{ matrixOnlyReason = "USER_PERMISSIONS" } } - - const nameMatch = event.content.body.match(/:([a-zA-Z0-9_]{2,}):/) - if (!nameMatch) { - return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "Not sure what you want to call this emoji. Try writing a new :name: in colons. The name can have letters, numbers, and underscores." - }) - } - const name = nameMatch[1] - - let mxc - const mxcMatch = event.content.body.match(/(mxc:\/\/.*?)\b/) - if (mxcMatch) { - mxc = mxcMatch[1] - } - if (!mxc && event.content["m.relates_to"]?.["m.in_reply_to"]?.event_id) { - const repliedToEventID = event.content["m.relates_to"]["m.in_reply_to"].event_id - const repliedToEvent = await api.getEvent(event.room_id, repliedToEventID) - if (repliedToEvent.type === "m.room.message" && repliedToEvent.content.msgtype === "m.image" && repliedToEvent.content.url) { - mxc = repliedToEvent.content.url + if (matrixOnlyReason) { + // If uploading to Matrix, check if we have permission + const state = await api.getAllState(event.room_id) + const kstate = ks.stateToKState(state) + const powerLevels = kstate["m.room.power_levels/"] + const required = powerLevels.events["im.ponies.room_emotes"] ?? powerLevels.state_default ?? 50 + const have = powerLevels.users[`@${reg.sender_localpart}:${reg.ooye.server_name}`] ?? powerLevels.users_default ?? 0 + if (have < required) { + return api.sendEvent(event.room_id, "m.room.message", { + ...ctx, + msgtype: "m.text", + body: "I don't have sufficient permissions in this Matrix room to edit emojis." + }) } } - if (!mxc) { + + /** @type {{url: string, name: string}[]} */ + const toUpload = [] + const nameMatch = realBody.match(/:([a-zA-Z0-9_]{2,}):/) + const mxcMatch = realBody.match(/(mxc:\/\/.*?)\b/) + if (event.content["m.relates_to"]?.["m.in_reply_to"]?.event_id) { + const repliedToEventID = event.content["m.relates_to"]["m.in_reply_to"].event_id + const repliedToEvent = await api.getEvent(event.room_id, repliedToEventID) + if (nameMatch && repliedToEvent.type === "m.room.message" && repliedToEvent.content.msgtype === "m.image" && repliedToEvent.content.url) { + toUpload.push({url: repliedToEvent.content.url, name: nameMatch[1]}) + } else if (repliedToEvent.type === "m.room.message" && repliedToEvent.content.msgtype === "m.text" && "formatted_body" in repliedToEvent.content) { + const namePrefixMatch = realBody.match(/:([a-zA-Z0-9_]{2,})(?:\b|:)/) + const imgMatches = [...repliedToEvent.content.formatted_body.matchAll(/]*>/g)] + for (const match of imgMatches) { + const e = match[0] + const url = e.match(/src="([^"]*)"/)?.[1] + let name = e.match(/title=":?([^":]*):?"/)?.[1] + if (!url || !name) continue + if (namePrefixMatch) name = namePrefixMatch[1] + name + toUpload.push({url, name}) + } + } + } + if (!toUpload.length && mxcMatch && nameMatch) { + toUpload.push({url: mxcMatch[1], name: nameMatch[1]}) + } + if (!toUpload.length) { return api.sendEvent(event.room_id, "m.room.message", { ...ctx, msgtype: "m.text", - body: "Not sure what image you wanted to add. Try replying to an uploaded image when you use the command, or write an mxc:// URL in your message." + body: "Not sure what image you wanted to add. Try replying to an uploaded image when you use the command, or write an mxc:// URL in your message. You should specify the new name :like_this:." }) } + const b = new MatrixStringBuilder() + .addLine("## Emoji preview", "

Emoji preview

") + .addLine(`Ⓜ️ This room isn't bridged to Discord. ${matrixOnlyConclusion}`, `Ⓜ️ This room isn't bridged to Discord. ${matrixOnlyConclusion}`, matrixOnlyReason === "NOT_BRIDGED") + .addLine(`Ⓜ️ *Discord ran out of space for emojis. ${matrixOnlyConclusion}`, `Ⓜ️ Discord ran out of space for emojis. ${matrixOnlyConclusion}`, matrixOnlyReason === "CAPACITY") + .addLine(`Ⓜ️ *If you were a Discord user, you wouldn't have permission to create emojis. ${matrixOnlyConclusion}`, `Ⓜ️ If you were a Discord user, you wouldn't have permission to create emojis. ${matrixOnlyConclusion}`, matrixOnlyReason === "CAPACITY") + .addLine("[Preview not available in plain text.]", "Preview:") + for (const e of toUpload) { + b.add("", `:${e.name}:`) + } + b.addLine("Hit ✅ to add it.") const sent = await api.sendEvent(event.room_id, "m.room.message", { ...ctx, - ...new MatrixStringBuilder() - .addLine("## Emoji preview", "

Emoji preview

") - .addLine(`Ⓜ️ This room isn't bridged to Discord. ${matrixOnlyConclusion}`, `Ⓜ️ This room isn't bridged to Discord. ${matrixOnlyConclusion}`, matrixOnlyReason === "NOT_BRIDGED") - .addLine(`Ⓜ️ *Discord ran out of space for emojis. ${matrixOnlyConclusion}`, `Ⓜ️ Discord ran out of space for emojis. ${matrixOnlyConclusion}`, matrixOnlyReason === "CAPACITY") - .addLine(`Ⓜ️ *If you were a Discord user, you wouldn't have permission to create emojis. ${matrixOnlyConclusion}`, `Ⓜ️ If you were a Discord user, you wouldn't have permission to create emojis. ${matrixOnlyConclusion}`, matrixOnlyReason === "CAPACITY") - .addLine("[Preview not available in plain text.]", `Preview: :${name}:`) - .addLine("Hit ✅ to add it.") - .get() + ...b.get() }) addButton(event.room_id, sent, "✅", event.sender).then(async () => { if (matrixOnlyReason) { @@ -223,30 +249,36 @@ const commands = [{ } } if (!("images" in pack)) pack.images = {} - pack.images[name] = { - url: mxc // Directly use the same file that the Matrix user uploaded. Don't need to worry about dimensions/filesize because clients already request their preferred resized version from the homeserver. + const b = new MatrixStringBuilder() + .addLine(`Created ${toUpload.length} emojis`, "") + for (const e of toUpload) { + pack.images[e.name] = { + url: e.url // Directly use the same file that the Matrix user uploaded. Don't need to worry about dimensions/filesize because clients already request their preferred resized version from the homeserver. + } + b.add("", `:${e.name}:`) } + await api.sendState(event.room_id, type, key, pack) api.sendEvent(event.room_id, "m.room.message", { ...ctx, - ...new MatrixStringBuilder() - .addLine(`Created :${name}:`, `:${name}:`) - .get() + ...b.get() }) } else { // Upload it to Discord and have the bridge sync it back to Matrix again - const publicUrl = mxUtils.getPublicUrlForMxc(mxc) - // @ts-ignore - const resizeInput = await fetch(publicUrl, {agent: false}).then(res => res.arrayBuffer()) - const resizeOutput = await sharp(resizeInput) - .resize(EMOJI_SIZE, EMOJI_SIZE, {fit: "inside", withoutEnlargement: true, background: {r: 0, g: 0, b: 0, alpha: 0}}) - .png() - .toBuffer({resolveWithObject: true}) - console.log(`uploading emoji ${resizeOutput.data.length} bytes to :${name}:`) - const emoji = await discord.snow.guildAssets.createEmoji(guildID, {name, image: "data:image/png;base64," + resizeOutput.data.toString("base64")}) + for (const e of toUpload) { + const publicUrl = mxUtils.getPublicUrlForMxc(e.url) + // @ts-ignore + const resizeInput = await fetch(publicUrl, {agent: false}).then(res => res.arrayBuffer()) + const resizeOutput = await sharp(resizeInput) + .resize(EMOJI_SIZE, EMOJI_SIZE, {fit: "inside", withoutEnlargement: true, background: {r: 0, g: 0, b: 0, alpha: 0}}) + .png() + .toBuffer({resolveWithObject: true}) + console.log(`uploading emoji ${resizeOutput.data.length} bytes to :${e.name}:`) + const emoji = await discord.snow.guildAssets.createEmoji(guildID, {name: e.name, image: "data:image/png;base64," + resizeOutput.data.toString("base64")}) + } api.sendEvent(event.room_id, "m.room.message", { ...ctx, msgtype: "m.text", - body: `Created :${name}:` + body: `Created ${toUpload.length} emojis` }) } }) @@ -276,7 +308,7 @@ async function execute(event) { const command = commands.find(c => c.aliases.includes(commandName)) if (!command) return - await command.execute(event) + await command.execute(event, realBody) } module.exports.execute = execute