diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 2dd62e0..322f8be 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -120,7 +120,8 @@ async function channelToKState(channel, guild) { if (customAvatar) { avatarEventContent.url = customAvatar } else if (guild.icon) { - avatarEventContent.url = {$url: file.guildIcon(guild)} + avatarEventContent.discord_path = file.guildIcon(guild) + avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } let history_visibility = PRIVACY_ENUMS.ROOM_HISTORY_VISIBILITY[privacyLevel] diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index a9b8448..b15cba6 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -65,6 +65,7 @@ async function guildToKState(guild, privacyLevel) { "m.room.name/": {name: guild.name}, "m.room.avatar/": { $if: guild.icon, + discord_path: file.guildIcon(guild), url: {$url: file.guildIcon(guild)} }, "m.room.guest_access/": {guest_access: createRoom.PRIVACY_ENUMS.GUEST_ACCESS[privacyLevel]}, diff --git a/d2m/actions/create-space.test.js b/d2m/actions/create-space.test.js index c4111db..b1c1f06 100644 --- a/d2m/actions/create-space.test.js +++ b/d2m/actions/create-space.test.js @@ -14,6 +14,7 @@ test("guild2space: can generate kstate for a guild, passing privacy level 0", as await kstateUploadMxc(kstateStripConditionals(await guildToKState(testData.guild.general, 0))), { "m.room.avatar/": { + discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF" }, "m.room.guest_access/": { diff --git a/d2m/converters/message-to-event.embeds.test.js b/d2m/converters/message-to-event.embeds.test.js index 05e3b5d..61a0822 100644 --- a/d2m/converters/message-to-event.embeds.test.js +++ b/d2m/converters/message-to-event.embeds.test.js @@ -6,13 +6,6 @@ const Ty = require("../../types") test("message2event embeds: nothing but a field", async t => { const events = await messageToEvent(data.message_with_embeds.nothing_but_a_field, data.guild.general, {}) t.deepEqual(events, [{ - $type: "m.room.message", - body: "> ↪️ @papiophidian: used `/stats`", - format: "org.matrix.custom.html", - formatted_body: "
↪️ @papiophidian used /stats
", - "m.mentions": {}, - msgtype: "m.text", - }, { $type: "m.room.message", "m.mentions": {}, msgtype: "m.notice", @@ -150,13 +143,6 @@ test("message2event embeds: crazy html is all escaped", async t => { test("message2event embeds: title without url", async t => { const events = await messageToEvent(data.message_with_embeds.title_without_url, data.guild.general) t.deepEqual(events, [{ - $type: "m.room.message", - body: "> ↪️ @papiophidian: used `/stats`", - format: "org.matrix.custom.html", - formatted_body: "
↪️ @papiophidian used /stats
", - "m.mentions": {}, - msgtype: "m.text", - }, { $type: "m.room.message", msgtype: "m.notice", body: "| ## Hi, I'm Amanda!\n| \n| I condone pirating music!", @@ -169,13 +155,6 @@ test("message2event embeds: title without url", async t => { test("message2event embeds: url without title", async t => { const events = await messageToEvent(data.message_with_embeds.url_without_title, data.guild.general) t.deepEqual(events, [{ - $type: "m.room.message", - body: "> ↪️ @papiophidian: used `/stats`", - format: "org.matrix.custom.html", - formatted_body: "
↪️ @papiophidian used /stats
", - "m.mentions": {}, - msgtype: "m.text", - }, { $type: "m.room.message", msgtype: "m.notice", body: "| I condone pirating music!", @@ -188,13 +167,6 @@ test("message2event embeds: url without title", async t => { test("message2event embeds: author without url", async t => { const events = await messageToEvent(data.message_with_embeds.author_without_url, data.guild.general) t.deepEqual(events, [{ - $type: "m.room.message", - body: "> ↪️ @papiophidian: used `/stats`", - format: "org.matrix.custom.html", - formatted_body: "
↪️ @papiophidian used /stats
", - "m.mentions": {}, - msgtype: "m.text", - }, { $type: "m.room.message", msgtype: "m.notice", body: "| ## Amanda\n| \n| I condone pirating music!", @@ -207,13 +179,6 @@ test("message2event embeds: author without url", async t => { test("message2event embeds: author url without name", async t => { const events = await messageToEvent(data.message_with_embeds.author_url_without_name, data.guild.general) t.deepEqual(events, [{ - $type: "m.room.message", - body: "> ↪️ @papiophidian: used `/stats`", - format: "org.matrix.custom.html", - formatted_body: "
↪️ @papiophidian used /stats
", - "m.mentions": {}, - msgtype: "m.text", - }, { $type: "m.room.message", msgtype: "m.notice", body: "| I condone pirating music!", diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index b86293e..1e77d9d 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -32,10 +32,7 @@ function getDiscordParseCallbacks(message, guild, useHTML) { /** @param {{id: string, type: "discordUser"}} node */ user: node => { const mxid = select("sim", "mxid", {user_id: node.id}).pluck().get() - const interaction = message.interaction_metadata || message.interaction - const username = message.mentions.find(ment => ment.id === node.id)?.username - || (interaction?.user.id === node.id ? interaction.user.username : null) - || node.id + const username = message.mentions.find(ment => ment.id === node.id)?.username || node.id if (mxid && useHTML) { return `@${username}` } else { @@ -232,13 +229,6 @@ async function messageToEvent(message, guild, options = {}, di) { }] } - const interaction = message.interaction_metadata || message.interaction - if (message.type === DiscordTypes.MessageType.ChatInputCommand && interaction && "name" in interaction) { - // Commands are sent by the responding bot. Need to attach the metadata of the person using the command at the top. - if (message.content) message.content = `\n${message.content}` - message.content = `> ↪️ <@${interaction.user.id}> used \`/${interaction.name}\`${message.content}` - } - /** @type {{room?: boolean, user_ids?: string[]}} We should consider the following scenarios for mentions: @@ -373,7 +363,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?):([^:>]{1,64}):([0-9]+)>/g)] + const emojiMatches = [...content.matchAll(/<(a?):([^:>]{2,64}):([0-9]+)>/g)] await Promise.all(emojiMatches.map(match => { const id = match[3] const name = match[2] diff --git a/d2m/discord-client.js b/d2m/discord-client.js index ace8481..80dcbcf 100644 --- a/d2m/discord-client.js +++ b/d2m/discord-client.js @@ -57,9 +57,6 @@ class DiscordClient { addEventLogger("error", "Error") addEventLogger("disconnected", "Disconnected") addEventLogger("ready", "Ready") - this.snow.requestHandler.on("requestError", (requestID, error) => { - console.error("request error:", error) - }) } } diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index ed47fae..0981827 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -181,7 +181,7 @@ const utils = { } catch (e) { // Let OOYE try to handle errors too - await eventDispatcher.onError(client, e, message) + eventDispatcher.onError(client, e, message) } } } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 57cb72c..7f27b77 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -50,7 +50,7 @@ module.exports = { * @param {Error} e * @param {import("cloudstorm").IGatewayMessage} gatewayMessage */ - async onError(client, e, gatewayMessage) { + onError(client, e, gatewayMessage) { console.error("hit event-dispatcher's error handler with this exception:") console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? definitely need to be able to store the formatted event body to load back in later console.error(`while handling this ${gatewayMessage.t} gateway event:`) @@ -83,7 +83,7 @@ module.exports = { builder.addLine(`Error trace:\n${stackLines.join("\n")}`, `
Error trace
${stackLines.join("\n")}
`) } builder.addLine("", `
Original payload
${util.inspect(gatewayMessage.d, false, 4, false)}
`) - await api.sendEvent(roomID, "m.room.message", { + api.sendEvent(roomID, "m.room.message", { ...builder.get(), "moe.cadence.ooye.error": { source: "discord", diff --git a/db/orm-defs.d.ts b/db/orm-defs.d.ts index 7484d76..e481f95 100644 --- a/db/orm-defs.d.ts +++ b/db/orm-defs.d.ts @@ -114,4 +114,3 @@ export type AllKeys = U extends any ? keyof U : never export type PickTypeOf> = T extends { [k in K]?: any } ? T[K] : never export type Merge = {[x in AllKeys]: PickTypeOf} export type Nullable = {[k in keyof T]: T[k] | null} -export type Numberish = {[k in keyof T]: T[k] extends number ? (number | bigint) : T[k]} diff --git a/db/orm.js b/db/orm.js index 601a7a0..09e4bc7 100644 --- a/db/orm.js +++ b/db/orm.js @@ -8,7 +8,7 @@ const U = require("./orm-defs") * @template {keyof U.Models[Table]} Col * @param {Table} table * @param {Col[] | Col} cols - * @param {Partial>} where + * @param {Partial} where * @param {string} [e] */ function select(table, cols, where = {}, e = "") { @@ -108,7 +108,7 @@ class From { } /** - * @param {Partial>} conditions + * @param {Partial} conditions */ where(conditions) { const wheres = Object.entries(conditions).map(([col, value]) => { diff --git a/discord/interactions/bridge.js b/discord/interactions/bridge.js index b2d1ac0..ee33bfd 100644 --- a/discord/interactions/bridge.js +++ b/discord/interactions/bridge.js @@ -39,7 +39,7 @@ async function getCachedHierarchy(spaceID) { /** @type {{name: string, value: string}[]} */ const childRooms = [] for (const room of result) { - if (room.name && !room.name.match(/^\[[⛓️🔊]\]/) && room.room_type !== "m.space") { + if (room.name) { childRooms.push({name: room.name, value: room.room_id}) reverseCache.set(room.room_id, spaceID) } diff --git a/discord/interactions/invite.js b/discord/interactions/invite.js index 689ea1a..0590be8 100644 --- a/discord/interactions/invite.js +++ b/discord/interactions/invite.js @@ -7,34 +7,31 @@ const {discord, sync, db, select, from} = require("../../passthrough") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") -/** - * @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction - * @returns {Promise} - */ -async function _interact({data, channel, guild_id}) { +/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction */ +async function interact({id, token, data, channel, member, guild_id}) { // Check guild is bridged const spaceID = select("guild_space", "space_id", {guild_id}).pluck().get() const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() - if (!spaceID || !roomID) return { + if (!spaceID || !roomID) return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: "This server isn't bridged to Matrix, so you can't invite Matrix users.", flags: DiscordTypes.MessageFlags.Ephemeral } - } + }) // Get named MXID /** @type {DiscordTypes.APIApplicationCommandInteractionDataStringOption[] | undefined} */ // @ts-ignore const options = data.options const input = options?.[0].value || "" const mxid = input.match(/@([^:]+):([a-z0-9:-]+\.[a-z0-9.:-]+)/)?.[0] - if (!mxid) return { + if (!mxid) return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`", flags: DiscordTypes.MessageFlags.Ephemeral } - } + }) // Check for existing invite to the space let spaceMember @@ -42,24 +39,24 @@ async function _interact({data, channel, guild_id}) { spaceMember = await api.getStateEvent(spaceID, "m.room.member", mxid) } catch (e) {} if (spaceMember && spaceMember.membership === "invite") { - return { + return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `\`${mxid}\` already has an invite, which they haven't accepted yet.`, flags: DiscordTypes.MessageFlags.Ephemeral } - } + }) } // Invite Matrix user if not in space if (!spaceMember || spaceMember.membership !== "join") { await api.inviteToRoom(spaceID, mxid) - return { + return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `You invited \`${mxid}\` to the server.` } - } + }) } // The Matrix user *is* in the space, maybe we want to invite them to this channel? @@ -68,7 +65,7 @@ async function _interact({data, channel, guild_id}) { roomMember = await api.getStateEvent(roomID, "m.room.member", mxid) } catch (e) {} if (!roomMember || (roomMember.membership !== "join" && roomMember.membership !== "invite")) { - return { + return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `\`${mxid}\` is already in this server. Would you like to additionally invite them to this specific channel?`, @@ -83,49 +80,34 @@ async function _interact({data, channel, guild_id}) { }] }] } - } + }) } // The Matrix user *is* in the space and in the channel. - return { + return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `\`${mxid}\` is already in this server and this channel.`, flags: DiscordTypes.MessageFlags.Ephemeral } - } + }) } -/** - * @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction - * @returns {Promise} - */ -async function _interactButton({channel, message}) { +/** @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction */ +async function interactButton({id, token, data, channel, member, guild_id, message}) { const mxid = message.content.match(/`(@(?:[^:]+):(?:[a-z0-9:-]+\.[a-z0-9.:-]+))`/)?.[1] assert(mxid) const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get() await api.inviteToRoom(roomID, mxid) - return { + return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.UpdateMessage, data: { content: `You invited \`${mxid}\` to the channel.`, flags: DiscordTypes.MessageFlags.Ephemeral, components: [] } - } -} - -/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction */ -async function interact(interaction) { - await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interact(interaction)) -} - -/** @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction */ -async function interactButton(interaction) { - await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interactButton(interaction)) + }) } module.exports.interact = interact module.exports.interactButton = interactButton -module.exports._interact = _interact -module.exports._interactButton = _interactButton diff --git a/discord/interactions/matrix-info.js b/discord/interactions/matrix-info.js index fac3804..9f0e9e1 100644 --- a/discord/interactions/matrix-info.js +++ b/discord/interactions/matrix-info.js @@ -7,8 +7,7 @@ const {discord, sync, db, select, from} = require("../../passthrough") const api = sync.require("../../matrix/api") /** @param {DiscordTypes.APIContextMenuGuildInteraction} interaction */ -/** @param {DiscordTypes.APIMessageApplicationCommandGuildInteraction} interaction */ -async function interact({id, token, guild_id, channel, data}) { +async function interact({id, token, data}) { const message = from("event_message").join("message_channel", "message_id").join("channel_room", "channel_id") .select("name", "nick", "source", "room_id", "event_id").where({message_id: data.target_id}).get() @@ -22,15 +21,12 @@ async function interact({id, token, guild_id, channel, data}) { }) } - const idInfo = `\n-# Room ID: \`${message.room_id}\`\n-# Event ID: \`${message.event_id}\`` - if (message.source === 1) { // from Discord - const userID = data.resolved.messages[data.target_id].author.id return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { - content: `Bridged <@${userID}> https://discord.com/channels/${guild_id}/${channel.id}/${data.target_id} on Discord to [${message.nick || message.name}]() on Matrix.` - + idInfo, + content: `This message was bridged to [${message.nick || message.name}]() on Matrix.` + + `\n-# Room ID: \`${message.room_id}\`\n-# Event ID: \`${message.event_id}\``, flags: DiscordTypes.MessageFlags.Ephemeral } }) @@ -41,8 +37,9 @@ async function interact({id, token, guild_id, channel, data}) { return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { - content: `Bridged [${event.sender}]()'s message in [${message.nick || message.name}]() on Matrix to https://discord.com/channels/${guild_id}/${channel.id}/${data.target_id} on Discord.` - + idInfo, + content: `This message was bridged from [${message.nick || message.name}]() on Matrix.` + + `\nIt was originally sent by [${event.sender}]().` + + `\n-# Room ID: \`${message.room_id}\`\n-# Event ID: \`${message.event_id}\``, flags: DiscordTypes.MessageFlags.Ephemeral } }) diff --git a/discord/interactions/permissions.js b/discord/interactions/permissions.js index 82c3d3c..d30f632 100644 --- a/discord/interactions/permissions.js +++ b/discord/interactions/permissions.js @@ -5,27 +5,23 @@ const Ty = require("../../types") const {discord, sync, db, select, from} = require("../../passthrough") const assert = require("assert/strict") - /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") -/** - * @param {DiscordTypes.APIContextMenuGuildInteraction} interaction - * @returns {Promise} - */ -async function _interact({data, channel, guild_id}) { +/** @param {DiscordTypes.APIContextMenuGuildInteraction} interaction */ +async function interact({data, channel, id, token, guild_id}) { const row = select("event_message", ["event_id", "source"], {message_id: data.target_id}).get() assert(row) // Can't operate on Discord users if (row.source === 1) { // discord - return { + return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `This command is only meaningful for Matrix users.`, flags: DiscordTypes.MessageFlags.Ephemeral } - } + }) } // Get the message sender, the person that will be inspected/edited @@ -46,16 +42,16 @@ async function _interact({data, channel, guild_id}) { // Administrators equal to the bot cannot be demoted if (userPower >= 100) { - return { + return discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `\`${sender}\` has administrator permissions. This cannot be edited.`, flags: DiscordTypes.MessageFlags.Ephemeral } - } + }) } - return { + await discord.snow.interaction.createInteractionResponse(id, token, { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `Showing permissions for \`${sender}\`. Click to edit.`, @@ -83,47 +79,30 @@ async function _interact({data, channel, guild_id}) { } ] } - } + }) } -/** - * @param {DiscordTypes.APIMessageComponentSelectMenuInteraction} interaction - */ -async function interactEdit({data, id, token, guild_id, message}) { +/** @param {DiscordTypes.APIMessageComponentSelectMenuInteraction} interaction */ +async function interactEdit({data, channel, id, token, guild_id, message}) { // Get the person that will be inspected/edited const mxid = message.content.match(/`(@(?:[^:]+):(?:[a-z0-9:-]+\.[a-z0-9.:-]+))`/)?.[1] assert(mxid) - const permission = data.values[0] - const power = permission === "moderator" ? 50 : 0 - - await discord.snow.interaction.createInteractionResponse(id, token, { - type: DiscordTypes.InteractionResponseType.UpdateMessage, - data: { - content: `Updating \`${mxid}\` to **${permission}**, please wait...`, - components: [] - } - }) - // Get the space, where the power levels will be inspected/edited const spaceID = select("guild_space", "space_id", {guild_id}).pluck().get() assert(spaceID) // Do it - await api.setUserPowerCascade(spaceID, mxid, power) + const permission = data.values[0] + const power = permission === "moderator" ? 50 : 0 + await api.setUserPower(spaceID, mxid, power) + // TODO: Cascade permissions through room hierarchy (make a helper for this already, geez...) // ACK - await discord.snow.interaction.editOriginalInteractionResponse(discord.application.id, token, { - content: `Updated \`${mxid}\` to **${permission}**.`, - components: [] + await discord.snow.interaction.createInteractionResponse(id, token, { + type: DiscordTypes.InteractionResponseType.DeferredMessageUpdate }) } -/** @param {DiscordTypes.APIContextMenuGuildInteraction} interaction */ -async function interact(interaction) { - await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interact(interaction)) -} - module.exports.interact = interact module.exports.interactEdit = interactEdit -module.exports._interact = _interact diff --git a/discord/register-interactions.js b/discord/register-interactions.js index a6e4332..79bcb14 100644 --- a/discord/register-interactions.js +++ b/discord/register-interactions.js @@ -48,23 +48,23 @@ discord.snow.interaction.bulkOverwriteApplicationCommands(id, [{ }]) async function dispatchInteraction(interaction) { - const interactionId = interaction.data.custom_id || interaction.data.name + const id = interaction.data.custom_id || interaction.data.name try { console.log(interaction) - if (interactionId === "Matrix info") { + if (id === "Matrix info") { await matrixInfo.interact(interaction) - } else if (interactionId === "invite") { + } else if (id === "invite") { await invite.interact(interaction) - } else if (interactionId === "invite_channel") { + } else if (id === "invite_channel") { await invite.interactButton(interaction) - } else if (interactionId === "Permissions") { + } else if (id === "Permissions") { await permissions.interact(interaction) - } else if (interactionId === "permissions_edit") { + } else if (id === "permissions_edit") { await permissions.interactEdit(interaction) - } else if (interactionId === "bridge") { + } else if (id === "bridge") { await bridge.interact(interaction) } else { - throw new Error(`Unknown interaction ${interactionId}`) + throw new Error(`Unknown interaction ${id}`) } } catch (e) { let stackLines = null @@ -75,11 +75,14 @@ async function dispatchInteraction(interaction) { stackLines = stackLines.slice(0, cloudstormLine - 2) } } - await discord.snow.interaction.createFollowupMessage(id, interaction.token, { - content: `Interaction failed: **${interactionId}**` - + `\nError trace:\n\`\`\`\n${stackLines.join("\n")}\`\`\`` - + `Interaction data:\n\`\`\`\n${JSON.stringify(interaction.data, null, 2)}\`\`\``, - flags: DiscordTypes.MessageFlags.Ephemeral + discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, { + type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, + data: { + content: `Interaction failed: **${id}**` + + `\nError trace:\n\`\`\`\n${stackLines.join("\n")}\`\`\`` + + `Interaction data:\n\`\`\`\n${JSON.stringify(interaction.data, null, 2)}\`\`\``, + flags: DiscordTypes.MessageFlags.Ephemeral + } }) } } diff --git a/discord/utils.js b/discord/utils.js index 865b2e3..57e563f 100644 --- a/discord/utils.js +++ b/discord/utils.js @@ -97,7 +97,8 @@ function hasAllPermissions(resolvedPermissions, permissionsToCheckFor) { * @param {DiscordTypes.APIMessage} message */ function isWebhookMessage(message) { - return message.webhook_id && message.type !== DiscordTypes.MessageType.ChatInputCommand + const isInteractionResponse = message.type === 20 + return message.webhook_id && !isInteractionResponse } /** diff --git a/m2d/actions/redact.js b/m2d/actions/redact.js index ffbb261..7569df4 100644 --- a/m2d/actions/redact.js +++ b/m2d/actions/redact.js @@ -25,6 +25,7 @@ async function deleteMessage(event) { */ async function removeReaction(event) { const hash = utils.getEventIDHash(event.redacts) + // TODO: this works but fix the type const row = from("reaction").join("message_channel", "message_id").select("channel_id", "message_id", "encoded_emoji").where({hashed_event_id: hash}).get() if (!row) return await discord.snow.channel.deleteReactionSelf(row.channel_id, row.message_id, row.encoded_emoji) diff --git a/matrix/api.js b/matrix/api.js index e94a1a5..7d8ea9f 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -260,21 +260,6 @@ async function setUserPower(roomID, mxid, power) { return powerLevels } -/** - * Set a user's power level for a whole room hierarchy. - * @param {string} roomID - * @param {string} mxid - * @param {number} power - */ -async function setUserPowerCascade(roomID, mxid, power) { - assert(roomID[0] === "!") - assert(mxid[0] === "@") - const rooms = await getFullHierarchy(roomID) - for (const room of rooms) { - await setUserPower(room.room_id, mxid, power) - } -} - module.exports.path = path module.exports.register = register module.exports.createRoom = createRoom @@ -296,4 +281,3 @@ module.exports.sendTyping = sendTyping module.exports.profileSetDisplayname = profileSetDisplayname module.exports.profileSetAvatarUrl = profileSetAvatarUrl module.exports.setUserPower = setUserPower -module.exports.setUserPowerCascade = setUserPowerCascade diff --git a/matrix/power.js b/matrix/power.js index cd2b8cb..5dac550 100644 --- a/matrix/power.js +++ b/matrix/power.js @@ -1,6 +1,7 @@ // @ts-check const {db, from} = require("../passthrough") +const api = require("./api") const reg = require("./read-registration") const ks = require("./kstate") const {applyKStateDiffToRoom, roomToKState} = require("../d2m/actions/create-room") @@ -10,16 +11,13 @@ for (const mxid of reg.ooye.invite) { db.prepare("INSERT OR IGNORE INTO member_power (mxid, room_id, power_level) VALUES (?, ?, 100)").run(mxid, "*") } -/** Apply global power level requests across ALL rooms where the member cache entry exists but the power level has not been applied yet. */ -function _getAffectedRooms() { - return from("member_cache").join("member_power", "mxid") - .and("where member_power.room_id = '*' and member_cache.power_level != member_power.power_level") - .selectUnsafe("mxid", "member_cache.room_id", "member_power.power_level") - .all() -} +// Apply global power level requests across ALL rooms where the member cache entry exists but the power level has not been applied yet. +const rows = from("member_cache").join("member_power", "mxid") + .and("where member_power.room_id = '*' and member_cache.power_level != member_power.power_level") + .selectUnsafe("mxid", "member_cache.room_id", "member_power.power_level") + .all() -async function applyPower() { - const rows = _getAffectedRooms() +;(async () => { for (const row of rows) { const kstate = await roomToKState(row.room_id) const diff = ks.diffKState(kstate, {"m.room.power_levels/": {users: {[row.mxid]: row.power_level}}}) @@ -28,7 +26,4 @@ async function applyPower() { // but we update it here anyway since the homeserver does not always deliver the event round-trip. db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(row.power_level, row.room_id, row.mxid) } -} - -module.exports._getAffectedRooms = _getAffectedRooms -module.exports.applyPower = applyPower +})() diff --git a/start.js b/start.js index 1ece1dd..63f5c57 100644 --- a/start.js +++ b/start.js @@ -25,14 +25,17 @@ const orm = sync.require("./db/orm") passthrough.from = orm.from passthrough.select = orm.select -const power = require("./matrix/power.js") sync.require("./m2d/event-dispatcher") +discord.snow.requestHandler.on("requestError", data => { + console.error("request error", data) +}) + ;(async () => { await migrate.migrate(db) await discord.cloud.connect() console.log("Discord gateway started") - await power.applyPower() + require("./matrix/power.js") require("./stdin") })() diff --git a/test/data.js b/test/data.js index 456033a..771c183 100644 --- a/test/data.js +++ b/test/data.js @@ -38,7 +38,8 @@ module.exports = { }] }, "m.room.avatar/": { - url: {$url: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024"} + discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024", + url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF" }, "m.room.power_levels/": { events: { diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index 4666b4d..1fb9e24 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -23,8 +23,7 @@ INSERT INTO sim (user_id, sim_name, localpart, mxid) VALUES ('114147806469554185', 'extremity', '_ooye_extremity', '@_ooye_extremity:cadence.moe'), ('111604486476181504', 'kyuugryphon', '_ooye_kyuugryphon', '@_ooye_kyuugryphon:cadence.moe'), ('1109360903096369153', 'amanda', '_ooye_amanda', '@_ooye_amanda:cadence.moe'), -('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '_pk_zoego', '_ooye__pk_zoego', '@_ooye__pk_zoego:cadence.moe'), -('320067006521147393', 'papiophidian', '_ooye_papiophidian', '@_ooye_papiophidian:cadence.moe'); +('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '_pk_zoego', '_ooye__pk_zoego', '@_ooye__pk_zoego:cadence.moe'); INSERT INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES ('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '196188877885538304', 'Azalea &flwr; 🌺'); @@ -126,21 +125,19 @@ INSERT INTO emoji (emoji_id, name, animated, mxc_url) VALUES ('606664341298872324', 'online', 0, 'mxc://cadence.moe/LCEqjStXCxvRQccEkuslXEyZ'), ('288858540888686602', 'upstinky', 0, 'mxc://cadence.moe/mwZaCtRGAQQyOItagDeCocEO'); -INSERT INTO member_cache (room_id, mxid, displayname, avatar_url, power_level) VALUES -('!kLRqKKUQXcibIMtOpl:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL, 0), -('!BpMdOUkWWhFxmTrENV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'malformed mxc', 0), -('!fGgIymcYWOqjbSRUdV:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU', 0), -('!fGgIymcYWOqjbSRUdV:cadence.moe', '@rnl:cadence.moe', 'RNL', NULL, 0), -('!BnKuBPCvyfOkhcUjEu:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU', 0), -('!maggESguZBqGBZtSnr:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU', 0), -('!CzvdIdUQXgUjDVKxeU:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU', 0), -('!cBxtVRxDlZvSVhJXVK:cadence.moe', '@Milan:tchncs.de', 'Milan', NULL, 0), -('!TqlyQmifxGUggEmdBN:cadence.moe', '@ampflower:matrix.org', 'Ampflower 🌺', 'mxc://cadence.moe/PRfhXYBTOalvgQYtmCLeUXko', 0), -('!TqlyQmifxGUggEmdBN:cadence.moe', '@aflower:syndicated.gay', 'Rose', 'mxc://syndicated.gay/ZkBUPXCiXTjdJvONpLJmcbKP', 0), -('!TqlyQmifxGUggEmdBN:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL, 0), -('!BnKuBPCvyfOkhcUjEu:cadence.moe', '@ami:the-apothecary.club', 'Ami (she/her)', NULL, 0), -('!kLRqKKUQXcibIMtOpl:cadence.moe', '@test_auto_invite:example.org', NULL, NULL, 0), -('!BpMdOUkWWhFxmTrENV:cadence.moe', '@test_auto_invite:example.org', NULL, NULL, 100); +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'), +('!fGgIymcYWOqjbSRUdV:cadence.moe', '@rnl:cadence.moe', 'RNL', NULL), +('!BnKuBPCvyfOkhcUjEu:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'), +('!maggESguZBqGBZtSnr:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'), +('!CzvdIdUQXgUjDVKxeU:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', 'mxc://cadence.moe/azCAhThKTojXSZJRoWwZmhvU'), +('!cBxtVRxDlZvSVhJXVK:cadence.moe', '@Milan:tchncs.de', 'Milan', NULL), +('!TqlyQmifxGUggEmdBN:cadence.moe', '@ampflower:matrix.org', 'Ampflower 🌺', 'mxc://cadence.moe/PRfhXYBTOalvgQYtmCLeUXko'), +('!TqlyQmifxGUggEmdBN:cadence.moe', '@aflower:syndicated.gay', 'Rose', 'mxc://syndicated.gay/ZkBUPXCiXTjdJvONpLJmcbKP'), +('!TqlyQmifxGUggEmdBN:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL), +('!BnKuBPCvyfOkhcUjEu:cadence.moe', '@ami:the-apothecary.club', 'Ami (she/her)', NULL); INSERT INTO member_power (mxid, room_id, power_level) VALUES ('@test_auto_invite:example.org', '*', 100); diff --git a/test/test.js b/test/test.js index 796ff68..b5977f1 100644 --- a/test/test.js +++ b/test/test.js @@ -23,7 +23,6 @@ reg.ooye.server_name = "cadence.moe" reg.id = "baby" // don't actually take authenticated actions on the server reg.as_token = "baby" reg.hs_token = "baby" -reg.ooye.invite = [] const sync = new HeatSync({watchFS: false}) @@ -117,7 +116,6 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not require("../matrix/kstate.test") require("../matrix/api.test") require("../matrix/file.test") - require("../matrix/power.test") require("../matrix/read-registration.test") require("../matrix/txnid.test") require("../d2m/actions/create-room.test")