import 'dart:io'; import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:get/get.dart'; import 'package:naiyouwl/app/bean/config.dart'; import 'package:naiyouwl/app/const/const.dart'; import 'package:yaml/yaml.dart'; import 'package:path/path.dart' as path; import 'package:flutter_emoji/flutter_emoji.dart'; final Map _defaultConfig = { 'selected': 'example.yaml', 'updateInterval': 86400, 'updateSubsAtStart': false, 'setSystemProxy': false, 'startAtLogin': false, 'breakConnections': false, 'language': 'zh_CN', 'port': 9899, 'subs': [], }; class ConfigController extends GetxController { late final dio; var config = Config.fromJson(_defaultConfig).obs; var clashCoreApiAddress = '127.0.0.1:9090'.obs; var clashCoreApiSecret = ''.obs; var clashCoreDns = ''.obs; var clashCoreTunEnable = false.obs; var servicePort = 0.obs; Future initConfig() async { var port = await getFreePort(); //dio.addSentry(); dio = Dio(BaseOptions(baseUrl: clashCoreApiAddress.value)); if (!await Paths.config.exists()) await Paths.config.create(recursive: true); if (!await Files.configCountryMmdb.exists()) await Files.assetsCountryMmdb.copy(Files.configCountryMmdb.path); if (Platform.isWindows && !await Files.configWintun.exists()) await Files.assetsWintun.copy(Files.configWintun.path); final locale = Get.deviceLocale!; _defaultConfig['language'] = '${locale.languageCode}_${locale.countryCode}'; if (await Files.configConfig.exists()) { final local = json.decode(await Files.configConfig.readAsString()); config.value = Config.fromJson({..._defaultConfig, ...local}); } else { config.value = Config.fromJson(_defaultConfig); } config.value.port = port; if (config.value.subs.isEmpty) { if (!await Files.configExample.exists()) await Files.assetsExample.copy(Files.configExample.path); config.value.subs.add(ConfigSub(name: 'example.yaml', url: '', updateTime: 0)); config.value.selected = 'example.yaml'; } await readClashCoreApi(); } Future save() async { await Files.configConfig.writeAsString(json.encode(config.toJson())); } Future readClashCoreApi() async { final configStr = await File(path.join(Paths.config.path, config.value.selected)).readAsString(); // final emoji = EmojiParser(); // final b = emoji.unemojify(_config); final configJson = loadYaml(configStr.replaceAll(EmojiParser.REGEX_EMOJI, 'emoji')); // print(_json["external-controller"]); // https://github.com/dart-lang/yaml/issues/53 // final _extControl = RegExp(r'''(? setLanguage(String language) async { config.value.language = language; await save(); config.refresh(); } Future setSystemProxy(bool open) async { config.value.setSystemProxy = open; await save(); config.refresh(); } Future setUpdateInterval(int value) async { config.value.updateInterval = value; await save(); config.refresh(); } Future setUpdateSubsAtStart(bool value) async { config.value.updateSubsAtStart = value; await save(); config.refresh(); } Future setSelectd(String selected) async { config.value.selected = selected; await save(); config.refresh(); } Future updateSub(ConfigSub sub) async { if ((sub.url ?? '').isEmpty) return false; final res = await dio.get(sub.url!); final subInfo = res.headers['subscription-userinfo']; final file = File(path.join(Paths.config.path, sub.name)); final oldConfig = await file.exists() ? await file.readAsString() : ''; final changed = oldConfig != res.data; if (changed) await file.writeAsString(res.data); sub.updateTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; sub.info = null; if (subInfo != null) { // final info = Map.fromEntries( // subInfo.first.split(RegExp(r';\s*')).where((s) => s.isNotEmpty).map((e) => e.split('=')).map((e) => MapEntry(e[0], int.parse(e[1])))); final entries = subInfo.first.split(RegExp(r';\s*')) .where((s) => s.isNotEmpty) .map((e) => e.split('=')) .toList(); final info = {}; for (var entry in entries) { info[entry[0]] = int.parse(entry[1]); } sub.info = ConfigSubInfo.fromJson(info); } await setSub(sub.name, sub); return changed; } Future setSub(String subName, ConfigSub sub) async { final idx = config.value.subs.indexWhere((it) => it.name == subName); config.value.subs[idx] = sub; if (subName != sub.name) { final file = File(path.join(Paths.config.path, subName)); if (await file.exists()) await file.rename(path.join(Paths.config.path, sub.name)); } await save(); config.refresh(); } Future addSub(ConfigSub sub) async { config.value.subs.add(sub); final file = File(path.join(Paths.config.path, sub.name)); if (!await file.exists()) await file.create(); await save(); config.refresh(); } Future deleteSub(String subName) async { final file = File(path.join(Paths.config.path, subName)); if (await file.exists()) await file.delete(); config.value.subs.removeWhere((it) => it.name == subName); await save(); config.refresh(); } Future setBreakConnections(bool value) async { config.value.breakConnections = value; await save(); config.refresh(); } Future getFreePort() async { var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); int port = server.port; await server.close(); return port; } }