fetch.rs 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. use crate::config::{ProfileExtra, ProfileResponse};
  2. use std::{
  3. str::FromStr,
  4. time::{SystemTime, UNIX_EPOCH},
  5. };
  6. /// parse the string
  7. fn parse_string<T: FromStr>(target: &str, key: &str) -> Option<T> {
  8. match target.find(key) {
  9. Some(idx) => {
  10. let idx = idx + key.len();
  11. let value = &target[idx..];
  12. match match value.split(';').nth(0) {
  13. Some(value) => value.trim().parse(),
  14. None => value.trim().parse(),
  15. } {
  16. Ok(r) => Some(r),
  17. Err(_) => None,
  18. }
  19. }
  20. None => None,
  21. }
  22. }
  23. /// fetch and parse the profile
  24. pub async fn fetch_profile(url: &str) -> Option<ProfileResponse> {
  25. let resp = match reqwest::get(url).await {
  26. Ok(res) => res,
  27. Err(_) => return None,
  28. };
  29. let header = resp.headers();
  30. // parse the Subscription Userinfo
  31. let extra = {
  32. let sub_info = match header.get("Subscription-Userinfo") {
  33. Some(value) => value.to_str().unwrap_or(""),
  34. None => "",
  35. };
  36. ProfileExtra {
  37. upload: parse_string(sub_info, "upload=").unwrap_or(0),
  38. download: parse_string(sub_info, "download=").unwrap_or(0),
  39. total: parse_string(sub_info, "total=").unwrap_or(0),
  40. expire: parse_string(sub_info, "expire=").unwrap_or(0),
  41. }
  42. };
  43. // parse the `name` and `file`
  44. let (name, file) = {
  45. let now = SystemTime::now()
  46. .duration_since(UNIX_EPOCH)
  47. .unwrap()
  48. .as_secs();
  49. let file = format!("{}.yaml", now);
  50. let name = header.get("Content-Disposition").unwrap().to_str().unwrap();
  51. let name = parse_string::<String>(name, "filename=");
  52. match name {
  53. Some(f) => (f, file),
  54. None => (file.clone(), file),
  55. }
  56. };
  57. // get the data
  58. let data = match resp.text_with_charset("utf-8").await {
  59. Ok(d) => d,
  60. Err(_) => return None,
  61. };
  62. Some(ProfileResponse {
  63. file,
  64. name,
  65. data,
  66. extra,
  67. })
  68. }
  69. #[test]
  70. fn test_parse_value() {
  71. let test_1 = "upload=111; download=2222; total=3333; expire=444";
  72. let test_2 = "attachment; filename=Clash.yaml";
  73. assert_eq!(parse_string::<usize>(test_1, "upload=").unwrap(), 111);
  74. assert_eq!(parse_string::<usize>(test_1, "download=").unwrap(), 2222);
  75. assert_eq!(parse_string::<usize>(test_1, "total=").unwrap(), 3333);
  76. assert_eq!(parse_string::<usize>(test_1, "expire=").unwrap(), 444);
  77. assert_eq!(
  78. parse_string::<String>(test_2, "filename=").unwrap(),
  79. format!("Clash.yaml")
  80. );
  81. assert_eq!(parse_string::<usize>(test_1, "aaa="), None);
  82. assert_eq!(parse_string::<usize>(test_1, "upload1="), None);
  83. assert_eq!(parse_string::<usize>(test_1, "expire1="), None);
  84. assert_eq!(parse_string::<usize>(test_2, "attachment="), None);
  85. }