feat: 后端支持获取稍后再看内容

This commit is contained in:
lanyeeee
2025-07-21 05:23:53 +08:00
parent a5d4d54335
commit 9f4a2b6099
6 changed files with 192 additions and 0 deletions
+36
View File
@@ -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<WatchLaterInfo> {
// 发送获取稍后观看信息的请求
let params = json!({"ps": 20, "pn": page});
let request = self
.api_client
.read()
.get("https://api.bilibili.com/x/v2/history/toview")
.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解析为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}")
+12
View File
@@ -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<WatchLaterInfo> {
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)
}
+1
View File
@@ -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]);
+1
View File
@@ -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;
+127
View File
@@ -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<MediaInWatchLater>,
}
#[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<String>,
pub mission_id: Option<i64>,
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<i64>,
pub first_frame: Option<String>,
pub pub_location: Option<String>,
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<i64>,
}
#[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<String>,
pub ctime: i64,
}
+15
View File
@@ -114,6 +114,14 @@ async getFavInfo(params: GetFavInfoParams) : Promise<Result<FavInfo, CommandErro
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async getWatchLaterInfo(page: number) : Promise<Result<WatchLaterInfo, CommandError>> {
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 }