feat: 优化对全局配置的处理,调整下载路径填充逻辑 (#523)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-11-06 17:25:26 +08:00
committed by GitHub
parent b6cba69e11
commit 854d39cf88
34 changed files with 706 additions and 466 deletions
+6 -6
View File
@@ -2,7 +2,6 @@ use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use crate::bilibili::error::BiliError;
use crate::config::VersionedConfig;
pub struct PageAnalyzer {
info: serde_json::Value,
@@ -131,14 +130,14 @@ pub enum Stream {
// 通用的获取流链接的方法,交由 Downloader 使用
impl Stream {
pub fn urls(&self) -> Vec<&str> {
pub fn urls(&self, enable_cdn_sorting: bool) -> Vec<&str> {
match self {
Self::Flv(url) | Self::Html5Mp4(url) | Self::EpisodeTryMp4(url) => vec![url],
Self::DashVideo { url, backup_url, .. } | Self::DashAudio { url, backup_url, .. } => {
let mut urls = std::iter::once(url.as_str())
.chain(backup_url.iter().map(|s| s.as_str()))
.collect::<Vec<_>>();
if VersionedConfig::get().load().cdn_sorting {
if enable_cdn_sorting {
urls.sort_by_key(|u| {
if u.contains("upos-") {
0 // 服务商 cdn
@@ -424,16 +423,17 @@ mod tests {
Some(AudioQuality::Quality192k),
),
];
let config = VersionedConfig::get().read();
for (bvid, video_quality, video_codec, audio_quality) in testcases.into_iter() {
let client = BiliClient::new();
let video = Video::new(&client, bvid.to_owned());
let video = Video::new(&client, bvid.to_owned(), &config.credential);
let pages = video.get_pages().await.expect("failed to get pages");
let first_page = pages.into_iter().next().expect("no page found");
let best_stream = video
.get_page_analyzer(&first_page)
.await
.expect("failed to get page analyzer")
.best_stream(&VersionedConfig::get().load().filter_option)
.best_stream(&config.filter_option)
.expect("failed to get best stream");
dbg!(bvid, &best_stream);
match best_stream {
@@ -469,7 +469,7 @@ mod tests {
codecs: VideoCodecs::AVC,
};
assert_eq!(
stream.urls(),
stream.urls(true),
vec![
"https://upos-sz-mirrorcos.bilivideo.com",
"https://cn-tj-cu-01-11.bilivideo.com",
+55 -33
View File
@@ -1,14 +1,14 @@
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use anyhow::{Result, bail};
use leaky_bucket::RateLimiter;
use reqwest::{Method, header};
use sea_orm::DatabaseConnection;
use ua_generator::ua;
use crate::bilibili::Credential;
use crate::bilibili::credential::WbiImg;
use crate::config::{RateLimit, VersionedCache, VersionedConfig};
use crate::config::{RateLimit, VersionedCache};
// 一个对 reqwest::Client 的简单封装,用于 Bilibili 请求
#[derive(Clone)]
@@ -60,56 +60,78 @@ impl Default for Client {
}
}
enum Limiter {
Latest(VersionedCache<Option<RateLimiter>>),
Snapshot(Arc<Option<RateLimiter>>),
}
pub struct BiliClient {
pub client: Client,
limiter: VersionedCache<Option<RateLimiter>>,
limiter: Limiter,
}
impl BiliClient {
pub fn new() -> Self {
let client = Client::new();
let limiter = VersionedCache::new(|config| {
Ok(config
.concurrent_limit
.rate_limit
.as_ref()
.map(|RateLimit { limit, duration }| {
RateLimiter::builder()
.initial(*limit)
.refill(*limit)
.max(*limit)
.interval(Duration::from_millis(*duration))
.build()
}))
})
.expect("failed to create rate limiter");
let limiter = Limiter::Latest(
VersionedCache::new(|config| {
Ok(config
.concurrent_limit
.rate_limit
.as_ref()
.map(|RateLimit { limit, duration }| {
RateLimiter::builder()
.initial(*limit)
.refill(*limit)
.max(*limit)
.interval(Duration::from_millis(*duration))
.build()
}))
})
.expect("failed to create rate limiter"),
);
Self { client, limiter }
}
/// 获取当前 BiliClient 的快照,快照中的限流器固定不变
pub fn snapshot(&self) -> Result<Self> {
let Limiter::Latest(inner) = &self.limiter else {
// 语法上没问题,但语义上不允许对快照进行快照
bail!("cannot snapshot a snapshot BiliClient");
};
Ok(Self {
client: self.client.clone(),
limiter: Limiter::Snapshot(inner.snapshot()),
})
}
/// 获取一个预构建的请求,通过该方法获取请求时会检查并等待速率限制
pub async fn request(&self, method: Method, url: &str) -> reqwest::RequestBuilder {
if let Some(limiter) = self.limiter.load().as_ref() {
limiter.acquire_one().await;
pub async fn request(&self, method: Method, url: &str, credential: &Credential) -> reqwest::RequestBuilder {
match &self.limiter {
Limiter::Latest(inner) => {
if let Some(limiter) = inner.read().as_ref() {
limiter.acquire_one().await;
}
}
Limiter::Snapshot(inner) => {
if let Some(limiter) = inner.as_ref() {
limiter.acquire_one().await;
}
}
}
let credential = &VersionedConfig::get().load().credential;
self.client.request(method, url, Some(credential))
}
pub async fn check_refresh(&self, connection: &DatabaseConnection) -> Result<()> {
let credential = &VersionedConfig::get().load().credential;
/// 检查并刷新 Credential,不需要刷新返回 Ok(None),需要刷新返回 Ok(Some(new_credential))
pub async fn check_refresh(&self, credential: &Credential) -> Result<Option<Credential>> {
if !credential.need_refresh(&self.client).await? {
return Ok(());
return Ok(None);
}
let new_credential = credential.refresh(&self.client).await?;
VersionedConfig::get()
.update_credential(new_credential, connection)
.await?;
Ok(())
Ok(Some(credential.refresh(&self.client).await?))
}
/// 获取 wbi img,用于生成请求签名
pub async fn wbi_img(&self) -> Result<WbiImg> {
let credential = &VersionedConfig::get().load().credential;
pub async fn wbi_img(&self, credential: &Credential) -> Result<WbiImg> {
credential.wbi_img(&self.client).await
}
}
+15 -5
View File
@@ -7,7 +7,7 @@ use reqwest::Method;
use serde::Deserialize;
use serde_json::Value;
use crate::bilibili::{BiliClient, Validate, VideoInfo};
use crate::bilibili::{BiliClient, Credential, Validate, VideoInfo};
#[derive(PartialEq, Eq, Hash, Clone, Debug, Default, Copy)]
pub enum CollectionType {
@@ -73,6 +73,7 @@ pub struct CollectionItem {
pub struct Collection<'a> {
client: &'a BiliClient,
pub collection: CollectionItem,
credential: &'a Credential,
}
#[derive(Debug, PartialEq)]
@@ -111,8 +112,12 @@ impl<'de> Deserialize<'de> for CollectionInfo {
}
impl<'a> Collection<'a> {
pub fn new(client: &'a BiliClient, collection: CollectionItem) -> Self {
Self { client, collection }
pub fn new(client: &'a BiliClient, collection: CollectionItem, credential: &'a Credential) -> Self {
Self {
client,
collection,
credential,
}
}
pub async fn get_info(&self) -> Result<CollectionInfo> {
@@ -126,7 +131,7 @@ impl<'a> Collection<'a> {
async fn get_series_info(&self) -> Result<Value> {
self.client
.request(Method::GET, "https://api.bilibili.com/x/series/series")
.request(Method::GET, "https://api.bilibili.com/x/series/series", self.credential)
.await
.query(&[("series_id", self.collection.sid.as_str())])
.send()
@@ -141,7 +146,11 @@ impl<'a> Collection<'a> {
let req = match self.collection.collection_type {
CollectionType::Series => self
.client
.request(Method::GET, "https://api.bilibili.com/x/series/archives")
.request(
Method::GET,
"https://api.bilibili.com/x/series/archives",
self.credential,
)
.await
.query(&[("pn", page)])
.query(&[
@@ -156,6 +165,7 @@ impl<'a> Collection<'a> {
.request(
Method::GET,
"https://api.bilibili.com/x/polymer/web-space/seasons_archives_list",
self.credential,
)
.await
.query(&[("page_num", page)])
@@ -3,10 +3,9 @@ use std::path::PathBuf;
use anyhow::Result;
use tokio::fs::{self, File};
use crate::bilibili::PageInfo;
use crate::bilibili::danmaku::canvas::CanvasConfig;
use crate::bilibili::danmaku::{AssWriter, Danmu};
use crate::config::VersionedConfig;
use crate::bilibili::{DanmakuOption, PageInfo};
pub struct DanmakuWriter<'a> {
page: &'a PageInfo,
@@ -18,12 +17,11 @@ impl<'a> DanmakuWriter<'a> {
DanmakuWriter { page, danmaku }
}
pub async fn write(self, path: PathBuf) -> Result<()> {
pub async fn write(self, path: PathBuf, danmaku_option: &DanmakuOption) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).await?;
}
let config = VersionedConfig::get().load_full();
let canvas_config = CanvasConfig::new(&config.danmaku_option, self.page);
let canvas_config = CanvasConfig::new(danmaku_option, self.page);
let mut writer =
AssWriter::construct(File::create(path).await?, self.page.name.clone(), canvas_config.clone()).await?;
let mut canvas = canvas_config.canvas();
+9 -3
View File
@@ -6,11 +6,12 @@ use futures::Stream;
use reqwest::Method;
use serde_json::Value;
use crate::bilibili::{BiliClient, MIXIN_KEY, Validate, VideoInfo, WbiSign};
use crate::bilibili::{BiliClient, Credential, MIXIN_KEY, Validate, VideoInfo, WbiSign};
pub struct Dynamic<'a> {
client: &'a BiliClient,
pub upper_id: String,
credential: &'a Credential,
}
#[derive(Debug, serde::Deserialize)]
@@ -20,8 +21,12 @@ pub struct DynamicItemPublished {
}
impl<'a> Dynamic<'a> {
pub fn new(client: &'a BiliClient, upper_id: String) -> Self {
Self { client, upper_id }
pub fn new(client: &'a BiliClient, upper_id: String, credential: &'a Credential) -> Self {
Self {
client,
upper_id,
credential,
}
}
pub async fn get_dynamics(&self, offset: Option<String>) -> Result<Value> {
@@ -29,6 +34,7 @@ impl<'a> Dynamic<'a> {
.request(
Method::GET,
"https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space",
self.credential,
)
.await
.query(&[
+18 -5
View File
@@ -3,10 +3,11 @@ use async_stream::try_stream;
use futures::Stream;
use serde_json::Value;
use crate::bilibili::{BiliClient, Validate, VideoInfo};
use crate::bilibili::{BiliClient, Credential, Validate, VideoInfo};
pub struct FavoriteList<'a> {
client: &'a BiliClient,
fid: String,
credential: &'a Credential,
}
#[derive(Debug, serde::Deserialize)]
@@ -22,14 +23,22 @@ pub struct Upper<T> {
pub face: String,
}
impl<'a> FavoriteList<'a> {
pub fn new(client: &'a BiliClient, fid: String) -> Self {
Self { client, fid }
pub fn new(client: &'a BiliClient, fid: String, credential: &'a Credential) -> Self {
Self {
client,
fid,
credential,
}
}
pub async fn get_info(&self) -> Result<FavoriteListInfo> {
let mut res = self
.client
.request(reqwest::Method::GET, "https://api.bilibili.com/x/v3/fav/folder/info")
.request(
reqwest::Method::GET,
"https://api.bilibili.com/x/v3/fav/folder/info",
self.credential,
)
.await
.query(&[("media_id", &self.fid)])
.send()
@@ -43,7 +52,11 @@ impl<'a> FavoriteList<'a> {
async fn get_videos(&self, page: u32) -> Result<Value> {
self.client
.request(reqwest::Method::GET, "https://api.bilibili.com/x/v3/fav/resource/list")
.request(
reqwest::Method::GET,
"https://api.bilibili.com/x/v3/fav/resource/list",
self.credential,
)
.await
.query(&[
("media_id", self.fid.as_str()),
+37 -19
View File
@@ -1,28 +1,32 @@
use anyhow::{Result, ensure};
use reqwest::Method;
use crate::bilibili::{BiliClient, Validate};
use crate::config::VersionedConfig;
use crate::bilibili::{BiliClient, Credential, Validate};
pub struct Me<'a> {
client: &'a BiliClient,
mid: String,
credential: &'a Credential,
}
impl<'a> Me<'a> {
pub fn new(client: &'a BiliClient) -> Self {
Self {
client,
mid: Self::my_id(),
}
pub fn new(client: &'a BiliClient, credential: &'a Credential) -> Self {
Self { client, credential }
}
pub async fn get_created_favorites(&self) -> Result<Option<Vec<FavoriteItem>>> {
ensure!(!self.mid.is_empty(), "未获取到用户 ID,请确保填写设置中的 B 站认证信息");
ensure!(
!self.mid().is_empty(),
"未获取到用户 ID,请确保填写设置中的 B 站认证信息"
);
let mut resp = self
.client
.request(Method::GET, "https://api.bilibili.com/x/v3/fav/folder/created/list-all")
.request(
Method::GET,
"https://api.bilibili.com/x/v3/fav/folder/created/list-all",
self.credential,
)
.await
.query(&[("up_mid", &self.mid)])
.query(&[("up_mid", &self.mid())])
.send()
.await?
.error_for_status()?
@@ -33,12 +37,19 @@ impl<'a> Me<'a> {
}
pub async fn get_followed_collections(&self, page_num: i32, page_size: i32) -> Result<Collections> {
ensure!(!self.mid.is_empty(), "未获取到用户 ID,请确保填写设置中的 B 站认证信息");
ensure!(
!self.mid().is_empty(),
"未获取到用户 ID,请确保填写设置中的 B 站认证信息"
);
let mut resp = self
.client
.request(Method::GET, "https://api.bilibili.com/x/v3/fav/folder/collected/list")
.request(
Method::GET,
"https://api.bilibili.com/x/v3/fav/folder/collected/list",
self.credential,
)
.await
.query(&[("up_mid", self.mid.as_str()), ("platform", "web")])
.query(&[("up_mid", self.mid()), ("platform", "web")])
.query(&[("pn", page_num), ("ps", page_size)])
.send()
.await?
@@ -50,12 +61,19 @@ impl<'a> Me<'a> {
}
pub async fn get_followed_uppers(&self, page_num: i32, page_size: i32) -> Result<FollowedUppers> {
ensure!(!self.mid.is_empty(), "未获取到用户 ID,请确保填写设置中的 B 站认证信息");
ensure!(
!self.mid().is_empty(),
"未获取到用户 ID,请确保填写设置中的 B 站认证信息"
);
let mut resp = self
.client
.request(Method::GET, "https://api.bilibili.com/x/relation/followings")
.request(
Method::GET,
"https://api.bilibili.com/x/relation/followings",
self.credential,
)
.await
.query(&[("vmid", self.mid.as_str())])
.query(&[("vmid", self.mid())])
.query(&[("pn", page_num), ("ps", page_size)])
.send()
.await?
@@ -66,8 +84,8 @@ impl<'a> Me<'a> {
Ok(serde_json::from_value(resp["data"].take())?)
}
fn my_id() -> String {
VersionedConfig::get().load().credential.dedeuserid.clone()
fn mid(&self) -> &str {
&self.credential.dedeuserid
}
}
+34 -12
View File
@@ -8,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, WbiImg};
pub use credential::Credential;
pub use danmaku::DanmakuOption;
pub use dynamic::Dynamic;
pub use error::BiliError;
@@ -208,10 +208,15 @@ mod tests {
#[tokio::test]
async fn test_video_info_type() -> Result<()> {
VersionedConfig::init_for_test(&setup_database(Path::new("./test.sqlite")).await?).await?;
let credential = &VersionedConfig::get().read().credential;
init_logger("None,bili_sync=debug", None);
let bili_client = BiliClient::new();
// 请求 UP 主视频必须要获取 mixin key,使用 key 计算请求参数的签名,否则直接提示权限不足返回空
let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?;
let mixin_key = bili_client
.wbi_img(credential)
.await?
.into_mixin_key()
.context("no mixin key")?;
set_global_mixin_key(mixin_key);
let collection = Collection::new(
&bili_client,
@@ -220,6 +225,7 @@ mod tests {
sid: "4523".to_string(),
collection_type: CollectionType::Season,
},
&credential,
);
let videos = collection
.into_video_stream()
@@ -230,7 +236,7 @@ mod tests {
assert!(videos.iter().all(|v| matches!(v, VideoInfo::Collection { .. })));
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
// 测试收藏夹
let favorite = FavoriteList::new(&bili_client, "3144336058".to_string());
let favorite = FavoriteList::new(&bili_client, "3144336058".to_string(), &credential);
let videos = favorite
.into_video_stream()
.take(20)
@@ -240,7 +246,7 @@ mod tests {
assert!(videos.iter().all(|v| matches!(v, VideoInfo::Favorite { .. })));
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
// 测试稍后再看
let watch_later = WatchLater::new(&bili_client);
let watch_later = WatchLater::new(&bili_client, &credential);
let videos = watch_later
.into_video_stream()
.take(20)
@@ -250,7 +256,7 @@ mod tests {
assert!(videos.iter().all(|v| matches!(v, VideoInfo::WatchLater { .. })));
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
// 测试投稿
let submission = Submission::new(&bili_client, "956761".to_string());
let submission = Submission::new(&bili_client, "956761".to_string(), &credential);
let videos = submission
.into_video_stream()
.take(20)
@@ -260,7 +266,7 @@ mod tests {
assert!(videos.iter().all(|v| matches!(v, VideoInfo::Submission { .. })));
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
// 测试动态
let dynamic = Dynamic::new(&bili_client, "659898".to_string());
let dynamic = Dynamic::new(&bili_client, "659898".to_string(), &credential);
let videos = dynamic
.into_video_stream()
.take(20)
@@ -275,10 +281,16 @@ mod tests {
#[ignore = "only for manual test"]
#[tokio::test]
async fn test_subtitle_parse() -> Result<()> {
VersionedConfig::init_for_test(&setup_database(Path::new("./test.sqlite")).await?).await?;
let credential = &VersionedConfig::get().read().credential;
let bili_client = BiliClient::new();
let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?;
let mixin_key = bili_client
.wbi_img(credential)
.await?
.into_mixin_key()
.context("no mixin key")?;
set_global_mixin_key(mixin_key);
let video = Video::new(&bili_client, "BV1gLfnY8E6D".to_string());
let video = Video::new(&bili_client, "BV1gLfnY8E6D".to_string(), &credential);
let pages = video.get_pages().await?;
println!("pages: {:?}", pages);
let subtitles = video.get_subtitles(&pages[0]).await?;
@@ -296,15 +308,20 @@ mod tests {
#[tokio::test]
async fn test_upower_parse() -> Result<()> {
VersionedConfig::init_for_test(&setup_database(Path::new("./test.sqlite")).await?).await?;
let credential = &VersionedConfig::get().read().credential;
let bili_client = BiliClient::new();
let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?;
let mixin_key = bili_client
.wbi_img(credential)
.await?
.into_mixin_key()
.context("no 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 video = Video::new(&bili_client, bvid.to_string(), credential);
let info = video.get_view_info().await?;
let VideoInfo::Detail {
is_upower_exclusive,
@@ -324,15 +341,20 @@ mod tests {
#[tokio::test]
async fn test_ep_parse() -> Result<()> {
VersionedConfig::init_for_test(&setup_database(Path::new("./test.sqlite")).await?).await?;
let credential = &VersionedConfig::get().read().credential;
let bili_client = BiliClient::new();
let mixin_key = bili_client.wbi_img().await?.into_mixin_key().context("no mixin key")?;
let mixin_key = bili_client
.wbi_img(credential)
.await?
.into_mixin_key()
.context("no 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 video = Video::new(&bili_client, bvid.to_string(), credential);
let info = video.get_view_info().await?;
let VideoInfo::Detail { redirect_url, .. } = info else {
unreachable!();
+19 -6
View File
@@ -5,27 +5,36 @@ use reqwest::Method;
use serde_json::Value;
use crate::bilibili::favorite_list::Upper;
use crate::bilibili::{BiliClient, Dynamic, MIXIN_KEY, Validate, VideoInfo, WbiSign};
use crate::bilibili::{BiliClient, Credential, Dynamic, MIXIN_KEY, Validate, VideoInfo, WbiSign};
pub struct Submission<'a> {
client: &'a BiliClient,
pub upper_id: String,
credential: &'a Credential,
}
impl<'a> From<Submission<'a>> for Dynamic<'a> {
fn from(submission: Submission<'a>) -> Self {
Dynamic::new(submission.client, submission.upper_id)
Dynamic::new(submission.client, submission.upper_id, submission.credential)
}
}
impl<'a> Submission<'a> {
pub fn new(client: &'a BiliClient, upper_id: String) -> Self {
Self { client, upper_id }
pub fn new(client: &'a BiliClient, upper_id: String, credential: &'a Credential) -> Self {
Self {
client,
upper_id,
credential,
}
}
pub async fn get_info(&self) -> Result<Upper<String>> {
let mut res = self
.client
.request(Method::GET, "https://api.bilibili.com/x/web-interface/card")
.request(
Method::GET,
"https://api.bilibili.com/x/web-interface/card",
self.credential,
)
.await
.query(&[("mid", self.upper_id.as_str())])
.send()
@@ -39,7 +48,11 @@ impl<'a> Submission<'a> {
async fn get_videos(&self, page: i32) -> Result<Value> {
self.client
.request(Method::GET, "https://api.bilibili.com/x/space/wbi/arc/search")
.request(
Method::GET,
"https://api.bilibili.com/x/space/wbi/arc/search",
self.credential,
)
.await
.query(&[
("mid", self.upper_id.as_str()),
+34 -9
View File
@@ -8,11 +8,12 @@ use crate::bilibili::analyzer::PageAnalyzer;
use crate::bilibili::client::BiliClient;
use crate::bilibili::danmaku::{DanmakuElem, DanmakuWriter, DmSegMobileReply};
use crate::bilibili::subtitle::{SubTitle, SubTitleBody, SubTitleInfo, SubTitlesInfo};
use crate::bilibili::{MIXIN_KEY, Validate, VideoInfo, WbiSign};
use crate::bilibili::{Credential, MIXIN_KEY, Validate, VideoInfo, WbiSign};
pub struct Video<'a> {
client: &'a BiliClient,
pub bvid: String,
credential: &'a Credential,
}
#[derive(Debug, serde::Deserialize, Default)]
@@ -34,15 +35,23 @@ pub struct Dimension {
}
impl<'a> Video<'a> {
pub fn new(client: &'a BiliClient, bvid: String) -> Self {
Self { client, bvid }
pub fn new(client: &'a BiliClient, bvid: String, credential: &'a Credential) -> Self {
Self {
client,
bvid,
credential,
}
}
/// 直接调用视频信息接口获取详细的视频信息,视频信息中包含了视频的分页信息
pub async fn get_view_info(&self) -> Result<VideoInfo> {
let mut res = self
.client
.request(Method::GET, "https://api.bilibili.com/x/web-interface/wbi/view")
.request(
Method::GET,
"https://api.bilibili.com/x/web-interface/wbi/view",
self.credential,
)
.await
.query(&[("bvid", &self.bvid)])
.wbi_sign(MIXIN_KEY.load().as_deref())?
@@ -59,7 +68,11 @@ impl<'a> Video<'a> {
pub async fn get_pages(&self) -> Result<Vec<PageInfo>> {
let mut res = self
.client
.request(Method::GET, "https://api.bilibili.com/x/player/pagelist")
.request(
Method::GET,
"https://api.bilibili.com/x/player/pagelist",
self.credential,
)
.await
.query(&[("bvid", &self.bvid)])
.send()
@@ -74,7 +87,11 @@ impl<'a> Video<'a> {
pub async fn get_tags(&self) -> Result<Vec<String>> {
let res = self
.client
.request(Method::GET, "https://api.bilibili.com/x/web-interface/view/detail/tag")
.request(
Method::GET,
"https://api.bilibili.com/x/web-interface/view/detail/tag",
self.credential,
)
.await
.query(&[("bvid", &self.bvid)])
.send()
@@ -105,7 +122,11 @@ impl<'a> Video<'a> {
async fn get_danmaku_segment(&self, page: &PageInfo, segment_idx: i64) -> Result<Vec<DanmakuElem>> {
let mut res = self
.client
.request(Method::GET, "https://api.bilibili.com/x/v2/dm/wbi/web/seg.so")
.request(
Method::GET,
"https://api.bilibili.com/x/v2/dm/wbi/web/seg.so",
self.credential,
)
.await
.query(&[("type", 1), ("oid", page.cid), ("segment_index", segment_idx)])
.wbi_sign(MIXIN_KEY.load().as_deref())?
@@ -126,7 +147,11 @@ impl<'a> Video<'a> {
pub async fn get_page_analyzer(&self, page: &PageInfo) -> Result<PageAnalyzer> {
let mut res = self
.client
.request(Method::GET, "https://api.bilibili.com/x/player/wbi/playurl")
.request(
Method::GET,
"https://api.bilibili.com/x/player/wbi/playurl",
self.credential,
)
.await
.query(&[
("bvid", self.bvid.as_str()),
@@ -149,7 +174,7 @@ impl<'a> Video<'a> {
pub async fn get_subtitles(&self, page: &PageInfo) -> Result<Vec<SubTitle>> {
let mut res = self
.client
.request(Method::GET, "https://api.bilibili.com/x/player/wbi/v2")
.request(Method::GET, "https://api.bilibili.com/x/player/wbi/v2", self.credential)
.await
.query(&[("bvid", self.bvid.as_str())])
.query(&[("cid", page.cid)])
+9 -4
View File
@@ -3,19 +3,24 @@ use async_stream::try_stream;
use futures::Stream;
use serde_json::Value;
use crate::bilibili::{BiliClient, Validate, VideoInfo};
use crate::bilibili::{BiliClient, Credential, Validate, VideoInfo};
pub struct WatchLater<'a> {
client: &'a BiliClient,
credential: &'a Credential,
}
impl<'a> WatchLater<'a> {
pub fn new(client: &'a BiliClient) -> Self {
Self { client }
pub fn new(client: &'a BiliClient, credential: &'a Credential) -> Self {
Self { client, credential }
}
async fn get_videos(&self) -> Result<Value> {
self.client
.request(reqwest::Method::GET, "https://api.bilibili.com/x/v2/history/toview")
.request(
reqwest::Method::GET,
"https://api.bilibili.com/x/v2/history/toview",
self.credential,
)
.await
.send()
.await?