diff --git a/src-tauri/src/bili_client.rs b/src-tauri/src/bili_client.rs index 113146b..fb55b11 100644 --- a/src-tauri/src/bili_client.rs +++ b/src-tauri/src/bili_client.rs @@ -21,6 +21,7 @@ use crate::{ 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, + watch_later_info::WatchLaterInfo, }, }; @@ -500,6 +501,41 @@ impl BiliClient { Ok(fav_info) } + pub async fn get_watch_later_info(&self, page: i32) -> anyhow::Result { + // 发送获取稍后观看信息的请求 + let params = json!({"ps": 20, "pn": page}); + let request = self + .api_client + .read() + .get("https://api.bilibili.com/x/v2/history/toview") + .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解析为WatchLaterInfo + let data_str = data.to_string(); + let watch_later_info: WatchLaterInfo = serde_json::from_str(&data_str) + .context(format!("将data解析为WatchLaterInfo失败: {data_str}"))?; + + Ok(watch_later_info) + } + fn get_cookie(&self) -> String { let sessdata = self.app.get_config().read().sessdata.clone(); format!("SESSDATA={sessdata}") diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index a606a20..f199748 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -13,6 +13,7 @@ use crate::{ 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, + watch_later_info::WatchLaterInfo, }, }; @@ -210,3 +211,14 @@ pub async fn get_fav_info(app: AppHandle, params: GetFavInfoParams) -> CommandRe .map_err(|err| CommandError::from("获取收藏夹内容失败", err))?; Ok(fav_info) } + +#[tauri::command(async)] +#[specta::specta] +pub async fn get_watch_later_info(app: AppHandle, page: i32) -> CommandResult { + let bili_client = app.get_bili_client(); + let watch_later_info = bili_client + .get_watch_later_info(page) + .await + .map_err(|err| CommandError::from("获取稍后观看内容失败", err))?; + Ok(watch_later_info) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1025d04..360a599 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -39,6 +39,7 @@ pub fn run() { get_player_info, get_fav_folders, get_fav_info, + get_watch_later_info, ]) .events(tauri_specta::collect_events![LogEvent]); diff --git a/src-tauri/src/types/mod.rs b/src-tauri/src/types/mod.rs index ead3d78..4889f43 100644 --- a/src-tauri/src/types/mod.rs +++ b/src-tauri/src/types/mod.rs @@ -15,3 +15,4 @@ pub mod player_info; pub mod qrcode_data; pub mod qrcode_status; pub mod user_info; +pub mod watch_later_info; diff --git a/src-tauri/src/types/watch_later_info.rs b/src-tauri/src/types/watch_later_info.rs new file mode 100644 index 0000000..953e36f --- /dev/null +++ b/src-tauri/src/types/watch_later_info.rs @@ -0,0 +1,127 @@ +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct WatchLaterInfo { + pub count: i64, + pub list: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct MediaInWatchLater { + pub aid: i64, + pub videos: i64, + pub tid: i64, + pub tname: String, + pub copyright: i64, + pub pic: String, + pub title: String, + pub pubdate: i64, + pub ctime: i64, + pub desc: String, + pub state: i64, + pub duration: i64, + pub redirect_url: Option, + pub mission_id: Option, + pub rights: RightsInWatchLater, + pub owner: OwnerInWatchLater, + pub stat: StatInWatchLater, + pub dynamic: String, + pub dimension: DimensionInWatchLater, + pub short_link_v2: String, + pub up_from_v2: Option, + pub first_frame: Option, + pub pub_location: Option, + pub cover43: String, + pub tidv2: i64, + pub tnamev2: String, + pub pid_v2: i64, + pub pid_name_v2: String, + pub page: PageInWatchLater, + pub count: i64, + pub cid: i64, + pub progress: i64, + pub add_at: i64, + pub bvid: String, + pub uri: String, + pub enable_vt: i64, + pub view_text_1: String, + pub card_type: i64, + pub left_icon_type: i64, + pub left_text: String, + pub right_icon_type: i64, + pub right_text: String, + pub arc_state: i64, + pub pgc_label: String, + pub show_up: bool, + pub forbid_fav: bool, + pub forbid_sort: bool, + pub season_title: String, + pub long_title: String, + pub index_title: String, + pub c_source: String, + pub season_id: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct RightsInWatchLater { + pub bp: i64, + pub elec: i64, + pub download: i64, + pub movie: i64, + pub pay: i64, + pub hd5: i64, + pub no_reprint: i64, + pub autoplay: i64, + pub ugc_pay: i64, + pub is_cooperation: i64, + pub ugc_pay_preview: i64, + pub no_background: i64, + pub arc_pay: i64, + pub pay_free_watch: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct OwnerInWatchLater { + pub mid: i64, + pub name: String, + pub face: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct StatInWatchLater { + pub aid: i64, + pub view: i64, + pub danmaku: i64, + pub reply: i64, + pub favorite: i64, + pub coin: i64, + pub share: i64, + pub now_rank: i64, + pub his_rank: i64, + pub like: i64, + pub dislike: i64, + pub vt: i64, + pub vv: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct DimensionInWatchLater { + pub width: i64, + pub height: i64, + pub rotate: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct PageInWatchLater { + pub cid: i64, + pub page: i64, + pub from: String, + pub part: String, + pub duration: i64, + pub vid: String, + pub weblink: String, + pub dimension: DimensionInWatchLater, + pub first_frame: Option, + pub ctime: i64, +} diff --git a/src/bindings.ts b/src/bindings.ts index 855efcb..16707e0 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -114,6 +114,14 @@ async getFavInfo(params: GetFavInfoParams) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_watch_later_info", { page }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} } } @@ -159,6 +167,7 @@ export type DashInNormal = { duration: number; min_buffer_time: number; video: M export type DescV2 = { raw_text: string; type: number; biz_id: number } 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 } export type Dolby = { type: number; audio: MediaInNormal[] | null } export type DurlDetailInBangumi = { size: number; ahead: string; length: number; vhead: string; backup_url: string[]; url: string; order: number; md5: string } export type DurlDetailInCheese = { size: number; ahead: string; length: number; vhead: string; backup_url: string[]; url: string; order: number; md5: string } @@ -201,6 +210,7 @@ export type MediaInBangumi = { start_with_sap: number; bandwidth: number; sar: s 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 MediaInWatchLater = { aid: number; videos: number; tid: number; tname: string; copyright: number; pic: string; title: string; pubdate: number; ctime: number; desc: string; state: number; duration: number; redirect_url: string | null; mission_id: number | null; rights: RightsInWatchLater; owner: OwnerInWatchLater; stat: StatInWatchLater; dynamic: string; dimension: DimensionInWatchLater; short_link_v2: string; up_from_v2: number | null; first_frame: string | null; pub_location: string | null; cover43: string; tidv2: number; tnamev2: string; pid_v2: number; pid_name_v2: string; page: PageInWatchLater; count: number; cid: number; progress: number; add_at: number; bvid: string; uri: string; enable_vt: number; view_text_1: string; card_type: number; left_icon_type: number; left_text: string; right_icon_type: number; right_text: string; arc_state: number; pgc_label: string; show_up: boolean; forbid_fav: boolean; forbid_sort: boolean; season_title: string; long_title: string; index_title: string; c_source: string; season_id: number | null } export type NewEp = { desc: string; id: number; is_new: number; title: string } export type NewEpInSeason = { cover: string; id: number; index_show: string } export type NormalInfo = { bvid: string; aid: number; videos: number; tid: number; tid_v2: number; tname: string; tname_v2: string; copyright: number; pic: string; title: string; pubdate: number; ctime: number; desc: string; desc_v2: DescV2[] | null; state: number; duration: number; rights: Rights; owner: OwnerInNormal; stat: StatInNormal; argue_info: ArgueInfo; dynamic: string; cid: number; dimension: Dimension; teenage_mode: number; is_chargeable_season: boolean; is_story: boolean; is_upower_exclusive: boolean; is_upower_play: boolean; is_upower_preview: boolean; enable_vt: number; vt_display: string; is_upower_exclusive_with_qa: boolean; no_cache: boolean; pages: PageInNormal[]; subtitle: SubtitleInNormal; staff: Staff[] | null; ugc_season: UgcSeason | null; is_season_display: boolean; user_garb: UserGarb; honor_reply: HonorReply; like_icon: string; need_jump_bv: boolean; disable_show_up_info: boolean; is_story_play: number; is_view_self: boolean } @@ -211,8 +221,10 @@ export type OnlineSwitch = { enable_gray_dash_playback: string; new_broadcast: s export type Op = { end: number; start: number } export type Options = { is_360: boolean; without_vip: boolean } export type OwnerInNormal = { mid: number; name: string; face: string } +export type OwnerInWatchLater = { mid: number; name: string; face: string } 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 PageInWatchLater = { cid: number; page: number; from: string; part: string; duration: number; vid: string; weblink: string; dimension: DimensionInWatchLater; first_frame: string | null; ctime: number } export type PaidJump = { jump_url_for_app: string; url: string } export type PayType = { allow_discount: number; allow_pack: number; allow_ticket: number; allow_time_limit: number; allow_vip_discount: number; forbid_bb: number } export type Payment = { bp_enough: number; desc: string; my_bp: number; pay_shade: string; price: number; price_format: string; price_unit: string; refresh_text: string; select_text: string } @@ -238,6 +250,7 @@ export type Rights = { bp: number; elec: number; download: number; movie: number export type RightsInBangumi = { allow_bp: number; allow_bp_rank: number; allow_download: number; allow_review: number; area_limit: number; ban_area_show: number; can_watch: number; copyright: string; forbid_pre: number; freya_white: number; is_cover_show: number; is_preview: number; only_vip_download: number; resource: string; watch_platform: number } export type RightsInBangumiEp = { allow_dm: number; allow_download: number; area_limit: number } export type RightsInNormalEp = { bp: number; elec: number; download: number; movie: number; pay: number; hd5: number; no_reprint: number; autoplay: number; ugc_pay: number; is_cooperation: number; ugc_pay_preview: number; arc_pay: number; free_watch: number } +export type RightsInWatchLater = { bp: number; elec: number; download: number; movie: number; pay: number; hd5: number; no_reprint: number; autoplay: number; ugc_pay: number; is_cooperation: number; ugc_pay_preview: number; no_background: number; arc_pay: number; pay_free_watch: number } export type Season = { badge: string; badge_info: BadgeInfo; badge_type: number; cover: string; enable_vt: boolean; horizontal_cover_1610: string; horizontal_cover_169: string; icon_font: IconFont; media_id: number; new_ep: NewEpInSeason; season_id: number; season_title: string; season_type: number; stat: StatInSeason } export type SectionInBangumi = { attr: number; episodes: EpInBangumi[]; id: number; title: string; type: number; type2: number } export type SectionInNormal = { season_id: number; id: number; title: string; type: number; episodes: EpInNormal[] } @@ -255,6 +268,7 @@ export type StatInNormal = { aid: number; view: number; danmaku: number; reply: export type StatInNormalEp = { aid: number; view: number; danmaku: number; reply: number; fav: number; coin: number; share: number; now_rank: number; his_rank: number; like: number; dislike: number; evaluation: string; argue_msg: string; vt: number; vv: number } export type StatInNormalSeason = { season_id: number; view: number; danmaku: number; reply: number; fav: number; coin: number; share: number; now_rank: number; his_rank: number; like: number; vt: number; vv: number } export type StatInSeason = { favorites: number; series_follow: number; views: number; vt: number } +export type StatInWatchLater = { aid: number; view: number; danmaku: number; reply: number; favorite: number; coin: number; share: number; now_rank: number; his_rank: number; like: number; dislike: number; vt: number; vv: number } export type SubtitleDetailInNormal = { id: number; lan: string; lan_doc: string; is_lock: boolean; subtitle_url: string; type: number; id_str: string; ai_type: number; ai_status: number } export type SubtitleDetailInPlayerInfo = { id: number; lan: string; lan_doc: string; is_lock: boolean; subtitle_url: string; type: number; id_str: string; ai_type: number; ai_status: number } export type SubtitleInNormal = { allow_submit: boolean; list: SubtitleDetailInNormal[] } @@ -278,6 +292,7 @@ export type VipInPlayerInfo = { type: number; status: number; due_date: number; export type VipInUserInfo = { type: number; status: number; due_date: number; vip_pay_type: number; theme_type: number; label: LabelInUserInfo; avatar_subscript: number; nickname_color: string; role: number; avatar_subscript_url: string; tv_vip_status: number; tv_vip_pay_type: number; tv_due_date: number } export type VipLabel = { 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 } export type Wallet = { mid: number; bcoin_balance: number; coupon_balance: number; coupon_due_time: number } +export type WatchLaterInfo = { count: number; list: MediaInWatchLater[] } export type WatchProgress = { current_watch_progress: number; last_ep_id: number; last_ep_index: string; last_time: number } export type WbiImg = { img_url: string; sub_url: string }