diff --git a/src-tauri/src/bili_client.rs b/src-tauri/src/bili_client.rs index afb0309..c191f49 100644 --- a/src-tauri/src/bili_client.rs +++ b/src-tauri/src/bili_client.rs @@ -15,7 +15,7 @@ use tauri::{ use crate::{ extensions::AppHandleExt, types::{ - bangumi_info::BangumiInfo, cheese_info::CheeseInfo, + bangumi_info::BangumiInfo, bangumi_media_url::BangumiMediaUrl, cheese_info::CheeseInfo, get_bangumi_info_params::GetBangumiInfoParams, get_cheese_info_params::GetCheeseInfoParams, get_normal_info_params::GetNormalInfoParams, normal_info::NormalInfo, normal_media_url::NormalMediaUrl, qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, @@ -302,6 +302,49 @@ impl BiliClient { Ok(media_url) } + pub async fn get_bangumi_url(&self, cid: i64) -> anyhow::Result { + let params = json!({ + "cid": cid, + "qn": 127, + "fnval": 4048, + }); + // 发送获取番剧url的请求 + let request = self + .api_client + .read() + .get("https://api.bilibili.com/pgc/player/web/playurl") + .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 == -10403 { + return Err(anyhow!( + "地区限制,请使用代理或切换线路后重试: {bili_resp:?}" + )); + } else 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解析为BangumiMediaUrl + let data_str = data.to_string(); + let media_url: BangumiMediaUrl = serde_json::from_str(&data_str) + .context(format!("将data解析为BangumiMediaUrl失败: {data_str}"))?; + + Ok(media_url) + } + 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 da6a9a4..3ac993d 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -7,7 +7,7 @@ use crate::{ extensions::AppHandleExt, logger, types::{ - bangumi_info::BangumiInfo, cheese_info::CheeseInfo, + bangumi_info::BangumiInfo, bangumi_media_url::BangumiMediaUrl, cheese_info::CheeseInfo, get_bangumi_info_params::GetBangumiInfoParams, get_cheese_info_params::GetCheeseInfoParams, get_normal_info_params::GetNormalInfoParams, normal_info::NormalInfo, normal_media_url::NormalMediaUrl, qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, @@ -154,3 +154,14 @@ pub async fn get_normal_url( .map_err(|err| CommandError::from("获取普通视频url失败", err))?; Ok(media_url) } + +#[tauri::command(async)] +#[specta::specta] +pub async fn get_bangumi_url(app: AppHandle, cid: i64) -> CommandResult { + let bili_client = app.get_bili_client(); + let media_url = bili_client + .get_bangumi_url(cid) + .await + .map_err(|err| CommandError::from("获取番剧视频url失败", err))?; + Ok(media_url) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 9884fac..f87db74 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -34,6 +34,7 @@ pub fn run() { get_bangumi_info, get_cheese_info, get_normal_url, + get_bangumi_url, ]) .events(tauri_specta::collect_events![LogEvent]); diff --git a/src-tauri/src/types/bangumi_media_url.rs b/src-tauri/src/types/bangumi_media_url.rs new file mode 100644 index 0000000..b0f8054 --- /dev/null +++ b/src-tauri/src/types/bangumi_media_url.rs @@ -0,0 +1,123 @@ +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct BangumiMediaUrl { + pub accept_format: String, + pub code: i64, + pub seek_param: String, + pub is_preview: i64, + pub fnval: i64, + pub video_project: bool, + pub fnver: i64, + #[serde(rename = "type")] + pub type_field: String, + pub bp: i64, + pub seek_type: String, + pub result: String, + pub vip_type: Option, + pub from: String, + pub video_codecid: i64, + pub record_info: Option, + pub is_drm: bool, + pub no_rexcode: i64, + pub format: String, + pub support_formats: Vec, + pub message: String, + pub accept_quality: Vec, + pub quality: i64, + pub timelength: i64, + pub durls: Vec, + pub has_paid: bool, + pub vip_status: Option, + pub error_code: i64, + pub dash: Option, + pub clip_info_list: Vec, + pub accept_description: Vec, + pub status: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct RecordInfo { + pub record_icon: String, + pub record: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct SupportFormatInBangumi { + pub display_desc: String, + pub has_preview: bool, + pub sub_description: String, + pub superscript: String, + pub need_login: Option, + pub codecs: Vec, + pub format: String, + pub description: String, + pub need_vip: Option, + pub attribute: i64, + pub quality: i64, + pub new_description: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct DashInBangumi { + pub duration: u64, + pub min_buffer_time: f64, + pub video: Vec, + pub audio: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct MediaInBangumi { + pub start_with_sap: i64, + pub bandwidth: i64, + pub sar: String, + pub backup_url: Vec, + pub codecs: String, + pub base_url: String, + pub segment_base: SegmentBaseInBangumi, + pub mime_type: String, + pub frame_rate: String, + pub codecid: i64, + pub size: i64, + pub width: i64, + pub id: i64, + pub height: i64, + pub md5: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct SegmentBaseInBangumi { + pub initialization: String, + pub index_range: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct ClipInfoList { + #[serde(rename = "materialNo")] + pub material_no: i64, + pub start: i64, + pub end: i64, + #[serde(rename = "toastText")] + pub toast_text: String, + #[serde(rename = "clipType")] + pub clip_type: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct DurlInBangumi { + pub durl: Vec, + pub quality: i64, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +pub struct DurlDetailInBangumi { + pub size: i64, + pub ahead: String, + pub length: i64, + pub vhead: String, + pub backup_url: Vec, + pub url: String, + pub order: i64, + pub md5: String, +} diff --git a/src-tauri/src/types/mod.rs b/src-tauri/src/types/mod.rs index fa7d392..8189044 100644 --- a/src-tauri/src/types/mod.rs +++ b/src-tauri/src/types/mod.rs @@ -1,4 +1,5 @@ pub mod bangumi_info; +pub mod bangumi_media_url; pub mod cheese_info; pub mod get_bangumi_info_params; pub mod get_cheese_info_params; diff --git a/src/bindings.ts b/src/bindings.ts index 930052f..f4fab27 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -74,6 +74,14 @@ async getNormalUrl(bvid: string, cid: number) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_bangumi_url", { cid }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} } } @@ -99,19 +107,24 @@ export type Area = { id: number; name: string } export type ArgueInfo = { argue_msg: string; argue_type: number; argue_link: string } export type Author = { mid: number; name: string; face: string } export type BadgeInfo = { bg_color: string; bg_color_night: string; text: string } +export type BangumiMediaUrl = { accept_format: string; code: number; seek_param: string; is_preview: number; fnval: number; video_project: boolean; fnver: number; type: string; bp: number; seek_type: string; result: string; vip_type: number | null; from: string; video_codecid: number; record_info: RecordInfo | null; is_drm: boolean; no_rexcode: number; format: string; support_formats: SupportFormatInBangumi[]; message: string; accept_quality: number[]; quality: number; timelength: number; durls: DurlInBangumi[]; has_paid: boolean; vip_status: number | null; error_code: number; dash: DashInBangumi | null; clip_info_list: ClipInfoList[]; accept_description: string[]; status: number } export type BangumiInfo = { activity: Activity; actors: string; alias: string; areas: Area[]; bkg_cover: string; cover: string; delivery_fragment_video: boolean; enable_vt: boolean; episodes: EpInBangumi[]; evaluate: string; hide_ep_vv_vt_dm: number; icon_font: IconFont; jp_title: string; link: string; media_id: number; mode: number; new_ep: NewEp; payment: PaymentInBangumi | null; play_strategy: PlayStrategy | null; positive: Positive; publish: Publish; rating: Rating | null; record: string; rights: RightsInBangumi; season_id: number; season_title: string; seasons: Season[]; section: SectionInBangumi[] | null; series: Series; share_copy: string; share_sub_title: string; share_url: string; show: Show; show_season_type: number; square_cover: string; staff: string; stat: StatInBangumi; status: number; styles: string[]; subtitle: string; title: string; total: number; type: number; up_info: UpInfoInBangumi | null; user_status: UserStatusInBangumi } 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 ClipInfoList = { materialNo: number; start: number; end: number; toastText: string; clipType: 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 } export type ContentList = { bold: boolean; content: string; number: string } export type Cooperation = { link: string } +export type DashInBangumi = { duration: number; min_buffer_time: number; video: MediaInBangumi[]; audio: MediaInBangumi[] | null } export type DashInNormal = { duration: number; min_buffer_time: number; video: MediaInNormal[]; audio: MediaInNormal[] | null; dolby: Dolby; flac: Flac | null } 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 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 DurlInBangumi = { durl: DurlDetailInBangumi[]; quality: number } export type Ed = { end: number; start: number } export type EpInBangumi = { aid: number; badge: string; badge_info: BadgeInfo; badge_type: number | null; bvid: string | null; cid: number; cover: string; dimension: DimensionInBangumi | null; duration: number | null; enable_vt: boolean; ep_id: number; from: string | null; id: number; is_view_hide: boolean; link: string; link_type: string | null; long_title: string | null; pub_time: number; pv: number; release_date: string | null; rights: RightsInBangumiEp | null; section_type: number; share_copy: string | null; share_url: string | null; short_link: string | null; showDrmLoginDialog: boolean; show_title: string | null; skip: Skip | null; status: number; subtitle: string | null; title: string; vid: string | null; icon_font: IconFont | null } export type EpInCheese = { aid: number; catalogue_index: number; cid: number; cover: string; duration: number; ep_status: number; episode_can_view: boolean; from: string; id: number; index: number; label: string | null; page: number; play: number; play_way: number; playable: boolean; release_date: number; show_vt: boolean; status: number; subtitle: string; title: string; watched: boolean; watchedHistory: number } @@ -134,6 +147,7 @@ export type LabelInUserInfo = { path: string; text: string; label_theme: string; export type LevelInfoInUserInfo = { current_level: number; current_min: number; current_exp: number } export type LogEvent = { timestamp: string; level: LogLevel; fields: { [key in string]: JsonValue }; target: string; filename: string; line_number: number } 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 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 } @@ -163,6 +177,7 @@ export type QrcodeData = { url: string; qrcode_key: string } export type QrcodeStatus = { url: string; refresh_token: string; timestamp: number; code: number; message: string } export type Rating = { count: number; score: number } export type RecommendSeason = { cover: string; ep_count: string; id: number; season_url: string; subtitle: string; title: string; view: number } +export type RecordInfo = { record_icon: string; record: string } export type Rights = { 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; clean_mode: number; is_stein_gate: number; is_360: number; no_share: number; arc_pay: number; free_watch: 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 } @@ -170,6 +185,7 @@ export type RightsInNormalEp = { bp: number; elec: number; download: number; mov 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[] } +export type SegmentBaseInBangumi = { initialization: string; index_range: string } export type SegmentBaseInNormal = { initialization: string; index_range: string } export type Series = { display_type: number; series_id: number; series_title: string } export type Show = { wide_screen: number } @@ -183,6 +199,7 @@ export type StatInNormalSeason = { season_id: number; view: number; danmaku: num export type StatInSeason = { favorites: number; series_follow: number; views: number; vt: 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 SubtitleInNormal = { allow_submit: boolean; list: SubtitleDetailInNormal[] } +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 SupportFormatInNormal = { quality: number; format: string; new_description: string; display_desc: string; superscript: string; codecs: string[] } 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 }