diff --git a/Cargo.lock b/Cargo.lock index e9c4071..3a2fc52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,12 +145,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - [[package]] name = "async-compression" version = "0.4.11" @@ -354,7 +348,6 @@ version = "2.7.0" dependencies = [ "anyhow", "arc-swap", - "assert_matches", "async-stream", "async-tempfile", "axum", @@ -365,7 +358,6 @@ dependencies = [ "chrono", "clap", "cookie", - "cow-utils", "dashmap", "dirs", "enum_dispatch", @@ -711,12 +703,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "cow-utils" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" - [[package]] name = "cpufeatures" version = "0.2.12" diff --git a/Cargo.toml b/Cargo.toml index 8651424..146b53f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,8 @@ bili_sync_migration = { path = "crates/bili_sync_migration" } anyhow = { version = "1.0.100", features = ["backtrace"] } arc-swap = { version = "1.7.1", features = ["serde"] } -assert_matches = "1.5.0" async-stream = "0.3.6" -async-tempfile = {version = "0.7.0", features = ["uuid"]} +async-tempfile = { version = "0.7.0", features = ["uuid"] } async-trait = "0.1.89" axum = { version = "0.8.6", features = ["macros", "ws"] } base64 = "0.22.1" @@ -27,7 +26,6 @@ built = { version = "0.7.7", features = ["git2", "chrono"] } chrono = { version = "0.4.42", features = ["serde"] } clap = { version = "4.5.48", features = ["env", "string"] } cookie = "0.18.1" -cow-utils = "0.1.3" dashmap = "6.1.0" derivative = "2.2.0" dirs = "6.0.0" diff --git a/crates/bili_sync/Cargo.toml b/crates/bili_sync/Cargo.toml index de5d35c..27f689c 100644 --- a/crates/bili_sync/Cargo.toml +++ b/crates/bili_sync/Cargo.toml @@ -21,7 +21,6 @@ bili_sync_migration = { workspace = true } chrono = { workspace = true } clap = { workspace = true } cookie = { workspace = true } -cow-utils = { workspace = true } dashmap = { workspace = true } dirs = { workspace = true } enum_dispatch = { workspace = true } @@ -59,9 +58,6 @@ ua_generator = { workspace = true } uuid = { workspace = true } validator = { workspace = true } -[dev-dependencies] -assert_matches = { workspace = true } - [build-dependencies] built = { workspace = true } git2 = { workspace = true } diff --git a/crates/bili_sync/src/bilibili/collection.rs b/crates/bili_sync/src/bilibili/collection.rs index 75301c0..9f59b36 100644 --- a/crates/bili_sync/src/bilibili/collection.rs +++ b/crates/bili_sync/src/bilibili/collection.rs @@ -7,8 +7,7 @@ use reqwest::Method; use serde::Deserialize; use serde_json::Value; -use crate::bilibili::credential::encoded_query; -use crate::bilibili::{BiliClient, MIXIN_KEY, Validate, VideoInfo}; +use crate::bilibili::{BiliClient, Validate, VideoInfo}; #[derive(PartialEq, Eq, Hash, Clone, Debug, Default, Copy)] pub enum CollectionType { @@ -139,46 +138,35 @@ impl<'a> Collection<'a> { } async fn get_videos(&self, page: i32) -> Result { - let page = page.to_string(); - let (url, query) = match self.collection.collection_type { - CollectionType::Series => ( - "https://api.bilibili.com/x/series/archives", - encoded_query( - vec![ - ("mid", self.collection.mid.as_str()), - ("series_id", self.collection.sid.as_str()), - ("only_normal", "true"), - ("sort", "desc"), - ("pn", page.as_str()), - ("ps", "30"), - ], - MIXIN_KEY.load().as_deref(), - ), - ), - CollectionType::Season => ( - "https://api.bilibili.com/x/polymer/web-space/seasons_archives_list", - encoded_query( - vec![ - ("mid", self.collection.mid.as_str()), - ("season_id", self.collection.sid.as_str()), - ("sort_reverse", "true"), - ("page_num", page.as_str()), - ("page_size", "30"), - ], - MIXIN_KEY.load().as_deref(), - ), - ), + let req = match self.collection.collection_type { + CollectionType::Series => self + .client + .request(Method::GET, "https://api.bilibili.com/x/series/archives") + .await + .query(&[("pn", page)]) + .query(&[ + ("mid", self.collection.mid.as_str()), + ("series_id", self.collection.sid.as_str()), + ("only_normal", "true"), + ("sort", "desc"), + ("ps", "30"), + ]), + CollectionType::Season => self + .client + .request( + Method::GET, + "https://api.bilibili.com/x/polymer/web-space/seasons_archives_list", + ) + .await + .query(&[("page_num", page)]) + .query(&[ + ("mid", self.collection.mid.as_str()), + ("season_id", self.collection.sid.as_str()), + ("sort_reverse", "true"), + ("page_size", "30"), + ]), }; - self.client - .request(Method::GET, url) - .await - .query(&query) - .send() - .await? - .error_for_status()? - .json::() - .await? - .validate() + req.send().await?.error_for_status()?.json::().await?.validate() } pub fn into_video_stream(self) -> impl Stream> + 'a { diff --git a/crates/bili_sync/src/bilibili/credential.rs b/crates/bili_sync/src/bilibili/credential.rs index 8669900..594394b 100644 --- a/crates/bili_sync/src/bilibili/credential.rs +++ b/crates/bili_sync/src/bilibili/credential.rs @@ -1,9 +1,7 @@ -use std::borrow::Cow; use std::collections::HashSet; use anyhow::{Context, Result, bail, ensure}; use cookie::Cookie; -use cow_utils::CowUtils; use regex::Regex; use reqwest::{Method, header}; use rsa::pkcs8::DecodePublicKey; @@ -30,17 +28,13 @@ pub struct Credential { #[derive(Debug, Deserialize)] pub struct WbiImg { - img_url: String, - sub_url: String, + pub(crate) img_url: String, + pub(crate) sub_url: String, } -impl From for Option { - /// 尝试将 WbiImg 转换成 mixin_key - fn from(value: WbiImg) -> Self { - let key = match ( - get_filename(value.img_url.as_str()), - get_filename(value.sub_url.as_str()), - ) { +impl WbiImg { + pub fn into_mixin_key(self) -> Option { + let key = match (get_filename(self.img_url.as_str()), get_filename(self.sub_url.as_str())) { (Some(img_key), Some(sub_key)) => img_key.to_string() + sub_key, _ => return None, }; @@ -213,47 +207,8 @@ fn get_filename(url: &str) -> Option<&str> { .map(|(s, _)| s) } -pub fn encoded_query<'a>( - params: Vec<(&'a str, impl Into>)>, - mixin_key: Option>, -) -> Vec<(&'a str, Cow<'a, str>)> { - match mixin_key { - Some(key) => _encoded_query(params, key.as_ref(), chrono::Local::now().timestamp().to_string()), - None => params.into_iter().map(|(k, v)| (k, v.into())).collect(), - } -} - -fn _encoded_query<'a>( - params: Vec<(&'a str, impl Into>)>, - mixin_key: &str, - timestamp: String, -) -> Vec<(&'a str, Cow<'a, str>)> { - let disallowed = ['!', '\'', '(', ')', '*']; - let mut params: Vec<(&'a str, Cow<'a, str>)> = params - .into_iter() - .map(|(k, v)| { - ( - k, - match Into::>::into(v) { - Cow::Borrowed(v) => v.cow_replace(&disallowed[..], ""), - Cow::Owned(v) => v.replace(&disallowed[..], "").into(), - }, - ) - }) - .collect(); - params.push(("wts", timestamp.into())); - params.sort_by(|a, b| a.0.cmp(b.0)); - let query = serde_urlencoded::to_string(¶ms) - .expect("fail to encode query") - .replace('+', "%20"); - params.push(("w_rid", format!("{:x}", md5::compute(query.clone() + mixin_key)).into())); - params -} - #[cfg(test)] mod tests { - use assert_matches::assert_matches; - use super::*; #[test] @@ -283,56 +238,4 @@ mod tests { "bar=%E4%BA%94%E4%B8%80%E5%9B%9B&baz=1919810&foo=one%20one%20four" ); } - - #[test] - fn test_wbi_key() { - let key = WbiImg { - img_url: "https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png".to_string(), - sub_url: "https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png".to_string(), - }; - let key = Option::::from(key).expect("fail to convert key"); - assert_eq!(key.as_str(), "ea1db124af3c7062474693fa704f4ff8"); - // 没有特殊字符 - assert_matches!( - &_encoded_query( - vec![("foo", "114"), ("bar", "514"), ("zab", "1919810")], - key.as_str(), - "1702204169".to_string(), - )[..], - [ - ("bar", Cow::Borrowed(a)), - ("foo", Cow::Borrowed(b)), - ("wts", Cow::Owned(c)), - ("zab", Cow::Borrowed(d)), - ("w_rid", Cow::Owned(e)), - ] => { - assert_eq!(*a, "514"); - assert_eq!(*b, "114"); - assert_eq!(c, "1702204169"); - assert_eq!(*d, "1919810"); - assert_eq!(e, "8f6f2b5b3d485fe1886cec6a0be8c5d4"); - } - ); - // 有特殊字符 - assert_matches!( - &_encoded_query( - vec![("foo", "'1(1)4'"), ("bar", "!5*1!14"), ("zab", "1919810")], - key.as_str(), - "1702204169".to_string(), - )[..], - [ - ("bar", Cow::Owned(a)), - ("foo", Cow::Owned(b)), - ("wts", Cow::Owned(c)), - ("zab", Cow::Borrowed(d)), - ("w_rid", Cow::Owned(e)), - ] => { - assert_eq!(a, "5114"); - assert_eq!(b, "114"); - assert_eq!(c, "1702204169"); - assert_eq!(*d, "1919810"); - assert_eq!(e, "6a2c86c4b0648ce062ba0dac2de91a85"); - } - ); - } } diff --git a/crates/bili_sync/src/bilibili/dynamic.rs b/crates/bili_sync/src/bilibili/dynamic.rs index 7e0aa20..53c0f0e 100644 --- a/crates/bili_sync/src/bilibili/dynamic.rs +++ b/crates/bili_sync/src/bilibili/dynamic.rs @@ -6,8 +6,7 @@ use futures::Stream; use reqwest::Method; use serde_json::Value; -use crate::bilibili::credential::encoded_query; -use crate::bilibili::{BiliClient, MIXIN_KEY, Validate, VideoInfo}; +use crate::bilibili::{BiliClient, MIXIN_KEY, Validate, VideoInfo, WbiSign}; pub struct Dynamic<'a> { client: &'a BiliClient, @@ -29,16 +28,15 @@ impl<'a> Dynamic<'a> { self.client .request( Method::GET, - "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all", + "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space", ) .await - .query(&encoded_query( - vec![ - ("host_mid", self.upper_id.as_str()), - ("offset", offset.as_deref().unwrap_or("")), - ], - MIXIN_KEY.load().as_deref(), - )) + .query(&[ + ("host_mid", self.upper_id.as_str()), + ("offset", offset.as_deref().unwrap_or("")), + ("type", "video"), + ]) + .wbi_sign(MIXIN_KEY.load().as_deref())? .send() .await? .error_for_status()? diff --git a/crates/bili_sync/src/bilibili/me.rs b/crates/bili_sync/src/bilibili/me.rs index f2e0efa..1f2ddde 100644 --- a/crates/bili_sync/src/bilibili/me.rs +++ b/crates/bili_sync/src/bilibili/me.rs @@ -38,12 +38,8 @@ impl<'a> Me<'a> { .client .request(Method::GET, "https://api.bilibili.com/x/v3/fav/folder/collected/list") .await - .query(&[ - ("up_mid", self.mid.as_str()), - ("pn", page_num.to_string().as_str()), - ("ps", page_size.to_string().as_str()), - ("platform", "web"), - ]) + .query(&[("up_mid", self.mid.as_str()), ("platform", "web")]) + .query(&[("pn", page_num), ("ps", page_size)]) .send() .await? .error_for_status()? @@ -59,11 +55,8 @@ impl<'a> Me<'a> { .client .request(Method::GET, "https://api.bilibili.com/x/relation/followings") .await - .query(&[ - ("vmid", self.mid.as_str()), - ("pn", page_num.to_string().as_str()), - ("ps", page_size.to_string().as_str()), - ]) + .query(&[("vmid", self.mid.as_str())]) + .query(&[("pn", page_num), ("ps", page_size)]) .send() .await? .error_for_status()? diff --git a/crates/bili_sync/src/bilibili/mod.rs b/crates/bili_sync/src/bilibili/mod.rs index 16dad5e..1f18026 100644 --- a/crates/bili_sync/src/bilibili/mod.rs +++ b/crates/bili_sync/src/bilibili/mod.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::sync::Arc; pub use analyzer::{BestStream, FilterOption}; @@ -7,7 +8,7 @@ use chrono::serde::ts_seconds; use chrono::{DateTime, Utc}; pub use client::{BiliClient, Client}; pub use collection::{Collection, CollectionItem, CollectionType}; -pub use credential::Credential; +pub use credential::{Credential, WbiImg}; pub use danmaku::DanmakuOption; pub use dynamic::Dynamic; pub use error::BiliError; @@ -15,6 +16,7 @@ pub use favorite_list::FavoriteList; use favorite_list::Upper; pub use me::Me; use once_cell::sync::Lazy; +use reqwest::RequestBuilder; pub use submission::Submission; pub use video::{Dimension, PageInfo, Video}; pub use watch_later::WatchLater; @@ -54,10 +56,47 @@ impl Validate for serde_json::Value { _ => bail!("no code or message found"), }; ensure!(code == 0, BiliError::RequestFailed(code, msg.to_owned())); + ensure!(self["data"]["v_voucher"].is_null(), BiliError::RiskControlOccurred); Ok(self) } } +pub(crate) trait WbiSign { + type Output; + + fn wbi_sign(self, mixin_key: Option>) -> Result; +} + +impl WbiSign for RequestBuilder { + type Output = RequestBuilder; + + fn wbi_sign(self, mixin_key: Option>) -> Result { + let Some(mixin_key) = mixin_key else { + return Ok(self); + }; + let (client, req) = self.build_split(); + let mut req = match req { + Ok(req) => req, + Err(e) => return Err(e.into()), + }; + sign_request(&mut req, mixin_key.as_ref(), chrono::Utc::now().timestamp())?; + Ok(RequestBuilder::from_parts(client, req)) + } +} + +fn sign_request(req: &mut reqwest::Request, mixin_key: &str, timestamp: i64) -> Result<()> { + let mut query_pairs = req.url().query_pairs().collect::>(); + let timestamp = timestamp.to_string(); + query_pairs.push(("wts".into(), Cow::Borrowed(timestamp.as_str()))); + query_pairs.sort_by(|a, b| a.0.cmp(&b.0)); + let query_str = serde_urlencoded::to_string(query_pairs)?.replace('+', "%20"); + let w_rid = format!("{:x}", md5::compute(query_str + mixin_key)); + req.url_mut() + .query_pairs_mut() + .extend_pairs([("w_rid", w_rid), ("wts", timestamp)]); + Ok(()) +} + #[derive(Debug, serde::Deserialize)] #[serde(untagged)] /// 注意此处的顺序是有要求的,因为对于 untagged 的 enum 来说,serde 会按照顺序匹配 @@ -155,9 +194,12 @@ pub enum VideoInfo { mod tests { use std::path::Path; + use anyhow::Context; use futures::StreamExt; + use reqwest::Method; use super::*; + use crate::bilibili::credential::WbiImg; use crate::config::VersionedConfig; use crate::database::setup_database; use crate::utils::init_logger; @@ -169,9 +211,7 @@ mod tests { init_logger("None,bili_sync=debug", None); let bili_client = BiliClient::new(); // 请求 UP 主视频必须要获取 mixin key,使用 key 计算请求参数的签名,否则直接提示权限不足返回空 - let Ok(Some(mixin_key)) = bili_client.wbi_img().await.map(|wbi_img| wbi_img.into()) else { - panic!("获取 mixin key 失败"); - }; + let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?; set_global_mixin_key(mixin_key); let collection = Collection::new( &bili_client, @@ -236,9 +276,7 @@ mod tests { #[tokio::test] async fn test_subtitle_parse() -> Result<()> { 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 失败"); - }; + let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?; set_global_mixin_key(mixin_key); let video = Video::new(&bili_client, "BV1gLfnY8E6D".to_string()); let pages = video.get_pages().await?; @@ -259,9 +297,7 @@ mod tests { 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 失败"); - }; + let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?; set_global_mixin_key(mixin_key); for (bvid, (upower_exclusive, upower_play)) in [ ("BV1HxXwYEEqt", (true, false)), // 充电专享且无权观看 @@ -289,9 +325,7 @@ mod tests { 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 失败"); - }; + let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?; set_global_mixin_key(mixin_key); for (bvid, redirect_is_none) in [ ("BV1SF411g796", false), // EP @@ -307,4 +341,56 @@ mod tests { } Ok(()) } + + #[test] + fn test_wbi_key() -> Result<()> { + let key = WbiImg { + img_url: "https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png".to_string(), + sub_url: "https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png".to_string(), + }; + let key = key.into_mixin_key().context("no mixin key")?; + assert_eq!(key.as_str(), "ea1db124af3c7062474693fa704f4ff8"); + let client = Client::new(); + let mut req = client + .request(Method::GET, "https://www.baidu.com/", None) + .query(&[("foo", "114"), ("bar", "514")]) + .query(&[("zab", "1919810")]) + .build()?; + sign_request(&mut req, key.as_str(), 1702204169).unwrap(); + let query: Vec<_> = req.url().query_pairs().collect(); + assert_eq!( + query, + vec![ + ("foo".into(), "114".into()), + ("bar".into(), "514".into()), + ("zab".into(), "1919810".into()), + ("w_rid".into(), "8f6f2b5b3d485fe1886cec6a0be8c5d4".into()), + ("wts".into(), "1702204169".into()), + ] + ); + let key = WbiImg { + img_url: "https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png".to_string(), + sub_url: "https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png".to_string(), + }; + let key = key.into_mixin_key().context("no mixin key")?; + let mut req = client + .request(Method::GET, "https://www.baidu.com/", None) + .query(&[("mid", "11997177"), ("token", "")]) + .query(&[("platform", "web"), ("web_location", "1550101")]) + .build()?; + sign_request(&mut req, key.as_str(), 1703513649).unwrap(); + let query: Vec<_> = req.url().query_pairs().collect(); + assert_eq!( + query, + vec![ + ("mid".into(), "11997177".into()), + ("token".into(), "".into()), + ("platform".into(), "web".into()), + ("web_location".into(), "1550101".into()), + ("w_rid".into(), "7d4428b3f2f9ee2811e116ec6fd41a4f".into()), + ("wts".into(), "1703513649".into()), + ] + ); + Ok(()) + } } diff --git a/crates/bili_sync/src/bilibili/submission.rs b/crates/bili_sync/src/bilibili/submission.rs index 307469b..260154c 100644 --- a/crates/bili_sync/src/bilibili/submission.rs +++ b/crates/bili_sync/src/bilibili/submission.rs @@ -4,9 +4,8 @@ use futures::Stream; use reqwest::Method; use serde_json::Value; -use crate::bilibili::credential::encoded_query; use crate::bilibili::favorite_list::Upper; -use crate::bilibili::{BiliClient, Dynamic, MIXIN_KEY, Validate, VideoInfo}; +use crate::bilibili::{BiliClient, Dynamic, MIXIN_KEY, Validate, VideoInfo, WbiSign}; pub struct Submission<'a> { client: &'a BiliClient, pub upper_id: String, @@ -42,18 +41,16 @@ impl<'a> Submission<'a> { self.client .request(Method::GET, "https://api.bilibili.com/x/space/wbi/arc/search") .await - .query(&encoded_query( - vec![ - ("mid", self.upper_id.as_str()), - ("order", "pubdate"), - ("order_avoided", "true"), - ("platform", "web"), - ("web_location", "1550101"), - ("pn", page.to_string().as_str()), - ("ps", "30"), - ], - MIXIN_KEY.load().as_deref(), - )) + .query(&[ + ("mid", self.upper_id.as_str()), + ("order", "pubdate"), + ("order_avoided", "true"), + ("platform", "web"), + ("web_location", "1550101"), + ("ps", "30"), + ]) + .query(&[("pn", page)]) + .wbi_sign(MIXIN_KEY.load().as_deref())? .send() .await? .error_for_status()? diff --git a/crates/bili_sync/src/bilibili/video.rs b/crates/bili_sync/src/bilibili/video.rs index c92e9cb..3ee3ed7 100644 --- a/crates/bili_sync/src/bilibili/video.rs +++ b/crates/bili_sync/src/bilibili/video.rs @@ -6,10 +6,9 @@ use reqwest::Method; use crate::bilibili::analyzer::PageAnalyzer; use crate::bilibili::client::BiliClient; -use crate::bilibili::credential::encoded_query; use crate::bilibili::danmaku::{DanmakuElem, DanmakuWriter, DmSegMobileReply}; use crate::bilibili::subtitle::{SubTitle, SubTitleBody, SubTitleInfo, SubTitlesInfo}; -use crate::bilibili::{MIXIN_KEY, Validate, VideoInfo}; +use crate::bilibili::{MIXIN_KEY, Validate, VideoInfo, WbiSign}; pub struct Video<'a> { client: &'a BiliClient, @@ -43,9 +42,10 @@ impl<'a> Video<'a> { pub async fn get_view_info(&self) -> Result { let mut res = self .client - .request(Method::GET, "https://api.bilibili.com/x/web-interface/view") + .request(Method::GET, "https://api.bilibili.com/x/web-interface/wbi/view") .await .query(&[("bvid", &self.bvid)]) + .wbi_sign(MIXIN_KEY.load().as_deref())? .send() .await? .error_for_status()? @@ -55,7 +55,7 @@ impl<'a> Video<'a> { Ok(serde_json::from_value(res["data"].take())?) } - #[allow(dead_code)] + #[cfg(test)] pub async fn get_pages(&self) -> Result> { let mut res = self .client @@ -105,9 +105,10 @@ impl<'a> Video<'a> { async fn get_danmaku_segment(&self, page: &PageInfo, segment_idx: i64) -> Result> { let mut res = self .client - .request(Method::GET, "http://api.bilibili.com/x/v2/dm/web/seg.so") + .request(Method::GET, "https://api.bilibili.com/x/v2/dm/wbi/web/seg.so") .await .query(&[("type", 1), ("oid", page.cid), ("segment_index", segment_idx)]) + .wbi_sign(MIXIN_KEY.load().as_deref())? .send() .await? .error_for_status()?; @@ -127,17 +128,15 @@ impl<'a> Video<'a> { .client .request(Method::GET, "https://api.bilibili.com/x/player/wbi/playurl") .await - .query(&encoded_query( - vec![ - ("bvid", self.bvid.as_str()), - ("cid", page.cid.to_string().as_str()), - ("qn", "127"), - ("otype", "json"), - ("fnval", "4048"), - ("fourk", "1"), - ], - MIXIN_KEY.load().as_deref(), - )) + .query(&[ + ("bvid", self.bvid.as_str()), + ("qn", "127"), + ("otype", "json"), + ("fnval", "4048"), + ("fourk", "1"), + ]) + .query(&[("cid", page.cid)]) + .wbi_sign(MIXIN_KEY.load().as_deref())? .send() .await? .error_for_status()? @@ -152,10 +151,9 @@ impl<'a> Video<'a> { .client .request(Method::GET, "https://api.bilibili.com/x/player/wbi/v2") .await - .query(&encoded_query( - vec![("cid", &page.cid.to_string()), ("bvid", &self.bvid)], - MIXIN_KEY.load().as_deref(), - )) + .query(&[("bvid", self.bvid.as_str())]) + .query(&[("cid", page.cid)]) + .wbi_sign(MIXIN_KEY.load().as_deref())? .send() .await? .error_for_status()? diff --git a/crates/bili_sync/src/task/video_downloader.rs b/crates/bili_sync/src/task/video_downloader.rs index db7fe39..fa129cf 100644 --- a/crates/bili_sync/src/task/video_downloader.rs +++ b/crates/bili_sync/src/task/video_downloader.rs @@ -4,7 +4,7 @@ use sea_orm::DatabaseConnection; use tokio::time; use crate::adapter::VideoSource; -use crate::bilibili::{self, BiliClient}; +use crate::bilibili::{self, BiliClient, WbiImg}; use crate::config::VersionedConfig; use crate::utils::model::get_enabled_video_sources; use crate::utils::task_notifier::TASK_STATUS_NOTIFIER; @@ -22,7 +22,7 @@ pub async fn video_downloader(connection: DatabaseConnection, bili_client: Arc bilibili::set_global_mixin_key(mixin_key), Ok(_) => { error!("解析 mixin key 失败,等待下一轮执行"); diff --git a/web/src/routes/video-sources/+page.svelte b/web/src/routes/video-sources/+page.svelte index 9d39245..df2a7ea 100644 --- a/web/src/routes/video-sources/+page.svelte +++ b/web/src/routes/video-sources/+page.svelte @@ -420,9 +420,8 @@

- 只有使用动态 API - 才能拉取到动态视频,但该接口不提供筛选参数,需要拉取全部类型的动态后在本地筛选出视频。
这在扫描时会获取到较多无效数据并增加请求次数,可根据实际情况酌情选择,推荐仅在 + 只有使用动态 API 才能拉取到动态视频,但该接口不提供分页参数,每次请求只能拉取 12 + 条视频。
这会一定程度上增加请求次数,用户可根据实际情况酌情选择,推荐仅在 UP 主有较多动态视频时开启。