refactor: extract application bootstrap

This commit is contained in:
MythEclipse
2026-05-19 15:08:02 +07:00
parent f7c8257ef1
commit dcbe204795
3 changed files with 155 additions and 123 deletions
+98
View File
@@ -0,0 +1,98 @@
import { Client } from "discord.js-selfbot-v13";
import { config } from "../config";
import { closeDatabase, initializeDatabase } from "../database/drizzle";
import { createDiscordClientOptions } from "../discordClientOptions";
import { createChildLogger } from "../logger";
import { startPendingAIAnalysisWorker } from "../moderation/aiAnalyzer";
import { syncBacklogMessages } from "../moderation/backlogSync";
import { registerMessageCapture } from "../moderation/messageCapture";
import { discordPlayer } from "../player";
import { VoiceController } from "../voiceController";
import { startWebserver } from "../webserver";
import { createGracefulShutdown } from "./shutdown";
export async function initializeApp() {
const logger = createChildLogger("bot");
const token = config.DISCORD_TOKEN;
logger.info(
{ hasToken: token.length > 0, tokenLength: token.length },
"Config loaded",
);
logger.info("Creating Discord client");
const client = new Client(createDiscordClientOptions());
const voiceController = new VoiceController(client);
const gracefulShutdown = createGracefulShutdown({
logger,
closeDatabase,
voiceController,
discordPlayer,
client,
});
try {
logger.info("Initializing database");
await initializeDatabase();
logger.info({ type: config.DATABASE_TYPE }, "Database initialized");
} catch (err) {
logger.error({ error: err }, "Failed to initialize database");
process.exit(1);
}
client.on("debug", (msg) => {
if (
msg.includes("[VOICE") ||
msg.includes("[ffmpeg") ||
msg.toLowerCase().includes("error") ||
msg.toLowerCase().includes("stream")
) {
logger.info({ debugMsg: msg }, "Discord Client Debug");
} else if (config.VERBOSE) {
logger.debug({ debugMsg: msg }, "Discord Client Debug");
}
});
client.on("ready", async () => {
logger.info({ user: client.user?.tag }, "Bot logged in");
registerMessageCapture(client);
startPendingAIAnalysisWorker();
syncBacklogMessages(client).catch((error) => {
logger.warn({ error }, "Backlog sync failed");
});
await startWebserver(config.WEBSERVER_PORT, client, voiceController);
});
client.on("error", (err) => {
logger.error({ error: err }, "Client error");
});
process.on("SIGINT", () => {
gracefulShutdown("SIGINT");
});
process.on("SIGTERM", () => {
gracefulShutdown("SIGTERM");
});
process.on("uncaughtException", (err) => {
logger.error({ error: err }, "Uncaught exception");
gracefulShutdown("uncaughtException");
});
process.on("unhandledRejection", (reason, promise) => {
logger.error({ reason, promise }, "Unhandled rejection");
gracefulShutdown("unhandledRejection");
});
logger.info("Calling Discord client.login");
client
.login(token)
.then(() => {
logger.info("Discord client.login resolved");
})
.catch((error) => {
logger.error({ error }, "Discord client.login failed");
});
}
+56
View File
@@ -0,0 +1,56 @@
import type { Client } from "discord.js-selfbot-v13";
import type { closeDatabase } from "../database/drizzle";
import type { createChildLogger } from "../logger";
import type { discordPlayer } from "../player";
import type { VoiceController } from "../voiceController";
type Logger = ReturnType<typeof createChildLogger>;
type CloseDatabase = typeof closeDatabase;
type DiscordPlayer = typeof discordPlayer;
export interface GracefulShutdownOptions {
logger: Logger;
closeDatabase: CloseDatabase;
voiceController: VoiceController;
discordPlayer: DiscordPlayer;
client: Client;
}
export function createGracefulShutdown(options: GracefulShutdownOptions) {
let isShuttingDown = false;
return async function gracefulShutdown(signal: string) {
if (isShuttingDown) {
options.logger.warn(`Already shutting down, ignoring ${signal}`);
return;
}
isShuttingDown = true;
options.logger.info({ signal }, "Graceful shutdown initiated");
try {
options.logger.info("Closing database...");
await options.closeDatabase();
options.logger.info("Database closed");
options.logger.info("Stopping voice connection...");
await options.voiceController.disconnect();
options.logger.info("Pausing player...");
options.discordPlayer.pause();
options.logger.info("Destroying Discord client...");
try {
options.client.destroy();
} catch (err) {
options.logger.warn({ error: err }, "Error destroying client");
}
options.logger.info("Graceful shutdown completed");
process.exit(0);
} catch (err) {
options.logger.error({ error: err }, "Error during graceful shutdown");
process.exit(1);
}
};
}
+1 -123
View File
@@ -2,133 +2,11 @@ import "./mock-crc";
import "libsodium-wrappers";
import "@snazzah/davey";
import "dotenv/config";
import { Client } from "discord.js-selfbot-v13";
import { config } from "./config";
import { closeDatabase, initializeDatabase } from "./database/drizzle";
import { createDiscordClientOptions } from "./discordClientOptions";
import { initializeApp } from "./app/bootstrap";
import { createChildLogger } from "./logger";
import { startPendingAIAnalysisWorker } from "./moderation/aiAnalyzer";
import { syncBacklogMessages } from "./moderation/backlogSync";
import { registerMessageCapture } from "./moderation/messageCapture";
import { discordPlayer } from "./player";
import { VoiceController } from "./voiceController";
import { startWebserver } from "./webserver";
const logger = createChildLogger("bot");
const token = config.DISCORD_TOKEN;
logger.info(
{ hasToken: token.length > 0, tokenLength: token.length },
"Config loaded",
);
logger.info("Creating Discord client");
const client = new Client(createDiscordClientOptions());
const voiceController = new VoiceController(client);
let isShuttingDown = false;
async function gracefulShutdown(signal: string) {
if (isShuttingDown) {
logger.warn(`Already shutting down, ignoring ${signal}`);
return;
}
isShuttingDown = true;
logger.info({ signal }, "Graceful shutdown initiated");
try {
logger.info("Closing database...");
await closeDatabase();
logger.info("Database closed");
logger.info("Stopping voice connection...");
await voiceController.disconnect();
logger.info("Pausing player...");
discordPlayer.pause();
logger.info("Destroying Discord client...");
try {
client.destroy();
} catch (err) {
logger.warn({ error: err }, "Error destroying client");
}
logger.info("Graceful shutdown completed");
process.exit(0);
} catch (err) {
logger.error({ error: err }, "Error during graceful shutdown");
process.exit(1);
}
}
async function initializeApp() {
try {
logger.info("Initializing database");
await initializeDatabase();
logger.info({ type: config.DATABASE_TYPE }, "Database initialized");
} catch (err) {
logger.error({ error: err }, "Failed to initialize database");
process.exit(1);
}
client.on("debug", (msg) => {
if (
msg.includes("[VOICE") ||
msg.includes("[ffmpeg") ||
msg.toLowerCase().includes("error") ||
msg.toLowerCase().includes("stream")
) {
logger.info({ debugMsg: msg }, "Discord Client Debug");
} else if (config.VERBOSE) {
logger.debug({ debugMsg: msg }, "Discord Client Debug");
}
});
client.on("ready", async () => {
logger.info({ user: client.user?.tag }, "Bot logged in");
registerMessageCapture(client);
startPendingAIAnalysisWorker();
syncBacklogMessages(client).catch((error) => {
logger.warn({ error }, "Backlog sync failed");
});
await startWebserver(config.WEBSERVER_PORT, client, voiceController);
});
client.on("error", (err) => {
logger.error({ error: err }, "Client error");
});
process.on("SIGINT", () => {
gracefulShutdown("SIGINT");
});
process.on("SIGTERM", () => {
gracefulShutdown("SIGTERM");
});
process.on("uncaughtException", (err) => {
logger.error({ error: err }, "Uncaught exception");
gracefulShutdown("uncaughtException");
});
process.on("unhandledRejection", (reason, promise) => {
logger.error({ reason, promise }, "Unhandled rejection");
gracefulShutdown("unhandledRejection");
});
logger.info("Calling Discord client.login");
client
.login(token)
.then(() => {
logger.info("Discord client.login resolved");
})
.catch((error) => {
logger.error({ error }, "Discord client.login failed");
});
}
initializeApp().catch((error) => {
logger.error({ error }, "Failed to initialize app");
process.exit(1);