From a5d4d54335d0b845dfa859f4259f5e78dcaf9607 Mon Sep 17 00:00:00 2001 From: lanyeeee Date: Sun, 20 Jul 2025 05:24:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=94=B6=E8=97=8F=E5=A4=B9=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/bili_client.rs | 49 ++++++++-- src-tauri/src/commands.rs | 18 +++- src-tauri/src/lib.rs | 1 + src-tauri/src/types/fav_info.rs | 100 +++++++++++++++++++++ src-tauri/src/types/get_fav_info_params.rs | 8 ++ src-tauri/src/types/mod.rs | 2 + src/bindings.ts | 17 ++++ 7 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 src-tauri/src/types/fav_info.rs create mode 100644 src-tauri/src/types/get_fav_info_params.rs diff --git a/src-tauri/src/bili_client.rs b/src-tauri/src/bili_client.rs index 86d4efe..113146b 100644 --- a/src-tauri/src/bili_client.rs +++ b/src-tauri/src/bili_client.rs @@ -16,11 +16,11 @@ use crate::{ extensions::AppHandleExt, types::{ bangumi_info::BangumiInfo, bangumi_media_url::BangumiMediaUrl, cheese_info::CheeseInfo, - cheese_media_url::CheeseMediaUrl, fav_folders::FavFolders, + cheese_media_url::CheeseMediaUrl, fav_folders::FavFolders, fav_info::FavInfo, get_bangumi_info_params::GetBangumiInfoParams, get_cheese_info_params::GetCheeseInfoParams, - get_normal_info_params::GetNormalInfoParams, normal_info::NormalInfo, - normal_media_url::NormalMediaUrl, player_info::PlayerInfo, qrcode_data::QrcodeData, - qrcode_status::QrcodeStatus, user_info::UserInfo, + get_fav_info_params::GetFavInfoParams, get_normal_info_params::GetNormalInfoParams, + normal_info::NormalInfo, normal_media_url::NormalMediaUrl, player_info::PlayerInfo, + qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, user_info::UserInfo, }, }; @@ -459,7 +459,46 @@ impl BiliClient { Ok(fav_folders) } - + + pub async fn get_fav_info(&self, params: GetFavInfoParams) -> anyhow::Result { + let params = json!({ + "media_id": params.media_list_id, + "pn": params.pn, + "ps": 36, + "platform": "web", + }); + // 发送获取收藏夹信息的请求 + let request = self + .api_client + .read() + .get("https://api.bilibili.com/x/v3/fav/resource/list") + .query(¶ms) + .header("cookie", self.get_cookie()); + let http_resp = request.send().await?; + // 检查http响应状态码 + let status = http_resp.status(); + let body = http_resp.text().await?; + if status != StatusCode::OK { + return Err(anyhow!("预料之外的状态码({status}): {body}")); + } + // 尝试将body解析为BiliResp + let bili_resp: BiliResp = + serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + // 检查BiliResp的code字段 + if bili_resp.code != 0 { + return Err(anyhow!("预料之外的code: {bili_resp:?}")); + } + // 检查BiliResp的data是否存在 + let Some(data) = bili_resp.data else { + return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + }; + // 尝试将data解析为FavInfo + let data_str = data.to_string(); + let fav_info: FavInfo = serde_json::from_str(&data_str) + .context(format!("将data解析为FavInfo失败: {data_str}"))?; + + Ok(fav_info) + } fn get_cookie(&self) -> String { let sessdata = self.app.get_config().read().sessdata.clone(); diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index ef2cb42..a606a20 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -7,7 +7,12 @@ use crate::{ extensions::AppHandleExt, logger, types::{ - bangumi_info::BangumiInfo, bangumi_media_url::BangumiMediaUrl, cheese_info::CheeseInfo, cheese_media_url::CheeseMediaUrl, fav_folders::FavFolders, get_bangumi_info_params::GetBangumiInfoParams, get_cheese_info_params::GetCheeseInfoParams, get_normal_info_params::GetNormalInfoParams, normal_info::NormalInfo, normal_media_url::NormalMediaUrl, player_info::PlayerInfo, qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, user_info::UserInfo + bangumi_info::BangumiInfo, bangumi_media_url::BangumiMediaUrl, cheese_info::CheeseInfo, + cheese_media_url::CheeseMediaUrl, fav_folders::FavFolders, fav_info::FavInfo, + get_bangumi_info_params::GetBangumiInfoParams, get_cheese_info_params::GetCheeseInfoParams, + get_fav_info_params::GetFavInfoParams, get_normal_info_params::GetNormalInfoParams, + normal_info::NormalInfo, normal_media_url::NormalMediaUrl, player_info::PlayerInfo, + qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, user_info::UserInfo, }, }; @@ -194,3 +199,14 @@ pub async fn get_fav_folders(app: AppHandle, uid: i64) -> CommandResult CommandResult { + let bili_client = app.get_bili_client(); + let fav_info = bili_client + .get_fav_info(params) + .await + .map_err(|err| CommandError::from("获取收藏夹内容失败", err))?; + Ok(fav_info) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ceb4d73..1025d04 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -38,6 +38,7 @@ pub fn run() { get_cheese_url, get_player_info, get_fav_folders, + get_fav_info, ]) .events(tauri_specta::collect_events![LogEvent]); diff --git a/src-tauri/src/types/fav_info.rs b/src-tauri/src/types/fav_info.rs new file mode 100644 index 0000000..7c6413b --- /dev/null +++ b/src-tauri/src/types/fav_info.rs @@ -0,0 +1,100 @@ +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct FavInfo { + pub info: Info, + pub medias: Option>, + pub has_more: bool, + pub ttl: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +#[allow(clippy::struct_field_names)] +pub struct Info { + pub id: i64, + pub fid: i64, + pub mid: i64, + pub attr: i64, + pub title: String, + pub cover: String, + pub upper: Upper, + pub cover_type: i64, + pub cnt_info: CntInfo, + #[serde(rename = "type")] + pub type_field: i64, + pub intro: String, + pub ctime: i64, + pub mtime: i64, + pub state: i64, + pub fav_state: i64, + pub like_state: i64, + pub media_count: i64, + pub is_top: bool, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct Upper { + pub mid: i64, + pub name: String, + pub face: String, + pub followed: bool, + pub vip_type: i64, + pub vip_statue: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct CntInfo { + pub collect: i64, + pub play: i64, + pub thumb_up: i64, + pub share: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +#[allow(clippy::struct_field_names)] +pub struct MediaInFav { + pub id: i64, + #[serde(rename = "type")] + pub type_field: i64, + pub title: String, + pub cover: String, + pub intro: String, + pub page: i64, + pub duration: u64, + pub upper: UpperInMedia, + pub attr: i64, + pub cnt_info: CntInfoInMedia, + pub link: String, + pub ctime: i64, + pub pubtime: i64, + pub fav_time: i64, + pub bv_id: String, + pub bvid: String, + pub ugc: Option, + pub media_list_link: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct UpperInMedia { + pub mid: i64, + pub name: String, + pub face: String, + pub jump_link: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct CntInfoInMedia { + pub collect: i64, + pub play: i64, + pub danmaku: i64, + pub vt: i64, + pub play_switch: i64, + pub reply: i64, + pub view_text_1: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct Ugc { + pub first_cid: i64, +} diff --git a/src-tauri/src/types/get_fav_info_params.rs b/src-tauri/src/types/get_fav_info_params.rs new file mode 100644 index 0000000..4f8bd21 --- /dev/null +++ b/src-tauri/src/types/get_fav_info_params.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct GetFavInfoParams { + pub media_list_id: i64, + pub pn: i64, +} diff --git a/src-tauri/src/types/mod.rs b/src-tauri/src/types/mod.rs index 3662634..ead3d78 100644 --- a/src-tauri/src/types/mod.rs +++ b/src-tauri/src/types/mod.rs @@ -3,8 +3,10 @@ pub mod bangumi_media_url; pub mod cheese_info; pub mod cheese_media_url; pub mod fav_folders; +pub mod fav_info; pub mod get_bangumi_info_params; pub mod get_cheese_info_params; +pub mod get_fav_info_params; pub mod get_normal_info_params; pub mod log_level; pub mod normal_info; diff --git a/src/bindings.ts b/src/bindings.ts index 1ece551..855efcb 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -106,6 +106,14 @@ async getFavFolders(uid: number) : Promise> { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } +}, +async getFavInfo(params: GetFavInfoParams) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_fav_info", { params }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} } } @@ -138,6 +146,8 @@ export type Brief = { content: string; img: Img[]; title: string; type: number } export type CheeseInfo = { abtest_info: AbtestInfo; be_subscription: boolean; brief: Brief; consulting: Consulting; cooperation: Cooperation; course_content: string; cover: string; ep_count: number; episode_page: EpPage; episode_sort: number; episode_tag: EpTag; episodes: EpInCheese[]; expiry_day: number; expiry_info_content: string; faq: Faq; faq1: Faq1; is_enable_cash: boolean; is_series: boolean; live_ep_count: number; opened_ep_count: number; paid_jump: PaidJump; paid_view: boolean; payment: Payment; previewed_purchase_note: PreviewedPurchaseNote; purchase_format_note: PurchaseFormatNote; purchase_note: PurchaseNote; purchase_protocol: PurchaseProtocol; recommend_seasons: RecommendSeason[]; release_bottom_info: string; release_info: string; release_info2: string; release_status: string; season_id: number; season_tag: number; share_url: string; short_link: string; show_watermark: boolean; stat: StatInCheese; status: number; stop_sell: boolean; subscription_update_count_cycle_text: string; subtitle: string; title: string; up_info: UpInfoInCheese; update_status: number; user_status: UserStatusInCheese; watermark_interval: number } export type CheeseMediaUrl = { accept_format: string; code: number; seek_param: string; is_preview: number; fnval: number; video_project: boolean; play_view_business_info: PlayViewBusinessInfo | null; fnver: number; type: string; result: string; seek_type: string; from: string; video_codecid: number; no_rexcode: number; format: string; support_formats: SupportFormatInCheese[]; message: string; accept_quality: number[]; quality: number; timelength: number; durls: DurlInCheese[]; has_paid: boolean; dash: DashInCheese | null; accept_description: string[]; status: number } export type ClipInfoList = { materialNo: number; start: number; end: number; toastText: string; clipType: string } +export type CntInfo = { collect: number; play: number; thumb_up: number; share: number } +export type CntInfoInMedia = { collect: number; play: number; danmaku: number; vt: number; play_switch: number; reply: number; view_text_1: string } export type CommandError = { err_title: string; err_message: string } export type Config = { downloadDir: string; enableFileLogger: boolean; sessdata: string } export type Consulting = { consulting_flag: boolean; consulting_url: string } @@ -165,17 +175,20 @@ export type Faq = { content: string; link: string; title: string } export type Faq1 = { items: Faq1Item[]; title: string } export type Faq1Item = { answer: string; question: string } export type FavFolders = { count: number; list: Folder[] } +export type FavInfo = { info: Info; medias: MediaInFav[] | null; has_more: boolean; ttl: number } export type Fawkes = { config_version: number; ff_version: number } export type Flac = { display: boolean; audio: MediaInNormal | null } export type Folder = { id: number; fid: number; mid: number; attr: number; title: string; fav_state: number; media_count: number } export type GetBangumiInfoParams = { EpId: number } | { SeasonId: number } export type GetCheeseInfoParams = { EpId: number } | { SeasonId: number } +export type GetFavInfoParams = { media_list_id: number; pn: number } export type GetNormalInfoParams = { Bvid: string } | { Aid: number } export type Honor = { aid: number; type: number; desc: string; weekly_recommend_num: number } export type HonorReply = { honor: Honor[] | null } export type IconFont = { name: string; text: string } export type IconResource = Record export type Img = { aspect_ratio: number; url: string } +export type Info = { id: number; fid: number; mid: number; attr: number; title: string; cover: string; upper: Upper; cover_type: number; cnt_info: CntInfo; type: number; intro: string; ctime: number; mtime: number; state: number; fav_state: number; like_state: number; media_count: number; is_top: boolean } export type IpInfo = { ip: string; zone_ip: string; zone_id: number; country: string; province: string; city: string } export type JsonValue = null | boolean | number | string | JsonValue[] | { [key in string]: JsonValue } export type LabelInPlayerInfo = { path: string; text: string; label_theme: string; text_color: string; bg_style: number; bg_color: string; border_color: string; use_img_label: boolean; img_label_uri_hans: string; img_label_uri_hant: string; img_label_uri_hans_static: string; img_label_uri_hant_static: string } @@ -186,6 +199,7 @@ export type LogEvent = { timestamp: string; level: LogLevel; fields: { [key in s export type LogLevel = "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR" export type MediaInBangumi = { start_with_sap: number; bandwidth: number; sar: string; backup_url: string[]; codecs: string; base_url: string; segment_base: SegmentBaseInBangumi; mime_type: string; frame_rate: string; codecid: number; size: number; width: number; id: number; height: number; md5: string } export type MediaInCheese = { start_with_sap: number; bandwidth: number; sar: string; codecs: string; base_url: string; backup_url: string[]; segment_base: SegmentBaseInCheese; frame_rate: string; codecid: number; size: number; mime_type: string; width: number; id: number; height: number; md5: string } +export type MediaInFav = { id: number; type: number; title: string; cover: string; intro: string; page: number; duration: number; upper: UpperInMedia; attr: number; cnt_info: CntInfoInMedia; link: string; ctime: number; pubtime: number; fav_time: number; bv_id: string; bvid: string; ugc: Ugc | null; media_list_link: string } export type MediaInNormal = { id: number; start_with_sap: number; bandwidth: number; sar: string; codecs: string; base_url: string; backup_url: string[]; segment_base: SegmentBaseInNormal; mime_type: string; frame_rate: string; width: number; height: number; codecid: number } export type NewEp = { desc: string; id: number; is_new: number; title: string } export type NewEpInSeason = { cover: string; id: number; index_show: string } @@ -248,9 +262,12 @@ export type SubtitleInPlayerInfo = { allow_submit: boolean; lan: string; lan_doc export type SupportFormatInBangumi = { display_desc: string; has_preview: boolean; sub_description: string; superscript: string; need_login: boolean | null; codecs: string[]; format: string; description: string; need_vip: boolean | null; attribute: number; quality: number; new_description: string } export type SupportFormatInCheese = { display_desc: string; superscript: string; need_login: boolean; codecs: string[]; format: string; description: string; quality: number; new_description: string } export type SupportFormatInNormal = { quality: number; format: string; new_description: string; display_desc: string; superscript: string; codecs: string[] } +export type Ugc = { first_cid: number } export type UgcSeason = { id: number; title: string; cover: string; mid: number; intro: string; sign_state: number; attribute: number; sections: SectionInNormal[]; stat: StatInNormalSeason; ep_count: number; season_type: number; is_pay_season: boolean; enable_vt: number } export type UpInfoInBangumi = { avatar: string; mid: number; uname: string } export type UpInfoInCheese = { avatar: string; brief: string; follower: number; is_follow: number; is_living: boolean; link: string; mid: number; pendant: PendantInCheese; season_count: number; uname: string } +export type Upper = { mid: number; name: string; face: string; followed: boolean; vip_type: number; vip_statue: number } +export type UpperInMedia = { mid: number; name: string; face: string; jump_link: string } export type UserGarb = { url_image_ani_cut: string } export type UserInfo = { isLogin: boolean; email_verified: number; face: string; face_nft: number; face_nft_type: number; level_info: LevelInfoInUserInfo; mid: number; mobile_verified: number; money: number; moral: number; official: Official; officialVerify: OfficialVerify; pendant: PendantInUserInfo; scores: number; uname: string; vipDueDate: number; vipStatus: number; vipType: number; vip_pay_type: number; vip_theme_type: number; vip_label: VipLabel; vip_avatar_subscript: number; vip_nickname_color: string; vip: VipInUserInfo; wallet: Wallet | null; has_shop: boolean; shop_url: string; answer_status: number; is_senior_member: number; wbi_img: WbiImg; is_jury: boolean } export type UserStatusInBangumi = { area_limit: number; ban_area_show: number; follow: number; follow_status: number; login: number; pay: number; pay_pack_paid: number; sponsor: number }