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