Compare commits

...

5 commits

Author SHA1 Message Date
Bea
fa9cadadcc
Merge branch 'feature/backfill-create-rooms' into testing 2026-03-02 18:36:01 +00:00
Bea
3f91399a9e
Merge branch 'bugfix/handle-expired-events' into testing 2026-03-02 18:35:53 +00:00
Bea
83e67fbdc0
feat(backfill): auto-create bridged rooms if they don't exist 2026-03-02 18:31:16 +00:00
Bea
6553946b88
test: fix null registration object and validation errors during setup
Running the test suite without a local `registration.yaml` previously
caused a TypeError because the `reg` export defaulted to null. This
injects a base template using `getTemplateRegistration` before applying
test-specific overrides.
2026-03-02 02:39:23 +00:00
Bea
ebc4dc1814
fix(d2m): handle expired Discord event and invite links
Wrap the scheduled event lookup in a try-catch block to prevent the
bridge from crashing when it encounters expired or invalid links.
When a 404 or Discord error code 10006 is caught, the bot now
generates a fallback "m.notice" event to inform Matrix users that
the event link has expired.
2026-03-02 02:39:23 +00:00
4 changed files with 65 additions and 8 deletions

View file

@ -38,12 +38,8 @@ passthrough.select = orm.select
/** @type {import("../src/d2m/event-dispatcher")}*/
const eventDispatcher = sync.require("../src/d2m/event-dispatcher")
const roomID = passthrough.select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
if (!roomID) {
console.error("Please choose a channel that's already bridged.")
process.exit(1)
}
/** @type {import("../src/d2m/actions/create-room")} */
const createRoom = sync.require("../src/d2m/actions/create-room")
;(async () => {
await discord.cloud.connect()
@ -60,6 +56,18 @@ async function event(event) {
if (!channel) return
const guild_id = event.d.id
let roomID = passthrough.select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
if (!roomID) {
console.log(`Channel #${channel.name} is not bridged yet. Attempting to auto-create...`)
try {
roomID = await createRoom.syncRoom(channelID)
console.log(`Successfully bridged to new room: ${roomID}`)
} catch (e) {
console.error(`Failed to auto-create room: ${e.message}`)
process.exit(1)
}
}
let last = backfill.prepare("SELECT cast(max(message_id) as TEXT) FROM backfill WHERE channel_id = ?").pluck().get(channelID) || "0"
console.log(`OK, processing messages for #${channel.name}, continuing from ${last}`)

View file

@ -769,7 +769,21 @@ async function messageToEvent(message, guild, options = {}, di) {
// Then scheduled events
if (message.content && di?.snow) {
for (const match of [...message.content.matchAll(/discord\.gg\/([A-Za-z0-9]+)\?event=([0-9]{18,})/g)]) { // snowflake has minimum 18 because the events feature is at least that old
const invite = await di.snow.invite.getInvite(match[1], {guild_scheduled_event_id: match[2]})
let invite
try {
invite = await di.snow.invite.getInvite(match[1], {guild_scheduled_event_id: match[2]})
} catch (e) {
// Skip expired events and invites
if (e.code === 10006 || e.httpStatus === 404) {
console.warn(`[Backfill] Skipped expired scheduled event: ${match[0]}`)
const fallbackBody = `[Expired Scheduled Event: ${match[0]}]`
const fallbackHtml = `<blockquote>Expired Scheduled Event: <a href="https://${match[0]}">${match[0]}</a></blockquote>`
await addTextEvent(fallbackBody, fallbackHtml, "m.notice")
continue
}
throw e
}
const event = invite.guild_scheduled_event
if (!event) continue // the event ID provided was not valid

View file

@ -1538,6 +1538,38 @@ test("message2event: vc invite event renders embed with room link", async t => {
])
})
test("message2event: expired event invite renders fallback notice", async t => {
const events = await messageToEvent({content: "https://discord.gg/placeholder?event=1381190945646710824"}, {}, {}, {
snow: {
invite: {
getInvite: async () => {
const error = new Error("Unknown Invite")
error.code = 10006
throw error
}
}
}
})
t.deepEqual(events, [
{
$type: "m.room.message",
body: "https://discord.gg/placeholder?event=1381190945646710824",
format: "org.matrix.custom.html",
formatted_body: "<a href=\"https://discord.gg/placeholder?event=1381190945646710824\">https://discord.gg/placeholder?event=1381190945646710824</a>",
"m.mentions": {},
msgtype: "m.text",
},
{
$type: "m.room.message",
msgtype: "m.notice",
body: "[Expired Scheduled Event: discord.gg/placeholder?event=1381190945646710824]",
format: "org.matrix.custom.html",
formatted_body: "<blockquote>Expired Scheduled Event: <a href=\"https://discord.gg/placeholder?event=1381190945646710824\">discord.gg/placeholder?event=1381190945646710824</a></blockquote>",
"m.mentions": {}
}
])
})
test("message2event: channel links are converted even inside lists (parser post-processer descends into list items)", async t => {
let called = 0
const events = await messageToEvent({

View file

@ -13,7 +13,10 @@ const {green} = require("ansi-colors")
const passthrough = require("../src/passthrough")
const db = new sqlite(":memory:")
const {reg} = require("../src/matrix/read-registration")
const readReg = require("../src/matrix/read-registration")
readReg.reg = readReg.getTemplateRegistration("cadence.moe")
const {reg} = readReg
reg.url = "http://localhost:6693"
reg.ooye.discord_token = "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby"
reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
reg.ooye.server_name = "cadence.moe"