diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 2dd62e01..a6b61cd3 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -2,6 +2,7 @@ const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") +const Ty = require("../../types") const reg = require("../../matrix/read-registration") 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. * @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel * @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 const parentChannel = discord.channels.get(channel.parent_id) /** Used for membership/permission checks. */ @@ -142,6 +144,11 @@ async function channelToKState(channel, guild) { const everyoneCanMentionEveryone = utils.hasAllPermissions(everyonePermissions, ["MentionEveryone"]) 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 = { "m.room.name/": {name: convertedName}, @@ -162,7 +169,7 @@ async function channelToKState(channel, guild) { notifications: { 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": { hidden: true @@ -311,7 +318,7 @@ async function _syncRoom(channelID, shouldActuallySync) { if (!existing) { 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) inflightRoomCreate.delete(channelID) // OK to release inflight waiters now. they will read the correct `existing` row return roomID @@ -328,7 +335,7 @@ async function _syncRoom(channelID, shouldActuallySync) { 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 const roomKState = await roomToKState(roomID) diff --git a/d2m/actions/create-room.test.js b/d2m/actions/create-room.test.js index 2a76e2e8..d4391a9d 100644 --- a/d2m/actions/create-room.test.js +++ b/d2m/actions/create-room.test.js @@ -9,45 +9,89 @@ const testData = require("../../test/data") const passthrough = require("../../passthrough") const {db} = passthrough + 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() 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, { "m.room.guest_access/": {guest_access: "forbidden"}, "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 => { + 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() 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, { "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 => { + 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() t.deepEqual( - kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general).then(x => x.channelKState)), - testData.room.general + kstateStripConditionals(await channelToKState(testData.channel.general, testData.guild.general, {api: {getStateEvent}}).then(x => x.channelKState)), + 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 => { + 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) 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( - 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 ) + t.equal(called, 1) }) test("convertNameAndTopic: custom name and topic", t => { diff --git a/matrix/power.test.js b/matrix/power.test.js new file mode 100644 index 00000000..5423c4fa --- /dev/null +++ b/matrix/power.test.js @@ -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", + }]) +}) diff --git a/test/data.js b/test/data.js index 18aaa1af..ffe2cc92 100644 --- a/test/data.js +++ b/test/data.js @@ -4065,6 +4065,73 @@ module.exports = { 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: { thinking_interaction_without_bot_user: {