Compare commits

...

3 commits

Author SHA1 Message Date
dd63ac7d44 New error trace stringifier 2025-03-12 16:01:36 +13:00
22c569c701 Ignore not having access to read pins 2025-03-12 10:25:23 +13:00
3975550582 Fix retrying failed m->d edits
The mutated event was being used as the error payload instead of the
original event, so it forgot that it was an edit.
2025-03-12 09:41:30 +13:00
7 changed files with 95 additions and 28 deletions

View file

@ -93,7 +93,7 @@ Total transitive production dependencies: 137
### <font size="+2">🦕</font> ### <font size="+2">🦕</font>
* (31) better-sqlite3: SQLite3 is the best database, and this is the best library for it. * (31) better-sqlite3: SQLite is the best database, and this is the best library for it.
* (27) @cloudrac3r/pug: Language for dynamic web pages. This is my fork. (I released code that hadn't made it to npm, and removed the heavy pug-filters feature.) * (27) @cloudrac3r/pug: Language for dynamic web pages. This is my fork. (I released code that hadn't made it to npm, and removed the heavy pug-filters feature.)
* (16) stream-mime-type@1: This seems like the best option. Version 1 is used because version 2 is ESM-only. * (16) stream-mime-type@1: This seems like the best option. Version 1 is used because version 2 is ESM-only.
* (9) h3: Web server. OOYE needs this for the appservice listener, authmedia proxy, self-service, and more. * (9) h3: Web server. OOYE needs this for the appservice listener, authmedia proxy, self-service, and more.

View file

