diff --git a/.gitignore b/.gitignore index 7b0c60d..c38dd88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,17 @@ -# Personal +# Secrets config.js registration.yaml ooye.db* events.db* backfill.db* custom-webroot -icon.svg -.devcontainer # Automatically generated node_modules coverage test/res/* !test/res/lottie* +icon.svg *~ .#* \#*# diff --git a/docs/threads-as-rooms.md b/docs/threads-as-rooms.md deleted file mode 100644 index 012c7af..0000000 --- a/docs/threads-as-rooms.md +++ /dev/null @@ -1,9 +0,0 @@ -I thought pretty hard about it and I opted to make threads separate rooms because - -1. parity: discord has separate things like permissions and pins for threads, matrix cannot do this at all unless the thread is a separate room -2. usage styles: most discord threads I've seen tend to be long-lived, spanning months or years, which isn't suited to matrix because of the timeline - - I'm in a discord thread for posting photos of food that gets a couple posts a week and has a timeline going back to 2023 -3. the timeline: if a matrix room has threads, and you want to scroll back through the timeline of a room OR of one of its threads, the timeline is merged, so you have to download every message linearised and throw them away if they aren't part of the thread you're looking through. it's bad for threads and it's bad for the main room -4. it is also very very complex for clients to implement read receipts and typing indicators correctly for the merged timeline. if your client doesn't implement this, or doesn't do it correctly, you have a bad experience. many clients don't. element seems to have done it well enough, but is an exception - -overall in my view, threads-as-rooms has better parity and fewer downsides over native threads. but if there are things you don't like about this approach, I'm happy to discuss and see if we can improve them. \ No newline at end of file diff --git a/src/d2m/actions/create-room.js b/src/d2m/actions/create-room.js index e7c5050..7f110ad 100644 --- a/src/d2m/actions/create-room.js +++ b/src/d2m/actions/create-room.js @@ -439,12 +439,12 @@ async function _syncRoom(channelID, shouldActuallySync) { return roomID } -/** Ensures the room exists. If it doesn't, creates the room with an accurate initial state. Before calling this, please make sure that: a channel_room entry exists, guild autocreate = 1, or you're operating on a thread.*/ +/** Ensures the room exists. If it doesn't, creates the room with an accurate initial state. Please check that a channel_room entry exists or guild autocreate = 1 before calling this. */ function ensureRoom(channelID) { return _syncRoom(channelID, false) } -/** Actually syncs. Gets all room state from the homeserver in order to diff, and uploads the icon to mxc if it has changed. Before calling this, please make sure that: a channel_room entry exists, guild autocreate = 1, or you're operating on a thread.*/ +/** Actually syncs. Gets all room state from the homeserver in order to diff, and uploads the icon to mxc if it has changed. Please check that a channel_room entry exists or guild autocreate = 1 before calling this. */ function syncRoom(channelID) { return _syncRoom(channelID, true) } diff --git a/src/d2m/converters/thread-to-announcement.js b/src/d2m/converters/thread-to-announcement.js index 179a559..575b3c5 100644 --- a/src/d2m/converters/thread-to-announcement.js +++ b/src/d2m/converters/thread-to-announcement.js @@ -19,21 +19,19 @@ const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) */ async function threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thread, di) { const branchedFromEventID = select("event_message", "event_id", {message_id: thread.id}).pluck().get() - /** @type {{"m.mentions"?: any, "m.relates_to"?: {event_id?: string, is_falling_back?: boolean, "m.in_reply_to"?: {event_id: string}, rel_type?: "m.replace"|"m.thread"}}} */ + /** @type {{"m.mentions"?: any, "m.in_reply_to"?: any}} */ const context = {} - let suffix = ""; if (branchedFromEventID) { // Need to figure out who sent that event... const event = await di.api.getEvent(parentRoomID, branchedFromEventID) - suffix = "\n[Note: You should continue the conversation in that room, rather than in this thread. Any messages sent in Matrix threads will be bridged to Discord as replies, not in-thread messages, which is probably not what you want.]"; - context["m.relates_to"] = {"m.in_reply_to": {event_id: event.event_id}, is_falling_back:false, event_id: event.event_id, rel_type: "m.thread"} + context["m.relates_to"] = {"m.in_reply_to": {event_id: event.event_id}} if (event.sender && !userRegex.some(rx => event.sender.match(rx))) context["m.mentions"] = {user_ids: [event.sender]} } const msgtype = creatorMxid ? "m.emote" : "m.text" - const template = creatorMxid ? "started a thread" : "New thread started:" + const template = creatorMxid ? "started a thread:" : "Thread started:" const via = await mxUtils.getViaServersQuery(threadRoomID, di.api) - let body = `${template} „${thread.name}” in room: https://matrix.to/#/${threadRoomID}?${via.toString()}${suffix}` + let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}?${via.toString()}` return { msgtype, diff --git a/src/d2m/converters/thread-to-announcement.test.js b/src/d2m/converters/thread-to-announcement.test.js index 8af4d79..3286f62 100644 --- a/src/d2m/converters/thread-to-announcement.test.js +++ b/src/d2m/converters/thread-to-announcement.test.js @@ -49,7 +49,7 @@ test("thread2announcement: no known creator, no branched from event", async t => }, {api: viaApi}) t.deepEqual(content, { msgtype: "m.text", - body: "New thread started: „test thread” in room: https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", + body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", "m.mentions": {} }) }) @@ -61,7 +61,7 @@ test("thread2announcement: known creator, no branched from event", async t => { }, {api: viaApi}) t.deepEqual(content, { msgtype: "m.emote", - body: "started a thread „test thread” in room: https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", + body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", "m.mentions": {} }) }) @@ -85,15 +85,12 @@ test("thread2announcement: no known creator, branched from discord event", async }) t.deepEqual(content, { msgtype: "m.text", - body: "New thread started: „test thread” in room: https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org\n[Note: You should continue the conversation in that room, rather than in this thread. Any messages sent in Matrix threads will be bridged to Discord as replies, not in-thread messages, which is probably not what you want.]", + body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", "m.mentions": {}, "m.relates_to": { - "event_id": "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", - "is_falling_back": false, "m.in_reply_to": { - "event_id": "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", - }, - "rel_type": "m.thread", + event_id: "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg" + } } }) }) @@ -117,15 +114,12 @@ test("thread2announcement: known creator, branched from discord event", async t }) t.deepEqual(content, { msgtype: "m.emote", - body: "started a thread „test thread” in room: https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org\n[Note: You should continue the conversation in that room, rather than in this thread. Any messages sent in Matrix threads will be bridged to Discord as replies, not in-thread messages, which is probably not what you want.]", + body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", "m.mentions": {}, "m.relates_to": { - "event_id": "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", - "is_falling_back": false, "m.in_reply_to": { - "event_id": "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg", - }, - "rel_type": "m.thread", + event_id: "$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg" + } } }) }) @@ -149,51 +143,14 @@ test("thread2announcement: no known creator, branched from matrix event", async }) t.deepEqual(content, { msgtype: "m.text", - body: "New thread started: „test thread” in room: https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org\n[Note: You should continue the conversation in that room, rather than in this thread. Any messages sent in Matrix threads will be bridged to Discord as replies, not in-thread messages, which is probably not what you want.]", + body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", "m.mentions": { user_ids: ["@cadence:cadence.moe"] }, "m.relates_to": { - "event_id": "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", - "is_falling_back": false, "m.in_reply_to": { - "event_id": "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", - }, - "rel_type": "m.thread", - } - }) -}) - -test("thread2announcement: known creator, branched from matrix event", async t => { - const content = await threadToAnnouncement("!kLRqKKUQXcibIMtOpl:cadence.moe", "!thread", "@_ooye_crunch_god:cadence.moe", { - name: "test thread", - id: "1128118177155526666" - }, { - api: { - getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { - type: "m.room.message", - content: { - msgtype: "m.text", - body: "so can you reply to my webhook uwu" - }, - sender: "@cadence:cadence.moe" - }), - ...viaApi - } - }) - t.deepEqual(content, { - msgtype: "m.emote", - body: "started a thread „test thread” in room: https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org\n[Note: You should continue the conversation in that room, rather than in this thread. Any messages sent in Matrix threads will be bridged to Discord as replies, not in-thread messages, which is probably not what you want.]", - "m.mentions": { - user_ids: ["@cadence:cadence.moe"] - }, - "m.relates_to": { - "event_id": "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", - "is_falling_back": false, - "m.in_reply_to": { - "event_id": "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", - }, - "rel_type": "m.thread", + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } } }) }) diff --git a/src/d2m/event-dispatcher.js b/src/d2m/event-dispatcher.js index eb0e7bd..b6593ec 100644 --- a/src/d2m/event-dispatcher.js +++ b/src/d2m/event-dispatcher.js @@ -212,7 +212,7 @@ module.exports = { const channelID = thread.parent_id || undefined const parentRoomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel (won't autocreate) - const threadRoomID = await createRoom.ensureRoom(thread.id) + const threadRoomID = await createRoom.syncRoom(thread.id) // Create room (will share the same inflight as the initial message to the thread) await announceThread.announceThread(parentRoomID, threadRoomID, thread) }, diff --git a/src/m2d/actions/send-event.js b/src/m2d/actions/send-event.js index bce45c6..00557a1 100644 --- a/src/m2d/actions/send-event.js +++ b/src/m2d/actions/send-event.js @@ -39,20 +39,14 @@ async function resolvePendingFiles(message) { if ("key" in p) { // Encrypted file const d = crypto.createDecipheriv("aes-256-ctr", Buffer.from(p.key, "base64url"), Buffer.from(p.iv, "base64url")) - await api.getMedia(p.mxc).then(res => stream.Readable.fromWeb( - // @ts-ignore - res.body - ).pipe(d)) + await api.getMedia(p.mxc).then(res => stream.Readable.fromWeb(res.body).pipe(d)) return { name: p.name, file: d } } else { // Unencrypted file - const body = await api.getMedia(p.mxc).then(res => stream.Readable.fromWeb( - // @ts-ignore - res.body - )) + const body = await api.getMedia(p.mxc).then(res => stream.Readable.fromWeb(res.body)) return { name: p.name, file: body diff --git a/src/m2d/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index 70a4a1e..af44c84 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -471,7 +471,6 @@ async function checkWrittenMentions(content, senderMxid, roomID, guild, di) { // @ts-ignore - typescript doesn't know about indices yet content: content.slice(0, writtenMentionMatch.indices[1][0]-1) + `@everyone` + content.slice(writtenMentionMatch.indices[1][1]), ensureJoined: [], - /**@type {DiscordTypes.AllowedMentionsTypes[]}*/ // @ts-ignore - TypeScript is for whatever reason conviced that "everyone" cannot be assigned to AllowedMentionsTypes, but if you „Go to Definition”, you'll see that "everyone" is a valid enum value. allowedMentionsParse: ["everyone"], allowedMentionsUsers: [] } @@ -546,7 +545,6 @@ async function getL1L2ReplyLine(called = false) { async function eventToMessage(event, guild, channel, di) { let displayName = event.sender let avatarURL = undefined - /**@type {DiscordTypes.AllowedMentionsTypes[]}*/ // @ts-ignore - TypeScript is for whatever reason conviced that neither "users" no "roles" cannot be assigned to AllowedMentionsTypes, but if you „Go to Definition”, you'll see that both are valid enum values. const allowedMentionsParse = ["users", "roles"] const allowedMentionsUsers = [] /** @type {string[]} */ diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index 3624df2..c11b696 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -156,14 +156,9 @@ async function sendError(roomID, source, type, e, payload) { } catch (e) {} } -/** - * @param {string} type - * @param {(event: Ty.Event.Outer & {type: any, redacts:any, state_key:any}, ...args: any)=>any} fn - */ function guard(type, fn) { - return async function(/** @type {Ty.Event.Outer} */ event, /** @type {any} */ ...args) { + return async function(event, ...args) { try { - // @ts-ignore return await fn(event, ...args) } catch (e) { await sendError(event.room_id, "Matrix", type, e, event) @@ -210,65 +205,16 @@ sync.addTemporaryListener(as, "type:m.room.message", guard("m.room.message", */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return - - let processCommands = true - if (event.content["m.relates_to"]?.rel_type === "m.thread") { - /**@type {string|null} */ - let toRedact = event.room_id - const bridgedTo = utils.getThreadRoomFromThreadEvent(event.content["m.relates_to"].event_id) - processCommands = false - - if (bridgedTo) event.room_id = bridgedTo; - else if (!await bridgeThread(event)) toRedact = null; - - if (toRedact){ - api.redactEvent(toRedact, event.event_id) - event.content["m.relates_to"] = undefined - api.sendEvent(event.room_id, event.type, event.content) - } - } - const messageResponses = await sendEvent.sendEvent(event) if (!messageResponses.length) return - - /** @type {string|undefined} */ - let executedCommand - if (event.type === "m.room.message" && event.content.msgtype === "m.text" && processCommands) { - executedCommand = await matrixCommandHandler.parseAndExecute( - // @ts-ignore - TypeScript doesn't know that the event.content.msgtype === "m.text" check ensures that event isn't of type Ty.Event.Outer_M_Room_Message_File (which, indeed, wouldn't fit here) - event - ) + if (event.type === "m.room.message" && event.content.msgtype === "m.text") { + // @ts-ignore + await matrixCommandHandler.execute(event) } - retrigger.messageFinishedBridging(event.event_id) await api.ackEvent(event) })) -/** - * @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File} event Used for determining the branching-point and the title; any relation data will be stripped and its room_id will mutate to the target thread-room, if one gets created. - * @returns {Promise} whether a thread-room was created - */ -async function bridgeThread(event) { - /** @type {string} */ // @ts-ignore - const channelID = select("channel_room", "channel_id", {room_id: event.room_id}).pluck().get() - const channel = discord.channels.get(channelID) - const guildID = channel?.["guild_id"] - if (!guildID) return false; //Room not bridged? We don't care. It's a Matrix-native room, let Matrix users have standard Matrix-native threads there. - - const eventID = event.content["m.relates_to"]?.event_id - if (!eventID) throw new Error("There was an event sent inside SOME Matrix thread, but it lacked any information as to what thread it actually was!"); //An „ugly error” is justified because if something like this DOES happen, then that means that it should be reported to us, as there is some broken client out there that we should account for. - const messageID = select("event_message", "message_id", {event_id: eventID}).pluck().get() - if (!messageID) return false; //Message not bridged? Too bad! Discord users will just see normal replies, and Matrix uses won't get a thread-room. We COULD technically create a "headless" thread on Discord side and bridge it to a new thread-room, but that comes with a whole host of complications on its own (notably: what do we do if the message gets bridged later (by reaction emoji), and then hypothetically gets its own thread; and: getThreadRoomFromThreadEvent will have to be much more complex than a simple DB call (probably a whole new DB table would have to be created, just to hold these Matrix-branched-but-headless-on-Discord threads) because the simple „MX event --(db)--> Discord Message --(Discord spec)--> Discord thread” relation would no longer hold true), which may not be worth it, as an unbridged message in a bridged channel is already an edge-case (so it seems pointless to introduce a whole bunch of edgier-cases that handling this edge-case "properly" would bring). - - let name = event.content.body - if (name.startsWith("/thread ")) name = name.substring(8); - else name = (await api.getEvent(event.room_id, eventID)).content.body; - name = name.length < 100 ? name.replaceAll("\n", " ") : name.slice(0, 96).replaceAll("\n", " ") + "..." - - event.room_id = await createRoom.ensureRoom((await discord.snow.channel.createThreadWithMessage(channelID, messageID, {name})).id) - return true; -} - sync.addTemporaryListener(as, "type:m.sticker", guard("m.sticker", /** * @param {Ty.Event.Outer_M_Sticker} event it is a m.sticker because that's what this listener is filtering for diff --git a/src/matrix/matrix-command-handler.js b/src/matrix/matrix-command-handler.js index d25c54c..b38b4b1 100644 --- a/src/matrix/matrix-command-handler.js +++ b/src/matrix/matrix-command-handler.js @@ -97,33 +97,6 @@ function replyctx(execute) { } } -/** - * @param {Error & {code?: string|number}} e - * @returns {e} -*/ -function unmarshallDiscordError(e) { - if (e.name === "DiscordAPIError"){ - try{ - const unmarshaled = JSON.parse(e.message) - return { - ...e, - ...unmarshaled - } - } catch (err) { - return { - ...err, - code: "JSON_PARSE_FAILED", - message: JSON.stringify({ - original_error_where_message_failed_to_parse: e, - json_parser_error_message: err.message, - json_parser_error_code: err.code, - }) - } - } - } - return e; -} - /** @type {Command[]} */ const commands = [{ aliases: ["emoji"], @@ -288,80 +261,8 @@ const commands = [{ body: "This command creates a thread on Discord. But you aren't allowed to do this, because if you were a Discord user, you wouldn't have the Create Public Threads permission." }) } - - const relation = event.content["m.relates_to"] - let isFallingBack = false; - let branchedFromMxEvent = relation?.["m.in_reply_to"]?.event_id // By default, attempt to branch the thread from the message to which /thread was replying. - if (relation?.rel_type === "m.thread") branchedFromMxEvent = relation?.event_id // If /thread was sent inside a Matrix thread, attempt to branch the Discord thread from the message, which that Matrix thread already is branching from. - if (!branchedFromMxEvent){ - branchedFromMxEvent = event.event_id // If /thread wasn't replying to anything (ie. branchedFromMxEvent was undefined at initial assignment), or if the event was somehow malformed (in such a way that that - one way or another - branchedFromMxEvent ended up being undefined, even if according to the spec it shouldn't), branch the thread from the /thread command-message that created it. - isFallingBack = true; - } - const branchedFromDiscordMessage = select("event_message", "message_id", {event_id: branchedFromMxEvent}).pluck().get() - if (words.length < 2){ - if (isFallingBack) return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "**`/thread` usage:**\nRun this command as `/thread [Thread Name]` to create a thread on Discord (and optionally Matrix, if one doesn't exist already). The message from which said thread will branch, is chosen based on the following rules:\n* If ran stand-alone (not as a reply, nor in a Matrix thread), the created thread will branch from the command-message itself. The `Thread Name` argument must be provided in this case, otherwise you get this help message.\n* If sent as a reply (outside a Matrix thread), the thread will branch from the message to which you replied.\n* If ran inside an existing Matrix thread (regardless of whether it's a reply or not), the created Discord thread will be branching from the same message as the Matrix thread already is.", - format: "org.matrix.custom.html", - formatted_body: "/thread usage:
Run this command as /thread [Thread Name] to create a thread on Discord (and optionally Matrix, if one doesn't exist already). The message from which said thread will branch, is chosen based on the following rules:
  • If ran stand-alone (not as a reply, nor in a Matrix thread), the created thread will branch from the command-message itself. The Thread Name argument must be provided in this case, otherwise you get this help message.
  • If sent as a reply (outside a Matrix thread), the thread will branch from the message to which you replied.
  • If ran inside an existing Matrix thread (regardless of whether it's a reply or not), the created Discord thread will be branching from the same message as the Matrix thread already is.
" - }) - words[1] = (await api.getEvent(event.room_id, branchedFromMxEvent)).content.body.replaceAll("\n", " ") - words[1] = words[1].length < 100 ? words[1] : words[1].slice(0, 96) + "..." - } - - try { - if (branchedFromDiscordMessage) await discord.snow.channel.createThreadWithMessage(channelID, branchedFromDiscordMessage, {name: words.slice(1).join(" ")}) - else throw {code: "NO_BRANCH_SOURCE", was_supposed_to_be: branchedFromMxEvent}; - } - catch (e){ - switch (unmarshallDiscordError(e).code) { - case "NO_BRANCH_SOURCE": return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "⚠️ Couldn't find a Discord representation of the message from which you're trying to branch this thread (event ID `"+e.was_supposed_to_be+"` on Matrix), so it wasn't created. Either you ran this command on an unbridged message (one sent by this bot or one that failed to bridge due to a previous error), or this is an error on our side and should be reported.", - format: "org.matrix.custom.html", - formatted_body: "⚠️ Couldn't find a Discord representation of the message from which you're trying to branch this thread (event ID "+e.was_supposed_to_be+" on Matrix), so it wasn't created. Either you ran this command on an unbridged message (one sent by this bot or one that failed to bridge due to a previous error), or this is an error on our side and should be reported." - }) - - case (160004): // see: https://docs.discord.com/developers/topics/opcodes-and-status-codes - if (isFallingBack){ - await api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "⚠️ Discord claims that there already exists a thread for the message you ran this command on, but that doesn't make logical sense, as it doesn't seem like you ran this command on any message. Either your Matrix client did something funny with reply/thread tags, or this is a logic error on OOYE's side. At any rate, this should be reported for further investigation. You should also attach the error message that's about to be sent below (or on the main room timeline, if the command was ran inside a thread).", - }) - throw e; - } - const thread = mxUtils.getThreadRoomFromThreadEvent(branchedFromMxEvent) - return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "There already exists a Discord thread for the message you ran this command on" + (thread ? " - you may join its bridged room here: https://matrix.to/#/"+thread+"?"+(await mxUtils.getViaServersQuery(thread, api)).toString() : ", so a new one cannot be crated. However, it seems like that thread isn't bridged to any Matrix rooms. Please ask the space/server admins to rectify this issue by creating the bridge. (If you're said admin and you can see that said bridge already exists, but this error message is still showing up, please report that as a bug.)") - }) - - case (50024): return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "You cannot create threads in a Discord channel of the type, to which this Matrix room is bridged to. Did you try to create a thread inside a thread?" - }) - - case (50035): return api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "Specified thread name is too long - thread creation failed. Please yap a bit less in the title, the thread body is for that. ;)" - }) - - default: - await api.sendEvent(event.room_id, "m.room.message", { - ...ctx, - msgtype: "m.text", - body: "⚠️ Unknown error occurred during thread creation. See error message below (or on the main room timeline, if the command was ran inside a thread) for details." - }) - throw e - } - } + await discord.snow.channel.createThreadWithoutMessage(channelID, {type: 11, name: words.slice(1).join(" ")}) } ) }, { @@ -420,11 +321,8 @@ const commands = [{ }] -/** - * @param {Ty.Event.Outer_M_Room_Message} event - * @returns {Promise} the executed command's name or undefined if no command execution was performed -*/ -async function parseAndExecute(event) { +/** @type {CommandExecute} */ +async function execute(event) { let realBody = event.content.body while (realBody.startsWith("> ")) { const i = realBody.indexOf("\n") @@ -445,8 +343,7 @@ async function parseAndExecute(event) { if (!command) return await command.execute(event, realBody, words) - return words[0] } -module.exports.parseAndExecute = parseAndExecute +module.exports.execute = execute module.exports.onReactionAdd = onReactionAdd diff --git a/src/matrix/utils.js b/src/matrix/utils.js index 2351a95..eee635b 100644 --- a/src/matrix/utils.js +++ b/src/matrix/utils.js @@ -4,7 +4,7 @@ const assert = require("assert").strict const Ty = require("../types") const {tag} = require("@cloudrac3r/html-template-tag") const passthrough = require("../passthrough") -const {db, select} = passthrough +const {db} = passthrough const {reg} = require("./read-registration") const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex)) @@ -385,16 +385,6 @@ async function setUserPowerCascade(spaceID, mxid, power, api) { } } -/** - * @param {undefined|string?} eventID - */ //^For some reason, „?” doesn't include Undefined and it needs to be explicitly specified -function getThreadRoomFromThreadEvent(eventID){ - if (!eventID) return eventID; - const threadID = select("event_message", "message_id", {event_id: eventID}).pluck().get() //Discord thread ID === its message ID - if (!threadID) return threadID; - return select("channel_room", "room_id", {channel_id: threadID}).pluck().get() -} - module.exports.bot = bot module.exports.BLOCK_ELEMENTS = BLOCK_ELEMENTS module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord @@ -410,4 +400,3 @@ module.exports.removeCreatorsFromPowerLevels = removeCreatorsFromPowerLevels module.exports.getEffectivePower = getEffectivePower module.exports.setUserPower = setUserPower module.exports.setUserPowerCascade = setUserPowerCascade -module.exports.getThreadRoomFromThreadEvent = getThreadRoomFromThreadEvent diff --git a/src/matrix/utils.test.js b/src/matrix/utils.test.js index 8db998d..842c513 100644 --- a/src/matrix/utils.test.js +++ b/src/matrix/utils.test.js @@ -2,7 +2,7 @@ const {select} = require("../passthrough") const {test} = require("supertape") -const {eventSenderIsFromDiscord, getEventIDHash, MatrixStringBuilder, getViaServers, roomHasAtLeastVersion, removeCreatorsFromPowerLevels, setUserPower, getThreadRoomFromThreadEvent} = require("./utils") +const {eventSenderIsFromDiscord, getEventIDHash, MatrixStringBuilder, getViaServers, roomHasAtLeastVersion, removeCreatorsFromPowerLevels, setUserPower} = require("./utils") const util = require("util") /** @param {string[]} mxids */ @@ -417,38 +417,4 @@ test("set user power: privileged users must demote themselves", async t => { t.equal(called, 3) }) -test("getThreadRoomFromThreadEvent: real message, but without a thread", t => { - const room = getThreadRoomFromThreadEvent("$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4") - const msg = "Expected null/undefined, got: "+room - if(room) t.fail(msg); - else t.pass(msg) -}) - -test("getThreadRoomFromThreadEvent: real message with a thread", t => { - const room = getThreadRoomFromThreadEvent("$fdD9o7NxMA4VPexlAiIx2CB9JbsiGhJeyJgnZG7U5xg") - t.equal(room, "!FuDZhlOAtqswlyxzeR:cadence.moe") -}) - -test("getThreadRoomFromThreadEvent: fake message", t => { - const room = getThreadRoomFromThreadEvent("$ThisEvent-IdDoesNotExistInTheDatabase4Sure") - const msg = "Expected null/undefined, got: "+room - if(room) t.fail(msg); - else t.pass(msg) -}) - -test("getThreadRoomFromThreadEvent: null", t => { - const room = getThreadRoomFromThreadEvent(null) - t.equal(room, null) -}) - -test("getThreadRoomFromThreadEvent: undefined", t => { - const room = getThreadRoomFromThreadEvent(undefined) - t.equal(room, undefined) -}) - -test("getThreadRoomFromThreadEvent: no value at all", t => { - const room = getThreadRoomFromThreadEvent() //This line should be giving a type-error, so it's not @ts-ignored on purpose. This is to test the desired behavior of that function, ie. „it CAN TAKE an undefined VALUE (as tested above), but you can just LEAVE the value completely undefined” (well, you can leave it like that from JS syntax perspective (which is why this test passes), but it makes no sense from usage standpoint, as it just gives back undefined). So this isn't a logic test (that's handled above), as much as it is a TypeScript test. - t.equal(room, undefined) -}) - module.exports.mockGetEffectivePower = mockGetEffectivePower diff --git a/src/types.d.ts b/src/types.d.ts index 494cba2..be037ca 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -190,12 +190,11 @@ export namespace Event { format?: "org.matrix.custom.html" formatted_body?: string, "m.relates_to"?: { - event_id?: string - is_falling_back?: boolean - "m.in_reply_to"?: { + "m.in_reply_to": { event_id: string } - rel_type?: "m.replace"|"m.thread" + rel_type?: "m.replace" + event_id?: string } } @@ -211,12 +210,11 @@ export namespace Event { info?: any "page.codeberg.everypizza.msc4193.spoiler"?: boolean "m.relates_to"?: { - event_id?: string - is_falling_back?: boolean - "m.in_reply_to"?: { + "m.in_reply_to": { event_id: string } - rel_type?: "m.replace"|"m.thread" + rel_type?: "m.replace" + event_id?: string } } @@ -248,12 +246,11 @@ export namespace Event { }, info?: any "m.relates_to"?: { - event_id?: string - is_falling_back?: boolean - "m.in_reply_to"?: { + "m.in_reply_to": { event_id: string } - rel_type?: "m.replace"|"m.thread" + rel_type?: "m.replace" + event_id?: string } } diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index ec287a7..07f8c24 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -95,14 +95,12 @@ WITH a (message_id, channel_id) AS (VALUES ('1381212840957972480', '112760669178241024'), ('1401760355339862066', '112760669178241024'), ('1439351590262800565', '1438284564815548418'), -('1404133238414376971', '112760669178241024'), -('1162005314908999790', '1100319550446252084')) +('1404133238414376971', '112760669178241024')) SELECT message_id, max(historical_room_index) as historical_room_index FROM a INNER JOIN historical_channel_room ON historical_channel_room.reference_channel_id = a.channel_id GROUP BY message_id; INSERT INTO event_message (event_id, event_type, event_subtype, message_id, part, reaction_part, source) VALUES ('$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg', 'm.room.message', 'm.text', '1126786462646550579', 0, 0, 1), ('$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4', 'm.room.message', 'm.text', '1128118177155526666', 0, 0, 0), -('$fdD9o7NxMA4VPexlAiIx2CB9JbsiGhJeyJgnZG7U5xg', 'm.room.message', 'm.text', '1162005314908999790', 0, 0, 1), ('$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10', 'm.room.message', 'm.text', '1141619794500649020', 0, 0, 1), ('$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY', 'm.room.message', 'm.text', '1141206225632112650', 0, 0, 1), ('$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA', 'm.room.message', 'm.text', '1141501302736695316', 0, 1, 1),