Make invite interaction async

Fix potential lag issues
This commit is contained in:
Cadence Ember 2024-10-14 13:09:30 +13:00
parent 6f7ed829b8
commit da5525a542
2 changed files with 79 additions and 66 deletions

View file

@ -1,8 +1,9 @@
// @ts-check // @ts-check
const DiscordTypes = require("discord-api-types/v10") const DiscordTypes = require("discord-api-types/v10")
const Ty = require("../../types")
const assert = require("assert/strict") const assert = require("assert/strict")
const {InteractionMethods} = require("snowtransfer")
const {id: botID} = require("../../../addbot")
const {discord, sync, db, select} = require("../../passthrough") const {discord, sync, db, select} = require("../../passthrough")
/** @type {import("../../d2m/actions/create-room")} */ /** @type {import("../../d2m/actions/create-room")} */
@ -15,21 +16,21 @@ const api = sync.require("../../matrix/api")
/** /**
* @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction * @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction
* @param {{api: typeof api}} di * @param {{api: typeof api}} di
* @returns {Promise<DiscordTypes.APIInteractionResponse>} * @returns {AsyncGenerator<{[k in keyof InteractionMethods]?: Parameters<InteractionMethods[k]>[2]}>}
*/ */
async function _interact({data, channel, guild_id}, {api}) { async function* _interact({data, channel, guild_id}, {api}) {
// Get named MXID // Get named MXID
/** @type {DiscordTypes.APIApplicationCommandInteractionDataStringOption[] | undefined} */ // @ts-ignore /** @type {DiscordTypes.APIApplicationCommandInteractionDataStringOption[] | undefined} */ // @ts-ignore
const options = data.options const options = data.options
const input = options?.[0]?.value || "" const input = options?.[0]?.value || ""
const mxid = input.match(/@([^:]+):([a-z0-9:-]+\.[a-z0-9.:-]+)/)?.[0] const mxid = input.match(/@([^:]+):([a-z0-9:-]+\.[a-z0-9.:-]+)/)?.[0]
if (!mxid) return { if (!mxid) return yield {createInteractionResponse: {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: { data: {
content: "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`", 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 flags: DiscordTypes.MessageFlags.Ephemeral
} }
} }}
const guild = discord.guilds.get(guild_id) const guild = discord.guilds.get(guild_id)
assert(guild) assert(guild)
@ -37,15 +38,22 @@ async function _interact({data, channel, guild_id}, {api}) {
// Ensure guild and room are bridged // Ensure guild and room are bridged
db.prepare("INSERT OR IGNORE INTO guild_active (guild_id, autocreate) VALUES (?, 1)").run(guild_id) db.prepare("INSERT OR IGNORE INTO guild_active (guild_id, autocreate) VALUES (?, 1)").run(guild_id)
const existing = createRoom.existsOrAutocreatable(channel, guild_id) const existing = createRoom.existsOrAutocreatable(channel, guild_id)
if (existing === 0) return { if (existing === 0) return yield {createInteractionResponse: {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: { data: {
content: "This channel isn't bridged, so you can't invite Matrix users yet. Try turning on automatic room-creation or link a Matrix room in the website.", content: "This channel isn't bridged, so you can't invite Matrix users yet. Try turning on automatic room-creation or link a Matrix room in the website.",
flags: DiscordTypes.MessageFlags.Ephemeral flags: DiscordTypes.MessageFlags.Ephemeral
} }
} }}
assert(existing) // can't be null or undefined as we just inserted the guild_active row assert(existing) // can't be null or undefined as we just inserted the guild_active row
yield {createInteractionResponse: {
type: DiscordTypes.InteractionResponseType.DeferredChannelMessageWithSource,
data: {
flags: DiscordTypes.MessageFlags.Ephemeral
}
}}
const spaceID = await createSpace.ensureSpace(guild) const spaceID = await createSpace.ensureSpace(guild)
const roomID = await createRoom.ensureRoom(channel.id) const roomID = await createRoom.ensureRoom(channel.id)
@ -55,24 +63,17 @@ async function _interact({data, channel, guild_id}, {api}) {
spaceMember = await api.getStateEvent(spaceID, "m.room.member", mxid) spaceMember = await api.getStateEvent(spaceID, "m.room.member", mxid)
} catch (e) {} } catch (e) {}
if (spaceMember && spaceMember.membership === "invite") { if (spaceMember && spaceMember.membership === "invite") {
return { return yield {editOriginalInteractionResponse: {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, content: `\`${mxid}\` already has an invite, which they haven't accepted yet.`,
data: { }}
content: `\`${mxid}\` already has an invite, which they haven't accepted yet.`,
flags: DiscordTypes.MessageFlags.Ephemeral
}
}
} }
// Invite Matrix user if not in space // Invite Matrix user if not in space
if (!spaceMember || spaceMember.membership !== "join") { if (!spaceMember || spaceMember.membership !== "join") {
await api.inviteToRoom(spaceID, mxid) await api.inviteToRoom(spaceID, mxid)
return { return yield {editOriginalInteractionResponse: {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, content: `You invited \`${mxid}\` to the server.`
data: { }}
content: `You invited \`${mxid}\` to the server.`
}
}
} }
// The Matrix user *is* in the space, maybe we want to invite them to this channel? // The Matrix user *is* in the space, maybe we want to invite them to this channel?
@ -81,32 +82,24 @@ async function _interact({data, channel, guild_id}, {api}) {
roomMember = await api.getStateEvent(roomID, "m.room.member", mxid) roomMember = await api.getStateEvent(roomID, "m.room.member", mxid)
} catch (e) {} } catch (e) {}
if (!roomMember || (roomMember.membership !== "join" && roomMember.membership !== "invite")) { if (!roomMember || (roomMember.membership !== "join" && roomMember.membership !== "invite")) {
return { return yield {editOriginalInteractionResponse: {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, content: `\`${mxid}\` is already in this server. Would you like to additionally invite them to this specific channel?`,
data: { components: [{
content: `\`${mxid}\` is already in this server. Would you like to additionally invite them to this specific channel?`, type: DiscordTypes.ComponentType.ActionRow,
flags: DiscordTypes.MessageFlags.Ephemeral,
components: [{ components: [{
type: DiscordTypes.ComponentType.ActionRow, type: DiscordTypes.ComponentType.Button,
components: [{ custom_id: "invite_channel",
type: DiscordTypes.ComponentType.Button, style: DiscordTypes.ButtonStyle.Primary,
custom_id: "invite_channel", label: "Sure",
style: DiscordTypes.ButtonStyle.Primary,
label: "Sure",
}]
}] }]
} }]
} }}
} }
// The Matrix user *is* in the space and in the channel. // The Matrix user *is* in the space and in the channel.
return { return yield {editOriginalInteractionResponse: {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource, content: `\`${mxid}\` is already in this server and this channel.`,
data: { }}
content: `\`${mxid}\` is already in this server and this channel.`,
flags: DiscordTypes.MessageFlags.Ephemeral
}
}
} }
/** /**
@ -133,7 +126,14 @@ async function _interactButton({channel, message}, {api}) {
/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction */ /** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction */
async function interact(interaction) { async function interact(interaction) {
await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interact(interaction, {api})) for await (const response of _interact(interaction, {api})) {
if (response.createInteractionResponse) {
// TODO: Test if it is reasonable to remove `await` from these calls. Or zip these calls with the next interaction iteration and use Promise.all.
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)
}
}
} }
/** @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction */ /** @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction */

