Add extra profile metadata
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'profile.freezed.dart';
|
||||
part 'profile.g.dart';
|
||||
@@ -13,13 +16,127 @@ class Profile with _$Profile {
|
||||
required bool active,
|
||||
required String name,
|
||||
required String url,
|
||||
SubscriptionInfo? subInfo,
|
||||
Duration? updateInterval,
|
||||
required DateTime lastUpdate,
|
||||
ProfileOptions? options,
|
||||
SubscriptionInfo? subInfo,
|
||||
ProfileExtra? extra,
|
||||
}) = _Profile;
|
||||
|
||||
bool get hasSubscriptionInfo => subInfo?.isValid ?? false;
|
||||
// TODO add content disposition parsing
|
||||
factory Profile.fromResponse(
|
||||
String url,
|
||||
Map<String, List<String>> headers,
|
||||
) {
|
||||
final titleHeader = headers['profile-title']?.single;
|
||||
var title = '';
|
||||
if (titleHeader != null) {
|
||||
if (titleHeader.startsWith("base64:")) {
|
||||
// TODO handle errors
|
||||
title =
|
||||
utf8.decode(base64.decode(titleHeader.replaceFirst("base64:", "")));
|
||||
} else {
|
||||
title = titleHeader;
|
||||
}
|
||||
}
|
||||
if (title.isEmpty) {
|
||||
final part = url.split("/").lastOrNull;
|
||||
if (part != null) {
|
||||
final pattern = RegExp(r"\.(yaml|yml|txt)[\s\S]*");
|
||||
title = part.replaceFirst(pattern, "");
|
||||
}
|
||||
}
|
||||
|
||||
final updateIntervalHeader = headers['profile-update-interval']?.single;
|
||||
ProfileOptions? options;
|
||||
if (updateIntervalHeader != null) {
|
||||
final updateInterval = Duration(hours: int.parse(updateIntervalHeader));
|
||||
options = ProfileOptions(updateInterval: updateInterval);
|
||||
}
|
||||
|
||||
final subscriptionInfoHeader = headers['subscription-userinfo']?.single;
|
||||
SubscriptionInfo? subInfo;
|
||||
if (subscriptionInfoHeader != null) {
|
||||
subInfo = SubscriptionInfo.fromResponseHeader(subscriptionInfoHeader);
|
||||
}
|
||||
|
||||
final webPageUrlHeader = headers['profile-web-page-url']?.single;
|
||||
final supportUrlHeader = headers['support-url']?.single;
|
||||
ProfileExtra? extra;
|
||||
if (webPageUrlHeader != null || supportUrlHeader != null) {
|
||||
extra = ProfileExtra(
|
||||
webPageUrl: webPageUrlHeader,
|
||||
supportUrl: supportUrlHeader,
|
||||
);
|
||||
}
|
||||
|
||||
return Profile(
|
||||
id: const Uuid().v4(),
|
||||
active: false,
|
||||
name: title,
|
||||
url: url,
|
||||
lastUpdate: DateTime.now(),
|
||||
options: options,
|
||||
subInfo: subInfo,
|
||||
extra: extra,
|
||||
);
|
||||
}
|
||||
|
||||
factory Profile.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfileFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfileOptions with _$ProfileOptions {
|
||||
const factory ProfileOptions({
|
||||
required Duration updateInterval,
|
||||
}) = _ProfileOptions;
|
||||
|
||||
factory ProfileOptions.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfileOptionsFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfileExtra with _$ProfileExtra {
|
||||
const factory ProfileExtra({
|
||||
String? webPageUrl,
|
||||
String? supportUrl,
|
||||
}) = _ProfileExtra;
|
||||
|
||||
factory ProfileExtra.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfileExtraFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SubscriptionInfo with _$SubscriptionInfo {
|
||||
const SubscriptionInfo._();
|
||||
|
||||
const factory SubscriptionInfo({
|
||||
required int upload,
|
||||
required int download,
|
||||
required int total,
|
||||
@JsonKey(fromJson: _dateTimeFromSecondsSinceEpoch) required DateTime expire,
|
||||
}) = _SubscriptionInfo;
|
||||
|
||||
bool get isExpired => expire <= DateTime.now();
|
||||
|
||||
int get consumption => upload + download;
|
||||
|
||||
double get ratio => consumption / total;
|
||||
|
||||
Duration get remaining => expire.difference(DateTime.now());
|
||||
|
||||
factory SubscriptionInfo.fromResponseHeader(String header) {
|
||||
final values = header.split(';');
|
||||
final map = {
|
||||
for (final v in values)
|
||||
v.split('=').first: int.tryParse(v.split('=').second)
|
||||
};
|
||||
return SubscriptionInfo.fromJson(map);
|
||||
}
|
||||
|
||||
factory SubscriptionInfo.fromJson(Map<String, dynamic> json) =>
|
||||
_$SubscriptionInfoFromJson(json);
|
||||
}
|
||||
|
||||
DateTime _dateTimeFromSecondsSinceEpoch(dynamic expire) =>
|
||||
DateTime.fromMillisecondsSinceEpoch((expire as int) * 1000);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export 'profile.dart';
|
||||
export 'profiles_failure.dart';
|
||||
export 'profiles_repository.dart';
|
||||
export 'subscription_info.dart';
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'subscription_info.freezed.dart';
|
||||
part 'subscription_info.g.dart';
|
||||
|
||||
// TODO: test and improve
|
||||
@freezed
|
||||
class SubscriptionInfo with _$SubscriptionInfo {
|
||||
const SubscriptionInfo._();
|
||||
|
||||
const factory SubscriptionInfo({
|
||||
int? upload,
|
||||
int? download,
|
||||
int? total,
|
||||
@JsonKey(fromJson: _dateTimeFromSecondsSinceEpoch) DateTime? expire,
|
||||
}) = _SubscriptionInfo;
|
||||
|
||||
bool get isValid =>
|
||||
total != null && download != null && upload != null && expire != null;
|
||||
|
||||
bool get isExpired => expire! <= DateTime.now();
|
||||
|
||||
int get consumption => upload! + download!;
|
||||
|
||||
double get ratio => consumption / total!;
|
||||
|
||||
Duration get remaining => expire!.difference(DateTime.now());
|
||||
|
||||
factory SubscriptionInfo.fromResponseHeader(String header) {
|
||||
final values = header.split(';');
|
||||
final map = {
|
||||
for (final v in values)
|
||||
v.split('=').first: int.tryParse(v.split('=').second)
|
||||
};
|
||||
return SubscriptionInfo.fromJson(map);
|
||||
}
|
||||
|
||||
factory SubscriptionInfo.fromJson(Map<String, dynamic> json) =>
|
||||
_$SubscriptionInfoFromJson(json);
|
||||
}
|
||||
|
||||
DateTime? _dateTimeFromSecondsSinceEpoch(dynamic expire) =>
|
||||
DateTime.fromMillisecondsSinceEpoch((expire as int) * 1000);
|
||||
Reference in New Issue
Block a user