|
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|
|
import 'dart:ffi' as ffi;
|
|
|
import 'dart:io';
|
|
|
import 'dart:isolate';
|
|
|
+import 'package:dio/dio.dart';
|
|
|
import 'package:naiyouwl/main.dart';
|
|
|
import 'package:dart_json_mapper/dart_json_mapper.dart';
|
|
|
import 'package:ffi/ffi.dart';
|
|
@@ -18,6 +19,7 @@ import 'package:path_provider/path_provider.dart';
|
|
|
import 'package:proxy_manager/proxy_manager.dart';
|
|
|
import 'package:tray_manager/tray_manager.dart';
|
|
|
import '../bean/clash_config_entity.dart';
|
|
|
+import '../data/model/NodeMode.dart';
|
|
|
late NativeLibrary clashFFI;
|
|
|
|
|
|
//android 或者ios
|
|
@@ -38,6 +40,9 @@ class ClashService extends GetxService with TrayListener {
|
|
|
final yamlConfigs = RxSet<FileSystemEntity>();
|
|
|
final currentYaml = 'config.yaml'.obs;
|
|
|
final proxyStatus = RxMap<String, int>();
|
|
|
+ final proxyYamlCurrent = "".obs;
|
|
|
+ final proxyYaml = 'proxy.yaml'.obs;
|
|
|
+
|
|
|
|
|
|
// action
|
|
|
static const ACTION_SET_SYSTEM_PROXY = "assr";
|
|
@@ -78,9 +83,9 @@ class ClashService extends GetxService with TrayListener {
|
|
|
_clashDirectory = await getApplicationSupportDirectory();
|
|
|
|
|
|
final _ = SpUtil.getData('yaml', defValue: currentYaml.value);
|
|
|
- initializedHttpPort = SpUtil.getData('http-port', defValue: 12346);
|
|
|
- initializedSockPort = SpUtil.getData('socks-port', defValue: 12347);
|
|
|
- initializedMixedPort = SpUtil.getData('mixed-port', defValue: 12348);
|
|
|
+ initializedHttpPort = SpUtil.getData('http-port', defValue: 7899);
|
|
|
+ initializedSockPort = SpUtil.getData('socks-port', defValue: 7877);
|
|
|
+ initializedMixedPort = SpUtil.getData('mixed-port', defValue: 7811);
|
|
|
currentYaml.value = _;
|
|
|
Request.setBaseUrl(clashBaseUrl);
|
|
|
final clashConfigPath = p.join(_clashDirectory.path, "clash");
|
|
@@ -100,6 +105,23 @@ class ClashService extends GetxService with TrayListener {
|
|
|
if (!mmdbF.existsSync()) {
|
|
|
await mmdbF.writeAsBytes(mmdb.buffer.asInt8List());
|
|
|
}
|
|
|
+ final countryGeoIP= p.join(_clashDirectory.path, 'geoip.dat');
|
|
|
+
|
|
|
+ final geoip = await rootBundle.load('assets/tp/clash/geoip.dat');
|
|
|
+ // write to clash dir
|
|
|
+ final geoipF = File(countryGeoIP);
|
|
|
+ if (!geoipF.existsSync()) {
|
|
|
+ await geoipF.writeAsBytes(geoip.buffer.asInt8List());
|
|
|
+ }
|
|
|
+ final countryGeoSite= p.join(_clashDirectory.path, 'geosite.dat');
|
|
|
+
|
|
|
+ final geoSite = await rootBundle.load('assets/tp/clash/geoip.dat');
|
|
|
+ // write to clash dir
|
|
|
+ final geoSiteF = File(countryGeoSite);
|
|
|
+ if (!geoSiteF.existsSync()) {
|
|
|
+ await geoSiteF.writeAsBytes(geoSite.buffer.asInt8List());
|
|
|
+ }
|
|
|
+
|
|
|
final config = await rootBundle.load('assets/tp/clash/config.yaml');
|
|
|
// write to clash dir
|
|
|
final configF = File(clashConf);
|
|
@@ -169,6 +191,78 @@ class ClashService extends GetxService with TrayListener {
|
|
|
// malloc.free(configPtr);
|
|
|
}
|
|
|
|
|
|
+ Future<void> chageProxyConfig() async {
|
|
|
+ final clashConf = p.join(_clashDirectory.path, proxyYaml.value);
|
|
|
+ final f = File(clashConf);
|
|
|
+ if (f.existsSync() && await changeYaml(f)) {
|
|
|
+ // set subscription
|
|
|
+ // await SpUtil.setData('profile_$name', url);
|
|
|
+ // await reload();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ String generateRules() {
|
|
|
+ return '''
|
|
|
+rules:
|
|
|
+ - GEOSITE,cn,DIRECT
|
|
|
+ - GEOIP,CN,DIRECT
|
|
|
+ - MATCH,proxy
|
|
|
+ ''';
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> makeClash(List<NodeMode> nodeModes) async {
|
|
|
+ var proxies = nodeModes.map(nodeToYaml).toList();
|
|
|
+
|
|
|
+ var config = '''
|
|
|
+port: 7891
|
|
|
+socks-port: 7890
|
|
|
+redir-port: 7893
|
|
|
+allow-lan: true
|
|
|
+mode: rule
|
|
|
+log-level: info
|
|
|
+ipv6: false
|
|
|
+unified-delay: false
|
|
|
+geodata-mode: true
|
|
|
+tcp-concurrent: false
|
|
|
+find-process-mode: strict
|
|
|
+global-client-fingerprint: chrome
|
|
|
+external-controller: 0.0.0.0:9090
|
|
|
+proxies:
|
|
|
+\n${proxies.join('\n')}
|
|
|
+proxy-groups:
|
|
|
+ - name: proxy
|
|
|
+ type: select
|
|
|
+ proxies:
|
|
|
+ ${nodeModes.map((node) => '- ${node.name}').join('\n ')}
|
|
|
+${generateRules()}
|
|
|
+ ''';
|
|
|
+ proxyYamlCurrent.value = config;
|
|
|
+ final clashConf = p.join(_clashDirectory.path, proxyYaml.value);
|
|
|
+ final configF = File(clashConf);
|
|
|
+ await configF.writeAsBytes(utf8.encode(config));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ String nodeToYaml(NodeMode node) {
|
|
|
+ const prefix = ' '; // 两个空格的缩进
|
|
|
+ switch (node.type) {
|
|
|
+ case 'trojan':
|
|
|
+ return '''$prefix- { name: ${node.name}, type: ${node.type}, server: ${node.host}, port: ${node.port}, password: ${node.passwd}, udp: 1 }''';
|
|
|
+ case 'shadowsocks':
|
|
|
+ return '''$prefix- { name: ${node.name}, type: ss, server: ${node.host}, port: ${node.port}, password: ${node.passwd}, cipher: ${node.method}, udp: 1 }''';
|
|
|
+ case 'v2ray':
|
|
|
+ final type = (node.vless == 1) ? 'vless' : 'vmess';
|
|
|
+ if (type == 'vless') {
|
|
|
+ return '''$prefix- { name: ${node.name}, type: $type, server: ${node.host}, port: ${node.port}, uuid: ${node.uuid}, alterId: ${node.v2AlterId}, udp: 1, flow: xtls-rprx-vision, servername: www.amazon.com, tls: true, reality-opts: { public-key: ${node.vlessPulkey} } }''';
|
|
|
+ } else {
|
|
|
+ return '''$prefix- { name: ${node.name}, type: $type, server: ${node.host}, port: ${node.port}, uuid: ${node.uuid}, alterId: ${node.v2AlterId}, cipher: ${node.method}, udp: 1 }''';
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
Future<void> reload() async {
|
|
|
// get configs
|
|
|
getConfigs();
|
|
@@ -185,31 +279,31 @@ class ClashService extends GetxService with TrayListener {
|
|
|
// await Future.delayed(const Duration(milliseconds: 500));
|
|
|
// }
|
|
|
// get traffic
|
|
|
- Timer.periodic(const Duration(seconds: 1), (t) {
|
|
|
- final trafficPtr = clashFFI.get_traffic().cast<Utf8>();
|
|
|
- final traffic = trafficPtr.toDartString();
|
|
|
- if (kDebugMode) {
|
|
|
- debugPrint(traffic);
|
|
|
- }
|
|
|
- try {
|
|
|
- final trafficJson = jsonDecode(traffic);
|
|
|
- uploadRate.value = trafficJson['Up'].toDouble() / 1024; // KB
|
|
|
- downRate.value = trafficJson['Down'].toDouble() / 1024; // KB
|
|
|
- // fix: 只有KDE不会导致Tray自动消失
|
|
|
- // final desktop = Platform.environment['XDG_CURRENT_DESKTOP'];
|
|
|
- // updateTray();
|
|
|
- } catch (e) {
|
|
|
- Get.printError(info: '$e');
|
|
|
- }
|
|
|
- // malloc.free(trafficPtr);
|
|
|
- });
|
|
|
- // system proxy
|
|
|
- // listen port
|
|
|
+ // Timer.periodic(const Duration(seconds: 1), (t) {
|
|
|
+ // final trafficPtr = clashFFI.get_traffic().cast<Utf8>();
|
|
|
+ // final traffic = trafficPtr.toDartString();
|
|
|
+ // if (kDebugMode) {
|
|
|
+ // debugPrint(traffic);
|
|
|
+ // }
|
|
|
+ // try {
|
|
|
+ // final trafficJson = jsonDecode(traffic);
|
|
|
+ // uploadRate.value = trafficJson['Up'].toDouble() / 1024; // KB
|
|
|
+ // downRate.value = trafficJson['Down'].toDouble() / 1024; // KB
|
|
|
+ // // fix: 只有KDE不会导致Tray自动消失
|
|
|
+ // // final desktop = Platform.environment['XDG_CURRENT_DESKTOP'];
|
|
|
+ // // updateTray();
|
|
|
+ // } catch (e) {
|
|
|
+ // Get.printError(info: '$e');
|
|
|
+ // }
|
|
|
+ // // malloc.free(trafficPtr);
|
|
|
+ // });
|
|
|
+ // // system proxy
|
|
|
+ // // listen port
|
|
|
await reload();
|
|
|
- checkPort();
|
|
|
- if (isSystemProxy()) {
|
|
|
- setSystemProxy();
|
|
|
- }
|
|
|
+ // checkPort();
|
|
|
+ // if (isSystemProxy()) {
|
|
|
+ // setSystemProxy();
|
|
|
+ // }
|
|
|
}
|
|
|
|
|
|
@override
|
|
@@ -357,7 +451,7 @@ class ClashService extends GetxService with TrayListener {
|
|
|
void getProxies() {
|
|
|
final proxiesPtr = clashFFI.get_proxies().cast<Utf8>();
|
|
|
proxies.value = json.decode(proxiesPtr.toDartString());
|
|
|
- // malloc.free(proxiesPtr);
|
|
|
+
|
|
|
}
|
|
|
|
|
|
bool isSystemProxy() {
|
|
@@ -514,7 +608,45 @@ class ClashService extends GetxService with TrayListener {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
+ Future<bool> addProfile(String name, String url) async {
|
|
|
+ final configName = '$name.yaml';
|
|
|
+ final newProfilePath = join(_clashDirectory.path, configName);
|
|
|
+ try {
|
|
|
+ final uri = Uri.tryParse(url);
|
|
|
+ if (uri == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ final resp = await Dio(BaseOptions(
|
|
|
+ headers: {'User-Agent': 'clash.meta'},
|
|
|
+ sendTimeout: 15000,
|
|
|
+ receiveTimeout: 15000))
|
|
|
+ .downloadUri(uri, newProfilePath, onReceiveProgress: (i, t) {
|
|
|
+ Get.printInfo(info: "$i/$t");
|
|
|
+ });
|
|
|
+ return resp.statusCode == 200;
|
|
|
+ } catch (e) {
|
|
|
+ //BrnToast.show("Error: ${e}", Get.context!);
|
|
|
+ } finally {
|
|
|
+ final f = File(newProfilePath);
|
|
|
+ if (f.existsSync() && await changeYaml(f)) {
|
|
|
+ // set subscription
|
|
|
+ await SpUtil.setData('profile_$name', url);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ Future<bool> deleteProfile(FileSystemEntity config) async {
|
|
|
+ if (config.existsSync()) {
|
|
|
+ config.deleteSync();
|
|
|
+ await SpUtil.remove('profile_${basename(config.path)}');
|
|
|
+ reload();
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
//切换配置文件
|
|
|
Future<bool> changeYaml(FileSystemEntity config) async {
|
|
|
try {
|
|
@@ -536,6 +668,19 @@ class ClashService extends GetxService with TrayListener {
|
|
|
}
|
|
|
return ret == 0;
|
|
|
}
|
|
|
+
|
|
|
+ bool isHideWindowWhenStart() {
|
|
|
+ return SpUtil.getData('boot_window_hide', defValue: false);
|
|
|
+ }
|
|
|
+
|
|
|
+ void handleSignal() {
|
|
|
+ StreamSubscription? subTerm;
|
|
|
+ subTerm = ProcessSignal.sigterm.watch().listen((event) {
|
|
|
+ subTerm?.cancel();
|
|
|
+ // _clashProcess?.kill();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
Future<String> convertConfig(String content) async {
|