import {fork} from "child_process" import {ChatInputCommandInteraction, Client, InteractionWebhook} from "discord.js" export enum RestartReason { RestartCommand = "restart", } export enum StartState { NeverStarted = "never_started", Errored = "errored", Crashed = "crashed", Started = "started", Ready = "ready", Failed = "failed", } export interface StartData { state: StartState, status?: string } export interface RestartData { appId: string token: string reason: RestartReason } let restartState: RestartData | null = null export async function checkIsRestart(): Promise { if (!process.connected) { return null } else { try { const result = await new Promise((resolve) => { process.on("message", (value: { restart?: RestartData }) => { if (value && value.restart) { resolve(value.restart) } else { resolve(null) } }) setTimeout(() => resolve(null), 50) }) if (result !== null) { restartState = result } return result } catch (ex) { return null } } } export async function reportStarted(): Promise { if (!process.send) { return } const startData: StartData = { state: StartState.Started, } process.send(startData) } export async function reportReady(c: Client): Promise { if (restartState) { try { const hook = new InteractionWebhook(c, restartState.appId, restartState.token) await hook.send({ content: "yawwwwn... Good morning...", }) } catch (ex) { console.log("followup failed to send", ex) } } if (!process.send) { return false } const startData: StartData = { state: StartState.Ready, } process.send(startData) return true } export async function reportFailed(c: Client, error: unknown): Promise { console.log("failed to start", error) if (restartState) { try { const hook = new InteractionWebhook(c, restartState.appId, restartState.token) await hook.send({ content: "Ugh... I can't get up... Head hurts... sorry...", }) } catch (ex) { console.log("followup failed to send", ex) } } if (!process.send) { return } const startData: StartData = { state: StartState.Failed, status: `${error}`, } process.send(startData) } export async function wrappedRestart(b: ChatInputCommandInteraction) { try { await doRestart(b.client, b.webhook.id, b.webhook.token, RestartReason.RestartCommand) } catch (ex) { console.log("failed doing restart", ex) let data: StartData | null = null if (typeof ex === "object" && ex !== null && ex.hasOwnProperty("state")) { data = ex as StartData } if ((data && data.state)) { try { b.client.user.presence.set({ status: "invisible", activities: [], }) await b.followUp("Mhhhh... I don't feel good...") } catch (ex) { console.log("failed resetting presence and sending followup", ex) } } } finally { b.client.destroy() } } export async function doRestart(client: Client, appId: string, token: string, restartReason: RestartReason): Promise { return new Promise((resolve, reject) => { const child = fork(process.argv[1], process.argv.slice(2), { execPath: process.execPath, execArgv: process.execArgv, cwd: process.cwd(), detached: true, stdio: "inherit", }) let result: StartData = { state: StartState.NeverStarted, } child.once("error", (err) => { result.state = StartState.Errored result.status = `${err}` if (child.connected) { child.unref() child.disconnect() } reject({...result}) }) child.once("exit", (code, signal) => { result.state = StartState.Crashed result.status = `Exited unexpectedly with code ${code} (signal ${signal})` if (child.connected) { child.unref() child.disconnect() } reject({...result}) }) child.on("message", (message: StartData) => { switch (typeof message === "object" && message.state) { case StartState.Started: result.state = StartState.Started break case StartState.Ready: resolve({...message}) if (child.connected) { child.unref() child.disconnect() } break case StartState.Failed: reject({...message}) if (child.connected) { child.unref() child.disconnect() } break default: result.state = StartState.Failed result.status = `Unexpected message: ${message}` reject({...message}) if (child.connected) { child.unref() child.disconnect() } break } }) process.on("disconnect", () => { result.state = StartState.Crashed reject({...result}) child.unref() }) child.send({restart: {appId, token, reason: restartReason}}) }) }