mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat: Add channelId support to bot configuration and database
This commit is contained in:
1 parent
6f21b6354a
commit
4244695075
7 files changed
+140
-39
No files matched your search
@@ -57,6 +57,7 @@ export interface CreateBotParams {
|
|||||||
queryPort?: number;
|
queryPort?: number;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
defaultChannel?: string;
|
defaultChannel?: string;
|
||||||
|
channelId?: string;
|
||||||
channelPassword?: string;
|
channelPassword?: string;
|
||||||
autoStart?: boolean;
|
autoStart?: boolean;
|
||||||
/** Force TS3 or TS6 protocol; omit or "unknown" for auto-detect. */
|
/** Force TS3 or TS6 protocol; omit or "unknown" for auto-detect. */
|
||||||
@@ -113,6 +114,7 @@ export class BotManager extends EventEmitter {
|
|||||||
queryPort: params.queryPort ?? 10011,
|
queryPort: params.queryPort ?? 10011,
|
||||||
nickname: params.nickname,
|
nickname: params.nickname,
|
||||||
defaultChannel: params.defaultChannel,
|
defaultChannel: params.defaultChannel,
|
||||||
|
channelId: params.channelId,
|
||||||
channelPassword: params.channelPassword,
|
channelPassword: params.channelPassword,
|
||||||
serverPassword: params.serverPassword,
|
serverPassword: params.serverPassword,
|
||||||
serverProtocol: params.serverProtocol,
|
serverProtocol: params.serverProtocol,
|
||||||
@@ -138,6 +140,7 @@ export class BotManager extends EventEmitter {
|
|||||||
serverPort: params.serverPort,
|
serverPort: params.serverPort,
|
||||||
nickname: params.nickname,
|
nickname: params.nickname,
|
||||||
defaultChannel: params.defaultChannel ?? "",
|
defaultChannel: params.defaultChannel ?? "",
|
||||||
|
channelId: params.channelId ?? "",
|
||||||
channelPassword: params.channelPassword ?? "",
|
channelPassword: params.channelPassword ?? "",
|
||||||
autoStart: params.autoStart ?? false,
|
autoStart: params.autoStart ?? false,
|
||||||
serverProtocol: params.serverProtocol ?? "",
|
serverProtocol: params.serverProtocol ?? "",
|
||||||
@@ -173,6 +176,7 @@ export class BotManager extends EventEmitter {
|
|||||||
serverPort: params.serverPort ?? existing.serverPort,
|
serverPort: params.serverPort ?? existing.serverPort,
|
||||||
nickname: params.nickname ?? existing.nickname,
|
nickname: params.nickname ?? existing.nickname,
|
||||||
defaultChannel: params.defaultChannel ?? existing.defaultChannel,
|
defaultChannel: params.defaultChannel ?? existing.defaultChannel,
|
||||||
|
channelId: params.channelId ?? existing.channelId,
|
||||||
channelPassword: params.channelPassword ?? existing.channelPassword,
|
channelPassword: params.channelPassword ?? existing.channelPassword,
|
||||||
serverProtocol: params.serverProtocol ?? existing.serverProtocol,
|
serverProtocol: params.serverProtocol ?? existing.serverProtocol,
|
||||||
ts6ApiKey: params.ts6ApiKey ?? existing.ts6ApiKey,
|
ts6ApiKey: params.ts6ApiKey ?? existing.ts6ApiKey,
|
||||||
@@ -231,6 +235,7 @@ export class BotManager extends EventEmitter {
|
|||||||
// each connect and strips all previously granted groups.
|
// each connect and strips all previously granted groups.
|
||||||
identity: saved.identity || undefined,
|
identity: saved.identity || undefined,
|
||||||
defaultChannel: saved.defaultChannel || undefined,
|
defaultChannel: saved.defaultChannel || undefined,
|
||||||
|
channelId: saved.channelId || undefined,
|
||||||
channelPassword: saved.channelPassword || undefined,
|
channelPassword: saved.channelPassword || undefined,
|
||||||
serverPassword: saved.serverPassword || undefined,
|
serverPassword: saved.serverPassword || undefined,
|
||||||
serverProtocol: proto === "ts3" || proto === "ts6" ? proto : undefined,
|
serverProtocol: proto === "ts3" || proto === "ts6" ? proto : undefined,
|
||||||
@@ -282,6 +287,7 @@ export class BotManager extends EventEmitter {
|
|||||||
nickname: saved.nickname,
|
nickname: saved.nickname,
|
||||||
identity: saved.identity || undefined,
|
identity: saved.identity || undefined,
|
||||||
defaultChannel: saved.defaultChannel || undefined,
|
defaultChannel: saved.defaultChannel || undefined,
|
||||||
|
channelId: saved.channelId || undefined,
|
||||||
channelPassword: saved.channelPassword || undefined,
|
channelPassword: saved.channelPassword || undefined,
|
||||||
serverPassword: saved.serverPassword || undefined,
|
serverPassword: saved.serverPassword || undefined,
|
||||||
serverProtocol: proto === "ts3" || proto === "ts6" ? proto : undefined,
|
serverProtocol: proto === "ts3" || proto === "ts6" ? proto : undefined,
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ describe("database", () => {
|
|||||||
serverPort: 9987,
|
serverPort: 9987,
|
||||||
nickname: "MusicBot",
|
nickname: "MusicBot",
|
||||||
defaultChannel: "Music",
|
defaultChannel: "Music",
|
||||||
|
channelId: "",
|
||||||
channelPassword: "",
|
channelPassword: "",
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
serverProtocol: "",
|
serverProtocol: "",
|
||||||
@@ -111,6 +112,7 @@ describe("database", () => {
|
|||||||
serverPort: 9987,
|
serverPort: 9987,
|
||||||
nickname: "MusicBot",
|
nickname: "MusicBot",
|
||||||
defaultChannel: "Music",
|
defaultChannel: "Music",
|
||||||
|
channelId: "",
|
||||||
channelPassword: "",
|
channelPassword: "",
|
||||||
autoStart: false,
|
autoStart: false,
|
||||||
serverProtocol: "",
|
serverProtocol: "",
|
||||||
@@ -131,6 +133,7 @@ describe("database", () => {
|
|||||||
serverPort: 9987,
|
serverPort: 9987,
|
||||||
nickname: "n",
|
nickname: "n",
|
||||||
defaultChannel: "",
|
defaultChannel: "",
|
||||||
|
channelId: "",
|
||||||
channelPassword: "",
|
channelPassword: "",
|
||||||
autoStart: false,
|
autoStart: false,
|
||||||
serverProtocol: "",
|
serverProtocol: "",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface BotInstance {
|
|||||||
serverPort: number;
|
serverPort: number;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
defaultChannel: string;
|
defaultChannel: string;
|
||||||
|
channelId: string;
|
||||||
channelPassword: string;
|
channelPassword: string;
|
||||||
autoStart: boolean;
|
autoStart: boolean;
|
||||||
/** "ts3" | "ts6" | "" (empty = auto-detect) */
|
/** "ts3" | "ts6" | "" (empty = auto-detect) */
|
||||||
@@ -96,6 +97,9 @@ function migrateSchema(db: Database.Database): void {
|
|||||||
if (!names.includes("serverPassword")) {
|
if (!names.includes("serverPassword")) {
|
||||||
db.exec("ALTER TABLE bot_instances ADD COLUMN serverPassword TEXT NOT NULL DEFAULT ''");
|
db.exec("ALTER TABLE bot_instances ADD COLUMN serverPassword TEXT NOT NULL DEFAULT ''");
|
||||||
}
|
}
|
||||||
|
if (!names.includes("channelId")) {
|
||||||
|
db.exec("ALTER TABLE bot_instances ADD COLUMN channelId TEXT NOT NULL DEFAULT ''");
|
||||||
|
}
|
||||||
// Profile feature flags
|
// Profile feature flags
|
||||||
const profileCols = [
|
const profileCols = [
|
||||||
"profile_avatar_enabled",
|
"profile_avatar_enabled",
|
||||||
@@ -142,6 +146,7 @@ function initTables(db: Database.Database): void {
|
|||||||
serverPort INTEGER NOT NULL,
|
serverPort INTEGER NOT NULL,
|
||||||
nickname TEXT NOT NULL,
|
nickname TEXT NOT NULL,
|
||||||
defaultChannel TEXT NOT NULL,
|
defaultChannel TEXT NOT NULL,
|
||||||
|
channelId TEXT NOT NULL DEFAULT '',
|
||||||
channelPassword TEXT NOT NULL,
|
channelPassword TEXT NOT NULL,
|
||||||
autoStart INTEGER NOT NULL DEFAULT 0,
|
autoStart INTEGER NOT NULL DEFAULT 0,
|
||||||
serverProtocol TEXT NOT NULL DEFAULT '',
|
serverProtocol TEXT NOT NULL DEFAULT '',
|
||||||
@@ -254,14 +259,15 @@ export function createDatabase(dbPath: string): BotDatabase {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const upsertInstance = db.prepare(`
|
const upsertInstance = db.prepare(`
|
||||||
INSERT INTO bot_instances (id, name, serverAddress, serverPort, nickname, defaultChannel, channelPassword, autoStart, serverProtocol, ts6ApiKey, serverPassword, identity)
|
INSERT INTO bot_instances (id, name, serverAddress, serverPort, nickname, defaultChannel, channelId, channelPassword, autoStart, serverProtocol, ts6ApiKey, serverPassword, identity)
|
||||||
VALUES (@id, @name, @serverAddress, @serverPort, @nickname, @defaultChannel, @channelPassword, @autoStart, @serverProtocol, @ts6ApiKey, @serverPassword, @identity)
|
VALUES (@id, @name, @serverAddress, @serverPort, @nickname, @defaultChannel, @channelId, @channelPassword, @autoStart, @serverProtocol, @ts6ApiKey, @serverPassword, @identity)
|
||||||
ON CONFLICT(id) DO UPDATE SET
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
name = excluded.name,
|
name = excluded.name,
|
||||||
serverAddress = excluded.serverAddress,
|
serverAddress = excluded.serverAddress,
|
||||||
serverPort = excluded.serverPort,
|
serverPort = excluded.serverPort,
|
||||||
nickname = excluded.nickname,
|
nickname = excluded.nickname,
|
||||||
defaultChannel = excluded.defaultChannel,
|
defaultChannel = excluded.defaultChannel,
|
||||||
|
channelId = excluded.channelId,
|
||||||
channelPassword = excluded.channelPassword,
|
channelPassword = excluded.channelPassword,
|
||||||
autoStart = excluded.autoStart,
|
autoStart = excluded.autoStart,
|
||||||
serverProtocol = excluded.serverProtocol,
|
serverProtocol = excluded.serverProtocol,
|
||||||
@@ -342,6 +348,7 @@ export function createDatabase(dbPath: string): BotDatabase {
|
|||||||
serverProtocol: r.serverProtocol ?? "",
|
serverProtocol: r.serverProtocol ?? "",
|
||||||
ts6ApiKey: r.ts6ApiKey ?? "",
|
ts6ApiKey: r.ts6ApiKey ?? "",
|
||||||
serverPassword: r.serverPassword ?? "",
|
serverPassword: r.serverPassword ?? "",
|
||||||
|
channelId: r.channelId ?? "",
|
||||||
identity: r.identity ?? undefined,
|
identity: r.identity ?? undefined,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|||||||
+84
-29
@@ -46,6 +46,7 @@ export interface TS3ClientOptions {
|
|||||||
nickname: string;
|
nickname: string;
|
||||||
identity?: string; // Exported identity string, or undefined to generate new
|
identity?: string; // Exported identity string, or undefined to generate new
|
||||||
defaultChannel?: string;
|
defaultChannel?: string;
|
||||||
|
channelId?: string; // Numeric channel ID (takes precedence over defaultChannel)
|
||||||
channelPassword?: string;
|
channelPassword?: string;
|
||||||
serverPassword?: string;
|
serverPassword?: string;
|
||||||
/** Force a specific protocol instead of auto-detecting. */
|
/** Force a specific protocol instead of auto-detecting. */
|
||||||
@@ -72,7 +73,10 @@ export class TS3Client extends EventEmitter {
|
|||||||
private httpQuery: TS6HttpQuery | null = null;
|
private httpQuery: TS6HttpQuery | null = null;
|
||||||
private udpErrorTimer: ReturnType<typeof setTimeout> | null = null;
|
private udpErrorTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
constructor(private options: TS3ClientOptions, logger: Logger) {
|
constructor(
|
||||||
|
private options: TS3ClientOptions,
|
||||||
|
logger: Logger,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
@@ -109,7 +113,10 @@ export class TS3Client extends EventEmitter {
|
|||||||
const addr = `${this.options.host}:${this.options.port}`;
|
const addr = `${this.options.host}:${this.options.port}`;
|
||||||
|
|
||||||
// Detect or use forced protocol
|
// Detect or use forced protocol
|
||||||
if (this.options.serverProtocol && this.options.serverProtocol !== "unknown") {
|
if (
|
||||||
|
this.options.serverProtocol &&
|
||||||
|
this.options.serverProtocol !== "unknown"
|
||||||
|
) {
|
||||||
this.detectedProtocol = this.options.serverProtocol;
|
this.detectedProtocol = this.options.serverProtocol;
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
{ addr, protocol: this.detectedProtocol },
|
{ addr, protocol: this.detectedProtocol },
|
||||||
@@ -132,7 +139,11 @@ export class TS3Client extends EventEmitter {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
{ addr, protocol: this.detectedProtocol, queryPort: detection.queryPort },
|
{
|
||||||
|
addr,
|
||||||
|
protocol: this.detectedProtocol,
|
||||||
|
queryPort: detection.queryPort,
|
||||||
|
},
|
||||||
`Server protocol detected: ${this.detectedProtocol.toUpperCase()}`,
|
`Server protocol detected: ${this.detectedProtocol.toUpperCase()}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -140,7 +151,8 @@ export class TS3Client extends EventEmitter {
|
|||||||
|
|
||||||
// Set up TS6 HTTP Query if applicable
|
// Set up TS6 HTTP Query if applicable
|
||||||
if (this.detectedProtocol === "ts6") {
|
if (this.detectedProtocol === "ts6") {
|
||||||
const queryPort = this.options.queryPort !== 10011 ? this.options.queryPort : 10080;
|
const queryPort =
|
||||||
|
this.options.queryPort !== 10011 ? this.options.queryPort : 10080;
|
||||||
this.httpQuery = new TS6HttpQuery({
|
this.httpQuery = new TS6HttpQuery({
|
||||||
host: this.options.host,
|
host: this.options.host,
|
||||||
port: queryPort,
|
port: queryPort,
|
||||||
@@ -151,7 +163,9 @@ export class TS3Client extends EventEmitter {
|
|||||||
// Guard against calling connect() while already connected.
|
// Guard against calling connect() while already connected.
|
||||||
// Save detectedProtocol first because disconnect() resets it.
|
// Save detectedProtocol first because disconnect() resets it.
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
this.logger.warn("connect() called while already connected, disconnecting first");
|
this.logger.warn(
|
||||||
|
"connect() called while already connected, disconnecting first",
|
||||||
|
);
|
||||||
const savedProtocol = this.detectedProtocol;
|
const savedProtocol = this.detectedProtocol;
|
||||||
const savedHttpQuery = this.httpQuery;
|
const savedHttpQuery = this.httpQuery;
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
@@ -178,7 +192,9 @@ export class TS3Client extends EventEmitter {
|
|||||||
if (this.udpErrorTimer) clearTimeout(this.udpErrorTimer);
|
if (this.udpErrorTimer) clearTimeout(this.udpErrorTimer);
|
||||||
this.udpErrorTimer = setTimeout(() => {
|
this.udpErrorTimer = setTimeout(() => {
|
||||||
if (udpErrorCount > 1) {
|
if (udpErrorCount > 1) {
|
||||||
this.logger.warn(`udp send error (repeated ${udpErrorCount} times, connection may be lost)`);
|
this.logger.warn(
|
||||||
|
`udp send error (repeated ${udpErrorCount} times, connection may be lost)`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
udpErrorCount = 0;
|
udpErrorCount = 0;
|
||||||
this.udpErrorTimer = null;
|
this.udpErrorTimer = null;
|
||||||
@@ -189,7 +205,11 @@ export class TS3Client extends EventEmitter {
|
|||||||
this.logger.warn(msg);
|
this.logger.warn(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client = new TS3FullClient(this.identity, addr, this.options.nickname, {
|
this.client = new TS3FullClient(
|
||||||
|
this.identity,
|
||||||
|
addr,
|
||||||
|
this.options.nickname,
|
||||||
|
{
|
||||||
// Forward server password to the protocol library so it can be
|
// Forward server password to the protocol library so it can be
|
||||||
// included in clientinit for password-protected servers
|
// included in clientinit for password-protected servers
|
||||||
serverPassword: this.options.serverPassword,
|
serverPassword: this.options.serverPassword,
|
||||||
@@ -199,7 +219,8 @@ export class TS3Client extends EventEmitter {
|
|||||||
warn: throttledWarn,
|
warn: throttledWarn,
|
||||||
error: (msg) => this.logger.error(msg),
|
error: (msg) => this.logger.error(msg),
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.client.on("textMessage", (msg: TextMessage) => {
|
this.client.on("textMessage", (msg: TextMessage) => {
|
||||||
const tsMsg: TS3TextMessage = {
|
const tsMsg: TS3TextMessage = {
|
||||||
@@ -221,7 +242,7 @@ export class TS3Client extends EventEmitter {
|
|||||||
this.client.on("clientEnter", (info: ClientInfo) => {
|
this.client.on("clientEnter", (info: ClientInfo) => {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
{ nickname: info.nickname, id: info.id },
|
{ nickname: info.nickname, id: info.id },
|
||||||
"Client entered"
|
"Client entered",
|
||||||
);
|
);
|
||||||
this.emit("clientEnter", info);
|
this.emit("clientEnter", info);
|
||||||
});
|
});
|
||||||
@@ -234,7 +255,7 @@ export class TS3Client extends EventEmitter {
|
|||||||
this.client.on("clientMoved", (ev: ClientMovedEvent) => {
|
this.client.on("clientMoved", (ev: ClientMovedEvent) => {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
{ id: ev.id, targetChannelID: ev.targetChannelID.toString() },
|
{ id: ev.id, targetChannelID: ev.targetChannelID.toString() },
|
||||||
"Client moved"
|
"Client moved",
|
||||||
);
|
);
|
||||||
this.emit("clientMoved", ev);
|
this.emit("clientMoved", ev);
|
||||||
});
|
});
|
||||||
@@ -254,11 +275,16 @@ export class TS3Client extends EventEmitter {
|
|||||||
`Logged in (visible client, ${this.detectedProtocol.toUpperCase()} server)`,
|
`Logged in (visible client, ${this.detectedProtocol.toUpperCase()} server)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Join default channel if specified
|
// Join channel by numeric ID (takes precedence) or by name
|
||||||
if (this.options.defaultChannel) {
|
if (this.options.channelId) {
|
||||||
|
await this.joinChannel(
|
||||||
|
this.options.channelId,
|
||||||
|
this.options.channelPassword,
|
||||||
|
);
|
||||||
|
} else if (this.options.defaultChannel) {
|
||||||
await this.joinChannel(
|
await this.joinChannel(
|
||||||
this.options.defaultChannel,
|
this.options.defaultChannel,
|
||||||
this.options.channelPassword
|
this.options.channelPassword,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,24 +294,32 @@ export class TS3Client extends EventEmitter {
|
|||||||
async joinChannel(channelName: string, password?: string): Promise<void> {
|
async joinChannel(channelName: string, password?: string): Promise<void> {
|
||||||
if (!this.client) return;
|
if (!this.client) return;
|
||||||
|
|
||||||
|
const isNumeric = /^\d+$/.test(channelName);
|
||||||
|
if (isNumeric) {
|
||||||
|
try {
|
||||||
|
await clientMove(
|
||||||
|
this.client,
|
||||||
|
this.clientId,
|
||||||
|
BigInt(channelName),
|
||||||
|
password,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error({ err, channelName }, "Failed to join channel");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const channels = await listChannels(this.client);
|
const channels = await listChannels(this.client);
|
||||||
const channel = channels.find((ch) => ch.name === channelName);
|
const channel = channels.find((ch) => ch.name === channelName);
|
||||||
|
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
this.logger.warn({ channelName }, "Channel not found");
|
this.logger.warn({ channelName }, "Channel not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await clientMove(this.client, this.clientId, channel.id, password);
|
||||||
await clientMove(
|
|
||||||
this.client,
|
|
||||||
this.clientId,
|
|
||||||
channel.id,
|
|
||||||
password
|
|
||||||
);
|
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
{ channelName, cid: channel.id.toString() },
|
{ channelName, cid: channel.id.toString() },
|
||||||
"Joined channel"
|
"Joined channel",
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error({ err, channelName }, "Failed to join channel");
|
this.logger.error({ err, channelName }, "Failed to join channel");
|
||||||
@@ -294,7 +328,7 @@ export class TS3Client extends EventEmitter {
|
|||||||
|
|
||||||
async sendTextMessage(
|
async sendTextMessage(
|
||||||
message: string,
|
message: string,
|
||||||
targetMode: number = 2
|
targetMode: number = 2,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.client) return;
|
if (!this.client) return;
|
||||||
// targetMode 2 = channel, target 0 = current channel
|
// targetMode 2 = channel, target 0 = current channel
|
||||||
@@ -326,7 +360,9 @@ export class TS3Client extends EventEmitter {
|
|||||||
await this.client.sendCommandNoWait(cmd);
|
await this.client.sendCommandNoWait(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
async execCommandWithResponse(cmd: string): Promise<Record<string, string>[]> {
|
async execCommandWithResponse(
|
||||||
|
cmd: string,
|
||||||
|
): Promise<Record<string, string>[]> {
|
||||||
if (!this.client) throw new Error("Not connected");
|
if (!this.client) throw new Error("Not connected");
|
||||||
return this.client.execCommandWithResponse(cmd);
|
return this.client.execCommandWithResponse(cmd);
|
||||||
}
|
}
|
||||||
@@ -339,15 +375,28 @@ export class TS3Client extends EventEmitter {
|
|||||||
overwrite = true,
|
overwrite = true,
|
||||||
): Promise<FileUploadInfo> {
|
): Promise<FileUploadInfo> {
|
||||||
if (!this.client) throw new Error("Not connected");
|
if (!this.client) throw new Error("Not connected");
|
||||||
return this.client.fileTransferInitUpload(channelID, path, password, size, overwrite);
|
return this.client.fileTransferInitUpload(
|
||||||
|
channelID,
|
||||||
|
path,
|
||||||
|
password,
|
||||||
|
size,
|
||||||
|
overwrite,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadFileData(host: string, info: FileUploadInfo, data: Readable): Promise<void> {
|
async uploadFileData(
|
||||||
|
host: string,
|
||||||
|
info: FileUploadInfo,
|
||||||
|
data: Readable,
|
||||||
|
): Promise<void> {
|
||||||
if (!this.client) throw new Error("Not connected");
|
if (!this.client) throw new Error("Not connected");
|
||||||
await this.client.uploadFileData(host, info, data);
|
await this.client.uploadFileData(host, info, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fileTransferDeleteFile(channelID: bigint, paths: string[]): Promise<void> {
|
async fileTransferDeleteFile(
|
||||||
|
channelID: bigint,
|
||||||
|
paths: string[],
|
||||||
|
): Promise<void> {
|
||||||
if (!this.client) throw new Error("Not connected");
|
if (!this.client) throw new Error("Not connected");
|
||||||
await fileTransferDeleteFile(this.client, channelID, paths);
|
await fileTransferDeleteFile(this.client, channelID, paths);
|
||||||
}
|
}
|
||||||
@@ -371,7 +420,10 @@ export class TS3Client extends EventEmitter {
|
|||||||
this.client.sendVoice(opusFrame, 5);
|
this.client.sendVoice(opusFrame, 5);
|
||||||
this.voiceFramesSent++;
|
this.voiceFramesSent++;
|
||||||
if (this.voiceFramesSent === 1) {
|
if (this.voiceFramesSent === 1) {
|
||||||
this.logger.info({ opusBytes: opusFrame.length, clientId: this.clientId }, "First voice packet sent to TeamSpeak");
|
this.logger.info(
|
||||||
|
{ opusBytes: opusFrame.length, clientId: this.clientId },
|
||||||
|
"First voice packet sent to TeamSpeak",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (this.voiceFramesSent === 0) {
|
if (this.voiceFramesSent === 0) {
|
||||||
@@ -392,7 +444,10 @@ export class TS3Client extends EventEmitter {
|
|||||||
if (this.client && !this.disconnecting) {
|
if (this.client && !this.disconnecting) {
|
||||||
this.disconnecting = true;
|
this.disconnecting = true;
|
||||||
const client = this.client;
|
const client = this.client;
|
||||||
client.disconnect().catch(() => {}).finally(() => {
|
client
|
||||||
|
.disconnect()
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
if (this.client === client) {
|
if (this.client === client) {
|
||||||
this.client = null;
|
this.client = null;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -159,6 +159,7 @@ export function createBotRouter(
|
|||||||
serverPort,
|
serverPort,
|
||||||
nickname,
|
nickname,
|
||||||
defaultChannel,
|
defaultChannel,
|
||||||
|
channelId,
|
||||||
channelPassword,
|
channelPassword,
|
||||||
serverPassword,
|
serverPassword,
|
||||||
autoStart,
|
autoStart,
|
||||||
@@ -175,6 +176,7 @@ export function createBotRouter(
|
|||||||
serverPort: serverPort ?? 9987,
|
serverPort: serverPort ?? 9987,
|
||||||
nickname,
|
nickname,
|
||||||
defaultChannel,
|
defaultChannel,
|
||||||
|
channelId,
|
||||||
channelPassword,
|
channelPassword,
|
||||||
serverPassword,
|
serverPassword,
|
||||||
autoStart: autoStart ?? false,
|
autoStart: autoStart ?? false,
|
||||||
@@ -194,10 +196,10 @@ export function createBotRouter(
|
|||||||
res.status(404).json({ error: "Bot not found" });
|
res.status(404).json({ error: "Bot not found" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { name, serverAddress, serverPort, nickname, defaultChannel, channelPassword, serverPassword } = req.body;
|
const { name, serverAddress, serverPort, nickname, defaultChannel, channelId, channelPassword, serverPassword } = req.body;
|
||||||
// Update in database
|
// Update in database
|
||||||
botManager.updateBot(req.params.id, {
|
botManager.updateBot(req.params.id, {
|
||||||
name, serverAddress, serverPort, nickname, defaultChannel, channelPassword, serverPassword,
|
name, serverAddress, serverPort, nickname, defaultChannel, channelId, channelPassword, serverPassword,
|
||||||
});
|
});
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -98,8 +98,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>默认频道(可选)</label>
|
<label>默认频道名称(可选)</label>
|
||||||
<input v-model="editForm.defaultChannel" class="input" placeholder="音乐频道" />
|
<input v-model="editForm.defaultChannel" :disabled="!!editForm.channelId" class="input" :class="{ disabled: !!editForm.channelId }" placeholder="音乐频道" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>默认频道ID(可选)</label>
|
||||||
|
<input v-model="editForm.channelId" :disabled="!!editForm.defaultChannel" class="input" :class="{ disabled: !!editForm.defaultChannel }" placeholder="如 12" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>频道密码(可选)</label>
|
<label>频道密码(可选)</label>
|
||||||
@@ -142,8 +146,12 @@
|
|||||||
<input v-model="newBotNickname" class="input" placeholder="MusicBot" />
|
<input v-model="newBotNickname" class="input" placeholder="MusicBot" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>默认频道(可选)</label>
|
<label>默认频道名称(可选)</label>
|
||||||
<input v-model="newBotChannel" class="input" placeholder="音乐频道" />
|
<input v-model="newBotChannel" :disabled="!!newBotChannelId" class="input" :class="{ disabled: !!newBotChannelId }" placeholder="音乐频道" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>默认频道ID(可选)</label>
|
||||||
|
<input v-model="newBotChannelId" :disabled="!!newBotChannel" class="input" :class="{ disabled: !!newBotChannel }" placeholder="如 12" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>服务器密码(可选)</label>
|
<label>服务器密码(可选)</label>
|
||||||
@@ -685,6 +693,7 @@ const newBotServer = ref('');
|
|||||||
const newBotPort = ref(9987);
|
const newBotPort = ref(9987);
|
||||||
const newBotNickname = ref('MusicBot');
|
const newBotNickname = ref('MusicBot');
|
||||||
const newBotChannel = ref('');
|
const newBotChannel = ref('');
|
||||||
|
const newBotChannelId = ref('');
|
||||||
const newBotServerPassword = ref('');
|
const newBotServerPassword = ref('');
|
||||||
const newBotAvatar = ref<string | null>(null);
|
const newBotAvatar = ref<string | null>(null);
|
||||||
|
|
||||||
@@ -696,6 +705,7 @@ const editForm = reactive({
|
|||||||
serverPort: 9987,
|
serverPort: 9987,
|
||||||
nickname: '',
|
nickname: '',
|
||||||
defaultChannel: '',
|
defaultChannel: '',
|
||||||
|
channelId: '',
|
||||||
channelPassword: '',
|
channelPassword: '',
|
||||||
serverPassword: '',
|
serverPassword: '',
|
||||||
});
|
});
|
||||||
@@ -853,6 +863,7 @@ async function createBot() {
|
|||||||
serverPort: newBotPort.value || 9987,
|
serverPort: newBotPort.value || 9987,
|
||||||
nickname: newBotNickname.value || newBotName.value,
|
nickname: newBotNickname.value || newBotName.value,
|
||||||
defaultChannel: newBotChannel.value || undefined,
|
defaultChannel: newBotChannel.value || undefined,
|
||||||
|
channelId: newBotChannelId.value || undefined,
|
||||||
serverPassword: newBotServerPassword.value || undefined,
|
serverPassword: newBotServerPassword.value || undefined,
|
||||||
autoStart: false,
|
autoStart: false,
|
||||||
});
|
});
|
||||||
@@ -868,6 +879,7 @@ async function createBot() {
|
|||||||
newBotPort.value = 9987;
|
newBotPort.value = 9987;
|
||||||
newBotNickname.value = 'MusicBot';
|
newBotNickname.value = 'MusicBot';
|
||||||
newBotChannel.value = '';
|
newBotChannel.value = '';
|
||||||
|
newBotChannelId.value = '';
|
||||||
newBotServerPassword.value = '';
|
newBotServerPassword.value = '';
|
||||||
newBotAvatar.value = null;
|
newBotAvatar.value = null;
|
||||||
await store.fetchBots();
|
await store.fetchBots();
|
||||||
@@ -901,6 +913,7 @@ async function openEditBot(bot: any) {
|
|||||||
editForm.serverPort = res.data.serverPort ?? 9987;
|
editForm.serverPort = res.data.serverPort ?? 9987;
|
||||||
editForm.nickname = res.data.nickname ?? '';
|
editForm.nickname = res.data.nickname ?? '';
|
||||||
editForm.defaultChannel = res.data.defaultChannel ?? '';
|
editForm.defaultChannel = res.data.defaultChannel ?? '';
|
||||||
|
editForm.channelId = res.data.channelId ?? '';
|
||||||
editForm.channelPassword = res.data.channelPassword ?? '';
|
editForm.channelPassword = res.data.channelPassword ?? '';
|
||||||
editForm.serverPassword = res.data.serverPassword ?? '';
|
editForm.serverPassword = res.data.serverPassword ?? '';
|
||||||
} catch {
|
} catch {
|
||||||
@@ -909,6 +922,7 @@ async function openEditBot(bot: any) {
|
|||||||
editForm.serverPort = 9987;
|
editForm.serverPort = 9987;
|
||||||
editForm.nickname = bot.name;
|
editForm.nickname = bot.name;
|
||||||
editForm.defaultChannel = '';
|
editForm.defaultChannel = '';
|
||||||
|
editForm.channelId = '';
|
||||||
editForm.channelPassword = '';
|
editForm.channelPassword = '';
|
||||||
editForm.serverPassword = '';
|
editForm.serverPassword = '';
|
||||||
}
|
}
|
||||||
@@ -1635,6 +1649,10 @@ onUnmounted(() => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
outline: none;
|
outline: none;
|
||||||
&:focus { border-color: var(--color-primary); }
|
&:focus { border-color: var(--color-primary); }
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-sm { max-width: 80px; }
|
.input-sm { max-width: 80px; }
|
||||||
|
|||||||
+12
-2
@@ -46,8 +46,12 @@
|
|||||||
<input v-model="nickname" placeholder="MusicBot" class="input" />
|
<input v-model="nickname" placeholder="MusicBot" class="input" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>默认频道 (可选)</label>
|
<label>默认频道名称(可选)</label>
|
||||||
<input v-model="defaultChannel" placeholder="音乐频道" class="input" />
|
<input v-model="defaultChannel" :disabled="!!channelId" placeholder="音乐频道" class="input" :class="{ disabled: !!channelId }" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>默认频道ID(可选)</label>
|
||||||
|
<input v-model="channelId" :disabled="!!defaultChannel" placeholder="如 1234" class="input" :class="{ disabled: !!defaultChannel }" />
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-secondary" @click="currentStep = 0">上一步</button>
|
<button class="btn-secondary" @click="currentStep = 0">上一步</button>
|
||||||
@@ -87,6 +91,7 @@ const serverAddress = ref('');
|
|||||||
const serverPort = ref(9987);
|
const serverPort = ref(9987);
|
||||||
const nickname = ref('MusicBot');
|
const nickname = ref('MusicBot');
|
||||||
const defaultChannel = ref('');
|
const defaultChannel = ref('');
|
||||||
|
const channelId = ref('');
|
||||||
|
|
||||||
async function createBotAndNext() {
|
async function createBotAndNext() {
|
||||||
try {
|
try {
|
||||||
@@ -96,6 +101,7 @@ async function createBotAndNext() {
|
|||||||
serverPort: serverPort.value,
|
serverPort: serverPort.value,
|
||||||
nickname: nickname.value,
|
nickname: nickname.value,
|
||||||
defaultChannel: defaultChannel.value,
|
defaultChannel: defaultChannel.value,
|
||||||
|
channelId: channelId.value || undefined,
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
});
|
});
|
||||||
currentStep.value = 2;
|
currentStep.value = 2;
|
||||||
@@ -193,6 +199,10 @@ async function createBotAndNext() {
|
|||||||
&:focus {
|
&:focus {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
|
|||||||
Reference in new issue
Block a user