1
0
Fork 0

Copy power levels from spaces to new rooms

This commit is contained in:
Cadence Ember 2024-08-31 16:18:33 +12:00
parent ee266f844a
commit 7afe3e7591
4 changed files with 142 additions and 12 deletions

View file

@ -2,6 +2,7 @@
const assert = require("assert").strict const assert = require("assert").strict
const DiscordTypes = require("discord-api-types/v10") const DiscordTypes = require("discord-api-types/v10")
const Ty = require("../../types")
const reg = require("../../matrix/read-registration") const reg = require("../../matrix/read-registration")
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
@ -89,8 +90,9 @@ function convertNameAndTopic(channel, guild, customName) {
* Async because it may create the guild and/or upload the guild icon to mxc. * Async because it may create the guild and/or upload the guild icon to mxc.
* @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel * @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel
* @param {DiscordTypes.APIGuild} guild * @param {DiscordTypes.APIGuild} guild
* @param {{api: {getStateEvent: typeof api.getStateEvent}}} di simple-as-nails dependency injection for the matrix API
*/ */
async function channelToKState(channel, guild) { async function channelToKState(channel, guild, di) {
// @ts-ignore // @ts-ignore
const parentChannel = discord.channels.get(channel.parent_id) const parentChannel = discord.channels.get(channel.parent_id)
/** Used for membership/permission checks. */ /** Used for membership/permission checks. */
@ -142,6 +144,11 @@ async function channelToKState(channel, guild) {
const everyoneCanMentionEveryone = utils.hasAllPermissions(everyonePermissions, ["MentionEveryone"]) const everyoneCanMentionEveryone = utils.hasAllPermissions(everyonePermissions, ["MentionEveryone"])
const globalAdmins = select("member_power", ["mxid", "power_level"], {room_id: "*"}).all() const globalAdmins = select("member_power", ["mxid", "power_level"], {room_id: "*"}).all()
const globalAdminPower = globalAdmins.reduce((a, c) => (a[c.mxid] = c.power_level, a), {})
/** @type {Ty.Event.M_Power_Levels} */
const spacePowerEvent = await di.api.getStateEvent(guildSpaceID, "m.room.power_levels", "")
const spacePower = spacePowerEvent.users
const channelKState = { const channelKState = {
"m.room.name/": {name: convertedName}, "m.room.name/": {name: convertedName},
@ -162,7 +169,7 @@ async function channelToKState(channel, guild) {
notifications: { notifications: {
room: everyoneCanMentionEveryone ? 0 : 20 room: everyoneCanMentionEveryone ? 0 : 20
}, },
users: globalAdmins.reduce((a, c) => (a[c.mxid] = c.power_level, a), {}) users: {...spacePower, ...globalAdminPower}
}, },
"chat.schildi.hide_ui/read_receipts": { "chat.schildi.hide_ui/read_receipts": {
hidden: true hidden: true
@ -311,7 +318,7 @@ async function _syncRoom(channelID, shouldActuallySync) {
if (!existing) { if (!existing) {
const creation = (async () => { const creation = (async () => {
const {spaceID, privacyLevel, channelKState} = await channelToKState(channel, guild) const {spaceID, privacyLevel, channelKState} = await channelToKState(channel, guild, {api})
const roomID = await createRoom(channel, guild, spaceID, channelKState, privacyLevel) const roomID = await createRoom(channel, guild, spaceID, channelKState, privacyLevel)
inflightRoomCreate.delete(channelID) // OK to release inflight waiters now. they will read the correct `existing` row inflightRoomCreate.delete(channelID) // OK to release inflight waiters now. they will read the correct `existing` row
return roomID return roomID
@ -328,7 +335,7 @@ async function _syncRoom(channelID, shouldActuallySync) {
console.log(`[room sync] to matrix: ${channel.name}`) console.log(`[room sync] to matrix: ${channel.name}`)
const {spaceID, channelKState} = await channelToKState(channel, guild) // calling this in both branches because we don't want to calculate this if not syncing const {spaceID, channelKState} = await channelToKState(channel, guild, {api}) // calling this in both branches because we don't want to calculate this if not syncing
// sync channel state to room // sync channel state to room
const roomKState = await roomToKState(roomID) const roomKState = await roomToKState(roomID)

View file

@ -9,45 +9,89 @@ const testData = require("../../test/data")
const passthrough = require("../../passthrough") const passthrough = require("../../passthrough")
const {db} = passthrough const {db} = passthrough
test("channel2room: discoverable privacy room", async t => { test("channel2room: discoverable privacy room", async t => {
let called = 0
async function getStateEvent(roomID, type, key) { // getting power levels from space to apply to room
called++
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe")
t.equal(type, "m.room.power_levels")
t.equal(key, "")
return {users: {"@example:matrix.org": 50}}
}
db.prepare("UPDATE guild_space SET privacy_level = 2").run() db.prepare("UPDATE guild_space SET privacy_level = 2").run()
t.deepEqual( t.deepEqual(
kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general, {api: {getStateEvent}}).then(x => x.channelKState)),
Object.assign({}, testData.room.general, { Object.assign({}, testData.room.general, {
"m.room.guest_access/": {guest_access: "forbidden"}, "m.room.guest_access/": {guest_access: "forbidden"},
"m.room.join_rules/": {join_rule: "public"}, "m.room.join_rules/": {join_rule: "public"},
"m.room.history_visibility/": {history_visibility: "world_readable"} "m.room.history_visibility/": {history_visibility: "world_readable"},
"m.room.power_levels/": mixin({users: {"@example:matrix.org": 50}}, testData.room.general["m.room.power_levels/"])
}) })
) )
t.equal(called, 1)
}) })
test("channel2room: linkable privacy room", async t => { test("channel2room: linkable privacy room", async t => {
let called = 0
async function getStateEvent(roomID, type, key) { // getting power levels from space to apply to room
called++
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe")
t.equal(type, "m.room.power_levels")
t.equal(key, "")
return {users: {"@example:matrix.org": 50}}
}
db.prepare("UPDATE guild_space SET privacy_level = 1").run() db.prepare("UPDATE guild_space SET privacy_level = 1").run()
t.deepEqual( t.deepEqual(
kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general, {api: {getStateEvent}}).then(x => x.channelKState)),
Object.assign({}, testData.room.general, { Object.assign({}, testData.room.general, {
"m.room.guest_access/": {guest_access: "forbidden"}, "m.room.guest_access/": {guest_access: "forbidden"},
"m.room.join_rules/": {join_rule: "public"} "m.room.join_rules/": {join_rule: "public"},
"m.room.power_levels/": mixin({users: {"@example:matrix.org": 50}}, testData.room.general["m.room.power_levels/"])
}) })
) )
t.equal(called, 1)
}) })
test("channel2room: invite-only privacy room", async t => { test("channel2room: invite-only privacy room", async t => {
let called = 0
async function getStateEvent(roomID, type, key) { // getting power levels from space to apply to room
called++
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe")
t.equal(type, "m.room.power_levels")
t.equal(key, "")
return {users: {"@example:matrix.org": 50}}
}
db.prepare("UPDATE guild_space SET privacy_level = 0").run() db.prepare("UPDATE guild_space SET privacy_level = 0").run()
t.deepEqual( t.deepEqual(
kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general, {api: {getStateEvent}}).then(x => x.channelKState)),
testData.room.general Object.assign({}, testData.room.general, {
"m.room.power_levels/": mixin({users: {"@example:matrix.org": 50}}, testData.room.general["m.room.power_levels/"])
})
) )
t.equal(called, 1)
}) })
test("channel2room: room where limited people can mention everyone", async t => { test("channel2room: room where limited people can mention everyone", async t => {
let called = 0
async function getStateEvent(roomID, type, key) { // getting power levels from space to apply to room
called++
t.equal(roomID, "!jjWAGMeQdNrVZSSfvz:cadence.moe")
t.equal(type, "m.room.power_levels")
t.equal(key, "")
return {users: {"@example:matrix.org": 50}}
}
const limitedGuild = mixin({}, testData.guild.general) const limitedGuild = mixin({}, testData.guild.general)
limitedGuild.roles[0].permissions = (BigInt(limitedGuild.roles[0].permissions) - 131072n).toString() limitedGuild.roles[0].permissions = (BigInt(limitedGuild.roles[0].permissions) - 131072n).toString()
const limitedRoom = mixin({}, testData.room.general, {"m.room.power_levels/": {notifications: {room: 20}}}) const limitedRoom = mixin({}, testData.room.general, {"m.room.power_levels/": {
notifications: {room: 20},
users: {"@example:matrix.org": 50}
}})
t.deepEqual( t.deepEqual(
kstateStripConditionals(await channelToKState(testData.channel.general, limitedGuild).then(x => x.channelKState)), kstateStripConditionals(await channelToKState(testData.channel.general, limitedGuild, {api: {getStateEvent}}).then(x => x.channelKState)),
limitedRoom limitedRoom
) )
t.equal(called, 1)
}) })
test("convertNameAndTopic: custom name and topic", t => { test("convertNameAndTopic: custom name and topic", t => {

12
matrix/power.test.js Normal file
View file

@ -0,0 +1,12 @@
// @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",
}])
})

View file

@ -4065,6 +4065,73 @@ module.exports = {
guild_id: "372271956562542592" guild_id: "372271956562542592"
} }
}, },
reply_with_missing_referenced_message: {
type: 19,
content: "there must have been 2 different thumbnails used - the other one was this I think: https://lostmediawiki.com/w/images/6/6d/YOUTUBE_HACKED%21.jpg",
mentions: [
{
id: "112816036671184896",
username: "accavish",
avatar: "0efadd2b09568c89e81f47d321c1db9f",
discriminator: "0",
public_flags: 0,
flags: 0,
banner: null,
accent_color: null,
global_name: "Yuv.yuv",
avatar_decoration_data: null,
banner_color: null,
clan: null
}
],
mention_roles: [],
attachments: [],
embeds: [
{
type: "image",
url: "https://lostmediawiki.com/w/images/6/6d/YOUTUBE_HACKED%21.jpg",
reference_id: "1277081824962809919",
thumbnail: {
url: "https://lostmediawiki.com/w/images/6/6d/YOUTUBE_HACKED%21.jpg",
proxy_url: "https://images-ext-1.discordapp.net/external/DqHSi6Hsvkn8CeYqcpNsgcPun_yykRNdKzyPTLxkrJ8/https/lostmediawiki.com/w/images/6/6d/YOUTUBE_HACKED%2521.jpg",
width: 898,
height: 459,
placeholder: "MPcFLIoEgndse3pydlUHeZSQZw==",
placeholder_version: 1
}
}
],
timestamp: "2024-08-25T01:47:14.104000+00:00",
edited_timestamp: null,
flags: 0,
components: [],
id: "1277081824962809919",
channel_id: "112760669178241024",
author: {
id: "628334893109215263",
username: "thecracksoverhead",
avatar: "e4eaad082f5ff0359cafa8d3ad5ddd4f",
discriminator: "0",
public_flags: 0,
flags: 0,
banner: null,
accent_color: null,
global_name: "jdl",
avatar_decoration_data: null,
banner_color: null,
clan: null
},
pinned: false,
mention_everyone: false,
tts: false,
message_reference: {
type: 0,
channel_id: "112760669178241024",
message_id: "1277081326008143934",
guild_id: "112760669178241024"
},
position: 0
}
}, },
interaction_message: { interaction_message: {
thinking_interaction_without_bot_user: { thinking_interaction_without_bot_user: {