fix(bilibili): 修复了B站分P视频播放时只能播放第一P,且时长显示为视频总时长

在网页端播放多P视频时弹出界面选择需要播放的P数,ts里!play播放则只播放第一P
This commit is contained in:
xxmod committed 2026-09-22 17:10:54 +08:00
1 parent 2ea02f54d9
commit 3c0e8df763
9 files changed
+795 -16

No files matched your search

+52
View File
@@ -1538,3 +1538,55 @@ describe("BotInstance live-queue persistence (#119)", () => {
}
});
});
describe("BotInstance Bilibili multi-P resolution", () => {
it("resolves multi-P search result to P1 with accurate duration and name", async () => {
const multiPSongDetail = {
id: "BV1multiP?p=1",
name: "测试视频 - P1 分P1",
artist: "UP主",
album: "",
duration: 100, // P1 duration
coverUrl: "",
platform: "bilibili" as const,
};
const mockBili = {
platform: "bilibili" as const,
search: vi.fn().mockResolvedValue({
songs: [{
id: "BV1multiP",
name: "测试视频",
artist: "UP主",
album: "",
duration: 300, // total duration in search
coverUrl: "",
platform: "bilibili",
}],
albums: [],
playlists: [],
}),
getSongDetail: vi.fn().mockResolvedValue(multiPSongDetail),
getSongUrl: vi.fn().mockResolvedValue({ url: "http://audio.test" }),
};
const ctx = {
config: { commandPrefix: "!" },
lastSearchResults: [] as any[],
getProvider: () => mockBili,
getProviderFor: () => mockBili,
};
const res = await (BotInstance.prototype as any).resolvePlayQuery.call(ctx, {
name: "play",
args: "测试视频",
rawArgs: ["测试视频"],
flags: new Set(),
});
expect(res.song).toBeDefined();
expect(res.song.id).toBe("BV1multiP?p=1");
expect(res.song.name).toBe("测试视频 - P1 分P1");
expect(res.song.duration).toBe(100);
});
});
+20 -2
View File
@@ -968,6 +968,14 @@ export class BotInstance extends EventEmitter {
this.voteSkipUsers.clear();
const provider = this.getProviderFor(song.platform);
try {
if (song.platform === "bilibili" && (!song.id.includes("?p=") || song.duration === 0)) {
const detail = await provider.getSongDetail(song.id);
if (detail) {
song.duration = detail.duration;
song.name = detail.name;
song.id = detail.id;
}
}
const result = await provider.getSongUrl(song.id);
if (!result?.url) {
this.logger.warn({ songId: song.id, name: song.name }, "No URL available, skipping");
@@ -1134,7 +1142,12 @@ export class BotInstance extends EventEmitter {
return { error: `No recent search. Use ${p}search <name> first.` };
if (sel > this.lastSearchResults.length)
return { error: `Invalid selection #${sel}. ${p}search returned ${this.lastSearchResults.length} results.` };
return { song: this.lastSearchResults[sel - 1] };
let song = this.lastSearchResults[sel - 1];
if (song.platform === "bilibili") {
const detail = await this.getProviderFor("bilibili").getSongDetail(song.id);
if (detail) song = { ...detail, platform: "bilibili" };
}
return { song };
}
// 2) id/URL — fetch that exact song.
@@ -1151,7 +1164,12 @@ export class BotInstance extends EventEmitter {
const provider = this.getProvider(cmd.flags);
const result = await provider.search(args, 1);
if (result.songs.length === 0) return { error: `No results found for: ${args}` };
return { song: { ...result.songs[0], platform: provider.platform } };
let song = result.songs[0];
if (provider.platform === "bilibili") {
const detail = await provider.getSongDetail(song.id);
if (detail) song = detail;
}
return { song: { ...song, platform: provider.platform } };
}
private async cmdSearch(cmd: ParsedCommand): Promise<string> {