mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat: !playnext command and /play-next-song endpoint
Mirror of !play / /play-song but uses queue.addNext to splice in at currentIndex+1 instead of clearing the queue. Idle bot still starts the song immediately. Adds 'playnext' / 'pn' to AUDIO_COMMANDS, the command switch, and the help text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
11c6a3f51b
commit
3e795d9a83
2 files changed
+65
No files matched your search
@@ -254,6 +254,8 @@ export class BotInstance extends EventEmitter {
|
|||||||
const AUDIO_COMMANDS = new Set([
|
const AUDIO_COMMANDS = new Set([
|
||||||
"play",
|
"play",
|
||||||
"add",
|
"add",
|
||||||
|
"playnext",
|
||||||
|
"pn",
|
||||||
"next",
|
"next",
|
||||||
"skip",
|
"skip",
|
||||||
"prev",
|
"prev",
|
||||||
@@ -270,6 +272,9 @@ export class BotInstance extends EventEmitter {
|
|||||||
return this.cmdPlay(cmd);
|
return this.cmdPlay(cmd);
|
||||||
case "add":
|
case "add":
|
||||||
return this.cmdAdd(cmd);
|
return this.cmdAdd(cmd);
|
||||||
|
case "playnext":
|
||||||
|
case "pn":
|
||||||
|
return this.cmdPlayNext(cmd);
|
||||||
case "pause":
|
case "pause":
|
||||||
return this.cmdPause();
|
return this.cmdPause();
|
||||||
case "resume":
|
case "resume":
|
||||||
@@ -428,6 +433,31 @@ export class BotInstance extends EventEmitter {
|
|||||||
return `Added to queue: ${song.name} - ${song.artist} (position ${this.queue.size()})`;
|
return `Added to queue: ${song.name} - ${song.artist} (position ${this.queue.size()})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async cmdPlayNext(cmd: ParsedCommand): Promise<string> {
|
||||||
|
if (!cmd.args) return "Usage: !playnext <song name>";
|
||||||
|
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 {
|
private cmdPause(): string {
|
||||||
this.player.pause();
|
this.player.pause();
|
||||||
this.emit("stateChange");
|
this.emit("stateChange");
|
||||||
@@ -715,6 +745,7 @@ export class BotInstance extends EventEmitter {
|
|||||||
`${p}play -b <song> — Search from BiliBili`,
|
`${p}play -b <song> — Search from BiliBili`,
|
||||||
`${p}play -y <song> — Search from YouTube (yt-dlp)`,
|
`${p}play -y <song> — Search from YouTube (yt-dlp)`,
|
||||||
`${p}add <song> — Add to queue`,
|
`${p}add <song> — Add to queue`,
|
||||||
|
`${p}playnext <song> — Insert as next song (alias: ${p}pn)`,
|
||||||
`${p}pause/resume — Pause/resume`,
|
`${p}pause/resume — Pause/resume`,
|
||||||
`${p}next/prev — Next/previous`,
|
`${p}next/prev — Next/previous`,
|
||||||
`${p}stop — Stop and clear queue`,
|
`${p}stop — Stop and clear queue`,
|
||||||
|
|||||||
@@ -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) => {
|
router.post("/:botId/add-song", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const bot = (req as any).bot;
|
const bot = (req as any).bot;
|
||||||
|
|||||||
Reference in new issue
Block a user