diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index ab142343a1..226b5579ed 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,5 +1,5 @@ use crate::{ - core::{ClashInfo, PrfItem, Profiles, VergeConfig}, + core::{ClashInfo, PrfItem, PrfOption, Profiles, VergeConfig}, states::{ClashState, ProfilesState, VergeState}, utils::{dirs, sysopt::SysProxyConfig}, }; @@ -28,10 +28,10 @@ pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), Str #[tauri::command] pub async fn import_profile( url: String, - with_proxy: bool, + option: Option, profiles_state: State<'_, ProfilesState>, ) -> Result<(), String> { - let item = wrap_err!(PrfItem::from_url(&url, None, None, with_proxy).await)?; + let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; let mut profiles = profiles_state.0.lock().unwrap(); wrap_err!(profiles.append_item(item)) @@ -55,7 +55,7 @@ pub async fn create_profile( #[tauri::command] pub async fn update_profile( index: String, - with_proxy: bool, + option: Option, clash_state: State<'_, ClashState>, profiles_state: State<'_, ProfilesState>, ) -> Result<(), String> { @@ -71,7 +71,7 @@ pub async fn update_profile( item.url.clone().unwrap() }; - let item = wrap_err!(PrfItem::from_url(&url, None, None, with_proxy).await)?; + let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; let mut profiles = profiles_state.0.lock().unwrap(); wrap_err!(profiles.update_item(index.clone(), item))?; diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs index d82d48d87f..6b7bdd51da 100644 --- a/src-tauri/src/core/profiles.rs +++ b/src-tauri/src/core/profiles.rs @@ -31,13 +31,17 @@ pub struct PrfItem { #[serde(skip_serializing_if = "Option::is_none")] pub selected: Option>, - /// user info + /// subscription user info #[serde(skip_serializing_if = "Option::is_none")] pub extra: Option, /// updated time pub updated: Option, + /// some options of the item + #[serde(skip_serializing_if = "Option::is_none")] + pub option: Option, + /// the file data #[serde(skip)] pub file_data: Option, @@ -57,6 +61,18 @@ pub struct PrfExtra { pub expire: usize, } +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct PrfOption { + /// for `remote` profile's http request + /// see issue #13 + #[serde(skip_serializing_if = "Option::is_none")] + pub user_agent: Option, + + /// for `remote` profile + #[serde(skip_serializing_if = "Option::is_none")] + pub with_proxy: Option, +} + impl Default for PrfItem { fn default() -> Self { PrfItem { @@ -69,6 +85,7 @@ impl Default for PrfItem { selected: None, extra: None, updated: None, + option: None, file_data: None, } } @@ -90,7 +107,7 @@ impl PrfItem { let url = item.url.as_ref().unwrap().as_str(); let name = item.name; let desc = item.desc; - PrfItem::from_url(url, name, desc, false).await + PrfItem::from_url(url, name, desc, item.option).await } "local" => { let name = item.name.unwrap_or("Local File".into()); @@ -126,6 +143,7 @@ impl PrfItem { url: None, selected: None, extra: None, + option: None, updated: Some(help::get_now()), file_data: Some(tmpl::ITEM_LOCAL.into()), }) @@ -137,13 +155,25 @@ impl PrfItem { url: &str, name: Option, desc: Option, - with_proxy: bool, + option: Option, ) -> Result { + let with_proxy = match option.as_ref() { + Some(opt) => opt.with_proxy.unwrap_or(false), + None => false, + }; + let user_agent = match option.as_ref() { + Some(opt) => opt.user_agent.clone(), + None => None, + }; + let mut builder = reqwest::ClientBuilder::new(); if !with_proxy { builder = builder.no_proxy(); } + if let Some(user_agent) = user_agent { + builder = builder.user_agent(user_agent); + } let resp = builder.build()?.get(url).send().await?; let header = resp.headers(); @@ -177,6 +207,7 @@ impl PrfItem { url: Some(url.into()), selected: None, extra, + option, updated: Some(help::get_now()), file_data: Some(data), }) @@ -197,6 +228,7 @@ impl PrfItem { url: None, selected: None, extra: None, + option: None, updated: Some(help::get_now()), file_data: Some(tmpl::ITEM_MERGE.into()), }) @@ -217,6 +249,7 @@ impl PrfItem { url: None, selected: None, extra: None, + option: None, updated: Some(help::get_now()), file_data: Some(tmpl::ITEM_SCRIPT.into()), }) @@ -382,6 +415,9 @@ impl Profiles { patch!(each, item, extra); patch!(each, item, updated); + // can be removed + each.option = item.option; + self.items = Some(items); return self.save_file(); } diff --git a/src/components/profile/profile-edit.tsx b/src/components/profile/profile-edit.tsx index 72693d8b87..a739ebbf86 100644 --- a/src/components/profile/profile-edit.tsx +++ b/src/components/profile/profile-edit.tsx @@ -1,5 +1,5 @@ import { mutate } from "swr"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useLockFn, useSetState } from "ahooks"; import { Button, @@ -7,8 +7,10 @@ import { DialogActions, DialogContent, DialogTitle, + IconButton, TextField, } from "@mui/material"; +import { Settings } from "@mui/icons-material"; import { CmdType } from "../../services/types"; import { patchProfile } from "../../services/cmds"; import Notice from "../base/base-notice"; @@ -20,13 +22,18 @@ interface Props { } // edit the profile item +// remote / local file / merge / script const ProfileEdit = (props: Props) => { const { open, itemData, onClose } = props; const [form, setForm] = useSetState({ ...itemData }); + const [option, setOption] = useSetState(itemData.option ?? {}); + const [showOpt, setShowOpt] = useState(!!itemData.option); useEffect(() => { if (itemData) { setForm({ ...itemData }); + setOption(itemData.option ?? {}); + setShowOpt(!!itemData.option?.user_agent); } }, [itemData]); @@ -34,12 +41,14 @@ const ProfileEdit = (props: Props) => { try { const { uid } = itemData; const { name, desc, url } = form; + const option_ = showOpt ? option : undefined; if (itemData.type === "remote" && !url) { throw new Error("Remote URL should not be null"); } - await patchProfile(uid, { uid, name, desc, url }); + await patchProfile(uid, { uid, name, desc, url, option: option_ }); + setShowOpt(false); mutate("getProfiles"); onClose(); } catch (err: any) { @@ -94,9 +103,28 @@ const ProfileEdit = (props: Props) => { onChange={(e) => setForm({ url: e.target.value })} /> )} + + {showOpt && ( + setOption({ user_agent: e.target.value })} + /> + )} - + + {form.type === "remote" && ( + setShowOpt((o) => !o)} + > + + + )} +