feat: 支持获取我的收藏夹、收藏的视频合集与关注的 up 主 (#349)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-06-02 00:15:21 +08:00
committed by GitHub
parent c07e475fe6
commit 8510aa318e
5 changed files with 135 additions and 43 deletions

View File

@@ -0,0 +1,119 @@
#![allow(unused)]
use anyhow::Result;
use reqwest::Method;
use crate::bilibili::{BiliClient, Validate};
use crate::config::CONFIG;
pub struct Me<'a> {
client: &'a BiliClient,
mid: String,
}
impl<'a> Me<'a> {
pub fn new(client: &'a BiliClient) -> Self {
Self {
client,
mid: Self::my_id(),
}
}
pub async fn get_created_favorites(&self) -> Result<Option<Vec<FavoriteItem>>> {
let mut resp = self
.client
.request(Method::GET, "https://api.bilibili.com/x/v3/fav/folder/created/list-all")
.await
.query(&[("up_mid", &self.mid)])
.send()
.await?
.error_for_status()?
.json::<serde_json::Value>()
.await?
.validate()?;
Ok(serde_json::from_value(resp["data"]["list"].take())?)
}
pub async fn get_followed_collections(&self, page: i32) -> Result<Collections> {
let mut resp = self
.client
.request(Method::GET, "https://api.bilibili.com/x/v3/fav/folder/collected/list")
.await
.query(&[
("up_mid", self.mid.as_ref()),
("pn", page.to_string().as_ref()),
("ps", "20"),
("platform", "web"),
])
.send()
.await?
.error_for_status()?
.json::<serde_json::Value>()
.await?
.validate()?;
Ok(serde_json::from_value(resp["data"].take())?)
}
pub async fn get_followed_uppers(&self, page: i32) -> Result<FollowedUppers> {
let mut resp = self
.client
.request(Method::GET, "https://api.bilibili.com/x/relation/followings")
.await
.query(&[
("vmid", self.mid.as_ref()),
("pn", page.to_string().as_ref()),
("ps", "20"),
])
.send()
.await?
.error_for_status()?
.json::<serde_json::Value>()
.await?
.validate()?;
Ok(serde_json::from_value(resp["data"].take())?)
}
fn my_id() -> String {
CONFIG
.credential
.load()
.as_deref()
.map(|c| c.dedeuserid.clone())
.unwrap()
}
}
#[derive(Debug, serde::Deserialize)]
pub struct FavoriteItem {
pub title: String,
pub media_count: i64,
pub fid: i64,
pub mid: i64,
}
#[derive(Debug, serde::Deserialize)]
pub struct CollectionItem {
pub id: i64,
pub mid: i64,
pub state: i32,
pub title: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct Collections {
pub count: i64,
pub list: Option<Vec<CollectionItem>>,
}
#[derive(Debug, serde::Deserialize)]
pub struct FollowedUppers {
pub total: i64,
pub list: Vec<FollowedUpper>,
}
#[derive(Debug, serde::Deserialize)]
pub struct FollowedUpper {
pub mid: i64,
pub uname: String,
pub face: String,
pub sign: String,
}

View File

@@ -1,7 +1,5 @@
use std::sync::Arc;
#[cfg(test)]
pub use analyzer::VideoCodecs;
pub use analyzer::{BestStream, FilterOption};
use anyhow::{Result, bail, ensure};
use arc_swap::ArcSwapOption;
@@ -26,6 +24,7 @@ mod credential;
mod danmaku;
mod error;
mod favorite_list;
mod me;
mod submission;
mod subtitle;
mod video;

View File

@@ -38,10 +38,12 @@ pub static ARGS: Lazy<Args> = Lazy::new(Args::parse);
pub static CONFIG_DIR: Lazy<PathBuf> =
Lazy::new(|| dirs::config_dir().expect("No config path found").join("bili-sync"));
#[cfg(not(test))]
fn load_config() -> Config {
if cfg!(test) {
return Config::load(&CONFIG_DIR.join("test_config.toml")).unwrap_or(Config::test_default());
}
info!("开始加载配置文件..");
let config = Config::load().unwrap_or_else(|err| {
let config = Config::load(&CONFIG_DIR.join("config.toml")).unwrap_or_else(|err| {
if err
.downcast_ref::<std::io::Error>()
.is_none_or(|e| e.kind() != std::io::ErrorKind::NotFound)
@@ -58,36 +60,3 @@ fn load_config() -> Config {
info!("配置文件检查通过");
config
}
#[cfg(test)]
fn load_config() -> Config {
use crate::bilibili::{FilterOption, VideoCodecs};
let credential = match (
std::env::var("TEST_SESSDATA"),
std::env::var("TEST_BILI_JCT"),
std::env::var("TEST_BUVID3"),
std::env::var("TEST_DEDEUSERID"),
std::env::var("TEST_AC_TIME_VALUE"),
) {
(Ok(sessdata), Ok(bili_jct), Ok(buvid3), Ok(dedeuserid), Ok(ac_time_value)) => {
Some(std::sync::Arc::new(crate::bilibili::Credential {
sessdata,
bili_jct,
buvid3,
dedeuserid,
ac_time_value,
}))
}
_ => None,
};
Config {
credential: arc_swap::ArcSwapOption::from(credential),
cdn_sorting: true,
filter_option: FilterOption {
codecs: vec![VideoCodecs::HEV, VideoCodecs::AV1, VideoCodecs::AVC],
..Default::default()
},
..Default::default()
}
}

View File

@@ -1,6 +1,6 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::Result;
@@ -105,10 +105,8 @@ impl Config {
Ok(())
}
#[cfg(not(test))]
fn load() -> Result<Self> {
let config_path = CONFIG_DIR.join("config.toml");
let config_content = std::fs::read_to_string(config_path)?;
fn load(path: &Path) -> Result<Self> {
let config_content = std::fs::read_to_string(path)?;
Ok(toml::from_str(&config_content)?)
}
@@ -129,7 +127,6 @@ impl Config {
params
}
#[cfg(not(test))]
pub fn check(&self) {
let mut ok = true;
let video_sources = self.as_video_sources();
@@ -184,4 +181,11 @@ impl Config {
);
}
}
pub(super) fn test_default() -> Self {
Self {
cdn_sorting: true,
..Default::default()
}
}
}

View File

@@ -8,6 +8,7 @@ use tokio::io::{AsyncWriteExt, BufWriter};
use crate::config::{CONFIG, NFOTimeType};
#[allow(clippy::upper_case_acronyms)]
pub enum NFO<'a> {
Movie(Movie<'a>),
TVShow(TVShow<'a>),