@ -25,7 +25,14 @@ function convertTimestamp(timestamp) {
* @param {number?} convertedTimestamp * @param {number?} convertedTimestamp
*/ */
async function updatePins(channelID, roomID, convertedTimestamp) { async function updatePins(channelID, roomID, convertedTimestamp) {
const discordPins = await discord.snow.channel.getChannelPinnedMessages(channelID) try {
var discordPins = await discord.snow.channel.getChannelPinnedMessages(channelID)
} catch (e) {
if (e.message === `{"message": "Missing Access", "code": 50001}`) {
return // Discord sends channel pins update events even for channels that the bot can't view/get pins in, just ignore it
}
throw e
}
const pinned = pinsToList.pinsToList(discordPins) const pinned = pinsToList.pinsToList(discordPins)
const kstate = await ks.roomToKState(roomID) const kstate = await ks.roomToKState(roomID)

View file

@ -35,6 +35,8 @@ const speedbump = sync.require("./actions/speedbump")
const retrigger = sync.require("./actions/retrigger") const retrigger = sync.require("./actions/retrigger")
/** @type {import("./actions/set-presence")} */ /** @type {import("./actions/set-presence")} */
const setPresence = sync.require("./actions/set-presence") const setPresence = sync.require("./actions/set-presence")
/** @type {import("../m2d/event-dispatcher")} */
const matrixEventDispatcher = sync.require("../m2d/event-dispatcher")
/** @type {any} */ // @ts-ignore bad types from semaphore /** @type {any} */ // @ts-ignore bad types from semaphore
const Semaphore = require("@chriscdn/promise-semaphore") const Semaphore = require("@chriscdn/promise-semaphore")
@ -66,22 +68,19 @@ module.exports = {
const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
if (!roomID) return if (!roomID) return
let stackLines = null
if (e.stack) {
stackLines = e.stack.split("\n")
let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/"))
if (cloudstormLine !== -1) {
stackLines = stackLines.slice(0, cloudstormLine - 2)
}
}
const builder = new mxUtils.MatrixStringBuilder() const builder = new mxUtils.MatrixStringBuilder()
builder.addLine("\u26a0 Bridged event from Discord not delivered", "\u26a0 <strong>Bridged event from Discord not delivered</strong>") builder.addLine("\u26a0 Bridged event from Discord not delivered", "\u26a0 <strong>Bridged event from Discord not delivered</strong>")
builder.addLine(`Gateway event: ${gatewayMessage.t}`) builder.addLine(`Gateway event: ${gatewayMessage.t}`)
builder.addLine(e.toString())
if (stackLines) { let errorIntroLine = e.toString()
builder.addLine(`Error trace:\n${stackLines.join("\n")}`, `<details><summary>Error trace</summary><pre>${stackLines.join("\n")}</pre></details>`) if (e.cause) {
errorIntroLine += ` (cause: ${e.cause})`
} }
builder.addLine(errorIntroLine)
const stack = matrixEventDispatcher.stringifyErrorStack(e)
builder.addLine(`Error trace:\n${stack}`, `<details><summary>Error trace</summary><pre>${stack}</pre></details>`)
builder.addLine("", `<details><summary>Original payload</summary><pre>${util.inspect(gatewayMessage.d, false, 4, false)}</pre></details>`) builder.addLine("", `<details><summary>Original payload</summary><pre>${util.inspect(gatewayMessage.d, false, 4, false)}</pre></details>`)
await api.sendEvent(roomID, "m.room.message", { await api.sendEvent(roomID, "m.room.message", {
...builder.get(), ...builder.get(),

View file

@ -546,7 +546,7 @@ async function eventToMessage(event, guild, di) {
if (!messageIDsToEdit.length) return if (!messageIDsToEdit.length) return
// Ok, it's an edit. // Ok, it's an edit.
event.content = event.content["m.new_content"] event = {...event, content: event.content["m.new_content"]}
// Is it editing a reply? We need special handling if it is. // Is it editing a reply? We need special handling if it is.
// Get the original event, then check if it was a reply // Get the original event, then check if it was a reply

View file

@ -28,6 +28,58 @@ const {reg} = require("../matrix/read-registration")
let lastReportedEvent = 0 let lastReportedEvent = 0
/**
* This function is adapted from Evan Kaufman's fantastic work.
* The original function and my adapted function are both MIT licensed.
* @url https://github.com/EvanK/npm-loggable-error/
* @param {number} [depth]
* @returns {string}
*/
function stringifyErrorStack(err, depth = 0) {
let collapsed = " ".repeat(depth);
if (!(err instanceof Error)) {
return collapsed + err
}
// add full stack trace if one exists, otherwise convert to string
let stackLines = ( err?.stack ?? `${err}` ).replace(/^/gm, " ".repeat(depth)).trim().split("\n")
let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/"))
if (cloudstormLine !== -1) {
stackLines = stackLines.slice(0, cloudstormLine - 2)
}
collapsed += stackLines.join("\n")
const props = Object.getOwnPropertyNames(err).filter(p => !["message", "stack"].includes(p))
// only break into object notation if we have addtl props to dump
if (props.length) {
const dedent = " ".repeat(depth);
const indent = " ".repeat(depth + 2);
collapsed += " {\n";
// loop and print each (indented) prop name
for (let property of props) {
collapsed += `${indent}[${property}]: `;
// if another error object, stringify it too
if (err[property] instanceof Error) {
collapsed += stringifyErrorStack(err[property], depth + 2).trimStart();
}
// otherwise stringify as JSON
else {
collapsed += JSON.stringify(err[property]);
}
collapsed += "\n";
}
collapsed += `${dedent}}\n`;
}
return collapsed;
}
function guard(type, fn) { function guard(type, fn) {
return async function(event, ...args) { return async function(event, ...args) {
try { try {
@ -39,7 +91,12 @@ function guard(type, fn) {
if (Date.now() - lastReportedEvent < 5000) return if (Date.now() - lastReportedEvent < 5000) return
lastReportedEvent = Date.now() lastReportedEvent = Date.now()
const cloudflareErrorTitle = e.toString().match(/<!DOCTYPE html>.*?<title>discord\.com \| ([^<]*)<\/title>/s)?.[1] let errorIntroLine = e.toString()
if (e.cause) {
errorIntroLine += ` (cause: ${e.cause})`
}
const cloudflareErrorTitle = errorIntroLine.match(/<!DOCTYPE html>.*?<title>discord\.com \| ([^<]*)<\/title>/s)?.[1]
if (cloudflareErrorTitle) { if (cloudflareErrorTitle) {
return api.sendEvent(event.room_id, "m.room.message", { return api.sendEvent(event.room_id, "m.room.message", {
msgtype: "m.text", msgtype: "m.text",
@ -53,16 +110,16 @@ function guard(type, fn) {
}) })
} }
let stackLines = e.stack.split("\n") const stack = stringifyErrorStack(e)
api.sendEvent(event.room_id, "m.room.message", { api.sendEvent(event.room_id, "m.room.message", {
msgtype: "m.text", msgtype: "m.text",
body: "\u26a0 Matrix event not delivered to Discord. See formatted content for full details.", body: "\u26a0 Matrix event not delivered to Discord. See formatted content for full details.",
format: "org.matrix.custom.html", format: "org.matrix.custom.html",
formatted_body: "\u26a0 <strong>Matrix event not delivered to Discord</strong>" formatted_body: "\u26a0 <strong>Matrix event not delivered to Discord</strong>"
+ `<br>Event type: ${type}` + `<br>Event type: ${type}`
+ `<br>${e.toString()}` + `<br>${errorIntroLine}`
+ `<br><details><summary>Error trace</summary>` + `<br><details><summary>Error trace</summary>`
+ `<pre>${stackLines.join("\n")}</pre></details>` + `<pre>${stack}</pre></details>`
+ `<details><summary>Original payload</summary>` + `<details><summary>Original payload</summary>`
+ `<pre>${util.inspect(event, false, 4, false)}</pre></details>`, + `<pre>${util.inspect(event, false, 4, false)}</pre></details>`,
"moe.cadence.ooye.error": { "moe.cadence.ooye.error": {
@ -297,3 +354,5 @@ async event => {
db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(newPower[mxid] || 0, event.room_id, mxid) db.prepare("UPDATE member_cache SET power_level = ? WHERE room_id = ? AND mxid = ?").run(newPower[mxid] || 0, event.room_id, mxid)
} }
})) }))
module.exports.stringifyErrorStack = stringifyErrorStack

View file

@ -29,7 +29,7 @@ reg.ooye.bridge_origin = "https://bridge.example.org"
const sync = new HeatSync({watchFS: false}) const sync = new HeatSync({watchFS: false})
const discord = { const discord = {
// @ts-ignore - only ignore guilds, because my data dump is missing random properties // @ts-ignore - ignore guilds, because my data dump is missing random properties
guilds: new Map([ guilds: new Map([
[data.guild.general.id, data.guild.general], [data.guild.general.id, data.guild.general],
[data.guild.fna.id, data.guild.fna], [data.guild.fna.id, data.guild.fna],
@ -43,6 +43,7 @@ const discord = {
application: { application: {
id: "684280192553844747" id: "684280192553844747"
}, },
// @ts-ignore - ignore channels, because my data dump is missing random properties
channels: new Map([ channels: new Map([
[data.channel.general.id, data.channel.general], [data.channel.general.id, data.channel.general],
[data.channel.updates.id, data.channel.updates], [data.channel.updates.id, data.channel.updates],
@ -127,6 +128,13 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("./addbot.test") require("./addbot.test")
require("../src/db/orm.test") require("../src/db/orm.test")
require("../src/web/server.test")
require("../src/web/routes/download-discord.test")
require("../src/web/routes/download-matrix.test")
require("../src/web/routes/guild.test")
require("../src/web/routes/guild-settings.test")
require("../src/web/routes/link.test")
require("../src/web/routes/log-in-with-matrix.test")
require("../src/discord/utils.test") require("../src/discord/utils.test")
require("../src/matrix/kstate.test") require("../src/matrix/kstate.test")
require("../src/matrix/api.test") require("../src/matrix/api.test")
@ -147,6 +155,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../src/d2m/converters/remove-reaction.test") require("../src/d2m/converters/remove-reaction.test")
require("../src/d2m/converters/thread-to-announcement.test") require("../src/d2m/converters/thread-to-announcement.test")
require("../src/d2m/converters/user-to-mxid.test") require("../src/d2m/converters/user-to-mxid.test")
require("../src/m2d/event-dispatcher.test")
require("../src/m2d/converters/diff-pins.test") require("../src/m2d/converters/diff-pins.test")
require("../src/m2d/converters/event-to-message.test") require("../src/m2d/converters/event-to-message.test")
require("../src/m2d/converters/emoji.test") require("../src/m2d/converters/emoji.test")
@ -157,11 +166,4 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
require("../src/discord/interactions/permissions.test") require("../src/discord/interactions/permissions.test")
require("../src/discord/interactions/privacy.test") require("../src/discord/interactions/privacy.test")
require("../src/discord/interactions/reactions.test") require("../src/discord/interactions/reactions.test")
require("../src/web/server.test")
require("../src/web/routes/download-discord.test")
require("../src/web/routes/download-matrix.test")
require("../src/web/routes/guild.test")
require("../src/web/routes/guild-settings.test")
require("../src/web/routes/link.test")
require("../src/web/routes/log-in-with-matrix.test")
})() })()

View file

@ -96,7 +96,7 @@ class Router {
const router = new Router() const router = new Router()
passthrough.as = {router} passthrough.as = {router, on() {}}
module.exports.router = router module.exports.router = router
module.exports.test = test module.exports.test = test