diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js
index 322f8be..2dd62e0 100644
--- a/d2m/actions/create-room.js
+++ b/d2m/actions/create-room.js
@@ -120,8 +120,7 @@ async function channelToKState(channel, guild) {
if (customAvatar) {
avatarEventContent.url = customAvatar
} else if (guild.icon) {
- 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
+ avatarEventContent.url = {$url: file.guildIcon(guild)}
}
let history_visibility = PRIVACY_ENUMS.ROOM_HISTORY_VISIBILITY[privacyLevel]
diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js
index b15cba6..a9b8448 100644
--- a/d2m/actions/create-space.js
+++ b/d2m/actions/create-space.js
@@ -65,7 +65,6 @@ 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 b1c1f06..c4111db 100644
--- a/d2m/actions/create-space.test.js
+++ b/d2m/actions/create-space.test.js
@@ -14,7 +14,6 @@ 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 61a0822..05e3b5d 100644
--- a/d2m/converters/message-to-event.embeds.test.js
+++ b/d2m/converters/message-to-event.embeds.test.js
@@ -6,6 +6,13 @@ 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",
@@ -143,6 +150,13 @@ 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!",
@@ -155,6 +169,13 @@ 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!",
@@ -167,6 +188,13 @@ 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!",
@@ -179,6 +207,13 @@ 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 1e77d9d..b86293e 100644
--- a/d2m/converters/message-to-event.js
+++ b/d2m/converters/message-to-event.js
@@ -32,7 +32,10 @@ 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 username = message.mentions.find(ment => ment.id === node.id)?.username || node.id
+ 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
if (mxid && useHTML) {
return `@${username}`
} else {
@@ -229,6 +232,13 @@ 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:
@@ -363,7 +373,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?):([^:>]{2,64}):([0-9]+)>/g)]
+ const emojiMatches = [...content.matchAll(/<(a?):([^:>]{1,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 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/d2m/discord-packets.js b/d2m/discord-packets.js
index 0981827..ed47fae 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
- eventDispatcher.onError(client, e, message)
+ await eventDispatcher.onError(client, e, message)
}
}
}
diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js
index 7f27b77..57cb72c 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
*/
- onError(client, e, gatewayMessage) {
+ async 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)}
`)
- api.sendEvent(roomID, "m.room.message", {
+ await 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 e481f95..7484d76 100644
--- a/db/orm-defs.d.ts
+++ b/db/orm-defs.d.ts
@@ -114,3 +114,4 @@ 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 09e4bc7..601a7a0 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 ee33bfd..b2d1ac0 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) {
+ if (room.name && !room.name.match(/^\[[⛓️🔊]\]/) && room.room_type !== "m.space") {
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 0590be8..689ea1a 100644
--- a/discord/interactions/invite.js
+++ b/discord/interactions/invite.js
@@ -7,31 +7,34 @@ const {discord, sync, db, select, from} = require("../../passthrough")
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
-/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction */
-async function interact({id, token, data, channel, member, guild_id}) {
+/**
+ * @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction
+ * @returns {Promise}
+ */
+async function _interact({data, channel, 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 discord.snow.interaction.createInteractionResponse(id, token, {
+ if (!spaceID || !roomID) return {
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 discord.snow.interaction.createInteractionResponse(id, token, {
+ if (!mxid) return {
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
@@ -39,24 +42,24 @@ async function interact({id, token, data, channel, member, guild_id}) {
spaceMember = await api.getStateEvent(spaceID, "m.room.member", mxid)
} catch (e) {}
if (spaceMember && spaceMember.membership === "invite") {
- return discord.snow.interaction.createInteractionResponse(id, token, {
+ return {
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 discord.snow.interaction.createInteractionResponse(id, token, {
+ return {
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?
@@ -65,7 +68,7 @@ async function interact({id, token, data, channel, member, guild_id}) {
roomMember = await api.getStateEvent(roomID, "m.room.member", mxid)
} catch (e) {}
if (!roomMember || (roomMember.membership !== "join" && roomMember.membership !== "invite")) {
- return discord.snow.interaction.createInteractionResponse(id, token, {
+ return {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: {
content: `\`${mxid}\` is already in this server. Would you like to additionally invite them to this specific channel?`,
@@ -80,34 +83,49 @@ async function interact({id, token, data, channel, member, guild_id}) {
}]
}]
}
- })
+ }
}
// The Matrix user *is* in the space and in the channel.
- return discord.snow.interaction.createInteractionResponse(id, token, {
+ return {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: {
content: `\`${mxid}\` is already in this server and this channel.`,
flags: DiscordTypes.MessageFlags.Ephemeral
}
- })
+ }
}
-/** @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction */
-async function interactButton({id, token, data, channel, member, guild_id, message}) {
+/**
+ * @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction
+ * @returns {Promise}
+ */
+async function _interactButton({channel, 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 discord.snow.interaction.createInteractionResponse(id, token, {
+ return {
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 9f0e9e1..fac3804 100644
--- a/discord/interactions/matrix-info.js
+++ b/discord/interactions/matrix-info.js
@@ -7,7 +7,8 @@ const {discord, sync, db, select, from} = require("../../passthrough")
const api = sync.require("../../matrix/api")
/** @param {DiscordTypes.APIContextMenuGuildInteraction} interaction */
-async function interact({id, token, data}) {
+/** @param {DiscordTypes.APIMessageApplicationCommandGuildInteraction} interaction */
+async function interact({id, token, guild_id, channel, 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()
@@ -21,12 +22,15 @@ async function interact({id, token, 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: `This message was bridged to [${message.nick || message.name}]() on Matrix.`
- + `\n-# Room ID: \`${message.room_id}\`\n-# Event ID: \`${message.event_id}\``,
+ content: `Bridged <@${userID}> https://discord.com/channels/${guild_id}/${channel.id}/${data.target_id} on Discord to [${message.nick || message.name}]() on Matrix.`
+ + idInfo,
flags: DiscordTypes.MessageFlags.Ephemeral
}
})
@@ -37,9 +41,8 @@ async function interact({id, token, data}) {
return discord.snow.interaction.createInteractionResponse(id, token, {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: {
- 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}\``,
+ 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,
flags: DiscordTypes.MessageFlags.Ephemeral
}
})
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/discord/utils.js b/discord/utils.js
index 57e563f..865b2e3 100644
--- a/discord/utils.js
+++ b/discord/utils.js
@@ -97,8 +97,7 @@ function hasAllPermissions(resolvedPermissions, permissionsToCheckFor) {
* @param {DiscordTypes.APIMessage} message
*/
function isWebhookMessage(message) {
- const isInteractionResponse = message.type === 20
- return message.webhook_id && !isInteractionResponse
+ return message.webhook_id && message.type !== DiscordTypes.MessageType.ChatInputCommand
}
/**
diff --git a/m2d/actions/redact.js b/m2d/actions/redact.js
index 7569df4..ffbb261 100644
--- a/m2d/actions/redact.js
+++ b/m2d/actions/redact.js
@@ -25,7 +25,6 @@ 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 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/matrix/power.js b/matrix/power.js
index 5dac550..cd2b8cb 100644
--- a/matrix/power.js
+++ b/matrix/power.js
@@ -1,7 +1,6 @@
// @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")
@@ -11,13 +10,16 @@ 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.
-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()
+/** 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()
+}
-;(async () => {
+async function applyPower() {
+ const rows = _getAffectedRooms()
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}}})
@@ -26,4 +28,7 @@ const rows = from("member_cache").join("member_power", "mxid")
// 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 63f5c57..1ece1dd 100644
--- a/start.js
+++ b/start.js
@@ -25,17 +25,14 @@ 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")
- require("./matrix/power.js")
+ await power.applyPower()
require("./stdin")
})()
diff --git a/test/data.js b/test/data.js
index 771c183..456033a 100644
--- a/test/data.js
+++ b/test/data.js
@@ -38,8 +38,7 @@ module.exports = {
}]
},
"m.room.avatar/": {
- discord_path: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024",
- url: "mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF"
+ url: {$url: "/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024"}
},
"m.room.power_levels/": {
events: {
diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql
index 1fb9e24..4666b4d 100644
--- a/test/ooye-test-data.sql
+++ b/test/ooye-test-data.sql
@@ -23,7 +23,8 @@ 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');
+('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '_pk_zoego', '_ooye__pk_zoego', '@_ooye__pk_zoego:cadence.moe'),
+('320067006521147393', 'papiophidian', '_ooye_papiophidian', '@_ooye_papiophidian:cadence.moe');
INSERT INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES
('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '196188877885538304', 'Azalea &flwr; 🌺');
@@ -125,19 +126,21 @@ 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) 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_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_power (mxid, room_id, power_level) VALUES
('@test_auto_invite:example.org', '*', 100);
diff --git a/test/test.js b/test/test.js
index b5977f1..796ff68 100644
--- a/test/test.js
+++ b/test/test.js
@@ -23,6 +23,7 @@ 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})
@@ -116,6 +117,7 @@ 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")