Compare commits

21 Commits

Author SHA1 Message Date
sepakeloudest 62d1af8517 feat: 修改接口方式
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m26s
Build Windows / build (push) Has been cancelled
2025-11-20 18:11:29 -08:00
sepakeloudest 3b05d4ff72 feat: 修改国家选择匹配问题 2025-11-20 09:08:23 -08:00
sepakeloudest bcb4d17236 feat: ai修改win 名称信息 2025-11-20 09:07:40 -08:00
sepakeloudest d99f588a5b feat: 修改win的配置
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 21m20s
Build Windows / build (push) Has been cancelled
2025-11-20 07:02:10 -08:00
sepakeloudest 691dd6e1d6 feat: 修改bug
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 21m44s
Build Windows / build (push) Has been cancelled
2025-11-20 06:34:12 -08:00
sepakeloudest b442289b8a feat: 修改bug
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has started running
2025-11-20 06:19:05 -08:00
sepakeloudest 89d13e6237 feat: 修改兼容 2025-11-20 00:51:14 -08:00
sepakeloudest 8fd742a688 feat: 修改兼容 2025-11-19 04:06:46 -08:00
sepakeloudest 325a63d35f feat: 完成国家配置
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m34s
Build Windows / build (push) Has been cancelled
2025-11-19 01:06:48 -08:00
sepakeloudest 679c303457 feat: 1.新增在 VPN 配置中添加了一条规则,2.修复全局代理无法使用 2025-11-18 05:15:06 -08:00
sepakeloudest 59aa67d456 feat: 提交国家分组功能和debug功能
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m13s
Build Windows / build (push) Has been cancelled
2025-11-16 22:51:30 -08:00
sepakeloudest 2a1ee3018d feat: 提交国家分组功能和debug功能 2025-11-16 22:50:09 -08:00
sepakeloudest 76e83e0135 feat: 先改变节点选择方法
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m11s
Build Windows / build (push) Has been cancelled
2025-11-14 20:19:10 -08:00
sepakeloudest 89d6ec3c76 feat: 先改变节点选择方法
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-14 20:18:38 -08:00
sepakeloudest 670eb7ebc9 feat: 保存节点调试进度
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m7s
Build Windows / build (push) Has been cancelled
2025-11-13 22:43:27 -08:00
sepakeloudest 46295f4543 feat: 修改windows构建
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 29m17s
Build Windows / build (push) Successful in 7h8m10s
2025-11-13 05:24:35 -08:00
sepakeloudest a47174df56 feat: 初始化并发问题处理
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
2025-11-13 05:15:18 -08:00
sepakeloudest 748ec6bee9 feat: 提交国家选择
Build Windows / build (push) Successful in 7h7m43s
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m1s
2025-11-11 01:29:16 -08:00
sepakeloudest a884e6c838 feat: 修改id和sn显示
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-09 22:55:01 -08:00
sepakeloudest 920b86e56a feat: 修改代码冲突
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-09 22:46:53 -08:00
sepakeloudest a78b40c339 feat: 修改代码冲突
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-09 22:43:57 -08:00
47 changed files with 2214 additions and 695 deletions
+1
View File
@@ -1,3 +1,4 @@
[submodule "libcore"] [submodule "libcore"]
path = libcore path = libcore
url = https://github.com/hiddify/hiddify-next-core url = https://github.com/hiddify/hiddify-next-core
branch = main
+3 -3
View File
@@ -57,7 +57,7 @@ android {
defaultConfig { defaultConfig {
applicationId "app.hifastvpn.com" applicationId "app.hifastvpn.com"
minSdkVersion flutter.minSdkVersion minSdkVersion flutter.minSdkVersion
targetSdkVersion 36 targetSdkVersion 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
multiDexEnabled true multiDexEnabled true
@@ -105,8 +105,8 @@ android {
debugSymbolLevel 'FULL' debugSymbolLevel 'FULL'
} }
// 禁用代码混淆和资源压缩,解决VPN连接问题 // 禁用代码混淆和资源压缩,解决VPN连接问题
minifyEnabled true minifyEnabled false
shrinkResources true shrinkResources false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
+5
View File
@@ -34,6 +34,11 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 如果 targetSdkVersion >= 33 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:name=".Application" android:name=".Application"
android:banner="@mipmap/ic_banner" android:banner="@mipmap/ic_banner"
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

+2 -2
View File
@@ -193,7 +193,7 @@
"rewardDetails": "Reward Details >", "rewardDetails": "Reward Details >",
"steps": "Invitation Steps", "steps": "Invitation Steps",
"inviteFriend": "Invite Friends", "inviteFriend": "Invite Friends",
"acceptInvite": "Friends accept invitation\nPlace order and register", "acceptInvite": "Friends accept invitationPlace order and register",
"getReward": "Get Reward", "getReward": "Get Reward",
"shareLink": "Share Link", "shareLink": "Share Link",
"shareQR": "Share QR Code", "shareQR": "Share QR Code",
@@ -398,7 +398,7 @@
"content": "Would you like to update now?", "content": "Would you like to update now?",
"updateNow": "Update Now", "updateNow": "Update Now",
"updateLater": "Later", "updateLater": "Later",
"defaultContent": "1. Optimize app performance\n2. Fix known issues\n3. Improve user experience" "defaultContent": "1. Optimize app performance2. Fix known issues3. Improve user experience"
}, },
"country": { "country": {
"cn": "China", "cn": "China",
+2 -2
View File
@@ -256,7 +256,7 @@
"rewardDetails": "Detalles de recompensa >", "rewardDetails": "Detalles de recompensa >",
"steps": "Pasos de invitación", "steps": "Pasos de invitación",
"inviteFriend": "Invitar amigo", "inviteFriend": "Invitar amigo",
"acceptInvite": "El amigo acepta la invitación\ny se registra", "acceptInvite": "El amigo acepta la invitacióny se registra",
"getReward": "Obtener recompensa", "getReward": "Obtener recompensa",
"shareLink": "Compartir por enlace", "shareLink": "Compartir por enlace",
"shareQR": "Compartir por código QR", "shareQR": "Compartir por código QR",
@@ -404,7 +404,7 @@
"content": "¿Actualizar ahora?", "content": "¿Actualizar ahora?",
"updateNow": "Actualizar ahora", "updateNow": "Actualizar ahora",
"updateLater": "Más tarde", "updateLater": "Más tarde",
"defaultContent": "1. Optimización del rendimiento de la aplicación\n2. Corrección de problemas conocidos\n3. Mejora de la experiencia del usuario" "defaultContent": "1. Optimización del rendimiento de la aplicación2. Corrección de problemas conocidos3. Mejora de la experiencia del usuario"
}, },
"kr_invite": { "kr_invite": {
"close": "Cerrar", "close": "Cerrar",
+2 -2
View File
@@ -193,7 +193,7 @@
"rewardDetails": "Tasu üksikasjad >", "rewardDetails": "Tasu üksikasjad >",
"steps": "Kutse Sammud", "steps": "Kutse Sammud",
"inviteFriend": "Kutsu Sõbrad", "inviteFriend": "Kutsu Sõbrad",
"acceptInvite": "Sõbrad aktsepteerivad kutset\nTee tellimus ja registreeru", "acceptInvite": "Sõbrad aktsepteerivad kutsetTee tellimus ja registreeru",
"getReward": "Saada Tasu", "getReward": "Saada Tasu",
"shareLink": "Jaga Linki", "shareLink": "Jaga Linki",
"shareQR": "Jaga QR-koodi", "shareQR": "Jaga QR-koodi",
@@ -395,7 +395,7 @@
"content": "Uuendada nüüd?", "content": "Uuendada nüüd?",
"updateNow": "Uuenda nüüd", "updateNow": "Uuenda nüüd",
"updateLater": "Hiljem", "updateLater": "Hiljem",
"defaultContent": "1. Rakenduse jõudluse optimeerimine\n2. Teadaolevate probleemide parandamine\n3. Kasutajamugavuse parandamine" "defaultContent": "1. Rakenduse jõudluse optimeerimine2. Teadaolevate probleemide parandamine3. Kasutajamugavuse parandamine"
}, },
"country": { "country": {
"cn": "Hiina", "cn": "Hiina",
+2 -2
View File
@@ -209,7 +209,7 @@
"rewardDetails": "報酬の詳細 >", "rewardDetails": "報酬の詳細 >",
"steps": "招待の手順", "steps": "招待の手順",
"inviteFriend": "友達を招待", "inviteFriend": "友達を招待",
"acceptInvite": "友達が招待を受け入れ\n注文して登録", "acceptInvite": "友達が招待を受け入れ注文して登録",
"getReward": "報酬を獲得", "getReward": "報酬を獲得",
"shareLink": "リンクを共有", "shareLink": "リンクを共有",
"shareQR": "QRコードを共有", "shareQR": "QRコードを共有",
@@ -412,7 +412,7 @@
"content": "今すぐアップデートしますか?", "content": "今すぐアップデートしますか?",
"updateNow": "今すぐアップデート", "updateNow": "今すぐアップデート",
"updateLater": "後で", "updateLater": "後で",
"defaultContent": "1. アプリのパフォーマンス最適化\n2. 既知の問題の修正\n3. ユーザー体験の向上" "defaultContent": "1. アプリのパフォーマンス最適化2. 既知の問題の修正3. ユーザー体験の向上"
}, },
"country": { "country": {
"cn": "中国", "cn": "中国",
+1 -1
View File
@@ -387,7 +387,7 @@
"content": "Хотите обновить сейчас?", "content": "Хотите обновить сейчас?",
"updateNow": "Обновить сейчас", "updateNow": "Обновить сейчас",
"later": "Позже", "later": "Позже",
"defaultContent": "1. Оптимизация производительности приложения\n2. Исправление известных проблем\n3. Улучшение пользовательского опыта" "defaultContent": "1. Оптимизация производительности приложения2. Исправление известных проблем3. Улучшение пользовательского опыта"
}, },
"country": { "country": {
"cn": "Китай", "cn": "Китай",
+9 -9
View File
@@ -232,13 +232,13 @@
"longestConnection": "最长连接时间", "longestConnection": "最长连接时间",
"days": "{days}天", "days": "{days}天",
"daysOfWeek": { "daysOfWeek": {
"monday": "周\n一", "monday": "周一",
"tuesday": "周\n二", "tuesday": "周二",
"wednesday": "周\n三", "wednesday": "周三",
"thursday": "周\n四", "thursday": "周四",
"friday": "周\n五", "friday": "周五",
"saturday": "周\n六", "saturday": "周六",
"sunday": "周\n日" "sunday": "周日"
}, },
"processTrafficFailed": "处理流量日志数据失败" "processTrafficFailed": "处理流量日志数据失败"
}, },
@@ -256,7 +256,7 @@
"rewardDetails": "奖励明细 >", "rewardDetails": "奖励明细 >",
"steps": "邀请步骤", "steps": "邀请步骤",
"inviteFriend": "邀请好友", "inviteFriend": "邀请好友",
"acceptInvite": "好友接受邀请\n下单并注册", "acceptInvite": "好友接受邀请下单并注册",
"getReward": "获得奖励", "getReward": "获得奖励",
"shareLink": "分享链接", "shareLink": "分享链接",
"shareQR": "分享二维码", "shareQR": "分享二维码",
@@ -415,7 +415,7 @@
"content": "是否立即更新?", "content": "是否立即更新?",
"updateNow": "立即更新", "updateNow": "立即更新",
"updateLater": "稍后再说", "updateLater": "稍后再说",
"defaultContent": "1. 优化应用性能\n2. 修复已知问题\n3. 提升用户体验" "defaultContent": "1. 优化应用性能2. 修复已知问题3. 提升用户体验"
}, },
"country": { "country": {
"cn": "中国", "cn": "中国",
@@ -170,13 +170,13 @@
"longestConnection": "最長連接時間", "longestConnection": "最長連接時間",
"days": "{days}天", "days": "{days}天",
"daysOfWeek": { "daysOfWeek": {
"monday": "週\n一", "monday": "週一",
"tuesday": "週\n二", "tuesday": "週二",
"wednesday": "週\n三", "wednesday": "週三",
"thursday": "週\n四", "thursday": "週四",
"friday": "週\n五", "friday": "週五",
"saturday": "週\n六", "saturday": "週六",
"sunday": "週\n日" "sunday": "週日"
}, },
"processTrafficFailed": "處理流量日誌資料失敗" "processTrafficFailed": "處理流量日誌資料失敗"
}, },
@@ -194,7 +194,7 @@
"rewardDetails": "獎勵明細 >", "rewardDetails": "獎勵明細 >",
"steps": "邀請步驟", "steps": "邀請步驟",
"inviteFriend": "邀請好友", "inviteFriend": "邀請好友",
"acceptInvite": "好友接受邀請\n下單並註冊", "acceptInvite": "好友接受邀請下單並註冊",
"getReward": "獲得獎勵", "getReward": "獲得獎勵",
"shareLink": "分享連結", "shareLink": "分享連結",
"shareQR": "分享二維碼", "shareQR": "分享二維碼",
@@ -332,7 +332,7 @@
"content": "是否立即更新?", "content": "是否立即更新?",
"updateNow": "立即更新", "updateNow": "立即更新",
"updateLater": "稍後", "updateLater": "稍後",
"defaultContent": "1. 優化應用性能\n2. 修復已知問題\n3. 改進用戶體驗" "defaultContent": "1. 優化應用性能2. 修復已知問題3. 改進用戶體驗"
}, },
"orderStatus": { "orderStatus": {
"title": "訂單狀態", "title": "訂單狀態",
+5 -5
View File
@@ -4,12 +4,12 @@ echo 📋 复制 libcore 文件...
:: 创建目标目录 :: 创建目标目录
mkdir libcore\bin >nul 2>&1 mkdir libcore\bin >nul 2>&1
:: 查找并复制 HiddifyCli.exe,重命名为 BearVPNCli.exe :: 查找并复制 HiddifyCli.exe,重命名为 CLI 可执行文件
for /r %%f in (HiddifyCli.exe) do ( for /r %%f in (HiddifyCli.exe) do (
if exist "%%f" ( if exist "%%f" (
echo ✅ 找到 HiddifyCli.exe: %%f echo ✅ 找到 HiddifyCli.exe: %%f
echo 📝 复制并重命名为 BearVPNCli.exe echo 📝 复制并重命名为 HiFastVPNCli.exe
copy "%%f" libcore\bin\BearVPNCli.exe copy "%%f" libcore\bin\HiFastVPNCli.exe
echo ✅ 重命名完成 echo ✅ 重命名完成
goto :dll goto :dll
) )
@@ -32,7 +32,7 @@ echo.
echo 📄 验证文件: echo 📄 验证文件:
if exist libcore\bin ( if exist libcore\bin (
dir libcore\bin dir libcore\bin
if exist libcore\bin\BearVPNCli.exe ( if exist libcore\bin\HiFastVPNCli.exe (
if exist libcore\bin\libcore.dll ( if exist libcore\bin\libcore.dll (
echo ✅ 验证成功:所有文件已正确复制 echo ✅ 验证成功:所有文件已正确复制
exit /b 0 exit /b 0
@@ -41,7 +41,7 @@ if exist libcore\bin (
exit /b 1 exit /b 1
) )
) else ( ) else (
echo ❌ 验证失败:BearVPNCli.exe 不存在 echo ❌ 验证失败:HiFastVPNCli.exe 不存在
exit /b 1 exit /b 1
) )
) else ( ) else (
+6 -1
View File
@@ -1,4 +1,5 @@
name: BearVPN name: HiFastVPN
app_name: HiFastVPN
version: 1.0.0 version: 1.0.0
build_number: 1 build_number: 1
targets: targets:
@@ -11,3 +12,7 @@ targets:
enable: true enable: true
# 不签名 # 不签名
sign: false sign: false
windows:
exe:
enable: true
icon: assets/images/tray_icon.ico
+1 -1
View File
@@ -1447,7 +1447,7 @@ class AppConfig {
kr_official_telegram = config.kr_official_telegram; kr_official_telegram = config.kr_official_telegram;
kr_official_telephone = config.kr_official_telephone; kr_official_telephone = config.kr_official_telephone;
kr_invitation_link = config.kr_invitation_link; kr_invitation_link = config.kr_invitation_link;
kr_website_id = config.kr_website_id; // kr_website_id = config.kr_website_id;
if (config.kr_domains.isNotEmpty) { if (config.kr_domains.isNotEmpty) {
KRDomain.kr_handleDomains(config.kr_domains); KRDomain.kr_handleDomains(config.kr_domains);
} }
+249 -151
View File
@@ -28,14 +28,66 @@ class KROutboundItem {
/// URL /// URL
String url = ""; String url = "";
@override // ✅ 1. 将传入的 nodeListItem 保存为类的 final 成员变量
String toString() { final KrNodeListItem nodeListItem;
return 'KROutboundItem(tag: $tag, country: $country, delay: ${urlTestDelay.value}ms)';
}
/// 服务器类型 /// 服务器类型
/// 构造函数,接受 KrNodeListItem 对象并初始化 KROutboundItem /// 构造函数,接受 KrItem 对象并初始化 KROutboundItem
KROutboundItem(KrNodeListItem nodeListItem) { KROutboundItem(this.nodeListItem) {
_initFromNodeListItem();
}
/// 静态工厂:构造虚拟 urltest 节点(用于 ${country}-auto
factory KROutboundItem.fromVirtual(String tag, String country, Map<String, dynamic> config) {
// 构造一个虚拟 KrNodeListItem,仅填充必要字段
final virtualNode = KrNodeListItem(
id: 0,
name: tag,
protocol: 'urltest',
serverAddr: '',
port: 0,
uuid: '',
config: jsonEncode(config),
city: '',
country: country,
tags: [],
latitude: 0,
longitude: 0,
latitudeCountry: 0,
longitudeCountry: 0,
relayNode: '',
relayMode: 'none',
protocols: '',
method: '',
speedLimit: 0,
traffic: 0,
trafficRatio: 0,
upload: 0,
download: 0,
startTime: '',
expireTime: '',
);
return KROutboundItem._virtual(virtualNode);
}
/// 私有构造:用于虚拟节点,避免重复解析
KROutboundItem._virtual(this.nodeListItem) {
// 直接填充虚拟节点所需字段
id = nodeListItem.id.toString();
protocol = nodeListItem.protocol;
tag = nodeListItem.name;
serverAddr = nodeListItem.serverAddr;
city = nodeListItem.city;
country = nodeListItem.country;
latitude = nodeListItem.latitude;
latitudeCountry = nodeListItem.latitudeCountry;
longitude = nodeListItem.longitude;
longitudeCountry = nodeListItem.longitudeCountry;
config = jsonDecode(nodeListItem.config); // 已知 config 有效
}
/// 初始化逻辑提取,供主构造调用
void _initFromNodeListItem() {
id = nodeListItem.id.toString(); id = nodeListItem.id.toString();
protocol = nodeListItem.protocol; protocol = nodeListItem.protocol;
latitude = nodeListItem.latitude; latitude = nodeListItem.latitude;
@@ -58,88 +110,34 @@ class KROutboundItem {
return; return;
} }
// 兜底:尝试解析 config 字段(旧API格式) // 兜底:尝试 config 字段解析(旧API格式)
if (nodeListItem.config.isEmpty) { if (nodeListItem.config.isNotEmpty) {
if (kDebugMode) {
print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
}
config = {};
return;
}
late Map<String, dynamic> json;
try { try {
json = jsonDecode(nodeListItem.config) as Map<String, dynamic>; final json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
} catch (e) {
if (kDebugMode) { if (kDebugMode) {
print('❌ 节点 ${nodeListItem.name} config 解析失败: $e,尝试使用直接字段'); print('📄 解析到 config JSON: $json');
} }
// 提取 transport 配置
Map<String, dynamic>? transportConfig;
if (json['transport'] != null && json['transport'] != 'tcp') {
transportConfig = _buildTransport(json);
if (kDebugMode) { if (kDebugMode) {
print('📄 Config 内容: ${nodeListItem.config}'); print('✅ 找到 transport 配置: $transportConfig');
} }
_buildConfigFromFields(nodeListItem);
return;
} }
// 提取 security_config
Map<String, dynamic>? securityConfig;
if (json['security_config'] != null) {
securityConfig = json['security_config'] as Map<String, dynamic>;
if (kDebugMode) {
print('✅ 找到 security_config: $securityConfig');
}
}
// 根据协议类型构建配置
switch (nodeListItem.protocol) { switch (nodeListItem.protocol) {
case "vless":
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name
String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr;
}
config = {
"type": "vless",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"uuid": nodeListItem.uuid,
if (json["flow"] != null && json["flow"] != "none")
"flow": json["flow"],
if (json["transport"] != null && json["transport"] != "tcp")
"transport": _buildTransport(json),
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig["fingerprint"] ?? "chrome"
}
}
};
break;
case "vmess":
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name
String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr;
}
config = {
"type": "vmess",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"uuid": nodeListItem.uuid,
"alter_id": 0,
"security": "auto",
if (json["transport"] != null && json["transport"] != "tcp")
"transport": _buildTransport(json),
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
break;
case "shadowsocks": case "shadowsocks":
config = { config = {
"type": "shadowsocks", "type": "shadowsocks",
@@ -150,11 +148,77 @@ class KROutboundItem {
"password": nodeListItem.uuid "password": nodeListItem.uuid
}; };
break; break;
case "vless":
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "vless",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"uuid": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig,
if (json["security"] == "tls") "tls": {
"enabled": true,
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
"insecure": securityConfig?["allow_insecure"] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
}
}
};
break;
case "vmess":
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "vmess",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"uuid": nodeListItem.uuid,
"alter_id": 0,
"security": "auto",
if (transportConfig != null) "transport": transportConfig,
if (json["security"] == "tls") "tls": {
"enabled": true,
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
"insecure": securityConfig?["allow_insecure"] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
}
}
};
break;
case "trojan":
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr);
config = {
"type": "trojan",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"password": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig,
"tls": {
"enabled": json["security"] == "tls",
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
"insecure": securityConfig?["allow_insecure"] ?? true,
"utls": {
"enabled": true,
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
}
}
};
break;
case "hysteria": case "hysteria":
case "hysteria2": case "hysteria2":
// 后端的 "hysteria" 实际上是 Hysteria2 协议 final securityConfig = json["security_config"] as Map<String, dynamic>? ?? {};
final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {};
config = { config = {
"type": "hysteria2", "type": "hysteria2",
"tag": nodeListItem.name, "tag": nodeListItem.name,
@@ -174,31 +238,11 @@ class KROutboundItem {
} }
}; };
break; break;
case "trojan": default:
final securityConfig = if (kDebugMode) {
json["security_config"] as Map<String, dynamic>? ?? {}; print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
// 智能设置 server_name
String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) {
// 如果没有配置 SNI,使用服务器地址
serverName = nodeListItem.serverAddr;
} }
config = {};
config = {
"type": "trojan",
"tag": nodeListItem.name,
"server": nodeListItem.serverAddr,
"server_port": json["port"],
"password": nodeListItem.uuid,
"tls": {
"enabled": json["security"] == "tls",
"server_name": serverName,
"insecure": securityConfig["allow_insecure"] ?? true,
"utls": {"enabled": true, "fingerprint": "chrome"}
}
};
break;
} }
// 检查 relayNode 是否为 JSON 字符串并解析 // 检查 relayNode 是否为 JSON 字符串并解析
@@ -211,7 +255,18 @@ class KROutboundItem {
config["server_port"] = randomNode["port"]; // 提取 port config["server_port"] = randomNode["port"]; // 提取 port
} }
} }
// 解析配置 } catch (e) {
if (kDebugMode) {
print('⚠️ 解析 config 字段失败: $e');
}
config = {};
}
}
}
@override
String toString() {
return 'KROutboundItem(name: ${nodeListItem.name}, protocol: ${nodeListItem.protocol}, server: ${nodeListItem.serverAddr}, port: ${nodeListItem.port})';
} }
/// 构建传输配置 /// 构建传输配置
@@ -249,31 +304,11 @@ class KROutboundItem {
if (kDebugMode) { if (kDebugMode) {
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}'); print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
} }
if (kDebugMode) {
print('📋 节点详细信息:');
}
if (kDebugMode) {
print(' - serverAddr: ${nodeListItem.serverAddr}');
}
if (kDebugMode) {
print(' - port: ${nodeListItem.port}');
}
if (kDebugMode) {
print(' - uuid: ${nodeListItem.uuid}');
}
if (kDebugMode) {
print(' - method: ${nodeListItem.method}');
}
if (kDebugMode) {
print(' - config: ${nodeListItem.config}');
}
if (kDebugMode) {
print(' - protocols: ${nodeListItem.protocols}');
}
// 🔧 尝试从 config 字段解析 transport 配置 // 🔧 尝试从 config 字段解析 transport 配置
Map<String, dynamic>? transportConfig; Map<String, dynamic>? transportConfig;
Map<String, dynamic>? securityConfig; Map<String, dynamic>? securityConfig;
int actualPort = nodeListItem.port; // 🔧 使用局部变量存储实际端口
// 🔧 关键修复:优先从 protocols 字段解析配置 // 🔧 关键修复:优先从 protocols 字段解析配置
if (nodeListItem.protocols.isNotEmpty) { if (nodeListItem.protocols.isNotEmpty) {
@@ -307,6 +342,28 @@ class KROutboundItem {
} }
if (matchedProtocol != null) { if (matchedProtocol != null) {
// 🔧 关键修复:只在顶层端口为0时,才使用 protocols 中的端口
// 这样可以保留顶层的正确端口(如 53441),不被 protocols 数组中的端口(如 287)覆盖
if (actualPort == 0 && matchedProtocol['port'] != null) {
// 安全解析端口号
int protocolPort = 0;
final portValue = matchedProtocol['port'];
if (portValue is int) {
protocolPort = portValue;
} else if (portValue is String) {
protocolPort = int.tryParse(portValue) ?? 0;
}
if (protocolPort > 0) {
actualPort = protocolPort;
if (kDebugMode) {
print(' ✅ 从 protocols 使用端口: $protocolPort (顶层端口为0)');
}
}
} else if (kDebugMode && matchedProtocol['port'] != null) {
print(' ✅ 保留顶层端口: $actualPort (protocols中的端口: ${matchedProtocol['port']})');
}
// 提取 transport 配置 // 提取 transport 配置
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) { if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
final network = matchedProtocol['network'] ?? matchedProtocol['transport']; final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
@@ -411,18 +468,17 @@ class KROutboundItem {
"type": "shadowsocks", "type": "shadowsocks",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"method": finalMethod, "method": finalMethod,
"password": nodeListItem.uuid "password": nodeListItem.uuid
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}'); print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
}
if (kDebugMode) {
print('📄 使用加密方法: $finalMethod'); print('📄 使用加密方法: $finalMethod');
} print('📄 完整配置 JSON:');
if (kDebugMode) { print(jsonEncode(config));
print('📄 完整配置: $config'); print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} }
break; break;
case "vless": case "vless":
@@ -436,17 +492,25 @@ class KROutboundItem {
serverName = securityConfig['sni'].toString(); serverName = securityConfig['sni'].toString();
} }
// 🔧 关键修复:根据 security_config 判断是否启用 TLS // 🔧 关键修复:智能判断是否启用 TLS
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false; // 1. 优先使用 securityConfig['tls_enabled']
// 2. 如果没有明确配置,根据端口和域名智能判断
bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? true; // 默认启用 TLS
// 如果端口是标准非TLS端口(80, 8080等),则禁用 TLS
if (nodeListItem.port == 80 || nodeListItem.port == 8080) {
vlessTlsEnabled = false;
}
if (kDebugMode) { if (kDebugMode) {
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled'); print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
} }
config = { config = {
"type": "vless", "type": "vless",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"uuid": nodeListItem.uuid, "uuid": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig, if (transportConfig != null) "transport": transportConfig,
if (vlessTlsEnabled) "tls": { if (vlessTlsEnabled) "tls": {
@@ -460,10 +524,23 @@ class KROutboundItem {
} }
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}'); print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
} print('📋 原始节点信息:');
if (kDebugMode) { print(' - serverAddr: ${nodeListItem.serverAddr}');
print('📄 完整配置: $config'); print(' - port: ${nodeListItem.port}');
print(' - uuid: ${nodeListItem.uuid}');
print(' - protocols: ${nodeListItem.protocols}');
print('🔐 安全配置:');
print(' - TLS 启用: $vlessTlsEnabled');
print(' - 是域名: $isDomain');
print(' - server_name: $serverName');
print(' - securityConfig: $securityConfig');
print('📡 传输配置:');
print(' - transportConfig: $transportConfig');
print('📄 最终生成的完整配置 JSON:');
print(jsonEncode(config));
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} }
break; break;
case "vmess": case "vmess":
@@ -477,17 +554,25 @@ class KROutboundItem {
serverName = securityConfig['sni'].toString(); serverName = securityConfig['sni'].toString();
} }
// 🔧 关键修复:根据 security_config 判断是否启用 TLS // 🔧 关键修复:智能判断是否启用 TLS
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false; // 1. 优先使用 securityConfig['tls_enabled']
// 2. 如果没有明确配置,根据端口和域名智能判断
bool tlsEnabled = securityConfig?['tls_enabled'] ?? true; // 默认启用 TLS
// 如果端口是标准非TLS端口(80, 8080等),则禁用 TLS
if (nodeListItem.port == 80 || nodeListItem.port == 8080) {
tlsEnabled = false;
}
if (kDebugMode) { if (kDebugMode) {
print('🔐 TLS 状态: enabled=$tlsEnabled'); print('🔐 VMess TLS 状态: enabled=$tlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
} }
config = { config = {
"type": "vmess", "type": "vmess",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"uuid": nodeListItem.uuid, "uuid": nodeListItem.uuid,
"alter_id": 0, "alter_id": 0,
"security": "auto", "security": "auto",
@@ -503,10 +588,23 @@ class KROutboundItem {
} }
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}'); print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
} print('📋 原始节点信息:');
if (kDebugMode) { print(' - serverAddr: ${nodeListItem.serverAddr}');
print('📄 完整配置: $config'); print(' - port: ${nodeListItem.port}');
print(' - uuid: ${nodeListItem.uuid}');
print(' - protocols: ${nodeListItem.protocols}');
print('🔐 安全配置:');
print(' - TLS 启用: $tlsEnabled');
print(' - 是域名: $isDomain');
print(' - server_name: $serverName');
print(' - securityConfig: $securityConfig');
print('📡 传输配置:');
print(' - transportConfig: $transportConfig');
print('📄 最终生成的完整配置 JSON:');
print(jsonEncode(config));
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} }
break; break;
case "trojan": case "trojan":
@@ -524,7 +622,7 @@ class KROutboundItem {
"type": "trojan", "type": "trojan",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"password": nodeListItem.uuid, "password": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig, if (transportConfig != null) "transport": transportConfig,
"tls": { "tls": {
@@ -88,6 +88,9 @@ class KrOutboundsList {
} }
} }
// 生成国家自动选择虚拟节点
_generateCountryAutoNodes(countryGroups);
// 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList // 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList
for (var tag in tagGroups.keys) { for (var tag in tagGroups.keys) {
final item = KRGroupOutboundList( final item = KRGroupOutboundList(
@@ -108,7 +111,41 @@ class KrOutboundsList {
country: country, country: country,
outboundList: countryGroups[country]!)); // 添加国家分组到列表 outboundList: countryGroups[country]!)); // 添加国家分组到列表
} }
}
/// 生成国家自动选择虚拟节点
void _generateCountryAutoNodes(Map<String, List<KROutboundItem>> countryGroups) {
for (var entry in countryGroups.entries) {
final country = entry.key;
final nodes = entry.value;
final autoTag = '${country}-auto';
if (kDebugMode) {
print('🤖 生成国家自动选择节点: $autoTag, 包含 ${nodes.length} 个节点');
}
// 构建 urltest 配置
final urltestConfig = {
'type': 'urltest',
'tag': autoTag,
'outbounds': nodes.map((node) => node.tag).toList(),
'url': 'https://www.google.com/generate_204',
'interval': '10m',
'tolerance': 50,
"interrupt_exist_connections": true,
};
// 创建虚拟节点
final virtualNode = KROutboundItem.fromVirtual(autoTag, country, urltestConfig);
// 添加到各个列表
allList.add(virtualNode);
keyList[autoTag] = virtualNode;
// configJsonList.add(urltestConfig);
if (kDebugMode) {
print('✅ 生成虚拟节点: $autoTag, 配置: ${urltestConfig.toString()}');
}
}
} }
} }
+52 -9
View File
@@ -136,25 +136,68 @@ class KrNodeListItem {
String method = json['method']?.toString() ?? ''; // 加密方法(Shadowsocks等) String method = json['method']?.toString() ?? ''; // 加密方法(Shadowsocks等)
final protocols = json['protocols']?.toString() ?? ''; // 协议配置JSON final protocols = json['protocols']?.toString() ?? ''; // 协议配置JSON
// 🔧 如果有 protocols 字段,从中解析 port 和 cipher // 🔧 打印原始节点 JSON(用于调试)
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
KRLogUtil.kr_i('📥 收到节点 API 原始数据:', tag: 'NodeList');
KRLogUtil.kr_i('节点名称: ${json['name']}', tag: 'NodeList');
KRLogUtil.kr_i('协议类型: ${json['protocol']}', tag: 'NodeList');
KRLogUtil.kr_i('完整 JSON:', tag: 'NodeList');
KRLogUtil.kr_i(jsonEncode(json), tag: 'NodeList');
// 🔧 如果有 protocols 字段,从中解析 cipher(但不覆盖顶层的 port
if (protocols.isNotEmpty) { if (protocols.isNotEmpty) {
try { try {
final protocolsList = jsonDecode(protocols) as List; final protocolsList = jsonDecode(protocols) as List;
final currentProtocol = json['protocol']?.toString().toLowerCase() ?? '';
KRLogUtil.kr_i('📋 protocols 字段内容 (${protocolsList.length} 个协议):', tag: 'NodeList');
for (var i = 0; i < protocolsList.length; i++) {
KRLogUtil.kr_i(' 协议 $i: ${jsonEncode(protocolsList[i])}', tag: 'NodeList');
}
if (protocolsList.isNotEmpty) { if (protocolsList.isNotEmpty) {
final firstProtocol = protocolsList[0] as Map<String, dynamic>; // 🔧 修复:查找与当前协议类型匹配的配置,而不是直接使用第一个
// 优先使用 protocols 中的配置 Map<String, dynamic>? matchedProtocolConfig;
if (firstProtocol['port'] != null) {
port = _parseIntSafely(firstProtocol['port']); // 尝试找到协议类型匹配的配置
for (var protocolConfig in protocolsList) {
final configMap = protocolConfig as Map<String, dynamic>;
// 🔧 修复:API 使用的字段名是 'type',不是 'protocol'
final protocolType = (configMap['type'] ?? configMap['protocol'])?.toString().toLowerCase() ?? '';
// 检查是否匹配(支持 shadowsocks/ss, vmess, vless, trojan 等)
if (protocolType == currentProtocol ||
(currentProtocol == 'shadowsocks' && protocolType == 'ss') ||
(currentProtocol == 'ss' && protocolType == 'shadowsocks')) {
matchedProtocolConfig = configMap;
KRLogUtil.kr_i('🎯 找到匹配的协议配置: $protocolType', tag: 'NodeList');
break;
} }
if (firstProtocol['cipher'] != null && firstProtocol['cipher'].toString().isNotEmpty) {
method = firstProtocol['cipher'].toString();
} }
KRLogUtil.kr_i('从 protocols 解析: port=$port, cipher=$method', tag: 'NodeList');
// 如果没找到匹配的,使用第一个配置(兼容旧API)
final targetProtocol = matchedProtocolConfig ?? (protocolsList[0] as Map<String, dynamic>);
// 🔧 关键修复:只在顶层没有 port 字段时,才使用 protocols 中的端口
// 这样可以保留顶层的正确端口(如 53441),不被 protocols 数组中的端口(如 287)覆盖
if (port == 0 && targetProtocol['port'] != null) {
port = _parseIntSafely(targetProtocol['port']);
KRLogUtil.kr_i('✅ 从 protocols 解析端口: $port', tag: 'NodeList');
} else {
KRLogUtil.kr_i('✅ 保留顶层端口: $port (protocols中的端口: ${targetProtocol['port']})', tag: 'NodeList');
}
// 提取 cipher(加密方法)
if (targetProtocol['cipher'] != null && targetProtocol['cipher'].toString().isNotEmpty) {
method = targetProtocol['cipher'].toString();
KRLogUtil.kr_i('✅ 从 protocols 解析 cipher: $method', tag: 'NodeList');
}
} }
} catch (e) { } catch (e) {
KRLogUtil.kr_w('解析 protocols 字段失败: $e', tag: 'NodeList'); KRLogUtil.kr_w('⚠️ 解析 protocols 字段失败: $e', tag: 'NodeList');
} }
} }
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
return KrNodeListItem( return KrNodeListItem(
id: _parseIntSafely(json['id']), id: _parseIntSafely(json['id']),
@@ -59,6 +59,8 @@ class UserInfoCard extends StatelessWidget {
children: [ children: [
Text( Text(
'ID: $userId', 'ID: $userId',
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 14.sp, fontSize: 14.sp,
@@ -12,6 +12,15 @@ class HINodeListController extends GetxController {
/// 首页服务 /// 首页服务
final KRHomeController homeController = Get.find<KRHomeController>(); final KRHomeController homeController = Get.find<KRHomeController>();
/// 调试模式状态
final RxBool isDebugMode = false.obs;
/// 模式按钮点击计数器
int modeButtonClickCount = 0;
/// 最后一次点击时间
DateTime? lastModeButtonClickTime;
/// 获取连接类型字符串 /// 获取连接类型字符串
String kr_getConnectionTypeString() { String kr_getConnectionTypeString() {
final connectionType = KRSingBoxImp.instance.kr_connectionType.value; final connectionType = KRSingBoxImp.instance.kr_connectionType.value;
@@ -41,4 +50,48 @@ class HINodeListController extends GetxController {
} }
} }
/// 处理模式按钮点击(用于激活调试模式)
void kr_handleModeButtonClick() {
final now = DateTime.now();
// 检查是否在2秒内连续点击
if (lastModeButtonClickTime != null &&
now.difference(lastModeButtonClickTime!).inSeconds > 2) {
// 超过2秒,重置计数器
modeButtonClickCount = 0;
}
modeButtonClickCount++;
lastModeButtonClickTime = now;
KRLogUtil.kr_i('模式按钮点击次数: $modeButtonClickCount', tag: 'HINodeListController');
if (modeButtonClickCount >= 5) {
// 激活调试模式
isDebugMode.value = true;
modeButtonClickCount = 0; // 重置计数器
KRLogUtil.kr_i('🐛 调试模式已激活!', tag: 'HINodeListController');
}
}
/// 获取要显示的节点列表(根据调试模式过滤)
List<dynamic> kr_getFilteredNodeList() {
if (isDebugMode.value) {
// 调试模式:显示所有节点
return kr_subscribeService.allList;
} else {
// 正常模式:只显示 country-auto 节点
return kr_subscribeService.allList.where((node) => node.tag.endsWith('-auto')).toList();
}
}
/// 重置调试模式
void kr_resetDebugMode() {
isDebugMode.value = false;
modeButtonClickCount = 0;
lastModeButtonClickTime = null;
KRLogUtil.kr_i('调试模式已重置', tag: 'HINodeListController');
}
} }
@@ -1,6 +1,5 @@
// hi_node_list_view.dart // hi_node_list_view.dart
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -48,17 +47,9 @@ class HINodeListView extends GetView<HINodeListController> {
) { ) {
if (outboundList.isEmpty) return 0; if (outboundList.isEmpty) return 0;
// 过滤掉不可用节点(延迟标记为 65535 或负数)
final validNodes = outboundList.where((node) {
final delay = node.urlTestDelay.value;
return delay > 0 && delay < 65535;
}).toList();
if (validNodes.isEmpty) return 0;
// 返回最小延迟值 // 返回最小延迟值
validNodes.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value)); outboundList.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
return validNodes.first.urlTestDelay.value; return outboundList.first.urlTestDelay.value;
} }
/// 找出延迟最低(最快)的节点对象 /// 找出延迟最低(最快)的节点对象
@@ -199,9 +190,20 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
...controller.kr_subscribeService.countryOutboundList.map((country) { ...controller.kr_subscribeService.countryOutboundList.map((country) {
return InkWell( return InkWell(
onTap: () { onTap: () async {
// 自动选择这个国家下的节点延迟中最快的 try {
controller.homeController.onCountrySelected(country.country);
final success =
await controller.homeController.kr_performNodeSwitch('${country.country}-auto');
print('node 点击 ${country.country} 节点数量${country.outboundList.length} 节点详情 ${country.outboundList}');
if (success) {
controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
}
} catch (e) {
KRLogUtil.kr_e('Auto选项切换异常: $e',
tag: 'NodeListView');
}
}, },
child: _kr_buildCountryListItem(context, country: country), child: _kr_buildCountryListItem(context, country: country),
); );
@@ -229,14 +231,24 @@ class HINodeListView extends GetView<HINodeListController> {
padding: EdgeInsets.symmetric(vertical: 8.w), padding: EdgeInsets.symmetric(vertical: 8.w),
// 2. 使用 children 属性,并一次性构建所有列表项 // 2. 使用 children 属性,并一次性构建所有列表项
children: [ children: [
if (controller.kr_subscribeService.allList.isEmpty) if (controller.kr_getFilteredNodeList().isEmpty)
_buildEmptyListPlaceholder(context, AppTranslations.kr_home.noNodes) _buildEmptyListPlaceholder(context,
controller.isDebugMode.value ? '调试模式:无节点数据' : AppTranslations.kr_home.noNodes)
else ...[ else ...[
InkWell( InkWell(
onTap: () { // 🔧 修复:改为 async,等待节点切换完成后再关闭列表
controller.homeController.kr_selectNode('auto'); onTap: () async {
try {
final success =
await controller.homeController.kr_performNodeSwitch('auto');
if (success) {
controller.homeController.kr_currentListStatus.value = controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none; KRHomeViewsListStatus.kr_none;
}
} catch (e) {
KRLogUtil.kr_e('Auto选项切换异常: $e',
tag: 'NodeListView');
}
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -281,13 +293,28 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
), ),
// 2. 第二个 Text: "根据网络IP自动匹配最快线路" // 2. 第二个 Text: "根据网络IP自动匹配最快线路"
Text( Obx(() {
'根据网络IP自动匹配最快线路', // 您指定的文本 // 当选择全局 auto 时,显示当前选中的节点信息
if (controller.homeController.kr_cutTag.value == 'auto') {
final autoNodeInfo = controller.homeController.kr_getGlobalAutoSelectedNode();
if (autoNodeInfo != null) {
return Text(
'当前: ${autoNodeInfo['tag']} (${autoNodeInfo['delay']}ms)',
style: KrAppTextStyle(
fontSize: 10,
color: Colors.white.withOpacity(0.8),
),
);
}
}
return Text(
'根据网络IP自动匹配最快线路', // 默认文本
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 10, fontSize: 10,
color: Colors.white, color: Colors.white,
), ),
), );
}),
], ],
), ),
), ),
@@ -308,9 +335,25 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
), ),
), ),
...controller.kr_subscribeService.allList.map((item) { ...controller.kr_getFilteredNodeList().map((item) {
return InkWell( return InkWell(
onTap: () => _onNodeSelected(item), // 🔧 修复:改为 async,等待节点切换完成后再关闭列表
onTap: () async {
try {
KRLogUtil.kr_i(
'🔄 用户点击节点: ${item.tag}');
final success = await controller.homeController
.kr_performNodeSwitch(item.tag);
if (success) {
controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
}
} catch (e) {
KRLogUtil.kr_e(
'节点切换异常: $e',
tag: 'NodeListView');
}
},
child: _kr_buildNodeListItem(context, item: item), child: _kr_buildNodeListItem(context, item: item),
); );
}).toList(), }).toList(),
@@ -344,15 +387,6 @@ class HINodeListView extends GetView<HINodeListController> {
); );
} }
/// 节点选中时的通用处理逻辑
void _onNodeSelected(KROutboundItem item) {
KRLogUtil.kr_i('Node selected: ${item.tag}');
KRSingBoxImp.instance.kr_selectOutbound(item.tag);
controller.homeController.kr_selectNode(item.tag);
// 切换回主页的仪表盘视图
controller.homeController.kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
}
/// 构建单个节点列表项的UI /// 构建单个节点列表项的UI
Widget _kr_buildNodeListItem(BuildContext context, {required KROutboundItem item}) { Widget _kr_buildNodeListItem(BuildContext context, {required KROutboundItem item}) {
return Container( return Container(
@@ -372,55 +406,75 @@ class HINodeListView extends GetView<HINodeListController> {
KRCountryFlag(countryCode: item.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false), KRCountryFlag(countryCode: item.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: Column( child: Obx(() {
crossAxisAlignment: CrossAxisAlignment.start, final isDebug = controller.isDebugMode.value;
children: [
Row( final text = isDebug
children: [ ? '${controller.homeController.kr_getCountryFullName(item.country)} - ${item.tag}'
Flexible( : '${controller.homeController.kr_getCountryFullName(item.country)}';
child: Text(
controller.homeController.kr_getCountryFullName(item.country),
style: KrAppTextStyle(fontSize: 14, color: Colors.white),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
Obx(() => controller.homeController.kr_cutTag.value == item.tag
? Container(
margin: EdgeInsets.only(left: 4.w),
padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.w),
decoration: BoxDecoration(
color: krModernGreenLight.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.w),
),
child: Text(
AppTranslations.kr_home.selected,
style: KrAppTextStyle(fontSize: 10, color: krModernGreen, fontWeight: FontWeight.w500),
),
)
: const SizedBox.shrink()),
],
),
SizedBox(height: 2.w),
Obx(() {
// 2. 获取用于显示的延迟值
final int delay = _getDisplayDelay(controller, item);
return Text( return Text(
'${delay}ms', text,
style: KrAppTextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white,
),
);
}),
),
Obx(() {
// 1. 获取延迟值和测速状态
int displayDelay;
bool isTesting = controller.homeController.kr_isLatency.value;
// 极简逻辑:只根据节点类型决定显示方式
print('🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}');
// 如果节点本身是auto组节点,显示组内最快节点的速度
if (item.tag.endsWith('-auto')) {
print('🎯 检测到auto组节点,获取最快节点信息');
final autoNodeInfo = controller.homeController.kr_getCountryAutoSelectedNode(item.country);
print('📊 _kr_buildNodeListItem获取auto最快节点: $autoNodeInfo');
displayDelay = autoNodeInfo?['delay'] ?? 0;
print('✅ _kr_buildNodeListItem使用auto最快节点延迟: $displayDelay');
} else {
// 普通节点,直接显示节点自身的速度
displayDelay = item.urlTestDelay.value;
print('🔄 _kr_buildNodeListItem使用普通节点延迟: $displayDelay');
}
// 2. 声明文本和颜色变量
String delayText;
Color delayColor;
// 3. 根据状态设置文本和颜色
if (isTesting && displayDelay == 0) {
delayText = '测速中...';
delayColor = Colors.grey; // 测速时使用灰色
} else if (displayDelay == 0) {
delayText = '- ms'; // 未测速或初始状态
delayColor = Colors.grey;
} else if (displayDelay >= 3000) {
delayText = AppTranslations.kr_home.timeout; // "超时"
delayColor = Colors.red; // 超时状态使用红色
} else {
delayText = '${displayDelay}ms';
// 正常延迟,根据快慢设置不同颜色
delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange;
}
// 4. 返回最终的 Text 组件
return Text(
delayText,
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 10, fontSize: 10,
color: krModernGreen, color: delayColor, // 使用动态计算出的颜色
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
); );
}), }),
Text( SizedBox(width: 12.w),
item.city,
style: KrAppTextStyle(fontSize: 12, color: Theme.of(context).textTheme.bodySmall?.color),
),
],
),
),
Obx(() => controller.homeController.kr_cutTag.value == item.tag Obx(() => controller.homeController.kr_cutTag.value == item.tag
? KrLocalImage( ? KrLocalImage(
imageName: 'radio-active-icon', imageName: 'radio-active-icon',
@@ -441,18 +495,7 @@ class HINodeListView extends GetView<HINodeListController> {
/// 构建国家列表项的UI /// 构建国家列表项的UI
Widget _kr_buildCountryListItem(BuildContext context, {required country}) { Widget _kr_buildCountryListItem(BuildContext context, {required country}) {
// 获取延迟颜色
Color getLatencyColor(int delay) {
if (delay == 0) {
return Colors.transparent;
} else if (delay < 500) {
return krModernGreen;
} else if (delay < 3000) {
return Color(0xFFFFB700); // 使用更容易看清的黄色
} else {
return Colors.red;
}
}
return Container( return Container(
key: ValueKey(country), key: ValueKey(country),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -476,22 +519,42 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
), ),
Obx(() { Obx(() {
final int delay = getFastestNodeDelay(controller, country.outboundList); // 1. 获取延迟值和测速状态
int displayDelay = getFastestNodeDelay(controller, country.outboundList);
bool isTesting = controller.homeController.kr_isLatency.value;
// 2. 声明文本和颜色变量
String delayText;
Color delayColor;
// 3. 根据状态设置文本和颜色
if (isTesting && displayDelay == 0) {
delayText = '测速中...';
delayColor = Colors.grey; // 测速时使用灰色
} else if (displayDelay == 0) {
delayText = '- ms'; // 未测速或初始状态
delayColor = Colors.grey;
} else if (displayDelay >= 3000) {
delayText = AppTranslations.kr_home.timeout; // "超时"
delayColor = Colors.red; // 超时状态使用红色
} else {
delayText = '${displayDelay}ms';
// 正常延迟,根据快慢设置不同颜色
delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange;
}
// 4. 返回最终的 Text 组件
return Text( return Text(
delay == 0 delayText,
? ''
: delay >= 3000
? AppTranslations.kr_home.timeout
: '${delay}ms',
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 10, fontSize: 10,
color: getLatencyColor(delay), color: delayColor, // 使用动态计算出的颜色
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
); );
}), }),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Obx(() => controller.homeController.kr_coutryText.value == country.country Obx(() => controller.homeController.kr_cutTag.value == '${country.country}-auto'
? KrLocalImage( ? KrLocalImage(
imageName: 'radio-active-icon', imageName: 'radio-active-icon',
imageType: ImageType.svg, imageType: ImageType.svg,
@@ -19,10 +19,47 @@ class HINodePageView extends GetView<HINodeListController> {
return HIBaseScaffold( return HIBaseScaffold(
child: Stack( child: Stack(
children: [ children: [
Positioned(
left: 0,
child: Obx(() => controller.isDebugMode.value
? GestureDetector(
onTap: () {
controller.kr_resetDebugMode();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.8),
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.bug_report,
size: 12.w,
color: Colors.white,
),
SizedBox(width: 3.w),
Text(
'关闭调试',
style: TextStyle(
fontSize: 10.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
],
),
),
)
: const SizedBox.shrink()),
),
// 主要内容区域 // 主要内容区域
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 模式切换器 // 模式切换器
Padding( Padding(
padding: EdgeInsets.only(left: 100.w, right: 60.w), padding: EdgeInsets.only(left: 100.w, right: 60.w),
@@ -159,7 +196,12 @@ class HINodePageView extends GetView<HINodeListController> {
}) { }) {
return Expanded( return Expanded(
child: GestureDetector( child: GestureDetector(
onTap: onTap, onTap: () {
// 处理模式按钮点击(用于调试模式激活)
controller.kr_handleModeButtonClick();
// 执行原有的点击逻辑
onTap();
},
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
padding: EdgeInsets.symmetric(vertical: 4.w), padding: EdgeInsets.symmetric(vertical: 4.w),
@@ -69,8 +69,12 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
children: [ children: [
Obx(() { Obx(() {
final account = KRAppRunData.getInstance().kr_account.value; final account = KRAppRunData.getInstance().kr_account.value;
final isDeviceLogin = account != null && account.startsWith('9000');
if (isDeviceLogin) return const SizedBox();
return Text( return Text(
(account != null && account.isNotEmpty) ? account : '未绑定', account ?? '',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20.sp, fontSize: 20.sp,
@@ -425,7 +429,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'设备:${_extractDeviceModel(userAgent)}', '设备:${_extractDeviceModel(userAgent, deviceType)}',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 10.sp, // fontSize: 10.sp, //
@@ -433,7 +437,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
'ID: ${identifier.substring(0, identifier.length > 4 ? 4 : identifier.length)}$id', 'SN: ${identifier.substring(0, identifier.length > 4 ? 4 : identifier.length)}$id',
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.9), color: Colors.white.withOpacity(0.9),
fontSize: 10.sp, fontSize: 10.sp,
@@ -585,12 +589,12 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
); );
} }
String _extractDeviceModel(String deviceName) { String _extractDeviceModel(String deviceName, String deviceType) {
// 匹配括号内内容 // 匹配括号内内容
final RegExp regExp = RegExp(r'\((.*?)\)'); final RegExp regExp = RegExp(r'\((.*?)\)');
final Match? match = regExp.firstMatch(deviceName); final Match? match = regExp.firstMatch(deviceName);
if (match != null && match.groupCount >= 1) { if (match != null && match.groupCount >= 1 ) {
// 获取括号内内容 // 获取括号内内容
final inside = match.group(1)!; // "Android; google Pixel 9; 15" final inside = match.group(1)!; // "Android; google Pixel 9; 15"
@@ -18,6 +18,7 @@ import '../../../common/app_config.dart';
import '../../../localization/app_translations.dart'; import '../../../localization/app_translations.dart';
import '../../../localization/kr_language_utils.dart'; import '../../../localization/kr_language_utils.dart';
import '../../../model/business/kr_group_outbound_list.dart'; import '../../../model/business/kr_group_outbound_list.dart';
import '../../../model/business/kr_outbound_item.dart';
import '../../../services/kr_announcement_service.dart'; import '../../../services/kr_announcement_service.dart';
import '../../../utils/kr_event_bus.dart'; import '../../../utils/kr_event_bus.dart';
import '../../../utils/kr_update_util.dart'; import '../../../utils/kr_update_util.dart';
@@ -133,11 +134,21 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
if (value != null) { if (value != null) {
isQuickConnectEnabled.value = value; isQuickConnectEnabled.value = value;
// 保存闪连状态到本地存储 // 保存闪连状态到本地存储
await _storage.kr_saveBool(key: _quickConnectKey, value: value); await _saveQuickConnectStatus(value);
KRLogUtil.kr_i('闪连状态已更新: $value', tag: 'QuickConnect'); KRLogUtil.kr_i('闪连状态已更新: $value', tag: 'QuickConnect');
} }
} }
// 保存闪连状态到本地存储
Future<void> _saveQuickConnectStatus(bool enabled) async {
try {
await _storage.kr_saveBool(key: _quickConnectKey, value: enabled);
KRLogUtil.kr_i('闪连状态已保存到本地存储: $enabled', tag: 'QuickConnect');
} catch (e) {
KRLogUtil.kr_e('保存闪连状态失败: $e', tag: 'QuickConnect');
}
}
// 从本地存储加载闪连状态 // 从本地存储加载闪连状态
Future<void> _loadQuickConnectStatus() async { Future<void> _loadQuickConnectStatus() async {
try { try {
@@ -839,6 +850,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
if (kDebugMode) { if (kDebugMode) {
} }
await KRSingBoxImp.instance.kr_start(); await KRSingBoxImp.instance.kr_start();
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController'); KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
if (kDebugMode) { if (kDebugMode) {
} }
@@ -1265,10 +1277,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
} }
} }
Future<void> onCountrySelected(String countryTag) async {
await KRSingBoxImp.instance.kr_selectCountry(countryTag);
KRCommonUtil.kr_showToast('已切换到 $countryTag 节点组');
}
/// 🔧 修复:简化的 kr_selectNode 方法 /// 🔧 修复:简化的 kr_selectNode 方法
/// 现在只是委托给新的 kr_performNodeSwitch 方法 /// 现在只是委托给新的 kr_performNodeSwitch 方法
@@ -1414,8 +1422,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
/// 获取真实连接的节点信息(auto 模式下获取实际连接的节点) /// 获取真实连接的节点信息(auto 模式下获取实际连接的节点)
Map<String, dynamic> kr_getRealConnectedNodeInfo() { Map<String, dynamic> kr_getRealConnectedNodeInfo() {
// 如果不是 auto 模式,直接返回当前选中的节点信息 // 如果不是 auto 模式,也不是 country-auto 模式,直接返回当前选中的节点信息
if (kr_cutTag.value != 'auto') { if (kr_cutTag.value != 'auto' && !kr_cutTag.value.endsWith('-auto')) {
final node = kr_subscribeService.keyList[kr_cutSeletedTag.value]; final node = kr_subscribeService.keyList[kr_cutSeletedTag.value];
return { return {
'nodeName': kr_cutSeletedTag.value, 'nodeName': kr_cutSeletedTag.value,
@@ -1424,11 +1432,25 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}; };
} }
// auto 模式下,获取 urltest 组的实际连接节点 // 处理 auto 模式(包括全局 auto 和 country-auto
print('当前活动组----${KRSingBoxImp.instance.kr_activeGroups.length}'); print('当前活动组----${KRSingBoxImp.instance.kr_activeGroups.length}');
for (var group in KRSingBoxImp.instance.kr_activeGroups) { for (var group in KRSingBoxImp.instance.kr_activeGroups) {
print('当前活动组----$group}'); print('当前活动组----$group}');
if (group.type == ProxyType.urltest) {
// 处理全局 auto 模式
if (kr_cutTag.value == 'auto' && group.type == ProxyType.urltest && group.tag == 'auto') {
final selectedNode = group.selected;
final node = kr_subscribeService.keyList[selectedNode];
return {
'nodeName': selectedNode,
'delay': node?.urlTestDelay.value ?? -2,
'country': node?.country ?? '',
};
}
// 处理 country-auto 模式
if (kr_cutTag.value.endsWith('-auto') && group.type == ProxyType.urltest) {
if (group.tag == kr_cutTag.value) {
final selectedNode = group.selected; final selectedNode = group.selected;
final node = kr_subscribeService.keyList[selectedNode]; final node = kr_subscribeService.keyList[selectedNode];
return { return {
@@ -1438,11 +1460,26 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}; };
} }
} }
}
print('hhhhhh${kr_subscribeService.keyList}', ); print('hhhhhh${kr_subscribeService.keyList}', );
// 处理 country-auto 模式的备用方案(当 SingBox 组数据不可用时)
if (kr_cutTag.value.endsWith('-auto')) {
final countryCode = kr_cutTag.value.replaceAll('-auto', '');
KRLogUtil.kr_i('🔄 kr_getRealConnectedNodeInfo 使用备用方案获取 country-auto 信息: $countryCode', tag: 'HomeController');
final autoNodeInfo = kr_getCountryAutoSelectedNode(countryCode);
if (autoNodeInfo != null) {
return {
'nodeName': autoNodeInfo['tag'],
'delay': autoNodeInfo['delay'] ?? -2,
'country': autoNodeInfo['country'] ?? countryCode,
};
}
}
// 如果没有找到 urltest 组,返回默认值 // 如果没有找到 urltest 组,返回默认值
return { return {
'nodeName': 'auto', 'nodeName': kr_cutTag.value,
'delay': -2, 'delay': -2,
'country': '', 'country': '',
}; };
@@ -1463,9 +1500,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
/// 获取真实连接的节点国家 /// 获取真实连接的节点国家
String kr_getRealConnectedNodeCountry() { String kr_getRealConnectedNodeCountry() {
final info = kr_getRealConnectedNodeInfo(); final info = kr_getRealConnectedNodeInfo();
final delay = kr_currentNodeLatency.value; final delay = info['delay'] as int; // 使用真实连接的节点延迟,而不是 kr_currentNodeLatency.value
final country1 = kr_getCurrentNodeCountry(); final country1 = kr_getCurrentNodeCountry();
print('country----$country1'); print('country----$country1');
print('kr_getRealConnectedNodeCountry - delay from info: $delay, country from info: ${info['country']}');
final country = kr_getCountryFullName(info['country']); final country = kr_getCountryFullName(info['country']);
if (delay == -2) { if (delay == -2) {
return '--'; return '--';
@@ -1932,7 +1970,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
final results = await KRLatencyTester.testMultipleNodes( final results = await KRLatencyTester.testMultipleNodes(
nodes: nodeAddresses, nodes: nodeAddresses,
concurrency: 10, // 每批10个并发 concurrency: 10, // 每批10个并发
timeout: 5000, // 超时时间5秒(毫秒) timeout: const Duration(seconds: 5),
); );
// 更新节点延迟 // 更新节点延迟
@@ -2173,6 +2211,225 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}); });
} }
/// 获取国家-auto组的当前选中子节点信息
Map<String, dynamic>? kr_getCountryAutoSelectedNode(String countryCode) {
try {
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
final allGroups = KRSingBoxImp.instance.kr_allGroups;
final autoGroupTag = '${countryCode}-auto';
KRLogUtil.kr_i('🔍 开始获取国家-auto选中节点: countryCode=$countryCode, autoGroupTag=$autoGroupTag', tag: 'HomeController');
KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}, 所有组数量: ${allGroups.length}', tag: 'HomeController');
// 优先检查活跃组
if (activeGroups.isNotEmpty) {
KRLogUtil.kr_i('✅ 活跃组不为空,优先检查活跃组', tag: 'HomeController');
for (var group in activeGroups) {
KRLogUtil.kr_i('🔄 检查活跃组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
if (group.tag == autoGroupTag && group.type == ProxyType.urltest) {
KRLogUtil.kr_i('✅ 在活跃组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController');
return _kr_extractSelectedNodeInfo(group, countryCode);
}
}
}
// 如果活跃组中没有,检查所有组
if (allGroups.isNotEmpty) {
KRLogUtil.kr_i('🔍 活跃组中未找到,检查所有组', tag: 'HomeController');
for (var group in allGroups) {
KRLogUtil.kr_i('🔄 检查所有组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
if (group.tag == autoGroupTag && group.type == ProxyType.urltest) {
KRLogUtil.kr_i('✅ 在所有组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController');
return _kr_extractSelectedNodeInfo(group, countryCode);
}
}
}
// 如果组数据都为空,使用备用方案:从订阅服务中找该国家最快的节点
KRLogUtil.kr_i('🔄 组数据为空,使用备用方案从订阅服务获取最快节点', tag: 'HomeController');
return _kr_getFastestNodeFromSubscribeService(countryCode);
} catch (e) {
KRLogUtil.kr_e('💥 获取国家-auto选中节点异常: $e', tag: 'HomeController');
}
return null;
}
/// 从订阅服务获取该国家最快节点的备用方案
Map<String, dynamic>? _kr_getFastestNodeFromSubscribeService(String countryCode) {
try {
KRLogUtil.kr_i('🔄 使用订阅服务查找国家最快节点: $countryCode', tag: 'HomeController');
// 从订阅服务中获取该国家的所有节点
final allNodes = kr_subscribeService.allList.where((node) =>
node.country == countryCode && !node.tag.endsWith('-auto')
).toList();
KRLogUtil.kr_i('📊 找到 ${allNodes.length} 个该国家的普通节点', tag: 'HomeController');
if (allNodes.isEmpty) {
KRLogUtil.kr_w('⚠️ 未找到该国家的任何普通节点: $countryCode', tag: 'HomeController');
return null;
}
// 找出延迟最小的节点(排除0和超时)
KROutboundItem? fastestNode;
int fastestDelay = 999999;
for (var node in allNodes) {
final delay = node.urlTestDelay.value;
if (delay > 0 && delay < 3000 && delay < fastestDelay) {
fastestDelay = delay;
fastestNode = node;
}
}
if (fastestNode != null) {
KRLogUtil.kr_i('✅ 找到最快节点: ${fastestNode.tag}, 延迟: ${fastestDelay}ms', tag: 'HomeController');
return {
'tag': fastestNode.tag,
'delay': fastestDelay,
'country': countryCode,
};
} else {
KRLogUtil.kr_w('⚠️ 该国家的节点都没有有效延迟数据', tag: 'HomeController');
return null;
}
} catch (e) {
KRLogUtil.kr_e('💥 获取订阅服务最快节点异常: $e', tag: 'HomeController');
return null;
}
}
/// 提取选中节点信息的辅助方法
Map<String, dynamic>? _kr_extractSelectedNodeInfo(dynamic group, String countryCode) {
try {
// 获取当前选中的节点
final selectedNode = group.selected;
KRLogUtil.kr_i('🎯 当前选中节点: $selectedNode', tag: 'HomeController');
KRLogUtil.kr_i('📋 组内项目数量: ${group.items.length}', tag: 'HomeController');
if (selectedNode != null && selectedNode.isNotEmpty) {
KRLogUtil.kr_i('✅ 选中节点有效,开始查找节点详情', tag: 'HomeController');
// 打印所有节点信息用于调试
for (var item in group.items) {
KRLogUtil.kr_i('📄 组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController');
}
// 在组内查找选中的节点
try {
final selectedItem = group.items.firstWhere(
(item) => item.tag == selectedNode,
);
KRLogUtil.kr_i('🎉 成功找到选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController');
return {
'tag': selectedNode,
'delay': selectedItem.urlTestDelay,
'country': countryCode,
};
} catch (e) {
KRLogUtil.kr_e('❌ 在组内未找到选中节点: $selectedNode', tag: 'HomeController');
return null;
}
} else {
KRLogUtil.kr_w('⚠️ 选中节点为空或无效', tag: 'HomeController');
return null;
}
} catch (e) {
KRLogUtil.kr_e('💥 提取选中节点信息异常: $e', tag: 'HomeController');
return null;
}
}
/// 获取全局 auto 组的当前选中子节点信息
Map<String, dynamic>? kr_getGlobalAutoSelectedNode() {
try {
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
KRLogUtil.kr_i('🌍 开始获取全局auto选中节点信息', tag: 'HomeController');
KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}', tag: 'HomeController');
for (var group in activeGroups) {
KRLogUtil.kr_i('🔄 检查全局组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
KRLogUtil.kr_i('📋 全局组内项目数量: ${group.items.length}', tag: 'HomeController');
if (group.tag == 'auto' && group.type == ProxyType.urltest) {
KRLogUtil.kr_i('✅ 找到全局auto urltest组', tag: 'HomeController');
// 获取当前选中的节点
final selectedNode = group.selected;
KRLogUtil.kr_i('🎯 全局auto当前选中节点: $selectedNode', tag: 'HomeController');
if (selectedNode != null && selectedNode.isNotEmpty) {
KRLogUtil.kr_i('✅ 全局auto选中节点有效,开始查找详情', tag: 'HomeController');
// 打印所有节点信息用于调试
for (var item in group.items) {
KRLogUtil.kr_i('📄 全局auto组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController');
}
// 在组内查找选中的节点
try {
final selectedItem = group.items.firstWhere(
(item) => item.tag == selectedNode,
);
KRLogUtil.kr_i('🎉 成功找到全局auto选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController');
return {
'tag': selectedNode,
'delay': selectedItem.urlTestDelay,
'country': '',
};
} catch (e) {
KRLogUtil.kr_e('❌ 在全局auto组内未找到选中节点: $selectedNode', tag: 'HomeController');
return null;
}
} else {
KRLogUtil.kr_w('⚠️ 全局auto选中节点为空或无效', tag: 'HomeController');
}
}
}
KRLogUtil.kr_w('❌ 未找到全局auto组', tag: 'HomeController');
} catch (e) {
KRLogUtil.kr_e('💥 获取全局auto选中节点异常: $e', tag: 'HomeController');
}
return null;
}
/// 获取指定国家的所有真实节点延迟列表
List<Map<String, dynamic>> kr_getCountryRealNodeDelays(String countryCode) {
final delays = <Map<String, dynamic>>[];
try {
// 从订阅服务中获取该国家的节点列表
final countryNodes = kr_subscribeService.keyList.values
.where((item) => item.country == countryCode && !item.tag.endsWith('-auto'))
.toList();
for (final node in countryNodes) {
delays.add({
'tag': node.tag,
'delay': node.urlTestDelay.value,
'city': node.city,
});
}
// 按延迟排序
delays.sort((a, b) => a['delay'].compareTo(b['delay']));
} catch (e) {
KRLogUtil.kr_e('获取国家真实节点延迟列表失败: $e', tag: 'HomeController');
}
return delays;
}
/// 尝试从活动组更新延迟值 /// 尝试从活动组更新延迟值
bool _kr_tryUpdateDelayFromActiveGroups() { bool _kr_tryUpdateDelayFromActiveGroups() {
try { try {
@@ -2198,6 +2455,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
} }
} }
} }
// 如果是国家-auto模式,从对应的国家urltest组获取延迟
else if (kr_cutTag.value.endsWith('-auto')) {
final countryCode = kr_cutTag.value.replaceAll('-auto', '');
for (var item in group.items) {
if (item.tag == kr_cutTag.value && item.urlTestDelay != 0) {
kr_currentNodeLatency.value = item.urlTestDelay;
KRLogUtil.kr_i('${countryCode}-auto模式延迟值: ${item.urlTestDelay}ms', tag: 'HomeController');
return true;
}
}
}
// 手动选择模式 // 手动选择模式
else { else {
for (var item in group.items) { for (var item in group.items) {
@@ -305,19 +305,24 @@ class KRLoginController extends GetxController
/// ///
void kr_sendCode() async { void kr_sendCode() async {
final either = await KRAuthApi().kr_sendCode( if (accountController.text.isEmpty) {
accountController.text, KRCommonUtil.kr_showToast(AppTranslations.kr_login.enterAccount);
2 return;
); // 3 }
/*
* int type;
* kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode final check = await KRAuthApi().kr_isRegister(accountController.text);
? 2 // 2 final result = check.fold((l) {
: 3*/ KRCommonUtil.kr_showToast(l.msg);
return null;
}, (isRegistered) => isRegistered ? 2 : 1);
if (result == null) return;
type = result;
final either = await KRAuthApi().kr_sendCode(accountController.text, type);
either.fold((l) { either.fold((l) {
KRCommonUtil.kr_showToast(l.msg); KRCommonUtil.kr_showToast(l.msg);
}, (r) async { }, (r) async {
///
_startCountdown(); _startCountdown();
}); });
} }
@@ -76,10 +76,13 @@ class KRLoginView extends GetView<KRLoginController> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Obx(() { Obx(() {
final _ = KRAppRunData.getInstance().kr_isLogin.value; final account = KRAppRunData.getInstance().kr_account.value;
final email = KRAppRunData.getInstance().kr_account.value ?? ''; final isDeviceLogin = account != null && account.startsWith('9000');
if (isDeviceLogin) return const SizedBox();
return Text( return Text(
email.isNotEmpty ? email : '', account ?? '',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20.sp, fontSize: 20.sp,
@@ -285,7 +285,7 @@ class KRSplashController extends GetxController {
KRLogUtil.kr_e('⏱️ 初始化超时: $e', tag: 'SplashController'); KRLogUtil.kr_e('⏱️ 初始化超时: $e', tag: 'SplashController');
print('⏱️ 初始化超时,直接跳转到主页'); print('⏱️ 初始化超时,直接跳转到主页');
// //
// Get.offAllNamed(Routes.KR_MAIN); // Get.offAllNamed(Routes.KR_HOME);
HIDialog.show( HIDialog.show(
message: '初始化超时,请检查网络或重试', message: '初始化超时,请检查网络或重试',
confirmText: '重试', confirmText: '重试',
@@ -484,7 +484,7 @@ class KRSplashController extends GetxController {
_initLog.logWarning('网络权限检查失败或超时,执行降级初始化', tag: 'Init'); _initLog.logWarning('网络权限检查失败或超时,执行降级初始化', tag: 'Init');
KRLogUtil.kr_w('⚠️ 网络权限检查失败或超时,执行降级初始化', tag: 'SplashController'); KRLogUtil.kr_w('⚠️ 网络权限检查失败或超时,执行降级初始化', tag: 'SplashController');
await _executeMinimalInitialization(); await _executeMinimalInitialization();
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
return; return;
} }
@@ -493,7 +493,7 @@ class KRSplashController extends GetxController {
_initLog.logError('网络权限检查异常,执行降级初始化', tag: 'Init', error: e); _initLog.logError('网络权限检查异常,执行降级初始化', tag: 'Init', error: e);
KRLogUtil.kr_w('⚠️ 网络权限检查异常: $e,执行降级初始化', tag: 'SplashController'); KRLogUtil.kr_w('⚠️ 网络权限检查异常: $e,执行降级初始化', tag: 'SplashController');
await _executeMinimalInitialization(); await _executeMinimalInitialization();
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
return; return;
} }
} else { } else {
@@ -582,7 +582,7 @@ class KRSplashController extends GetxController {
_initLog.logError('启动页初始化异常,执行降级策略', tag: 'Continue', error: e); _initLog.logError('启动页初始化异常,执行降级策略', tag: 'Continue', error: e);
KRLogUtil.kr_w('⚠️ 启动页初始化异常,执行降级策略: $e', tag: 'SplashController'); KRLogUtil.kr_w('⚠️ 启动页初始化异常,执行降级策略: $e', tag: 'SplashController');
await _executeMinimalInitialization(); await _executeMinimalInitialization();
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
} }
} }
@@ -726,7 +726,7 @@ class KRSplashController extends GetxController {
kr_isLoading.value = false; kr_isLoading.value = false;
// //
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
} }
/// 🔧 1.1DEBUG模式专用 /// 🔧 1.1DEBUG模式专用
+1 -1
View File
@@ -1,7 +1,7 @@
/// ///
abstract class Api { abstract class Api {
/// ///
static const String kr_isRegister = "/v1/app/auth/check"; static const String kr_isRegister = "/v1/auth/check";
/// ///
static const String kr_checkSubscription = "/v1/public/user/subscribe_status"; static const String kr_checkSubscription = "/v1/public/user/subscribe_status";
@@ -36,7 +36,7 @@ class KRAuthApi {
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance() BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
.request<KRIsRegister>(Api.kr_isRegister, data, .request<KRIsRegister>(Api.kr_isRegister, data,
method: HttpMethod.POST, isShowLoading: true); method: HttpMethod.GET, isShowLoading: true);
if (!baseResponse.isSuccess) { if (!baseResponse.isSuccess) {
return left( return left(
@@ -298,7 +298,7 @@ class KRAuthApi {
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance() BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
.request<dynamic>(Api.kr_deleteAccount, data, .request<dynamic>(Api.kr_deleteAccount, data,
method: HttpMethod.DELETE, isShowLoading: true); method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) { if (!baseResponse.isSuccess) {
return left( return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode)); HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
@@ -309,6 +309,7 @@ class KRSubscribeService {
// //
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList); KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.countryOutboundList);
// //
_kr_updateSubscribeStatus(); _kr_updateSubscribeStatus();
@@ -661,6 +662,8 @@ class KRSubscribeService {
// //
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList); KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.countryOutboundList);
// //
_kr_updateSubscribeStatus(); _kr_updateSubscribeStatus();
@@ -722,6 +725,8 @@ class KRSubscribeService {
// //
KRSingBoxImp.instance.kr_saveOutbounds([]); KRSingBoxImp.instance.kr_saveOutbounds([]);
KRSingBoxImp.instance.kr_saveAllOutbounds([]);
} }
/// ///
+524 -106
View File
@@ -12,15 +12,19 @@ import 'package:kaer_with_panels/singbox/service/singbox_service.dart';
import 'package:kaer_with_panels/singbox/service/singbox_service_provider.dart'; import 'package:kaer_with_panels/singbox/service/singbox_service_provider.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:device_info_plus/device_info_plus.dart';
import '../../../core/model/directories.dart'; import '../../../core/model/directories.dart';
import '../../../singbox/model/singbox_config_option.dart'; import '../../../singbox/model/singbox_config_option.dart';
import '../../../singbox/model/singbox_outbound.dart'; import '../../../singbox/model/singbox_outbound.dart';
import '../../../singbox/model/singbox_stats.dart'; import '../../../singbox/model/singbox_stats.dart';
import '../../../singbox/model/singbox_status.dart'; import '../../../singbox/model/singbox_status.dart';
import '../../model/business/kr_group_outbound_list.dart';
import '../../model/business/kr_outbound_item.dart';
import '../../utils/kr_country_util.dart'; import '../../utils/kr_country_util.dart';
import '../../utils/kr_log_util.dart'; import '../../utils/kr_log_util.dart';
import '../../utils/kr_secure_storage.dart'; import '../../utils/kr_secure_storage.dart';
import '../../utils/kr_windows_dns_util.dart';
import '../../common/app_run_data.dart'; import '../../common/app_run_data.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -64,6 +68,7 @@ class KRSingBoxImp {
Map<String, dynamic> kr_configOption = {}; Map<String, dynamic> kr_configOption = {};
List<Map<String, dynamic>> kr_outbounds = []; List<Map<String, dynamic>> kr_outbounds = [];
List<KRCountryOutboundList> Kr_allOutbounds = [];
/// ///
RxBool kr_isFristStart = false.obs; RxBool kr_isFristStart = false.obs;
@@ -107,6 +112,65 @@ class KRSingBoxImp {
/// ///
bool _kr_isInitialized = false; bool _kr_isInitialized = false;
/// Futuresingle-flight
Future<void>? _kr_initFuture;
/// Android模拟器
bool _kr_isAndroidEmulator = false;
///
///
/// - Android
/// - brand/model/product/manufacturer
/// `_kr_isAndroidEmulator`
Future<void> _kr_detectEmulator() async {
try {
if (!Platform.isAndroid) {
_kr_isAndroidEmulator = false;
return;
}
final info = await DeviceInfoPlugin().androidInfo;
final brand = (info.brand ?? '').toLowerCase();
final model = (info.model ?? '').toLowerCase();
final product = (info.product ?? '').toLowerCase();
final manufacturer = (info.manufacturer ?? '').toLowerCase();
final device = (info.device ?? '').toLowerCase();
bool containsAny(String s, List<String> keys) => keys.any((k) => s.contains(k));
final indicators = <bool>[
containsAny(brand, [
'generic', 'unknown', 'google', 'vbox', 'virtualbox',
'bluestacks', 'nox', 'ldplayer', 'genymotion', 'mumu', 'netease', 'leidian'
]),
containsAny(model, [
'emulator', 'sdk', 'sdk_gphone', 'google_sdk', 'android sdk built for x86',
'bluestacks', 'genymotion', 'nox', 'ldplayer', 'mumu'
]),
containsAny(product, [
'sdk', 'google_sdk', 'sdk_gphone', 'emulator', 'vbox', 'virtualbox'
]),
containsAny(manufacturer, [
'genymotion', 'unknown', 'bluestacks', 'nox', 'ld', 'netease', 'mumu'
]),
containsAny(device, [
'emulator', 'generic', 'vbox', 'virtualbox', 'sdk'
]),
];
_kr_isAndroidEmulator = indicators.any((v) => v);
KRLogUtil.kr_i(
'🔍 Android 模拟器检测: ${_kr_isAndroidEmulator ? '是模拟器' : '非模拟器'}'
' (brand=$brand, model=$model, product=$product, manufacturer=$manufacturer, device=$device)',
tag: 'SingBox',
);
} catch (e) {
_kr_isAndroidEmulator = false;
KRLogUtil.kr_w('⚠️ 模拟器检测失败,按真机处理: $e', tag: 'SingBox');
}
}
/// ///
bool get kr_isProxyReady => kr_status.value is SingboxStarted; bool get kr_isProxyReady => kr_status.value is SingboxStarted;
@@ -139,18 +203,32 @@ class KRSingBoxImp {
/// ///
Future<void> init() async { Future<void> init() async {
// //
if (_kr_isInitialized) { if (_kr_isInitialized) {
KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化', tag: 'SingBox'); KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化', tag: 'SingBox');
return; return;
} }
// Future
if (_kr_initFuture != null) {
KRLogUtil.kr_i('SingBox 初始化进行中,等待完成(single-flight', tag: 'SingBox');
await _kr_initFuture;
return;
}
// single-flight Future
final completer = Completer<void>();
_kr_initFuture = completer.future;
try { try {
KRLogUtil.kr_i('开始初始化 SingBox'); KRLogUtil.kr_i('开始初始化 SingBox');
// //
await KRCountryUtil.kr_init(); await KRCountryUtil.kr_init();
KRLogUtil.kr_i('国家工具初始化完成'); KRLogUtil.kr_i('国家工具初始化完成');
// Android模拟器
await _kr_detectEmulator();
final oOption = SingboxConfigOption.fromJson(_getConfigOption()); final oOption = SingboxConfigOption.fromJson(_getConfigOption());
KRLogUtil.kr_i('配置选项初始化完成'); KRLogUtil.kr_i('配置选项初始化完成');
@@ -410,12 +488,22 @@ class KRSingBoxImp {
); );
KRLogUtil.kr_i('✅ SingBox 初始化完成'); KRLogUtil.kr_i('✅ SingBox 初始化完成');
// 🔧 geosite workingDir
KRLogUtil.kr_i('📦 开始提取 geosite 文件...', tag: 'SingBox');
await _kr_extractGeositeFiles();
KRLogUtil.kr_i('✅ geosite 文件提取完成', tag: 'SingBox');
_kr_isInitialized = true; _kr_isInitialized = true;
// 🔑 // 🔑
// UI libcore // UI libcore
_kr_subscribeToStatus(); _kr_subscribeToStatus();
KRLogUtil.kr_i('✅ 状态订阅已设置', tag: 'SingBox'); KRLogUtil.kr_i('✅ 状态订阅已设置', tag: 'SingBox');
// single-flight
completer.complete();
_kr_initFuture = null;
} catch (e, stackTrace) { } catch (e, stackTrace) {
KRLogUtil.kr_e('❌ SingBox 初始化失败: $e'); KRLogUtil.kr_e('❌ SingBox 初始化失败: $e');
KRLogUtil.kr_e('📚 错误堆栈: $stackTrace'); KRLogUtil.kr_e('📚 错误堆栈: $stackTrace');
@@ -451,26 +539,89 @@ class KRSingBoxImp {
// //
_kr_isInitialized = false; _kr_isInitialized = false;
// single-flight
if (!completer.isCompleted) {
completer.completeError(e, stackTrace);
}
_kr_initFuture = null;
rethrow; rethrow;
} }
} }
/// geosite workingDir
Future<void> _kr_extractGeositeFiles() async {
try {
// geosite
final geositeDir = Directory(p.join(kr_configDics.workingDir.path, 'geosite'));
if (!geositeDir.existsSync()) {
await geositeDir.create(recursive: true);
KRLogUtil.kr_i('✅ 已创建 geosite 目录: ${geositeDir.path}', tag: 'SingBox');
}
//
final files = ['geoip-cn.srs', 'geosite-cn.srs'];
for (final filename in files) {
final assetPath = 'assets/geosite/$filename';
final targetPath = p.join(geositeDir.path, filename);
final targetFile = File(targetPath);
//
if (targetFile.existsSync()) {
final fileSize = await targetFile.length();
KRLogUtil.kr_i('📄 $filename 已存在 (${fileSize} bytes), 跳过', tag: 'SingBox');
continue;
}
// assets
KRLogUtil.kr_i('📥 正在提取 $filename...', tag: 'SingBox');
final byteData = await rootBundle.load(assetPath);
final bytes = byteData.buffer.asUint8List();
//
await targetFile.writeAsBytes(bytes);
final writtenSize = await targetFile.length();
KRLogUtil.kr_i('✅ 提取成功: $filename (${writtenSize} bytes)', tag: 'SingBox');
}
KRLogUtil.kr_i('🎉 所有 geosite 文件提取完成', tag: 'SingBox');
} catch (e, stackTrace) {
KRLogUtil.kr_e('❌ 提取 geosite 文件失败: $e', tag: 'SingBox');
KRLogUtil.kr_e('堆栈: $stackTrace', tag: 'SingBox');
// ,(使)
}
}
Map<String, dynamic> _getConfigOption() { Map<String, dynamic> _getConfigOption() {
// 使 // 使
// if (kr_configOption.isNotEmpty) { // if (kr_configOption.isNotEmpty) {
// return kr_configOption; // return kr_configOption;
// } // }
// 🔧 region 'other'libcore
final String effectiveRegion;
if (kr_connectionType.value == KRConnectionType.global) {
effectiveRegion = 'other'; //
KRLogUtil.kr_i('🌐 [全局代理模式] region 设为 other,所有流量走代理', tag: 'SingBox');
} else {
effectiveRegion = KRCountryUtil.kr_getCurrentCountryCode(); // 使
KRLogUtil.kr_i('✅ [智能代理模式] region 设为 $effectiveRegion', tag: 'SingBox');
}
final op = { final op = {
"region": "other", // hiddify-app: 使 "other" "region": effectiveRegion, // 🔧 region
"block-ads": false, // hiddify-app: 广 "block-ads": false, // hiddify-app: 广
"use-xray-core-when-possible": false, "use-xray-core-when-possible": false,
"execute-config-as-is": false, "execute-config-as-is": true, // 🔧 使 libcore
"log-level": "info", // 使 info warn "log-level": "info", // 使 info warn
"resolve-destination": false, "resolve-destination": false,
"ipv6-mode": "ipv4_only", // hiddify-app: 使 IPv4 (: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only) "ipv6-mode": "ipv4_only", // hiddify-app: 使 IPv4 (: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
"remote-dns-address": "udp://1.1.1.1", // hiddify-app: 使 Cloudflare DNS "remote-dns-address": _kr_isAndroidEmulator
? "local"
: "https://dns.google/dns-query",
"remote-dns-domain-strategy": "prefer_ipv4", "remote-dns-domain-strategy": "prefer_ipv4",
"direct-dns-address": "udp://1.1.1.1", // hiddify-app: 使 1.1.1.1 "direct-dns-address": "local",
"direct-dns-domain-strategy": "prefer_ipv4", "direct-dns-domain-strategy": "prefer_ipv4",
"mixed-port": kr_port, "mixed-port": kr_port,
"tproxy-port": kr_port, "tproxy-port": kr_port,
@@ -491,7 +642,15 @@ class KRSingBoxImp {
"enable-fake-dns": false, "enable-fake-dns": false,
"enable-dns-routing": true, "enable-dns-routing": true,
"independent-dns-cache": true, "independent-dns-cache": true,
"rules": [], "rules": [
// - HiddifyOptions.Rules
// Native config.BuildConfig()
// libcore domains 使 "domain:ip.sb" domain_suffix
{
"domains": "domain:api.hifast.biz", // domain: domain_suffix
"outbound": "bypass" // bypass = direct
}
],
"mux": { "mux": {
"enable": false, "enable": false,
"padding": false, "padding": false,
@@ -536,9 +695,61 @@ class KRSingBoxImp {
} }
}; };
kr_configOption = op; kr_configOption = op;
// 🔧
final rules = op["rules"] as List?;
KRLogUtil.kr_i('✅ HiddifyOptions 已生成,包含 ${rules?.length ?? 0} 条自定义路由规则', tag: 'SingBox');
if (rules != null && rules.isNotEmpty) {
for (var rule in rules) {
final ruleMap = rule as Map<String, dynamic>;
KRLogUtil.kr_i(' - 规则: domains=${ruleMap["domains"]}, outbound=${ruleMap["outbound"]}', tag: 'SingBox');
}
}
return op; return op;
} }
/// DNS
///
/// - 使 DoH + DNSfinal
/// - 使 DNSfinal DNS
/// Sing-box `dns` Map
Map<String, dynamic> _kr_buildDnsConfig() {
if (_kr_isAndroidEmulator) {
KRLogUtil.kr_i('🛡️ 模拟器兼容模式:强制使用系统 DNS', tag: 'SingBox');
return {
"servers": [
{
"tag": "dns-direct",
"address": "local",
"detour": "direct"
}
],
"rules": [],
"final": "dns-direct",
"strategy": "prefer_ipv4"
};
}
return {
"servers": [
{
"tag": "dns-remote",
"address": "https://1.1.1.1/dns-query",
"address_resolver": "dns-direct"
},
{
"tag": "dns-direct",
"address": "local",
"detour": "direct"
}
],
"rules": _kr_buildDnsRules(),
"final": "dns-remote",
"strategy": "prefer_ipv4"
};
}
/// ///
/// hiddify-app: libcore UI /// hiddify-app: libcore UI
void _kr_subscribeToStatus() { void _kr_subscribeToStatus() {
@@ -952,7 +1163,7 @@ class KRSingBoxImp {
await Future.delayed(const Duration(milliseconds: 2000)); await Future.delayed(const Duration(milliseconds: 2000));
final savedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode); final savedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
if (savedNode != null && savedNode.isNotEmpty && savedNode != 'auto') { if (savedNode != null && savedNode.isNotEmpty && savedNode != 'auto' && !savedNode.endsWith('-auto')) {
KRLogUtil.kr_i('🔄 恢复用户选择的节点: $savedNode', tag: 'SingBox'); KRLogUtil.kr_i('🔄 恢复用户选择的节点: $savedNode', tag: 'SingBox');
try { try {
@@ -993,7 +1204,14 @@ class KRSingBoxImp {
// print("错误堆栈: $stack"); // print("错误堆栈: $stack");
// } // }
// } // }
void kr_saveAllOutbounds(List<KRCountryOutboundList> outbounds) {
Kr_allOutbounds = outbounds;
KRLogUtil.kr_i('📊 保存国家分组: ${Kr_allOutbounds.length}', tag: 'SingBox');
for (int i = 0; i < Kr_allOutbounds.length; i++) {
final c = Kr_allOutbounds[i];
KRLogUtil.kr_i(' group[$i] country="${c.country}" outbounds=${c.outboundList.length}', tag: 'SingBox');
}
}
/// ///
void kr_saveOutbounds(List<Map<String, dynamic>> outbounds) async { void kr_saveOutbounds(List<Map<String, dynamic>> outbounds) async {
KRLogUtil.kr_i('💾 开始保存配置文件...', tag: 'SingBox'); KRLogUtil.kr_i('💾 开始保存配置文件...', tag: 'SingBox');
@@ -1010,6 +1228,9 @@ class KRSingBoxImp {
if (outbound['method'] != null) { if (outbound['method'] != null) {
KRLogUtil.kr_i(' - method: ${outbound['method']}', tag: 'SingBox'); KRLogUtil.kr_i(' - method: ${outbound['method']}', tag: 'SingBox');
} }
if (outbound['interval'] != null) {
KRLogUtil.kr_i(' - interval: ${outbound['interval']}', tag: 'SingBox');
}
if (outbound['password'] != null) { if (outbound['password'] != null) {
KRLogUtil.kr_i(' - password: ${outbound['password']?.toString().substring(0, 8)}...', tag: 'SingBox'); KRLogUtil.kr_i(' - password: ${outbound['password']?.toString().substring(0, 8)}...', tag: 'SingBox');
} }
@@ -1031,17 +1252,64 @@ class KRSingBoxImp {
KRLogUtil.kr_i('✅ 过滤后节点数量: ${kr_outbounds.length}/${outbounds.length}', tag: 'SingBox'); KRLogUtil.kr_i('✅ 过滤后节点数量: ${kr_outbounds.length}/${outbounds.length}', tag: 'SingBox');
// outboundsMobile.buildConfig() // 🔧 SingBox
final map = { // {"outbounds": [...]}, libcore
"outbounds": kr_outbounds //
final Map<String, dynamic> fullConfig = {
"log": {
"level": "debug",
"timestamp": true
},
"dns": _kr_buildDnsConfig(),
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "utun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"strict_route": true,
"sniff": true,
"sniff_override_destination": false
}
],
"outbounds": [
// 🔧 selector
{
"type": "selector",
"tag": "proxy",
"outbounds": kr_outbounds.map((o) => o['tag'] as String).toList(),
"default": kr_outbounds.isNotEmpty ? kr_outbounds[0]['tag'] : "direct",
},
...kr_outbounds,
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": _kr_buildRouteRules(), // 使
"rule_set": _kr_buildRuleSets(), // 使
"final": "proxy", // 🔧 使 selector
"auto_detect_interface": true
}
}; };
final file = _file(kr_configName); final file = _file(kr_configName);
final temp = _tempFile(kr_configName); final temp = _tempFile(kr_configName);
final mapStr = jsonEncode(map); final mapStr = jsonEncode(fullConfig);
KRLogUtil.kr_i('📄 配置文件内容长度: ${mapStr.length}', tag: 'SingBox'); KRLogUtil.kr_i('📄 完整配置文件长度: ${mapStr.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 完整配置文件内容: $mapStr', tag: 'SingBox'); KRLogUtil.kr_i('📄 Outbounds 数量: ${kr_outbounds.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 配置前800字符:\n${mapStr.substring(0, mapStr.length > 800 ? 800 : mapStr.length)}', tag: 'SingBox');
await file.writeAsString(mapStr); await file.writeAsString(mapStr);
await temp.writeAsString(mapStr); await temp.writeAsString(mapStr);
@@ -1058,6 +1326,128 @@ class KRSingBoxImp {
KRLogUtil.kr_i('✅ 配置文件保存完成', tag: 'SingBox'); KRLogUtil.kr_i('✅ 配置文件保存完成', tag: 'SingBox');
} }
/// DNS
List<Map<String, dynamic>> _kr_buildDnsRules() {
final currentCountryCode = KRCountryUtil.kr_getCurrentCountryCode();
final rules = <Map<String, dynamic>>[];
// 🔧 "智能代理"
// "全局代理"使
if (kr_connectionType.value == KRConnectionType.rule && currentCountryCode != 'other') {
rules.add({
"rule_set": [
"geoip-$currentCountryCode",
"geosite-$currentCountryCode",
],
"server": "dns-direct"
});
KRLogUtil.kr_i('✅ [智能代理模式] 添加 DNS 规则: $currentCountryCode 域名使用直连 DNS', tag: 'SingBox');
} else if (kr_connectionType.value == KRConnectionType.global) {
KRLogUtil.kr_i('🌐 [全局代理模式] 跳过国家 DNS 规则,所有DNS查询走代理', tag: 'SingBox');
}
return rules;
}
///
List<Map<String, dynamic>> _kr_buildRouteRules() {
final currentCountryCode = KRCountryUtil.kr_getCurrentCountryCode();
final rules = <Map<String, dynamic>>[];
// : DNS dns-out
rules.add({
"protocol": "dns",
"outbound": "dns-out"
});
//
// rules.add({
// "domain_suffix": ["ip138.com"],
// "outbound": "direct"
// });
// KRLogUtil.kr_i('✅ 添加自定义域名直连规则: ip138.com -> direct', tag: 'SingBox');
// 🔧 "智能代理"
// "全局代理"使
if (kr_connectionType.value == KRConnectionType.rule && currentCountryCode != 'other') {
rules.add({
"rule_set": [
"geoip-$currentCountryCode",
"geosite-$currentCountryCode",
],
"outbound": "direct"
});
KRLogUtil.kr_i('✅ [智能代理模式] 添加路由规则: $currentCountryCode IP/域名直连', tag: 'SingBox');
} else if (kr_connectionType.value == KRConnectionType.global) {
KRLogUtil.kr_i('🌐 [全局代理模式] 跳过国家路由规则,所有流量走代理', tag: 'SingBox');
}
return rules;
}
///
List<Map<String, dynamic>> _kr_buildRuleSets() {
final currentCountryCode = KRCountryUtil.kr_getCurrentCountryCode();
final ruleSets = <Map<String, dynamic>>[];
// 🔧 "智能代理"
// "全局代理"使
if (kr_connectionType.value == KRConnectionType.rule && currentCountryCode != 'other') {
//
final geoipFile = File(p.join(kr_configDics.workingDir.path, 'geosite', 'geoip-$currentCountryCode.srs'));
final geositeFile = File(p.join(kr_configDics.workingDir.path, 'geosite', 'geosite-$currentCountryCode.srs'));
if (geoipFile.existsSync() && geositeFile.existsSync()) {
// 使
ruleSets.add({
"type": "local",
"tag": "geoip-$currentCountryCode",
"format": "binary",
"path": "./geosite/geoip-$currentCountryCode.srs" // workingDir
});
ruleSets.add({
"type": "local",
"tag": "geosite-$currentCountryCode",
"format": "binary",
"path": "./geosite/geosite-$currentCountryCode.srs"
});
KRLogUtil.kr_i('✅ 使用本地规则集: $currentCountryCode', tag: 'SingBox');
KRLogUtil.kr_i(' - geoip: ./geosite/geoip-$currentCountryCode.srs', tag: 'SingBox');
KRLogUtil.kr_i(' - geosite: ./geosite/geosite-$currentCountryCode.srs', tag: 'SingBox');
} else {
// ,使
KRLogUtil.kr_w('⚠️ 本地规则集不存在,使用远程规则集', tag: 'SingBox');
KRLogUtil.kr_w(' - geoip 文件存在: ${geoipFile.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_w(' - geosite 文件存在: ${geositeFile.existsSync()}', tag: 'SingBox');
ruleSets.add({
"type": "remote",
"tag": "geoip-$currentCountryCode",
"format": "binary",
"url": "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geoip-$currentCountryCode.srs",
"download_detour": "direct",
"update_interval": "7d"
});
ruleSets.add({
"type": "remote",
"tag": "geosite-$currentCountryCode",
"format": "binary",
"url": "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geosite-$currentCountryCode.srs",
"download_detour": "direct",
"update_interval": "7d"
});
}
}
return ruleSets;
}
Future<void> kr_start() async { Future<void> kr_start() async {
// libcore status stream // libcore status stream
try { try {
@@ -1069,9 +1459,68 @@ class KRSingBoxImp {
// - v2.0-lazy-load // - v2.0-lazy-load
KRLogUtil.kr_i('🚀🚀🚀 [v2.0-lazy-load] 开始启动 SingBox...', tag: 'SingBox'); KRLogUtil.kr_i('🚀🚀🚀 [v2.0-lazy-load] 开始启动 SingBox...', tag: 'SingBox');
// 🔧
if (kr_outbounds.isNotEmpty) {
KRLogUtil.kr_i('🔄 启动前强制重新生成配置文件...', tag: 'SingBox');
final selectedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
KRLogUtil.kr_i('📊 国家分组数量: ${Kr_allOutbounds.length}', tag: 'SingBox');
for (int i = 0; i < Kr_allOutbounds.length; i++) {
final g = Kr_allOutbounds[i];
KRLogUtil.kr_i('Kr_allOutbounds[$i] country="${g.country}" outbounds=${g.outboundList.length}', tag: 'SingBox');
}
List<Map<String, dynamic>> toSave = [];
if (selectedNode != null && selectedNode.endsWith('-auto')) {
KRLogUtil.kr_i('🤖 检测到自动国家节点: $selectedNode', tag: 'SingBox');
String selectedCountry = selectedNode.replaceAll(RegExp(r'-auto$'), '');
final selectedCountryLower = selectedCountry.toLowerCase();
final matchedGroup = Kr_allOutbounds.firstWhere(
(g) => g.country.toLowerCase() == selectedCountryLower,
orElse: () => KRCountryOutboundList(country: '', outboundList: []),
);
if (matchedGroup.country.isNotEmpty) {
KRLogUtil.kr_i('🎯 命中分组: ${matchedGroup.country}, 节点数: ${matchedGroup.outboundList.length}', tag: 'SingBox');
toSave = matchedGroup.outboundList.map((it) => it.config).toList();
} else {
for (final g in Kr_allOutbounds) {
for (final it in g.outboundList) {
final tagLower = it.tag.toLowerCase();
if (tagLower == selectedNode.toLowerCase() || tagLower.contains(selectedCountryLower)) {
toSave.add(it.config);
}
}
}
}
KRLogUtil.kr_i('✅ 自动国家过滤: ${toSave.length}', tag: 'SingBox');
} else {
for (final g in Kr_allOutbounds) {
for (final it in g.outboundList) {
toSave.add(it.config);
}
}
}
kr_saveOutbounds(toSave);
await Future.delayed(const Duration(milliseconds: 100));
}
KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox'); KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox');
KRLogUtil.kr_i('📝 配置名称: $kr_configName', tag: 'SingBox'); KRLogUtil.kr_i('📝 配置名称: $kr_configName', tag: 'SingBox');
// 🔑 Windows DNS
if (Platform.isWindows) {
KRLogUtil.kr_i('🪟 Windows 平台,备份 DNS 设置...', tag: 'SingBox');
try {
final backupSuccess = await KRWindowsDnsUtil.instance.kr_backupDnsSettings();
if (backupSuccess) {
KRLogUtil.kr_i('✅ Windows DNS 备份成功', tag: 'SingBox');
} else {
KRLogUtil.kr_w('⚠️ Windows DNS 备份失败,将在停止时使用兜底恢复', tag: 'SingBox');
}
} catch (e) {
KRLogUtil.kr_w('⚠️ Windows DNS 备份异常: $e,将在停止时使用兜底恢复', tag: 'SingBox');
}
}
// 🔑 command.sock // 🔑 command.sock
// libcore "command.sock: no such file" // libcore "command.sock: no such file"
try { try {
@@ -1225,12 +1674,12 @@ class KRSingBoxImp {
await Future.delayed(const Duration(milliseconds: 100)); await Future.delayed(const Duration(milliseconds: 100));
// , stop() // , stop()
// // 🔧 10 Windows DNS
try { try {
await kr_singBox.stop().run().timeout( await kr_singBox.stop().run().timeout(
const Duration(seconds: 3), const Duration(seconds: 10), // 3 10
onTimeout: () { onTimeout: () {
KRLogUtil.kr_w('⚠️ 停止操作超时(3秒),强制继续', tag: 'SingBox'); KRLogUtil.kr_w('⚠️ 停止操作超时(10秒),强制继续', tag: 'SingBox');
return const Left('timeout'); return const Left('timeout');
}, },
); );
@@ -1239,7 +1688,29 @@ class KRSingBoxImp {
// //
} }
// 🔑 Windows DNS
if (Platform.isWindows) {
KRLogUtil.kr_i('🪟 Windows 平台,开始恢复 DNS 设置...', tag: 'SingBox');
// sing-box
await Future.delayed(const Duration(milliseconds: 1000));
try {
// DNS
final restoreSuccess = await KRWindowsDnsUtil.instance.kr_restoreDnsSettings();
if (restoreSuccess) {
KRLogUtil.kr_i('✅ Windows DNS 恢复成功', tag: 'SingBox');
} else {
KRLogUtil.kr_e('❌ Windows DNS 恢复失败', tag: 'SingBox');
}
} catch (e) {
KRLogUtil.kr_e('❌ Windows DNS 恢复异常: $e', tag: 'SingBox');
//
}
} else {
// Windows
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
}
// 便 // 便
final subscriptionsToCancel = _kr_subscriptions.where((sub) { final subscriptionsToCancel = _kr_subscriptions.where((sub) {
@@ -1261,6 +1732,17 @@ class KRSingBoxImp {
} catch (e, stackTrace) { } catch (e, stackTrace) {
KRLogUtil.kr_e('停止服务时出错: $e'); KRLogUtil.kr_e('停止服务时出错: $e');
KRLogUtil.kr_e('错误堆栈: $stackTrace'); KRLogUtil.kr_e('错误堆栈: $stackTrace');
// 🔑 使 Windows DNS
if (Platform.isWindows) {
KRLogUtil.kr_w('⚠️ 停止异常,强制执行 DNS 恢复', tag: 'SingBox');
try {
await KRWindowsDnsUtil.instance.kr_restoreDnsSettings();
} catch (dnsError) {
KRLogUtil.kr_e('❌ 强制 DNS 恢复失败: $dnsError', tag: 'SingBox');
}
}
// libcore // libcore
rethrow; rethrow;
} }
@@ -1284,10 +1766,25 @@ class KRSingBoxImp {
} }
Future<void> kr_restart() async { Future<void> kr_restart() async {
KRLogUtil.kr_i("restart"); KRLogUtil.kr_i('🔄 重启 SingBox...', tag: 'SingBox');
kr_singBox.restart(_cutPath, kr_configName, false).mapLeft((err) {
KRLogUtil.kr_e('重启失败: $err'); // 🔧 使 stop + start restart
}).run(); // Windows DNS
try {
// 1. DNS
await kr_stop();
// 2.
await Future.delayed(const Duration(milliseconds: 500));
// 3. DNS
await kr_start();
KRLogUtil.kr_i('✅ SingBox 重启完成', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ 重启失败: $e', tag: 'SingBox');
rethrow;
}
} }
//// ////
@@ -1429,9 +1926,11 @@ class KRSingBoxImp {
await _kr_ensureCommandClientInitialized(); await _kr_ensureCommandClientInitialized();
KRLogUtil.kr_i('✅ Command client 已就绪,执行节点切换', tag: 'SingBox'); KRLogUtil.kr_i('✅ Command client 已就绪,执行节点切换', tag: 'SingBox');
// 🔧 使 await // 🔧 使 group tag
KRLogUtil.kr_i('⏳ 调用 selectOutbound("select", "$tag")...', tag: 'SingBox'); // libcore selector组的tag是"proxy""select"
await _kr_selectOutboundWithRetry("select", tag, maxAttempts: 3, initialDelay: 50); final selectorGroupTag = kr_activeGroups.any((g) => g.tag == 'select') ? 'select' : 'proxy';
KRLogUtil.kr_i('⏳ 调用 selectOutbound("$selectorGroupTag", "$tag")...', tag: 'SingBox');
await _kr_selectOutboundWithRetry(selectorGroupTag, tag, maxAttempts: 3, initialDelay: 50);
KRLogUtil.kr_i('✅ 节点切换API调用完成: $tag', tag: 'SingBox'); KRLogUtil.kr_i('✅ 节点切换API调用完成: $tag', tag: 'SingBox');
// 🔧 // 🔧
@@ -1447,7 +1946,7 @@ class KRSingBoxImp {
// 🔄 auto // 🔄 auto
// urltest // urltest
_nodeSelectionTimer?.cancel(); _nodeSelectionTimer?.cancel();
if (tag != 'auto') { if (tag != 'auto' && !tag.endsWith('-auto')) {
KRLogUtil.kr_i('🔁 启动节点选择监控,防止被 auto 覆盖', tag: 'SingBox'); KRLogUtil.kr_i('🔁 启动节点选择监控,防止被 auto 覆盖', tag: 'SingBox');
_nodeSelectionTimer = Timer.periodic(const Duration(seconds: 20), (timer) { _nodeSelectionTimer = Timer.periodic(const Duration(seconds: 20), (timer) {
// 20 // 20
@@ -1465,87 +1964,6 @@ class KRSingBoxImp {
} }
} }
/// 🌍
/// 'auto' kr_selectOutbound('auto')
///
Future<void> kr_selectCountry(String country) async {
KRLogUtil.kr_i('🌎 [v2.1] 开始选择国家: $country', tag: 'SingBox');
if (country == 'auto') {
KRLogUtil.kr_i('🌀 国家为 auto,执行自动节点选择逻辑', tag: 'SingBox');
await kr_selectOutbound('auto');
return;
}
if (kr_activeGroups.isEmpty) {
KRLogUtil.kr_w('⚠️ kr_activeGroups 当前为空,无法进行国家选择', tag: 'SingBox');
return;
}
/*final selectGroup = kr_activeGroups.firstWhere(
(group) => group.tag == 'select',
orElse: () => throw Exception('未找到 "select" 组'),
);
final countryNodes = selectGroup.items.where((item) {
final nodeCountry = item.options?['country'] ?? '';
return nodeCountry.toString().toLowerCase() == country.toLowerCase();
}).toList();
if (countryNodes.isEmpty) {
KRLogUtil.kr_w('⚠️ 未找到该国家的节点: $country', tag: 'SingBox');
for (var item in selectGroup.items) {
KRLogUtil.kr_d(
' 可用节点: ${item.tag} (${item.options?['country'] ?? '未知'})',
tag: 'SingBox',
);
}
return;
}
KRLogUtil.kr_i('🇨🇭 找到 ${countryNodes.length} 个属于 "$country" 的节点', tag: 'SingBox');
// 9999
countryNodes.sort((a, b) =>
(a.urlTestDelay ?? 9999).compareTo(b.urlTestDelay ?? 9999));
final fastestNode = countryNodes.first;
final selectedTag = fastestNode.tag;
final delay = fastestNode.urlTestDelay ?? -1;
KRLogUtil.kr_i('✅ 选中该国家最快节点: $selectedTag (${delay}ms)', tag: 'SingBox');
try {
await KRSecureStorage().kr_saveData(key: 'selected_country', value: country);
await KRSecureStorage().kr_saveData(key: _keySelectedNode, value: selectedTag);
} catch (e) {
KRLogUtil.kr_w('⚠️ 保存国家/节点信息失败: $e', tag: 'SingBox');
}
try {
await _kr_ensureCommandClientInitialized();
await _kr_selectOutboundWithRetry('select', selectedTag,
maxAttempts: 3, initialDelay: 100);
KRLogUtil.kr_i('✅ 国家内节点切换成功: $selectedTag', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ 国家内节点选择失败: $e', tag: 'SingBox');
rethrow;
}
_nodeSelectionTimer?.cancel();
KRLogUtil.kr_i('🔁 启动定时器防止节点被 auto 覆盖', tag: 'SingBox');
_nodeSelectionTimer =
Timer.periodic(const Duration(seconds: 20), (timer) async {
try {
await kr_singBox.selectOutbound('select', selectedTag).run();
KRLogUtil.kr_d('🔁 定时确认国家内节点: $selectedTag', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_w('🔁 定时确认失败: $e', tag: 'SingBox');
}
});*/
}
/// ///
Directory get directory => Directory get directory =>
+4 -1
View File
@@ -15,7 +15,8 @@ enum KRCountry {
ru('俄罗斯'), ru('俄罗斯'),
id('印度尼西亚'), id('印度尼西亚'),
tr('土耳其'), tr('土耳其'),
br('巴西'); br('巴西'),
other('全局代理'); //
final String kr_name; final String kr_name;
const KRCountry(this.kr_name); const KRCountry(this.kr_name);
@@ -131,6 +132,8 @@ class KRCountryUtil {
return AppTranslations.kr_country.tr; return AppTranslations.kr_country.tr;
case KRCountry.br: case KRCountry.br:
return AppTranslations.kr_country.br; return AppTranslations.kr_country.br;
case KRCountry.other:
return '全局代理'; //
} }
} }
+116 -92
View File
@@ -1,127 +1,151 @@
import 'dart:io'; import 'dart:io';
import 'dart:async'; import 'dart:async';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'kr_log_util.dart';
/// ///
/// TCP
class KRLatencyTester { class KRLatencyTester {
/// /// TCP
/// /// 65535
/// : static Future<int> testTcpLatency({
/// - host:
/// - port:
/// - timeout:
///
/// :
/// - 65535
static Future<int> testNode({
required String host, required String host,
required int port, required int port,
int timeout = 5000, Duration timeout = const Duration(seconds: 5),
}) async { }) async {
try { Socket? socket;
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch();
final socket = await Socket.connect( try {
KRLogUtil.kr_i('🔌 开始测试: $host:$port', tag: 'LatencyTester');
stopwatch.start();
// TCP
socket = await Socket.connect(
host, host,
port, port,
timeout: Duration(milliseconds: timeout),
).timeout(Duration(milliseconds: timeout));
stopwatch.stop();
//
await socket.close();
socket.destroy();
final latency = stopwatch.elapsedMilliseconds;
KRLogUtil.kr_i('✅ 延迟测试成功: $host:$port = ${latency}ms', tag: 'KRLatencyTester');
return latency;
} catch (e) {
KRLogUtil.kr_w('❌ 延迟测试失败: $host:$port - $e', tag: 'KRLatencyTester');
return 65535; //
}
}
///
///
/// :
/// - nodes: [{"host": "example.com", "port": 443}]
/// - concurrency:
/// - timeout:
///
/// :
/// - "host:port"
static Future<Map<String, int>> testMultipleNodes({
required List<MapEntry<String, SocketAddress>> nodes,
int concurrency = 10,
int timeout = 5000,
}) async {
final results = <String, int>{};
final semaphore = Completer<void>();
var activeCount = 0;
var completedCount = 0;
KRLogUtil.kr_i('🚀 开始批量延迟测试,共 ${nodes.length} 个节点,并发数: $concurrency', tag: 'KRLatencyTester');
Future<void> processNode(MapEntry<String, SocketAddress> node) async {
try {
final host = node.value.address;
final port = node.value.port;
final key = node.key;
final latency = await testNode(
host: host,
port: port,
timeout: timeout, timeout: timeout,
); );
results[key] = latency; stopwatch.stop();
final latency = stopwatch.elapsedMilliseconds;
KRLogUtil.kr_i('✅ 测试成功: $host:$port - ${latency}ms', tag: 'LatencyTester');
return latency;
} on SocketException catch (e) {
stopwatch.stop();
KRLogUtil.kr_w('❌ 连接失败: $host:$port - ${e.message}', tag: 'LatencyTester');
return 65535;
} on TimeoutException catch (e) {
stopwatch.stop();
KRLogUtil.kr_w('⏱️ 连接超时: $host:$port - $e', tag: 'LatencyTester');
return 65535;
} catch (e) { } catch (e) {
results[node.key] = 65535; stopwatch.stop();
KRLogUtil.kr_e('节点测试异常: ${node.key} - $e', tag: 'KRLatencyTester'); KRLogUtil.kr_e('❌ 测试异常: $host:$port - $e', tag: 'LatencyTester');
return 65535;
} finally { } finally {
//
try {
await socket?.close();
} catch (e) {
//
}
}
}
///
/// Map<tag, ms>
static Future<Map<String, int>> testMultipleNodes({
required List<MapEntry<String, SocketAddress>> nodes,
int concurrency = 10, //
Duration timeout = const Duration(seconds: 5),
}) async {
final results = <String, int>{};
final List<List<MapEntry<String, SocketAddress>>> batches = [];
//
for (int i = 0; i < nodes.length; i += concurrency) {
batches.add(
nodes.sublist(i, i + concurrency > nodes.length ? nodes.length : i + concurrency)
);
}
KRLogUtil.kr_i('📊 开始批量测试: ${nodes.length} 个节点,分 ${batches.length} 批,每批 $concurrency', tag: 'LatencyTester');
int completedCount = 0;
//
for (int batchIndex = 0; batchIndex < batches.length; batchIndex++) {
final batch = batches[batchIndex];
KRLogUtil.kr_i('📦 测试第 ${batchIndex + 1}/${batches.length} 批(${batch.length} 个节点)', tag: 'LatencyTester');
//
final futures = batch.map((node) async {
final tag = node.key;
final address = node.value;
final latency = await testTcpLatency(
host: address.host,
port: address.port,
timeout: timeout,
);
completedCount++; completedCount++;
activeCount--;
if (completedCount >= nodes.length) { if (completedCount % 5 == 0 || completedCount == nodes.length) {
semaphore.complete(); KRLogUtil.kr_i('📈 测试进度: $completedCount/${nodes.length}', tag: 'LatencyTester');
}
}
} }
// return MapEntry(tag, latency);
for (var i = 0; i < nodes.length; i += concurrency) { }).toList();
final batch = nodes.skip(i).take(concurrency);
for (final node in batch) {
activeCount++;
processNode(node);
}
// //
if (i + concurrency < nodes.length) { final batchResults = await Future.wait(futures);
await Future.delayed(Duration(milliseconds: 100));
//
for (final result in batchResults) {
results[result.key] = result.value;
} }
} }
// //
await semaphore.future; final successCount = results.values.where((latency) => latency < 65535).length;
final failCount = results.length - successCount;
KRLogUtil.kr_i('✅ 批量测试完成', tag: 'LatencyTester');
KRLogUtil.kr_i('📊 成功: $successCount, 失败: $failCount', tag: 'LatencyTester');
// 3
final successNodes = results.entries
.where((e) => e.value < 65535)
.toList()
..sort((a, b) => a.value.compareTo(b.value));
if (successNodes.isNotEmpty) {
KRLogUtil.kr_i('🏆 延迟最低的前3个节点:', tag: 'LatencyTester');
for (int i = 0; i < 3 && i < successNodes.length; i++) {
KRLogUtil.kr_i(' ${i + 1}. ${successNodes[i].key}: ${successNodes[i].value}ms', tag: 'LatencyTester');
}
}
KRLogUtil.kr_i('✅ 批量延迟测试完成,成功: ${results.length}', tag: 'KRLatencyTester');
return results; return results;
} }
} }
/// Socket ///
///
class SocketAddress { class SocketAddress {
final String address; final String host;
final int port; final int port;
SocketAddress(this.address, this.port); SocketAddress(this.host, this.port);
@override @override
String toString() => '$address:$port'; String toString() => '$host:$port';
} }
+1 -1
View File
@@ -45,7 +45,7 @@ class KRWindowManager with WindowListener, TrayListener {
// Windows // Windows
if (Platform.isWindows) { if (Platform.isWindows) {
await windowManager.setTitleBarStyle(TitleBarStyle.normal); await windowManager.setTitleBarStyle(TitleBarStyle.normal);
await windowManager.setTitle('BearVPN'); await windowManager.setTitle('Hi快VPN');
await windowManager.setSize(const Size(800, 668)); await windowManager.setSize(const Size(800, 668));
await windowManager.setMinimumSize(const Size(800, 668)); await windowManager.setMinimumSize(const Size(800, 668));
await windowManager.center(); await windowManager.center();
+439
View File
@@ -0,0 +1,439 @@
import 'dart:io';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
/// Windows DNS
///
/// Windows DNS
///
/// 1. DNS
/// 2. DNS
/// 3. DNS (223.5.5.5 114.114.114.114)
class KRWindowsDnsUtil {
///
KRWindowsDnsUtil._();
///
static final KRWindowsDnsUtil _instance = KRWindowsDnsUtil._();
///
factory KRWindowsDnsUtil() => _instance;
///
static KRWindowsDnsUtil get instance => _instance;
/// DNS
List<String>? _originalDnsServers;
///
String? _primaryInterfaceName;
/// DNS
///
/// VPN DNS
/// true-false-
Future<bool> kr_backupDnsSettings() async {
if (!Platform.isWindows) {
KRLogUtil.kr_w('❌ 非 Windows 平台,跳过 DNS 备份', tag: 'WindowsDNS');
return false;
}
try {
KRLogUtil.kr_i('📦 开始备份 Windows DNS 设置...', tag: 'WindowsDNS');
// 1.
final interfaceName = await _kr_getPrimaryNetworkInterface();
if (interfaceName == null) {
KRLogUtil.kr_e('❌ 无法获取主网络接口', tag: 'WindowsDNS');
return false;
}
_primaryInterfaceName = interfaceName;
KRLogUtil.kr_i('🔍 主网络接口: $_primaryInterfaceName', tag: 'WindowsDNS');
// 2. DNS
final dnsServers = await _kr_getCurrentDnsServers(interfaceName);
if (dnsServers.isEmpty) {
KRLogUtil.kr_w('⚠️ 当前 DNS 为空,可能是自动获取', tag: 'WindowsDNS');
_originalDnsServers = []; // DHCP
} else {
_originalDnsServers = dnsServers;
KRLogUtil.kr_i('✅ 已备份 DNS: ${dnsServers.join(", ")}', tag: 'WindowsDNS');
}
return true;
} catch (e) {
KRLogUtil.kr_e('❌ 备份 DNS 设置失败: $e', tag: 'WindowsDNS');
return false;
}
}
/// DNS
///
/// VPN DNS
/// DNS
/// true-false-
Future<bool> kr_restoreDnsSettings() async {
if (!Platform.isWindows) {
KRLogUtil.kr_w('❌ 非 Windows 平台,跳过 DNS 恢复', tag: 'WindowsDNS');
return false;
}
try {
KRLogUtil.kr_i('🔄 开始恢复 Windows DNS 设置...', tag: 'WindowsDNS');
// 1.
if (_primaryInterfaceName == null) {
KRLogUtil.kr_w('⚠️ 没有备份的网络接口,尝试自动检测', tag: 'WindowsDNS');
_primaryInterfaceName = await _kr_getPrimaryNetworkInterface();
if (_primaryInterfaceName == null) {
KRLogUtil.kr_e('❌ 无法检测网络接口,执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
}
// 2. DNS
if (_originalDnsServers == null) {
KRLogUtil.kr_w('⚠️ 没有备份的 DNS,执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
if (_originalDnsServers!.isEmpty) {
// DHCP
KRLogUtil.kr_i('🔄 恢复为 DHCP 自动获取 DNS', tag: 'WindowsDNS');
final success = await _kr_setDnsToAuto(_primaryInterfaceName!);
if (!success) {
KRLogUtil.kr_w('⚠️ 恢复 DHCP 失败,执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
} else {
// DNS
KRLogUtil.kr_i('🔄 恢复原始 DNS: ${_originalDnsServers!.join(", ")}', tag: 'WindowsDNS');
final success = await _kr_setDnsServers(
_primaryInterfaceName!,
_originalDnsServers!,
);
if (!success) {
KRLogUtil.kr_w('⚠️ 恢复原始 DNS 失败,执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
}
// 3. DNS
await Future.delayed(const Duration(milliseconds: 500));
final currentDns = await _kr_getCurrentDnsServers(_primaryInterfaceName!);
KRLogUtil.kr_i('✅ 当前 DNS: ${currentDns.join(", ")}', tag: 'WindowsDNS');
// 4. DNS 127.0.0.1sing-box DNS
final hasLocalhost = currentDns.any((dns) => dns.startsWith('127.'));
if (hasLocalhost) {
KRLogUtil.kr_w('⚠️ DNS 仍包含 127.0.0.1,可能未完全恢复', tag: 'WindowsDNS');
KRLogUtil.kr_w('⚠️ 执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
return true;
} catch (e) {
KRLogUtil.kr_e('❌ 恢复 DNS 设置失败: $e', tag: 'WindowsDNS');
KRLogUtil.kr_w('⚠️ 执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
}
/// DNS
///
/// DNS
/// - DNS: 223.5.5.5 ()
/// - DNS: 114.114.114.114 (114DNS)
Future<bool> _kr_fallbackRestoreDns() async {
try {
KRLogUtil.kr_w('🆘 执行 DNS 兜底恢复机制', tag: 'WindowsDNS');
KRLogUtil.kr_i('🔧 设置为国内公共 DNS: 223.5.5.5, 114.114.114.114', tag: 'WindowsDNS');
// 1.
if (_primaryInterfaceName == null) {
_primaryInterfaceName = await _kr_getPrimaryNetworkInterface();
if (_primaryInterfaceName == null) {
KRLogUtil.kr_e('❌ 无法检测网络接口,兜底恢复失败', tag: 'WindowsDNS');
return false;
}
}
// 2. DNS
final fallbackDns = ['223.5.5.5', '114.114.114.114'];
final success = await _kr_setDnsServers(_primaryInterfaceName!, fallbackDns);
if (success) {
KRLogUtil.kr_i('✅ 兜底 DNS 设置成功', tag: 'WindowsDNS');
// 3.
await Future.delayed(const Duration(milliseconds: 500));
final currentDns = await _kr_getCurrentDnsServers(_primaryInterfaceName!);
KRLogUtil.kr_i('✅ 验证当前 DNS: ${currentDns.join(", ")}', tag: 'WindowsDNS');
return true;
} else {
KRLogUtil.kr_e('❌ 兜底 DNS 设置失败', tag: 'WindowsDNS');
return false;
}
} catch (e) {
KRLogUtil.kr_e('❌ 兜底恢复失败: $e', tag: 'WindowsDNS');
return false;
}
}
///
///
/// netsh "已连接"
/// null
Future<String?> _kr_getPrimaryNetworkInterface() async {
try {
// 使 netsh
final result = await Process.run('netsh', ['interface', 'show', 'interface']);
if (result.exitCode != 0) {
KRLogUtil.kr_e('❌ 获取网络接口失败: ${result.stderr}', tag: 'WindowsDNS');
return null;
}
final output = result.stdout.toString();
final lines = output.split('\n');
//
final connectedInterfaces = <String>[];
// "已连接"
// Windows
for (var line in lines) {
// : "已连接", : "Connected", "Enabled"
if (line.contains('已连接') ||
line.contains('Connected') ||
line.toLowerCase().contains('enabled')) {
//
if (line.contains('Admin') ||
line.contains('管理') ||
line.contains('State') ||
line.contains('状态')) {
continue;
}
//
final parts = line.trim().split(RegExp(r'\s{2,}'));
if (parts.length >= 4) {
final interfaceName = parts.last.trim();
//
if (interfaceName.isNotEmpty && interfaceName.length > 1) {
connectedInterfaces.add(interfaceName);
}
}
}
}
if (connectedInterfaces.isEmpty) {
KRLogUtil.kr_w('⚠️ 未找到已连接的网络接口', tag: 'WindowsDNS');
return null;
}
// 🔧 线 Wi-Fi
// 线
String? selectedInterface;
for (var interface in connectedInterfaces) {
final lowerName = interface.toLowerCase();
//
if (lowerName.contains('ethernet') ||
lowerName.contains('以太网') ||
lowerName.contains('lan') ||
lowerName.contains('local')) {
selectedInterface = interface;
KRLogUtil.kr_i('🔍 选择有线网络接口: $interface', tag: 'WindowsDNS');
break;
}
}
// 线 Wi-Fi
selectedInterface ??= connectedInterfaces.first;
if (selectedInterface != connectedInterfaces.first) {
KRLogUtil.kr_d('🔍 选择网络接口: $selectedInterface', tag: 'WindowsDNS');
} else {
KRLogUtil.kr_i('🔍 选择网络接口: $selectedInterface', tag: 'WindowsDNS');
}
return selectedInterface;
} catch (e) {
KRLogUtil.kr_e('❌ 获取网络接口异常: $e', tag: 'WindowsDNS');
return null;
}
}
/// DNS
///
///
/// - interfaceName:
///
/// DNS
Future<List<String>> _kr_getCurrentDnsServers(String interfaceName) async {
try {
final result = await Process.run('netsh', [
'interface',
'ipv4',
'show',
'dnsservers',
'name="$interfaceName"',
]);
if (result.exitCode != 0) {
KRLogUtil.kr_e('❌ 获取 DNS 失败: ${result.stderr}', tag: 'WindowsDNS');
return [];
}
final output = result.stdout.toString();
final dnsServers = <String>[];
// DNS
final lines = output.split('\n');
for (var line in lines) {
// IP
final ipMatch = RegExp(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b').firstMatch(line);
if (ipMatch != null) {
final ip = ipMatch.group(0)!;
//
if (!ip.startsWith('127.')) {
dnsServers.add(ip);
}
}
}
KRLogUtil.kr_d('🔍 当前 DNS: ${dnsServers.join(", ")}', tag: 'WindowsDNS');
return dnsServers;
} catch (e) {
KRLogUtil.kr_e('❌ 获取 DNS 异常: $e', tag: 'WindowsDNS');
return [];
}
}
/// DNS
///
///
/// - interfaceName:
/// - dnsServers: DNS
///
/// true-false-
Future<bool> _kr_setDnsServers(String interfaceName, List<String> dnsServers) async {
try {
if (dnsServers.isEmpty) {
KRLogUtil.kr_w('⚠️ DNS 列表为空,无法设置', tag: 'WindowsDNS');
return false;
}
// 1. DNS
KRLogUtil.kr_i('🔧 设置主 DNS: ${dnsServers[0]}', tag: 'WindowsDNS');
var result = await Process.run('netsh', [
'interface',
'ipv4',
'set',
'dnsservers',
'name="$interfaceName"',
'source=static',
'address=${dnsServers[0]}',
'validate=no',
]);
if (result.exitCode != 0) {
KRLogUtil.kr_e('❌ 设置主 DNS 失败: ${result.stderr}', tag: 'WindowsDNS');
return false;
}
// 2. DNS
if (dnsServers.length > 1) {
for (int i = 1; i < dnsServers.length; i++) {
KRLogUtil.kr_i('🔧 设置备用 DNS ${i}: ${dnsServers[i]}', tag: 'WindowsDNS');
result = await Process.run('netsh', [
'interface',
'ipv4',
'add',
'dnsservers',
'name="$interfaceName"',
'address=${dnsServers[i]}',
'index=${i + 1}',
'validate=no',
]);
if (result.exitCode != 0) {
KRLogUtil.kr_w('⚠️ 设置备用 DNS $i 失败: ${result.stderr}', tag: 'WindowsDNS');
//
}
}
}
// 3. DNS
await _kr_flushDnsCache();
KRLogUtil.kr_i('✅ DNS 服务器设置完成', tag: 'WindowsDNS');
return true;
} catch (e) {
KRLogUtil.kr_e('❌ 设置 DNS 异常: $e', tag: 'WindowsDNS');
return false;
}
}
/// DNS DHCP
///
///
/// - interfaceName:
///
/// true-false-
Future<bool> _kr_setDnsToAuto(String interfaceName) async {
try {
KRLogUtil.kr_i('🔧 设置 DNS 为自动获取 (DHCP)', tag: 'WindowsDNS');
final result = await Process.run('netsh', [
'interface',
'ipv4',
'set',
'dnsservers',
'name="$interfaceName"',
'source=dhcp',
]);
if (result.exitCode != 0) {
KRLogUtil.kr_e('❌ 设置 DHCP 失败: ${result.stderr}', tag: 'WindowsDNS');
return false;
}
// DNS
await _kr_flushDnsCache();
KRLogUtil.kr_i('✅ DNS 已设置为自动获取', tag: 'WindowsDNS');
return true;
} catch (e) {
KRLogUtil.kr_e('❌ 设置 DHCP 异常: $e', tag: 'WindowsDNS');
return false;
}
}
/// DNS
///
/// ipconfig /flushdns DNS
Future<void> _kr_flushDnsCache() async {
try {
KRLogUtil.kr_i('🔄 刷新 DNS 缓存...', tag: 'WindowsDNS');
final result = await Process.run('ipconfig', ['/flushdns']);
if (result.exitCode == 0) {
KRLogUtil.kr_i('✅ DNS 缓存已刷新', tag: 'WindowsDNS');
} else {
KRLogUtil.kr_w('⚠️ 刷新 DNS 缓存失败: ${result.stderr}', tag: 'WindowsDNS');
}
} catch (e) {
KRLogUtil.kr_w('⚠️ 刷新 DNS 缓存异常: $e', tag: 'WindowsDNS');
}
}
///
///
/// 退
void kr_clearBackup() {
_originalDnsServers = null;
_primaryInterfaceName = null;
KRLogUtil.kr_d('🗑️ 已清除 DNS 备份数据', tag: 'WindowsDNS');
}
}
+1 -1
View File
@@ -114,7 +114,7 @@ install(FILES "../libcore/bin/lib/libcore.so" DESTINATION "${INSTALL_BUNDLE_LIB_
install( install(
FILES "../libcore/bin/HiddifyCli" FILES "../libcore/bin/HiddifyCli"
DESTINATION "${CMAKE_INSTALL_PREFIX}" DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime RENAME BearVPNCli COMPONENT Runtime RENAME HiFastVPNCli
) )
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+3 -3
View File
@@ -7,7 +7,7 @@ export LD_LIBRARY_PATH=usr/lib
show_help() { show_help() {
cat << EOF cat << EOF
Usage: ${0##*/} ... Usage: ${0##*/} ...
start Hiddify or BearVPNCli, when no parameter is given, Hiddify is executed. start Hiddify or HiFastVPNCli, when no parameter is given, Hiddify is executed.
-v show version -v show version
EOF EOF
} }
@@ -30,8 +30,8 @@ else
# processing arguments # processing arguments
case $1 in case $1 in
BearVPNCli) HiFastVPNCli)
exec ./BearVPNCli ${@:3} exec ./HiFastVPNCli ${@:3}
exit 0 exit 0
;; ;;
h) h)
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# BearVPN macOS 公证脚本 # HiFastVPN macOS 公证脚本
# 作者: AI Assistant # 作者: AI Assistant
set -e set -e
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# BearVPN macOS 签名、公证和打包脚本 # HiFastVPN macOS 签名、公证和打包脚本
# 作者: AI Assistant # 作者: AI Assistant
# 日期: $(date) # 日期: $(date)
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# BearVPN macOS 签名和打包脚本 # HiFastVPN macOS 签名和打包脚本
# 作者: AI Assistant # 作者: AI Assistant
# 日期: $(date) # 日期: $(date)
+8 -8
View File
@@ -1,6 +1,6 @@
# Project-level configuration. # Project-level configuration.
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
project(BearVPN LANGUAGES CXX) project(HiFastVPN LANGUAGES CXX)
# CMake # CMake
# CMP0175: add_custom_command() flutter_inappwebview_windows # CMP0175: add_custom_command() flutter_inappwebview_windows
@@ -22,7 +22,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FS")
# The name of the executable created for the application. Change this to change # The name of the executable created for the application. Change this to change
# the on-disk name of your application. # the on-disk name of your application.
set(BINARY_NAME "BearVPN") set(BINARY_NAME "HiFastVPN")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake. # versions of CMake.
@@ -100,7 +100,7 @@ endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
# CLI BearVPNCli.exe # CLI HiFastVPNCli.exe
set(INSTALL_BUNDLE_CLI_DIR "${CMAKE_INSTALL_PREFIX}/cli") set(INSTALL_BUNDLE_CLI_DIR "${CMAKE_INSTALL_PREFIX}/cli")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
@@ -112,16 +112,16 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
# libcore.dll BearVPN.exe # libcore.dll HiFastVPN.exe
install(FILES "../libcore/bin/libcore.dll" install(FILES "../libcore/bin/libcore.dll"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime COMPONENT Runtime
OPTIONAL) OPTIONAL)
# BearVPNCli.exe libcore/bin # HiFastVPNCli.exe libcore/bin
# libcore HiddifyCli.exe BearVPNCli.exe # libcore HiddifyCli.exeHiFastVPNCli.exe
# BearVPNCli.exe # HiFastVPNCli.exe
install(FILES "../libcore/bin/BearVPNCli.exe" install(FILES "../libcore/bin/HiFastVPNCli.exe"
DESTINATION "${CMAKE_INSTALL_PREFIX}" DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime COMPONENT Runtime
OPTIONAL) OPTIONAL)
+1
View File
@@ -7,6 +7,7 @@ echo 正在复制必要的 DLL 文件...
copy "%VC_REDIST_DIR%\msvcp140.dll" "%DLL_DIR%" copy "%VC_REDIST_DIR%\msvcp140.dll" "%DLL_DIR%"
copy "%VC_REDIST_DIR%\vcruntime140.dll" "%DLL_DIR%" copy "%VC_REDIST_DIR%\vcruntime140.dll" "%DLL_DIR%"
copy "%VC_REDIST_DIR%\vcruntime140_1.dll" "%DLL_DIR%" copy "%VC_REDIST_DIR%\vcruntime140_1.dll" "%DLL_DIR%"
copy "%~dp0..\libcore\libcore.dll" "%DLL_DIR%"
echo DLL 文件复制完成! echo DLL 文件复制完成!
pause pause
+3 -3
View File
@@ -68,8 +68,8 @@ function InitializeSetup(): Boolean;
var var
ResultCode: Integer; ResultCode: Integer;
begin begin
Exec('taskkill', '/F /IM BearVPN.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) Exec('taskkill', '/F /IM HiFastVPN.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
Exec('net', 'stop "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) Exec('net', 'stop "HiFastVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
Exec('sc.exe', 'delete "BearVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) Exec('sc.exe', 'delete "HiFastVPNTunnelService"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
Result := True; Result := True;
end; end;
+5 -5
View File
@@ -1,11 +1,11 @@
app_id: 6L903538-42B1-4596-G479-BJ779F21A65E app_id: 6L903538-42B1-4596-G479-BJ779F21A65E
publisher: BearVPN publisher: HiFastVPN
publisher_url: https://github.com/hiddify/hiddify-next publisher_url: https://github.com/hiddify/hiddify-next
display_name: BearVPN display_name: Hi快VPN
executable_name: BearVPN.exe executable_name: HiFastVPN.exe
output_base_file_name: BearVPN.exe output_base_file_name: HiFastVPN.exe
create_desktop_icon: true create_desktop_icon: true
install_dir_name: "{autopf64}\\BearVPN" install_dir_name: "{autopf64}\\HiFastVPN"
setup_icon_file: ..\..\windows\runner\resources\app_icon.ico setup_icon_file: ..\..\windows\runner\resources\app_icon.ico
locales: locales:
- ar - ar
+6 -6
View File
@@ -89,13 +89,13 @@ BEGIN
BEGIN BEGIN
BLOCK "040904e4" BLOCK "040904e4"
BEGIN BEGIN
VALUE "CompanyName", "BearVPN" "\0" VALUE "CompanyName", "HiFastVPN" "\0"
VALUE "FileDescription", "BearVPN" "\0" VALUE "FileDescription", "HiFastVPN" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "app.baer.com" "\0" VALUE "InternalName", "HiFastVPN" "\0"
VALUE "LegalCopyright", "Copyright (C) 2024 BearVPN. All rights reserved." "\0" VALUE "LegalCopyright", "Copyright (C) 2024 HiFastVPN. All rights reserved." "\0"
VALUE "OriginalFilename", "BearVPN.exe" "\0" VALUE "OriginalFilename", "HiFastVPN.exe" "\0"
VALUE "ProductName", "BearVPN" "\0" VALUE "ProductName", "HiFastVPN" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"
END END
END END
+4 -4
View File
@@ -9,15 +9,15 @@
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) { _In_ wchar_t *command_line, _In_ int show_command) {
HANDLE hMutexInstance = CreateMutex(NULL, TRUE, L"BearVPNMutex"); HANDLE hMutexInstance = CreateMutex(NULL, TRUE, L"HiFastVPNMutex");
HWND handle = FindWindowA(NULL, "BearVPN"); HWND handle = FindWindowA(NULL, "HiFastVPN");
if (GetLastError() == ERROR_ALREADY_EXISTS) { if (GetLastError() == ERROR_ALREADY_EXISTS) {
flutter::DartProject project(L"data"); flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments = GetCommandLineArguments(); std::vector<std::string> command_line_arguments = GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project); FlutterWindow window(project);
if (window.SendAppLinkToInstance(L"BearVPN")) { if (window.SendAppLinkToInstance(L"HiFastVPN")) {
return false; return false;
} }
@@ -47,7 +47,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.Create(L"BearVPN", origin, size)) { if (!window.Create(L"HiFastVPN", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

+4 -4
View File
@@ -1,7 +1,7 @@
#define MyAppName "Kaer VPN" #define MyAppName "Hi快VPN"
#define MyAppVersion "1.0.0" #define MyAppVersion "1.0.0"
#define MyAppPublisher "Kaer" #define MyAppPublisher "HiFastVPN"
#define MyAppExeName "kaer_vpn.exe" #define MyAppExeName "HiFastVPN.exe"
[Setup] [Setup]
AppId={{YOUR-APP-ID-HERE} AppId={{YOUR-APP-ID-HERE}
@@ -11,7 +11,7 @@ AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}\{#MyAppName} DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
OutputDir=installer OutputDir=installer
OutputBaseFilename=BearVPN_Setup OutputBaseFilename=HiFastVPN_Setup
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
WizardStyle=modern WizardStyle=modern