From 605c55fdec6c240f1d5a93e26b7a9a5ca0bc6705 Mon Sep 17 00:00:00 2001 From: lanyeeee Date: Sun, 7 Sep 2025 06:12:38 +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=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/bili_client.rs | 59 +++++++++++++++++-- src-tauri/src/commands.rs | 16 +++++ src-tauri/src/lib.rs | 1 + .../src/types/get_history_info_params.rs | 38 ++++++++++++ src-tauri/src/types/history_info.rs | 52 ++++++++++++++++ src-tauri/src/types/mod.rs | 2 + src/bindings.ts | 14 +++++ 9 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 src-tauri/src/types/get_history_info_params.rs create mode 100644 src-tauri/src/types/history_info.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1db3dec..b1947c4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -304,6 +304,7 @@ dependencies = [ "reqwest-retry", "serde", "serde_json", + "serde_repr", "specta", "specta-typescript", "strfmt", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7e7aaaa..10ceb50 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -25,6 +25,7 @@ tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +serde_repr = "0.1" specta = { version = "=2.0.0-rc.20", features = ["serde_json"] } tauri-specta = { version = "=2.0.0-rc.20", features = ["derive", "typescript"] } diff --git a/src-tauri/src/bili_client.rs b/src-tauri/src/bili_client.rs index 89f4c1d..8554b6b 100644 --- a/src-tauri/src/bili_client.rs +++ b/src-tauri/src/bili_client.rs @@ -26,11 +26,13 @@ use crate::{ cheese_media_url::CheeseMediaUrl, fav_folders::FavFolders, fav_info::FavInfo, get_bangumi_follow_info_params::GetBangumiFollowInfoParams, get_bangumi_info_params::GetBangumiInfoParams, get_cheese_info_params::GetCheeseInfoParams, - get_fav_info_params::GetFavInfoParams, get_normal_info_params::GetNormalInfoParams, - get_user_video_info_params::GetUserVideoInfoParams, normal_info::NormalInfo, - normal_media_url::NormalMediaUrl, player_info::PlayerInfo, qrcode_data::QrcodeData, - qrcode_status::QrcodeStatus, skip_segments::SkipSegments, subtitle::Subtitle, tags::Tags, - user_info::UserInfo, user_video_info::UserVideoInfo, watch_later_info::WatchLaterInfo, + get_fav_info_params::GetFavInfoParams, get_history_info_params::GetHistoryInfoParams, + get_normal_info_params::GetNormalInfoParams, + get_user_video_info_params::GetUserVideoInfoParams, history_info::HistoryInfo, + normal_info::NormalInfo, normal_media_url::NormalMediaUrl, player_info::PlayerInfo, + qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, skip_segments::SkipSegments, + subtitle::Subtitle, tags::Tags, user_info::UserInfo, user_video_info::UserVideoInfo, + watch_later_info::WatchLaterInfo, }, }; @@ -672,6 +674,53 @@ impl BiliClient { Ok(bangumi_follow_info) } + pub async fn get_history_info( + &self, + params: GetHistoryInfoParams, + ) -> anyhow::Result { + let device_type: i64 = params.device_type.into(); + let params = json!({ + "pn": params.pn, + "keyword": params.keyword, + "business": "archive", + "add_time_start": params.add_time_start, + "add_time_end": params.add_time_end, + "arc_max_duration": params.arc_max_duration, + "arc_min_duration": params.arc_min_duration, + "device_type": device_type, + }); + let request = self + .api_client + .read() + .get("https://api.bilibili.com/x/web-interface/history/search") + .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解析为HistoryInfo + let data_str = data.to_string(); + let history_info: HistoryInfo = serde_json::from_str(&data_str) + .context(format!("将data解析为HistoryInfo失败: {data_str}"))?; + + Ok(history_info) + } + pub async fn get_media_chunk( &self, media_url: &str, diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index f19f14e..313e161 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -18,8 +18,10 @@ use crate::{ get_bangumi_info_params::GetBangumiInfoParams, get_cheese_info_params::GetCheeseInfoParams, get_fav_info_params::GetFavInfoParams, + get_history_info_params::GetHistoryInfoParams, get_normal_info_params::GetNormalInfoParams, get_user_video_info_params::GetUserVideoInfoParams, + history_info::HistoryInfo, normal_info::NormalInfo, qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, @@ -210,6 +212,20 @@ pub async fn get_bangumi_follow_info( Ok(bangumi_follow_info) } +#[tauri::command(async)] +#[specta::specta] +pub async fn get_history_info( + app: AppHandle, + params: GetHistoryInfoParams, +) -> CommandResult { + let bili_client = app.get_bili_client(); + let history_info = bili_client + .get_history_info(params) + .await + .map_err(|err| CommandError::from("获取历史记录失败", err))?; + Ok(history_info) +} + #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0862f71..0472855 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -46,6 +46,7 @@ pub fn run() { get_fav_info, get_watch_later_info, get_bangumi_follow_info, + get_history_info, create_download_tasks, pause_download_tasks, resume_download_tasks, diff --git a/src-tauri/src/types/get_history_info_params.rs b/src-tauri/src/types/get_history_info_params.rs new file mode 100644 index 0000000..8aa221f --- /dev/null +++ b/src-tauri/src/types/get_history_info_params.rs @@ -0,0 +1,38 @@ +use num_enum::{FromPrimitive, IntoPrimitive}; +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct GetHistoryInfoParams { + pub pn: i64, + pub keyword: String, + pub add_time_start: i64, + pub add_time_end: i64, + pub arc_max_duration: i64, + pub arc_min_duration: i64, + pub device_type: DeviceType, +} + +#[derive( + Default, + Debug, + Clone, + Copy, + Hash, + Eq, + PartialEq, + Serialize, + Deserialize, + Type, + IntoPrimitive, + FromPrimitive, +)] +#[repr(i64)] +pub enum DeviceType { + #[default] + All = 0, + PC = 1, + Mobile = 2, + Pad = 3, + TV = 4, +} diff --git a/src-tauri/src/types/history_info.rs b/src-tauri/src/types/history_info.rs new file mode 100644 index 0000000..c8046a7 --- /dev/null +++ b/src-tauri/src/types/history_info.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct HistoryInfo { + pub has_more: bool, + pub page: PageInHistory, + pub list: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct PageInHistory { + pub pn: i64, + pub total: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct HistoryDetail { + pub title: String, + pub long_title: String, + pub cover: String, + pub uri: String, + pub history: History, + pub videos: i64, + pub author_name: String, + pub author_face: String, + pub author_mid: i64, + pub view_at: i64, + pub progress: i64, + pub badge: String, + pub show_title: String, + pub duration: i64, + pub total: i64, + pub new_desc: String, + pub is_finish: i64, + pub is_fav: i64, + pub kid: i64, + pub tag_name: String, + pub live_status: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct History { + pub oid: i64, + pub epid: i64, + pub bvid: String, + pub page: i64, + pub cid: i64, + pub part: String, + pub business: String, + pub dt: i64, +} diff --git a/src-tauri/src/types/mod.rs b/src-tauri/src/types/mod.rs index cf5e5de..b3cc9e7 100644 --- a/src-tauri/src/types/mod.rs +++ b/src-tauri/src/types/mod.rs @@ -12,8 +12,10 @@ pub mod get_bangumi_follow_info_params; pub mod get_bangumi_info_params; pub mod get_cheese_info_params; pub mod get_fav_info_params; +pub mod get_history_info_params; pub mod get_normal_info_params; pub mod get_user_video_info_params; +pub mod history_info; pub mod log_level; pub mod normal_info; pub mod normal_media_url; diff --git a/src/bindings.ts b/src/bindings.ts index 11d5586..4b2be01 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -96,6 +96,14 @@ async getBangumiFollowInfo(params: GetBangumiFollowInfoParams) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_history_info", { params }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, async createDownloadTasks(params: CreateDownloadTaskParams) : Promise { await TAURI_INVOKE("create_download_tasks", { params }); }, @@ -259,6 +267,7 @@ export type CreateDownloadTaskParams = { Normal: CreateNormalDownloadTaskParams export type CreateNormalDownloadTaskParams = { info: NormalInfo; aid_cid_pairs: ([number, number | null])[] } export type DanmakuTask = { xml_selected: boolean; ass_selected: boolean; json_selected: boolean; completed: boolean } export type DescV2 = { raw_text: string; type: number; biz_id: number } +export type DeviceType = "All" | "PC" | "Mobile" | "Pad" | "TV" export type Dimension = { width: number; height: number; rotate: number } export type DimensionInBangumi = { height: number; rotate: number; width: number } export type DimensionInWatchLater = { width: number; height: number; rotate: number } @@ -290,8 +299,12 @@ type: number; pn: number; follow_status: 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 GetHistoryInfoParams = { pn: number; keyword: string; add_time_start: number; add_time_end: number; arc_max_duration: number; arc_min_duration: number; device_type: DeviceType } export type GetNormalInfoParams = { Bvid: string } | { Aid: number } export type GetUserVideoInfoParams = { pn: number; mid: number } +export type History = { oid: number; epid: number; bvid: string; page: number; cid: number; part: string; business: string; dt: number } +export type HistoryDetail = { title: string; long_title: string; cover: string; uri: string; history: History; videos: number; author_name: string; author_face: string; author_mid: number; view_at: number; progress: number; badge: string; show_title: string; duration: number; total: number; new_desc: string; is_finish: number; is_fav: number; kid: number; tag_name: string; live_status: number } +export type HistoryInfo = { has_more: boolean; page: PageInHistory; list: HistoryDetail[] | null } 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 } @@ -319,6 +332,7 @@ export type OfficialVerify = { type: number; desc: string } export type Op = { end: number; start: number } export type OwnerInNormal = { mid: number; name: string; face: string } export type OwnerInWatchLater = { mid: number; name: string; face: string } +export type PageInHistory = { pn: number; total: number } export type PageInNormal = { cid: number; page: number; from: string; part: string; duration: number; vid: string; weblink: string; dimension: Dimension; ctime: number } export type PageInNormalEp = { cid: number; page: number; from: string; part: string; duration: number; vid: string; weblink: string; dimension: Dimension } export type PageInUserVideo = { pn: number; ps: number; count: number }