diff --git a/d2m/discord-client.js b/d2m/discord-client.js index 80dcbcf..ace8481 100644 --- a/d2m/discord-client.js +++ b/d2m/discord-client.js @@ -57,6 +57,9 @@ 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/discord/interactions/permissions.js b/discord/interactions/permissions.js index d30f632..82c3d3c 100644 --- a/discord/interactions/permissions.js +++ b/discord/interactions/permissions.js @@ -5,23 +5,27 @@ 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 */ -async function interact({data, channel, id, token, guild_id}) { +/** + * @param {DiscordTypes.APIContextMenuGuildInteraction} interaction + * @returns {Promise} + */ +async function _interact({data, channel, 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 discord.snow.interaction.createInteractionResponse(id, token, { + return { 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 @@ -42,16 +46,16 @@ async function interact({data, channel, id, token, guild_id}) { // Administrators equal to the bot cannot be demoted if (userPower >= 100) { - return discord.snow.interaction.createInteractionResponse(id, token, { + return { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `\`${sender}\` has administrator permissions. This cannot be edited.`, flags: DiscordTypes.MessageFlags.Ephemeral } - }) + } } - await discord.snow.interaction.createInteractionResponse(id, token, { + return { type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, data: { content: `Showing permissions for \`${sender}\`. Click to edit.`, @@ -79,30 +83,47 @@ async function interact({data, channel, id, token, guild_id}) { } ] } - }) + } } -/** @param {DiscordTypes.APIMessageComponentSelectMenuInteraction} interaction */ -async function interactEdit({data, channel, id, token, guild_id, message}) { +/** + * @param {DiscordTypes.APIMessageComponentSelectMenuInteraction} interaction + */ +async function interactEdit({data, 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 - 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...) + await api.setUserPowerCascade(spaceID, mxid, power) // ACK - await discord.snow.interaction.createInteractionResponse(id, token, { - type: DiscordTypes.InteractionResponseType.DeferredMessageUpdate + await discord.snow.interaction.editOriginalInteractionResponse(discord.application.id, token, { + content: `Updated \`${mxid}\` to **${permission}**.`, + components: [] }) } +/** @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 79bcb14..a6e4332 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 id = interaction.data.custom_id || interaction.data.name + const interactionId = interaction.data.custom_id || interaction.data.name try { console.log(interaction) - if (id === "Matrix info") { + if (interactionId === "Matrix info") { await matrixInfo.interact(interaction) - } else if (id === "invite") { + } else if (interactionId === "invite") { await invite.interact(interaction) - } else if (id === "invite_channel") { + } else if (interactionId === "invite_channel") { await invite.interactButton(interaction) - } else if (id === "Permissions") { + } else if (interactionId === "Permissions") { await permissions.interact(interaction) - } else if (id === "permissions_edit") { + } else if (interactionId === "permissions_edit") { await permissions.interactEdit(interaction) - } else if (id === "bridge") { + } else if (interactionId === "bridge") { await bridge.interact(interaction) } else { - throw new Error(`Unknown interaction ${id}`) + throw new Error(`Unknown interaction ${interactionId}`) } } catch (e) { let stackLines = null @@ -75,14 +75,11 @@ async function dispatchInteraction(interaction) { stackLines = stackLines.slice(0, cloudstormLine - 2) } } - 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 - } + 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 }) } } diff --git a/matrix/api.js b/matrix/api.js index 7d8ea9f..e94a1a5 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -260,6 +260,21 @@ 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 @@ -281,3 +296,4 @@ module.exports.sendTyping = sendTyping module.exports.profileSetDisplayname = profileSetDisplayname module.exports.profileSetAvatarUrl = profileSetAvatarUrl module.exports.setUserPower = setUserPower +module.exports.setUserPowerCascade = setUserPowerCascade diff --git a/start.js b/start.js index 63f5c57..e218819 100644 --- a/start.js +++ b/start.js @@ -27,10 +27,6 @@ passthrough.select = orm.select 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()