mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat: play-by-id and play-playlist endpoints, fix lyrics sync timing
- New endpoints: play-by-id, add-by-id, play-playlist (load all songs at once) - Playlist 'Play All' now uses server-side bulk load (no N sequential searches) - Search/Home/Playlist views use playById instead of search-by-name - Fixed updateBotStatus timing: capture prev state before mutation - Early return on song change prevents stale pause/resume logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2c87f1cff9
commit
2e4d6d8d40
6 files changed
+163
-20
No files matched your search
+117
-1
@@ -1,13 +1,16 @@
|
||||
import { Router } from "express";
|
||||
import type { BotManager } from "../../bot/manager.js";
|
||||
import type { BotDatabase } from "../../data/database.js";
|
||||
import type { MusicProvider } from "../../music/provider.js";
|
||||
import type { Logger } from "../../logger.js";
|
||||
import { parseCommand } from "../../bot/commands.js";
|
||||
|
||||
export function createPlayerRouter(
|
||||
botManager: BotManager,
|
||||
logger: Logger,
|
||||
database?: BotDatabase
|
||||
database?: BotDatabase,
|
||||
neteaseProvider?: MusicProvider,
|
||||
qqProvider?: MusicProvider,
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
@@ -133,6 +136,119 @@ export function createPlayerRouter(
|
||||
}
|
||||
});
|
||||
|
||||
// Play a playlist by ID — fetches all songs, gets URLs, loads into queue
|
||||
router.post("/:botId/play-playlist", async (req, res) => {
|
||||
try {
|
||||
const bot = (req as any).bot;
|
||||
const { playlistId, platform } = req.body;
|
||||
const provider = platform === "qq" ? qqProvider : neteaseProvider;
|
||||
if (!provider) {
|
||||
res.status(500).json({ error: "Provider not available" });
|
||||
return;
|
||||
}
|
||||
|
||||
const songs = await provider.getPlaylistSongs(playlistId);
|
||||
if (songs.length === 0) {
|
||||
res.json({ message: "Playlist is empty" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Get URL for first song and play it
|
||||
const queue = bot.getQueueManager();
|
||||
const player = bot.getPlayer();
|
||||
queue.clear();
|
||||
|
||||
let firstUrl: string | null = null;
|
||||
for (const song of songs) {
|
||||
const url = await provider.getSongUrl(song.id);
|
||||
if (url) {
|
||||
queue.add({ ...song, url, platform: provider.platform });
|
||||
if (!firstUrl) firstUrl = url;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstUrl) {
|
||||
queue.play();
|
||||
player.play(firstUrl);
|
||||
}
|
||||
|
||||
res.json({ message: `Loaded ${queue.size()} songs. Now playing: ${queue.current()?.name ?? "unknown"}` });
|
||||
} catch (err) {
|
||||
logger.error({ err }, "Play playlist failed");
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
// Play a single song by ID — no search needed
|
||||
router.post("/:botId/play-by-id", async (req, res) => {
|
||||
try {
|
||||
const bot = (req as any).bot;
|
||||
const { songId, platform } = req.body;
|
||||
const provider = platform === "qq" ? qqProvider : neteaseProvider;
|
||||
if (!provider) {
|
||||
res.status(500).json({ error: "Provider not available" });
|
||||
return;
|
||||
}
|
||||
|
||||
const [song, url] = await Promise.all([
|
||||
provider.getSongDetail(songId),
|
||||
provider.getSongUrl(songId),
|
||||
]);
|
||||
|
||||
if (!song || !url) {
|
||||
res.json({ message: "Cannot get song URL" });
|
||||
return;
|
||||
}
|
||||
|
||||
const queue = bot.getQueueManager();
|
||||
const player = bot.getPlayer();
|
||||
queue.clear();
|
||||
queue.add({ ...song, url, platform: provider.platform });
|
||||
queue.play();
|
||||
player.play(url);
|
||||
|
||||
res.json({ message: `Now playing: ${song.name} - ${song.artist}` });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
// Add a song to queue by ID
|
||||
router.post("/:botId/add-by-id", async (req, res) => {
|
||||
try {
|
||||
const bot = (req as any).bot;
|
||||
const { songId, platform } = req.body;
|
||||
const provider = platform === "qq" ? qqProvider : neteaseProvider;
|
||||
if (!provider) {
|
||||
res.status(500).json({ error: "Provider not available" });
|
||||
return;
|
||||
}
|
||||
|
||||
const [song, url] = await Promise.all([
|
||||
provider.getSongDetail(songId),
|
||||
provider.getSongUrl(songId),
|
||||
]);
|
||||
|
||||
if (!song || !url) {
|
||||
res.json({ message: "Cannot get song URL" });
|
||||
return;
|
||||
}
|
||||
|
||||
const queue = bot.getQueueManager();
|
||||
queue.add({ ...song, url, platform: provider.platform });
|
||||
|
||||
// If nothing is playing, start playing
|
||||
if (bot.getPlayer().getState() === "idle") {
|
||||
queue.play();
|
||||
bot.getPlayer().play(url);
|
||||
}
|
||||
|
||||
res.json({ message: `Added: ${song.name} - ${song.artist} (position ${queue.size()})` });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/:botId/history", (req, res) => {
|
||||
if (!database) {
|
||||
res.json({ history: [] });
|
||||
|
||||
+4
-1
@@ -46,7 +46,10 @@ export function createWebServer(options: WebServerOptions): WebServer {
|
||||
"/api/music",
|
||||
createMusicRouter(options.neteaseProvider, options.qqProvider, logger)
|
||||
);
|
||||
app.use("/api/player", createPlayerRouter(options.botManager, logger, options.database));
|
||||
app.use("/api/player", createPlayerRouter(
|
||||
options.botManager, logger, options.database,
|
||||
options.neteaseProvider, options.qqProvider,
|
||||
));
|
||||
app.use(
|
||||
"/api/auth",
|
||||
createAuthRouter(options.neteaseProvider, options.qqProvider, logger, options.cookieStore)
|
||||
|
||||
Reference in new issue
Block a user