2026-06-02 22:20:46 +07:00
import { createChildLogger } from "@bete/shared/logger" ;
2026-06-01 21:44:29 +07:00
import { Client } from "discord.js-selfbot-v13" ;
import { startPendingAIAnalysisWorker } from "../modules/ai-moderation/aiAnalyzer.js" ;
2026-06-02 00:32:38 +07:00
import { CommandHandler } from "../modules/command-handler/commandHandler.js" ;
2026-06-01 21:44:29 +07:00
import {
EventBroadcaster ,
RedisEventPublisher ,
} from "../modules/event-broadcaster/index.js" ;
import {
registerMessageCapture ,
setEventBroadcaster ,
} from "../modules/message-capture/messageCapture.js" ;
import { VoiceController } from "../modules/voice-recording/voiceController.js" ;
import { config } from "../shared/config/config.js" ;
import {
closeDatabase ,
initializeDatabase ,
} from "../shared/database/drizzle.js" ;
import { runMigrations } from "../shared/database/migrate.js" ;
import { createDiscordClientOptions } from "../shared/discord/clientOptions.js" ;
import { createGracefulShutdown } from "./shutdown.js" ;
const logger = createChildLogger ( "discord-gateway" );
export async function initializeDiscordGateway() {
2026-06-02 21:44:42 +07:00
if ( config . AI_ANALYSIS_ENABLED && ! config . AI_LLM_API_KEY ) {
2026-06-01 21:44:29 +07:00
logger . error (
2026-06-02 21:44:42 +07:00
"AI_ANALYSIS_ENABLED=true but AI_LLM_API_KEY is missing from environment. Force closing application because AI analysis cannot run without credentials." ,
2026-06-01 21:44:29 +07:00
);
process . exit ( 1 );
}
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 );
// Initialize Redis event broadcaster
const redisPublisher = new RedisEventPublisher ( config . REDIS_URL , logger );
const eventBroadcaster = new EventBroadcaster ( redisPublisher , logger );
2026-06-02 00:32:38 +07:00
// Initialize Redis command handler for backend→gateway commands
const commandHandler = new CommandHandler ();
2026-06-01 21:44:29 +07:00
const gracefulShutdown = createGracefulShutdown ({
logger ,
closeDatabase ,
voiceController ,
client ,
eventBroadcaster ,
2026-06-02 00:32:38 +07:00
commandHandler ,
2026-06-01 21:44:29 +07:00
});
try {
if ( config . AUTO_MIGRATE_ON_STARTUP ) {
logger . info (
"AUTO_MIGRATE_ON_STARTUP enabled; running database migrations" ,
);
await runMigrations ();
}
logger . info ( "Initializing database" );
await initializeDatabase ();
logger . info ( "PostgreSQL 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" );
setEventBroadcaster ( eventBroadcaster );
registerMessageCapture ( client );
2026-06-02 12:17:51 +07:00
startPendingAIAnalysisWorker ( client , eventBroadcaster );
2026-06-02 00:32:38 +07:00
// Start command handler after Discord is ready
commandHandler . start ( client , voiceController );
logger . info ( "Command handler started" );
2026-06-01 21:44:29 +07:00
});
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 : unknown ) => {
logger . error ({ error }, "Discord client.login failed" );
});
}