Highly experimental message interactions
This commit is contained in:
parent
b8793dae0f
commit
be405d3eed
9 changed files with 491 additions and 4 deletions
|
@ -1,10 +1,10 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
const config = require("./config")
|
const config = require("./config")
|
||||||
|
const token = config.discordToken
|
||||||
|
const id = Buffer.from(token.split(".")[0], "base64").toString()
|
||||||
|
|
||||||
function addbot() {
|
function addbot() {
|
||||||
const token = config.discordToken
|
|
||||||
const id = Buffer.from(token.split(".")[0], "base64")
|
|
||||||
return `Open this link to add the bot to a Discord server:\nhttps://discord.com/oauth2/authorize?client_id=${id}&scope=bot&permissions=1610883072 `
|
return `Open this link to add the bot to a Discord server:\nhttps://discord.com/oauth2/authorize?client_id=${id}&scope=bot&permissions=1610883072 `
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,4 +12,5 @@ if (process.argv.find(a => a.endsWith("addbot") || a.endsWith("addbot.js"))) {
|
||||||
console.log(addbot())
|
console.log(addbot())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.id = id
|
||||||
module.exports.addbot = addbot
|
module.exports.addbot = addbot
|
||||||
|
|
|
@ -16,6 +16,8 @@ const utils = {
|
||||||
// requiring this later so that the client is already constructed by the time event-dispatcher is loaded
|
// requiring this later so that the client is already constructed by the time event-dispatcher is loaded
|
||||||
/** @type {typeof import("./event-dispatcher")} */
|
/** @type {typeof import("./event-dispatcher")} */
|
||||||
const eventDispatcher = sync.require("./event-dispatcher")
|
const eventDispatcher = sync.require("./event-dispatcher")
|
||||||
|
/** @type {import("../discord/register-interactions")} */
|
||||||
|
const interactions = sync.require("../discord/register-interactions")
|
||||||
|
|
||||||
// Client internals, keep track of the state we need
|
// Client internals, keep track of the state we need
|
||||||
if (message.t === "READY") {
|
if (message.t === "READY") {
|
||||||
|
@ -172,7 +174,11 @@ const utils = {
|
||||||
|
|
||||||
} else if (message.t === "MESSAGE_REACTION_REMOVE" || message.t === "MESSAGE_REACTION_REMOVE_EMOJI" || message.t === "MESSAGE_REACTION_REMOVE_ALL") {
|
} else if (message.t === "MESSAGE_REACTION_REMOVE" || message.t === "MESSAGE_REACTION_REMOVE_EMOJI" || message.t === "MESSAGE_REACTION_REMOVE_ALL") {
|
||||||
await eventDispatcher.onSomeReactionsRemoved(client, message.d)
|
await eventDispatcher.onSomeReactionsRemoved(client, message.d)
|
||||||
|
|
||||||
|
} else if (message.t === "INTERACTION_CREATE") {
|
||||||
|
await interactions.dispatchInteraction(message.d)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Let OOYE try to handle errors too
|
// Let OOYE try to handle errors too
|
||||||
eventDispatcher.onError(client, e, message)
|
eventDispatcher.onError(client, e, message)
|
||||||
|
|
121
discord/interactions/bridge.js
Normal file
121
discord/interactions/bridge.js
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
const Ty = require("../../types")
|
||||||
|
const {discord, sync, db, select, from, as} = require("../../passthrough")
|
||||||
|
const assert = require("assert/strict")
|
||||||
|
|
||||||
|
/** @type {import("../../matrix/api")} */
|
||||||
|
const api = sync.require("../../matrix/api")
|
||||||
|
|
||||||
|
/** @type {Map<string, Promise<{name: string, value: string}[]>>} spaceID -> list of rooms */
|
||||||
|
const cache = new Map()
|
||||||
|
/** @type {Map<string, string>} roomID -> spaceID */
|
||||||
|
const reverseCache = new Map()
|
||||||
|
|
||||||
|
// Manage clearing the cache
|
||||||
|
sync.addTemporaryListener(as, "type:m.room.name", /** @param {Ty.Event.StateOuter<Ty.Event.M_Room_Name>} event */ async event => {
|
||||||
|
if (event.state_key !== "") return
|
||||||
|
const roomID = event.room_id
|
||||||
|
const spaceID = reverseCache.get(roomID)
|
||||||
|
if (!spaceID) return
|
||||||
|
const childRooms = await cache.get(spaceID)
|
||||||
|
if (!childRooms) return
|
||||||
|
if (event.content.name) {
|
||||||
|
const found = childRooms.find(r => r.value === roomID)
|
||||||
|
if (!found) return
|
||||||
|
found.name = event.content.name
|
||||||
|
} else {
|
||||||
|
cache.set(spaceID, Promise.resolve(childRooms.filter(r => r.value !== roomID)))
|
||||||
|
reverseCache.delete(roomID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Manage adding to the cache
|
||||||
|
async function getHierarchy(spaceID) {
|
||||||
|
return cache.get(spaceID) || (() => {
|
||||||
|
const entry = (async () => {
|
||||||
|
/** @type {{name: string, value: string}[]} */
|
||||||
|
let childRooms = []
|
||||||
|
/** @type {string | undefined} */
|
||||||
|
let nextBatch = undefined
|
||||||
|
do {
|
||||||
|
/** @type {Ty.HierarchyPagination<Ty.R.Hierarchy>} */
|
||||||
|
const res = await api.getHierarchy(spaceID, {from: nextBatch})
|
||||||
|
for (const room of res.rooms) {
|
||||||
|
if (room.name) {
|
||||||
|
childRooms.push({name: room.name, value: room.room_id})
|
||||||
|
reverseCache.set(room.room_id, spaceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextBatch = res.next_batch
|
||||||
|
} while (nextBatch)
|
||||||
|
return childRooms
|
||||||
|
})()
|
||||||
|
cache.set(spaceID, entry)
|
||||||
|
return entry
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {DiscordTypes.APIApplicationCommandAutocompleteGuildInteraction} interaction */
|
||||||
|
async function interactAutocomplete({id, token, data, guild_id}) {
|
||||||
|
const spaceID = select("guild_space", "space_id", {guild_id}).pluck().get()
|
||||||
|
if (!spaceID) {
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ApplicationCommandAutocompleteResult,
|
||||||
|
data: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
name: `Error: This server needs to be bridged somewhere first...`,
|
||||||
|
value: "baby"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let rooms = await getHierarchy(spaceID)
|
||||||
|
// @ts-ignore
|
||||||
|
rooms = rooms.filter(r => r.name.startsWith(data.options[0].value))
|
||||||
|
|
||||||
|
await discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ApplicationCommandAutocompleteResult,
|
||||||
|
data: {
|
||||||
|
choices: rooms
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction} interaction */
|
||||||
|
async function interactSubmit({id, token, data, guild_id}) {
|
||||||
|
const spaceID = select("guild_space", "space_id", {guild_id}).pluck().get()
|
||||||
|
if (!spaceID) {
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||||
|
data: {
|
||||||
|
content: "Error: This server needs to be bridged somewhere first...",
|
||||||
|
flags: DiscordTypes.MessageFlags.Ephemeral
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||||
|
data: {
|
||||||
|
content: "Valid input. This would do something but it isn't implemented yet.",
|
||||||
|
flags: DiscordTypes.MessageFlags.Ephemeral
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {DiscordTypes.APIGuildInteraction} interaction */
|
||||||
|
async function interact(interaction) {
|
||||||
|
if (interaction.type === DiscordTypes.InteractionType.ApplicationCommandAutocomplete) {
|
||||||
|
return interactAutocomplete(interaction)
|
||||||
|
} else if (interaction.type === DiscordTypes.InteractionType.ApplicationCommand) {
|
||||||
|
// @ts-ignore
|
||||||
|
return interactSubmit(interaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.interact = interact
|
113
discord/interactions/invite.js
Normal file
113
discord/interactions/invite.js
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
const assert = require("assert/strict")
|
||||||
|
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}) {
|
||||||
|
// 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, {
|
||||||
|
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, {
|
||||||
|
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
|
||||||
|
try {
|
||||||
|
spaceMember = await api.getStateEvent(spaceID, "m.room.member", mxid)
|
||||||
|
} catch (e) {}
|
||||||
|
if (spaceMember && spaceMember.membership === "invite") {
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
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, {
|
||||||
|
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?
|
||||||
|
let roomMember
|
||||||
|
try {
|
||||||
|
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, {
|
||||||
|
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,
|
||||||
|
components: [{
|
||||||
|
type: DiscordTypes.ComponentType.ActionRow,
|
||||||
|
components: [{
|
||||||
|
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 discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
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}) {
|
||||||
|
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, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.UpdateMessage,
|
||||||
|
data: {
|
||||||
|
content: `You invited \`${mxid}\` to the channel.`,
|
||||||
|
flags: DiscordTypes.MessageFlags.Ephemeral,
|
||||||
|
components: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.interact = interact
|
||||||
|
module.exports.interactButton = interactButton
|
48
discord/interactions/matrix-info.js
Normal file
48
discord/interactions/matrix-info.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
const {discord, sync, db, select, from} = require("../../passthrough")
|
||||||
|
|
||||||
|
/** @type {import("../../matrix/api")} */
|
||||||
|
const api = sync.require("../../matrix/api")
|
||||||
|
|
||||||
|
/** @param {DiscordTypes.APIContextMenuGuildInteraction} interaction */
|
||||||
|
async function interact({id, token, 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()
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||||
|
data: {
|
||||||
|
content: "This message hasn't been bridged to Matrix.",
|
||||||
|
flags: DiscordTypes.MessageFlags.Ephemeral
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.source === 1) { // from Discord
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||||
|
data: {
|
||||||
|
content: `This message was bridged to [${message.nick || message.name}](<https://matrix.to/#/${message.room_id}/${message.event_id}>) on Matrix.`
|
||||||
|
+ `\n-# Room ID: \`${message.room_id}\`\n-# Event ID: \`${message.event_id}\``,
|
||||||
|
flags: DiscordTypes.MessageFlags.Ephemeral
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// from Matrix
|
||||||
|
const event = await api.getEvent(message.room_id, message.event_id)
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||||
|
data: {
|
||||||
|
content: `This message was bridged from [${message.nick || message.name}](<https://matrix.to/#/${message.room_id}/${message.event_id}>) on Matrix.`
|
||||||
|
+ `\nIt was originally sent by [${event.sender}](<https://matrix.to/#/${event.sender}>).`
|
||||||
|
+ `\n-# Room ID: \`${message.room_id}\`\n-# Event ID: \`${message.event_id}\``,
|
||||||
|
flags: DiscordTypes.MessageFlags.Ephemeral
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.interact = interact
|
108
discord/interactions/permissions.js
Normal file
108
discord/interactions/permissions.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
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}) {
|
||||||
|
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, {
|
||||||
|
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
|
||||||
|
const eventID = row.event_id
|
||||||
|
const roomID = select("channel_room", "room_id", {channel_id: channel.id}).pluck().get()
|
||||||
|
assert(roomID)
|
||||||
|
const event = await api.getEvent(roomID, eventID)
|
||||||
|
const sender = event.sender
|
||||||
|
|
||||||
|
// Get the space, where the power levels will be inspected/edited
|
||||||
|
const spaceID = select("guild_space", "space_id", {guild_id}).pluck().get()
|
||||||
|
assert(spaceID)
|
||||||
|
|
||||||
|
// Get the power level
|
||||||
|
/** @type {Ty.Event.M_Power_Levels} */
|
||||||
|
const powerLevelsContent = await api.getStateEvent(spaceID, "m.room.power_levels", "")
|
||||||
|
const userPower = powerLevelsContent.users?.[event.sender] || 0
|
||||||
|
|
||||||
|
// Administrators equal to the bot cannot be demoted
|
||||||
|
if (userPower >= 100) {
|
||||||
|
return discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
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, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||||
|
data: {
|
||||||
|
content: `Showing permissions for \`${sender}\`. Click to edit.`,
|
||||||
|
flags: DiscordTypes.MessageFlags.Ephemeral,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: DiscordTypes.ComponentType.ActionRow,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: DiscordTypes.ComponentType.StringSelect,
|
||||||
|
custom_id: "permissions_edit",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Default",
|
||||||
|
value: "default",
|
||||||
|
default: userPower < 50
|
||||||
|
}, {
|
||||||
|
label: "Moderator",
|
||||||
|
value: "moderator",
|
||||||
|
default: userPower >= 50 && userPower < 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {DiscordTypes.APIMessageComponentSelectMenuInteraction} interaction */
|
||||||
|
async function interactEdit({data, channel, 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)
|
||||||
|
|
||||||
|
// 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...)
|
||||||
|
|
||||||
|
// ACK
|
||||||
|
await discord.snow.interaction.createInteractionResponse(id, token, {
|
||||||
|
type: DiscordTypes.InteractionResponseType.DeferredMessageUpdate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.interact = interact
|
||||||
|
module.exports.interactEdit = interactEdit
|
90
discord/register-interactions.js
Normal file
90
discord/register-interactions.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
const {discord, sync, db, select} = require("../passthrough")
|
||||||
|
const {id} = require("../addbot")
|
||||||
|
|
||||||
|
const matrixInfo = sync.require("./interactions/matrix-info.js")
|
||||||
|
const invite = sync.require("./interactions/invite.js")
|
||||||
|
const permissions = sync.require("./interactions/permissions.js")
|
||||||
|
const bridge = sync.require("./interactions/bridge.js")
|
||||||
|
|
||||||
|
discord.snow.interaction.bulkOverwriteApplicationCommands(id, [{
|
||||||
|
name: "Matrix info",
|
||||||
|
contexts: [DiscordTypes.InteractionContextType.Guild],
|
||||||
|
type: DiscordTypes.ApplicationCommandType.Message,
|
||||||
|
}, {
|
||||||
|
name: "Permissions",
|
||||||
|
contexts: [DiscordTypes.InteractionContextType.Guild],
|
||||||
|
type: DiscordTypes.ApplicationCommandType.Message,
|
||||||
|
default_member_permissions: String(DiscordTypes.PermissionFlagsBits.KickMembers)
|
||||||
|
}, {
|
||||||
|
name: "invite",
|
||||||
|
contexts: [DiscordTypes.InteractionContextType.Guild],
|
||||||
|
type: DiscordTypes.ApplicationCommandType.ChatInput,
|
||||||
|
description: "Invite a Matrix user to this Discord server",
|
||||||
|
default_member_permissions: String(DiscordTypes.PermissionFlagsBits.CreateInstantInvite),
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||||
|
description: "The Matrix user to invite, e.g. @username:example.org",
|
||||||
|
name: "user"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
name: "bridge",
|
||||||
|
contexts: [DiscordTypes.InteractionContextType.Guild],
|
||||||
|
type: DiscordTypes.ApplicationCommandType.ChatInput,
|
||||||
|
description: "Start bridging this channel to a Matrix room.",
|
||||||
|
default_member_permissions: String(DiscordTypes.PermissionFlagsBits.ManageChannels),
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||||
|
description: "Destination room to bridge to.",
|
||||||
|
name: "room",
|
||||||
|
autocomplete: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}])
|
||||||
|
|
||||||
|
async function dispatchInteraction(interaction) {
|
||||||
|
const id = interaction.data.custom_id || interaction.data.name
|
||||||
|
try {
|
||||||
|
console.log(interaction)
|
||||||
|
if (id === "Matrix info") {
|
||||||
|
await matrixInfo.interact(interaction)
|
||||||
|
} else if (id === "invite") {
|
||||||
|
await invite.interact(interaction)
|
||||||
|
} else if (id === "invite_channel") {
|
||||||
|
await invite.interactButton(interaction)
|
||||||
|
} else if (id === "Permissions") {
|
||||||
|
await permissions.interact(interaction)
|
||||||
|
} else if (id === "permissions_edit") {
|
||||||
|
await permissions.interactEdit(interaction)
|
||||||
|
} else if (id === "bridge") {
|
||||||
|
await bridge.interact(interaction)
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown interaction ${id}`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
let stackLines = null
|
||||||
|
if (e.stack) {
|
||||||
|
stackLines = e.stack.split("\n")
|
||||||
|
let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/"))
|
||||||
|
if (cloudstormLine !== -1) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.dispatchInteraction = dispatchInteraction
|
|
@ -102,11 +102,10 @@ function isWebhookMessage(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ephemeral messages can be generated if a slash command is attached to the same bot that OOYE is running on
|
|
||||||
* @param {Pick<DiscordTypes.APIMessage, "flags">} message
|
* @param {Pick<DiscordTypes.APIMessage, "flags">} message
|
||||||
*/
|
*/
|
||||||
function isEphemeralMessage(message) {
|
function isEphemeralMessage(message) {
|
||||||
return message.flags && (message.flags & (1 << 6));
|
return message.flags && (message.flags & DiscordTypes.MessageFlags.Ephemeral)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {string} snowflake */
|
/** @param {string} snowflake */
|
||||||
|
|
1
start.js
1
start.js
|
@ -8,6 +8,7 @@ const config = require("./config")
|
||||||
const passthrough = require("./passthrough")
|
const passthrough = require("./passthrough")
|
||||||
const db = new sqlite("db/ooye.db")
|
const db = new sqlite("db/ooye.db")
|
||||||
|
|
||||||
|
/** @type {import("heatsync").default} */ // @ts-ignore
|
||||||
const sync = new HeatSync()
|
const sync = new HeatSync()
|
||||||
|
|
||||||
Object.assign(passthrough, {config, sync, db})
|
Object.assign(passthrough, {config, sync, db})
|
||||||
|
|
Loading…
Reference in a new issue