From eb2606f120a60ecfc9ad2075034c62a600686644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=80=E1=B4=8D=E1=B4=9B=E1=B4=8F=E1=B4=80=E1=B4=87?= =?UTF-8?q?=CA=80?= Date: Sun, 12 Oct 2025 03:01:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8A=A0=E5=85=A5=E5=85=85=E7=94=B5?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=92=8C=E7=95=AA=E5=89=A7=E3=80=81=E5=BD=B1?= =?UTF-8?q?=E8=A7=86=E5=88=A4=E6=96=AD=EF=BC=8C=E5=90=8C=E6=97=B6=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20category=20=E8=A2=AB=E9=94=99=E8=AF=AF=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E7=9A=84=E9=97=AE=E9=A2=98=20(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bili_sync/src/bilibili/mod.rs | 57 +++++++++++++++++++++++++++ crates/bili_sync/src/utils/convert.rs | 12 +++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/crates/bili_sync/src/bilibili/mod.rs b/crates/bili_sync/src/bilibili/mod.rs index 0f36bce..16dad5e 100644 --- a/crates/bili_sync/src/bilibili/mod.rs +++ b/crates/bili_sync/src/bilibili/mod.rs @@ -78,6 +78,9 @@ pub enum VideoInfo { ctime: DateTime, #[serde(rename = "pubdate", with = "ts_seconds")] pubtime: DateTime, + is_upower_exclusive: bool, + is_upower_play: bool, + redirect_url: Option, pages: Vec, state: i32, }, @@ -250,4 +253,58 @@ mod tests { } Ok(()) } + + #[ignore = "only for manual test"] + #[tokio::test] + async fn test_upower_parse() -> Result<()> { + VersionedConfig::init_for_test(&setup_database(Path::new("./test.sqlite")).await?).await?; + let bili_client = BiliClient::new(); + let Ok(Some(mixin_key)) = bili_client.wbi_img().await.map(|wbi_img| wbi_img.into()) else { + panic!("获取 mixin key 失败"); + }; + set_global_mixin_key(mixin_key); + for (bvid, (upower_exclusive, upower_play)) in [ + ("BV1HxXwYEEqt", (true, false)), // 充电专享且无权观看 + ("BV16w41187fx", (true, true)), // 充电专享但有权观看 + ("BV1n34jzPEYq", (false, false)), // 普通视频 + ] { + let video = Video::new(&bili_client, bvid.to_string()); + let info = video.get_view_info().await?; + let VideoInfo::Detail { + is_upower_exclusive, + is_upower_play, + .. + } = info + else { + unreachable!(); + }; + assert_eq!(is_upower_exclusive, upower_exclusive, "bvid: {}", bvid); + assert_eq!(is_upower_play, upower_play, "bvid: {}", bvid); + } + Ok(()) + } + + #[ignore = "only for manual test"] + #[tokio::test] + async fn test_ep_parse() -> Result<()> { + VersionedConfig::init_for_test(&setup_database(Path::new("./test.sqlite")).await?).await?; + let bili_client = BiliClient::new(); + let Ok(Some(mixin_key)) = bili_client.wbi_img().await.map(|wbi_img| wbi_img.into()) else { + panic!("获取 mixin key 失败"); + }; + set_global_mixin_key(mixin_key); + for (bvid, redirect_is_none) in [ + ("BV1SF411g796", false), // EP + ("BV13xtnzPEye", false), // 番剧 + ("BV1kT4NzTEZj", true), // 普通视频 + ] { + let video = Video::new(&bili_client, bvid.to_string()); + let info = video.get_view_info().await?; + let VideoInfo::Detail { redirect_url, .. } = info else { + unreachable!(); + }; + assert_eq!(redirect_url.is_none(), redirect_is_none, "bvid: {}", bvid); + } + Ok(()) + } } diff --git a/crates/bili_sync/src/utils/convert.rs b/crates/bili_sync/src/utils/convert.rs index f4e75ff..c1d0a40 100644 --- a/crates/bili_sync/src/utils/convert.rs +++ b/crates/bili_sync/src/utils/convert.rs @@ -130,11 +130,13 @@ impl VideoInfo { ctime, pubtime, state, + is_upower_exclusive, + is_upower_play, + redirect_url, .. } => bili_sync_entity::video::ActiveModel { bvid: Set(bvid), name: Set(title), - category: Set(2), intro: Set(intro), cover: Set(cover), ctime: Set(ctime.naive_utc()), @@ -145,7 +147,13 @@ impl VideoInfo { Set(pubtime.naive_utc()) // 未设置过 favtime,使用 pubtime 填充 }, download_status: Set(0), - valid: Set(state == 0), + // state == 0 表示开放浏览 + // is_upower_exclusive 和 is_upower_play 相等有两种情况: + // 1. 都为 true,表示视频是充电专享但是已经充过电,有权观看 + // 2. 都为 false,表示视频是非充电视频 + // redirect_url 仅在视频为番剧、影视、纪录片等特殊视频时才会有值,如果为空说明是普通视频 + // 仅在三种条件都满足时,才认为视频是可下载的 + valid: Set(state == 0 && (is_upower_exclusive == is_upower_play) && redirect_url.is_none()), upper_id: Set(upper.mid), upper_name: Set(upper.name), upper_face: Set(upper.face),