View file

@ -4,19 +4,32 @@ const {db, discord} = require("../../passthrough")
const {MatrixServerError} = require("../../matrix/mreq") const {MatrixServerError} = require("../../matrix/mreq")
const {_interact, _interactButton} = require("./invite") const {_interact, _interactButton} = require("./invite")
/**
* @template T
* @param {AsyncIterable<T>} ai
* @returns {Promise<T[]>}
*/
async function fromAsync(ai) {
const result = []
for await (const value of ai) {
result.push(value)
}
return result
}
test("invite: checks for missing matrix ID", async t => { test("invite: checks for missing matrix ID", async t => {
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [] options: []
}, },
channel: discord.channels.get("0"), channel: discord.channels.get("0"),
guild_id: "112760669178241024" guild_id: "112760669178241024"
}, {}) }, {}))
t.equal(msg.data.content, "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`") t.equal(msgs[0].createInteractionResponse.data.content, "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`")
}) })
test("invite: checks for invalid matrix ID", async t => { test("invite: checks for invalid matrix ID", async t => {
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [{ options: [{
name: "user", name: "user",
@ -26,13 +39,13 @@ test("invite: checks for invalid matrix ID", async t => {
}, },
channel: discord.channels.get("0"), channel: discord.channels.get("0"),
guild_id: "112760669178241024" guild_id: "112760669178241024"
}, {}) }, {}))
t.equal(msg.data.content, "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`") t.equal(msgs[0].createInteractionResponse.data.content, "You have to say the Matrix ID of the person you want to invite. Matrix IDs look like this: `@username:example.org`")
}) })
test("invite: checks if channel exists or is autocreatable", async t => { test("invite: checks if channel exists or is autocreatable", async t => {
db.prepare("UPDATE guild_active SET autocreate = 0").run() db.prepare("UPDATE guild_active SET autocreate = 0").run()
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [{ options: [{
name: "user", name: "user",
@ -42,14 +55,14 @@ test("invite: checks if channel exists or is autocreatable", async t => {
}, },
channel: discord.channels.get("498323546729086986"), channel: discord.channels.get("498323546729086986"),
guild_id: "112760669178241024" guild_id: "112760669178241024"
}, {}) }, {}))
t.equal(msg.data.content, "This channel isn't bridged, so you can't invite Matrix users yet. Try turning on automatic room-creation or link a Matrix room in the website.") t.equal(msgs[0].createInteractionResponse.data.content, "This channel isn't bridged, so you can't invite Matrix users yet. Try turning on automatic room-creation or link a Matrix room in the website.")
db.prepare("UPDATE guild_active SET autocreate = 1").run() db.prepare("UPDATE guild_active SET autocreate = 1").run()
}) })
test("invite: checks if user is already invited to space", async t => { test("invite: checks if user is already invited to space", async t => {
let called = 0 let called = 0
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [{ options: [{
name: "user", name: "user",
@ -72,14 +85,14 @@ test("invite: checks if user is already invited to space", async t => {
} }
} }
} }
}) }))
t.equal(msg.data.content, "`@cadence:cadence.moe` already has an invite, which they haven't accepted yet.") t.equal(msgs[1].editOriginalInteractionResponse.content, "`@cadence:cadence.moe` already has an invite, which they haven't accepted yet.")
t.equal(called, 1) t.equal(called, 1)
}) })
test("invite: invites if user is not in space", async t => { test("invite: invites if user is not in space", async t => {
let called = 0 let called = 0
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [{ options: [{
name: "user", name: "user",
@ -104,14 +117,14 @@ test("invite: invites if user is not in space", async t => {
t.equal(mxid, "@cadence:cadence.moe") t.equal(mxid, "@cadence:cadence.moe")
} }
} }
}) }))
t.equal(msg.data.content, "You invited `@cadence:cadence.moe` to the server.") t.equal(msgs[1].editOriginalInteractionResponse.content, "You invited `@cadence:cadence.moe` to the server.")
t.equal(called, 2) t.equal(called, 2)
}) })
test("invite: prompts to invite to room (if never joined)", async t => { test("invite: prompts to invite to room (if never joined)", async t => {
let called = 0 let called = 0
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [{ options: [{
name: "user", name: "user",
@ -137,14 +150,14 @@ test("invite: prompts to invite to room (if never joined)", async t => {
} }
} }
} }
}) }))
t.equal(msg.data.content, "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?") t.equal(msgs[1].editOriginalInteractionResponse.content, "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?")
t.equal(called, 2) t.equal(called, 2)
}) })
test("invite: prompts to invite to room (if left)", async t => { test("invite: prompts to invite to room (if left)", async t => {
let called = 0 let called = 0
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [{ options: [{
name: "user", name: "user",
@ -173,8 +186,8 @@ test("invite: prompts to invite to room (if left)", async t => {
} }
} }
} }
}) }))
t.equal(msg.data.content, "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?") t.equal(msgs[1].editOriginalInteractionResponse.content, "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?")
t.equal(called, 2) t.equal(called, 2)
}) })
@ -200,7 +213,7 @@ test("invite button: invites to room when button clicked", async t => {
test("invite: no-op if in room and space", async t => { test("invite: no-op if in room and space", async t => {
let called = 0 let called = 0
const msg = await _interact({ const msgs = await fromAsync(_interact({
data: { data: {
options: [{ options: [{
name: "user", name: "user",
@ -222,7 +235,7 @@ test("invite: no-op if in room and space", async t => {
} }
} }
} }
}) }))
t.equal(msg.data.content, "`@cadence:cadence.moe` is already in this server and this channel.") t.equal(msgs[1].editOriginalInteractionResponse.content, "`@cadence:cadence.moe` is already in this server and this channel.")
t.equal(called, 2) t.equal(called, 2)
}) })