feat: 后端支持获取历史记录

This commit is contained in:
lanyeeee
2025-09-08 06:08:20 +08:00
parent dd86eb9e41
commit 605c55fdec
9 changed files with 179 additions and 5 deletions
+1
View File
@@ -304,6 +304,7 @@ dependencies = [
"reqwest-retry",
"serde",
"serde_json",
"serde_repr",
"specta",
"specta-typescript",
"strfmt",
+1
View File
@@ -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"] }
+54 -5
View File
@@ -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<HistoryInfo> {
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(&params)
.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,
+16
View File
@@ -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<HistoryInfo> {
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]
+1
View File
@@ -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,
@@ -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,
}
+52
View File
@@ -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<Vec<HistoryDetail>>,
}
#[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,
}
+2
View File
@@ -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;
+14
View File
@@ -96,6 +96,14 @@ async getBangumiFollowInfo(params: GetBangumiFollowInfoParams) : Promise<Result<
else return { status: "error", error: e as any };
}
},
async getHistoryInfo(params: GetHistoryInfoParams) : Promise<Result<HistoryInfo, CommandError>> {
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<void> {
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 }