diff --git a/src/bot/instance.ts b/src/bot/instance.ts index 0174207..febd20c 100755 --- a/src/bot/instance.ts +++ b/src/bot/instance.ts @@ -254,6 +254,8 @@ export class BotInstance extends EventEmitter { const AUDIO_COMMANDS = new Set([ "play", "add", + "playnext", + "pn", "next", "skip", "prev", @@ -270,6 +272,9 @@ export class BotInstance extends EventEmitter { return this.cmdPlay(cmd); case "add": return this.cmdAdd(cmd); + case "playnext": + case "pn": + return this.cmdPlayNext(cmd); case "pause": return this.cmdPause(); case "resume": @@ -428,6 +433,31 @@ export class BotInstance extends EventEmitter { return `Added to queue: ${song.name} - ${song.artist} (position ${this.queue.size()})`; } + private async cmdPlayNext(cmd: ParsedCommand): Promise { + if (!cmd.args) return "Usage: !playnext "; + const provider = this.getProvider(cmd.flags); + const result = await provider.search(cmd.args, 1); + if (result.songs.length === 0) + return `No results found for: ${cmd.args}`; + + const song = result.songs[0]; + const wasIdle = this.player.getState() === "idle"; + this.queue.addNext({ ...song, platform: provider.platform }); + + if (wasIdle) { + // Nothing playing — addNext fell through to push, promote and start. + this.queue.playAt(this.queue.size() - 1); + this.player.resetFailures(); + const ok = await this.resolveAndPlay(this.queue.current()!); + this.emit("stateChange"); + if (!ok) return `Cannot play: ${song.name}`; + return `Now playing: ${song.name} - ${song.artist}`; + } + + this.emit("stateChange"); + return `Up next: ${song.name} - ${song.artist}`; + } + private cmdPause(): string { this.player.pause(); this.emit("stateChange"); @@ -715,6 +745,7 @@ export class BotInstance extends EventEmitter { `${p}play -b — Search from BiliBili`, `${p}play -y — Search from YouTube (yt-dlp)`, `${p}add — Add to queue`, + `${p}playnext — Insert as next song (alias: ${p}pn)`, `${p}pause/resume — Pause/resume`, `${p}next/prev — Next/previous`, `${p}stop — Stop and clear queue`, diff --git a/src/web/api/player.ts b/src/web/api/player.ts index cb0e0ba..ae418c6 100644 --- a/src/web/api/player.ts +++ b/src/web/api/player.ts @@ -340,6 +340,40 @@ export function createPlayerRouter( } }); + // Insert a single song to play right after the current one. + // If nothing is playing, behaves like /play-song (start immediately). + router.post("/:botId/play-next-song", async (req, res) => { + try { + const bot = (req as any).bot; + const { song } = req.body; + if (!song || !song.id || !song.platform) { + res.status(400).json({ error: "song object with id and platform is required" }); + return; + } + const queue = bot.getQueueManager(); + const wasIdle = bot.getPlayer().getState() === "idle"; + queue.addNext(song); + + if (wasIdle) { + // No current playback — promote the just-added song to current + // and start it. addNext fell through to push, so it's the last item. + queue.playAt(queue.size() - 1); + bot.getPlayer().resetFailures(); + const ok = await bot.resolveAndPlay(queue.current()!); + if (!ok) { + res.json({ ok: false, message: `无法播放「${song.name || song.id}」(区域/版权限制)` }); + return; + } + res.json({ ok: true, message: `正在播放:${song.name || 'Unknown'} - ${song.artist || 'Unknown'}` }); + return; + } + + res.json({ ok: true, message: `已加入下一首:${song.name || 'Unknown'} - ${song.artist || 'Unknown'}` }); + } catch (err) { + res.status(500).json({ error: (err as Error).message }); + } + }); + router.post("/:botId/add-song", async (req, res) => { try { const bot = (req as any).bot;