From 2b783dc83e44f7033a862934cc145de2c6f453c8 Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Sat, 12 Apr 2025 02:07:18 +0800 Subject: [PATCH 1/4] feat: better showing errors on web page --- tomoon-web/src/App.jsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tomoon-web/src/App.jsx b/tomoon-web/src/App.jsx index 764339b..db5e919 100644 --- a/tomoon-web/src/App.jsx +++ b/tomoon-web/src/App.jsx @@ -6,7 +6,7 @@ import axios from 'axios' function App() { const [url, setUrl] = useState(""); - const [isSubscribed, setIsSubscribed] = useState(true); + const [isSubscribed, setIsSubscribed] = useState(false); const handleUrlChange = (event) => { setUrl(event.target.value); @@ -75,18 +75,25 @@ const on_download_btn_click = (url, isSubscribed) => { confirmButtonColor: '#5A6242', background: '#DEE7BF' }); - } - }).catch(error => { - if (error.response) { + } else { Swal.fire({ icon: 'error', iconColor: '#5E5F55', - title: '失败', - text: error.response.data?.error?.message, + title: '后端失败', + html: `错误状态: ${response.status}
错误信息: ${response.data?.error?.message}`, confirmButtonColor: '#5A6242', background: '#DEE7BF' }); } + }).catch(error => { + Swal.fire({ + icon: 'error', + iconColor: '#5E5F55', + title: '请求失败', + html: `错误类型: ${error.name}
错误信息: ${error?.message}`, + confirmButtonColor: '#5A6242', + background: '#DEE7BF' + }); }); } From 9e8908394bef023cb098a3a2fa898b57db816d70 Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Sat, 12 Apr 2025 02:10:21 +0800 Subject: [PATCH 2/4] fix: fix filename detection in Content-Disposition --- backend/Cargo.toml | 3 +- backend/src/external_web.rs | 74 ++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 340aed5..acfa5e5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -24,4 +24,5 @@ env_logger = "0.10.0" local-ip-address = "0.5.1" actix-cors = "0.6.4" tokio = { version = "1.24.1", features = ["macros", "process"]} -urlencoding = "2.1.3" \ No newline at end of file +urlencoding = "2.1.3" +content_disposition = "0.4.0" diff --git a/backend/src/external_web.rs b/backend/src/external_web.rs index ce737cd..2a6e50a 100644 --- a/backend/src/external_web.rs +++ b/backend/src/external_web.rs @@ -2,12 +2,8 @@ use actix_web::{body::BoxBody, web, HttpResponse, Result}; use local_ip_address::local_ip; use rand::{distributions::Alphanumeric, Rng}; use serde::{Deserialize, Serialize}; -use std::time::Duration; use std::{collections::HashMap, fs, path::PathBuf, sync::Mutex}; -use tokio::net::TcpStream; -use tokio::process::Command; -use tokio::sync::mpsc; -use tokio::time::sleep; +use content_disposition; use crate::{ control::{ClashError, ClashErrorKind, EnhancedMode}, @@ -693,7 +689,7 @@ pub async fn download_sub( .with_header( "User-Agent", format!( - "ToMoon/{} mihomo/1.18.3 Clash/v1.18.0", + "ToMoon/{} mihomo/1.19.4 clash-verge/2.2.3 Clash/v1.18.0", env!("CARGO_PKG_VERSION") ), ) @@ -710,41 +706,41 @@ pub async fn download_sub( ErrorKind: ClashErrorKind::ConfigFormatError, })); } - let filename = x.headers.get("content-disposition"); - let filename = match filename { - Some(x) => { - let filename = x.split("filename=").collect::>()[1] - .split(";") - .collect::>()[0] - .replace("\"", ""); - filename.to_string() - } - None => { - let slash_split = *url.split("/").collect::>().last().unwrap(); - slash_split - .split("?") - .collect::>() - .first() - .unwrap() - .to_string() - } - }; - let filename = if filename.is_empty() { - log::warn!("The downloaded subscription does not have a file name."); - gen_random_name() - } else { - filename + let filename = x.headers.get("content-disposition") + .and_then(|header| { + // 尝试从 content-disposition 头部获取文件名 + // header.split("filename=").nth(1) + // .and_then(|s| s.split(';').next()) + // .map(|s| s.trim_matches('"')) + content_disposition::parse_content_disposition(header).filename_full() + }) + .filter(|s| !s.is_empty()) + .or_else(|| { + // 如果 content-disposition 头部中没有文件名,则尝试从 URL 中获取 + log::info!("Failed to get content-disposition, using url instead."); + url.rsplit('/').next() + .and_then(|last_part| last_part.split('?').next()).map(|s| s.to_string()) + }) + .unwrap_or_else(|| { + // 如果 URL 中没有文件名,则生成一个随机文件名 + log::warn!("The downloaded subscription does not have a file name."); + gen_random_name() + }); + let filename = match filename.to_ascii_lowercase() { + ref lower if lower.ends_with(".yaml") || lower.ends_with(".yml") => filename, + _ => filename + ".yaml", }; - let filename = if filename.to_lowercase().ends_with(".yaml") - || filename.to_lowercase().ends_with(".yml") - { - filename - } else { - filename + ".yaml" - }; - let mut path = path.join(filename); + let path = path.join(filename); if fs::metadata(&path).is_ok() { - path = path.parent().unwrap().join(gen_random_name() + ".yaml"); + // 如果文件名重复,则尝试删除原有 + if let Err(e) = fs::remove_file(&path) { + log::error!("Failed while removing old sub."); + log::error!("Error Message:{}", e); + return Err(actix_web::Error::from(ClashError { + Message: e.to_string(), + ErrorKind: ClashErrorKind::InnerError, + })); + } } //保存订阅 if let Some(parent) = path.parent() { From cdaf11c484d39f284523270ffae9886d3a510091 Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Sat, 12 Apr 2025 02:42:38 +0800 Subject: [PATCH 3/4] feat: show link text correspond to qr code --- src/pages/Subscriptions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Subscriptions.tsx b/src/pages/Subscriptions.tsx index debf656..aeb6655 100644 --- a/src/pages/Subscriptions.tsx +++ b/src/pages/Subscriptions.tsx @@ -127,6 +127,7 @@ export const Subscriptions: FC = ({ Subscriptions }) => {
+

{QRPageUrl}

Date: Sat, 12 Apr 2025 03:01:00 +0800 Subject: [PATCH 4/4] chore: better file naming logic --- backend/src/external_web.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/backend/src/external_web.rs b/backend/src/external_web.rs index 2a6e50a..69069b0 100644 --- a/backend/src/external_web.rs +++ b/backend/src/external_web.rs @@ -730,20 +730,25 @@ pub async fn download_sub( ref lower if lower.ends_with(".yaml") || lower.ends_with(".yml") => filename, _ => filename + ".yaml", }; - let path = path.join(filename); - if fs::metadata(&path).is_ok() { - // 如果文件名重复,则尝试删除原有 - if let Err(e) = fs::remove_file(&path) { - log::error!("Failed while removing old sub."); - log::error!("Error Message:{}", e); + let mut filepath = path.join(filename.clone()); + if filepath.exists() { + for i in 1..=128 { + let new_filename = format!("{}_{}.yaml", filename.trim_end_matches(".yaml"), i); + filepath = path.join(new_filename); + if !filepath.exists() { + break; + } + } + if filepath.exists() { + log::error!("Failed while saving sub, cannot find a new name."); return Err(actix_web::Error::from(ClashError { - Message: e.to_string(), + Message: "The file already exists.".to_string(), ErrorKind: ClashErrorKind::InnerError, })); } } //保存订阅 - if let Some(parent) = path.parent() { + if let Some(parent) = filepath.parent() { if let Err(e) = std::fs::create_dir_all(parent) { log::error!("Failed while creating sub dir."); log::error!("Error Message:{}", e); @@ -753,7 +758,7 @@ pub async fn download_sub( })); } } - let path = path.to_str().unwrap(); + let path = filepath.to_str().unwrap(); log::info!("Writing to path: {}", path); if let Err(e) = fs::write(path, response) { log::error!("Failed while saving sub.");