diff --git a/src/d2m/actions/edit-message.js b/src/d2m/actions/edit-message.js index 57b8f41..5970b59 100644 --- a/src/d2m/actions/edit-message.js +++ b/src/d2m/actions/edit-message.js @@ -21,7 +21,7 @@ const mreq = sync.require("../../matrix/mreq") async function editMessage(message, guild, row) { const historicalRoomOfMessage = from("message_room").join("historical_channel_room", "historical_room_index").where({message_id: message.id}).select("room_id").get() const currentRoom = from("channel_room").join("historical_channel_room", "room_id").where({channel_id: message.channel_id}).select("room_id", "historical_room_index").get() - if (!currentRoom) return + assert(currentRoom) if (historicalRoomOfMessage && historicalRoomOfMessage.room_id !== currentRoom.room_id) return // tombstoned rooms should not have new events (including edits) sent to them diff --git a/src/d2m/actions/speedbump.js b/src/d2m/actions/speedbump.js index 1a6ef63..7c3109b 100644 --- a/src/d2m/actions/speedbump.js +++ b/src/d2m/actions/speedbump.js @@ -4,14 +4,6 @@ const DiscordTypes = require("discord-api-types/v10") const passthrough = require("../../passthrough") const {discord, select, db} = passthrough -const DEBUG_SPEEDBUMP = false - -function debugSpeedbump(message) { - if (DEBUG_SPEEDBUMP) { - console.log(message) - } -} - const SPEEDBUMP_SPEED = 4000 // 4 seconds delay const SPEEDBUMP_UPDATE_FREQUENCY = 2 * 60 * 60 // 2 hours @@ -35,8 +27,8 @@ async function updateCache(channelID, lastChecked) { db.prepare("UPDATE channel_room SET speedbump_id = ?, speedbump_webhook_id = ?, speedbump_checked = ? WHERE channel_id = ?").run(foundApplication, foundWebhook, now, channelID) } -/** @type {Map} messageID -> number of gateway events currently bumping */ -const bumping = new Map() +/** @type {Set} set of messageID */ +const bumping = new Set() /** * Slow down a message. After it passes the speedbump, return whether it's okay or if it's been deleted. @@ -44,26 +36,9 @@ const bumping = new Map() * @returns whether it was deleted */ async function doSpeedbump(messageID) { - let value = (bumping.get(messageID) ?? 0) + 1 - bumping.set(messageID, value) - debugSpeedbump(`[speedbump] WAIT ${messageID}++ = ${value}`) - + bumping.add(messageID) await new Promise(resolve => setTimeout(resolve, SPEEDBUMP_SPEED)) - - if (!bumping.has(messageID)) { - debugSpeedbump(`[speedbump] DELETED ${messageID}`) - return true - } - value = bumping.get(messageID) - 1 - if (value === 0) { - debugSpeedbump(`[speedbump] OK ${messageID}-- = ${value}`) - bumping.delete(messageID) - return false - } else { - debugSpeedbump(`[speedbump] MULTI ${messageID}-- = ${value}`) - bumping.set(messageID, value) - return true - } + return !bumping.delete(messageID) } /** diff --git a/src/d2m/converters/edit-to-changes.js b/src/d2m/converters/edit-to-changes.js index 48b7dd3..869bb3c 100644 --- a/src/d2m/converters/edit-to-changes.js +++ b/src/d2m/converters/edit-to-changes.js @@ -227,8 +227,8 @@ async function editToChanges(message, guild, api) { */ function makeReplacementEventContent(oldID, newFallbackContent, newInnerContent) { const content = { - "m.mentions": {}, ...newFallbackContent, + "m.mentions": {}, "m.new_content": { ...newInnerContent }, diff --git a/src/d2m/converters/message-to-event.test.embeds.js b/src/d2m/converters/message-to-event.embeds.test.js similarity index 100% rename from src/d2m/converters/message-to-event.test.embeds.js rename to src/d2m/converters/message-to-event.embeds.test.js diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index 8a8e50f..ffce2f0 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -107,10 +107,9 @@ const embedTitleParser = markdown.markdownEngine.parserFor({ /** * @param {{room?: boolean, user_ids?: string[]}} mentions - * @param {Omit} attachment - * @param {boolean} [alwaysLink] + * @param {DiscordTypes.APIAttachment} attachment */ -async function attachmentToEvent(mentions, attachment, alwaysLink) { +async function attachmentToEvent(mentions, attachment) { const external_url = dUtils.getPublicUrlForCdn(attachment.url) const emoji = attachment.content_type?.startsWith("image/jp") ? "📸" @@ -131,7 +130,7 @@ async function attachmentToEvent(mentions, attachment, alwaysLink) { } } // for large files, always link them instead of uploading so I don't use up all the space in the content repo - else if (alwaysLink || attachment.size > reg.ooye.max_file_size) { + else if (attachment.size > reg.ooye.max_file_size) { return { $type: "m.room.message", "m.mentions": mentions, @@ -229,7 +228,6 @@ async function pollToEvent(poll) { return matrixAnswer; }) return { - /** @type {"org.matrix.msc3381.poll.start"} */ $type: "org.matrix.msc3381.poll.start", "org.matrix.msc3381.poll.start": { question: { @@ -540,7 +538,7 @@ async function messageToEvent(message, guild, options = {}, di) { // 1. The replied-to event is in a different room to where the reply will be sent (i.e. a room upgrade occurred between) // 2. The replied-to message has no corresponding Matrix event (repliedToUnknownEvent is true) // This branch is optional - do NOT change anything apart from the reply fallback, since it may not be run - if ((repliedToEventRow || repliedToUnknownEvent) && options.includeReplyFallback !== false && events.length === 0) { + if ((repliedToEventRow || repliedToUnknownEvent) && options.includeReplyFallback !== false) { const latestRoomID = repliedToEventRow ? select("channel_room", "room_id", {channel_id: repliedToEventRow.channel_id}).pluck().get() : null if (latestRoomID !== repliedToEventRow?.room_id) repliedToEventInDifferentRoom = true @@ -743,7 +741,7 @@ async function messageToEvent(message, guild, options = {}, di) { // Then attachments if (message.attachments) { - const attachmentEvents = await Promise.all(message.attachments.map(attachment => attachmentToEvent(mentions, attachment))) + const attachmentEvents = await Promise.all(message.attachments.map(attachmentToEvent.bind(null, mentions))) // Try to merge attachment events with the previous event // This means that if the attachments ended up as a text link, and especially if there were many of them, the events will be joined together. @@ -758,101 +756,6 @@ async function messageToEvent(message, guild, options = {}, di) { } } - // Then components - if (message.components?.length) { - const stack = [new mxUtils.MatrixStringBuilder()] - /** @param {DiscordTypes.APIMessageComponent} component */ - async function processComponent(component) { - // Standalone components - if (component.type === DiscordTypes.ComponentType.TextDisplay) { - const {body, html} = await transformContent(component.content) - stack[0].addParagraph(body, html) - } - else if (component.type === DiscordTypes.ComponentType.Separator) { - stack[0].addParagraph("----", "
") - } - else if (component.type === DiscordTypes.ComponentType.File) { - const ev = await attachmentToEvent({}, {...component.file, filename: component.name, size: component.size}, true) - stack[0].addLine(ev.body, ev.formatted_body) - } - else if (component.type === DiscordTypes.ComponentType.MediaGallery) { - const description = component.items.length === 1 ? component.items[0].description || "Image:" : "Image gallery:" - const images = component.items.map(item => { - const publicURL = dUtils.getPublicUrlForCdn(item.media.url) - return { - url: publicURL, - estimatedName: item.media.url.match(/\/([^/?]+)(\?|$)/)?.[1] || publicURL - } - }) - stack[0].addLine(`🖼️ ${description} ${images.map(i => i.url).join(", ")}`, tag`🖼️ ${description} $${images.map(i => tag`${i.estimatedName}`).join(", ")}`) - } - // string select, text input, user select, role select, mentionable select, channel select - - // Components that can have things nested - else if (component.type === DiscordTypes.ComponentType.Container) { - // May contain action row, text display, section, media gallery, separator, file - stack.unshift(new mxUtils.MatrixStringBuilder()) - for (const innerComponent of component.components) { - await processComponent(innerComponent) - } - let {body, formatted_body} = stack.shift().get() - body = body.split("\n").map(l => "| " + l).join("\n") - formatted_body = `
${formatted_body}
` - if (stack[0].body) stack[0].body += "\n\n" - stack[0].add(body, formatted_body) - } - else if (component.type === DiscordTypes.ComponentType.Section) { - // May contain text display, possibly more in the future - // Accessory may be button or thumbnail - stack.unshift(new mxUtils.MatrixStringBuilder()) - for (const innerComponent of component.components) { - await processComponent(innerComponent) - } - if (component.accessory) { - stack.unshift(new mxUtils.MatrixStringBuilder()) - await processComponent(component.accessory) - const {body, formatted_body} = stack.shift().get() - stack[0].addLine(body, formatted_body) - } - const {body, formatted_body} = stack.shift().get() - stack[0].addParagraph(body, formatted_body) - } - else if (component.type === DiscordTypes.ComponentType.ActionRow) { - const linkButtons = component.components.filter(c => c.type === DiscordTypes.ComponentType.Button && c.style === DiscordTypes.ButtonStyle.Link) - if (linkButtons.length) { - stack[0].addLine("") - for (const linkButton of linkButtons) { - await processComponent(linkButton) - } - } - } - // Components that can only be inside things - else if (component.type === DiscordTypes.ComponentType.Thumbnail) { - // May only be a section accessory - stack[0].add(`🖼️ ${component.media.url}`, tag`🖼️ ${component.media.url}`) - } - else if (component.type === DiscordTypes.ComponentType.Button) { - // May only be a section accessory or in an action row (up to 5) - if (component.style === DiscordTypes.ButtonStyle.Link) { - if (component.label) { - stack[0].add(`[${component.label} ${component.url}] `, tag`${component.label} `) - } else { - stack[0].add(component.url) - } - } - } - - // Not handling file upload or label because they are modal-only components - } - - for (const component of message.components) { - await processComponent(component) - } - - const {body, formatted_body} = stack[0].get() - await addTextEvent(body, formatted_body, "m.text") - } - // Then polls if (message.poll) { const pollEvent = await pollToEvent(message.poll) @@ -870,7 +773,7 @@ async function messageToEvent(message, guild, options = {}, di) { continue // Matrix's own URL previews are fine for images. } - if (embed.type === "video" && !embed.title && message.content.includes(embed.video?.url)) { + if (embed.type === "video" && !embed.title && !embed.description && message.content.includes(embed.video?.url)) { continue // Doesn't add extra information and the direct video URL is already there. } @@ -1001,7 +904,7 @@ async function messageToEvent(message, guild, options = {}, di) { // Strip formatted_body where equivalent to body if (!options.alwaysReturnFormattedBody) { for (const event of events) { - if (event.$type === "m.room.message" && "msgtype" in event && ["m.text", "m.notice"].includes(event.msgtype) && event.body === event.formatted_body) { + if (["m.text", "m.notice"].includes(event.msgtype) && event.body === event.formatted_body) { delete event.format delete event.formatted_body } diff --git a/src/d2m/converters/message-to-event.test.pk.js b/src/d2m/converters/message-to-event.pk.test.js similarity index 100% rename from src/d2m/converters/message-to-event.test.pk.js rename to src/d2m/converters/message-to-event.pk.test.js diff --git a/src/d2m/converters/message-to-event.test.components.js b/src/d2m/converters/message-to-event.test.components.js deleted file mode 100644 index 7d875a6..0000000 --- a/src/d2m/converters/message-to-event.test.components.js +++ /dev/null @@ -1,79 +0,0 @@ -const {test} = require("supertape") -const {messageToEvent} = require("./message-to-event") -const data = require("../../../test/data") - -test("message2event components: pk question mark output", async t => { - const events = await messageToEvent(data.message_with_components.pk_question_mark_response, data.guild.general, {}) - t.deepEqual(events, [{ - $type: "m.room.message", - body: - "| ### Lillith (INX)" - + "\n| " - + "\n| **Display name:** Lillith (she/her)" - + "\n| **Pronouns:** She/Her" - + "\n| **Message count:** 3091" - + "\n| 🖼️ https://files.inx.moe/p/cdn/lillith.webp" - + "\n| " - + "\n| ----" - + "\n| " - + "\n| **Proxy tags:**" - + "\n| ``l;text``" - + "\n| ``l:text``" - + "\n| ``l.text``" - + "\n| ``textl.``" - + "\n| ``textl;``" - + "\n| ``textl:``" - + "\n" - + "\n-# System ID: `xffgnx` ∙ Member ID: `pphhoh`" - + "\n-# Created: 2025-12-31 03:16:45 UTC" - + "\n[View on dashboard https://dash.pluralkit.me/profile/m/pphhoh] " - + "\n" - + "\n----" - + "\n" - + "\n| **System:** INX (`xffgnx`)" - + "\n| **Member:** Lillith (`pphhoh`)" - + "\n| **Sent by:** infinidoge1337 (@unknown-user:)" - + "\n| " - + "\n| **Account Roles (7)**" - + "\n| §b, !, ‼, Ears Port Ping, Ears Update Ping, Yttr Ping, unsup Ping" - + "\n| 🖼️ https://files.inx.moe/p/cdn/lillith.webp" - + "\n| " - + "\n| ----" - + "\n| " - + "\n| Same hat" - + "\n| 🖼️ Image: https://bridge.example.org/download/discordcdn/934955898965729280/1466556006527012987/image.png" - + "\n" - + "\n-# Original Message ID: 1466556003645657118 · ", - format: "org.matrix.custom.html", - formatted_body: "
" - + "

Lillith (INX)

" - + "

Display name: Lillith (she/her)" - + "
Pronouns: She/Her" - + "
Message count: 3091

" - + `🖼️ https://files.inx.moe/p/cdn/lillith.webp` - + "
" - + "

Proxy tags:" - + "
l;text" - + "
l:text" - + "
l.text" - + "
textl." - + "
textl;" - + "
textl:

" - + "

System ID: xffgnx ∙ Member ID: pphhoh
" - + "Created: 2025-12-31 03:16:45 UTC

" - + `View on dashboard ` - + "
" - + "

System: INX (xffgnx)" - + "
Member: Lillith (pphhoh)" - + "
Sent by: infinidoge1337 (@unknown-user:)" - + "

Account Roles (7)" - + "
§b, !, ‼, Ears Port Ping, Ears Update Ping, Yttr Ping, unsup Ping

" - + `🖼️ https://files.inx.moe/p/cdn/lillith.webp` - + "
" - + "

Same hat

" - + `🖼️ Image: image.png
` - + "

Original Message ID: 1466556003645657118 · <t:1769724599:f>

", - "m.mentions": {}, - msgtype: "m.text", - }]) -}) diff --git a/src/d2m/event-dispatcher.js b/src/d2m/event-dispatcher.js index c25d1c6..7c2e118 100644 --- a/src/d2m/event-dispatcher.js +++ b/src/d2m/event-dispatcher.js @@ -274,7 +274,7 @@ module.exports = { // Based on looking at data they've sent me over the gateway, this is the best way to check for meaningful changes. // If the message content is a string then it includes all interesting fields and is meaningful. // Otherwise, if there are embeds, then the system generated URL preview embeds. - if (!(typeof data.content === "string" || "embeds" in data || "components" in data)) return + if (!(typeof data.content === "string" || "embeds" in data)) return if (dUtils.isEphemeralMessage(data)) return // Ephemeral messages are for the eyes of the receiver only! @@ -282,10 +282,8 @@ module.exports = { const {affected, row} = await speedbump.maybeDoSpeedbump(data.channel_id, data.id) if (affected) return - if (!row) { - // Check that the sending-to room exists, and deal with Eventual Consistency(TM) - if (retrigger.eventNotFoundThenRetrigger(data.id, module.exports.MESSAGE_UPDATE, client, data)) return - } + // Check that the sending-to room exists, and deal with Eventual Consistency(TM) + if (retrigger.eventNotFoundThenRetrigger(data.id, module.exports.MESSAGE_UPDATE, client, data)) return /** @type {DiscordTypes.GatewayMessageCreateDispatchData} */ // @ts-ignore diff --git a/src/discord/interactions/ping.js b/src/discord/interactions/ping.js deleted file mode 100644 index 6487175..0000000 --- a/src/discord/interactions/ping.js +++ /dev/null @@ -1,201 +0,0 @@ -// @ts-check - -const assert = require("assert").strict -const Ty = require("../../types") -const DiscordTypes = require("discord-api-types/v10") -const {discord, sync, select, from} = require("../../passthrough") -const {id: botID} = require("../../../addbot") -const {InteractionMethods} = require("snowtransfer") - -/** @type {import("../../matrix/api")} */ -const api = sync.require("../../matrix/api") -/** @type {import("../../matrix/utils")} */ -const utils = sync.require("../../matrix/utils") -/** @type {import("../../web/routes/guild")} */ -const webGuild = sync.require("../../web/routes/guild") - -/** - * @param {DiscordTypes.APIApplicationCommandAutocompleteGuildInteraction} interaction - * @param {{api: typeof api}} di - * @returns {AsyncGenerator<{[k in keyof InteractionMethods]?: Parameters[2]}>} - */ -async function* _interactAutocomplete({data, channel}, {api}) { - function exit() { - return {createInteractionResponse: { - /** @type {DiscordTypes.InteractionResponseType.ApplicationCommandAutocompleteResult} */ - type: DiscordTypes.InteractionResponseType.ApplicationCommandAutocompleteResult, - data: { - choices: [] - } - }} - } - - // Check it was used in a bridged channel - const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() - if (!roomID) return yield exit() - - // Check we are in fact autocompleting the first option, the user - if (!data.options?.[0] || data.options[0].type !== DiscordTypes.ApplicationCommandOptionType.String || !data.options[0].focused) { - return yield exit() - } - - /** @type {{displayname: string | null, mxid: string}[][]} */ - const providedMatches = [] - - const input = data.options[0].value - if (input === "") { - const events = await api.getEvents(roomID, "b", {limit: 40}) - const recents = new Set(events.chunk.map(e => e.sender)) - const matches = select("member_cache", ["mxid", "displayname"], {room_id: roomID}, "AND displayname IS NOT NULL LIMIT 25").all() - matches.sort((a, b) => +recents.has(b.mxid) - +recents.has(a.mxid)) - providedMatches.push(matches) - } else if (input.startsWith("@")) { // only autocomplete mxids - const query = input.replaceAll(/[%_$]/g, char => `$${char}`) + "%" - const matches = select("member_cache", ["mxid", "displayname"], {room_id: roomID}, "AND mxid LIKE ? ESCAPE '$' LIMIT 25").all(query) - providedMatches.push(matches) - } else { - const query = "%" + input.replaceAll(/[%_$]/g, char => `$${char}`) + "%" - const displaynameMatches = select("member_cache", ["mxid", "displayname"], {room_id: roomID}, "AND displayname IS NOT NULL AND displayname LIKE ? ESCAPE '$' LIMIT 25").all(query) - // prioritise matches closer to the start - displaynameMatches.sort((a, b) => { - let ai = a.displayname.toLowerCase().indexOf(input.toLowerCase()) - if (ai === -1) ai = 999 - let bi = b.displayname.toLowerCase().indexOf(input.toLowerCase()) - if (bi === -1) bi = 999 - return ai - bi - }) - providedMatches.push(displaynameMatches) - let mxidMatches = select("member_cache", ["mxid", "displayname"], {room_id: roomID}, "AND displayname IS NOT NULL AND mxid LIKE ? ESCAPE '$' LIMIT 25").all(query) - mxidMatches = mxidMatches.filter(match => { - // don't include matches in domain part of mxid - if (!match.mxid.match(/^[^:]*/)?.includes(query)) return false - if (displaynameMatches.some(m => m.mxid === match.mxid)) return false - return true - }) - providedMatches.push(mxidMatches) - } - - // merge together - let matches = providedMatches.flat() - - // don't include bot - matches = matches.filter(m => m.mxid !== utils.bot) - - // remove duplicates and count up to 25 - const limitedMatches = [] - const seen = new Set() - for (const match of matches) { - if (limitedMatches.length >= 25) break - if (seen.has(match.mxid)) continue - limitedMatches.push(match) - seen.add(match.mxid) - } - - yield {createInteractionResponse: { - type: DiscordTypes.InteractionResponseType.ApplicationCommandAutocompleteResult, - data: { - choices: limitedMatches.map(row => ({name: (row.displayname || row.mxid).slice(0, 100), value: row.mxid.slice(0, 100)})) - } - }} -} - -/** - * @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction - * @param {{api: typeof api}} di - * @returns {AsyncGenerator<{[k in keyof InteractionMethods]?: Parameters[2]}>} - */ -async function* _interactCommand({data, channel, guild_id}, {api}) { - const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() - if (!roomID) { - return yield {createInteractionResponse: { - type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, - data: { - flags: DiscordTypes.MessageFlags.Ephemeral, - content: "This channel isn't bridged to Matrix." - } - }} - } - - assert(data.options?.[0]?.type === DiscordTypes.ApplicationCommandOptionType.String) - const mxid = data.options[0].value - if (!mxid.match(/^@[^:]*:./)) { - return yield {createInteractionResponse: { - type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, - data: { - flags: DiscordTypes.MessageFlags.Ephemeral, - // embeds: [{ - // description: "⚠️ To use /ping, you must select an option from autocomplete, or type a full Matrix ID.", - // footer: { - // text: "Tip: This command is not necessary. You can also ping Matrix users just by typing @their name in your message. It won't look like anything, but it does go through." - // } - // }] - content: "⚠️ To use `/ping`, you must select an option from autocomplete, or type a full Matrix ID.\n> Tip: This command is not necessary. You can also ping Matrix users just by typing @their name in your message. It won't look like anything, but it does go through." - } - }} - } - - yield {createInteractionResponse: { - type: DiscordTypes.InteractionResponseType.DeferredChannelMessageWithSource - }} - - try { - /** @type {Ty.Event.M_Room_Member} */ - var member = await api.getStateEvent(roomID, "m.room.member", mxid) - } catch (e) {} - - if (!member || member.membership !== "join") { - const inChannels = discord.guildChannelMap.get(guild_id) - .map(cid => discord.channels.get(cid)) - .sort((a, b) => webGuild._getPosition(a, discord.channels) - webGuild._getPosition(b, discord.channels)) - .filter(channel => from("channel_room").join("member_cache", "room_id").select("mxid").where({channel_id: channel.id, mxid}).get()) - if (inChannels.length) { - return yield {editOriginalInteractionResponse: { - content: `That person isn't in this channel. They have only joined the following channels:\n${inChannels.map(c => `<#${c.id}>`).join(" • ")}\nYou can ask them to join this channel with \`/invite\`.`, - }} - } else { - return yield {editOriginalInteractionResponse: { - content: "That person isn't in this channel. You can invite them with `/invite`." - }} - } - } - - yield {editOriginalInteractionResponse: { - content: "@" + (member.displayname || mxid) - }} - - yield {createFollowupMessage: { - flags: DiscordTypes.MessageFlags.Ephemeral | DiscordTypes.MessageFlags.IsComponentsV2, - components: [{ - type: DiscordTypes.ComponentType.Container, - components: [{ - type: DiscordTypes.ComponentType.TextDisplay, - content: "Tip: This command is not necessary. You can also ping Matrix users just by typing @their name in your message. It won't look like anything, but it does go through." - }] - }] - }} -} - -/* c8 ignore start */ - -/** @param {(DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}) | DiscordTypes.APIApplicationCommandAutocompleteGuildInteraction} interaction */ -async function interact(interaction) { - if (interaction.type === DiscordTypes.InteractionType.ApplicationCommandAutocomplete) { - for await (const response of _interactAutocomplete(interaction, {api})) { - if (response.createInteractionResponse) { - await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, response.createInteractionResponse) - } - } - } else { - for await (const response of _interactCommand(interaction, {api})) { - if (response.createInteractionResponse) { - await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, response.createInteractionResponse) - } else if (response.editOriginalInteractionResponse) { - await discord.snow.interaction.editOriginalInteractionResponse(botID, interaction.token, response.editOriginalInteractionResponse) - } else if (response.createFollowupMessage) { - await discord.snow.interaction.createFollowupMessage(botID, interaction.token, response.createFollowupMessage) - } - } - } -} - -module.exports.interact = interact diff --git a/src/discord/register-interactions.js b/src/discord/register-interactions.js index b37f28e..63b04b0 100644 --- a/src/discord/register-interactions.js +++ b/src/discord/register-interactions.js @@ -10,7 +10,6 @@ const permissions = sync.require("./interactions/permissions.js") const reactions = sync.require("./interactions/reactions.js") const privacy = sync.require("./interactions/privacy.js") const poll = sync.require("./interactions/poll.js") -const ping = sync.require("./interactions/ping.js") // User must have EVERY permission in default_member_permissions to be able to use the command @@ -39,20 +38,6 @@ discord.snow.interaction.bulkOverwriteApplicationCommands(id, [{ description: "The Matrix user to invite, e.g. @username:example.org", name: "user" } - ], -}, { - name: "ping", - contexts: [DiscordTypes.InteractionContextType.Guild], - type: DiscordTypes.ApplicationCommandType.ChatInput, - description: "Ping a Matrix user.", - options: [ - { - type: DiscordTypes.ApplicationCommandOptionType.String, - description: "Display name or ID of the Matrix user", - name: "user", - autocomplete: true, - required: true - } ] }, { name: "privacy", @@ -109,8 +94,6 @@ async function dispatchInteraction(interaction) { await permissions.interactEdit(interaction) } else if (interactionId === "Reactions") { await reactions.interact(interaction) - } else if (interactionId === "ping") { - await ping.interact(interaction) } else if (interactionId === "privacy") { await privacy.interact(interaction) } else { diff --git a/src/matrix/api.js b/src/matrix/api.js index 1cd05d3..7e503c2 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -128,19 +128,6 @@ async function getEventForTimestamp(roomID, ts) { return root } -/** - * @param {string} roomID - * @param {"b" | "f"} dir - * @param {{from?: string, limit?: any}} [pagination] - * @param {any} [filter] - */ -async function getEvents(roomID, dir, pagination = {}, filter) { - filter = filter && JSON.stringify(filter) - /** @type {Ty.Pagination>} */ - const root = await mreq.mreq("GET", path(`/client/v3/rooms/${roomID}/messages`, null, {...pagination, dir, filter})) - return root -} - /** * @param {string} roomID * @returns {Promise[]>} @@ -596,7 +583,6 @@ module.exports.leaveRoom = leaveRoom module.exports.leaveRoomWithReason = leaveRoomWithReason module.exports.getEvent = getEvent module.exports.getEventForTimestamp = getEventForTimestamp -module.exports.getEvents = getEvents module.exports.getAllState = getAllState module.exports.getStateEvent = getStateEvent module.exports.getStateEventOuter = getStateEventOuter diff --git a/src/matrix/utils.js b/src/matrix/utils.js index b131510..f299d95 100644 --- a/src/matrix/utils.js +++ b/src/matrix/utils.js @@ -106,8 +106,7 @@ class MatrixStringBuilder { if (formattedBody == undefined) formattedBody = body if (this.body.length && this.body.slice(-1) !== "\n") this.body += "\n\n" this.body += body - const match = formattedBody.match(/^<([a-zA-Z]+[a-zA-Z0-9]*)/) - if (!match || !BLOCK_ELEMENTS.includes(match[1].toUpperCase())) formattedBody = `

${formattedBody}

` + formattedBody = `

${formattedBody}

` this.formattedBody += formattedBody } return this diff --git a/test/data.js b/test/data.js index 09749e6..786737c 100644 --- a/test/data.js +++ b/test/data.js @@ -4975,194 +4975,6 @@ module.exports = { tts: false } }, - message_with_components: { - pk_question_mark_response: { - type: 0, - content: '', - mentions: [], - mention_roles: [], - attachments: [], - embeds: [], - timestamp: '2026-01-30T01:20:07.488000+00:00', - edited_timestamp: null, - flags: 32768, - author: { - id: '772659086046658620', - username: 'cadence.worm', - avatar: '466df0c98b1af1e1388f595b4c1ad1b9', - discriminator: '0', - public_flags: 0, - flags: 0, - banner: null, - accent_color: null, - global_name: 'cadence', - avatar_decoration_data: null, - collectibles: null, - display_name_styles: null, - banner_color: null, - clan: { - identity_guild_id: '532245108070809601', - identity_enabled: true, - tag: 'doll', - badge: 'dba08126b4e810a0e096cc7cd5bc37f0' - }, - primary_guild: { - identity_guild_id: '532245108070809601', - identity_enabled: true, - tag: 'doll', - badge: 'dba08126b4e810a0e096cc7cd5bc37f0' - } - }, - components: [ - { - type: 17, - id: 1, - accent_color: 1042150, - components: [ - { - type: 9, - id: 2, - components: [ - { type: 10, id: 3, content: '### Lillith (INX)' }, - { - type: 10, - id: 4, - content: '**Display name:** Lillith (she/her)\n' + - '**Pronouns:** She/Her\n' + - '**Message count:** 3091' - } - ], - accessory: { - type: 11, - id: 5, - media: { - id: '1466603856149610687', - url: 'https://files.inx.moe/p/cdn/lillith.webp', - proxy_url: 'https://images-ext-1.discordapp.net/external/Kn5b32mM4o8AAQbq0k39KOzp9-fy6D1tWKvK_XI27LI/https/files.inx.moe/p/cdn/lillith.webp', - width: 256, - height: 256, - placeholder: 'KVoKJwSnt7lZl5ecj1mal5eGWjAHZXIA', - placeholder_version: 1, - content_scan_metadata: { version: 4, flags: 0 }, - content_type: 'image/webp', - loading_state: 2, - flags: 0 - }, - description: null, - spoiler: false - } - }, - { type: 14, id: 6, spacing: 1, divider: true }, - { - type: 10, - id: 7, - content: '**Proxy tags:**\n' + - '``l;text``\n' + - '``l:text``\n' + - '``l.text``\n' + - '``textl.``\n' + - '``textl;``\n' + - '``textl:``' - } - ], - spoiler: false - }, - { - type: 9, - id: 8, - components: [ - { - type: 10, - id: 9, - content: '-# System ID: `xffgnx` ∙ Member ID: `pphhoh`\n' + - '-# Created: 2025-12-31 03:16:45 UTC' - } - ], - accessory: { - type: 2, - id: 10, - style: 5, - label: 'View on dashboard', - url: 'https://dash.pluralkit.me/profile/m/pphhoh' - } - }, - { type: 14, id: 11, spacing: 1, divider: true }, - { - type: 17, - id: 12, - accent_color: null, - components: [ - { - type: 9, - id: 13, - components: [ - { - type: 10, - id: 14, - content: '**System:** INX (`xffgnx`)\n' + - '**Member:** Lillith (`pphhoh`)\n' + - '**Sent by:** infinidoge1337 (<@197126718400626689>)\n' + - '\n' + - '**Account Roles (7)**\n' + - '§b, !, ‼, Ears Port Ping, Ears Update Ping, Yttr Ping, unsup Ping' - } - ], - accessory: { - type: 11, - id: 15, - media: { - id: '1466603856149610689', - url: 'https://files.inx.moe/p/cdn/lillith.webp', - proxy_url: 'https://images-ext-1.discordapp.net/external/Kn5b32mM4o8AAQbq0k39KOzp9-fy6D1tWKvK_XI27LI/https/files.inx.moe/p/cdn/lillith.webp', - width: 256, - height: 256, - placeholder: 'KVoKJwSnt7lZl5ecj1mal5eGWjAHZXIA', - placeholder_version: 1, - content_scan_metadata: { version: 4, flags: 0 }, - content_type: 'image/webp', - loading_state: 2, - flags: 0 - }, - description: null, - spoiler: false - } - }, - { type: 14, id: 16, spacing: 2, divider: true }, - { type: 10, id: 17, content: 'Same hat' }, - { - type: 12, - id: 18, - items: [ - { - media: { - id: '1466603856149610690', - url: 'https://cdn.discordapp.com/attachments/934955898965729280/1466556006527012987/image.png?ex=697d2c37&is=697bdab7&hm=09c5028be61ce01ebbdda5c79c42e4dc10d053ce0c4b12c9d84135a0708e9db6&', - proxy_url: 'https://media.discordapp.net/attachments/934955898965729280/1466556006527012987/image.png?ex=697d2c37&is=697bdab7&hm=09c5028be61ce01ebbdda5c79c42e4dc10d053ce0c4b12c9d84135a0708e9db6&', - width: 285, - height: 126, - placeholder: '0PcBA4BqSIl9t/dnn9f0rm0=', - placeholder_version: 1, - content_scan_metadata: { version: 4, flags: 0 }, - content_type: 'image/png', - loading_state: 2, - flags: 0 - }, - description: null, - spoiler: false - } - ] - } - ], - spoiler: false - }, - { - type: 10, - id: 19, - content: '-# Original Message ID: 1466556003645657118 · ' - } - ] - } - }, message_update: { edit_by_webhook: { application_id: "684280192553844747", diff --git a/test/test.js b/test/test.js index 81c079a..5ae9f67 100644 --- a/test/test.js +++ b/test/test.js @@ -160,9 +160,8 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../src/d2m/converters/emoji-to-key.test") require("../src/d2m/converters/lottie.test") require("../src/d2m/converters/message-to-event.test") - require("../src/d2m/converters/message-to-event.test.components") - require("../src/d2m/converters/message-to-event.test.embeds") - require("../src/d2m/converters/message-to-event.test.pk") + require("../src/d2m/converters/message-to-event.embeds.test") + require("../src/d2m/converters/message-to-event.pk.test") require("../src/d2m/converters/pins-to-list.test") require("../src/d2m/converters/remove-reaction.test") require("../src/d2m/converters/thread-to-announcement.test")