added support for special headers and displaying their information on the main page

This commit is contained in:
coolcoala
2025-07-15 03:07:42 +03:00
parent c090ae3b11
commit 6f1d9ba1b4
6 changed files with 122 additions and 8 deletions

1
src-tauri/Cargo.lock generated
View File

@@ -1125,6 +1125,7 @@ dependencies = [
"tokio", "tokio",
"tokio-tungstenite 0.27.0", "tokio-tungstenite 0.27.0",
"tungstenite 0.27.0", "tungstenite 0.27.0",
"url",
"users", "users",
"warp", "warp",
"winapi", "winapi",

View File

@@ -16,6 +16,7 @@ identifier = "io.github.clash-verge-rev.clash-verge-rev"
tauri-build = { version = "2.3.0", features = [] } tauri-build = { version = "2.3.0", features = [] }
[dependencies] [dependencies]
url = "2.5.4"
os_info = "3.0" os_info = "3.0"
machine-uid = "0.2" machine-uid = "0.2"
warp = "0.3.7" warp = "0.3.7"

View File

@@ -8,6 +8,8 @@ use reqwest::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::{fs, time::Duration}; use std::{fs, time::Duration};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use url::Url;
use super::Config; use super::Config;
@@ -53,6 +55,14 @@ pub struct PrfItem {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub home: Option<String>, pub home: Option<String>,
/// profile support url
#[serde(skip_serializing_if = "Option::is_none")]
pub support_url: Option<String>,
/// profile announce
#[serde(skip_serializing_if = "Option::is_none")]
pub announce: Option<String>,
/// the file data /// the file data
#[serde(skip)] #[serde(skip)]
pub file_data: Option<String>, pub file_data: Option<String>,
@@ -234,6 +244,8 @@ impl PrfItem {
..PrfOption::default() ..PrfOption::default()
}), }),
home: None, home: None,
support_url: None,
announce: None,
updated: Some(chrono::Local::now().timestamp() as usize), updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())), file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
}) })
@@ -297,6 +309,21 @@ impl PrfItem {
let header = resp.headers(); let header = resp.headers();
let mut final_url = url.to_string();
if let Some(new_domain_value) = header.get("new-sub-domain") {
if let Ok(new_domain) = new_domain_value.to_str() {
if !new_domain.is_empty() {
if let Ok(mut parsed_url) = Url::parse(url) {
if parsed_url.set_host(Some(new_domain)).is_ok() {
final_url = parsed_url.to_string();
log::info!(target: "app", "URL host updated to -> {}", final_url);
}
}
}
}
}
// parse the Subscription UserInfo // parse the Subscription UserInfo
let extra = match header.get("Subscription-Userinfo") { let extra = match header.get("Subscription-Userinfo") {
Some(value) => { Some(value) => {
@@ -354,9 +381,45 @@ impl PrfItem {
None => None, None => None,
}; };
let support_url = match header.get("support-url") {
Some(value) => {
let str_value = value.to_str().unwrap_or("");
Some(str_value.to_string())
}
None => None,
};
let announce = match header.get("announce") {
Some(value) => {
let str_value = value.to_str().unwrap_or("");
if let Some(b64_data) = str_value.strip_prefix("base64:") {
STANDARD.decode(b64_data)
.ok()
.and_then(|bytes| String::from_utf8(bytes).ok())
} else {
Some(str_value.to_string())
}
}
None => None,
};
let profile_title = match header.get("profile-title") {
Some(value) => {
let str_value = value.to_str().unwrap_or("");
if let Some(b64_data) = str_value.strip_prefix("base64:") {
STANDARD.decode(b64_data)
.ok()
.and_then(|bytes| String::from_utf8(bytes).ok())
} else {
Some(str_value.to_string())
}
}
None => None,
};
let uid = help::get_uid("R"); let uid = help::get_uid("R");
let file = format!("{uid}.yaml"); let file = format!("{uid}.yaml");
let name = name.unwrap_or(filename.unwrap_or("Remote File".into())); let name = name.or(profile_title).unwrap_or(filename.unwrap_or("Remote File".into()));
let data = resp.text_with_charset("utf-8").await?; let data = resp.text_with_charset("utf-8").await?;
// process the charset "UTF-8 with BOM" // process the charset "UTF-8 with BOM"
@@ -404,7 +467,7 @@ impl PrfItem {
name: Some(name), name: Some(name),
desc, desc,
file: Some(file), file: Some(file),
url: Some(url.into()), url: Some(final_url),
selected: None, selected: None,
extra, extra,
option: Some(PrfOption { option: Some(PrfOption {
@@ -417,6 +480,8 @@ impl PrfItem {
..PrfOption::default() ..PrfOption::default()
}), }),
home, home,
support_url,
announce,
updated: Some(chrono::Local::now().timestamp() as usize), updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(data.into()), file_data: Some(data.into()),
}) })
@@ -444,6 +509,8 @@ impl PrfItem {
extra: None, extra: None,
option: None, option: None,
home: None, home: None,
support_url: None,
announce: None,
updated: Some(chrono::Local::now().timestamp() as usize), updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(template), file_data: Some(template),
}) })
@@ -466,6 +533,8 @@ impl PrfItem {
file: Some(file), file: Some(file),
url: None, url: None,
home: None, home: None,
support_url: None,
announce: None,
selected: None, selected: None,
extra: None, extra: None,
option: None, option: None,
@@ -487,6 +556,8 @@ impl PrfItem {
file: Some(file), file: Some(file),
url: None, url: None,
home: None, home: None,
support_url: None,
announce: None,
selected: None, selected: None,
extra: None, extra: None,
option: None, option: None,
@@ -508,6 +579,8 @@ impl PrfItem {
file: Some(file), file: Some(file),
url: None, url: None,
home: None, home: None,
support_url: None,
announce: None,
selected: None, selected: None,
extra: None, extra: None,
option: None, option: None,
@@ -529,6 +602,8 @@ impl PrfItem {
file: Some(file), file: Some(file),
url: None, url: None,
home: None, home: None,
support_url: None,
announce: None,
selected: None, selected: None,
extra: None, extra: None,
option: None, option: None,

View File

@@ -220,6 +220,10 @@ impl IProfiles {
each.extra = item.extra; each.extra = item.extra;
each.updated = item.updated; each.updated = item.updated;
each.home = item.home; each.home = item.home;
each.announce = item.announce;
each.support_url = item.support_url;
each.name = item.name;
each.url = item.url;
each.option = PrfOption::merge(each.option.clone(), item.option); each.option = PrfOption::merge(each.option.clone(), item.option);
// save the file data // save the file data
// move the field value after save // move the field value after save

View File

@@ -26,6 +26,8 @@ import {
Wrench, Wrench,
AlertTriangle, AlertTriangle,
Loader2, Loader2,
Globe,
Send,
} from "lucide-react"; } from "lucide-react";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { useSystemState } from "@/hooks/use-system-state"; import { useSystemState } from "@/hooks/use-system-state";
@@ -34,6 +36,7 @@ import { Switch } from "@/components/ui/switch";
import { ProxySelectors } from "@/components/home/proxy-selectors"; import { ProxySelectors } from "@/components/home/proxy-selectors";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { closeAllConnections } from "@/services/api"; import { closeAllConnections } from "@/services/api";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
const MinimalHomePage: React.FC = () => { const MinimalHomePage: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -50,12 +53,12 @@ const MinimalHomePage: React.FC = () => {
return items.filter((i: any) => i && allowedTypes.includes(i.type!)); return items.filter((i: any) => i && allowedTypes.includes(i.type!));
}, [profiles]); }, [profiles]);
const currentProfileName = useMemo(() => { const currentProfile = useMemo(() => {
return ( return profileItems.find(p => p.uid === profiles?.current);
profileItems.find((p) => p.uid === profiles?.current)?.name ||
profiles?.current
);
}, [profileItems, profiles?.current]); }, [profileItems, profiles?.current]);
console.log(currentProfile);
const currentProfileName = currentProfile?.name || profiles?.current;
const activateProfile = useCallback( const activateProfile = useCallback(
async (uid: string, notifySuccess: boolean) => { async (uid: string, notifySuccess: boolean) => {
try { try {
@@ -200,6 +203,11 @@ const MinimalHomePage: React.FC = () => {
<div className="flex items-center justify-center flex-grow w-full"> <div className="flex items-center justify-center flex-grow w-full">
<div className="flex flex-col items-center gap-8 pt-10"> <div className="flex flex-col items-center gap-8 pt-10">
{currentProfile?.announce && (
<p className="relative -translate-y-15 text-xl font-semibold text-foreground max-w-lg text-center">
{currentProfile.announce}
</p>
)}
<div className="text-center"> <div className="text-center">
<h1 <h1
className="text-4xl mb-2 font-semibold" className="text-4xl mb-2 font-semibold"
@@ -270,7 +278,29 @@ const MinimalHomePage: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
<footer className="flex justify-center p-4 flex-shrink-0">
{currentProfile?.support_url && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>{t("Support")}:</span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<a href={currentProfile.support_url} target="_blank" rel="noopener noreferrer" className="transition-colors hover:text-primary">
{(currentProfile.support_url.includes('t.me') || currentProfile.support_url.includes('telegram')) ? (
<Send className="h-5 w-5" />
) : (
<Globe className="h-5 w-5" />
)}
</a>
</TooltipTrigger>
<TooltipContent>
<p>{currentProfile.support_url}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)}
</footer>
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} /> <ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
</div> </div>
); );

View File

@@ -181,6 +181,7 @@ interface IClashInfo {
} }
interface IProfileItem { interface IProfileItem {
currentProfile: any;
uid: string; uid: string;
type?: "local" | "remote" | "merge" | "script"; type?: "local" | "remote" | "merge" | "script";
name?: string; name?: string;
@@ -200,6 +201,8 @@ interface IProfileItem {
}; };
option?: IProfileOption; option?: IProfileOption;
home?: string; home?: string;
support_url?: string;
announce?: string;
} }
interface IProfileOption { interface IProfileOption {