2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:io';
|
|
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
|
|
|
|
import 'package:fl_clash/common/common.dart';
|
2025-07-31 17:09:18 +08:00
|
|
|
import 'package:fl_clash/core/controller.dart';
|
2024-11-09 20:17:57 +08:00
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
2024-06-19 13:13:31 +08:00
|
|
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2025-03-12 17:15:31 +08:00
|
|
|
import 'clash_config.dart';
|
|
|
|
|
|
2024-06-19 13:13:31 +08:00
|
|
|
part 'generated/profile.freezed.dart';
|
2024-11-09 20:17:57 +08:00
|
|
|
part 'generated/profile.g.dart';
|
2024-06-19 13:13:31 +08:00
|
|
|
|
2024-05-10 10:11:27 +08:00
|
|
|
typedef SelectedMap = Map<String, String>;
|
|
|
|
|
|
2024-06-19 13:13:31 +08:00
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class SubscriptionInfo with _$SubscriptionInfo {
|
2024-11-09 20:17:57 +08:00
|
|
|
const factory SubscriptionInfo({
|
2024-06-19 13:13:31 +08:00
|
|
|
@Default(0) int upload,
|
|
|
|
|
@Default(0) int download,
|
|
|
|
|
@Default(0) int total,
|
|
|
|
|
@Default(0) int expire,
|
2024-11-09 20:17:57 +08:00
|
|
|
}) = _SubscriptionInfo;
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-11-09 20:17:57 +08:00
|
|
|
factory SubscriptionInfo.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$SubscriptionInfoFromJson(json);
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-11-09 20:17:57 +08:00
|
|
|
factory SubscriptionInfo.formHString(String? info) {
|
|
|
|
|
if (info == null) return const SubscriptionInfo();
|
2025-06-07 01:48:34 +08:00
|
|
|
final list = info.split(';');
|
2024-04-30 23:38:49 +08:00
|
|
|
Map<String, int?> map = {};
|
2024-06-07 17:22:55 +08:00
|
|
|
for (final i in list) {
|
2025-06-07 01:48:34 +08:00
|
|
|
final keyValue = i.trim().split('=');
|
2024-04-30 23:38:49 +08:00
|
|
|
map[keyValue[0]] = int.tryParse(keyValue[1]);
|
|
|
|
|
}
|
2024-11-09 20:17:57 +08:00
|
|
|
return SubscriptionInfo(
|
2025-06-07 01:48:34 +08:00
|
|
|
upload: map['upload'] ?? 0,
|
|
|
|
|
download: map['download'] ?? 0,
|
|
|
|
|
total: map['total'] ?? 0,
|
|
|
|
|
expire: map['expire'] ?? 0,
|
2024-04-30 23:38:49 +08:00
|
|
|
);
|
|
|
|
|
}
|
2024-06-19 13:13:31 +08:00
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-06-19 13:13:31 +08:00
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class Profile with _$Profile {
|
2024-06-19 13:13:31 +08:00
|
|
|
const factory Profile({
|
|
|
|
|
required String id,
|
|
|
|
|
String? label,
|
|
|
|
|
String? currentGroupName,
|
2025-06-07 01:48:34 +08:00
|
|
|
@Default('') String url,
|
2024-06-19 13:13:31 +08:00
|
|
|
DateTime? lastUpdateDate,
|
|
|
|
|
required Duration autoUpdateDuration,
|
2024-11-09 20:17:57 +08:00
|
|
|
SubscriptionInfo? subscriptionInfo,
|
2024-06-19 13:13:31 +08:00
|
|
|
@Default(true) bool autoUpdate,
|
|
|
|
|
@Default({}) SelectedMap selectedMap,
|
2024-06-23 00:26:24 +08:00
|
|
|
@Default({}) Set<String> unfoldSet,
|
2025-03-12 17:15:31 +08:00
|
|
|
@Default(OverrideData()) OverrideData overrideData,
|
2024-08-04 08:21:14 +08:00
|
|
|
@JsonKey(includeToJson: false, includeFromJson: false)
|
|
|
|
|
@Default(false)
|
|
|
|
|
bool isUpdating,
|
2024-06-19 13:13:31 +08:00
|
|
|
}) = _Profile;
|
|
|
|
|
|
|
|
|
|
factory Profile.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$ProfileFromJson(json);
|
|
|
|
|
|
2025-07-31 17:09:18 +08:00
|
|
|
factory Profile.normal({String? label, String url = ''}) {
|
2024-06-19 13:13:31 +08:00
|
|
|
return Profile(
|
|
|
|
|
label: label,
|
|
|
|
|
url: url,
|
|
|
|
|
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
|
|
|
|
autoUpdateDuration: defaultUpdateDuration,
|
|
|
|
|
);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-12 17:15:31 +08:00
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class OverrideData with _$OverrideData {
|
2025-03-12 17:15:31 +08:00
|
|
|
const factory OverrideData({
|
|
|
|
|
@Default(false) bool enable,
|
|
|
|
|
@Default(OverrideRule()) OverrideRule rule,
|
|
|
|
|
}) = _OverrideData;
|
|
|
|
|
|
|
|
|
|
factory OverrideData.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$OverrideDataFromJson(json);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension OverrideDataExt on OverrideData {
|
|
|
|
|
List<String> get runningRule {
|
|
|
|
|
if (!enable) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
return rule.rules.map((item) => item.value).toList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@freezed
|
2025-07-31 17:09:18 +08:00
|
|
|
abstract class OverrideRule with _$OverrideRule {
|
2025-03-12 17:15:31 +08:00
|
|
|
const factory OverrideRule({
|
|
|
|
|
@Default(OverrideRuleType.added) OverrideRuleType type,
|
|
|
|
|
@Default([]) List<Rule> overrideRules,
|
|
|
|
|
@Default([]) List<Rule> addedRules,
|
|
|
|
|
}) = _OverrideRule;
|
|
|
|
|
|
|
|
|
|
factory OverrideRule.fromJson(Map<String, Object?> json) =>
|
|
|
|
|
_$OverrideRuleFromJson(json);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension OverrideRuleExt on OverrideRule {
|
|
|
|
|
List<Rule> get rules => switch (type == OverrideRuleType.override) {
|
2025-07-31 17:09:18 +08:00
|
|
|
true => overrideRules,
|
|
|
|
|
false => addedRules,
|
|
|
|
|
};
|
2025-03-12 17:15:31 +08:00
|
|
|
|
|
|
|
|
OverrideRule updateRules(List<Rule> Function(List<Rule> rules) builder) {
|
|
|
|
|
if (type == OverrideRuleType.added) {
|
|
|
|
|
return copyWith(addedRules: builder(addedRules));
|
|
|
|
|
}
|
|
|
|
|
return copyWith(overrideRules: builder(overrideRules));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
|
|
|
|
|
extension ProfilesExt on List<Profile> {
|
|
|
|
|
Profile? getProfile(String? profileId) {
|
|
|
|
|
final index = indexWhere((profile) => profile.id == profileId);
|
|
|
|
|
return index == -1 ? null : this[index];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 13:13:31 +08:00
|
|
|
extension ProfileExtension on Profile {
|
2024-06-07 17:22:55 +08:00
|
|
|
ProfileType get type =>
|
2024-06-19 13:13:31 +08:00
|
|
|
url.isEmpty == true ? ProfileType.file : ProfileType.url;
|
|
|
|
|
|
2024-08-04 08:21:14 +08:00
|
|
|
bool get realAutoUpdate => url.isEmpty == true ? false : autoUpdate;
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-06-03 18:02:05 +08:00
|
|
|
Future<void> checkAndUpdate() async {
|
2024-05-20 15:15:09 +08:00
|
|
|
final isExists = await check();
|
2024-06-03 18:02:05 +08:00
|
|
|
if (!isExists) {
|
2024-06-19 13:13:31 +08:00
|
|
|
if (url.isNotEmpty) {
|
|
|
|
|
await update();
|
2024-05-20 15:15:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
Future<bool> check() async {
|
|
|
|
|
final profilePath = await appPath.getProfilePath(id);
|
2025-05-02 02:24:12 +08:00
|
|
|
return await File(profilePath).exists();
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
Future<File> getFile() async {
|
|
|
|
|
final path = await appPath.getProfilePath(id);
|
2025-05-02 02:24:12 +08:00
|
|
|
final file = File(path);
|
2024-12-03 21:47:12 +08:00
|
|
|
final isExists = await file.exists();
|
|
|
|
|
if (!isExists) {
|
|
|
|
|
await file.create(recursive: true);
|
|
|
|
|
}
|
|
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<int> get profileLastModified async {
|
|
|
|
|
final file = await getFile();
|
|
|
|
|
return (await file.lastModified()).microsecondsSinceEpoch;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 13:13:31 +08:00
|
|
|
Future<Profile> update() async {
|
|
|
|
|
final response = await request.getFileResponseForUrl(url);
|
2025-06-07 01:48:34 +08:00
|
|
|
final disposition = response.headers.value('content-disposition');
|
2024-06-19 13:13:31 +08:00
|
|
|
final userinfo = response.headers.value('subscription-userinfo');
|
|
|
|
|
return await copyWith(
|
2025-04-09 16:46:14 +08:00
|
|
|
label: label ?? utils.getFileNameForDisposition(disposition) ?? id,
|
2024-11-09 20:17:57 +08:00
|
|
|
subscriptionInfo: SubscriptionInfo.formHString(userinfo),
|
2024-06-19 13:13:31 +08:00
|
|
|
).saveFile(response.data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<Profile> saveFile(Uint8List bytes) async {
|
2025-09-23 21:02:47 +08:00
|
|
|
final message = await coreController.validateConfigFormBytes(bytes);
|
2024-06-03 18:02:05 +08:00
|
|
|
if (message.isNotEmpty) {
|
|
|
|
|
throw message;
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
final file = await getFile();
|
2024-04-30 23:38:49 +08:00
|
|
|
await file.writeAsBytes(bytes);
|
2024-06-19 13:13:31 +08:00
|
|
|
return copyWith(lastUpdateDate: DateTime.now());
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|