prfitem.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. use crate::utils::{dirs, help, tmpl};
  2. use anyhow::{bail, Context, Result};
  3. use reqwest::StatusCode;
  4. use serde::{Deserialize, Serialize};
  5. use serde_yaml::Mapping;
  6. use std::fs;
  7. use sysproxy::Sysproxy;
  8. use super::Config;
  9. #[derive(Debug, Clone, Deserialize, Serialize)]
  10. pub struct PrfItem {
  11. pub uid: Option<String>,
  12. /// profile item type
  13. /// enum value: remote | local | script | merge
  14. #[serde(rename = "type")]
  15. pub itype: Option<String>,
  16. /// profile name
  17. pub name: Option<String>,
  18. /// profile file
  19. pub file: Option<String>,
  20. /// profile description
  21. #[serde(skip_serializing_if = "Option::is_none")]
  22. pub desc: Option<String>,
  23. /// source url
  24. #[serde(skip_serializing_if = "Option::is_none")]
  25. pub url: Option<String>,
  26. /// selected information
  27. #[serde(skip_serializing_if = "Option::is_none")]
  28. pub selected: Option<Vec<PrfSelected>>,
  29. /// subscription user info
  30. #[serde(skip_serializing_if = "Option::is_none")]
  31. pub extra: Option<PrfExtra>,
  32. /// updated time
  33. pub updated: Option<usize>,
  34. /// some options of the item
  35. #[serde(skip_serializing_if = "Option::is_none")]
  36. pub option: Option<PrfOption>,
  37. /// the file data
  38. #[serde(skip)]
  39. pub file_data: Option<String>,
  40. }
  41. #[derive(Default, Debug, Clone, Deserialize, Serialize)]
  42. pub struct PrfSelected {
  43. pub name: Option<String>,
  44. pub now: Option<String>,
  45. }
  46. #[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
  47. pub struct PrfExtra {
  48. pub upload: usize,
  49. pub download: usize,
  50. pub total: usize,
  51. pub expire: usize,
  52. }
  53. #[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
  54. pub struct PrfOption {
  55. /// for `remote` profile's http request
  56. /// see issue #13
  57. #[serde(skip_serializing_if = "Option::is_none")]
  58. pub user_agent: Option<String>,
  59. /// for `remote` profile
  60. /// use system proxy
  61. #[serde(skip_serializing_if = "Option::is_none")]
  62. pub with_proxy: Option<bool>,
  63. /// for `remote` profile
  64. /// use self proxy
  65. #[serde(skip_serializing_if = "Option::is_none")]
  66. pub self_proxy: Option<bool>,
  67. #[serde(skip_serializing_if = "Option::is_none")]
  68. pub update_interval: Option<u64>,
  69. }
  70. impl PrfOption {
  71. pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
  72. match (one, other) {
  73. (Some(mut a), Some(b)) => {
  74. a.user_agent = b.user_agent.or(a.user_agent);
  75. a.with_proxy = b.with_proxy.or(a.with_proxy);
  76. a.self_proxy = b.self_proxy.or(a.self_proxy);
  77. a.update_interval = b.update_interval.or(a.update_interval);
  78. Some(a)
  79. }
  80. t @ _ => t.0.or(t.1),
  81. }
  82. }
  83. }
  84. impl Default for PrfItem {
  85. fn default() -> Self {
  86. PrfItem {
  87. uid: None,
  88. itype: None,
  89. name: None,
  90. desc: None,
  91. file: None,
  92. url: None,
  93. selected: None,
  94. extra: None,
  95. updated: None,
  96. option: None,
  97. file_data: None,
  98. }
  99. }
  100. }
  101. impl PrfItem {
  102. /// From partial item
  103. /// must contain `itype`
  104. pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
  105. if item.itype.is_none() {
  106. bail!("type should not be null");
  107. }
  108. match item.itype.unwrap().as_str() {
  109. "remote" => {
  110. if item.url.is_none() {
  111. bail!("url should not be null");
  112. }
  113. let url = item.url.as_ref().unwrap().as_str();
  114. let name = item.name;
  115. let desc = item.desc;
  116. PrfItem::from_url(url, name, desc, item.option).await
  117. }
  118. "local" => {
  119. let name = item.name.unwrap_or("Local File".into());
  120. let desc = item.desc.unwrap_or("".into());
  121. PrfItem::from_local(name, desc, file_data)
  122. }
  123. "merge" => {
  124. let name = item.name.unwrap_or("Merge".into());
  125. let desc = item.desc.unwrap_or("".into());
  126. PrfItem::from_merge(name, desc)
  127. }
  128. "script" => {
  129. let name = item.name.unwrap_or("Script".into());
  130. let desc = item.desc.unwrap_or("".into());
  131. PrfItem::from_script(name, desc)
  132. }
  133. typ @ _ => bail!("invalid profile item type \"{typ}\""),
  134. }
  135. }
  136. /// ## Local type
  137. /// create a new item from name/desc
  138. pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
  139. let uid = help::get_uid("l");
  140. let file = format!("{uid}.yaml");
  141. Ok(PrfItem {
  142. uid: Some(uid),
  143. itype: Some("local".into()),
  144. name: Some(name),
  145. desc: Some(desc),
  146. file: Some(file),
  147. url: None,
  148. selected: None,
  149. extra: None,
  150. option: None,
  151. updated: Some(chrono::Local::now().timestamp() as usize),
  152. file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
  153. })
  154. }
  155. /// ## Remote type
  156. /// create a new item from url
  157. pub async fn from_url(
  158. url: &str,
  159. name: Option<String>,
  160. desc: Option<String>,
  161. option: Option<PrfOption>,
  162. ) -> Result<PrfItem> {
  163. let opt_ref = option.as_ref();
  164. let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
  165. let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
  166. let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone());
  167. let mut builder = reqwest::ClientBuilder::new().no_proxy();
  168. // 使用软件自己的代理
  169. if self_proxy {
  170. let port = Config::clash().data().get_mixed_port();
  171. let proxy_scheme = format!("http://127.0.0.1:{port}");
  172. if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
  173. builder = builder.proxy(proxy);
  174. }
  175. if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
  176. builder = builder.proxy(proxy);
  177. }
  178. if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
  179. builder = builder.proxy(proxy);
  180. }
  181. }
  182. // 使用系统代理
  183. else if with_proxy {
  184. match Sysproxy::get_system_proxy() {
  185. Ok(p @ Sysproxy { enable: true, .. }) => {
  186. let proxy_scheme = format!("http://{}:{}", p.host, p.port);
  187. if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
  188. builder = builder.proxy(proxy);
  189. }
  190. if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
  191. builder = builder.proxy(proxy);
  192. }
  193. if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
  194. builder = builder.proxy(proxy);
  195. }
  196. }
  197. _ => {}
  198. };
  199. }
  200. let version = unsafe { dirs::APP_VERSION };
  201. let version = format!("clash-verge/{version}");
  202. builder = builder.user_agent(user_agent.unwrap_or(version));
  203. let resp = builder.build()?.get(url).send().await?;
  204. let status_code = resp.status();
  205. if !StatusCode::is_success(&status_code) {
  206. bail!("failed to fetch remote profile with status {status_code}")
  207. }
  208. let header = resp.headers();
  209. // parse the Subscription UserInfo
  210. let extra = match header.get("Subscription-Userinfo") {
  211. Some(value) => {
  212. let sub_info = value.to_str().unwrap_or("");
  213. Some(PrfExtra {
  214. upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
  215. download: help::parse_str(sub_info, "download=").unwrap_or(0),
  216. total: help::parse_str(sub_info, "total=").unwrap_or(0),
  217. expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
  218. })
  219. }
  220. None => None,
  221. };
  222. // parse the Content-Disposition
  223. let filename = match header.get("Content-Disposition") {
  224. Some(value) => {
  225. let filename = value.to_str().unwrap_or("");
  226. help::parse_str::<String>(filename, "filename=")
  227. }
  228. None => None,
  229. };
  230. // parse the profile-update-interval
  231. let option = match header.get("profile-update-interval") {
  232. Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
  233. Ok(val) => Some(PrfOption {
  234. update_interval: Some(val * 60), // hour -> min
  235. ..PrfOption::default()
  236. }),
  237. Err(_) => None,
  238. },
  239. None => None,
  240. };
  241. let uid = help::get_uid("r");
  242. let file = format!("{uid}.yaml");
  243. let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
  244. let data = resp.text_with_charset("utf-8").await?;
  245. // process the charset "UTF-8 with BOM"
  246. let data = data.trim_start_matches('\u{feff}');
  247. // check the data whether the valid yaml format
  248. let yaml = serde_yaml::from_str::<Mapping>(data)
  249. .context("the remote profile data is invalid yaml")?;
  250. if !yaml.contains_key("proxies") && !yaml.contains_key("proxy-providers") {
  251. bail!("profile does not contain `proxies` or `proxy-providers`");
  252. }
  253. Ok(PrfItem {
  254. uid: Some(uid),
  255. itype: Some("remote".into()),
  256. name: Some(name),
  257. desc,
  258. file: Some(file),
  259. url: Some(url.into()),
  260. selected: None,
  261. extra,
  262. option,
  263. updated: Some(chrono::Local::now().timestamp() as usize),
  264. file_data: Some(data.into()),
  265. })
  266. }
  267. /// ## Merge type (enhance)
  268. /// create the enhanced item by using `merge` rule
  269. pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
  270. let uid = help::get_uid("m");
  271. let file = format!("{uid}.yaml");
  272. Ok(PrfItem {
  273. uid: Some(uid),
  274. itype: Some("merge".into()),
  275. name: Some(name),
  276. desc: Some(desc),
  277. file: Some(file),
  278. url: None,
  279. selected: None,
  280. extra: None,
  281. option: None,
  282. updated: Some(chrono::Local::now().timestamp() as usize),
  283. file_data: Some(tmpl::ITEM_MERGE.into()),
  284. })
  285. }
  286. /// ## Script type (enhance)
  287. /// create the enhanced item by using javascript quick.js
  288. pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
  289. let uid = help::get_uid("s");
  290. let file = format!("{uid}.js"); // js ext
  291. Ok(PrfItem {
  292. uid: Some(uid),
  293. itype: Some("script".into()),
  294. name: Some(name),
  295. desc: Some(desc),
  296. file: Some(file),
  297. url: None,
  298. selected: None,
  299. extra: None,
  300. option: None,
  301. updated: Some(chrono::Local::now().timestamp() as usize),
  302. file_data: Some(tmpl::ITEM_SCRIPT.into()),
  303. })
  304. }
  305. /// get the file data
  306. pub fn read_file(&self) -> Result<String> {
  307. if self.file.is_none() {
  308. bail!("could not find the file");
  309. }
  310. let file = self.file.clone().unwrap();
  311. let path = dirs::app_profiles_dir()?.join(file);
  312. fs::read_to_string(path).context("failed to read the file")
  313. }
  314. /// save the file data
  315. pub fn save_file(&self, data: String) -> Result<()> {
  316. if self.file.is_none() {
  317. bail!("could not find the file");
  318. }
  319. let file = self.file.clone().unwrap();
  320. let path = dirs::app_profiles_dir()?.join(file);
  321. fs::write(path, data.as_bytes()).context("failed to save the file")
  322. }
  323. }