Survive revoice's ffmpeg teardown and explain failed deletions
Switching tracks killed the process: revoice's #cleanUp() dereferences this.fProc unconditionally, and its own ffmpeg error handler calls stop() a second time after stop() has already nulled that field. Killing ffmpeg is what triggers that error, so its handlers are detached first, the instance's stop() is wrapped defensively (revoice calls it internally), and an uncaughtException handler keeps the bot in the channel if the dependency throws from another async callback. Failed command-message deletions were logged at debug, i.e. invisible in the default configuration; they now warn with Stoat's error type, and the README says which permission the bot's role needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
315760e076
commit
f22b08b350
+59
-55
@@ -1,55 +1,59 @@
|
||||
import { startApiServer } from "./api/server.js";
|
||||
import { startBot } from "./bot/index.js";
|
||||
import { config } from "./config.js";
|
||||
import { MusicManager } from "./core/manager.js";
|
||||
import { logger } from "./logger.js";
|
||||
import { checkCookies, checkYtDlp } from "./sources/index.js";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const ytdlpVersion = await checkYtDlp();
|
||||
if (ytdlpVersion) {
|
||||
logger.info({ version: ytdlpVersion }, "yt-dlp detected");
|
||||
} else {
|
||||
logger.warn(
|
||||
{ path: config.YTDLP_PATH },
|
||||
"yt-dlp not found — YouTube/SoundCloud playback will fail; only direct links and local files will work",
|
||||
);
|
||||
}
|
||||
|
||||
const cookies = await checkCookies();
|
||||
if (cookies === "ok") {
|
||||
logger.info({ path: config.YTDLP_COOKIES }, "using YouTube cookies");
|
||||
} else if (cookies === "read-only") {
|
||||
logger.warn(
|
||||
{ path: config.YTDLP_COOKIES },
|
||||
"cookie file is not writable — yt-dlp cannot persist rotated cookies and the session will expire early",
|
||||
);
|
||||
} else if (cookies === "missing") {
|
||||
logger.warn({ path: config.YTDLP_COOKIES }, "cookie file from YTDLP_COOKIES does not exist");
|
||||
}
|
||||
|
||||
const manager = new MusicManager();
|
||||
const bot = await startBot(manager);
|
||||
const app = await startApiServer({ manager, context: bot.context });
|
||||
|
||||
const shutdown = async (signal: string): Promise<void> => {
|
||||
logger.info({ signal }, "shutting down");
|
||||
try {
|
||||
await app.close();
|
||||
await bot.stop();
|
||||
} catch (err) {
|
||||
logger.error({ err }, "shutdown failed");
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => void shutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
||||
process.on("unhandledRejection", (err) => logger.error({ err }, "unhandled rejection"));
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logger.fatal({ err }, "failed to start");
|
||||
process.exit(1);
|
||||
});
|
||||
import { startApiServer } from "./api/server.js";
|
||||
import { startBot } from "./bot/index.js";
|
||||
import { config } from "./config.js";
|
||||
import { MusicManager } from "./core/manager.js";
|
||||
import { logger } from "./logger.js";
|
||||
import { checkCookies, checkYtDlp } from "./sources/index.js";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const ytdlpVersion = await checkYtDlp();
|
||||
if (ytdlpVersion) {
|
||||
logger.info({ version: ytdlpVersion }, "yt-dlp detected");
|
||||
} else {
|
||||
logger.warn(
|
||||
{ path: config.YTDLP_PATH },
|
||||
"yt-dlp not found — YouTube/SoundCloud playback will fail; only direct links and local files will work",
|
||||
);
|
||||
}
|
||||
|
||||
const cookies = await checkCookies();
|
||||
if (cookies === "ok") {
|
||||
logger.info({ path: config.YTDLP_COOKIES }, "using YouTube cookies");
|
||||
} else if (cookies === "read-only") {
|
||||
logger.warn(
|
||||
{ path: config.YTDLP_COOKIES },
|
||||
"cookie file is not writable — yt-dlp cannot persist rotated cookies and the session will expire early",
|
||||
);
|
||||
} else if (cookies === "missing") {
|
||||
logger.warn({ path: config.YTDLP_COOKIES }, "cookie file from YTDLP_COOKIES does not exist");
|
||||
}
|
||||
|
||||
const manager = new MusicManager();
|
||||
const bot = await startBot(manager);
|
||||
const app = await startApiServer({ manager, context: bot.context });
|
||||
|
||||
const shutdown = async (signal: string): Promise<void> => {
|
||||
logger.info({ signal }, "shutting down");
|
||||
try {
|
||||
await app.close();
|
||||
await bot.stop();
|
||||
} catch (err) {
|
||||
logger.error({ err }, "shutdown failed");
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => void shutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
||||
process.on("unhandledRejection", (err) => logger.error({ err }, "unhandled rejection"));
|
||||
// revoice.js throws from async callbacks we cannot wrap (its ffmpeg teardown
|
||||
// in particular). Dying there would drop the bot out of the voice channel
|
||||
// mid-track, so we log loudly and keep serving instead.
|
||||
process.on("uncaughtException", (err) => logger.error({ err }, "uncaught exception, continuing"));
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logger.fatal({ err }, "failed to start");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user