Backfill missed pins and pins from the past

This commit is contained in:
Cadence Ember 2024-01-18 00:30:55 +13:00
parent ed7404ea19
commit 8987107685
9 changed files with 147 additions and 13 deletions

View file

@ -1,22 +1,37 @@
// @ts-check
const passthrough = require("../../passthrough")
const {discord, sync} = passthrough
const {discord, sync, db} = passthrough
/** @type {import("../converters/pins-to-list")} */
const pinsToList = sync.require("../converters/pins-to-list")
/** @type {import("../../matrix/api")} */
const api = sync.require("../../matrix/api")
/**
* @param {string} channelID
* @param {string} roomID
* @template {string | null | undefined} T
* @param {T} timestamp
* @returns {T extends string ? number : null}
*/
async function updatePins(channelID, roomID) {
const pins = await discord.snow.channel.getChannelPinnedMessages(channelID)
const eventIDs = pinsToList.pinsToList(pins)
await api.sendState(roomID, "m.room.pinned_events", "", {
pinned: eventIDs
})
function convertTimestamp(timestamp) {
// @ts-ignore
return typeof timestamp === "string" ? Math.floor(new Date(timestamp).getTime() / 1000) : null
}
/**
* @param {string} channelID
* @param {string} roomID
* @param {number?} convertedTimestamp
*/
async function updatePins(channelID, roomID, convertedTimestamp) {
const pins = await discord.snow.channel.getChannelPinnedMessages(channelID)
const eventIDs = pinsToList.pinsToList(pins)
if (pins.length === eventIDs.length || eventIDs.length) {
await api.sendState(roomID, "m.room.pinned_events", "", {
pinned: eventIDs
})
}
db.prepare("UPDATE channel_room SET last_bridged_pin_timestamp = ? WHERE channel_id = ?").run(convertedTimestamp || 0, channelID)
}
module.exports.convertTimestamp = convertTimestamp
module.exports.updatePins = updatePins

View file

@ -6,7 +6,7 @@ test("pins2list: converts known IDs, ignores unknown IDs", t => {
const result = pinsToList(data.pins.faked)
t.deepEqual(result, [
"$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4",
"$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuAno",
"$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA",
"$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg"
])
})

View file

@ -43,6 +43,7 @@ const utils = {
}
if (listen === "full") {
eventDispatcher.checkMissedExpressions(message.d)
eventDispatcher.checkMissedPins(client, message.d)
eventDispatcher.checkMissedMessages(client, message.d)
}
@ -94,6 +95,13 @@ const utils = {
client.channels.set(message.d.id, message.d)
} else if (message.t === "CHANNEL_PINS_UPDATE") {
const channel = client.channels.get(message.d.channel_id)
if (channel) {
channel["last_pin_timestamp"] = message.d.last_pin_timestamp
}
} else if (message.t === "GUILD_DELETE") {
client.guilds.delete(message.d.id)
const channels = client.guildChannelMap.get(message.d.id)

View file

@ -25,9 +25,15 @@ const createSpace = sync.require("./actions/create-space")
const updatePins = sync.require("./actions/update-pins")
/** @type {import("../matrix/api")}) */
const api = sync.require("../matrix/api")
/** @type {import("../discord/utils")} */
const utils = sync.require("../discord/utils")
/** @type {import("../discord/discord-command-handler")}) */
const discordCommandHandler = sync.require("../discord/discord-command-handler")
/** @type {any} */ // @ts-ignore bad types from semaphore
const Semaphore = require("@chriscdn/promise-semaphore")
const checkMissedPinsSema = new Semaphore()
let lastReportedEvent = 0
// Grab Discord events we care about for the bridge, check them, and pass them on
@ -103,6 +109,14 @@ module.exports = {
const latestWasBridged = prepared.get(channel.last_message_id)
if (latestWasBridged) continue
// Permissions check
const member = guild.members.find(m => m.user?.id === client.user.id)
if (!member) return
if (!("permission_overwrites" in channel)) continue
const permissions = utils.getPermissions(member.roles, guild.roles, client.user.id, channel.permission_overwrites)
const wants = BigInt(1 << 10) | BigInt(1 << 16) // VIEW_CHANNEL + READ_MESSAGE_HISTORY
if ((permissions & wants) !== wants) continue // We don't have permission to look back in this channel
/** More recent messages come first. */
// console.log(`[check missed messages] in ${channel.id} (${guild.name} / ${channel.name}) because its last message ${channel.last_message_id} is not in the database`)
let messages
@ -132,6 +146,34 @@ module.exports = {
}
},
/**
* When logging back in, check if the pins on Matrix-side are up to date. If they aren't, update all pins.
* Rather than query every room on Matrix-side, we cache the latest pinned message in the database and compare against that.
* @param {import("./discord-client")} client
* @param {DiscordTypes.GatewayGuildCreateDispatchData} guild
*/
async checkMissedPins(client, guild) {
if (guild.unavailable) return
const member = guild.members.find(m => m.user?.id === client.user.id)
if (!member) return
for (const channel of guild.channels) {
if (!("last_pin_timestamp" in channel) || !channel.last_pin_timestamp) continue // Only care about channels that have pins
if (!("permission_overwrites" in channel)) continue
const lastPin = updatePins.convertTimestamp(channel.last_pin_timestamp)
// Permissions check
const permissions = utils.getPermissions(member.roles, guild.roles, client.user.id, channel.permission_overwrites)
const wants = BigInt(1 << 10) | BigInt(1 << 16) // VIEW_CHANNEL + READ_MESSAGE_HISTORY
if ((permissions & wants) !== wants) continue // We don't have permission to look up the pins in this channel
const row = select("channel_room", ["room_id", "last_bridged_pin_timestamp"], {channel_id: channel.id}).get()
if (!row) continue // Only care about already bridged channels
if (row.last_bridged_pin_timestamp == null || lastPin > row.last_bridged_pin_timestamp) {
checkMissedPinsSema.request(() => updatePins.updatePins(channel.id, row.room_id, lastPin))
}
}
},
/**
* When logging back in, check if we missed any changes to emojis or stickers. Apply the changes if so.
* @param {DiscordTypes.GatewayGuildCreateDispatchData} guild
@ -183,7 +225,8 @@ module.exports = {
async onChannelPinsUpdate(client, data) {
const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get()
if (!roomID) return // No target room to update pins in
await updatePins.updatePins(data.channel_id, roomID)
const convertedTimestamp = updatePins.convertTimestamp(data.last_pin_timestamp)
await updatePins.updatePins(data.channel_id, roomID, convertedTimestamp)
},
/**