mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-09-05 15:39:07 +08:00
feat: 支持 up 主投稿视频下载 (#155)
This commit is contained in:
@@ -13,6 +13,7 @@ pub use error::BiliError;
|
||||
pub use favorite_list::FavoriteList;
|
||||
use favorite_list::Upper;
|
||||
use once_cell::sync::Lazy;
|
||||
pub use submission::Submission;
|
||||
pub use video::{Dimension, PageInfo, Video};
|
||||
pub use watch_later::WatchLater;
|
||||
|
||||
@@ -23,6 +24,7 @@ mod credential;
|
||||
mod danmaku;
|
||||
mod error;
|
||||
mod favorite_list;
|
||||
mod submission;
|
||||
mod video;
|
||||
mod watch_later;
|
||||
|
||||
@@ -121,6 +123,16 @@ pub enum VideoInfo {
|
||||
#[serde(rename = "pubdate", with = "ts_seconds")]
|
||||
pubtime: DateTime<Utc>,
|
||||
},
|
||||
Submission {
|
||||
title: String,
|
||||
bvid: String,
|
||||
#[serde(rename = "description")]
|
||||
intro: String,
|
||||
#[serde(rename = "pic")]
|
||||
cover: String,
|
||||
#[serde(rename = "created", with = "ts_seconds")]
|
||||
ctime: DateTime<Utc>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -160,5 +172,9 @@ mod tests {
|
||||
let stream = watch_later.into_video_stream();
|
||||
pin_mut!(stream);
|
||||
assert!(matches!(stream.next().await, Some(VideoInfo::WatchLater { .. })));
|
||||
let submission = Submission::new(&bili_client, "956761".to_string());
|
||||
let stream = submission.into_video_stream();
|
||||
pin_mut!(stream);
|
||||
assert!(matches!(stream.next().await, Some(VideoInfo::Submission { .. })));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
use anyhow::Result;
|
||||
use arc_swap::access::Access;
|
||||
use async_stream::stream;
|
||||
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, Validate, VideoInfo, MIXIN_KEY};
|
||||
pub struct Submission<'a> {
|
||||
client: &'a BiliClient,
|
||||
upper_id: String,
|
||||
}
|
||||
|
||||
impl<'a> Submission<'a> {
|
||||
pub fn new(client: &'a BiliClient, upper_id: String) -> Self {
|
||||
Self { client, upper_id }
|
||||
}
|
||||
|
||||
pub async fn get_info(&self) -> Result<Upper> {
|
||||
let mut res = self
|
||||
.client
|
||||
.request(Method::GET, "https://api.bilibili.com/x/web-interface/card")
|
||||
.query(&[("mid", self.upper_id.as_str())])
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json::<serde_json::Value>()
|
||||
.await?
|
||||
.validate()?;
|
||||
Ok(serde_json::from_value(res["data"]["card"].take())?)
|
||||
}
|
||||
|
||||
async fn get_videos(&self, page: i32) -> Result<Value> {
|
||||
self.client
|
||||
.request(Method::GET, "https://api.bilibili.com/x/space/wbi/arc/search")
|
||||
.query(&encoded_query(
|
||||
vec![
|
||||
("mid", self.upper_id.clone()),
|
||||
("order", "pubdate".to_string()),
|
||||
("order_avoided", "true".to_string()),
|
||||
("platform", "web".to_string()),
|
||||
("web_location", "1550101".to_string()),
|
||||
("pn", page.to_string()),
|
||||
("ps", "30".to_string()),
|
||||
],
|
||||
MIXIN_KEY.load().as_ref().unwrap(),
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json::<serde_json::Value>()
|
||||
.await?
|
||||
.validate()
|
||||
}
|
||||
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = VideoInfo> + 'a {
|
||||
stream! {
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let mut videos = match self.get_videos(page).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to get videos of upper {} page {}: {}", self.upper_id, page, e);
|
||||
break;
|
||||
},
|
||||
};
|
||||
if !videos["data"]["list"]["vlist"].is_array() {
|
||||
warn!("no medias found in upper {} page {}", self.upper_id, page);
|
||||
break;
|
||||
}
|
||||
let videos_info = match serde_json::from_value::<Vec<VideoInfo>>(videos["data"]["list"]["vlist"].take()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to parse videos of upper {} page {}: {}", self.upper_id, page, e);
|
||||
break;
|
||||
},
|
||||
};
|
||||
for video_info in videos_info{
|
||||
yield video_info;
|
||||
}
|
||||
if videos["data"]["page"]["count"].is_i64() && videos["data"]["page"]["count"].as_i64().unwrap() > (page * 30) as i64 {
|
||||
page += 1;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user