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", "