feat: add BiliBili audio source integration

Implement BiliBiliProvider for video-as-audio playback using direct
BiliBili API calls (search, video info, DASH audio URL extraction).
Add QR code login support and cookie persistence. Update FFmpeg to
send Referer header for BiliBili CDN URLs. Extend platform union type
to "netease" | "qq" | "bilibili" across all interfaces. Add -b flag
for chat commands and B站 badge in web UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.6 committed 2026-03-30 15:14:27 +08:00
1 parent 7d329a0db1
commit a6881403ef
15 files changed
+425 -36

No files matched your search

+6 -2
View File
@@ -6,12 +6,14 @@ import type { Logger } from "../../logger.js";
export function createAuthRouter(
neteaseProvider: MusicProvider,
qqProvider: MusicProvider,
bilibiliProvider: MusicProvider,
logger: Logger,
cookieStore?: CookieStore
): Router {
const router = Router();
function getProvider(platform?: string): MusicProvider {
if (platform === "bilibili") return bilibiliProvider;
return platform === "qq" ? qqProvider : neteaseProvider;
}
@@ -55,7 +57,8 @@ export function createAuthRouter(
// When confirmed, persist cookie
if (status === "confirmed") {
const cookie = provider.getCookie();
const plat = (platform as string) === "qq" ? "qq" as const : "netease" as const;
const plat = (platform as string) === "bilibili" ? "bilibili" as const
: (platform as string) === "qq" ? "qq" as const : "netease" as const;
if (cookie && cookieStore) {
cookieStore.save(plat, cookie);
logger.info({ platform: plat }, "Cookie persisted to disk");
@@ -118,7 +121,8 @@ export function createAuthRouter(
}
const provider = getProvider(platform);
provider.setCookie(cookie);
const plat = platform === "qq" ? "qq" as const : "netease" as const;
const plat = platform === "bilibili" ? "bilibili" as const
: platform === "qq" ? "qq" as const : "netease" as const;
if (cookieStore) {
cookieStore.save(plat, cookie);
}
+9 -1
View File
@@ -5,11 +5,13 @@ import type { Logger } from "../../logger.js";
export function createMusicRouter(
neteaseProvider: MusicProvider,
qqProvider: MusicProvider,
bilibiliProvider: MusicProvider,
logger: Logger
): Router {
const router = Router();
function getProvider(platform?: string): MusicProvider {
if (platform === "bilibili") return bilibiliProvider;
return platform === "qq" ? qqProvider : neteaseProvider;
}
@@ -40,9 +42,10 @@ export function createMusicRouter(
return;
}
const parsedLimit = parseInt(limit as string) || 20;
const [neteaseResult, qqResult] = await Promise.allSettled([
const [neteaseResult, qqResult, bilibiliResult] = await Promise.allSettled([
neteaseProvider.search(q as string, parsedLimit),
qqProvider.search(q as string, parsedLimit),
bilibiliProvider.search(q as string, parsedLimit),
]);
const songs = [
@@ -50,6 +53,7 @@ export function createMusicRouter(
? neteaseResult.value.songs
: []),
...(qqResult.status === "fulfilled" ? qqResult.value.songs : []),
...(bilibiliResult.status === "fulfilled" ? bilibiliResult.value.songs : []),
];
res.json({ songs });
@@ -198,6 +202,7 @@ export function createMusicRouter(
res.json({
netease: neteaseProvider.getQuality(),
qq: qqProvider.getQuality(),
bilibili: bilibiliProvider.getQuality(),
});
});
@@ -214,6 +219,9 @@ export function createMusicRouter(
if (!platform || platform === "qq") {
qqProvider.setQuality(quality);
}
if (!platform || platform === "bilibili") {
bilibiliProvider.setQuality(quality);
}
logger.info({ quality, platform }, "Audio quality changed");
res.json({ success: true, quality });
});
+7 -6
View File
@@ -11,6 +11,7 @@ export function createPlayerRouter(
database?: BotDatabase,
neteaseProvider?: MusicProvider,
qqProvider?: MusicProvider,
bilibiliProvider?: MusicProvider,
): Router {
const router = Router();
@@ -32,7 +33,7 @@ export function createPlayerRouter(
res.status(400).json({ error: "query is required" });
return;
}
const flags = platform === "qq" ? "-q" : "";
const flags = platform === "bilibili" ? "-b" : platform === "qq" ? "-q" : "";
const cmd = parseCommand(`!play ${flags} ${query}`.trim(), "!");
if (!cmd) {
res.status(400).json({ error: "Invalid command" });
@@ -49,7 +50,7 @@ export function createPlayerRouter(
try {
const bot = (req as any).bot;
const { query, platform } = req.body;
const flags = platform === "qq" ? "-q" : "";
const flags = platform === "bilibili" ? "-b" : platform === "qq" ? "-q" : "";
const cmd = parseCommand(`!add ${flags} ${query}`.trim(), "!");
if (!cmd) {
res.status(400).json({ error: "Invalid command" });
@@ -173,7 +174,7 @@ export function createPlayerRouter(
try {
const bot = (req as any).bot;
const { playlistId, platform } = req.body;
const flags = platform === "qq" ? "-q" : "";
const flags = platform === "bilibili" ? "-b" : platform === "qq" ? "-q" : "";
const cmd = parseCommand(
`!playlist ${flags} ${playlistId}`.trim(),
"!"
@@ -191,7 +192,7 @@ export function createPlayerRouter(
try {
const bot = (req as any).bot;
const { playlistId, platform } = req.body;
const provider = platform === "qq" ? qqProvider : neteaseProvider;
const provider = platform === "bilibili" ? bilibiliProvider : platform === "qq" ? qqProvider : neteaseProvider;
if (!provider) {
res.status(500).json({ error: "Provider not available" });
return;
@@ -238,7 +239,7 @@ export function createPlayerRouter(
try {
const bot = (req as any).bot;
const { songId, platform } = req.body;
const provider = platform === "qq" ? qqProvider : neteaseProvider;
const provider = platform === "bilibili" ? bilibiliProvider : platform === "qq" ? qqProvider : neteaseProvider;
if (!provider) {
res.status(500).json({ error: "Provider not available" });
return;
@@ -272,7 +273,7 @@ export function createPlayerRouter(
try {
const bot = (req as any).bot;
const { songId, platform } = req.body;
const provider = platform === "qq" ? qqProvider : neteaseProvider;
const provider = platform === "bilibili" ? bilibiliProvider : platform === "qq" ? qqProvider : neteaseProvider;
if (!provider) {
res.status(500).json({ error: "Provider not available" });
return;