diff --git a/src/d2m/discord-client.js b/src/d2m/discord-client.js index 9f1d1ff..b9b45e2 100644 --- a/src/d2m/discord-client.js +++ b/src/d2m/discord-client.js @@ -14,6 +14,8 @@ const {sync} = passthrough /** @type {import("./discord-packets")} */ const discordPackets = sync.require("./discord-packets") +const CONNECTION_DEBUG = false + class DiscordClient { /** * @param {string} discordToken @@ -59,15 +61,17 @@ class DiscordClient { }) } - const addEventLogger = (eventName, logName) => { - this.cloud.on(eventName, (...args) => { - const d = new Date().toISOString().slice(0, 19) - console.error(`[${d} Client ${logName}]`, ...args) - }) + if (CONNECTION_DEBUG) { + const addEventLogger = (eventName, logName) => { + this.cloud.on(eventName, (...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") } } diff --git a/src/d2m/discord-packets.js b/src/d2m/discord-packets.js index 9bab1de..b17ad4e 100644 --- a/src/d2m/discord-packets.js +++ b/src/d2m/discord-packets.js @@ -1,12 +1,15 @@ // @ts-check const assert = require("assert") +const {scheduler} = require("timers/promises") const passthrough = require("../passthrough") const {sync} = passthrough /** @type {import("../matrix/homeserver-status")} */ const homeserverStatus = sync.require("../matrix/homeserver-status") +let checkedHomeserver = false + /** * @param {import("./discord-client")} client * @param {import("cloudstorm").IGatewayMessage} message @@ -48,6 +51,31 @@ async function onPacket(client, message, listen) { 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) { + checkedHomeserver = true + process.stdout.write("Connecting to homeserver... ") + await homeserverStatus.homeserverStatus.waitForOnline(true) + console.log("ok.\nReplaying past events. Welcome to Out Of Your Element.") + } else { + await homeserverStatus.homeserverStatus.waitForOnline(false) + } + await eventDispatcher.checkMissedExpressions(message.d) await eventDispatcher.checkMissedMessages(client, message.d) await eventDispatcher.checkMissedPins(client, message.d) diff --git a/src/matrix/homeserver-status.js b/src/matrix/homeserver-status.js index 0c5966f..3ceac84 100644 --- a/src/matrix/homeserver-status.js +++ b/src/matrix/homeserver-status.js @@ -11,7 +11,7 @@ const discordPackets = sync.require("../d2m/discord-packets") /** @type {import("../matrix/api")} */ const api = sync.require("../matrix/api") -const DEBUG_HOMESERVER_STATUS = true +const DEBUG_HOMESERVER_STATUS = false function debugHomeserverStatus(message) { if (DEBUG_HOMESERVER_STATUS) { @@ -31,6 +31,19 @@ const homeserverStatus = new class HomeserverStatus { 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 () => { @@ -64,9 +77,12 @@ const homeserverStatus = new class HomeserverStatus { .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() } @@ -74,6 +90,30 @@ const homeserverStatus = new class HomeserverStatus { 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. */ diff --git a/start.js b/start.js index 39e8ea0..51942c6 100755 --- a/start.js +++ b/start.js @@ -31,8 +31,9 @@ sync.require("./src/m2d/event-dispatcher") ;(async () => { await migrate.migrate(db) + process.stdout.write("Connecting to Discord... ") await discord.cloud.connect() - console.log("Discord gateway started") + console.log("ok.") sync.require("./src/web/server") await power.applyPower()