Compare commits
4 commits
8ec7b3f67a
...
f92d280494
| Author | SHA1 | Date | |
|---|---|---|---|
| f92d280494 | |||
| 414da4caf4 | |||
| 8779b8b1b0 | |||
| 51e7d8479b |
17 changed files with 402 additions and 183 deletions
|
|
@ -113,6 +113,7 @@ Total transitive production dependencies: 144
|
||||||
* (0) ansi-colors: Helps with interactive prompting for the initial setup, and it's already pulled in by enquirer.
|
* (0) ansi-colors: Helps with interactive prompting for the initial setup, and it's already pulled in by enquirer.
|
||||||
* (1) chunk-text: It does what I want.
|
* (1) chunk-text: It does what I want.
|
||||||
* (0) cloudstorm: Discord gateway library with bring-your-own-caching that I trust.
|
* (0) cloudstorm: Discord gateway library with bring-your-own-caching that I trust.
|
||||||
|
* (0) denque: Much faster than using arrays as a queue.
|
||||||
* (0) discord-api-types: Bitfields needed at runtime and types needed for development.
|
* (0) discord-api-types: Bitfields needed at runtime and types needed for development.
|
||||||
* (0) domino: DOM implementation that's already pulled in by turndown.
|
* (0) domino: DOM implementation that's already pulled in by turndown.
|
||||||
* (2) enquirer: Interactive prompting for the initial setup rather than forcing users to edit YAML non-interactively.
|
* (2) enquirer: Interactive prompting for the initial setup rather than forcing users to edit YAML non-interactively.
|
||||||
|
|
|
||||||
38
package-lock.json
generated
38
package-lock.json
generated
|
|
@ -24,7 +24,8 @@
|
||||||
"ansi-colors": "^4.1.3",
|
"ansi-colors": "^4.1.3",
|
||||||
"better-sqlite3": "^12.2.0",
|
"better-sqlite3": "^12.2.0",
|
||||||
"chunk-text": "^2.0.1",
|
"chunk-text": "^2.0.1",
|
||||||
"cloudstorm": "^0.17.1",
|
"cloudstorm": "^0.19.0",
|
||||||
|
"denque": "^2.1.0",
|
||||||
"discord-api-types": "^0.38.38",
|
"discord-api-types": "^0.38.38",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"prettier-bytes": "^1.0.4",
|
"prettier-bytes": "^1.0.4",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"snowtransfer": "^0.17.5",
|
"snowtransfer": "^0.19.0",
|
||||||
"try-to-catch": "^4.0.5",
|
"try-to-catch": "^4.0.5",
|
||||||
"uqr": "^0.1.2",
|
"uqr": "^0.1.2",
|
||||||
"xxhash-wasm": "^1.0.2",
|
"xxhash-wasm": "^1.0.2",
|
||||||
|
|
@ -1316,13 +1317,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cloudstorm": {
|
"node_modules/cloudstorm": {
|
||||||
"version": "0.17.1",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.19.0.tgz",
|
||||||
"integrity": "sha512-LYUwzHagRYRd93XocOqi+HCHdzPYI9cW7Yf7pYqinxgG+Qka1OiqBKWTCcLiEuiqXaOV30kr8c6aZ/c1QcDP4Q==",
|
"integrity": "sha512-mjuP5o2nS/CgSljajh08KjN6jYMfNBBttSPHc1YUN74f7OkmhUhv8QE8JFDXomxCfdnLNJcqBQtjRLrs8nkfVQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord-api-types": "^0.38.47",
|
"discord-api-types": "^0.38.49",
|
||||||
"snowtransfer": "^0.17.5"
|
"snowtransfer": "^0.19.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.0.0"
|
"node": ">=22.0.0"
|
||||||
|
|
@ -1457,6 +1458,15 @@
|
||||||
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
|
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/destr": {
|
"node_modules/destr": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||||
|
|
@ -1473,9 +1483,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/discord-api-types": {
|
"node_modules/discord-api-types": {
|
||||||
"version": "0.38.47",
|
"version": "0.38.49",
|
||||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.47.tgz",
|
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.49.tgz",
|
||||||
"integrity": "sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA==",
|
"integrity": "sha512-XnqcWmnFZFAE8ZM8SHAw9DIV8D3Or00rMQ8iQLotrEA2PmXhl+ykaf6L6q4l474hrSUH1JaYcv+iOMRWp2p6Tg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"scripts/actions/documentation"
|
"scripts/actions/documentation"
|
||||||
|
|
@ -2579,12 +2589,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/snowtransfer": {
|
"node_modules/snowtransfer": {
|
||||||
"version": "0.17.7",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.17.7.tgz",
|
"resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.19.0.tgz",
|
||||||
"integrity": "sha512-scbOjYezo1Ycfk21atCEkeXIISTT7R7JTHCdiZ/7m7k4XbSb6o5q8Mu2fev5IqFpNyqIVjA0d/MZQ+eP/gtwfg==",
|
"integrity": "sha512-Vebj4FCdpUsEKAzqC1p50ol5o09CLnrffvBCpTv0FNFefvXPXwogxR8kr4zJApIRt7oWQVZZVkxAq4XnGlICjQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord-api-types": "^0.38.47"
|
"discord-api-types": "^0.38.49"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.0.0"
|
"node": ">=22.0.0"
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@
|
||||||
"ansi-colors": "^4.1.3",
|
"ansi-colors": "^4.1.3",
|
||||||
"better-sqlite3": "^12.2.0",
|
"better-sqlite3": "^12.2.0",
|
||||||
"chunk-text": "^2.0.1",
|
"chunk-text": "^2.0.1",
|
||||||
"cloudstorm": "^0.17.1",
|
"cloudstorm": "^0.19.0",
|
||||||
|
"denque": "^2.1.0",
|
||||||
"discord-api-types": "^0.38.38",
|
"discord-api-types": "^0.38.38",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
|
|
@ -46,7 +47,7 @@
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"prettier-bytes": "^1.0.4",
|
"prettier-bytes": "^1.0.4",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"snowtransfer": "^0.17.5",
|
"snowtransfer": "^0.19.0",
|
||||||
"try-to-catch": "^4.0.5",
|
"try-to-catch": "^4.0.5",
|
||||||
"uqr": "^0.1.2",
|
"uqr": "^0.1.2",
|
||||||
"xxhash-wasm": "^1.0.2",
|
"xxhash-wasm": "^1.0.2",
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,7 @@ function defineEchoHandler() {
|
||||||
console.log("✅ Emojis are ready...")
|
console.log("✅ Emojis are ready...")
|
||||||
|
|
||||||
// set profile data on discord...
|
// set profile data on discord...
|
||||||
await discord.snow.user.updateSelf({avatar: "data:image/png;base64," + avatarBuffer.toString("base64")})
|
await discord.snow.user.editSelf({avatar: "data:image/png;base64," + avatarBuffer.toString("base64")})
|
||||||
console.log("✅ Discord profile updated...")
|
console.log("✅ Discord profile updated...")
|
||||||
|
|
||||||
// set profile data on homeserver...
|
// set profile data on homeserver...
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ const {reg} = require("../../matrix/read-registration")
|
||||||
|
|
||||||
const passthrough = require("../../passthrough")
|
const passthrough = require("../../passthrough")
|
||||||
const {discord, sync, db, select} = passthrough
|
const {discord, sync, db, select} = passthrough
|
||||||
|
/** @type {import("../../matrix/mreq")} */
|
||||||
|
const mreq = sync.require("../../matrix/mreq")
|
||||||
/** @type {import("../../matrix/api")} */
|
/** @type {import("../../matrix/api")} */
|
||||||
const api = sync.require("../../matrix/api")
|
const api = sync.require("../../matrix/api")
|
||||||
/** @type {import("../../matrix/file")} */
|
/** @type {import("../../matrix/file")} */
|
||||||
|
|
@ -237,8 +239,12 @@ async function syncSpaceExpressions(data, checkBeforeSync) {
|
||||||
try {
|
try {
|
||||||
existing = await api.getStateEvent(spaceID, "im.ponies.room_emotes", eventKey)
|
existing = await api.getStateEvent(spaceID, "im.ponies.room_emotes", eventKey)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// State event not found. This space doesn't have any existing emojis. We create a dummy empty event for comparison's sake.
|
if (e instanceof mreq.MatrixServerError && e.httpStatus < 400) {
|
||||||
existing = fn([], guild)
|
// State event not found. This space doesn't have any existing emojis. We create a dummy empty event for comparison's sake.
|
||||||
|
existing = fn([], guild)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isDeepStrictEqual(existing, content)) return
|
if (isDeepStrictEqual(existing, content)) return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ const {sync} = passthrough
|
||||||
/** @type {import("./discord-packets")} */
|
/** @type {import("./discord-packets")} */
|
||||||
const discordPackets = sync.require("./discord-packets")
|
const discordPackets = sync.require("./discord-packets")
|
||||||
|
|
||||||
|
const CONNECTION_DEBUG = false
|
||||||
|
|
||||||
class DiscordClient {
|
class DiscordClient {
|
||||||
/**
|
/**
|
||||||
* @param {string} discordToken
|
* @param {string} discordToken
|
||||||
|
|
@ -59,15 +61,17 @@ class DiscordClient {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addEventLogger = (eventName, logName) => {
|
if (CONNECTION_DEBUG) {
|
||||||
this.cloud.on(eventName, (...args) => {
|
const addEventLogger = (eventName, logName) => {
|
||||||
const d = new Date().toISOString().slice(0, 19)
|
this.cloud.on(eventName, (...args) => {
|
||||||
console.error(`[${d} Client ${logName}]`, ...args)
|
const d = new Date().toISOString().slice(0, 19)
|
||||||
})
|
console.error(`[${d} Client ${logName}]`, ...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addEventLogger("error", "Error")
|
||||||
|
addEventLogger("disconnected", "Disconnected")
|
||||||
|
addEventLogger("ready", "Ready")
|
||||||
}
|
}
|
||||||
addEventLogger("error", "Error")
|
|
||||||
addEventLogger("disconnected", "Disconnected")
|
|
||||||
addEventLogger("ready", "Ready")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,173 +1,222 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
// Discord library internals type beat
|
const assert = require("assert")
|
||||||
|
const {scheduler} = require("timers/promises")
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
|
||||||
const passthrough = require("../passthrough")
|
const passthrough = require("../passthrough")
|
||||||
const {sync, db} = passthrough
|
const {sync} = passthrough
|
||||||
|
|
||||||
const utils = {
|
/** @type {import("../matrix/homeserver-status")} */
|
||||||
/**
|
const homeserverStatus = sync.require("../matrix/homeserver-status")
|
||||||
* @param {import("./discord-client")} client
|
|
||||||
* @param {import("cloudstorm").IGatewayMessage} message
|
|
||||||
* @param {string} listen "full", "half", "no" - whether to set up the event listeners for OOYE to operate
|
|
||||||
*/
|
|
||||||
async onPacket(client, message, listen) {
|
|
||||||
// requiring this later so that the client is already constructed by the time event-dispatcher is loaded
|
|
||||||
/** @type {typeof import("./event-dispatcher")} */
|
|
||||||
const eventDispatcher = sync.require("./event-dispatcher")
|
|
||||||
/** @type {import("../discord/register-interactions")} */
|
|
||||||
const interactions = sync.require("../discord/register-interactions")
|
|
||||||
|
|
||||||
// Client internals, keep track of the state we need
|
let checkedHomeserver = false
|
||||||
if (message.t === "READY") {
|
|
||||||
if (client.ready) return
|
|
||||||
client.ready = true
|
|
||||||
client.user = message.d.user
|
|
||||||
client.application = message.d.application
|
|
||||||
console.log(`Discord logged in as ${client.user.username}#${client.user.discriminator} (${client.user.id})`)
|
|
||||||
interactions.registerInteractions()
|
|
||||||
|
|
||||||
} else if (message.t === "GUILD_CREATE") {
|
/**
|
||||||
message.d.members = message.d.members.filter(m => m.user.id === client.user.id) // only keep the bot's own member - it's needed to determine private channels on web
|
* @param {import("./discord-client")} client
|
||||||
client.guilds.set(message.d.id, message.d)
|
* @param {import("cloudstorm").IGatewayMessage} message
|
||||||
const arr = []
|
* @param {string} listen "full", "half", "no" - whether to set up the event listeners for OOYE to operate
|
||||||
client.guildChannelMap.set(message.d.id, arr)
|
*/
|
||||||
for (const channel of message.d.channels || []) {
|
async function onPacket(client, message, listen) {
|
||||||
// @ts-ignore
|
// requiring this later so that the client is already constructed by the time event-dispatcher is loaded
|
||||||
channel.guild_id = message.d.id
|
/** @type {typeof import("./event-dispatcher")} */
|
||||||
arr.push(channel.id)
|
const eventDispatcher = sync.require("./event-dispatcher")
|
||||||
client.channels.set(channel.id, channel)
|
/** @type {import("../discord/register-interactions")} */
|
||||||
}
|
const interactions = sync.require("../discord/register-interactions")
|
||||||
for (const thread of message.d.threads || []) {
|
|
||||||
// @ts-ignore
|
|
||||||
thread.guild_id = message.d.id
|
|
||||||
arr.push(thread.id)
|
|
||||||
client.channels.set(thread.id, thread)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listen === "full") {
|
// Client internals, keep track of the state we need
|
||||||
try {
|
if (message.t === "READY") {
|
||||||
await eventDispatcher.checkMissedExpressions(message.d)
|
if (client.ready) return
|
||||||
await eventDispatcher.checkMissedMessages(client, message.d)
|
client.ready = true
|
||||||
await eventDispatcher.checkMissedPins(client, message.d)
|
client.user = message.d.user
|
||||||
await eventDispatcher.checkMissedLeaves(client, message.d)
|
client.application = message.d.application
|
||||||
} catch (e) {
|
console.log(`Discord logged in as ${client.user.username}#${client.user.discriminator} (${client.user.id})`)
|
||||||
console.error("Failed to sync missed events. To retry, please fix this error and restart OOYE:")
|
interactions.registerInteractions()
|
||||||
console.error(e)
|
|
||||||
|
} else if (message.t === "GUILD_CREATE") {
|
||||||
|
message.d.members = message.d.members.filter(m => m.user.id === client.user.id) // only keep the bot's own member - it's needed to determine private channels on web
|
||||||
|
client.guilds.set(message.d.id, message.d)
|
||||||
|
const arr = []
|
||||||
|
client.guildChannelMap.set(message.d.id, arr)
|
||||||
|
for (const channel of message.d.channels || []) {
|
||||||
|
// @ts-ignore
|
||||||
|
channel.guild_id = message.d.id
|
||||||
|
arr.push(channel.id)
|
||||||
|
client.channels.set(channel.id, channel)
|
||||||
|
}
|
||||||
|
for (const thread of message.d.threads || []) {
|
||||||
|
// @ts-ignore
|
||||||
|
thread.guild_id = message.d.id
|
||||||
|
arr.push(thread.id)
|
||||||
|
client.channels.set(thread.id, thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen === "full") {
|
||||||
|
try {
|
||||||
|
/*
|
||||||
|
Info about guilds is populated one guild at a time.
|
||||||
|
For m->d bridging to work, the guild needs to be populated, so we need to have GUILD_CREATE for the guild.
|
||||||
|
If we ping the homeserver, it will send us any pending events, so we need to wait for all GUILD_CREATES before we ping.
|
||||||
|
We must attempt a ping because we don't want to try sending missed d->m messages to an offline homeserver.
|
||||||
|
This delay can be removed if ONE of the following is done:
|
||||||
|
1. m->d can queue incoming events until their guild exists in memory
|
||||||
|
2. d->m missed messages can have their errors handled and added to queue, rather than pinging first
|
||||||
|
*/
|
||||||
|
let isMainCharacter = false
|
||||||
|
if (!checkedHomeserver) {
|
||||||
|
checkedHomeserver = true
|
||||||
|
isMainCharacter = true
|
||||||
|
console.log("Warming up guilds~")
|
||||||
}
|
}
|
||||||
}
|
await scheduler.wait(5000)
|
||||||
|
if (isMainCharacter) {
|
||||||
} else if (message.t === "GUILD_UPDATE") {
|
checkedHomeserver = true
|
||||||
const guild = client.guilds.get(message.d.id)
|
process.stdout.write("Connecting to homeserver... ")
|
||||||
if (guild) {
|
await homeserverStatus.homeserverStatus.waitForOnline(true)
|
||||||
for (const prop of Object.keys(message.d)) {
|
console.log("ok.\nReplaying past events. Welcome to Out Of Your Element.")
|
||||||
if (!["channels", "threads"].includes(prop)) {
|
|
||||||
guild[prop] = message.d[prop]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (message.t === "GUILD_EMOJIS_UPDATE") {
|
|
||||||
const guild = client.guilds.get(message.d.guild_id)
|
|
||||||
if (guild) {
|
|
||||||
guild.emojis = message.d.emojis
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (message.t === "GUILD_STICKERS_UPDATE") {
|
|
||||||
const guild = client.guilds.get(message.d.guild_id)
|
|
||||||
if (guild) {
|
|
||||||
guild.stickers = message.d.stickers
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (message.t === "GUILD_ROLE_CREATE" || message.t === "GUILD_ROLE_UPDATE" || message.t === "GUILD_ROLE_DELETE") {
|
|
||||||
const guild = client.guilds.get(message.d.guild_id)
|
|
||||||
/** Delete this in case of UPDATE or DELETE */
|
|
||||||
const targetID = "role_id" in message.d ? message.d.role_id : message.d.role.id
|
|
||||||
/** Add this in case of CREATE or UPDATE */
|
|
||||||
const newRoles = []
|
|
||||||
if ("role" in message.d) newRoles.push(message.d.role)
|
|
||||||
if (guild) {
|
|
||||||
const targetIndex = guild.roles.findIndex(r => r.id === targetID)
|
|
||||||
if (targetIndex !== -1) {
|
|
||||||
// Role already exists. Delete it and maybe replace it.
|
|
||||||
guild.roles.splice(targetIndex, 1, ...newRoles)
|
|
||||||
} else {
|
} else {
|
||||||
// Role doesn't already exist.
|
await homeserverStatus.homeserverStatus.waitForOnline(false)
|
||||||
guild.roles.push(...newRoles)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await eventDispatcher.checkMissedExpressions(message.d)
|
||||||
|
await eventDispatcher.checkMissedMessages(client, message.d)
|
||||||
|
await eventDispatcher.checkMissedPins(client, message.d)
|
||||||
|
await eventDispatcher.checkMissedLeaves(client, message.d)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to sync missed events. To retry, please fix this error and restart OOYE:")
|
||||||
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (message.t === "GUILD_MEMBER_UPDATE") {
|
} else if (message.t === "GUILD_UPDATE") {
|
||||||
const guild = client.guilds.get(message.d.guild_id)
|
const guild = client.guilds.get(message.d.id)
|
||||||
const member = guild?.members.find(m => m.user.id === message.d.user.id)
|
if (guild) {
|
||||||
if (member) { // only update existing members (i.e. the bot's own member) - don't want to inflate the cache with new irrelevant ones
|
for (const prop of Object.keys(message.d)) {
|
||||||
Object.assign(member, message.d)
|
if (!["channels", "threads"].includes(prop)) {
|
||||||
}
|
guild[prop] = message.d[prop]
|
||||||
|
|
||||||
} else if (message.t === "THREAD_CREATE") {
|
|
||||||
client.channels.set(message.d.id, message.d)
|
|
||||||
if (message.d["guild_id"]) {
|
|
||||||
const channels = client.guildChannelMap.get(message.d["guild_id"])
|
|
||||||
if (channels && !channels.includes(message.d.id)) channels.push(message.d.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (message.t === "CHANNEL_UPDATE" || message.t === "THREAD_UPDATE") {
|
|
||||||
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)
|
|
||||||
if (channels) {
|
|
||||||
for (const id of channels) client.channels.delete(id)
|
|
||||||
}
|
|
||||||
client.guildChannelMap.delete(message.d.id)
|
|
||||||
|
|
||||||
|
|
||||||
} else if (message.t === "CHANNEL_CREATE") {
|
|
||||||
client.channels.set(message.d.id, message.d)
|
|
||||||
if (message.d["guild_id"]) { // obj[prop] notation can be used to access a property without typescript complaining that it doesn't exist on all values something can have
|
|
||||||
const channels = client.guildChannelMap.get(message.d["guild_id"])
|
|
||||||
if (channels && !channels.includes(message.d.id)) channels.push(message.d.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (message.t === "CHANNEL_DELETE") {
|
|
||||||
client.channels.delete(message.d.id)
|
|
||||||
if (message.d["guild_id"]) {
|
|
||||||
const channels = client.guildChannelMap.get(message.d["guild_id"])
|
|
||||||
if (channels) {
|
|
||||||
const previous = channels.indexOf(message.d.id)
|
|
||||||
if (previous !== -1) channels.splice(previous, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event dispatcher for OOYE bridge operations
|
} else if (message.t === "GUILD_EMOJIS_UPDATE") {
|
||||||
if (listen === "full" && message.t) {
|
const guild = client.guilds.get(message.d.guild_id)
|
||||||
try {
|
if (guild) {
|
||||||
if (message.t === "MESSAGE_REACTION_REMOVE" || message.t === "MESSAGE_REACTION_REMOVE_EMOJI" || message.t === "MESSAGE_REACTION_REMOVE_ALL") {
|
guild.emojis = message.d.emojis
|
||||||
await eventDispatcher.onSomeReactionsRemoved(client, message.d)
|
}
|
||||||
|
|
||||||
} else if (message.t === "INTERACTION_CREATE") {
|
} else if (message.t === "GUILD_STICKERS_UPDATE") {
|
||||||
await interactions.dispatchInteraction(message.d)
|
const guild = client.guilds.get(message.d.guild_id)
|
||||||
|
if (guild) {
|
||||||
|
guild.stickers = message.d.stickers
|
||||||
|
}
|
||||||
|
|
||||||
} else if (message.t in eventDispatcher) {
|
} else if (message.t === "GUILD_ROLE_CREATE" || message.t === "GUILD_ROLE_UPDATE" || message.t === "GUILD_ROLE_DELETE") {
|
||||||
await eventDispatcher[message.t](client, message.d)
|
const guild = client.guilds.get(message.d.guild_id)
|
||||||
}
|
/** Delete this in case of UPDATE or DELETE */
|
||||||
} catch (e) {
|
const targetID = "role_id" in message.d ? message.d.role_id : message.d.role.id
|
||||||
// Let OOYE try to handle errors too
|
/** Add this in case of CREATE or UPDATE */
|
||||||
await eventDispatcher.onError(client, e, message)
|
const newRoles = []
|
||||||
|
if ("role" in message.d) newRoles.push(message.d.role)
|
||||||
|
if (guild) {
|
||||||
|
const targetIndex = guild.roles.findIndex(r => r.id === targetID)
|
||||||
|
if (targetIndex !== -1) {
|
||||||
|
// Role already exists. Delete it and maybe replace it.
|
||||||
|
guild.roles.splice(targetIndex, 1, ...newRoles)
|
||||||
|
} else {
|
||||||
|
// Role doesn't already exist.
|
||||||
|
guild.roles.push(...newRoles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (message.t === "GUILD_MEMBER_UPDATE") {
|
||||||
|
const guild = client.guilds.get(message.d.guild_id)
|
||||||
|
const member = guild?.members.find(m => m.user.id === message.d.user.id)
|
||||||
|
if (member) { // only update existing members (i.e. the bot's own member) - don't want to inflate the cache with new irrelevant ones
|
||||||
|
Object.assign(member, message.d)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (message.t === "THREAD_CREATE") {
|
||||||
|
client.channels.set(message.d.id, message.d)
|
||||||
|
if (message.d["guild_id"]) {
|
||||||
|
const channels = client.guildChannelMap.get(message.d["guild_id"])
|
||||||
|
if (channels && !channels.includes(message.d.id)) channels.push(message.d.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (message.t === "CHANNEL_UPDATE" || message.t === "THREAD_UPDATE") {
|
||||||
|
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)
|
||||||
|
if (channels) {
|
||||||
|
for (const id of channels) client.channels.delete(id)
|
||||||
|
}
|
||||||
|
client.guildChannelMap.delete(message.d.id)
|
||||||
|
|
||||||
|
|
||||||
|
} else if (message.t === "CHANNEL_CREATE") {
|
||||||
|
client.channels.set(message.d.id, message.d)
|
||||||
|
if (message.d["guild_id"]) { // obj[prop] notation can be used to access a property without typescript complaining that it doesn't exist on all values something can have
|
||||||
|
const channels = client.guildChannelMap.get(message.d["guild_id"])
|
||||||
|
if (channels && !channels.includes(message.d.id)) channels.push(message.d.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (message.t === "CHANNEL_DELETE") {
|
||||||
|
client.channels.delete(message.d.id)
|
||||||
|
if (message.d["guild_id"]) {
|
||||||
|
const channels = client.guildChannelMap.get(message.d["guild_id"])
|
||||||
|
if (channels) {
|
||||||
|
const previous = channels.indexOf(message.d.id)
|
||||||
|
if (previous !== -1) channels.splice(previous, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event dispatcher for OOYE bridge operations
|
||||||
|
if (listen === "full" && message.t) {
|
||||||
|
const alwaysRealTimeEvents = ["PRESENCE_UPDATE"]
|
||||||
|
if (alwaysRealTimeEvents.includes(message.t) || homeserverStatus.homeserverStatus.isRealTime()) {
|
||||||
|
dispatchPacketToBridge(client, message)
|
||||||
|
} else {
|
||||||
|
homeserverStatus.homeserverStatus.queuePacket(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = utils
|
/**
|
||||||
|
* @param {import("./discord-client")} client
|
||||||
|
* @param {import("cloudstorm").IGatewayMessage} message
|
||||||
|
*/
|
||||||
|
async function dispatchPacketToBridge(client, message) {
|
||||||
|
// requiring this later so that the client is already constructed by the time event-dispatcher is loaded
|
||||||
|
/** @type {typeof import("./event-dispatcher")} */
|
||||||
|
const eventDispatcher = sync.require("./event-dispatcher")
|
||||||
|
/** @type {import("../discord/register-interactions")} */
|
||||||
|
const interactions = sync.require("../discord/register-interactions")
|
||||||
|
|
||||||
|
assert(message.t) // checked above
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (message.t === "MESSAGE_REACTION_REMOVE" || message.t === "MESSAGE_REACTION_REMOVE_EMOJI" || message.t === "MESSAGE_REACTION_REMOVE_ALL") {
|
||||||
|
await eventDispatcher.onSomeReactionsRemoved(client, message.d)
|
||||||
|
|
||||||
|
} else if (message.t === "INTERACTION_CREATE") {
|
||||||
|
await interactions.dispatchInteraction(message.d)
|
||||||
|
|
||||||
|
} else if (message.t in eventDispatcher) {
|
||||||
|
await eventDispatcher[message.t](client, message.d)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Let OOYE try to handle errors too
|
||||||
|
await eventDispatcher.onError(client, e, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.onPacket = onPacket
|
||||||
|
module.exports.dispatchPacketToBridge = dispatchPacketToBridge
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ module.exports = {
|
||||||
|
|
||||||
matrixEventDispatcher.printError(gatewayMessage.t, "Discord", e, gatewayMessage)
|
matrixEventDispatcher.printError(gatewayMessage.t, "Discord", e, gatewayMessage)
|
||||||
|
|
||||||
const channelID = gatewayMessage.d["channel_id"]
|
const channelID = gatewayMessage.d?.["channel_id"]
|
||||||
if (!channelID) return
|
if (!channelID) return
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const ping = sync.require("./interactions/ping.js")
|
||||||
// User must have EVERY permission in default_member_permissions to be able to use the command
|
// User must have EVERY permission in default_member_permissions to be able to use the command
|
||||||
|
|
||||||
function registerInteractions() {
|
function registerInteractions() {
|
||||||
discord.snow.interaction.bulkOverwriteApplicationCommands(id, [{
|
discord.snow.interaction.editApplicationCommands(id, [{
|
||||||
name: "Matrix info",
|
name: "Matrix info",
|
||||||
contexts: [DiscordTypes.InteractionContextType.Guild],
|
contexts: [DiscordTypes.InteractionContextType.Guild],
|
||||||
type: DiscordTypes.ApplicationCommandType.Message,
|
type: DiscordTypes.ApplicationCommandType.Message,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ async function getAndConvertEmoji(mxc) {
|
||||||
const res = await api.getMedia(mxc, {signal: abortController.signal})
|
const res = await api.getMedia(mxc, {signal: abortController.signal})
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
const root = await res.json()
|
const root = await res.json()
|
||||||
throw new mreq.MatrixServerError(root, {mxc})
|
throw new mreq.MatrixServerError(root, res.status, {mxc})
|
||||||
}
|
}
|
||||||
const readable = stream.Readable.fromWeb(res.body)
|
const readable = stream.Readable.fromWeb(res.body)
|
||||||
return emojiSheetConverter.convertImageStream(readable, () => {
|
return emojiSheetConverter.convertImageStream(readable, () => {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ async function getAndResizeSticker(mxc) {
|
||||||
const res = await api.getMedia(mxc)
|
const res = await api.getMedia(mxc)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
const root = await res.json()
|
const root = await res.json()
|
||||||
throw new mreq.MatrixServerError(root, {mxc})
|
throw new mreq.MatrixServerError(root, res.status, {mxc})
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamIn = Readable.fromWeb(res.body)
|
const streamIn = Readable.fromWeb(res.body)
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ async function updatePins(pins, prev) {
|
||||||
.select("reference_channel_id", "message_id").where({event_id}).and("ORDER BY part ASC").get()
|
.select("reference_channel_id", "message_id").where({event_id}).and("ORDER BY part ASC").get()
|
||||||
if (!row) continue
|
if (!row) continue
|
||||||
if (added) {
|
if (added) {
|
||||||
discord.snow.channel.addChannelPinnedMessage(row.reference_channel_id, row.message_id, "Message pinned on Matrix")
|
discord.snow.channel.createChannelPinnedMessage(row.reference_channel_id, row.message_id, "Message pinned on Matrix")
|
||||||
} else {
|
} else {
|
||||||
discord.snow.channel.removeChannelPinnedMessage(row.reference_channel_id, row.message_id, "Message unpinned on Matrix")
|
discord.snow.channel.deleteChannelPinnedMessage(row.reference_channel_id, row.message_id, "Message unpinned on Matrix")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ const vote = sync.require("./actions/vote")
|
||||||
const matrixCommandHandler = sync.require("../matrix/matrix-command-handler")
|
const matrixCommandHandler = sync.require("../matrix/matrix-command-handler")
|
||||||
/** @type {import("../matrix/utils")} */
|
/** @type {import("../matrix/utils")} */
|
||||||
const utils = sync.require("../matrix/utils")
|
const utils = sync.require("../matrix/utils")
|
||||||
|
/** @type {import("../matrix/mreq")}) */
|
||||||
|
const mreq = sync.require("../matrix/mreq")
|
||||||
/** @type {import("../matrix/api")}) */
|
/** @type {import("../matrix/api")}) */
|
||||||
const api = sync.require("../matrix/api")
|
const api = sync.require("../matrix/api")
|
||||||
/** @type {import("../d2m/actions/create-room")} */
|
/** @type {import("../d2m/actions/create-room")} */
|
||||||
|
|
@ -32,6 +34,8 @@ const createRoom = sync.require("../d2m/actions/create-room")
|
||||||
const roomUpgrade = require("../matrix/room-upgrade")
|
const roomUpgrade = require("../matrix/room-upgrade")
|
||||||
/** @type {import("../d2m/actions/retrigger")} */
|
/** @type {import("../d2m/actions/retrigger")} */
|
||||||
const retrigger = sync.require("../d2m/actions/retrigger")
|
const retrigger = sync.require("../d2m/actions/retrigger")
|
||||||
|
/** @type {import("../matrix/homeserver-status")} */
|
||||||
|
const homeserverStatus = sync.require("../matrix/homeserver-status")
|
||||||
const {reg} = require("../matrix/read-registration")
|
const {reg} = require("../matrix/read-registration")
|
||||||
|
|
||||||
let lastReportedEvent = 0
|
let lastReportedEvent = 0
|
||||||
|
|
@ -166,7 +170,12 @@ async function sendError(roomID, source, type, e, payload) {
|
||||||
key: "🔁"
|
key: "🔁"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
if (e instanceof mreq.MatrixServerError && [502, 503].includes(e.httpStatus)) {
|
||||||
|
// Matrix homeserver is down (reverse proxy indicated failure; synapse doesn't generate 502/503 when posting an event to a room)
|
||||||
|
homeserverStatus.homeserverStatus.setErrorWithPacket(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function guard(type, fn) {
|
function guard(type, fn) {
|
||||||
|
|
|
||||||
|
|
@ -452,7 +452,8 @@ async function ping() {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${reg.as_token}`
|
Authorization: `Bearer ${reg.as_token}`
|
||||||
},
|
},
|
||||||
body: "{}"
|
body: "{}",
|
||||||
|
signal: AbortSignal.timeout(15e3)
|
||||||
})
|
})
|
||||||
const root = await res.json()
|
const root = await res.json()
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
134
src/matrix/homeserver-status.js
Normal file
134
src/matrix/homeserver-status.js
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const assert = require("assert")
|
||||||
|
const Denque = require("denque")
|
||||||
|
const StateMachine = require("snowtransfer").StateMachine
|
||||||
|
|
||||||
|
const passthrough = require("../passthrough")
|
||||||
|
const {sync} = passthrough
|
||||||
|
/** @type {import("../d2m/discord-packets")} */
|
||||||
|
const discordPackets = sync.require("../d2m/discord-packets")
|
||||||
|
/** @type {import("../matrix/api")} */
|
||||||
|
const api = sync.require("../matrix/api")
|
||||||
|
|
||||||
|
const DEBUG_HOMESERVER_STATUS = false
|
||||||
|
|
||||||
|
function debugHomeserverStatus(message) {
|
||||||
|
if (DEBUG_HOMESERVER_STATUS) {
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeserverStatus = new class HomeserverStatus {
|
||||||
|
constructor() {
|
||||||
|
/** @private */
|
||||||
|
this.queue = new Denque()
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
this.pingInterval = undefined
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
this.sm = new StateMachine("online")
|
||||||
|
.defineState("online")
|
||||||
|
|
||||||
|
.defineState("checking", {
|
||||||
|
onEnter: [async () => {
|
||||||
|
const pingResult = await api.ping().catch(e => ({ok: false}))
|
||||||
|
if (pingResult.ok) {
|
||||||
|
this.sm.doTransition("check ok")
|
||||||
|
} else {
|
||||||
|
this.sm.doTransition("check fail")
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
onLeave: [],
|
||||||
|
transitions: new Map()
|
||||||
|
})
|
||||||
|
|
||||||
|
.defineState("offline", {
|
||||||
|
onEnter: [() => {
|
||||||
|
this.pingInterval = setInterval(async () => {
|
||||||
|
const pingResult = await api.ping().catch(e => ({ok: false, status: "net", root: e.message}))
|
||||||
|
if (pingResult.ok) {
|
||||||
|
this.sm.doTransition("ping ok")
|
||||||
|
}
|
||||||
|
}, 15e3)
|
||||||
|
}],
|
||||||
|
onLeave: [() => {
|
||||||
|
clearInterval(this.pingInterval)
|
||||||
|
}],
|
||||||
|
transitions: new Map()
|
||||||
|
})
|
||||||
|
|
||||||
|
.defineState("recovering", {
|
||||||
|
onEnter: [async () => { // Drain queue.
|
||||||
|
while (!this.queue.isEmpty()) {
|
||||||
|
const packet = this.queue.peekFront() // same position as .shift()
|
||||||
|
debugHomeserverStatus(`homeserver status: ${new Date().toISOString()} dq packet ${packet.t} ${packet.d?.content}`)
|
||||||
|
await discordPackets.dispatchPacketToBridge(passthrough.discord, packet)
|
||||||
|
if (this.sm.currentStateName !== "recovering") return // got kicked out due to another error
|
||||||
|
this.queue.shift()
|
||||||
|
}
|
||||||
|
this.sm.doTransition("recovered")
|
||||||
|
}],
|
||||||
|
onLeave: [],
|
||||||
|
transitions: new Map()
|
||||||
|
})
|
||||||
|
|
||||||
|
.defineUniversalTransition("error", "offline")
|
||||||
|
.defineTransition("offline", "ping ok", "recovering")
|
||||||
|
.defineTransition("recovering", "recovered", "online")
|
||||||
|
.defineTransition("online", "check", "checking")
|
||||||
|
.defineTransition("checking", "check ok", "recovering")
|
||||||
|
.defineTransition("checking", "check fail", "offline")
|
||||||
|
|
||||||
|
this.sm.on("enter", st => debugHomeserverStatus(`homeserver status: ${st}`))
|
||||||
|
this.sm.setMaxListeners(101)
|
||||||
|
this.sm.freeze()
|
||||||
|
}
|
||||||
|
|
||||||
|
isRealTime() {
|
||||||
|
return this.sm.currentStateName === "online"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {boolean} forceCheck */
|
||||||
|
waitForOnline(forceCheck) {
|
||||||
|
const onlinePromise = new Promise(resolve => {
|
||||||
|
// Already online? Start check or just done
|
||||||
|
if (this.sm.currentStateName === "online") {
|
||||||
|
if (forceCheck) {
|
||||||
|
this.sm.doTransition("check")
|
||||||
|
} else {
|
||||||
|
return resolve(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking or not online. Wait for online.
|
||||||
|
const onlineListener = stateName => {
|
||||||
|
if (stateName === "online") {
|
||||||
|
this.sm.removeListener("enter", onlineListener)
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sm.on("enter", onlineListener)
|
||||||
|
})
|
||||||
|
return onlinePromise
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When offline or recovering, call this for incoming packets to queue them to be sent in order later.
|
||||||
|
*/
|
||||||
|
queuePacket(packet) {
|
||||||
|
assert(["offline", "recovering"].includes(this.sm.currentStateName))
|
||||||
|
this.queue.push(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrorWithPacket(packet) {
|
||||||
|
const wasRecovering = this.sm.currentStateName === "recovering"
|
||||||
|
this.sm.doTransition("error")
|
||||||
|
if (!wasRecovering) { // if was recovering then packet is already in the right place in queue
|
||||||
|
this.queuePacket(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.homeserverStatus = homeserverStatus
|
||||||
|
|
@ -9,9 +9,12 @@ const {reg} = require("./read-registration.js")
|
||||||
const baseUrl = `${reg.ooye.server_origin}/_matrix`
|
const baseUrl = `${reg.ooye.server_origin}/_matrix`
|
||||||
|
|
||||||
class MatrixServerError extends Error {
|
class MatrixServerError extends Error {
|
||||||
constructor(data, opts) {
|
/** @param {number} httpStatus} */
|
||||||
|
constructor(data, httpStatus, opts) {
|
||||||
super(data.error || data.errcode)
|
super(data.error || data.errcode)
|
||||||
this.data = data
|
this.data = data
|
||||||
|
/** @type {number} */
|
||||||
|
this.httpStatus = httpStatus
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
this.errcode = data.errcode
|
this.errcode = data.errcode
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
|
|
@ -44,11 +47,11 @@ async function _convertBody(body) {
|
||||||
async function makeMatrixServerError(res, opts = {}) {
|
async function makeMatrixServerError(res, opts = {}) {
|
||||||
delete opts.headers?.["Authorization"]
|
delete opts.headers?.["Authorization"]
|
||||||
if (res.headers.get("content-type") === "application/json") {
|
if (res.headers.get("content-type") === "application/json") {
|
||||||
return new MatrixServerError(await res.json(), opts)
|
return new MatrixServerError(await res.json(), res.status, opts)
|
||||||
} else if (res.headers.get("content-type")?.startsWith("text/")) {
|
} else if (res.headers.get("content-type")?.startsWith("text/")) {
|
||||||
return new MatrixServerError({errcode: "CX_SERVER_ERROR", error: `Server returned HTTP status ${res.status}`, message: await res.text()}, opts)
|
return new MatrixServerError({errcode: "CX_SERVER_ERROR", error: `Server returned HTTP status ${res.status}`, message: await res.text()}, res.status, opts)
|
||||||
} else {
|
} else {
|
||||||
return new MatrixServerError({errcode: "CX_SERVER_ERROR", error: `Server returned HTTP status ${res.status}`, content_type: res.headers.get("content-type")}, opts)
|
return new MatrixServerError({errcode: "CX_SERVER_ERROR", error: `Server returned HTTP status ${res.status}`, content_type: res.headers.get("content-type")}, res.status, opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,12 +81,12 @@ async function mreq(method, url, bodyIn, extra = {}) {
|
||||||
var root = JSON.parse(text)
|
var root = JSON.parse(text)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
delete opts.headers?.["Authorization"]
|
delete opts.headers?.["Authorization"]
|
||||||
throw new MatrixServerError(text, {baseUrl, url, ...opts})
|
throw new MatrixServerError(text, res.status, {baseUrl, url, ...opts})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.ok || root.errcode) {
|
if (!res.ok || root.errcode) {
|
||||||
delete opts.headers?.["Authorization"]
|
delete opts.headers?.["Authorization"]
|
||||||
throw new MatrixServerError(root, {baseUrl, url, ...opts})
|
throw new MatrixServerError(root, res.status, {baseUrl, url, ...opts})
|
||||||
}
|
}
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
start.js
3
start.js
|
|
@ -31,8 +31,9 @@ sync.require("./src/m2d/event-dispatcher")
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await migrate.migrate(db)
|
await migrate.migrate(db)
|
||||||
|
process.stdout.write("Connecting to Discord... ")
|
||||||
await discord.cloud.connect()
|
await discord.cloud.connect()
|
||||||
console.log("Discord gateway started")
|
console.log("ok.")
|
||||||
sync.require("./src/web/server")
|
sync.require("./src/web/server")
|
||||||
await power.applyPower()
|
await power.applyPower()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue