clash.rs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. extern crate log;
  2. use crate::{
  3. events::emit::{clash_start, ClashInfoPayload},
  4. utils::{
  5. app_home_dir,
  6. config::{read_clash_controller, read_profiles, read_yaml, save_yaml},
  7. },
  8. };
  9. use reqwest::header::HeaderMap;
  10. use serde_yaml::{Mapping, Value};
  11. use std::{collections::HashMap, env::temp_dir};
  12. use tauri::{
  13. api::process::{Command, CommandEvent},
  14. AppHandle,
  15. };
  16. /// Run the clash bin
  17. pub fn run_clash_bin(app_handle: &AppHandle) -> ClashInfoPayload {
  18. let app_dir = app_home_dir();
  19. let app_dir = app_dir.as_os_str().to_str().unwrap();
  20. let mut payload = ClashInfoPayload {
  21. status: "success".to_string(),
  22. controller: None,
  23. message: None,
  24. };
  25. let result = match Command::new_sidecar("clash") {
  26. Ok(cmd) => match cmd.args(["-d", app_dir]).spawn() {
  27. Ok(res) => Ok(res),
  28. Err(err) => Err(err.to_string()),
  29. },
  30. Err(err) => Err(err.to_string()),
  31. };
  32. match result {
  33. Ok((mut rx, _)) => {
  34. log::info!("Successfully execute clash sidecar");
  35. payload.controller = Some(read_clash_controller());
  36. tauri::async_runtime::spawn(async move {
  37. while let Some(event) = rx.recv().await {
  38. match event {
  39. CommandEvent::Stdout(line) => log::info!("{}", line),
  40. CommandEvent::Stderr(err) => log::error!("{}", err),
  41. _ => {}
  42. }
  43. }
  44. });
  45. }
  46. Err(err) => {
  47. log::error!("Failed to execute clash sidecar for \"{}\"", err);
  48. payload.status = "error".to_string();
  49. payload.message = Some(err.to_string());
  50. }
  51. };
  52. clash_start(app_handle, &payload);
  53. payload
  54. }
  55. /// Update the clash profile firstly
  56. pub async fn put_clash_profile(payload: &ClashInfoPayload) -> Result<(), String> {
  57. let profile = {
  58. let profiles = read_profiles();
  59. let current = profiles.current.unwrap_or(0) as usize;
  60. match profiles.items {
  61. Some(items) => {
  62. if items.len() == 0 {
  63. return Err("can not read profiles".to_string());
  64. }
  65. let idx = if current < items.len() { current } else { 0 };
  66. items[idx].clone()
  67. }
  68. None => {
  69. return Err("can not read profiles".to_string());
  70. }
  71. }
  72. };
  73. // temp profile's path
  74. let temp_path = temp_dir().join("clash-verge-runtime.yaml");
  75. // generate temp profile
  76. {
  77. let file_name = match profile.file {
  78. Some(file_name) => file_name.clone(),
  79. None => {
  80. return Err(format!("profile item should have `file` field"));
  81. }
  82. };
  83. let file_path = app_home_dir().join("profiles").join(file_name);
  84. if !file_path.exists() {
  85. return Err(format!("profile `{:?}` not exists", file_path));
  86. }
  87. // Only the following fields are allowed:
  88. // proxies/proxy-providers/proxy-groups/rule-providers/rules
  89. let config = read_yaml::<Mapping>(file_path.clone());
  90. let mut new_config = Mapping::new();
  91. vec![
  92. "proxies",
  93. "proxy-providers",
  94. "proxy-groups",
  95. "rule-providers",
  96. "rules",
  97. ]
  98. .iter()
  99. .map(|item| Value::String(item.to_string()))
  100. .for_each(|key| {
  101. if config.contains_key(&key) {
  102. let value = config[&key].clone();
  103. new_config.insert(key, value);
  104. }
  105. });
  106. match save_yaml(
  107. temp_path.clone(),
  108. &new_config,
  109. Some("# Clash Verge Temp File"),
  110. ) {
  111. Err(err) => return Err(err),
  112. _ => {}
  113. };
  114. }
  115. let ctrl = payload.controller.clone().unwrap();
  116. let server = format!("http://{}/configs", ctrl.server.unwrap());
  117. let mut headers = HeaderMap::new();
  118. headers.insert("Content-Type", "application/json".parse().unwrap());
  119. if let Some(secret) = ctrl.secret {
  120. headers.insert(
  121. "Authorization",
  122. format!("Bearer {}", secret).parse().unwrap(),
  123. );
  124. }
  125. let mut data = HashMap::new();
  126. data.insert("path", temp_path.as_os_str().to_str().unwrap());
  127. let client = reqwest::Client::new();
  128. match client.put(server).headers(headers).json(&data).send().await {
  129. Ok(_) => Ok(()),
  130. Err(err) => Err(format!("request failed `{}`", err.to_string())),
  131. }
  132. }