mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
6 files changed
+122
-1
No files matched your search
Regular → Executable
+51
@@ -61,7 +61,11 @@ export class BotInstance extends EventEmitter {
|
||||
private disconnectEmitted = false;
|
||||
private voteSkipUsers = new Set<string>();
|
||||
private isAdvancing = false;
|
||||
<<<<<<< HEAD
|
||||
private idleTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private channelUserCount = 0;
|
||||
private profileManager: BotProfileManager;
|
||||
>>>>>>> origin/main
|
||||
|
||||
constructor(options: BotInstanceOptions) {
|
||||
super();
|
||||
@@ -131,6 +135,10 @@ export class BotInstance extends EventEmitter {
|
||||
this.disconnectEmitted = true;
|
||||
this.emit("disconnected");
|
||||
});
|
||||
|
||||
this.tsClient.on("connected", () => {
|
||||
this._startIdlePoller();
|
||||
});
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
@@ -149,6 +157,7 @@ export class BotInstance extends EventEmitter {
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this._cancelIdleTimer();
|
||||
this.player.stop();
|
||||
this.connected = false;
|
||||
if (!this.disconnectEmitted) {
|
||||
@@ -158,6 +167,48 @@ export class BotInstance extends EventEmitter {
|
||||
this.tsClient.disconnect();
|
||||
}
|
||||
|
||||
/** 外部更新 idleTimeoutMinutes(由 API 保存时调用) */
|
||||
updateIdleTimeout(minutes: number): void {
|
||||
this.config.idleTimeoutMinutes = minutes;
|
||||
if (minutes === 0) this._cancelIdleTimer();
|
||||
}
|
||||
|
||||
private _startIdlePoller(): void {
|
||||
// 每 30 秒检查一次频道人数
|
||||
const poll = async () => {
|
||||
if (!this.connected) return;
|
||||
try {
|
||||
const clients = await this.tsClient.getClientsInChannel();
|
||||
const userCount = clients.length - 1; // 排除 bot 自身
|
||||
if (userCount <= 0) {
|
||||
this._scheduleIdleCheck();
|
||||
} else {
|
||||
this._cancelIdleTimer();
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
setTimeout(poll, 30_000);
|
||||
};
|
||||
setTimeout(poll, 30_000);
|
||||
}
|
||||
|
||||
private _scheduleIdleCheck(): void {
|
||||
if (this.idleTimer !== null) return; // 已经在倒计时,不重复创建
|
||||
const minutes = this.config.idleTimeoutMinutes ?? 0;
|
||||
if (!this.connected || minutes <= 0) return;
|
||||
this.idleTimer = setTimeout(() => {
|
||||
if (!this.connected) return;
|
||||
this.logger.info({ idleMinutes: minutes }, "Channel empty, disconnecting due to idle timeout");
|
||||
this.disconnect();
|
||||
}, minutes * 60 * 1000);
|
||||
}
|
||||
|
||||
private _cancelIdleTimer(): void {
|
||||
if (this.idleTimer) {
|
||||
clearTimeout(this.idleTimer);
|
||||
this.idleTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTextMessage(msg: TS3TextMessage): Promise<void> {
|
||||
const parsed = parseCommand(
|
||||
msg.message,
|
||||
|
||||
Regular → Executable
+2
@@ -13,6 +13,7 @@ export interface BotConfig {
|
||||
adminGroups: number[];
|
||||
autoReturnDelay: number;
|
||||
autoPauseOnEmpty: boolean;
|
||||
idleTimeoutMinutes: number;
|
||||
}
|
||||
|
||||
export function getDefaultConfig(): BotConfig {
|
||||
@@ -28,6 +29,7 @@ export function getDefaultConfig(): BotConfig {
|
||||
adminGroups: [],
|
||||
autoReturnDelay: 300,
|
||||
autoPauseOnEmpty: true,
|
||||
idleTimeoutMinutes: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Regular → Executable
+1
@@ -71,6 +71,7 @@ async function main() {
|
||||
bilibiliProvider,
|
||||
database: db,
|
||||
config,
|
||||
configPath: CONFIG_PATH,
|
||||
logger,
|
||||
cookieStore,
|
||||
staticDir: STATIC_DIR,
|
||||
|
||||
Regular → Executable
+23
@@ -1,11 +1,13 @@
|
||||
import { Router } from "express";
|
||||
import type { BotManager } from "../../bot/manager.js";
|
||||
import type { BotConfig } from "../../data/config.js";
|
||||
import { saveConfig } from "../../data/config.js";
|
||||
import type { Logger } from "../../logger.js";
|
||||
|
||||
export function createBotRouter(
|
||||
botManager: BotManager,
|
||||
config: BotConfig,
|
||||
configPath: string,
|
||||
logger: Logger
|
||||
): Router {
|
||||
const router = Router();
|
||||
@@ -116,5 +118,26 @@ export function createBotRouter(
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/bot/settings — 读取全局 bot 行为设置
|
||||
router.get("/settings", (_req, res) => {
|
||||
res.json({ idleTimeoutMinutes: config.idleTimeoutMinutes ?? 0 });
|
||||
});
|
||||
|
||||
// POST /api/bot/settings — 保存全局 bot 行为设置
|
||||
router.post("/settings", (req, res) => {
|
||||
const { idleTimeoutMinutes } = req.body;
|
||||
if (typeof idleTimeoutMinutes !== "number" || idleTimeoutMinutes < 0) {
|
||||
res.status(400).json({ error: "idleTimeoutMinutes must be a non-negative number" });
|
||||
return;
|
||||
}
|
||||
config.idleTimeoutMinutes = idleTimeoutMinutes;
|
||||
saveConfig(configPath, config);
|
||||
// 通知所有 bot 实例更新定时器
|
||||
for (const bot of botManager.getAllBots()) {
|
||||
bot.updateIdleTimeout(idleTimeoutMinutes);
|
||||
}
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
Regular → Executable
+2
-1
@@ -22,6 +22,7 @@ export interface WebServerOptions {
|
||||
bilibiliProvider: MusicProvider;
|
||||
database: BotDatabase;
|
||||
config: BotConfig;
|
||||
configPath: string;
|
||||
logger: Logger;
|
||||
cookieStore?: CookieStore;
|
||||
staticDir?: string;
|
||||
@@ -41,7 +42,7 @@ export function createWebServer(options: WebServerOptions): WebServer {
|
||||
|
||||
app.use(
|
||||
"/api/bot",
|
||||
createBotRouter(options.botManager, options.config, logger)
|
||||
createBotRouter(options.botManager, options.config, options.configPath, logger)
|
||||
);
|
||||
app.use(
|
||||
"/api/music",
|
||||
|
||||
Regular → Executable
+43
@@ -371,6 +371,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Idle Timeout -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">行为设置</h2>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">
|
||||
<Icon icon="mdi:timer-off-outline" class="setting-icon" />
|
||||
<div>
|
||||
<div>闲置自动退出</div>
|
||||
<div style="font-size:12px; opacity:0.6; margin-top:2px">频道无人时,机器人自动断开的等待时间(0 = 不退出)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prefix-input-wrap">
|
||||
<input
|
||||
v-model.number="idleTimeout"
|
||||
type="number"
|
||||
min="0"
|
||||
class="input input-sm"
|
||||
style="max-width:80px"
|
||||
placeholder="0"
|
||||
/>
|
||||
<span style="font-size:13px; opacity:0.7">分钟</span>
|
||||
<button class="btn-primary" @click="saveIdleTimeout">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -661,10 +687,27 @@ async function savePrefix() {
|
||||
// Prefix is saved client-side for now
|
||||
}
|
||||
|
||||
// Idle timeout
|
||||
const idleTimeout = ref(0);
|
||||
|
||||
async function loadIdleTimeout() {
|
||||
try {
|
||||
const res = await axios.get('/api/bot/settings');
|
||||
idleTimeout.value = res.data.idleTimeoutMinutes ?? 0;
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function saveIdleTimeout() {
|
||||
try {
|
||||
await axios.post('/api/bot/settings', { idleTimeoutMinutes: idleTimeout.value });
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
store.fetchBots(); // Refresh bot status on page visit
|
||||
checkAuthStatus();
|
||||
loadQuality();
|
||||
loadIdleTimeout();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
Reference in new issue
Block a user