diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index bee45e2..51926ee 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -15,24 +15,6 @@ const ks = sync.require("../../matrix/kstate") /** @type {import("./create-space")}) */ const createSpace = sync.require("./create-space") // watch out for the require loop -/** - * There are 3 levels of room privacy: - * 0: Room is invite-only. - * 1: Anybody can use a link to join. - * 2: Room is published in room directory. - */ -const PRIVACY_ENUMS = { - PRESET: ["private_chat", "public_chat", "public_chat"], - VISIBILITY: ["private", "private", "public"], - SPACE_HISTORY_VISIBILITY: ["invited", "world_readable", "world_readable"], // copying from element client - ROOM_HISTORY_VISIBILITY: ["shared", "shared", "world_readable"], // any events sent after are visible, but for world_readable anybody can read without even joining - GUEST_ACCESS: ["can_join", "forbidden", "forbidden"], // whether guests can join space if other conditions are met - SPACE_JOIN_RULES: ["invite", "public", "public"], - ROOM_JOIN_RULES: ["restricted", "public", "public"] -} - -const DEFAULT_PRIVACY_LEVEL = 0 - /** @type {Map>} channel ID -> Promise */ const inflightRoomCreate = new Map() @@ -87,9 +69,7 @@ function convertNameAndTopic(channel, guild, customName) { */ async function channelToKState(channel, guild) { const spaceID = await createSpace.ensureSpace(guild) - assert(typeof spaceID === "string") - const privacyLevel = select("guild_space", "privacy_level", {space_id: spaceID}).pluck().get() - assert(privacyLevel) + assert.ok(typeof spaceID === "string") const row = select("channel_room", ["nick", "custom_avatar"], {channel_id: channel.id}).get() const customName = row?.nick @@ -104,33 +84,27 @@ async function channelToKState(channel, guild) { avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } - let history_visibility = PRIVACY_ENUMS.ROOM_HISTORY_VISIBILITY[privacyLevel] + let history_visibility = "shared" if (channel["thread_metadata"]) history_visibility = "world_readable" - /** @type {{join_rule: string, allow?: any}} */ - let join_rules = { - join_rule: "restricted", - allow: [{ - type: "m.room_membership", - room_id: spaceID - }] - } - if (PRIVACY_ENUMS.ROOM_JOIN_RULES[privacyLevel] !== "restricted") { - join_rules = {join_rule: PRIVACY_ENUMS.ROOM_JOIN_RULES[privacyLevel]} - } - const channelKState = { "m.room.name/": {name: convertedName}, "m.room.topic/": {topic: convertedTopic}, "m.room.avatar/": avatarEventContent, - "m.room.guest_access/": {guest_access: PRIVACY_ENUMS.GUEST_ACCESS[privacyLevel]}, + "m.room.guest_access/": {guest_access: "can_join"}, "m.room.history_visibility/": {history_visibility}, [`m.space.parent/${spaceID}`]: { via: [reg.ooye.server_name], canonical: true }, /** @type {{join_rule: string, [x: string]: any}} */ - "m.room.join_rules/": join_rules, + "m.room.join_rules/": { + join_rule: "restricted", + allow: [{ + type: "m.room_membership", + room_id: spaceID + }] + }, "m.room.power_levels/": { events: { "m.room.avatar": 0 @@ -158,7 +132,7 @@ async function channelToKState(channel, guild) { } } - return {spaceID, privacyLevel, channelKState} + return {spaceID, channelKState} } /** @@ -167,10 +141,9 @@ async function channelToKState(channel, guild) { * @param guild * @param {string} spaceID * @param {any} kstate - * @param {number} privacyLevel * @returns {Promise} room ID */ -async function createRoom(channel, guild, spaceID, kstate, privacyLevel) { +async function createRoom(channel, guild, spaceID, kstate) { let threadParent = null if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id @@ -187,8 +160,8 @@ async function createRoom(channel, guild, spaceID, kstate, privacyLevel) { const roomID = await api.createRoom({ name, topic, - preset: PRIVACY_ENUMS.ROOM_HISTORY_VISIBILITY[privacyLevel], // This is closest to what we want, but properties from kstate override it anyway - visibility: PRIVACY_ENUMS.VISIBILITY[privacyLevel], + preset: "private_chat", // This is closest to what we want, but properties from kstate override it anyway + visibility: "private", // Not shown in the room directory invite: [], initial_state: ks.kstateToState(kstate) }) @@ -279,8 +252,8 @@ async function _syncRoom(channelID, shouldActuallySync) { if (!existing) { const creation = (async () => { - const {spaceID, privacyLevel, channelKState} = await channelToKState(channel, guild) - const roomID = await createRoom(channel, guild, spaceID, channelKState, privacyLevel) + const {spaceID, channelKState} = await channelToKState(channel, guild) + const roomID = await createRoom(channel, guild, spaceID, channelKState) inflightRoomCreate.delete(channelID) // OK to release inflight waiters now. they will read the correct `existing` row return roomID })() @@ -398,8 +371,6 @@ async function createAllForGuild(guildID) { } } -module.exports.DEFAULT_PRIVACY_LEVEL = DEFAULT_PRIVACY_LEVEL -module.exports.PRIVACY_ENUMS = PRIVACY_ENUMS module.exports.createRoom = createRoom module.exports.ensureRoom = ensureRoom module.exports.syncRoom = syncRoom diff --git a/d2m/actions/create-space.js b/d2m/actions/create-space.js index 1c1c357..da877e0 100644 --- a/d2m/actions/create-space.js +++ b/d2m/actions/create-space.js @@ -32,8 +32,8 @@ async function createSpace(guild, kstate) { const roomID = await createRoom.postApplyPowerLevels(kstate, async kstate => { return api.createRoom({ name, - preset: createRoom.PRIVACY_ENUMS.PRESET[createRoom.DEFAULT_PRIVACY_LEVEL], // New spaces will have to use the default privacy level; we obviously can't look up the existing entry - visibility: createRoom.PRIVACY_ENUMS.VISIBILITY[createRoom.DEFAULT_PRIVACY_LEVEL], + preset: "private_chat", // cannot join space unless invited + visibility: "private", power_level_content_override: { events_default: 100, // space can only be managed by bridge invite: 0 // any existing member can invite others @@ -51,21 +51,23 @@ async function createSpace(guild, kstate) { } /** - * @param {DiscordTypes.APIGuild} guild - * @param {number} privacyLevel + * @param {DiscordTypes.APIGuild} guild] */ -async function guildToKState(guild, privacyLevel) { +async function guildToKState(guild) { const avatarEventContent = {} if (guild.icon) { avatarEventContent.discord_path = file.guildIcon(guild) avatarEventContent.url = await file.uploadDiscordFileToMxc(avatarEventContent.discord_path) // TODO: somehow represent future values in kstate (callbacks?), while still allowing for diffing, so test cases don't need to touch the media API } + let history_visibility = "invited" + if (guild["thread_metadata"]) history_visibility = "world_readable" + const guildKState = { "m.room.name/": {name: guild.name}, "m.room.avatar/": avatarEventContent, - "m.room.guest_access/": {guest_access: createRoom.PRIVACY_ENUMS.GUEST_ACCESS[privacyLevel]}, - "m.room.history_visibility/": {history_visibility: createRoom.PRIVACY_ENUMS.GUEST_ACCESS[privacyLevel]} + "m.room.guest_access/": {guest_access: "can_join"}, // guests can join space if other conditions are met + "m.room.history_visibility/": {history_visibility: "invited"} // any events sent after user was invited are visible } return guildKState @@ -84,11 +86,11 @@ async function _syncSpace(guild, shouldActuallySync) { await inflightSpaceCreate.get(guild.id) // just waiting, and then doing a new db query afterwards, is the simplest way of doing it } - const row = select("guild_space", ["space_id", "privacy_level"], {guild_id: guild.id}).get() + const spaceID = select("guild_space", "space_id", {guild_id: guild.id}).pluck().get() - if (!row) { + if (!spaceID) { const creation = (async () => { - const guildKState = await guildToKState(guild, createRoom.DEFAULT_PRIVACY_LEVEL) // New spaces will have to use the default privacy level; we obviously can't look up the existing entry + const guildKState = await guildToKState(guild) const spaceID = await createSpace(guild, guildKState) inflightSpaceCreate.delete(guild.id) return spaceID @@ -97,15 +99,13 @@ async function _syncSpace(guild, shouldActuallySync) { return creation // Naturally, the newly created space is already up to date, so we can always skip syncing here. } - const {space_id: spaceID, privacy_level} = row - if (!shouldActuallySync) { return spaceID // only need to ensure space exists, and it does. return the space ID } console.log(`[space sync] to matrix: ${guild.name}`) - const guildKState = await guildToKState(guild, privacy_level) // calling this in both branches because we don't want to calculate this if not syncing + const guildKState = await guildToKState(guild) // calling this in both branches because we don't want to calculate this if not syncing // sync guild state to space const spaceKState = await createRoom.roomToKState(spaceID) @@ -159,20 +159,17 @@ async function syncSpaceFully(guildID) { const guild = discord.guilds.get(guildID) assert.ok(guild) - const row = select("guild_space", ["space_id", "privacy_level"], {guild_id: guildID}).get() + const spaceID = select("guild_space", "space_id", {guild_id: guildID}).pluck().get() - if (!row) { - const guildKState = await guildToKState(guild, createRoom.DEFAULT_PRIVACY_LEVEL) + const guildKState = await guildToKState(guild) + + if (!spaceID) { const spaceID = await createSpace(guild, guildKState) return spaceID // Naturally, the newly created space is already up to date, so we can always skip syncing here. } - const {space_id: spaceID, privacy_level} = row - console.log(`[space sync] to matrix: ${guild.name}`) - const guildKState = await guildToKState(guild, privacy_level) - // sync guild state to space const spaceKState = await createRoom.roomToKState(spaceID) const spaceDiff = ks.diffKState(spaceKState, guildKState) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index a0027b0..843ee20 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -40,11 +40,9 @@ async function sendMessage(message, guild) { } for (const event of events) { const eventType = event.$type - if (event.$sender) senderMxid = event.$sender - /** @type {Pick> & { $type?: string, $sender?: string }} */ + /** @type {Pick> & { $type?: string }} */ const eventWithoutType = {...event} delete eventWithoutType.$type - delete eventWithoutType.$sender const useTimestamp = message["backfill"] ? new Date(message.timestamp).getTime() : undefined const eventID = await api.sendEvent(roomID, eventType, eventWithoutType, senderMxid, useTimestamp) diff --git a/db/migrations/0005-clear-member-cache.sql b/db/migrations/0005-clear-member-cache.sql deleted file mode 100644 index 6899fe6..0000000 --- a/db/migrations/0005-clear-member-cache.sql +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN TRANSACTION; - -DELETE FROM member_cache; - -COMMIT; diff --git a/db/migrations/0006-add-privacy-to-space.sql b/db/migrations/0006-add-privacy-to-space.sql deleted file mode 100644 index a5a69e2..0000000 --- a/db/migrations/0006-add-privacy-to-space.sql +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN TRANSACTION; - -ALTER TABLE guild_space ADD COLUMN privacy_level TEXT NOT NULL DEFAULT 0; - -COMMIT; diff --git a/db/orm-defs.d.ts b/db/orm-defs.d.ts index 0714e0b..292a445 100644 --- a/db/orm-defs.d.ts +++ b/db/orm-defs.d.ts @@ -25,7 +25,6 @@ export type Models = { guild_space: { guild_id: string space_id: string - privacy_level: number } lottie: { diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index f7e5e1b..48add81 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -147,6 +147,6 @@ sync.addTemporaryListener(as, "type:m.room.member", guard("m.room.member", */ async event => { if (event.state_key[0] !== "@") return - if (utils.eventSenderIsFromDiscord(event.state_key)) return - db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(event.room_id, event.state_key, event.content.displayname || null, event.content.avatar_url || null) + if (utils.eventSenderIsFromDiscord(event.sender)) return + db.prepare("REPLACE INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES (?, ?, ?, ?)").run(event.room_id, event.sender, event.content.displayname || null, event.content.avatar_url || null) })) diff --git a/package-lock.json b/package-lock.json index d1ed09e..16ed46c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "out-of-your-element", - "version": "1.1.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "out-of-your-element", - "version": "1.1.1", + "version": "1.1.0", "license": "AGPL-3.0-or-later", "dependencies": { "@chriscdn/promise-semaphore": "^2.0.1", diff --git a/package.json b/package.json index e8eec83..7c84ebc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "out-of-your-element", - "version": "1.1.1", + "version": "1.1.0", "description": "A bridge between Matrix and Discord", "main": "index.js", "repository": { diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index 2070b66..1ce0467 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -1,7 +1,7 @@ BEGIN TRANSACTION; -INSERT INTO guild_space (guild_id, space_id, privacy_level) VALUES -('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe', 0); +INSERT INTO guild_space (guild_id, space_id) VALUES +('112760669178241024', '!jjWAGMeQdNrVZSSfvz:cadence.moe'); INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent, custom_avatar) VALUES ('112760669178241024', '!kLRqKKUQXcibIMtOpl:cadence.moe', 'heave', 'main', NULL, NULL),