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
const DiscordTypes = require("discord-api-types/v10")
const Ty = require("../../types")
const assert = require("assert/strict")
const {InteractionMethods} = require("snowtransfer")
const {id: botID} = require("../../../addbot")
const {discord, sync, db, select} = require("../../passthrough")
/** @type {import("../../d2m/actions/create-room")} */
@ -15,21 +16,21 @@ const api = sync.require("../../matrix/api")
/**
* @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction
* @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
/** @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 yield {createInteractionResponse: {
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
}
}
}}
const guild = discord.guilds.get(guild_id)
assert(guild)
@ -37,15 +38,22 @@ async function _interact({data, channel, guild_id}, {api}) {
// Ensure guild and room are bridged
db.prepare("INSERT OR IGNORE INTO guild_active (guild_id, autocreate) VALUES (?, 1)").run(guild_id)
const existing = createRoom.existsOrAutocreatable(channel, guild_id)
if (existing === 0) return {
if (existing === 0) return yield {createInteractionResponse: {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
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.",
flags: DiscordTypes.MessageFlags.Ephemeral
}
}
}}
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 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)
} catch (e) {}
if (spaceMember && spaceMember.membership === "invite") {
return {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: {
content: `\`${mxid}\` already has an invite, which they haven't accepted yet.`,
flags: DiscordTypes.MessageFlags.Ephemeral
}
}
return yield {editOriginalInteractionResponse: {
content: `\`${mxid}\` already has an invite, which they haven't accepted yet.`,
}}
}
// Invite Matrix user if not in space
if (!spaceMember || spaceMember.membership !== "join") {
await api.inviteToRoom(spaceID, mxid)
return {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: {
content: `You invited \`${mxid}\` to the server.`
}
}
return yield {editOriginalInteractionResponse: {
content: `You invited \`${mxid}\` to the server.`
}}
}
// 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)
} catch (e) {}
if (!roomMember || (roomMember.membership !== "join" && roomMember.membership !== "invite")) {
return {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: {
content: `\`${mxid}\` is already in this server. Would you like to additionally invite them to this specific channel?`,
flags: DiscordTypes.MessageFlags.Ephemeral,
return yield {editOriginalInteractionResponse: {
content: `\`${mxid}\` is already in this server. Would you like to additionally invite them to this specific channel?`,
components: [{
type: DiscordTypes.ComponentType.ActionRow,
components: [{
type: DiscordTypes.ComponentType.ActionRow,
components: [{
type: DiscordTypes.ComponentType.Button,
custom_id: "invite_channel",
style: DiscordTypes.ButtonStyle.Primary,
label: "Sure",
}]
type: DiscordTypes.ComponentType.Button,
custom_id: "invite_channel",
style: DiscordTypes.ButtonStyle.Primary,
label: "Sure",
}]
}
}
}]
}}
}
// The Matrix user *is* in the space and in the channel.
return {
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
data: {
content: `\`${mxid}\` is already in this server and this channel.`,
flags: DiscordTypes.MessageFlags.Ephemeral
}
}
return yield {editOriginalInteractionResponse: {
content: `\`${mxid}\` is already in this server and this channel.`,
}}
}
/**
@ -133,7 +126,14 @@ async function _interactButton({channel, message}, {api}) {
/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} 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 */

View file

@ -4,19 +4,32 @@ const {db, discord} = require("../../passthrough")
const {MatrixServerError} = require("../../matrix/mreq")
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 => {
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: []
},
channel: discord.channels.get("0"),
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 => {
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: [{
name: "user",
@ -26,13 +39,13 @@ test("invite: checks for invalid matrix ID", async t => {
},
channel: discord.channels.get("0"),
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 => {
db.prepare("UPDATE guild_active SET autocreate = 0").run()
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: [{
name: "user",
@ -42,14 +55,14 @@ test("invite: checks if channel exists or is autocreatable", async t => {
},
channel: discord.channels.get("498323546729086986"),
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()
})
test("invite: checks if user is already invited to space", async t => {
let called = 0
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: [{
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)
})
test("invite: invites if user is not in space", async t => {
let called = 0
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: [{
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(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)
})
test("invite: prompts to invite to room (if never joined)", async t => {
let called = 0
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: [{
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)
})
test("invite: prompts to invite to room (if left)", async t => {
let called = 0
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: [{
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)
})
@ -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 => {
let called = 0
const msg = await _interact({
const msgs = await fromAsync(_interact({
data: {
options: [{
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)
})