Backfill missed pins and pins from the past
This commit is contained in:
parent
ed7404ea19
commit
8987107685
9 changed files with 147 additions and 13 deletions
|
@ -1,22 +1,37 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
const passthrough = require("../../passthrough")
|
const passthrough = require("../../passthrough")
|
||||||
const {discord, sync} = passthrough
|
const {discord, sync, db} = passthrough
|
||||||
/** @type {import("../converters/pins-to-list")} */
|
/** @type {import("../converters/pins-to-list")} */
|
||||||
const pinsToList = sync.require("../converters/pins-to-list")
|
const pinsToList = sync.require("../converters/pins-to-list")
|
||||||
/** @type {import("../../matrix/api")} */
|
/** @type {import("../../matrix/api")} */
|
||||||
const api = sync.require("../../matrix/api")
|
const api = sync.require("../../matrix/api")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {string | null | undefined} T
|
||||||
|
* @param {T} timestamp
|
||||||
|
* @returns {T extends string ? number : null}
|
||||||
|
*/
|
||||||
|
function convertTimestamp(timestamp) {
|
||||||
|
// @ts-ignore
|
||||||
|
return typeof timestamp === "string" ? Math.floor(new Date(timestamp).getTime() / 1000) : null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} channelID
|
* @param {string} channelID
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
|
* @param {number?} convertedTimestamp
|
||||||
*/
|
*/
|
||||||
async function updatePins(channelID, roomID) {
|
async function updatePins(channelID, roomID, convertedTimestamp) {
|
||||||
const pins = await discord.snow.channel.getChannelPinnedMessages(channelID)
|
const pins = await discord.snow.channel.getChannelPinnedMessages(channelID)
|
||||||
const eventIDs = pinsToList.pinsToList(pins)
|
const eventIDs = pinsToList.pinsToList(pins)
|
||||||
|
if (pins.length === eventIDs.length || eventIDs.length) {
|
||||||
await api.sendState(roomID, "m.room.pinned_events", "", {
|
await api.sendState(roomID, "m.room.pinned_events", "", {
|
||||||
pinned: eventIDs
|
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
|
module.exports.updatePins = updatePins
|
||||||
|
|
|
@ -6,7 +6,7 @@ test("pins2list: converts known IDs, ignores unknown IDs", t => {
|
||||||
const result = pinsToList(data.pins.faked)
|
const result = pinsToList(data.pins.faked)
|
||||||
t.deepEqual(result, [
|
t.deepEqual(result, [
|
||||||
"$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4",
|
"$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4",
|
||||||
"$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuAno",
|
"$mtR8cJqM4fKno1bVsm8F4wUVqSntt2sq6jav1lyavuA",
|
||||||
"$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg"
|
"$X16nfVks1wsrhq4E9SSLiqrf2N8KD0erD0scZG7U5xg"
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
|
@ -43,6 +43,7 @@ const utils = {
|
||||||
}
|
}
|
||||||
if (listen === "full") {
|
if (listen === "full") {
|
||||||
eventDispatcher.checkMissedExpressions(message.d)
|
eventDispatcher.checkMissedExpressions(message.d)
|
||||||
|
eventDispatcher.checkMissedPins(client, message.d)
|
||||||
eventDispatcher.checkMissedMessages(client, message.d)
|
eventDispatcher.checkMissedMessages(client, message.d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +95,13 @@ const utils = {
|
||||||
client.channels.set(message.d.id, message.d)
|
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") {
|
} else if (message.t === "GUILD_DELETE") {
|
||||||
client.guilds.delete(message.d.id)
|
client.guilds.delete(message.d.id)
|
||||||
const channels = client.guildChannelMap.get(message.d.id)
|
const channels = client.guildChannelMap.get(message.d.id)
|
||||||
|
|
|
@ -25,9 +25,15 @@ const createSpace = sync.require("./actions/create-space")
|
||||||
const updatePins = sync.require("./actions/update-pins")
|
const updatePins = sync.require("./actions/update-pins")
|
||||||
/** @type {import("../matrix/api")}) */
|
/** @type {import("../matrix/api")}) */
|
||||||
const api = sync.require("../matrix/api")
|
const api = sync.require("../matrix/api")
|
||||||
|
/** @type {import("../discord/utils")} */
|
||||||
|
const utils = sync.require("../discord/utils")
|
||||||
/** @type {import("../discord/discord-command-handler")}) */
|
/** @type {import("../discord/discord-command-handler")}) */
|
||||||
const discordCommandHandler = sync.require("../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
|
let lastReportedEvent = 0
|
||||||
|
|
||||||
// Grab Discord events we care about for the bridge, check them, and pass them on
|
// 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)
|
const latestWasBridged = prepared.get(channel.last_message_id)
|
||||||
if (latestWasBridged) continue
|
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. */
|
/** 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`)
|
// 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
|
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.
|
* When logging back in, check if we missed any changes to emojis or stickers. Apply the changes if so.
|
||||||
* @param {DiscordTypes.GatewayGuildCreateDispatchData} guild
|
* @param {DiscordTypes.GatewayGuildCreateDispatchData} guild
|
||||||
|
@ -183,7 +225,8 @@ module.exports = {
|
||||||
async onChannelPinsUpdate(client, data) {
|
async onChannelPinsUpdate(client, data) {
|
||||||
const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get()
|
const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get()
|
||||||
if (!roomID) return // No target room to update pins in
|
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)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
5
db/migrations/0008-add-last-bridged-pin-timestamp.sql
Normal file
5
db/migrations/0008-add-last-bridged-pin-timestamp.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE channel_room ADD COLUMN last_bridged_pin_timestamp INTEGER;
|
||||||
|
|
||||||
|
COMMIT;
|
1
db/orm-defs.d.ts
vendored
1
db/orm-defs.d.ts
vendored
|
@ -6,6 +6,7 @@ export type Models = {
|
||||||
nick: string | null
|
nick: string | null
|
||||||
thread_parent: string | null
|
thread_parent: string | null
|
||||||
custom_avatar: string | null
|
custom_avatar: string | null
|
||||||
|
last_bridged_pin_timestamp: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
event_message: {
|
event_message: {
|
||||||
|
|
|
@ -34,7 +34,7 @@ function getPermissions(userRoles, guildRoles, userID, channelOverwrites) {
|
||||||
// Role deny
|
// Role deny
|
||||||
overwrite => userRoles.includes(overwrite.id) && (allowed &= ~BigInt(overwrite.deny)),
|
overwrite => userRoles.includes(overwrite.id) && (allowed &= ~BigInt(overwrite.deny)),
|
||||||
// Role allow
|
// Role allow
|
||||||
overwrite => userRoles.includes(overwrite.id) && (allowed |= ~BigInt(overwrite.allow)),
|
overwrite => userRoles.includes(overwrite.id) && (allowed |= BigInt(overwrite.allow)),
|
||||||
// User deny
|
// User deny
|
||||||
overwrite => overwrite.id === userID && (allowed &= ~BigInt(overwrite.deny)),
|
overwrite => overwrite.id === userID && (allowed &= ~BigInt(overwrite.deny)),
|
||||||
// User allow
|
// User allow
|
||||||
|
|
|
@ -18,6 +18,67 @@ test("discord utils: converts snowflake to timestamp", t => {
|
||||||
t.equal(utils.snowflakeToTimestampExact("86913608335773696"), 1440792219004)
|
t.equal(utils.snowflakeToTimestampExact("86913608335773696"), 1440792219004)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("discerd utils: converts timestamp to snowflake", t => {
|
test("discord utils: converts timestamp to snowflake", t => {
|
||||||
t.match(utils.timestampToSnowflakeInexact(1440792219004), /^869136083357.....$/)
|
t.match(utils.timestampToSnowflakeInexact(1440792219004), /^869136083357.....$/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("getPermissions: channel overwrite to allow role works", t => {
|
||||||
|
const guildRoles = [
|
||||||
|
{
|
||||||
|
version: 1695412489043,
|
||||||
|
unicode_emoji: null,
|
||||||
|
tags: {},
|
||||||
|
position: 0,
|
||||||
|
permissions: "559623605571137",
|
||||||
|
name: "@everyone",
|
||||||
|
mentionable: false,
|
||||||
|
managed: false,
|
||||||
|
id: "1154868424724463687",
|
||||||
|
icon: null,
|
||||||
|
hoist: false,
|
||||||
|
flags: 0,
|
||||||
|
color: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: 1695412604262,
|
||||||
|
unicode_emoji: null,
|
||||||
|
tags: { bot_id: "466378653216014359" },
|
||||||
|
position: 1,
|
||||||
|
permissions: "536995904",
|
||||||
|
name: "PluralKit",
|
||||||
|
mentionable: false,
|
||||||
|
managed: true,
|
||||||
|
id: "1154868908336099444",
|
||||||
|
icon: null,
|
||||||
|
hoist: false,
|
||||||
|
flags: 0,
|
||||||
|
color: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: 1698778936921,
|
||||||
|
unicode_emoji: null,
|
||||||
|
tags: {},
|
||||||
|
position: 1,
|
||||||
|
permissions: "536870912",
|
||||||
|
name: "web hookers",
|
||||||
|
mentionable: false,
|
||||||
|
managed: false,
|
||||||
|
id: "1168988246680801360",
|
||||||
|
icon: null,
|
||||||
|
hoist: false,
|
||||||
|
flags: 0,
|
||||||
|
color: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const userRoles = [ "1168988246680801360" ]
|
||||||
|
const userID = "684280192553844747"
|
||||||
|
const overwrites = [
|
||||||
|
{ type: 0, id: "1154868908336099444", deny: "0", allow: "1024" },
|
||||||
|
{ type: 0, id: "1154868424724463687", deny: "1024", allow: "0" },
|
||||||
|
{ type: 0, id: "1168988246680801360", deny: "0", allow: "1024" },
|
||||||
|
{ type: 1, id: "353373325575323648", deny: "0", allow: "1024" }
|
||||||
|
]
|
||||||
|
const permissions = utils.getPermissions(userRoles, guildRoles, userID, overwrites)
|
||||||
|
const want = BigInt(1 << 10 | 1 << 16)
|
||||||
|
t.equal((permissions & want), want)
|
||||||
|
})
|
||||||
|
|
1
stdin.js
1
stdin.js
|
@ -16,6 +16,7 @@ const api = sync.require("./matrix/api")
|
||||||
const file = sync.require("./matrix/file")
|
const file = sync.require("./matrix/file")
|
||||||
const sendEvent = sync.require("./m2d/actions/send-event")
|
const sendEvent = sync.require("./m2d/actions/send-event")
|
||||||
const eventDispatcher = sync.require("./d2m/event-dispatcher")
|
const eventDispatcher = sync.require("./d2m/event-dispatcher")
|
||||||
|
const updatePins = sync.require("./d2m/actions/update-pins")
|
||||||
const ks = sync.require("./matrix/kstate")
|
const ks = sync.require("./matrix/kstate")
|
||||||
const guildID = "112760669178241024"
|
const guildID = "112760669178241024"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue