|
|
|
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<RestartData | null> {
|
|
|
|
if (!process.connected) {
|
|
|
|
return null
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const result = await new Promise<RestartData | null>((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<void> {
|
|
|
|
if (!process.send) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const startData: StartData = {
|
|
|
|
state: StartState.Started,
|
|
|
|
}
|
|
|
|
process.send(startData)
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function reportReady(c: Client<true>): Promise<boolean> {
|
|
|
|
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<true>, error: unknown): Promise<void> {
|
|
|
|
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, cleanup: () => Promise<void>) {
|
|
|
|
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 {
|
|
|
|
await cleanup()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function doRestart(client: Client, appId: string, token: string,
|
|
|
|
restartReason: RestartReason): Promise<StartData> {
|
|
|
|
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}})
|
|
|
|
})
|
|
|
|
}
|