clash_api.rs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. use crate::config::Config;
  2. use anyhow::{bail, Result};
  3. use reqwest::header::HeaderMap;
  4. use serde_yaml::Mapping;
  5. use std::collections::HashMap;
  6. /// PUT /configs
  7. /// path 是绝对路径
  8. pub async fn put_configs(path: &str) -> Result<()> {
  9. let (url, headers) = clash_client_info()?;
  10. let url = format!("{url}/configs");
  11. let mut data = HashMap::new();
  12. data.insert("path", path);
  13. let client = reqwest::ClientBuilder::new().no_proxy().build()?;
  14. let builder = client.put(&url).headers(headers).json(&data);
  15. let response = builder.send().await?;
  16. match response.status().as_u16() {
  17. 204 => Ok(()),
  18. status @ _ => {
  19. bail!("failed to put configs with status \"{status}\"")
  20. }
  21. }
  22. }
  23. /// PATCH /configs
  24. pub async fn patch_configs(config: &Mapping) -> Result<()> {
  25. let (url, headers) = clash_client_info()?;
  26. let url = format!("{url}/configs");
  27. let client = reqwest::ClientBuilder::new().no_proxy().build()?;
  28. let builder = client.patch(&url).headers(headers.clone()).json(config);
  29. builder.send().await?;
  30. Ok(())
  31. }
  32. /// 根据clash info获取clash服务地址和请求头
  33. fn clash_client_info() -> Result<(String, HeaderMap)> {
  34. let client = { Config::clash().data().get_client_info() };
  35. let server = format!("http://{}", client.server);
  36. let mut headers = HeaderMap::new();
  37. headers.insert("Content-Type", "application/json".parse()?);
  38. if let Some(secret) = client.secret {
  39. let secret = format!("Bearer {}", secret).parse()?;
  40. headers.insert("Authorization", secret);
  41. }
  42. Ok((server, headers))
  43. }
  44. /// 缩短clash的日志
  45. pub fn parse_log(log: String) -> String {
  46. if log.starts_with("time=") && log.len() > 33 {
  47. return (&log[33..]).to_owned();
  48. }
  49. if log.len() > 9 {
  50. return (&log[9..]).to_owned();
  51. }
  52. return log;
  53. }
  54. /// 缩短clash -t的错误输出
  55. /// 仅适配 clash p核 8-26、clash meta 1.13.1
  56. pub fn parse_check_output(log: String) -> String {
  57. let t = log.find("time=");
  58. let m = log.find("msg=");
  59. let mr = log.rfind('"');
  60. if let (Some(_), Some(m), Some(mr)) = (t, m, mr) {
  61. let e = match log.find("level=error msg=") {
  62. Some(e) => e + 17,
  63. None => m + 5,
  64. };
  65. if mr > m {
  66. return (&log[e..mr]).to_owned();
  67. }
  68. }
  69. let l = log.find("error=");
  70. let r = log.find("path=").or(Some(log.len()));
  71. if let (Some(l), Some(r)) = (l, r) {
  72. return (&log[(l + 6)..(r - 1)]).to_owned();
  73. }
  74. log
  75. }
  76. #[test]
  77. fn test_parse_check_output() {
  78. let str1 = r#"xxxx\n time="2022-11-18T20:42:58+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'""#;
  79. let str2 = r#"20:43:49 ERR [Config] configuration file test failed error=proxy 0: unsupport proxy type: hysteria path=xxx"#;
  80. let str3 = r#"
  81. "time="2022-11-18T21:38:01+08:00" level=info msg="Start initial configuration in progress"
  82. time="2022-11-18T21:38:01+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'"
  83. configuration file xxx\n
  84. "#;
  85. let res1 = parse_check_output(str1.into());
  86. let res2 = parse_check_output(str2.into());
  87. let res3 = parse_check_output(str3.into());
  88. println!("res1: {res1}");
  89. println!("res2: {res2}");
  90. println!("res3: {res3}");
  91. assert_eq!(res1, res3);
  92. }