Compare commits
2 commits
5dbd79cf39
...
bad8c5b8c2
Author | SHA1 | Date | |
---|---|---|---|
bad8c5b8c2 | |||
65170c1282 |
16 changed files with 407 additions and 186 deletions
|
@ -1,25 +0,0 @@
|
|||
id: de8c56117637cb5d9f4ac216f612dc2adb1de4c09ae8d13553f28c33a28147c7
|
||||
hs_token: [a unique 64 character hex string]
|
||||
as_token: [a unique 64 character hex string]
|
||||
url: http://localhost:6693
|
||||
sender_localpart: _ooye_bot
|
||||
protocols:
|
||||
- discord
|
||||
namespaces:
|
||||
users:
|
||||
- exclusive: true
|
||||
regex: '@_ooye_.*'
|
||||
aliases:
|
||||
- exclusive: true
|
||||
regex: '#_ooye_.*'
|
||||
rate_limited: false
|
||||
ooye:
|
||||
namespace_prefix: _ooye_
|
||||
max_file_size: 5000000
|
||||
server_name: [the part after the colon in your matrix id, like cadence.moe]
|
||||
server_origin: [the full protocol and domain of your actual matrix server's location, with no trailing slash, like https://matrix.cadence.moe]
|
||||
content_length_workaround: false
|
||||
include_user_id_in_mxid: false
|
||||
invite:
|
||||
# uncomment this to auto-invite the named user to newly created spaces and mark them as admin (PL 100) everywhere
|
||||
# - '@cadence:cadence.moe'
|
|
@ -7,6 +7,7 @@ const sqlite = require("better-sqlite3")
|
|||
const {scheduler} = require("timers/promises")
|
||||
const {isDeepStrictEqual} = require("util")
|
||||
const {createServer} = require("http")
|
||||
const {join} = require("path")
|
||||
|
||||
const {prompt} = require("enquirer")
|
||||
const Input = require("enquirer/lib/prompts/input")
|
||||
|
@ -208,7 +209,6 @@ async function validateHomeserverOrigin(serverUrlPrompt, url) {
|
|||
url: bridgeOriginResponse.bridge_origin,
|
||||
ooye: {
|
||||
...template.ooye,
|
||||
...serverNameResponse,
|
||||
...bridgeOriginResponse,
|
||||
server_origin: serverOrigin,
|
||||
...discordTokenResponse,
|
||||
|
@ -335,8 +335,8 @@ async function validateHomeserverOrigin(serverUrlPrompt, url) {
|
|||
}
|
||||
// Upload those emojis to the chosen location
|
||||
db.prepare("REPLACE INTO auto_emoji (name, emoji_id, guild_id) VALUES ('_', '_', ?)").run(guild.id)
|
||||
await uploadAutoEmoji(discord.snow, guild, "L1", "docs/img/L1.png")
|
||||
await uploadAutoEmoji(discord.snow, guild, "L2", "docs/img/L2.png")
|
||||
await uploadAutoEmoji(discord.snow, guild, "L1", join(__dirname, "../docs/img/L1.png"))
|
||||
await uploadAutoEmoji(discord.snow, guild, "L2", join(__dirname, "../docs/img/L2.png"))
|
||||
}
|
||||
console.log("✅ Emojis are ready...")
|
||||
|
||||
|
|
|
@ -58,3 +58,13 @@ test("orm: from: join direction works", t => {
|
|||
const hasNoOwnerInner = from("sim").join("sim_proxy", "user_id", "inner").select("user_id", "proxy_owner_id").where({sim_name: "crunch_god"}).get()
|
||||
t.deepEqual(hasNoOwnerInner, undefined)
|
||||
})
|
||||
|
||||
test("orm: select unsafe works (to select complex column names that can't be type verified)", t => {
|
||||
const results = from("member_cache")
|
||||
.join("member_power", "mxid")
|
||||
.join("channel_room", "room_id") // only include rooms that are bridged
|
||||
.and("where member_power.room_id = '*' and member_cache.power_level != member_power.power_level")
|
||||
.selectUnsafe("mxid", "member_cache.room_id", "member_power.power_level")
|
||||
.all()
|
||||
t.equal(results[0].power_level, 100)
|
||||
})
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
// @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 getCachedHierarchy(spaceID) {
|
||||
return cache.get(spaceID) || (() => {
|
||||
const entry = (async () => {
|
||||
const result = await api.getFullHierarchy(spaceID)
|
||||
/** @type {{name: string, value: string}[]} */
|
||||
const childRooms = []
|
||||
for (const room of result) {
|
||||
if (room.name && !room.name.match(/^\[[⛓️🔊]\]/) && room.room_type !== "m.space") {
|
||||
childRooms.push({name: room.name, value: room.room_id})
|
||||
reverseCache.set(room.room_id, spaceID)
|
||||
}
|
||||
}
|
||||
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 getCachedHierarchy(spaceID)
|
||||
// @ts-ignore
|
||||
rooms = rooms.filter(r => r.name.includes(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
|
|
@ -14,13 +14,14 @@ const api = sync.require("../../matrix/api")
|
|||
|
||||
/**
|
||||
* @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction
|
||||
* @param {{api: typeof api}} di
|
||||
* @returns {Promise<DiscordTypes.APIInteractionResponse>}
|
||||
*/
|
||||
async function _interact({data, channel, guild_id}) {
|
||||
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 input = options?.[0]?.value || ""
|
||||
const mxid = input.match(/@([^:]+):([a-z0-9:-]+\.[a-z0-9.:-]+)/)?.[0]
|
||||
if (!mxid) return {
|
||||
type: DiscordTypes.InteractionResponseType.ChannelMessageWithSource,
|
||||
|
@ -110,9 +111,10 @@ async function _interact({data, channel, guild_id}) {
|
|||
|
||||
/**
|
||||
* @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction
|
||||
* @param {{api: typeof api}} di
|
||||
* @returns {Promise<DiscordTypes.APIInteractionResponse>}
|
||||
*/
|
||||
async function _interactButton({channel, message}) {
|
||||
async function _interactButton({channel, message}, {api}) {
|
||||
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()
|
||||
|
@ -127,14 +129,16 @@ async function _interactButton({channel, message}) {
|
|||
}
|
||||
}
|
||||
|
||||
/* c8 ignore start */
|
||||
|
||||
/** @param {DiscordTypes.APIChatInputApplicationCommandGuildInteraction & {channel: DiscordTypes.APIGuildTextChannel}} interaction */
|
||||
async function interact(interaction) {
|
||||
await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interact(interaction))
|
||||
await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interact(interaction, {api}))
|
||||
}
|
||||
|
||||
/** @param {DiscordTypes.APIMessageComponentGuildInteraction} interaction */
|
||||
async function interactButton(interaction) {
|
||||
await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interactButton(interaction))
|
||||
await discord.snow.interaction.createInteractionResponse(interaction.id, interaction.token, await _interactButton(interaction, {api}))
|
||||
}
|
||||
|
||||
module.exports.interact = interact
|
||||
|
|
228
src/discord/interactions/invite.test.js
Normal file
228
src/discord/interactions/invite.test.js
Normal file
|
@ -0,0 +1,228 @@
|
|||
const {test} = require("supertape")
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const {db, discord} = require("../../passthrough")
|
||||
const {MatrixServerError} = require("../../matrix/mreq")
|
||||
const {_interact, _interactButton} = require("./invite")
|
||||
|
||||
test("invite: checks for missing matrix ID", async t => {
|
||||
const msg = await _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`")
|
||||
})
|
||||
|
||||
test("invite: checks for invalid matrix ID", async t => {
|
||||
const msg = await _interact({
|
||||
data: {
|
||||
options: [{
|
||||
name: "user",
|
||||
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||
value: "@cadence"
|
||||
}]
|
||||
},
|
||||
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`")
|
||||
})
|
||||
|
||||
test("invite: checks if channel exists or is autocreatable", async t => {
|
||||
db.prepare("UPDATE guild_active SET autocreate = 0").run()
|
||||
const msg = await _interact({
|
||||
data: {
|
||||
options: [{
|
||||
name: "user",
|
||||
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||
value: "@cadence:cadence.moe"
|
||||
}]
|
||||
},
|
||||
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.")
|
||||
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({
|
||||
data: {
|
||||
options: [{
|
||||
name: "user",
|
||||
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||
value: "@cadence:cadence.moe"
|
||||
}]
|
||||
},
|
||||
channel: discord.channels.get("112760669178241024"),
|
||||
guild_id: "112760669178241024"
|
||||
}, {
|
||||
api: {
|
||||
getStateEvent: async (roomID, type, stateKey) => {
|
||||
called++
|
||||
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") // space ID
|
||||
t.equal(type, "m.room.member")
|
||||
t.equal(stateKey, "@cadence:cadence.moe")
|
||||
return {
|
||||
displayname: "cadence",
|
||||
membership: "invite"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
t.equal(msg.data.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({
|
||||
data: {
|
||||
options: [{
|
||||
name: "user",
|
||||
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||
value: "@cadence:cadence.moe"
|
||||
}]
|
||||
},
|
||||
channel: discord.channels.get("112760669178241024"),
|
||||
guild_id: "112760669178241024"
|
||||
}, {
|
||||
api: {
|
||||
getStateEvent: async (roomID, type, stateKey) => {
|
||||
called++
|
||||
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") // space ID
|
||||
t.equal(type, "m.room.member")
|
||||
t.equal(stateKey, "@cadence:cadence.moe")
|
||||
throw new MatrixServerError("State event doesn't exist or something")
|
||||
},
|
||||
inviteToRoom: async (roomID, mxid) => {
|
||||
called++
|
||||
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe") // space ID
|
||||
t.equal(mxid, "@cadence:cadence.moe")
|
||||
}
|
||||
}
|
||||
})
|
||||
t.equal(msg.data.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({
|
||||
data: {
|
||||
options: [{
|
||||
name: "user",
|
||||
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||
value: "@cadence:cadence.moe"
|
||||
}]
|
||||
},
|
||||
channel: discord.channels.get("112760669178241024"),
|
||||
guild_id: "112760669178241024"
|
||||
}, {
|
||||
api: {
|
||||
getStateEvent: async (roomID, type, stateKey) => {
|
||||
called++
|
||||
t.equal(type, "m.room.member")
|
||||
t.equal(stateKey, "@cadence:cadence.moe")
|
||||
if (roomID === "!jjWAGMeQdNrVZSSfvz:cadence.moe") { // space ID
|
||||
return {
|
||||
displayname: "cadence",
|
||||
membership: "join"
|
||||
}
|
||||
} else {
|
||||
throw new MatrixServerError("State event doesn't exist or something")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
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(called, 2)
|
||||
})
|
||||
|
||||
test("invite: prompts to invite to room (if left)", async t => {
|
||||
let called = 0
|
||||
const msg = await _interact({
|
||||
data: {
|
||||
options: [{
|
||||
name: "user",
|
||||
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||
value: "@cadence:cadence.moe"
|
||||
}]
|
||||
},
|
||||
channel: discord.channels.get("112760669178241024"),
|
||||
guild_id: "112760669178241024"
|
||||
}, {
|
||||
api: {
|
||||
getStateEvent: async (roomID, type, stateKey) => {
|
||||
called++
|
||||
t.equal(type, "m.room.member")
|
||||
t.equal(stateKey, "@cadence:cadence.moe")
|
||||
if (roomID === "!jjWAGMeQdNrVZSSfvz:cadence.moe") { // space ID
|
||||
return {
|
||||
displayname: "cadence",
|
||||
membership: "join"
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
displayname: "cadence",
|
||||
membership: "leave"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
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(called, 2)
|
||||
})
|
||||
|
||||
test("invite button: invites to room when button clicked", async t => {
|
||||
let called = 0
|
||||
const msg = await _interactButton({
|
||||
channel: discord.channels.get("112760669178241024"),
|
||||
message: {
|
||||
content: "`@cadence:cadence.moe` is already in this server. Would you like to additionally invite them to this specific channel?"
|
||||
}
|
||||
}, {
|
||||
api: {
|
||||
inviteToRoom: async (roomID, mxid) => {
|
||||
called++
|
||||
t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") // room ID
|
||||
t.equal(mxid, "@cadence:cadence.moe")
|
||||
}
|
||||
}
|
||||
})
|
||||
t.equal(msg.data.content, "You invited `@cadence:cadence.moe` to the channel.")
|
||||
t.equal(called, 1)
|
||||
})
|
||||
|
||||
test("invite: no-op if in room and space", async t => {
|
||||
let called = 0
|
||||
const msg = await _interact({
|
||||
data: {
|
||||
options: [{
|
||||
name: "user",
|
||||
type: DiscordTypes.ApplicationCommandOptionType.String,
|
||||
value: "@cadence:cadence.moe"
|
||||
}]
|
||||
},
|
||||
channel: discord.channels.get("112760669178241024"),
|
||||
guild_id: "112760669178241024"
|
||||
}, {
|
||||
api: {
|
||||
getStateEvent: async (roomID, type, stateKey) => {
|
||||
called++
|
||||
t.equal(type, "m.room.member")
|
||||
t.equal(stateKey, "@cadence:cadence.moe")
|
||||
return {
|
||||
displayname: "cadence",
|
||||
membership: "join"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
t.equal(msg.data.content, "`@cadence:cadence.moe` is already in this server and this channel.")
|
||||
t.equal(called, 2)
|
||||
})
|
|
@ -7,7 +7,6 @@ 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")
|
||||
const reactions = sync.require("./interactions/reactions.js")
|
||||
const privacy = sync.require("./interactions/privacy.js")
|
||||
|
||||
|
@ -39,20 +38,6 @@ discord.snow.interaction.bulkOverwriteApplicationCommands(id, [{
|
|||
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
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: "privacy",
|
||||
contexts: [DiscordTypes.InteractionContextType.Guild],
|
||||
|
@ -94,8 +79,6 @@ async function dispatchInteraction(interaction) {
|
|||
await permissions.interact(interaction)
|
||||
} else if (interactionId === "permissions_edit") {
|
||||
await permissions.interactEdit(interaction)
|
||||
} else if (interactionId === "bridge") {
|
||||
await bridge.interact(interaction)
|
||||
} else if (interactionId === "Reactions") {
|
||||
await reactions.interact(interaction)
|
||||
} else if (interactionId === "privacy") {
|
||||
|
|
|
@ -113,7 +113,7 @@ function isWebhookMessage(message) {
|
|||
* @param {Pick<DiscordTypes.APIMessage, "flags">} message
|
||||
*/
|
||||
function isEphemeralMessage(message) {
|
||||
return message.flags && (message.flags & DiscordTypes.MessageFlags.Ephemeral)
|
||||
return Boolean(message.flags && (message.flags & DiscordTypes.MessageFlags.Ephemeral))
|
||||
}
|
||||
|
||||
/** @param {string} snowflake */
|
||||
|
|
|
@ -84,6 +84,67 @@ test("getPermissions: channel overwrite to allow role works", t => {
|
|||
t.equal((permissions & want), want)
|
||||
})
|
||||
|
||||
test("getPermissions: channel overwrite to allow user works", t => {
|
||||
const guildRoles = [
|
||||
{
|
||||
version: 1695412489043,
|
||||
unicode_emoji: null,
|
||||
tags: {},
|
||||
position: 0,
|
||||
permissions: "559623605571137",
|
||||
name: "@everyone",
|
||||
mentionable: false,
|
||||
managed: false,
|
||||
id: "1154868424724463687",
|
||||
icon: null,
|
||||
hoist: false,
|
||||
flags: 0,
|
||||
color: 0
|
||||
},
|
||||
{
|
||||
version: 1695412604262,
|
||||
unicode_emoji: null,
|
||||
tags: { bot_id: "466378653216014359" },
|
||||
position: 1,
|
||||
permissions: "536995904",
|
||||
name: "PluralKit",
|
||||
mentionable: false,
|
||||
managed: true,
|
||||
id: "1154868908336099444",
|
||||
icon: null,
|
||||
hoist: false,
|
||||
flags: 0,
|
||||
color: 0
|
||||
},
|
||||
{
|
||||
version: 1698778936921,
|
||||
unicode_emoji: null,
|
||||
tags: {},
|
||||
position: 1,
|
||||
permissions: "536870912",
|
||||
name: "web hookers",
|
||||
mentionable: false,
|
||||
managed: false,
|
||||
id: "1168988246680801360",
|
||||
icon: null,
|
||||
hoist: false,
|
||||
flags: 0,
|
||||
color: 0
|
||||
}
|
||||
]
|
||||
const userRoles = []
|
||||
const userID = "353373325575323648"
|
||||
const overwrites = [
|
||||
{ type: 0, id: "1154868908336099444", deny: "0", allow: "1024" },
|
||||
{ type: 0, id: "1154868424724463687", deny: "1024", allow: "0" },
|
||||
{ type: 0, id: "1168988246680801360", deny: "0", allow: "1024" },
|
||||
{ type: 1, id: "353373325575323648", deny: "0", allow: "1024" }
|
||||
]
|
||||
const permissions = utils.getPermissions(userRoles, guildRoles, userID, overwrites)
|
||||
const want = BigInt(1 << 10 | 1 << 16)
|
||||
t.equal((permissions & want), want)
|
||||
})
|
||||
|
||||
test("hasSomePermissions: detects the permission", t => {
|
||||
const userPermissions = DiscordTypes.PermissionFlagsBits.MentionEveryone | DiscordTypes.PermissionFlagsBits.BanMembers
|
||||
const canRemoveMembers = utils.hasSomePermissions(userPermissions, ["KickMembers", "BanMembers"])
|
||||
|
@ -107,3 +168,15 @@ test("hasAllPermissions: doesn't detect not the permissions", t => {
|
|||
const canRemoveMembers = utils.hasAllPermissions(userPermissions, ["KickMembers", "BanMembers"])
|
||||
t.equal(canRemoveMembers, false)
|
||||
})
|
||||
|
||||
test("isEphemeralMessage: detects ephemeral message", t => {
|
||||
t.equal(utils.isEphemeralMessage(data.special_message.ephemeral_message), true)
|
||||
})
|
||||
|
||||
test("isEphemeralMessage: doesn't detect normal message", t => {
|
||||
t.equal(utils.isEphemeralMessage(data.message.simple_plaintext), false)
|
||||
})
|
||||
|
||||
test("getPublicUrlForCdn: no-op on non-discord URL", t => {
|
||||
t.equal(utils.getPublicUrlForCdn("https://cadence.moe"), "https://cadence.moe")
|
||||
})
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
const {test} = require("supertape")
|
||||
const power = require("./power")
|
||||
|
||||
test("power: get affected rooms", t => {
|
||||
t.deepEqual(power._getAffectedRooms(), [{
|
||||
mxid: "@test_auto_invite:example.org",
|
||||
power_level: 100,
|
||||
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe",
|
||||
}])
|
||||
})
|
|
@ -9,7 +9,7 @@ const registrationFilePath = path.join(process.cwd(), "registration.yaml")
|
|||
|
||||
/** @param {import("../types").AppServiceRegistrationConfig} reg */
|
||||
function checkRegistration(reg) {
|
||||
reg["ooye"].invite = (reg.ooye.invite || []).filter(mxid => mxid.endsWith(`:${reg.ooye.server_name}`)) // one day I will understand why typescript disagrees with dot notation on this line
|
||||
reg["ooye"].invite = reg.ooye.invite.filter(mxid => mxid.endsWith(`:${reg.ooye.server_name}`)) // one day I will understand why typescript disagrees with dot notation on this line
|
||||
assert(reg.ooye?.max_file_size)
|
||||
assert(reg.ooye?.namespace_prefix)
|
||||
assert(reg.ooye?.server_name)
|
||||
|
@ -19,6 +19,7 @@ function checkRegistration(reg) {
|
|||
assert.match(reg.url, /^https?:/, "url must start with http:// or https://")
|
||||
}
|
||||
|
||||
/* c8 ignore next 4 */
|
||||
/** @param {import("../types").AppServiceRegistrationConfig} reg */
|
||||
function writeRegistration(reg) {
|
||||
fs.writeFileSync(registrationFilePath, JSON.stringify(reg, null, 2))
|
||||
|
@ -52,6 +53,7 @@ function getTemplateRegistration(serverName) {
|
|||
socket: 6693,
|
||||
ooye: {
|
||||
namespace_prefix,
|
||||
server_name: serverName,
|
||||
max_file_size: 5000000,
|
||||
content_length_workaround: false,
|
||||
include_user_id_in_mxid: false,
|
||||
|
@ -66,6 +68,8 @@ function readRegistration() {
|
|||
try {
|
||||
const content = fs.readFileSync(registrationFilePath, "utf8")
|
||||
result = JSON.parse(content)
|
||||
result.ooye.invite ||= []
|
||||
/* c8 ignore next */
|
||||
} catch (e) {}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// @ts-check
|
||||
|
||||
const tryToCatch = require("try-to-catch")
|
||||
const {test} = require("supertape")
|
||||
const {reg} = require("./read-registration")
|
||||
const {reg, checkRegistration, getTemplateRegistration} = require("./read-registration")
|
||||
|
||||
test("reg: has necessary parameters", t => {
|
||||
const propertiesToCheck = ["sender_localpart", "id", "as_token", "ooye"]
|
||||
|
@ -8,3 +11,19 @@ test("reg: has necessary parameters", t => {
|
|||
propertiesToCheck
|
||||
)
|
||||
})
|
||||
|
||||
test("check: passes on sample", t => {
|
||||
checkRegistration(reg)
|
||||
t.pass("all assertions passed")
|
||||
})
|
||||
|
||||
test("check: fails on template as template is missing some required values that are gathered during setup", t => {
|
||||
let err
|
||||
try {
|
||||
// @ts-ignore
|
||||
checkRegistration(getTemplateRegistration("cadence.moe"))
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
t.ok(err, "one of the assertions failed as expected")
|
||||
})
|
||||
|
|
7
src/types.d.ts
vendored
7
src/types.d.ts
vendored
|
@ -55,9 +55,10 @@ export type InitialAppServiceRegistrationConfig = {
|
|||
socket?: string | number,
|
||||
ooye: {
|
||||
namespace_prefix: string
|
||||
max_file_size: number,
|
||||
content_length_workaround: boolean,
|
||||
invite: string[],
|
||||
server_name: string
|
||||
max_file_size: number
|
||||
content_length_workaround: boolean
|
||||
invite: string[]
|
||||
include_user_id_in_mxid: boolean
|
||||
}
|
||||
}
|
||||
|
|
49
test/data.js
49
test/data.js
|
@ -4128,7 +4128,54 @@ module.exports = {
|
|||
guild_id: "112760669178241024"
|
||||
},
|
||||
position: 0
|
||||
}
|
||||
},
|
||||
ephemeral_message: {
|
||||
webhook_id: "684280192553844747",
|
||||
type: 20,
|
||||
tts: false,
|
||||
timestamp: "2024-09-29T11:22:04.865000+00:00",
|
||||
position: 0,
|
||||
pinned: false,
|
||||
nonce: "1289910062243905536",
|
||||
mentions: [],
|
||||
mention_roles: [],
|
||||
mention_everyone: false,
|
||||
interaction_metadata: {
|
||||
user: {baby: true},
|
||||
type: 2,
|
||||
name: "invite",
|
||||
id: "1289910063691206717",
|
||||
command_type: 1,
|
||||
authorizing_integration_owners: {baby: true}
|
||||
},
|
||||
interaction: {
|
||||
user: {baby: true},
|
||||
type: 2,
|
||||
name: "invite",
|
||||
id: "1289910063691206717"
|
||||
},
|
||||
id: "1289910064995504182",
|
||||
flags: 64,
|
||||
embeds: [],
|
||||
edited_timestamp: null,
|
||||
content: "`@cadence:cadence.moe` is already in this server and this channel.",
|
||||
components: [],
|
||||
channel_id: "1100319550446252084",
|
||||
author: {
|
||||
username: "Matrix Bridge",
|
||||
public_flags: 0,
|
||||
id: "684280192553844747",
|
||||
global_name: null,
|
||||
discriminator: "5728",
|
||||
clan: null,
|
||||
bot: true,
|
||||
avatar_decoration_data: null,
|
||||
avatar: "48ae3c24f2a6ec5c60c41bdabd904018"
|
||||
},
|
||||
attachments: [],
|
||||
application_id: "684280192553844747"
|
||||
},
|
||||
shard_id: 0
|
||||
},
|
||||
interaction_message: {
|
||||
thinking_interaction_without_bot_user: {
|
||||
|
|
|
@ -3,6 +3,9 @@ BEGIN TRANSACTION;
|
|||
INSERT INTO guild_space (guild_id, space_id, privacy_level) VALUES
|
||||
('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe', 0);
|
||||
|
||||
INSERT INTO guild_active (guild_id, autocreate) VALUES
|
||||
('112760669178241024', 1);
|
||||
|
||||
INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent, custom_avatar) VALUES
|
||||
('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, NULL),
|
||||
('497161350934560778', '!CzvdIdUQXgUjDVKxeU:cadence.moe', 'amanda-spam', NULL, NULL, NULL),
|
||||
|
|
|
@ -23,8 +23,8 @@ reg.id = "baby" // don't actually take authenticated actions on the server
|
|||
reg.as_token = "baby"
|
||||
reg.hs_token = "baby"
|
||||
reg.ooye.bridge_origin = "https://bridge.example.org"
|
||||
reg.ooye.invite = []
|
||||
|
||||
/** @type {import("heatsync").default} */ // @ts-ignore
|
||||
const sync = new HeatSync({watchFS: false})
|
||||
|
||||
const discord = {
|
||||
|
@ -35,6 +35,7 @@ const discord = {
|
|||
id: "684280192553844747"
|
||||
},
|
||||
channels: new Map([
|
||||
[data.channel.general.id, data.channel.general],
|
||||
["497161350934560778", {
|
||||
guild_id: "497159726455455754"
|
||||
}],
|
||||
|
@ -117,7 +118,6 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
|
|||
require("../src/matrix/kstate.test")
|
||||
require("../src/matrix/api.test")
|
||||
require("../src/matrix/file.test")
|
||||
require("../src/matrix/power.test")
|
||||
require("../src/matrix/read-registration.test")
|
||||
require("../src/matrix/txnid.test")
|
||||
require("../src/d2m/actions/create-room.test")
|
||||
|
@ -136,4 +136,5 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
|
|||
require("../src/m2d/converters/event-to-message.test")
|
||||
require("../src/m2d/converters/utils.test")
|
||||
require("../src/m2d/converters/emoji-sheet.test")
|
||||
require("../src/discord/interactions/invite.test")
|
||||
})()
|
||||
|
|
Loading…
Reference in a new issue