added support for special headers and displaying their information on the main page
This commit is contained in:
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@@ -1125,6 +1125,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.27.0",
|
||||
"tungstenite 0.27.0",
|
||||
"url",
|
||||
"users",
|
||||
"warp",
|
||||
"winapi",
|
||||
|
||||
@@ -16,6 +16,7 @@ identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||
tauri-build = { version = "2.3.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
url = "2.5.4"
|
||||
os_info = "3.0"
|
||||
machine-uid = "0.2"
|
||||
warp = "0.3.7"
|
||||
|
||||
@@ -8,6 +8,8 @@ use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use std::{fs, time::Duration};
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use url::Url;
|
||||
|
||||
use super::Config;
|
||||
|
||||
@@ -53,6 +55,14 @@ pub struct PrfItem {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
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
|
||||
#[serde(skip)]
|
||||
pub file_data: Option<String>,
|
||||
@@ -234,6 +244,8 @@ impl PrfItem {
|
||||
..PrfOption::default()
|
||||
}),
|
||||
home: None,
|
||||
support_url: None,
|
||||
announce: None,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
||||
})
|
||||
@@ -297,6 +309,21 @@ impl PrfItem {
|
||||
|
||||
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
|
||||
let extra = match header.get("Subscription-Userinfo") {
|
||||
Some(value) => {
|
||||
@@ -354,9 +381,45 @@ impl PrfItem {
|
||||
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 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?;
|
||||
|
||||
// process the charset "UTF-8 with BOM"
|
||||
@@ -404,7 +467,7 @@ impl PrfItem {
|
||||
name: Some(name),
|
||||
desc,
|
||||
file: Some(file),
|
||||
url: Some(url.into()),
|
||||
url: Some(final_url),
|
||||
selected: None,
|
||||
extra,
|
||||
option: Some(PrfOption {
|
||||
@@ -417,6 +480,8 @@ impl PrfItem {
|
||||
..PrfOption::default()
|
||||
}),
|
||||
home,
|
||||
support_url,
|
||||
announce,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(data.into()),
|
||||
})
|
||||
@@ -444,6 +509,8 @@ impl PrfItem {
|
||||
extra: None,
|
||||
option: None,
|
||||
home: None,
|
||||
support_url: None,
|
||||
announce: None,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(template),
|
||||
})
|
||||
@@ -466,6 +533,8 @@ impl PrfItem {
|
||||
file: Some(file),
|
||||
url: None,
|
||||
home: None,
|
||||
support_url: None,
|
||||
announce: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
@@ -487,6 +556,8 @@ impl PrfItem {
|
||||
file: Some(file),
|
||||
url: None,
|
||||
home: None,
|
||||
support_url: None,
|
||||
announce: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
@@ -508,6 +579,8 @@ impl PrfItem {
|
||||
file: Some(file),
|
||||
url: None,
|
||||
home: None,
|
||||
support_url: None,
|
||||
announce: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
@@ -529,6 +602,8 @@ impl PrfItem {
|
||||
file: Some(file),
|
||||
url: None,
|
||||
home: None,
|
||||
support_url: None,
|
||||
announce: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
|
||||
@@ -220,6 +220,10 @@ impl IProfiles {
|
||||
each.extra = item.extra;
|
||||
each.updated = item.updated;
|
||||
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);
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
Wrench,
|
||||
AlertTriangle,
|
||||
Loader2,
|
||||
Globe,
|
||||
Send,
|
||||
} from "lucide-react";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
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 { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { closeAllConnections } from "@/services/api";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
|
||||
const MinimalHomePage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -50,12 +53,12 @@ const MinimalHomePage: React.FC = () => {
|
||||
return items.filter((i: any) => i && allowedTypes.includes(i.type!));
|
||||
}, [profiles]);
|
||||
|
||||
const currentProfileName = useMemo(() => {
|
||||
return (
|
||||
profileItems.find((p) => p.uid === profiles?.current)?.name ||
|
||||
profiles?.current
|
||||
);
|
||||
const currentProfile = useMemo(() => {
|
||||
return profileItems.find(p => p.uid === profiles?.current);
|
||||
}, [profileItems, profiles?.current]);
|
||||
console.log(currentProfile);
|
||||
const currentProfileName = currentProfile?.name || profiles?.current;
|
||||
|
||||
const activateProfile = useCallback(
|
||||
async (uid: string, notifySuccess: boolean) => {
|
||||
try {
|
||||
@@ -200,6 +203,11 @@ const MinimalHomePage: React.FC = () => {
|
||||
|
||||
<div className="flex items-center justify-center flex-grow w-full">
|
||||
<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">
|
||||
<h1
|
||||
className="text-4xl mb-2 font-semibold"
|
||||
@@ -270,7 +278,29 @@ const MinimalHomePage: React.FC = () => {
|
||||
</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()} />
|
||||
</div>
|
||||
);
|
||||
|
||||
3
src/services/types.d.ts
vendored
3
src/services/types.d.ts
vendored
@@ -181,6 +181,7 @@ interface IClashInfo {
|
||||
}
|
||||
|
||||
interface IProfileItem {
|
||||
currentProfile: any;
|
||||
uid: string;
|
||||
type?: "local" | "remote" | "merge" | "script";
|
||||
name?: string;
|
||||
@@ -200,6 +201,8 @@ interface IProfileItem {
|
||||
};
|
||||
option?: IProfileOption;
|
||||
home?: string;
|
||||
support_url?: string;
|
||||
announce?: string;
|
||||
}
|
||||
|
||||
interface IProfileOption {
|
||||
|
||||
Reference in New Issue
Block a user