From b63be91f39e512465cbf9b2dff7557db0e5e403d Mon Sep 17 00:00:00 2001 From: lanyeeee Date: Sat, 2 Aug 2025 05:12:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/bili_client.rs | 51 +++++++++++++++++++++++++++++++++--- src-tauri/src/commands.rs | 19 ++++++++++---- src-tauri/src/config.rs | 14 ++++++++++ src/bindings.ts | 3 ++- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/bili_client.rs b/src-tauri/src/bili_client.rs index 4930eb7..fd06455 100644 --- a/src-tauri/src/bili_client.rs +++ b/src-tauri/src/bili_client.rs @@ -16,7 +16,8 @@ use tauri::{ use tokio::task::JoinSet; use crate::{ - extensions::AppHandleExt, + config::ProxyMode, + extensions::{AnyhowErrorToStringChain, AppHandleExt}, protobuf::DmSegMobileReply, types::{ bangumi_info::BangumiInfo, bangumi_media_url::BangumiMediaUrl, cheese_info::CheeseInfo, @@ -59,6 +60,15 @@ impl BiliClient { } } + pub fn reload_client(&self) { + let api_client = create_api_client(&self.app); + *self.api_client.write() = api_client; + let media_client = create_media_client(&self.app); + *self.media_client.write() = media_client; + let content_length_client = create_content_length_client(&self.app); + *self.content_length_client.write() = content_length_client; + } + pub async fn generate_qrcode(&self) -> anyhow::Result { // 发送生成二维码请求 let request = self @@ -800,7 +810,7 @@ impl BiliClient { } } -fn create_api_client(_app: &AppHandle) -> ClientWithMiddleware { +fn create_api_client(app: &AppHandle) -> ClientWithMiddleware { let retry_policy = ExponentialBackoff::builder() .base(1) .jitter(Jitter::Bounded) @@ -811,6 +821,7 @@ fn create_api_client(_app: &AppHandle) -> ClientWithMiddleware { headers.insert("referer", HeaderValue::from_static(REFERRER)); let client = reqwest::ClientBuilder::new() + .set_proxy(app, "api_client") .timeout(Duration::from_secs(3)) .default_headers(headers) .build() @@ -821,7 +832,7 @@ fn create_api_client(_app: &AppHandle) -> ClientWithMiddleware { .build() } -fn create_media_client(_app: &AppHandle) -> ClientWithMiddleware { +fn create_media_client(app: &AppHandle) -> ClientWithMiddleware { let retry_policy = ExponentialBackoff::builder() .base(1) .jitter(Jitter::Bounded) @@ -832,6 +843,7 @@ fn create_media_client(_app: &AppHandle) -> ClientWithMiddleware { headers.insert("referer", HeaderValue::from_static(REFERRER)); let client = reqwest::ClientBuilder::new() + .set_proxy(app, "media_client") .default_headers(headers) .build() .unwrap(); @@ -841,18 +853,49 @@ fn create_media_client(_app: &AppHandle) -> ClientWithMiddleware { .build() } -fn create_content_length_client(_app: &AppHandle) -> Client { +fn create_content_length_client(app: &AppHandle) -> Client { let mut headers = HeaderMap::new(); headers.insert("user-agent", HeaderValue::from_static(USER_AGENT)); headers.insert("referer", HeaderValue::from_static(REFERRER)); reqwest::ClientBuilder::new() + .set_proxy(app, "content_length_client") .timeout(Duration::from_secs(5)) .default_headers(headers) .build() .unwrap() } +trait ClientBuilderExt { + fn set_proxy(self, app: &AppHandle, client_name: &str) -> Self; +} + +impl ClientBuilderExt for reqwest::ClientBuilder { + fn set_proxy(self, app: &AppHandle, client_name: &str) -> reqwest::ClientBuilder { + let proxy_mode = app.get_config().read().proxy_mode; + match proxy_mode { + ProxyMode::NoProxy => self.no_proxy(), + ProxyMode::System => self, + ProxyMode::Custom => { + let config = app.get_config().inner().read(); + let proxy_host = &config.proxy_host; + let proxy_port = &config.proxy_port; + let proxy_url = format!("http://{proxy_host}:{proxy_port}"); + + match reqwest::Proxy::all(&proxy_url).map_err(anyhow::Error::from) { + Ok(proxy) => self.proxy(proxy), + Err(err) => { + let err_title = format!("{client_name}将`{proxy_url}`设为代理失败,将直连"); + let string_chain = err.to_string_chain(); + tracing::error!(err_title, message = string_chain); + self.no_proxy() + } + } + } + } + } +} + #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct BiliResp { pub code: i64, diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 1b6d618..c30dce1 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -52,13 +52,18 @@ pub fn get_config(config: tauri::State>) -> Config { #[specta::specta] #[allow(clippy::needless_pass_by_value)] pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> { + let bili_client = app.get_bili_client(); let config_state = app.get_config(); + let proxy_changed = { + let config_state = config_state.read(); + config_state.proxy_mode != config.proxy_mode + || config_state.proxy_host != config.proxy_host + || config_state.proxy_port != config.proxy_port + }; + let enable_file_logger = config.enable_file_logger; - let enable_file_logger_changed = config_state - .read() - .enable_file_logger - .ne(&enable_file_logger); + let file_logger_changed = config_state.read().enable_file_logger != enable_file_logger; { // 包裹在大括号中,以便自动释放写锁 @@ -70,7 +75,11 @@ pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> { tracing::debug!("保存配置成功"); } - if enable_file_logger_changed { + if proxy_changed { + bili_client.reload_client(); + } + + if file_logger_changed { if enable_file_logger { logger::reload_file_logger() .map_err(|err| CommandError::from("重新加载文件日志失败", err))?; diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 1c41870..84cdfc9 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -30,6 +30,9 @@ pub struct Config { pub dir_fmt: String, pub dir_fmt_for_part: String, pub time_fmt: String, + pub proxy_mode: ProxyMode, + pub proxy_host: String, + pub proxy_port: u16, pub task_concurrency: usize, pub task_download_interval_sec: u64, pub chunk_concurrency: usize, @@ -111,6 +114,9 @@ impl Config { dir_fmt: "{collection_title}/{episode_title}".to_string(), dir_fmt_for_part: DEFAULT_FMT_FOR_PART.to_string(), time_fmt: "%Y-%m-%d_%H-%M-%S".to_string(), + proxy_mode: ProxyMode::NoProxy, + proxy_host: "127.0.0.1".to_string(), + proxy_port: 7890, task_concurrency: 3, task_download_interval_sec: 0, chunk_concurrency: 16, @@ -215,3 +221,11 @@ pub enum PreferAudioQuality { #[serde(rename = "HiRes")] AudioHiRes = 30251, } + +#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Type)] +pub enum ProxyMode { + #[default] + NoProxy, + System, + Custom, +} diff --git a/src/bindings.ts b/src/bindings.ts index 3bc514f..843d85c 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -258,7 +258,7 @@ export type CntInfo = { collect: number; play: number; thumb_up: number; share: export type CntInfoInMedia = { collect: number; play: number; danmaku: number; vt: number; play_switch: number; reply: number; view_text_1: string } export type CodecType = "Unknown" | "Audio" | "AVC" | "HEVC" | "AV1" export type CommandError = { err_title: string; err_message: string } -export type Config = { download_dir: string; enable_file_logger: boolean; sessdata: string; prefer_video_quality: PreferVideoQuality; prefer_codec_type: PreferCodecType; prefer_audio_quality: PreferAudioQuality; download_video: boolean; download_audio: boolean; auto_merge: boolean; download_xml_danmaku: boolean; download_ass_danmaku: boolean; download_json_danmaku: boolean; download_subtitle: boolean; download_cover: boolean; download_nfo: boolean; download_json: boolean; dir_fmt: string; dir_fmt_for_part: string; time_fmt: string; task_concurrency: number; task_download_interval_sec: number; chunk_concurrency: number; chunk_download_interval_sec: number; danmaku_config: CanvasConfig } +export type Config = { download_dir: string; enable_file_logger: boolean; sessdata: string; prefer_video_quality: PreferVideoQuality; prefer_codec_type: PreferCodecType; prefer_audio_quality: PreferAudioQuality; download_video: boolean; download_audio: boolean; auto_merge: boolean; download_xml_danmaku: boolean; download_ass_danmaku: boolean; download_json_danmaku: boolean; download_subtitle: boolean; download_cover: boolean; download_nfo: boolean; download_json: boolean; dir_fmt: string; dir_fmt_for_part: string; time_fmt: string; proxy_mode: ProxyMode; proxy_host: string; proxy_port: number; task_concurrency: number; task_download_interval_sec: number; chunk_concurrency: number; chunk_download_interval_sec: number; danmaku_config: CanvasConfig } export type Consulting = { consulting_flag: boolean; consulting_url: string } export type ContentList = { bold: boolean; content: string; number: string } export type Cooperation = { link: string } @@ -361,6 +361,7 @@ export type PreferAudioQuality = "Best" | "64K" | "132K" | "192K" | "Dolby" | "H export type PreferCodecType = "Unknown" | "AVC" | "HEVC" | "AV1" export type PreferVideoQuality = "Best" | "240P" | "360P" | "480P" | "720P" | "720P60" | "1080P" | "AiRepair" | "1080P+" | "1080P60" | "4K" | "HDR" | "Dolby" | "8K" export type PreviewedPurchaseNote = { long_watch_text: string; pay_text: string; price_format: string; watch_text: string; watching_text: string } +export type ProxyMode = "NoProxy" | "System" | "Custom" export type Publish = { is_finish: number; is_started: number; pub_time: string; pub_time_show: string; unknow_pub_date: number; weekday: number } export type PurchaseFormatNote = { content_list: ContentList[]; link: string; title: string } export type PurchaseNote = { content: string; link: string; title: string }