Merge pull request #421 from pumpkinperson996/fix/normalize-bilibili-url

fix: 规范化含 BV 号的任意 B 站链接(稍后再看/收藏夹/追踪参数)
This commit is contained in:
Jianwu Huang
2026-08-25 13:57:02 +08:00
committed by GitHub
4 changed files with 111 additions and 3 deletions
+11 -2
View File
@@ -7,7 +7,7 @@ from typing import Optional
from urllib.parse import urlparse
from fastapi import APIRouter, HTTPException, BackgroundTasks, UploadFile, File
from pydantic import BaseModel, validator, field_validator
from pydantic import BaseModel, validator, field_validator, model_validator
from dataclasses import asdict
from app.db.video_task_dao import get_task_by_video
@@ -17,7 +17,7 @@ from app.exceptions.note import NoteError
from app.services.note import NoteGenerator, logger
from app.services.task_serial_executor import task_serial_executor
from app.utils.response import ResponseWrapper as R
from app.utils.url_parser import extract_video_id
from app.utils.url_parser import extract_video_id, normalize_video_url
from app.validators.video_url_validator import is_supported_video_url
from fastapi import APIRouter, Request, HTTPException
from fastapi.responses import StreamingResponse
@@ -55,6 +55,15 @@ class VideoRequest(BaseModel):
# {"language": "zh", "full_text": "...", "segments": [{"start","end","text"}, ...]}
prefetched_transcript: Optional[dict] = None
@model_validator(mode="before")
@classmethod
def normalize_url(cls, data):
# 稍后再看/收藏夹/带追踪参数的 B 站链接先规范化成标准 /video/BVxxx 形式,
# 后续校验和 yt-dlp 下载拿到的都是干净链接
if isinstance(data, dict) and data.get("platform") == "bilibili" and data.get("video_url"):
data["video_url"] = normalize_video_url(str(data["video_url"]))
return data
@field_validator("video_url")
def validate_supported_url(cls, v):
url = str(v)
+23
View File
@@ -35,6 +35,29 @@ def extract_video_id(url: str, platform: str) -> Optional[str]:
return None
def normalize_video_url(url: str) -> str:
"""
将任意包含 BV 号的 B 站链接规范化为标准视频链接。
支持稍后再看(/list/watchlater/?bvid=BV...)、收藏夹播放页(/list/mlXXX?bvid=BV...)、
带追踪参数的分享链接等。保留分 P 参数,丢弃其余查询参数。
b23.tv 短链与无 BV 号的链接原样返回(后者交由校验器拒绝)。
"""
if "b23.tv" in url:
return url
match = re.search(r"BV([0-9A-Za-z]+)", url)
if not match:
return url
normalized = f"https://www.bilibili.com/video/BV{match.group(1)}"
p = extract_bilibili_p_number(url)
if p:
normalized += f"?p={p}"
return normalized
def resolve_bilibili_short_url(short_url: str) -> Optional[str]:
"""
解析哔哩哔哩短链接以获取真实视频链接
+10 -1
View File
@@ -1,7 +1,9 @@
from pydantic import AnyUrl, validator, BaseModel, field_validator
from pydantic import AnyUrl, validator, BaseModel, field_validator, model_validator
import re
from urllib.parse import urlparse
from app.utils.url_parser import normalize_video_url
SUPPORTED_PLATFORMS = {
"bilibili": r"(https?://)?(www\.)?bilibili\.com/video/[a-zA-Z0-9]+",
"youtube": r"(https?://)?(www\.)?(youtube\.com/(watch\?v=|shorts/)|youtu\.be/)[\w\-]+",
@@ -31,6 +33,13 @@ class VideoRequest(BaseModel):
url: AnyUrl
platform: str
@model_validator(mode="before")
@classmethod
def normalize_url(cls, data):
if isinstance(data, dict) and data.get("platform") == "bilibili" and data.get("url"):
data["url"] = normalize_video_url(str(data["url"]))
return data
@field_validator("url")
def validate_video_url(cls, v):
if not is_supported_video_url(str(v)):