Make invite interaction async
Fix potential lag issues
This commit is contained in:
		| @@ -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 */ | ||||
|   | ||||
| @@ -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) | ||||
| }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Cadence Ember
					Cadence Ember