feat: implement logging and retry mechanism with pino and p-retry
This commit is contained in:
@@ -15,18 +15,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/opus": "^0.10.0",
|
"@discordjs/opus": "^0.10.0",
|
||||||
"@discordjs/voice": "^0.19.1",
|
"@discordjs/voice": "^0.19.1",
|
||||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
||||||
"@snazzah/davey": "^0.1.10",
|
"@snazzah/davey": "^0.1.10",
|
||||||
"crc-32": "^1.2.2",
|
|
||||||
"discord.js-selfbot-v13": "^3.7.1",
|
"discord.js-selfbot-v13": "^3.7.1",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"ffmpeg-static": "^5.3.0",
|
|
||||||
"fluent-ffmpeg": "^2.1.3",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"libsodium-wrappers": "^0.8.2",
|
"libsodium-wrappers": "^0.8.2",
|
||||||
"node-crc": "^4.0.0",
|
|
||||||
"prism-media": "2.0.0-alpha.0",
|
"prism-media": "2.0.0-alpha.0",
|
||||||
"sodium-native": "^4.3.2",
|
"sodium-native": "^4.3.2",
|
||||||
"ws": "^8.20.1"
|
"ws": "^8.20.1",
|
||||||
|
"pino": "^9.4.0",
|
||||||
|
"p-retry": "^6.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "latest",
|
"@biomejs/biome": "latest",
|
||||||
|
|||||||
28
src/index.ts
28
src/index.ts
@@ -7,6 +7,10 @@ import { config } from "./config";
|
|||||||
import { discordPlayer } from "./player";
|
import { discordPlayer } from "./player";
|
||||||
import { startRecording } from "./recorder";
|
import { startRecording } from "./recorder";
|
||||||
import { startWebserver } from "./webserver";
|
import { startWebserver } from "./webserver";
|
||||||
|
import { createChildLogger } from "./logger";
|
||||||
|
import { retryWithBackoff } from "./retry";
|
||||||
|
|
||||||
|
const logger = createChildLogger("bot");
|
||||||
|
|
||||||
// Validasi environment variables
|
// Validasi environment variables
|
||||||
const token = process.env.DISCORD_TOKEN;
|
const token = process.env.DISCORD_TOKEN;
|
||||||
@@ -17,18 +21,18 @@ if (!token) throw new Error("Missing DISCORD_TOKEN in .env");
|
|||||||
if (!voiceChannelId) throw new Error("Missing VOICE_CHANNEL_ID in .env");
|
if (!voiceChannelId) throw new Error("Missing VOICE_CHANNEL_ID in .env");
|
||||||
if (!guildId) throw new Error("Missing GUILD_ID in .env");
|
if (!guildId) throw new Error("Missing GUILD_ID in .env");
|
||||||
|
|
||||||
// Inisialisasi selfbot client (gunakan checkUpdate: false supaya tidak ada prompt update)
|
// Inisialisasi selfbot client
|
||||||
const client = new Client();
|
const client = new Client();
|
||||||
|
|
||||||
client.on("ready", async () => {
|
client.on("ready", async () => {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log(`[bot] Logged in as ${client.user!.tag}`);
|
logger.info({ user: client.user?.tag }, "Bot logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ambil guild
|
// Ambil guild
|
||||||
const guild = client.guilds.cache.get(guildId!);
|
const guild = client.guilds.cache.get(guildId!);
|
||||||
if (!guild) {
|
if (!guild) {
|
||||||
console.error(`[bot] Guild not found: ${guildId}`);
|
logger.error({ guildId }, "Guild not found");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,24 +42,24 @@ client.on("ready", async () => {
|
|||||||
(await guild.channels.fetch(voiceChannelId!).catch(() => null));
|
(await guild.channels.fetch(voiceChannelId!).catch(() => null));
|
||||||
|
|
||||||
if (!channel || channel.type !== "GUILD_VOICE") {
|
if (!channel || channel.type !== "GUILD_VOICE") {
|
||||||
console.error(
|
logger.error({ voiceChannelId }, "Voice channel not found or wrong type");
|
||||||
`[bot] Voice channel not found or wrong type: ${voiceChannelId}`,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log(
|
logger.info(
|
||||||
`[bot] Joining voice channel: #${channel.name} (${channel.id})`,
|
{ channelName: channel.name, channelId: channel.id },
|
||||||
|
"Joining voice channel",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await startRecording(client, channel as any);
|
await startRecording(client, channel as any);
|
||||||
|
|
||||||
// Set up player connection
|
// Set up player connection
|
||||||
const connection = getVoiceConnection(guildId!);
|
const connection = getVoiceConnection(guildId!);
|
||||||
if (connection) {
|
if (connection) {
|
||||||
discordPlayer.setConnection(connection);
|
discordPlayer.setConnection(connection);
|
||||||
console.log("[bot] Player connected to voice channel");
|
logger.info("Player connected to voice channel");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start Webserver
|
// Start Webserver
|
||||||
@@ -63,13 +67,13 @@ client.on("ready", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
console.error("[bot] Client error:", err);
|
logger.error({ error: err }, "Client error");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log("\n[bot] Shutting down...");
|
logger.info("Shutting down gracefully...");
|
||||||
}
|
}
|
||||||
client.destroy();
|
client.destroy();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
@@ -77,7 +81,7 @@ process.on("SIGINT", () => {
|
|||||||
|
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log("[bot] Terminating...");
|
logger.info("Terminating...");
|
||||||
}
|
}
|
||||||
client.destroy();
|
client.destroy();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|||||||
22
src/logger.ts
Normal file
22
src/logger.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import pino from "pino";
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
|
export const logger = pino({
|
||||||
|
level: process.env.LOG_LEVEL || (isDev ? "debug" : "info"),
|
||||||
|
transport: isDev
|
||||||
|
? {
|
||||||
|
target: "pino-pretty",
|
||||||
|
options: {
|
||||||
|
colorize: true,
|
||||||
|
translateTime: "SYS:standard",
|
||||||
|
ignore: "pid,hostname",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
timestamp: pino.stdTimeFunctions.isoTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createChildLogger = (context: string) => {
|
||||||
|
return logger.child({ context });
|
||||||
|
};
|
||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
VoiceConnectionStatus,
|
VoiceConnectionStatus,
|
||||||
} from "@discordjs/voice";
|
} from "@discordjs/voice";
|
||||||
import type { Client, VoiceChannel } from "discord.js-selfbot-v13";
|
import type { Client, VoiceChannel } from "discord.js-selfbot-v13";
|
||||||
import prism from "prism-media";
|
|
||||||
import { config } from "./config";
|
import { config } from "./config";
|
||||||
import { PacketFilter } from "./packetFilter";
|
import { PacketFilter } from "./packetFilter";
|
||||||
import { subscribeToAudioStream } from "./recorder/audioStream";
|
import { subscribeToAudioStream } from "./recorder/audioStream";
|
||||||
@@ -19,6 +18,10 @@ import {
|
|||||||
} from "./recorder/metadata";
|
} from "./recorder/metadata";
|
||||||
import { SegmentManager } from "./recorder/segment";
|
import { SegmentManager } from "./recorder/segment";
|
||||||
import type { PcmBroadcaster } from "./types";
|
import type { PcmBroadcaster } from "./types";
|
||||||
|
import { createChildLogger } from "./logger";
|
||||||
|
import { retryWithBackoff } from "./retry";
|
||||||
|
|
||||||
|
const logger = createChildLogger("recorder");
|
||||||
|
|
||||||
const recordingsDir = config.recordingsDir;
|
const recordingsDir = config.recordingsDir;
|
||||||
|
|
||||||
@@ -43,32 +46,37 @@ export async function startRecording(
|
|||||||
debug: true,
|
debug: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.verbose) {
|
logger.info({ channelName: channel.name }, "Joining voice channel");
|
||||||
console.log(`[recorder] Joining voice channel: #${channel.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.on("debug", (msg) => {
|
connection.on("debug", (msg) => {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log(`[voice-debug] ${msg}`);
|
logger.debug({ message: msg }, "Voice debug");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on("error", (err) => {
|
connection.on("error", (err) => {
|
||||||
console.error(`[voice-error]`, err);
|
logger.error({ error: err }, "Voice connection error");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tunggu sampai benar-benar terhubung
|
// Tunggu sampai benar-benar terhubung dengan retry logic
|
||||||
try {
|
try {
|
||||||
await entersState(
|
await retryWithBackoff(
|
||||||
|
() =>
|
||||||
|
entersState(
|
||||||
connection,
|
connection,
|
||||||
VoiceConnectionStatus.Ready,
|
VoiceConnectionStatus.Ready,
|
||||||
config.voiceConnectionTimeoutMs,
|
config.voiceConnectionTimeoutMs,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
retries: 3,
|
||||||
|
minTimeout: 1000,
|
||||||
|
maxTimeout: 5000,
|
||||||
|
logger,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (config.verbose) {
|
logger.info("Connected to voice channel. Recording started");
|
||||||
console.log("[recorder] Connected to voice channel. Recording started.");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[recorder] Failed to connect:", err);
|
logger.error({ error: err }, "Failed to connect to voice channel");
|
||||||
connection.destroy();
|
connection.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,7 +87,7 @@ export async function startRecording(
|
|||||||
// Dengarkan siapapun yang mulai bicara
|
// Dengarkan siapapun yang mulai bicara
|
||||||
receiver.speaking.on("start", async (userId) => {
|
receiver.speaking.on("start", async (userId) => {
|
||||||
const userMetadata = await collectUserMetadata(client, userId, channel);
|
const userMetadata = await collectUserMetadata(client, userId, channel);
|
||||||
console.log(`${userMetadata.username} [voice activity]`);
|
logger.info({ userId, username: userMetadata.username }, "Voice activity detected");
|
||||||
|
|
||||||
// Notify webserver
|
// Notify webserver
|
||||||
broadcaster.updateActiveUser?.(userId, {
|
broadcaster.updateActiveUser?.(userId, {
|
||||||
@@ -132,7 +140,7 @@ export async function startRecording(
|
|||||||
let currentSegment = segmentManager.open(oggPacketStream);
|
let currentSegment = segmentManager.open(oggPacketStream);
|
||||||
currentSegment.out.on("finish", () => {
|
currentSegment.out.on("finish", () => {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log(`[recorder] Saved: ${currentSegment.filename}`);
|
logger.info({ filename: currentSegment.filename }, "Segment saved");
|
||||||
}
|
}
|
||||||
const metadata = createSegmentMetadata(
|
const metadata = createSegmentMetadata(
|
||||||
userMetadata,
|
userMetadata,
|
||||||
@@ -146,14 +154,18 @@ export async function startRecording(
|
|||||||
JSON.stringify(metadata, null, 2),
|
JSON.stringify(metadata, null, 2),
|
||||||
);
|
);
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log(
|
logger.info(
|
||||||
`[recorder] Saved metadata: ${currentSegment.jsonFilename}`,
|
{ jsonFile: currentSegment.jsonFilename },
|
||||||
|
"Metadata saved",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
currentSegment.out.on("error", (err) => {
|
currentSegment.out.on("error", (err) => {
|
||||||
console.error(`[recorder] File write error ${userId}:`, err.message);
|
logger.error(
|
||||||
|
{ userId, error: err.message },
|
||||||
|
"File write error",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Feed Opus packets one-by-one
|
// Feed Opus packets one-by-one
|
||||||
@@ -166,7 +178,7 @@ export async function startRecording(
|
|||||||
decoder.write(chunk);
|
decoder.write(chunk);
|
||||||
},
|
},
|
||||||
onEnd: () => {
|
onEnd: () => {
|
||||||
const segment = segmentManager.close(oggPacketStream);
|
segmentManager.close(oggPacketStream);
|
||||||
decoder.destroy();
|
decoder.destroy();
|
||||||
broadcaster.updateActiveUser?.(userId, {
|
broadcaster.updateActiveUser?.(userId, {
|
||||||
username: userMetadata.username,
|
username: userMetadata.username,
|
||||||
@@ -177,31 +189,32 @@ export async function startRecording(
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
segmentManager.close(oggPacketStream);
|
segmentManager.close(oggPacketStream);
|
||||||
decoder.destroy();
|
decoder.destroy();
|
||||||
console.error(
|
logger.error(
|
||||||
`[recorder] Audio Stream error ${userId}:`,
|
{ userId, error: error.message },
|
||||||
error.message,
|
"Audio stream error",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
packetFilterForOgg.on("error", (err) => {
|
packetFilterForOgg.on("error", (err) => {
|
||||||
segmentManager.close(oggPacketStream);
|
segmentManager.close(oggPacketStream);
|
||||||
console.error(
|
logger.error(
|
||||||
`[recorder] PacketFilter(ogg) error ${userId}:`,
|
{ userId, error: err.message },
|
||||||
err.message,
|
"PacketFilter error",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[recorder] Failed to create stream for ${userId}:`, e);
|
logger.error(
|
||||||
|
{ userId, error: e instanceof Error ? e.message : String(e) },
|
||||||
|
"Failed to create stream",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle disconnect yang tidak disengaja
|
// Handle disconnect yang tidak disengaja
|
||||||
connection.on(VoiceConnectionStatus.Disconnected, async () => {
|
connection.on(VoiceConnectionStatus.Disconnected, async () => {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.warn(
|
logger.warn("Disconnected from voice channel. Reconnecting...");
|
||||||
"[recorder] Disconnected from voice channel. Reconnecting...",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
@@ -218,14 +231,14 @@ export async function startRecording(
|
|||||||
]);
|
]);
|
||||||
// Berhasil reconnect
|
// Berhasil reconnect
|
||||||
} catch {
|
} catch {
|
||||||
console.error("[recorder] Could not reconnect. Destroying connection.");
|
logger.error("Could not reconnect. Destroying connection");
|
||||||
connection.destroy();
|
connection.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.on(VoiceConnectionStatus.Destroyed, () => {
|
connection.on(VoiceConnectionStatus.Destroyed, () => {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log("[recorder] Voice connection destroyed.");
|
logger.info("Voice connection destroyed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -238,9 +251,9 @@ export function stopRecording(guildId: string): void {
|
|||||||
if (connection) {
|
if (connection) {
|
||||||
connection.destroy();
|
connection.destroy();
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
console.log("[recorder] Recording stopped and disconnected.");
|
logger.info("Recording stopped and disconnected");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("[recorder] No active connection to stop.");
|
logger.warn("No active connection to stop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/retry.ts
Normal file
42
src/retry.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import pRetry from "p-retry";
|
||||||
|
import type { Logger } from "pino";
|
||||||
|
|
||||||
|
export interface RetryOptions {
|
||||||
|
retries?: number;
|
||||||
|
minTimeout?: number;
|
||||||
|
maxTimeout?: number;
|
||||||
|
factor?: number;
|
||||||
|
logger?: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function retryWithBackoff<T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
options: RetryOptions = {},
|
||||||
|
): Promise<T> {
|
||||||
|
const {
|
||||||
|
retries = 3,
|
||||||
|
minTimeout = 1000,
|
||||||
|
maxTimeout = 30000,
|
||||||
|
factor = 2,
|
||||||
|
logger,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return pRetry(fn, {
|
||||||
|
retries,
|
||||||
|
minTimeout,
|
||||||
|
maxTimeout,
|
||||||
|
factor,
|
||||||
|
onFailedAttempt: (error) => {
|
||||||
|
if (logger) {
|
||||||
|
logger.warn(
|
||||||
|
{
|
||||||
|
attempt: error.attemptNumber,
|
||||||
|
retriesLeft: error.retriesLeft,
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
"Retry attempt",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,6 +4,9 @@ import path from "path";
|
|||||||
import prism from "prism-media";
|
import prism from "prism-media";
|
||||||
import { WebSocketServer } from "ws";
|
import { WebSocketServer } from "ws";
|
||||||
import { discordPlayer } from "./player";
|
import { discordPlayer } from "./player";
|
||||||
|
import { createChildLogger } from "./logger";
|
||||||
|
|
||||||
|
const logger = createChildLogger("webserver");
|
||||||
|
|
||||||
const activeUsers = new Map<
|
const activeUsers = new Map<
|
||||||
string,
|
string,
|
||||||
@@ -42,9 +45,7 @@ export function startWebserver(port: number = 3000) {
|
|||||||
|
|
||||||
const wsPort = port + 1;
|
const wsPort = port + 1;
|
||||||
const wss = new WebSocketServer({ port: wsPort, host: "0.0.0.0" });
|
const wss = new WebSocketServer({ port: wsPort, host: "0.0.0.0" });
|
||||||
console.log(
|
logger.info({ wsPort }, "WebSocket server listening");
|
||||||
`[webserver] WebSocket server listening on ws://0.0.0.0:${wsPort}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, "../public")));
|
app.use(express.static(path.join(__dirname, "../public")));
|
||||||
|
|
||||||
@@ -124,8 +125,9 @@ export function startWebserver(port: number = 3000) {
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (dbCount > 0) {
|
if (dbCount > 0) {
|
||||||
const avg = dbAccum / dbCount;
|
const avg = dbAccum / dbCount;
|
||||||
console.log(
|
logger.info(
|
||||||
`[transmit] Audio level: ${avg.toFixed(1)} dBFS (${dbCount} frames/2s)`,
|
{ level: avg.toFixed(1), frames: dbCount },
|
||||||
|
"Audio level",
|
||||||
);
|
);
|
||||||
dbAccum = 0;
|
dbAccum = 0;
|
||||||
dbCount = 0;
|
dbCount = 0;
|
||||||
@@ -140,8 +142,8 @@ export function startWebserver(port: number = 3000) {
|
|||||||
|
|
||||||
if (pcmBuffer.length >= BYTES_PER_FRAME) {
|
if (pcmBuffer.length >= BYTES_PER_FRAME) {
|
||||||
// Real audio available
|
// Real audio available
|
||||||
frame = pcmBuffer.slice(0, BYTES_PER_FRAME);
|
frame = pcmBuffer.subarray(0, BYTES_PER_FRAME);
|
||||||
pcmBuffer = pcmBuffer.slice(BYTES_PER_FRAME);
|
pcmBuffer = pcmBuffer.subarray(BYTES_PER_FRAME);
|
||||||
|
|
||||||
// Track level for logging
|
// Track level for logging
|
||||||
dbAccum += rmsDb(frame);
|
dbAccum += rmsDb(frame);
|
||||||
@@ -150,7 +152,7 @@ export function startWebserver(port: number = 3000) {
|
|||||||
if (playerPaused) {
|
if (playerPaused) {
|
||||||
discordPlayer.unpause();
|
discordPlayer.unpause();
|
||||||
playerPaused = false;
|
playerPaused = false;
|
||||||
console.log("[transmit] Transmitting — Discord indicator ON");
|
logger.info("Transmitting — Discord indicator ON");
|
||||||
}
|
}
|
||||||
} else if (msSinceAudio < SILENCE_TAIL_MS && msSinceAudio > 0) {
|
} else if (msSinceAudio < SILENCE_TAIL_MS && msSinceAudio > 0) {
|
||||||
// Buffer drained but audio was recent — pad silence to avoid OGG gap
|
// Buffer drained but audio was recent — pad silence to avoid OGG gap
|
||||||
@@ -159,7 +161,7 @@ export function startWebserver(port: number = 3000) {
|
|||||||
// No audio for a while — pause Discord indicator
|
// No audio for a while — pause Discord indicator
|
||||||
discordPlayer.pause();
|
discordPlayer.pause();
|
||||||
playerPaused = true;
|
playerPaused = true;
|
||||||
console.log("[transmit] Stopped — Discord indicator OFF");
|
logger.info("Stopped — Discord indicator OFF");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
return; // already paused, nothing to do
|
return; // already paused, nothing to do
|
||||||
@@ -173,7 +175,7 @@ export function startWebserver(port: number = 3000) {
|
|||||||
}, 20);
|
}, 20);
|
||||||
|
|
||||||
wss.on("connection", (ws) => {
|
wss.on("connection", (ws) => {
|
||||||
console.log("[webserver] New WebSocket connection on port " + wsPort);
|
logger.info({ wsPort }, "New WebSocket connection");
|
||||||
wsClients.add(ws);
|
wsClients.add(ws);
|
||||||
|
|
||||||
ws.send(
|
ws.send(
|
||||||
@@ -208,8 +210,6 @@ export function startWebserver(port: number = 3000) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.listen(port, "0.0.0.0", () => {
|
server.listen(port, "0.0.0.0", () => {
|
||||||
console.log(
|
logger.info({ port }, "Web interface listening");
|
||||||
`[webserver] Web interface listening on http://0.0.0.0:${port}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user