Files
umbrix/lib/utils/link_parsers.dart
Umbrix Developer 76a374950f feat: mobile-like window size and always-visible stats
- Changed window size to mobile phone format (400x800)
- Removed width condition for ActiveProxyFooter - now always visible
- Added run-umbrix.sh launch script with icon copying
- Stats cards now display on all screen sizes
2026-01-17 13:09:20 +03:00

107 lines
3.8 KiB
Dart

import 'dart:convert';
import 'package:dartx/dartx.dart';
import 'package:umbrix/features/profile/data/profile_parser.dart';
import 'package:umbrix/features/profile/data/profile_repository.dart';
import 'package:umbrix/singbox/model/singbox_proxy_type.dart';
import 'package:umbrix/utils/validators.dart';
typedef ProfileLink = ({String url, String name});
// TODO: test and improve
abstract class LinkParser {
static String generateSubShareLink(String url, [String? name]) {
final uri = Uri.tryParse(url);
if (uri == null) return '';
final modifiedUri = Uri(
scheme: uri.scheme,
host: uri.host,
path: uri.path,
query: uri.query,
fragment: name ?? uri.fragment,
);
// return 'umbrix://import/$modifiedUri';
return '$modifiedUri';
}
// protocols schemas
static const protocols = {'clash', 'clashmeta', 'sing-box', 'umbrix'};
static ProfileLink? parse(String link) {
return simple(link) ?? deep(link);
}
static ProfileLink? simple(String link) {
if (!isUrl(link)) return null;
final uri = Uri.parse(link.trim());
return (
url: uri.toString(),
name: uri.queryParameters['name'] ?? '',
);
}
static ({String content, String name})? protocol(String content) {
final normalContent = safeDecodeBase64(content);
final lines = normalContent.split('\n');
String? name;
for (final line in lines) {
final uri = Uri.tryParse(line);
if (uri == null) continue;
final fragment = uri.hasFragment ? Uri.decodeComponent(uri.fragment.split("&&detour")[0]) : null;
name ??= switch (uri.scheme) {
'ss' => fragment ?? ProxyType.shadowsocks.label,
'ssconf' => fragment ?? ProxyType.shadowsocks.label,
'vmess' => ProxyType.vmess.label,
'vless' => fragment ?? ProxyType.vless.label,
'trojan' => fragment ?? ProxyType.trojan.label,
'tuic' => fragment ?? ProxyType.tuic.label,
'hy2' || 'hysteria2' => fragment ?? ProxyType.hysteria2.label,
'hy' || 'hysteria' => fragment ?? ProxyType.hysteria.label,
'ssh' => fragment ?? ProxyType.ssh.label,
'wg' => fragment ?? ProxyType.wireguard.label,
'warp' => fragment ?? ProxyType.warp.label,
_ => null,
};
}
final headers = ProfileRepositoryImpl.parseHeadersFromContent(content);
final subinfo = ProfileParser.parse("", headers);
if (subinfo.name.isNotNullOrEmpty && subinfo.name != "Remote Profile") {
name = subinfo.name;
}
return (content: normalContent, name: name ?? ProxyType.unknown.label);
}
static ProfileLink? deep(String link) {
final uri = Uri.tryParse(link.trim());
if (uri == null || !uri.hasScheme || !uri.hasAuthority) return null;
final queryParams = uri.queryParameters;
switch (uri.scheme) {
case 'clash' || 'clashmeta' when uri.authority == 'install-config':
if (uri.authority != 'install-config' || !queryParams.containsKey('url')) return null;
return (url: queryParams['url']!, name: queryParams['name'] ?? '');
case 'sing-box':
if (uri.authority != 'import-remote-profile' || !queryParams.containsKey('url')) return null;
return (url: queryParams['url']!, name: queryParams['name'] ?? '');
case 'umbrix':
if (uri.authority == "import") {
return (url: uri.path.substring(1) + (uri.hasQuery ? "?${uri.query}" : ""), name: uri.fragment);
}
//for backward compatibility
if ((uri.authority != 'install-config' && uri.authority != 'install-sub') || !queryParams.containsKey('url')) return null;
return (url: queryParams['url']!, name: queryParams['name'] ?? '');
default:
return null;
}
}
}
String safeDecodeBase64(String str) {
try {
return utf8.decode(base64Decode(str));
} catch (e) {
return str;
}
}