Support multiple versions of rooms

This commit is contained in:
Cadence Ember
2025-11-26 15:43:48 +13:00
parent e6c30f80b5
commit a441c476f8
29 changed files with 520 additions and 349 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
// @ts-check
const assert = require("assert")
const assert = require("assert").strict
const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough
+6 -3
View File
@@ -228,7 +228,10 @@ async function createRoom(channel, guild, spaceID, kstate, privacyLevel) {
...spaceCreationContent
})
db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent)
db.transaction(() => {
db.prepare("INSERT INTO channel_room (channel_id, room_id, name, nick, thread_parent) VALUES (?, ?, ?, NULL, ?)").run(channel.id, roomID, channel.name, threadParent)
db.prepare("INSERT INTO historical_channel_room (channel_id, room_id) VALUES (?, ?)").run(channel.id, roomID)
})()
return roomID
})
@@ -400,7 +403,7 @@ async function _syncRoom(channelID, shouldActuallySync) {
}
const roomDiff = ks.diffKState(roomKState, channelKState)
const roomApply = ks.applyKStateDiffToRoom(roomID, roomDiff)
db.prepare("UPDATE channel_room SET name = ? WHERE room_id = ?").run(channel.name, roomID)
db.prepare("UPDATE channel_room SET name = ? WHERE channel_id = ?").run(channel.name, channel.id)
// sync room as space member
const spaceApply = _syncSpaceMember(channel, spaceID, roomID, guild.id)
@@ -464,7 +467,7 @@ async function unbridgeDeletedChannel(channel, guildID) {
// delete room from database
db.prepare("DELETE FROM member_cache WHERE room_id = ?").run(roomID)
db.prepare("DELETE FROM channel_room WHERE room_id = ? AND channel_id = ?").run(roomID, channel.id) // cascades to most other tables, like messages
db.prepare("DELETE FROM channel_room WHERE room_id = ? AND channel_id = ?").run(roomID, channel.id) // cascades to most other tables, like messages and historical rooms
if (!botInRoom) return
+14 -10
View File
@@ -14,11 +14,13 @@ async function deleteMessage(data) {
const row = select("channel_room", ["room_id", "speedbump_checked", "thread_parent"], {channel_id: data.channel_id}).get()
if (!row) return
const eventsToRedact = select("event_message", "event_id", {message_id: data.id}).pluck().all()
db.prepare("DELETE FROM message_channel WHERE message_id = ?").run(data.id)
for (const eventID of eventsToRedact) {
// Assume we can redact from tombstoned rooms.
const eventsToRedact = from("event_message").join("message_room", "message_id").join("historical_channel_room", "historical_room_index")
.select("event_id", "room_id").where({message_id: data.id}).all()
db.prepare("DELETE FROM message_room WHERE message_id = ?").run(data.id)
for (const {event_id, room_id} of eventsToRedact) {
// Unfortunately, we can't specify a sender to do the redaction as, unless we find out that info via the audit logs
await api.redactEvent(row.room_id, eventID)
await api.redactEvent(room_id, event_id)
}
await speedbump.updateCache(row.thread_parent || data.channel_id, row.speedbump_checked)
@@ -28,15 +30,17 @@ async function deleteMessage(data) {
* @param {import("discord-api-types/v10").GatewayMessageDeleteBulkDispatchData} data
*/
async function deleteMessageBulk(data) {
const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get()
if (!roomID) return
const row = select("channel_room", "room_id", {channel_id: data.channel_id}).get()
if (!row) return
const sids = JSON.stringify(data.ids)
const eventsToRedact = from("event_message").pluck("event_id").and("WHERE message_id IN (SELECT value FROM json_each(?))").all(sids)
db.prepare("DELETE FROM message_channel WHERE message_id IN (SELECT value FROM json_each(?))").run(sids)
for (const eventID of eventsToRedact) {
// Assume we can redact from tombstoned rooms.
const eventsToRedact = from("event_message").join("message_room", "message_id").join("historical_channel_room", "historical_room_index")
.select("event_id", "room_id").and("WHERE message_id IN (SELECT value FROM json_each(?))").all(sids)
db.prepare("DELETE FROM message_room WHERE message_id IN (SELECT value FROM json_each(?))").run(sids)
for (const {event_id, room_id} of eventsToRedact) {
// Awaiting will make it go slower, but since this could be a long-running operation either way, we want to leave rate limit capacity for other operations
await api.redactEvent(roomID, eventID)
await api.redactEvent(room_id, event_id)
}
}
+8 -2
View File
@@ -3,7 +3,7 @@
const assert = require("assert").strict
const passthrough = require("../../passthrough")
const {sync, db, select} = passthrough
const {sync, db, select, from} = passthrough
/** @type {import("../converters/edit-to-changes")} */
const editToChanges = sync.require("../converters/edit-to-changes")
/** @type {import("./register-pk-user")} */
@@ -19,6 +19,12 @@ const mreq = sync.require("../../matrix/mreq")
* @param {{speedbump_id: string, speedbump_webhook_id: string} | null} row data about the webhook which is proxying messages in this channel
*/
async function editMessage(message, guild, row) {
const historicalRoomOfMessage = from("message_room").join("historical_channel_room", "historical_room_index").where({message_id: message.id}).select("room_id").get()
const currentRoom = from("channel_room").join("historical_channel_room", "room_id").where({channel_id: message.channel_id}).select("room_id", "historical_room_index").get()
assert(currentRoom)
if (historicalRoomOfMessage && historicalRoomOfMessage.room_id !== currentRoom.room_id) return // tombstoned rooms should not have new events (including edits) sent to them
let {roomID, eventsToRedact, eventsToReplace, eventsToSend, senderMxid, promotions} = await editToChanges.editToChanges(message, guild, api)
if (row && row.speedbump_webhook_id === message.webhook_id) {
@@ -61,7 +67,7 @@ async function editMessage(message, guild, row) {
// 4. Send all the things.
if (eventsToSend.length) {
db.prepare("INSERT OR IGNORE INTO message_channel (message_id, channel_id) VALUES (?, ?)").run(message.id, message.channel_id)
db.prepare("INSERT OR IGNORE INTO message_room (message_id, historical_room_index) VALUES (?, ?)").run(message.id, currentRoom.historical_room_index)
}
for (const content of eventsToSend) {
const eventType = content.$type
+4 -7
View File
@@ -4,10 +4,9 @@ const assert = require("assert").strict
const {reg} = require("../../matrix/read-registration")
const DiscordTypes = require("discord-api-types/v10")
const Ty = require("../../types")
const mixin = require("@cloudrac3r/mixin-deep")
const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough
const {discord, sync, db, from, select} = passthrough
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
/** @type {import("../../matrix/file")} */
@@ -222,7 +221,8 @@ async function syncUser(user, member, channel, guild, roomID) {
* @param {string} roomID
*/
async function syncAllUsersInRoom(roomID) {
const mxids = select("sim_member", "mxid", {room_id: roomID}).pluck().all()
const users = from("sim_member").join("sim", "mxid")
.where({room_id: roomID}).and("and user_id not like '%-%' and user_id not like '%\\_%' escape '\\'").pluck("user_id").all()
const channelID = select("channel_room", "channel_id", {room_id: roomID}).pluck().get()
assert.ok(typeof channelID === "string")
@@ -234,10 +234,7 @@ async function syncAllUsersInRoom(roomID) {
/** @ts-ignore @type {DiscordTypes.APIGuild} */
const guild = discord.guilds.get(guildID)
for (const mxid of mxids) {
const userID = select("sim", "user_id", {mxid}).pluck().get()
assert.ok(typeof userID === "string")
for (const userID of users) {
/** @ts-ignore @type {Required<DiscordTypes.APIGuildMember>} */
const member = await discord.snow.guild.getGuildMember(guildID, userID)
/** @ts-ignore @type {Required<DiscordTypes.APIUser>} user */
+10 -7
View File
@@ -4,7 +4,7 @@ const Ty = require("../../types")
const DiscordTypes = require("discord-api-types/v10")
const passthrough = require("../../passthrough")
const {discord, sync, db, select} = passthrough
const {discord, sync, db, from, select} = passthrough
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
/** @type {import("../converters/emoji-to-key")} */
@@ -18,12 +18,15 @@ const converter = sync.require("../converters/remove-reaction")
* @param {DiscordTypes.GatewayMessageReactionRemoveDispatchData | DiscordTypes.GatewayMessageReactionRemoveEmojiDispatchData | DiscordTypes.GatewayMessageReactionRemoveAllDispatchData} data
*/
async function removeSomeReactions(data) {
const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get()
if (!roomID) return
const eventIDForMessage = select("event_message", "event_id", {message_id: data.message_id, reaction_part: 0}).pluck().get()
if (!eventIDForMessage) return
const row = select("channel_room", "room_id", {channel_id: data.channel_id}).get()
if (!row) return
const reactions = await api.getFullRelations(roomID, eventIDForMessage, "m.annotation")
const eventReactedTo = from("event_message").join("message_room", "message_id").join("historical_channel_room", "historical_room_index")
.where({message_id: data.message_id, reaction_part: 0}).select("event_id", "room_id").get()
if (!eventReactedTo) return
// Due to server restrictions, all relations (i.e. reactions) have to be in the same room as the original event.
const reactions = await api.getFullRelations(eventReactedTo.room_id, eventReactedTo.event_id, "m.annotation")
// Run the proper strategy and any strategy-specific database changes
const removals = await
@@ -33,7 +36,7 @@ async function removeSomeReactions(data) {
// Redact the events and delete individual stored events in the database
for (const removal of removals) {
await api.redactEvent(roomID, removal.eventID, removal.mxid)
await api.redactEvent(eventReactedTo.room_id, removal.eventID, removal.mxid)
if (removal.hash) db.prepare("DELETE FROM reaction WHERE hashed_event_id = ?").run(removal.hash)
}
}
+3 -1
View File
@@ -28,6 +28,8 @@ const dUtils = sync.require("../../discord/utils")
*/
async function sendMessage(message, channel, guild, row) {
const roomID = await createRoom.ensureRoom(message.channel_id)
const historicalRoomIndex = select("historical_channel_room", "historical_room_index", {room_id: roomID}).pluck().get()
assert(historicalRoomIndex)
let senderMxid = null
if (dUtils.isWebhookMessage(message)) {
@@ -52,7 +54,7 @@ async function sendMessage(message, channel, guild, row) {
const events = await messageToEvent.messageToEvent(message, guild, {}, {api, snow: discord.snow})
const eventIDs = []
if (events.length) {
db.prepare("INSERT OR IGNORE INTO message_channel (message_id, channel_id) VALUES (?, ?)").run(message.id, message.channel_id)
db.prepare("INSERT OR IGNORE INTO message_room (message_id, historical_room_index) VALUES (?, ?)").run(message.id, historicalRoomIndex)
if (senderMxid) api.sendTyping(roomID, false, senderMxid).catch(() => {})
}
for (const event of events) {