setUserPower should account for room version 12

This commit is contained in:
Cadence Ember
2026-01-07 15:38:09 +13:00
parent 55e0e5dfa1
commit 4bea696a5a
29 changed files with 199 additions and 133 deletions

View File

@@ -37,7 +37,9 @@ const createRoom = sync.require("../d2m/actions/create-room")
/** @type {import("../src/matrix/mreq")} */
const mreq = sync.require("../matrix/mreq")
/** @type {import("../src/matrix/api")} */
const api = sync.require("../matrix/api")
const api = sync.require("../src/matrix/api")
/** @type {import("../src/matrix/utils")} */
const utils = sync.require("../src/matrix/utils")
const sema = new Semaphore()
@@ -89,7 +91,7 @@ async function migrateGuild(guild) {
throw e
}
}
await api.setUserPower(roomID, newBridgeMxid, 100)
await utils.setUserPower(roomID, newBridgeMxid, 100, api)
})
await api.joinRoom(roomID)

View File

@@ -10,6 +10,7 @@ const passthrough = require("../src/passthrough")
Object.assign(passthrough, {db, sync})
const api = require("../src/matrix/api")
const utils = require("../src/matrix/utils")
const mreq = require("../src/matrix/mreq")
const rooms = db.prepare("select room_id from channel_room").pluck().all()
@@ -25,7 +26,7 @@ const rooms = db.prepare("select room_id from channel_room").pluck().all()
await api.leaveRoom(roomID, mxid)
}
}
await api.setUserPower(roomID, "@_discord_bot:cadence.moe", 0)
await utils.setUserPower(roomID, "@_discord_bot:cadence.moe", 0, api)
await api.leaveRoom(roomID)
} catch (e) {
if (e.message.includes("Appservice not in room")) {

View File

@@ -17,8 +17,8 @@ const mreq = sync.require("../../matrix/mreq")
const ks = sync.require("../../matrix/kstate")
/** @type {import("../../discord/utils")} */
const dUtils = sync.require("../../discord/utils")
/** @type {import("../../m2d/converters/utils")} */
const mUtils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const mUtils = sync.require("../../matrix/utils")
/** @type {import("./create-space")} */
const createSpace = sync.require("./create-space")

View File

@@ -12,7 +12,9 @@ const api = sync.require("../../matrix/api")
/** @type {import("../../matrix/file")} */
const file = sync.require("../../matrix/file")
/** @type {import("../../discord/utils")} */
const utils = sync.require("../../discord/utils")
const dUtils = sync.require("../../discord/utils")
/** @type {import("../../matrix/utils")} */
const mxUtils = sync.require("../../matrix/utils")
/** @type {import("../converters/user-to-mxid")} */
const userToMxid = sync.require("../converters/user-to-mxid")
/** @type {import("./create-room")} */
@@ -159,8 +161,8 @@ async function memberToStateContent(user, member, guildID) {
function memberToPowerLevel(user, member, guild, channel) {
if (!member) return 0
const permissions = utils.getPermissions(member.roles, guild.roles, user.id, channel.permission_overwrites)
const everyonePermissions = utils.getPermissions([], guild.roles, undefined, channel.permission_overwrites)
const permissions = dUtils.getPermissions(member.roles, guild.roles, user.id, channel.permission_overwrites)
const everyonePermissions = dUtils.getPermissions([], guild.roles, undefined, channel.permission_overwrites)
/*
* PL 100 = Administrator = People who can brick the room. RATIONALE:
* - Administrator.
@@ -169,7 +171,7 @@ function memberToPowerLevel(user, member, guild, channel) {
* - Manage Channels: People who can manage the channel can delete it.
* (Setting sim users to PL 100 is safe because even though we can't demote the sims we can use code to make the sims demote themselves.)
*/
if (guild.owner_id === user.id || utils.hasSomePermissions(permissions, ["Administrator", "ManageWebhooks", "ManageGuild", "ManageChannels"])) return 100
if (guild.owner_id === user.id || dUtils.hasSomePermissions(permissions, ["Administrator", "ManageWebhooks", "ManageGuild", "ManageChannels"])) return 100
/*
* PL 50 = Moderator = People who can manage people and messages in many ways. RATIONALE:
* - Manage Messages: Can moderate by pinning or deleting the conversation.
@@ -179,14 +181,14 @@ function memberToPowerLevel(user, member, guild, channel) {
* - Mute Members & Deafen Members: Can moderate by silencing disruptive people in ways they can't undo.
* - Moderate Members.
*/
if (utils.hasSomePermissions(permissions, ["ManageMessages", "ManageNicknames", "ManageThreads", "KickMembers", "BanMembers", "MuteMembers", "DeafenMembers", "ModerateMembers"])) return 50
if (dUtils.hasSomePermissions(permissions, ["ManageMessages", "ManageNicknames", "ManageThreads", "KickMembers", "BanMembers", "MuteMembers", "DeafenMembers", "ModerateMembers"])) return 50
/* PL 50 = if room is read-only but the user has been specially allowed to send messages */
const everyoneCanSend = utils.hasPermission(everyonePermissions, DiscordTypes.PermissionFlagsBits.SendMessages)
const userCanSend = utils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.SendMessages)
const everyoneCanSend = dUtils.hasPermission(everyonePermissions, DiscordTypes.PermissionFlagsBits.SendMessages)
const userCanSend = dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.SendMessages)
if (!everyoneCanSend && userCanSend) return createRoom.READ_ONLY_ROOM_EVENTS_DEFAULT_POWER
/* PL 20 = Mention Everyone for technical reasons. */
const everyoneCanMentionEveryone = utils.hasPermission(everyonePermissions, DiscordTypes.PermissionFlagsBits.MentionEveryone)
const userCanMentionEveryone = utils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.MentionEveryone)
const everyoneCanMentionEveryone = dUtils.hasPermission(everyonePermissions, DiscordTypes.PermissionFlagsBits.MentionEveryone)
const userCanMentionEveryone = dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.MentionEveryone)
if (!everyoneCanMentionEveryone && userCanMentionEveryone) return 20
return 0
}
@@ -247,7 +249,7 @@ async function _sendSyncUser(roomID, mxid, content, powerLevel, options) {
actions.push(api.sendState(roomID, "m.room.member", mxid, content, mxid))
// Update power levels
if (powerLevel != null) {
actions.push(api.setUserPower(roomID, mxid, powerLevel))
actions.push(mxUtils.setUserPower(roomID, mxid, powerLevel, api))
}
// Update global profile (if supported by server)
if (await supportsMsc4069) {

View File

@@ -6,8 +6,8 @@ const passthrough = require("../../passthrough")
const {sync, select, from} = passthrough
/** @type {import("./message-to-event")} */
const messageToEvent = sync.require("../converters/message-to-event")
/** @type {import("../../m2d/converters/utils")} */
const utils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const utils = sync.require("../../matrix/utils")
function eventCanBeEdited(ev) {
// Discord does not allow files, images, attachments, or videos to be edited.

View File

@@ -1,7 +1,7 @@
const {test} = require("supertape")
const {messageToEvent} = require("./message-to-event")
const data = require("../../../test/data")
const {mockGetEffectivePower} = require("../../m2d/converters/utils.test")
const {mockGetEffectivePower} = require("../../matrix/utils.test")
const {db} = require("../../passthrough")
test("message2event embeds: nothing but a field", async t => {

View File

@@ -14,8 +14,8 @@ const file = sync.require("../../matrix/file")
const emojiToKey = sync.require("./emoji-to-key")
/** @type {import("../actions/lottie")} */
const lottie = sync.require("../actions/lottie")
/** @type {import("../../m2d/converters/utils")} */
const mxUtils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const mxUtils = sync.require("../../matrix/utils")
/** @type {import("../../discord/utils")} */
const dUtils = sync.require("../../discord/utils")
const {reg} = require("../../matrix/read-registration")

View File

@@ -2,7 +2,7 @@ const {test} = require("supertape")
const {messageToEvent} = require("./message-to-event")
const {MatrixServerError} = require("../../matrix/mreq")
const data = require("../../../test/data")
const {mockGetEffectivePower} = require("../../m2d/converters/utils.test")
const {mockGetEffectivePower} = require("../../matrix/utils.test")
const Ty = require("../../types")
/**

View File

@@ -5,8 +5,8 @@ const DiscordTypes = require("discord-api-types/v10")
const passthrough = require("../../passthrough")
const {discord, sync, select} = passthrough
/** @type {import("../../m2d/converters/utils")} */
const utils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const utils = sync.require("../../matrix/utils")
/**
* @typedef ReactionRemoveRequest

View File

@@ -4,8 +4,8 @@ const assert = require("assert").strict
const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough
/** @type {import("../../m2d/converters/utils")} */
const mxUtils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const mxUtils = sync.require("../../matrix/utils")
const {reg} = require("../../matrix/read-registration.js")
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))

View File

@@ -2,7 +2,7 @@ const {test} = require("supertape")
const {threadToAnnouncement} = require("./thread-to-announcement")
const data = require("../../../test/data")
const Ty = require("../../types")
const {mockGetEffectivePower} = require("../../m2d/converters/utils.test")
const {mockGetEffectivePower} = require("../../matrix/utils.test")
/**
* @param {string} roomID

View File

@@ -9,8 +9,8 @@ const {InteractionMethods} = require("snowtransfer")
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
/** @type {import("../../m2d/converters/utils")} */
const utils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const utils = sync.require("../../matrix/utils")
/**
* @param {DiscordTypes.APIContextMenuGuildInteraction} interaction
@@ -126,7 +126,7 @@ async function* _interactEdit({data, guild_id, message}, {api}) {
assert(spaceID)
// Do it
await api.setUserPowerCascade(spaceID, mxid, power)
await utils.setUserPowerCascade(spaceID, mxid, power, api)
// ACK
yield {editOriginalInteractionResponse: {

View File

@@ -2,7 +2,7 @@ const {test} = require("supertape")
const DiscordTypes = require("discord-api-types/v10")
const {select, db} = require("../../passthrough")
const {_interact, _interactEdit} = require("./permissions")
const {mockGetEffectivePower} = require("../../m2d/converters/utils.test")
const {mockGetEffectivePower} = require("../../matrix/utils.test")
/**
* @template T
@@ -156,7 +156,7 @@ test("permissions: reports permissions of selected matrix user (admin v11 cannot
})
test("permissions: can update user to moderator", async t => {
let called = 0
let called = []
const msgs = await fromAsync(_interactEdit({
data: {
target_id: "1128118177155526666",
@@ -168,22 +168,48 @@ test("permissions: can update user to moderator", async t => {
guild_id: "112760669178241024"
}, {
api: {
async setUserPowerCascade(roomID, mxid, power) {
called++
t.equal(roomID, "!jjmvBegULiLucuWEHU:cadence.moe") // space ID
t.equal(mxid, "@cadence:cadence.moe")
t.equal(power, 50)
async getStateEvent(roomID, type, key) {
called.push("get power levels")
t.equal(type, "m.room.power_levels")
return {}
},
async getStateEventOuter(roomID, type, key) {
called.push("get room create")
return {
type: "m.room.create",
state_key: "",
sender: "@_ooye_bot:cadence.moe",
event_id: "$create",
origin_server_ts: 0,
room_id: roomID,
content: {
room_version: "11"
}
}
},
async *generateFullHierarchy(spaceID) {
called.push("generate full hierarchy")
},
async sendState(roomID, type, key, content) {
called.push("set power levels")
t.ok(["!hierarchy", "!jjmvBegULiLucuWEHU:cadence.moe"].includes(roomID), `expected room ID to be in hierarchy, but was ${roomID}`)
t.equal(type, "m.room.power_levels")
t.equal(key, "")
t.deepEqual(content, {
users: {"@cadence:cadence.moe": 50}
})
return "$updated"
}
}
}))
t.equal(msgs.length, 2)
t.equal(msgs[0].createInteractionResponse.data.content, "Updating `@cadence:cadence.moe` to **moderator**, please wait...")
t.equal(msgs[1].editOriginalInteractionResponse.content, "Updated `@cadence:cadence.moe` to **moderator**.")
t.equal(called, 1)
t.deepEqual(called, ["generate full hierarchy", "get room create", "get power levels", "set power levels"])
})
test("permissions: can update user to default", async t => {
let called = 0
let called = []
const msgs = await fromAsync(_interactEdit({
data: {
target_id: "1128118177155526666",
@@ -195,16 +221,44 @@ test("permissions: can update user to default", async t => {
guild_id: "112760669178241024"
}, {
api: {
async setUserPowerCascade(roomID, mxid, power) {
called++
t.equal(roomID, "!jjmvBegULiLucuWEHU:cadence.moe") // space ID
t.equal(mxid, "@cadence:cadence.moe")
t.equal(power, 0)
async getStateEvent(roomID, type, key) {
called.push("get power levels")
t.equal(type, "m.room.power_levels")
return {
users: {"@cadence:cadence.moe": 50}
}
},
async getStateEventOuter(roomID, type, key) {
called.push("get room create")
return {
type: "m.room.create",
state_key: "",
sender: "@_ooye_bot:cadence.moe",
event_id: "$create",
origin_server_ts: 0,
room_id: roomID,
content: {
room_version: "11"
}
}
},
async *generateFullHierarchy(spaceID) {
called.push("generate full hierarchy")
},
async sendState(roomID, type, key, content) {
called.push("set power levels")
t.ok(["!hierarchy", "!jjmvBegULiLucuWEHU:cadence.moe"].includes(roomID), `expected room ID to be in hierarchy, but was ${roomID}`)
t.equal(type, "m.room.power_levels")
t.equal(key, "")
t.deepEqual(content, {
users: {}
})
return "$updated"
}
}
}))
t.equal(msgs.length, 2)
t.equal(msgs[0].createInteractionResponse.data.content, "Updating `@cadence:cadence.moe` to **default**, please wait...")
t.equal(msgs[1].editOriginalInteractionResponse.content, "Updated `@cadence:cadence.moe` to **default**.")
t.equal(called, 1)
t.deepEqual(called, ["generate full hierarchy", "get room create", "get power levels", "set power levels"])
})

View File

@@ -7,8 +7,8 @@ const {InteractionMethods} = require("snowtransfer")
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
/** @type {import("../../m2d/converters/utils")} */
const utils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const utils = sync.require("../../matrix/utils")
/**
* @param {DiscordTypes.APIMessageApplicationCommandGuildInteraction} interaction

View File

@@ -5,8 +5,8 @@ const Ty = require("../../types")
const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough
/** @type {import("../converters/utils")} */
const utils = sync.require("../converters/utils")
/** @type {import("../../matrix/utils")} */
const utils = sync.require("../../matrix/utils")
/** @type {import("../converters/emoji")} */
const emoji = sync.require("../converters/emoji")

View File

@@ -5,8 +5,8 @@ const Ty = require("../../types")
const passthrough = require("../../passthrough")
const {discord, sync, db, select, from} = passthrough
/** @type {import("../converters/utils")} */
const utils = sync.require("../converters/utils")
/** @type {import("../../matrix/utils")} */
const utils = sync.require("../../matrix/utils")
/**
* @param {Ty.Event.Outer_M_Room_Redaction} event

View File

@@ -14,8 +14,8 @@ const {tag} = require("@cloudrac3r/html-template-tag")
const passthrough = require("../../passthrough")
const {sync, db, discord, select, from} = passthrough
const {reg} = require("../../matrix/read-registration")
/** @type {import("../converters/utils")} */
const mxUtils = sync.require("../converters/utils")
/** @type {import("../../matrix/utils")} */
const mxUtils = sync.require("../../matrix/utils")
/** @type {import("../../discord/utils")} */
const dUtils = sync.require("../../discord/utils")
/** @type {import("../../matrix/file")} */

View File

@@ -20,8 +20,8 @@ const redact = sync.require("./actions/redact")
const updatePins = sync.require("./actions/update-pins")
/** @type {import("../matrix/matrix-command-handler")} */
const matrixCommandHandler = sync.require("../matrix/matrix-command-handler")
/** @type {import("./converters/utils")} */
const utils = sync.require("./converters/utils")
/** @type {import("../matrix/utils")} */
const utils = sync.require("../matrix/utils")
/** @type {import("../matrix/api")}) */
const api = sync.require("../matrix/api")
/** @type {import("../d2m/actions/create-room")} */

View File

@@ -358,55 +358,6 @@ async function profileSetAvatarUrl(mxid, avatar_url, inhibitPropagate) {
}
}
/**
* Set a user's power level within a room.
* @param {string} roomID
* @param {string} mxid
* @param {number} newPower
*/
async function setUserPower(roomID, mxid, newPower) {
assert(roomID[0] === "!")
assert(mxid[0] === "@")
// Yes there's no shortcut https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352
const power = await getStateEvent(roomID, "m.room.power_levels", "")
power.users = power.users || {}
// Check if it has really changed to avoid sending a useless state event
// (Can't diff kstate here because of (a) circular imports (b) kstate has special behaviour diffing power levels)
const oldPowerLevel = power.users?.[mxid] ?? power.users_default ?? 0
if (oldPowerLevel === newPower) return
// Bridge bot can't demote equal power users, so need to decide which user will send the event
const botPowerLevel = power.users?.[`@${reg.sender_localpart}:${reg.ooye.server_name}`] ?? power.users_default ?? 0
const eventSender = oldPowerLevel >= botPowerLevel ? mxid : undefined
// Update the event content
if (newPower == null || newPower === (power.users_default ?? 0)) {
delete power.users[mxid]
} else {
power.users[mxid] = newPower
}
await sendState(roomID, "m.room.power_levels", "", power, eventSender)
return power
}
/**
* Set a user's power level for a whole room hierarchy.
* @param {string} spaceID
* @param {string} mxid
* @param {number} power
*/
async function setUserPowerCascade(spaceID, mxid, power) {
assert(spaceID[0] === "!")
assert(mxid[0] === "@")
const rooms = await getFullHierarchy(spaceID)
await setUserPower(spaceID, mxid, power)
for (const room of rooms) {
await setUserPower(room.room_id, mxid, power)
}
}
async function ping() {
// not using mreq so that we can read the status code
const res = await fetch(`${mreq.baseUrl}/client/v1/appservice/${reg.id}/ping`, {
@@ -579,8 +530,6 @@ module.exports.redactEvent = redactEvent
module.exports.sendTyping = sendTyping
module.exports.profileSetDisplayname = profileSetDisplayname
module.exports.profileSetAvatarUrl = profileSetAvatarUrl
module.exports.setUserPower = setUserPower
module.exports.setUserPowerCascade = setUserPowerCascade
module.exports.ping = ping
module.exports.getMedia = getMedia
module.exports.sendReadReceipt = sendReadReceipt

View File

@@ -10,8 +10,8 @@ const {sync} = passthrough
const file = sync.require("./file")
/** @type {import("./api")} */
const api = sync.require("./api")
/** @type {import("../m2d/converters/utils")} */
const utils = sync.require("../m2d/converters/utils")
/** @type {import("./utils")} */
const utils = sync.require("./utils")
/** Mutates the input. Not recursive - can only include or exclude entire state events. */
function kstateStripConditionals(kstate) {

View File

@@ -8,8 +8,8 @@ const sharp = require("sharp")
const {discord, sync, db, select} = require("../passthrough")
/** @type {import("./api")}) */
const api = sync.require("./api")
/** @type {import("../m2d/converters/utils")} */
const mxUtils = sync.require("../m2d/converters/utils")
/** @type {import("./utils")} */
const mxUtils = sync.require("./utils")
/** @type {import("../discord/utils")} */
const dUtils = sync.require("../discord/utils")
/** @type {import("./kstate")} */

View File

@@ -10,8 +10,8 @@ const {discord, db, sync, as, select, from} = require("../passthrough")
const api = sync.require("./api")
/** @type {import("../d2m/actions/create-room")}) */
const createRoom = sync.require("../d2m/actions/create-room")
/** @type {import("../m2d/converters/utils")}) */
const utils = sync.require("../m2d/converters/utils")
/** @type {import("./utils")}) */
const utils = sync.require("./utils")
const roomUpgradeSema = new Semaphore()

View File

@@ -1,11 +1,11 @@
// @ts-check
const assert = require("assert").strict
const Ty = require("../../types")
const passthrough = require("../../passthrough")
const Ty = require("../types")
const passthrough = require("../passthrough")
const {db} = passthrough
const {reg} = require("../../matrix/read-registration")
const {reg} = require("./read-registration")
const userRegex = reg.namespaces.users.map(u => new RegExp(u.regex))
/** @type {import("xxhash-wasm").XXHashAPI} */ // @ts-ignore
@@ -129,7 +129,7 @@ class MatrixStringBuilder {
* https://spec.matrix.org/v1.9/appendices/#routing
* https://gitdab.com/cadence/out-of-your-element/issues/11
* @param {string} roomID
* @param {{[K in "getStateEvent" | "getStateEventOuter" | "getJoinedMembers"]: import("../../matrix/api")[K]} | {getEffectivePower: (roomID: string, mxids: string[], api: any) => Promise<{powers: Record<string, number>, allCreators: string[], tombstone: number, roomCreate: Ty.Event.StateOuter<Ty.Event.M_Room_Create>, powerLevels: Ty.Event.M_Power_Levels}>, getJoinedMembers: import("../../matrix/api")["getJoinedMembers"]}} api
* @param {{[K in "getStateEvent" | "getStateEventOuter" | "getJoinedMembers"]: import("./api")[K]} | {getEffectivePower: (roomID: string, mxids: string[], api: any) => Promise<{powers: Record<string, number>, allCreators: string[], tombstone: number, roomCreate: Ty.Event.StateOuter<Ty.Event.M_Room_Create>, powerLevels: Ty.Event.M_Power_Levels}>, getJoinedMembers: import("./api")["getJoinedMembers"]}} api
*/
async function getViaServers(roomID, api) {
const candidates = []
@@ -188,7 +188,7 @@ async function getViaServers(roomID, api) {
* https://spec.matrix.org/v1.9/appendices/#routing
* https://gitdab.com/cadence/out-of-your-element/issues/11
* @param {string} roomID
* @param {{[K in "getStateEvent" | "getStateEventOuter" | "getJoinedMembers"]: import("../../matrix/api")[K]}} api
* @param {{[K in "getStateEvent" | "getStateEventOuter" | "getJoinedMembers"]: import("./api")[K]}} api
* @returns {Promise<URLSearchParams>}
*/
async function getViaServersQuery(roomID, api) {
@@ -273,7 +273,7 @@ function removeCreatorsFromPowerLevels(roomCreateOuter, powerLevels) {
* @template {string} T
* @param {string} roomID
* @param {T[]} mxids
* @param {{[K in "getStateEvent" | "getStateEventOuter"]: import("../../matrix/api")[K]}} api
* @param {{[K in "getStateEvent" | "getStateEventOuter"]: import("./api")[K]}} api
* @returns {Promise<{powers: Record<T, number>, allCreators: string[], tombstone: number, roomCreate: Ty.Event.StateOuter<Ty.Event.M_Room_Create>, powerLevels: Ty.Event.M_Power_Levels}>}
*/
async function getEffectivePower(roomID, mxids, api) {
@@ -300,6 +300,56 @@ async function getEffectivePower(roomID, mxids, api) {
return {powers, allCreators, tombstone, roomCreate, powerLevels}
}
/**
* Set a user's power level within a room.
* @param {string} roomID
* @param {string} mxid
* @param {number} newPower
* @param {{[K in "getStateEvent" | "getStateEventOuter" | "sendState"]: import("./api")[K]}} api
*/
async function setUserPower(roomID, mxid, newPower, api) {
assert(roomID[0] === "!")
assert(mxid[0] === "@")
// Yes there's no shortcut https://github.com/matrix-org/matrix-appservice-bridge/blob/2334b0bae28a285a767fe7244dad59f5a5963037/src/components/intent.ts#L352
const {powerLevels, powers: {[mxid]: oldPowerLevel, [bot]: botPowerLevel}} = await getEffectivePower(roomID, [mxid, bot], api)
// Check if it has really changed to avoid sending a useless state event
if (oldPowerLevel === newPower) return
// Bridge bot can't demote equal power users, so need to decide which user will send the event
const eventSender = oldPowerLevel >= botPowerLevel ? mxid : undefined
// Update the event content
powerLevels.users ??= {}
if (newPower == null || newPower === (powerLevels.users_default ?? 0)) {
delete powerLevels.users[mxid]
} else {
powerLevels.users[mxid] = newPower
}
await api.sendState(roomID, "m.room.power_levels", "", powerLevels, eventSender)
}
/**
* Set a user's power level for a whole room hierarchy.
* @param {string} spaceID
* @param {string} mxid
* @param {number} power
* @param {{[K in "getStateEvent" | "getStateEventOuter" | "sendState" | "generateFullHierarchy"]: import("./api")[K]}} api
*/
async function setUserPowerCascade(spaceID, mxid, power, api) {
assert(spaceID[0] === "!")
assert(mxid[0] === "@")
let seenSpace = false
for await (const room of api.generateFullHierarchy(spaceID)) {
if (room.room_id === spaceID) seenSpace = true
await setUserPower(room.room_id, mxid, power, api)
}
if (!seenSpace) {
await setUserPower(spaceID, mxid, power, api)
}
}
module.exports.bot = bot
module.exports.BLOCK_ELEMENTS = BLOCK_ELEMENTS
module.exports.eventSenderIsFromDiscord = eventSenderIsFromDiscord
@@ -311,3 +361,5 @@ module.exports.getViaServersQuery = getViaServersQuery
module.exports.roomHasAtLeastVersion = roomHasAtLeastVersion
module.exports.removeCreatorsFromPowerLevels = removeCreatorsFromPowerLevels
module.exports.getEffectivePower = getEffectivePower
module.exports.setUserPower = setUserPower
module.exports.setUserPowerCascade = setUserPowerCascade

View File

@@ -1,7 +1,5 @@
// @ts-check
const e = new Error("Custom error")
const {test} = require("supertape")
const {eventSenderIsFromDiscord, getEventIDHash, MatrixStringBuilder, getViaServers, roomHasAtLeastVersion} = require("./utils")
const util = require("util")
@@ -41,8 +39,14 @@ test("event hash: hash is different for different inputs", t => {
})
test("MatrixStringBuilder: add, addLine, add same text", t => {
const e = {
stack: "Error: Custom error\n at ./example.test.js:3:11)",
toString() {
return "Error: Custom error"
}
}
const gatewayMessage = {t: "MY_MESSAGE", d: {display: "Custom message data"}}
let stackLines = e.stack?.split("\n")
let stackLines = e.stack.split("\n")
const builder = new MatrixStringBuilder()
builder.addLine("\u26a0 Bridged event from Discord not delivered", "\u26a0 <strong>Bridged event from Discord not delivered</strong>")
@@ -63,12 +67,12 @@ test("MatrixStringBuilder: add, addLine, add same text", t => {
+ "\nError: Custom error"
+ "\nError trace:"
+ "\nError: Custom error"
+ "\n at ./m2d/converters/utils.test.js:3:11)\n",
+ "\n at ./example.test.js:3:11)\n",
format: "org.matrix.custom.html",
formatted_body: "\u26a0 <strong>Bridged event from Discord not delivered</strong>"
+ "<br>Gateway event: MY_MESSAGE"
+ "<br>Error: Custom error"
+ "<br><details><summary>Error trace</summary><pre>Error: Custom error\n at ./m2d/converters/utils.test.js:3:11)</pre></details>"
+ "<br><details><summary>Error trace</summary><pre>Error: Custom error\n at ./example.test.js:3:11)</pre></details>"
+ `<details><summary>Original payload</summary><pre>{ display: 'Custom message data' }</pre></details>`
})
})

View File

@@ -18,7 +18,9 @@ const createSpace = sync.require("../../d2m/actions/create-space")
/** @type {import("../auth")} */
const auth = require("../auth")
/** @type {import("../../discord/utils")} */
const utils = sync.require("../../discord/utils")
const dUtils = sync.require("../../discord/utils")
/** @type {import("../../matrix/utils")} */
const mxUtils = sync.require("../../matrix/utils")
const {reg} = require("../../matrix/read-registration")
const schema = {
@@ -102,8 +104,8 @@ function getChannelRoomsLinks(guild, rooms, roles) {
let unlinkedChannels = unlinkedChannelIDs.map(c => discord.channels.get(c))
let removedWrongTypeChannels = filterTo(unlinkedChannels, c => c && [0, 5].includes(c.type))
let removedPrivateChannels = filterTo(unlinkedChannels, c => {
const permissions = utils.getPermissions(roles, guild.roles, botID, c["permission_overwrites"])
return utils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.ViewChannel)
const permissions = dUtils.getPermissions(roles, guild.roles, botID, c["permission_overwrites"])
return dUtils.hasPermission(permissions, DiscordTypes.PermissionFlagsBits.ViewChannel)
})
unlinkedChannels.sort((a, b) => getPosition(a) - getPosition(b))
@@ -228,7 +230,7 @@ as.router.post("/api/invite", defineEventHandler(async event => {
( parsedBody.permissions === "admin" ? 100
: parsedBody.permissions === "moderator" ? 50
: 0)
if (powerLevel) await api.setUserPowerCascade(spaceID, parsedBody.mxid, powerLevel)
if (powerLevel) await mxUtils.setUserPowerCascade(spaceID, parsedBody.mxid, powerLevel, api)
if (parsedBody.guild_id) {
setResponseHeader(event, "HX-Refresh", true)

View File

@@ -4,8 +4,8 @@ const {z} = require("zod")
const {defineEventHandler, getValidatedQuery, H3Event} = require("h3")
const {as, from, sync, select} = require("../../passthrough")
/** @type {import("../../m2d/converters/utils")} */
const mUtils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")} */
const mUtils = sync.require("../../matrix/utils")
/**
* @param {H3Event} event

View File

@@ -10,8 +10,8 @@ const {discord, db, as, sync, select, from} = require("../../passthrough")
const auth = sync.require("../auth")
/** @type {import("../../matrix/mreq")} */
const mreq = sync.require("../../matrix/mreq")
/** @type {import("../../m2d/converters/utils")}*/
const utils = sync.require("../../m2d/converters/utils")
/** @type {import("../../matrix/utils")}*/
const utils = sync.require("../../matrix/utils")
const {reg} = require("../../matrix/read-registration")
/**

View File

@@ -13,8 +13,8 @@ const reg = require("../matrix/read-registration")
const {sync, discord, as, select} = require("../passthrough")
/** @type {import("./pug-sync")} */
const pugSync = sync.require("./pug-sync")
/** @type {import("../m2d/converters/utils")} */
const mUtils = sync.require("../m2d/converters/utils")
/** @type {import("../matrix/utils")} */
const mUtils = sync.require("../matrix/utils")
const {id} = require("../../addbot")
// Pug

View File

@@ -147,6 +147,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../src/matrix/mreq.test")
require("../src/matrix/read-registration.test")
require("../src/matrix/txnid.test")
require("../src/matrix/utils.test")
require("../src/d2m/actions/create-room.test")
require("../src/d2m/actions/create-space.test")
require("../src/d2m/actions/register-user.test")
@@ -164,7 +165,6 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../src/m2d/converters/diff-pins.test")
require("../src/m2d/converters/event-to-message.test")
require("../src/m2d/converters/emoji.test")
require("../src/m2d/converters/utils.test")
require("../src/m2d/converters/emoji-sheet.test")
require("../src/discord/interactions/invite.test")
require("../src/discord/interactions/matrix-info.test")