mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
feat(spotify): wire provider through manager/instance; skip playback sentinel
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
848b3931b8
commit
9a9f68c446
4 files changed
+44
-2
No files matched your search
@@ -210,3 +210,11 @@ describe("BotInstance.handleTextMessage — command permission gate", () => {
|
||||
expect(ctx.executeCommand).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("BotInstance.getProviderFor — spotify routing", () => {
|
||||
it("getProviderFor routes 'spotify' to the injected spotify provider", () => {
|
||||
const spotify = { platform: "spotify" } as any;
|
||||
const ctx = { spotifyProvider: spotify, neteaseProvider: { platform: "netease" } } as any;
|
||||
expect(BotInstance.prototype.getProviderFor.call(ctx, "spotify" as any)).toBe(spotify);
|
||||
});
|
||||
});
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
occupancyFromClientList,
|
||||
shouldResumeOnReturn,
|
||||
} from "./auto-pause.js";
|
||||
import { isSpotifyUri } from "../music/spotify/webapi.js";
|
||||
|
||||
/** Reply sent when a non-admin invokes an admin-only chat command. */
|
||||
export const COMMAND_DENIED_MESSAGE = "⛔ 需要管理员权限(该命令仅限管理员服务器组)";
|
||||
@@ -37,6 +38,7 @@ export interface BotInstanceOptions {
|
||||
youtubeProvider: MusicProvider;
|
||||
localProvider?: MusicProvider;
|
||||
kugouProvider?: MusicProvider;
|
||||
spotifyProvider?: MusicProvider;
|
||||
database: BotDatabase;
|
||||
config: BotConfig;
|
||||
logger: Logger;
|
||||
@@ -71,6 +73,7 @@ export class BotInstance extends EventEmitter {
|
||||
private youtubeProvider: MusicProvider;
|
||||
private localProvider: MusicProvider;
|
||||
private kugouProvider: MusicProvider;
|
||||
private spotifyProvider: MusicProvider;
|
||||
private database: BotDatabase;
|
||||
private config: BotConfig;
|
||||
private logger: Logger;
|
||||
@@ -101,6 +104,7 @@ export class BotInstance extends EventEmitter {
|
||||
this.youtubeProvider = options.youtubeProvider;
|
||||
this.localProvider = options.localProvider ?? options.neteaseProvider;
|
||||
this.kugouProvider = options.kugouProvider ?? options.neteaseProvider;
|
||||
this.spotifyProvider = options.spotifyProvider ?? options.neteaseProvider;
|
||||
this.database = options.database;
|
||||
this.config = options.config;
|
||||
this.logger = options.logger.child({ botId: this.id });
|
||||
@@ -532,6 +536,7 @@ export class BotInstance extends EventEmitter {
|
||||
if (platform === "youtube") return this.youtubeProvider;
|
||||
if (platform === "local") return this.localProvider;
|
||||
if (platform === "kugou") return this.kugouProvider;
|
||||
if (platform === "spotify") return this.spotifyProvider;
|
||||
return platform === "qq" ? this.qqProvider : this.neteaseProvider;
|
||||
}
|
||||
|
||||
@@ -545,6 +550,7 @@ export class BotInstance extends EventEmitter {
|
||||
if (flags.has("q")) return this.qqProvider;
|
||||
if (flags.has("y")) return this.youtubeProvider;
|
||||
if (flags.has("k")) return this.kugouProvider;
|
||||
if (flags.has("s")) return this.spotifyProvider;
|
||||
return this.neteaseProvider;
|
||||
}
|
||||
|
||||
@@ -581,6 +587,17 @@ export class BotInstance extends EventEmitter {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Stage 1: Spotify metadata works but audio is not wired yet. getSongUrl
|
||||
// returns a `spotify:` sentinel — never hand it to ffmpeg. Tell the user
|
||||
// and skip so the queue keeps moving. `sendTextMessage` is the same
|
||||
// channel-message helper the command handlers use elsewhere in this file.
|
||||
if (isSpotifyUri(result.url)) {
|
||||
this.logger.info({ songId: song.id, name: song.name }, "Spotify playback not enabled yet — skipping");
|
||||
await this.tsClient.sendTextMessage(
|
||||
"⚠️ Spotify 播放尚未启用(需要 librespot 音频后端,将在后续版本支持)。"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
song.url = result.url;
|
||||
// 试听片段用试听时长(让 player nearEnd 正确触发自动切歌);完整曲回退 song.duration
|
||||
this.effectiveDuration = result.trialDuration ?? song.duration;
|
||||
|
||||
+7
-1
@@ -76,6 +76,7 @@ export class BotManager extends EventEmitter {
|
||||
private youtubeProvider: MusicProvider;
|
||||
private localProvider: MusicProvider;
|
||||
private kugouProvider: MusicProvider;
|
||||
private spotifyProvider: MusicProvider;
|
||||
private database: BotDatabase;
|
||||
private config: BotConfig;
|
||||
private logger: Logger;
|
||||
@@ -94,7 +95,8 @@ export class BotManager extends EventEmitter {
|
||||
permissions: PermissionStore,
|
||||
configPath: string,
|
||||
localProvider?: MusicProvider,
|
||||
kugouProvider?: MusicProvider
|
||||
kugouProvider?: MusicProvider,
|
||||
spotifyProvider?: MusicProvider
|
||||
) {
|
||||
super();
|
||||
this.neteaseProvider = neteaseProvider;
|
||||
@@ -103,6 +105,7 @@ export class BotManager extends EventEmitter {
|
||||
this.youtubeProvider = new YouTubeProvider();
|
||||
this.localProvider = localProvider ?? neteaseProvider;
|
||||
this.kugouProvider = kugouProvider ?? neteaseProvider;
|
||||
this.spotifyProvider = spotifyProvider ?? neteaseProvider;
|
||||
// Let the local provider see which uploads are still referenced by any
|
||||
// bot's queue, so it never deletes a file another queue/bot still needs.
|
||||
const referenceable = this.localProvider as Partial<{
|
||||
@@ -141,6 +144,7 @@ export class BotManager extends EventEmitter {
|
||||
youtubeProvider: this.youtubeProvider,
|
||||
localProvider: this.localProvider,
|
||||
kugouProvider: this.kugouProvider,
|
||||
spotifyProvider: this.spotifyProvider,
|
||||
database: this.database,
|
||||
config: this.config,
|
||||
logger: this.logger,
|
||||
@@ -281,6 +285,7 @@ export class BotManager extends EventEmitter {
|
||||
youtubeProvider: this.youtubeProvider,
|
||||
localProvider: this.localProvider,
|
||||
kugouProvider: this.kugouProvider,
|
||||
spotifyProvider: this.spotifyProvider,
|
||||
database: this.database,
|
||||
config: this.config,
|
||||
logger: this.logger,
|
||||
@@ -335,6 +340,7 @@ export class BotManager extends EventEmitter {
|
||||
youtubeProvider: this.youtubeProvider,
|
||||
localProvider: this.localProvider,
|
||||
kugouProvider: this.kugouProvider,
|
||||
spotifyProvider: this.spotifyProvider,
|
||||
database: this.database,
|
||||
config: this.config,
|
||||
logger: this.logger,
|
||||
|
||||
+12
-1
@@ -9,6 +9,7 @@ import { QQMusicProvider } from "./music/qq.js";
|
||||
import { BiliBiliProvider } from "./music/bilibili.js";
|
||||
import { LocalMusicProvider } from "./music/local.js";
|
||||
import { KugouProvider } from "./music/kugou.js";
|
||||
import { SpotifyProvider } from "./music/spotify/provider.js";
|
||||
import { createCookieStore } from "./music/auth.js";
|
||||
import { createAvatarStore } from "./data/avatars.js";
|
||||
import { createPermissionStore } from "./data/permissions.js";
|
||||
@@ -59,6 +60,14 @@ async function main() {
|
||||
const bilibiliProvider = new BiliBiliProvider();
|
||||
const localProvider = new LocalMusicProvider(LOCAL_AUDIO_DIR);
|
||||
const kugouProvider = new KugouProvider();
|
||||
const spotifyProvider = new SpotifyProvider();
|
||||
// Safety gate (spec §7): the source is inert unless EXPLICITLY enabled.
|
||||
// Only feed credentials when enabled — otherwise the provider has no creds,
|
||||
// hasCreds() is false, search returns empty, and getAuthStatus() is loggedIn:false,
|
||||
// so setting a Client ID/Secret alone (enabled:false) never activates Spotify.
|
||||
if (config.spotify.enabled && config.spotify.clientId) {
|
||||
spotifyProvider.setCreds(config.spotify.clientId, config.spotify.clientSecret);
|
||||
}
|
||||
|
||||
const cookieStore = createCookieStore(COOKIE_DIR);
|
||||
const avatarStore = createAvatarStore(AVATAR_DIR);
|
||||
@@ -84,7 +93,8 @@ async function main() {
|
||||
permissions,
|
||||
CONFIG_PATH,
|
||||
localProvider,
|
||||
kugouProvider
|
||||
kugouProvider,
|
||||
spotifyProvider
|
||||
);
|
||||
await botManager.loadSavedBots();
|
||||
|
||||
@@ -96,6 +106,7 @@ async function main() {
|
||||
bilibiliProvider,
|
||||
localProvider,
|
||||
kugouProvider,
|
||||
spotifyProvider,
|
||||
database: db,
|
||||
avatarStore,
|
||||
config,
|
||||
|
||||
Reference in new issue
Block a user