feat(signature): make host configurable and add WS reconnect

- New optional env SIGNATURE_HOST overrides the hardcoded
  192.168.10.5:2967 (defaults preserved for back-compat).
- WebSocket now reconnects with exponential backoff (1s, 2s, 4s ...
  capped at 30s) on close/error. Previously a dropped signature
  server connection silently disabled signature playback until the
  bot was restarted.
This commit is contained in:
Claude Owner
2026-05-26 14:40:05 +09:00
parent 9123afc14e
commit fe10ed1bd9
2 changed files with 31 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ import WebSocket from "ws";
import ffmpeg from "fluent-ffmpeg"; import ffmpeg from "fluent-ffmpeg";
import { existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs"; import { existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
import { Readable } from "node:stream"; import { Readable } from "node:stream";
import { Config } from "../utils/Config";
interface SignatureItem { interface SignatureItem {
name: string[]; name: string[];
@@ -17,10 +18,13 @@ mkdirSync(filePath, { recursive: true });
export class SignatureClient { export class SignatureClient {
private readonly myClientId = "bot-"+Math.random().toString(36).slice(2, 9); private readonly myClientId = "bot-"+Math.random().toString(36).slice(2, 9);
private readonly host = `192.168.10.5:2967`; private readonly host = Config.signatureHost;
private readonly wsUrl = `ws://${this.host}?clientId=${this.myClientId}`; private readonly wsUrl = `ws://${this.host}?clientId=${this.myClientId}`;
private ws = new WebSocket(this.wsUrl); private ws!: WebSocket;
private signatureList: SignatureItem[] = []; private signatureList: SignatureItem[] = [];
private reconnectDelayMs = 1000;
private readonly reconnectMaxMs = 30_000;
private reconnectTimer?: NodeJS.Timeout;
get list() { get list() {
return this.signatureList; return this.signatureList;
@@ -36,8 +40,14 @@ export class SignatureClient {
} }
constructor() { constructor() {
this.connect();
}
private connect() {
this.ws = new WebSocket(this.wsUrl);
this.ws.on("open", () => { this.ws.on("open", () => {
Logger.ready("[WS] 서버 연결 성공"); Logger.ready("[WS] 서버 연결 성공");
this.reconnectDelayMs = 1000; // reset backoff on successful connect
const requestMessage = JSON.stringify({ type: "GET_LIST" }); const requestMessage = JSON.stringify({ type: "GET_LIST" });
this.ws.send(requestMessage); this.ws.send(requestMessage);
}); });
@@ -54,12 +64,27 @@ export class SignatureClient {
}); });
this.ws.on("close", () => { this.ws.on("close", () => {
Logger.info("[WS] 서버 연결 종료"); Logger.info("[WS] 서버 연결 종료");
this.scheduleReconnect();
}); });
this.ws.on("error", (error) => { this.ws.on("error", (error) => {
Logger.error(`[WS] 오류 발생: ${String(error)}`); Logger.error(`[WS] 오류 발생: ${String(error)}`);
// 'error' is typically followed by 'close' on the ws lib, but call
// out reconnect anyway in case the socket never closes cleanly.
try { this.ws.terminate(); } catch {}
}); });
} }
private scheduleReconnect() {
if (this.reconnectTimer) return; // already scheduled
const delay = this.reconnectDelayMs;
this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, this.reconnectMaxMs);
Logger.info(`[WS] ${delay}ms 후 재연결 시도`);
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = undefined;
this.connect();
}, delay);
}
async changeVolume(buffer: Buffer, volume: number) { async changeVolume(buffer: Buffer, volume: number) {
try { try {
const inputStream = Readable.from(buffer); const inputStream = Readable.from(buffer);

View File

@@ -13,6 +13,7 @@ export const Config = {
nid_ses: process.env.CHZZK_NID_SES?.trim(), nid_ses: process.env.CHZZK_NID_SES?.trim(),
}, },
_ttsPath: process.env.TTSPATH?.trim(), _ttsPath: process.env.TTSPATH?.trim(),
_signatureHost: process.env.SIGNATURE_HOST?.trim() || "192.168.10.5:2967",
debug: process.env.DEBUG?.trim()?.toLocaleLowerCase() === "true", debug: process.env.DEBUG?.trim()?.toLocaleLowerCase() === "true",
dev: process.env.DEV?.trim()?.toLocaleLowerCase() === "true", dev: process.env.DEV?.trim()?.toLocaleLowerCase() === "true",
replaceObj: { ...def_replaceObj, ...JSON.parse(process.env.REPLACETEXT?.trim() || "[{}]")[0] }, replaceObj: { ...def_replaceObj, ...JSON.parse(process.env.REPLACETEXT?.trim() || "[{}]")[0] },
@@ -48,6 +49,9 @@ export const Config = {
get ttsPath() { get ttsPath() {
if (!this._ttsPath) throw new ReferenceError("TTSPATH is missing"); if (!this._ttsPath) throw new ReferenceError("TTSPATH is missing");
return this._ttsPath; return this._ttsPath;
},
get signatureHost() {
return this._signatureHost;
} }
}; };