mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-21 08:38:37 +00:00
feat: match info
opt dateformat Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import 'package:PiliPlus/models/common/account_type.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
@@ -46,7 +46,7 @@ class LoginAccount implements Account {
|
||||
@override
|
||||
late final Map<String, String> headers = {
|
||||
'x-bili-mid': _midStr,
|
||||
'x-bili-aurora-eid': Utils.genAuroraEid(mid),
|
||||
'x-bili-aurora-eid': IdUtils.genAuroraEid(mid),
|
||||
};
|
||||
@override
|
||||
late String csrf =
|
||||
@@ -165,7 +165,7 @@ extension BiliCookieJar on DefaultCookieJar {
|
||||
void setBuvid3() {
|
||||
domainCookies['bilibili.com'] ??= {'/': {}};
|
||||
domainCookies['bilibili.com']!['/']!['buvid3'] ??= SerializableCookie(
|
||||
Cookie('buvid3', Utils.genBuvid3())..setBiliDomain());
|
||||
Cookie('buvid3', IdUtils.genBuvid3())..setBiliDomain());
|
||||
}
|
||||
|
||||
static DefaultCookieJar fromJson(Map json) =>
|
||||
|
||||
@@ -6,9 +6,9 @@ import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/models/common/account_type.dart';
|
||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||
import 'package:PiliPlus/utils/app_sign.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
@@ -121,7 +121,7 @@ class AccountManager extends Interceptor {
|
||||
}
|
||||
dataPtr['ts'] ??=
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
|
||||
Utils.appSign(dataPtr);
|
||||
AppSign.appSign(dataPtr);
|
||||
// if (kDebugMode) debugPrint(dataPtr.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,6 +802,21 @@ class PiliScheme {
|
||||
}
|
||||
launchURL();
|
||||
return false;
|
||||
case 'match' || 'v':
|
||||
if (path.contains('match/data/detail') ||
|
||||
path.contains('match/singledata')) {
|
||||
String? cid = uriDigitRegExp.firstMatch(path)?.group(1);
|
||||
if (cid != null) {
|
||||
PageUtils.toDupNamed(
|
||||
'/matchInfo',
|
||||
parameters: {'cid': cid},
|
||||
off: off,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
launchURL();
|
||||
return false;
|
||||
default:
|
||||
Map map = IdUtils.matchAvorBv(input: area?.split('?').first);
|
||||
if (map.isNotEmpty) {
|
||||
|
||||
19
lib/utils/app_sign.dart
Normal file
19
lib/utils/app_sign.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
class AppSign {
|
||||
static void appSign(Map<String, dynamic> params,
|
||||
[String appkey = Constants.appKey, String appsec = Constants.appSec]) {
|
||||
params['appkey'] = appkey;
|
||||
var searchParams = Uri(
|
||||
queryParameters:
|
||||
params.map((key, value) => MapEntry(key, value.toString()))).query;
|
||||
var sortedQueryString = (searchParams.split('&')..sort()).join('&');
|
||||
|
||||
params['sign'] = md5
|
||||
.convert(utf8.encode(sortedQueryString + appsec))
|
||||
.toString(); // 获取MD5哈希值
|
||||
}
|
||||
}
|
||||
79
lib/utils/date_util.dart
Normal file
79
lib/utils/date_util.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:intl/intl.dart' show DateFormat;
|
||||
|
||||
class DateUtil {
|
||||
static final _shortFormat = DateFormat('MM-dd');
|
||||
static final longFormat = DateFormat('yyyy-MM-dd');
|
||||
static final _shortFormatD = DateFormat('MM-dd HH:mm');
|
||||
static final longFormatD = DateFormat('yyyy-MM-dd HH:mm');
|
||||
static final longFormatDs = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
|
||||
static String dateFormat(
|
||||
int? time, {
|
||||
DateFormat? shortFormat,
|
||||
DateFormat? longFormat,
|
||||
}) {
|
||||
if (time == null || time == 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
final now = DateTime.now();
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
||||
final diff = now.difference(date);
|
||||
|
||||
final diffInMins = diff.inMinutes;
|
||||
if (diffInMins < 1) return '刚刚';
|
||||
if (diffInMins < 60) return '$diffInMins分钟前';
|
||||
|
||||
final diffInHours = diff.inHours;
|
||||
if (diffInHours < 24) return '$diffInHours小时前';
|
||||
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final dateDay = DateTime(date.year, date.month, date.day);
|
||||
final dayDiff = today.difference(dateDay).inDays;
|
||||
if (dayDiff == 1) {
|
||||
return '昨天 ${_twoDigits(date.hour)}:${_twoDigits(date.minute)}';
|
||||
}
|
||||
if (dayDiff < 4) {
|
||||
return '$dayDiff天前';
|
||||
}
|
||||
final DateFormat sdf = now.year == date.year
|
||||
? shortFormat ?? _shortFormat
|
||||
: longFormat ?? DateUtil.longFormat;
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
static String _twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
|
||||
static String chatFormat(int? time, {bool isHistory = false}) {
|
||||
if (time == null || time == 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
final now = DateTime.now();
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
||||
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final dateDay = DateTime(date.year, date.month, date.day);
|
||||
if (today == dateDay) {
|
||||
return '${isHistory ? '今天 ' : ''}${_twoDigits(date.hour)}:${_twoDigits(date.minute)}';
|
||||
}
|
||||
final isYesterday = today.subtract(const Duration(days: 1)) == dateDay;
|
||||
if (isYesterday) {
|
||||
return '昨天 ${_twoDigits(date.hour)}:${_twoDigits(date.minute)}';
|
||||
}
|
||||
if (isHistory) {
|
||||
final DateFormat sdf =
|
||||
now.year == date.year ? _shortFormatD : longFormatD;
|
||||
return sdf.format(date);
|
||||
}
|
||||
return longFormatD.format(date);
|
||||
}
|
||||
|
||||
static String format(int? time, {DateFormat? format}) {
|
||||
if (time == null || time == 0) {
|
||||
return '';
|
||||
}
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
||||
return (format ?? longFormatD).format(date);
|
||||
}
|
||||
}
|
||||
29
lib/utils/duration_util.dart
Normal file
29
lib/utils/duration_util.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'dart:math' show pow;
|
||||
|
||||
class DurationUtil {
|
||||
static String formatDuration(num? seconds) {
|
||||
if (seconds == null || seconds == 0) {
|
||||
return '00:00';
|
||||
}
|
||||
int h = seconds ~/ 3600;
|
||||
seconds %= 3600;
|
||||
int m = seconds ~/ 60;
|
||||
seconds %= 60;
|
||||
String sms = seconds is double
|
||||
? seconds.toStringAsFixed(3).padLeft(6, '0')
|
||||
: seconds.toString().padLeft(2, '0');
|
||||
return h == 0
|
||||
? "${m.toString().padLeft(2, '0')}:$sms"
|
||||
: "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms";
|
||||
}
|
||||
|
||||
static int parseDuration(String data) {
|
||||
List<int> split =
|
||||
data.split(':').reversed.map((e) => int.parse(e)).toList();
|
||||
int duration = 0;
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
duration += split[i] * pow(60, i).toInt();
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
19
lib/utils/fav_util.dart
Normal file
19
lib/utils/fav_util.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
class FavUtil {
|
||||
static bool isDefaultFav(int? attr) {
|
||||
if (attr == null) {
|
||||
return false;
|
||||
}
|
||||
return (attr & 2) == 0;
|
||||
}
|
||||
|
||||
static String isPublicFavText(int? attr) {
|
||||
if (attr == null) {
|
||||
return '';
|
||||
}
|
||||
return isPublicFav(attr) ? '公开' : '私密';
|
||||
}
|
||||
|
||||
static bool isPublicFav(int attr) {
|
||||
return (attr & 1) == 0;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:uuid/v4.dart';
|
||||
|
||||
class IdUtils {
|
||||
static const XOR_CODE = 23442827791579;
|
||||
static const MASK_CODE = 2251799813685247;
|
||||
@@ -67,18 +70,41 @@ class IdUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
// eid生成
|
||||
static String? genAuroraEid(int uid) {
|
||||
static String genBuvid3() {
|
||||
return '${const UuidV4().generate().toUpperCase()}${Utils.random.nextInt(100000).toString().padLeft(5, "0")}infoc';
|
||||
}
|
||||
|
||||
static String genAuroraEid(int uid) {
|
||||
if (uid == 0) {
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
String uidString = uid.toString();
|
||||
List<int> resultBytes = List.generate(
|
||||
uidString.length,
|
||||
(i) => uidString.codeUnitAt(i) ^ "ad1va46a7lza".codeUnitAt(i % 12),
|
||||
);
|
||||
String auroraEid = base64Url.encode(resultBytes);
|
||||
auroraEid = auroraEid.replaceAll(RegExp(r'=*$', multiLine: true), '');
|
||||
return auroraEid;
|
||||
|
||||
var midByte = utf8.encode(uid.toString());
|
||||
|
||||
const key = 'ad1va46a7lza';
|
||||
for (int i = 0; i < midByte.length; i++) {
|
||||
midByte[i] ^= key.codeUnitAt(i % key.length);
|
||||
}
|
||||
|
||||
String base64Encoded = base64.encode(midByte).replaceAll('=', '');
|
||||
|
||||
return base64Encoded;
|
||||
}
|
||||
|
||||
static String genTraceId() {
|
||||
String randomId = Utils.generateRandomString(32);
|
||||
|
||||
StringBuffer randomTraceId = StringBuffer(randomId.substring(0, 24));
|
||||
|
||||
int ts = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
for (int i = 2; i >= 0; i--) {
|
||||
ts >>= 8;
|
||||
randomTraceId.write((ts & 0xFF).toRadixString(16).padLeft(2, '0'));
|
||||
}
|
||||
|
||||
randomTraceId.write(randomId.substring(30, 32));
|
||||
|
||||
return '${randomTraceId.toString()}:${randomTraceId.toString().substring(16, 32)}:0:0';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,34 @@ import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart' show DateFormat;
|
||||
import 'package:live_photo_maker/live_photo_maker.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:saver_gallery/saver_gallery.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class DownloadUtils {
|
||||
class ImageUtil {
|
||||
static String get time =>
|
||||
DateFormat('yyyy-MM-dd_HH-mm-ss').format(DateTime.now());
|
||||
|
||||
// 图片分享
|
||||
static Future<void> onShareImg(String imgUrl) async {
|
||||
static Future<void> onShareImg(String url) async {
|
||||
try {
|
||||
SmartDialog.showLoading();
|
||||
var response = await Request()
|
||||
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
|
||||
.get(url, options: Options(responseType: ResponseType.bytes));
|
||||
final temp = await getTemporaryDirectory();
|
||||
SmartDialog.dismiss();
|
||||
String imgName =
|
||||
"plpl_pic_${DateTime.now().toString().split('-').join()}.jpg";
|
||||
var path = '${temp.path}/$imgName';
|
||||
var name = Utils.getFileName(url);
|
||||
var path = '${temp.path}/$name';
|
||||
File(path).writeAsBytesSync(response.data);
|
||||
|
||||
Rect? sharePositionOrigin;
|
||||
@@ -35,7 +39,7 @@ class DownloadUtils {
|
||||
|
||||
Share.shareXFiles(
|
||||
[XFile(path)],
|
||||
subject: imgUrl,
|
||||
subject: url,
|
||||
sharePositionOrigin: sharePositionOrigin,
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -111,17 +115,9 @@ class DownloadUtils {
|
||||
SmartDialog.showLoading(msg: '正在下载');
|
||||
|
||||
String tmpPath = (await getTemporaryDirectory()).path;
|
||||
String time = DateTime.now()
|
||||
.toString()
|
||||
.replaceAll(' ', '_')
|
||||
.replaceAll(':', '-')
|
||||
.split('.')
|
||||
.first;
|
||||
late String imageName =
|
||||
"cover_$time.${url.split('.').lastOrNull ?? 'jpg'}";
|
||||
late String imageName = "cover_${Utils.getFileName(url)}";
|
||||
late String imagePath = '$tmpPath/$imageName';
|
||||
String videoName =
|
||||
"video_$time.${liveUrl.split('.').lastOrNull ?? 'mp4'}";
|
||||
String videoName = "video_${Utils.getFileName(liveUrl)}";
|
||||
String videoPath = '$tmpPath/$videoName';
|
||||
|
||||
await Request.dio.download(liveUrl, videoPath);
|
||||
@@ -167,10 +163,7 @@ class DownloadUtils {
|
||||
}
|
||||
|
||||
static Future<bool> downloadImg(
|
||||
BuildContext context,
|
||||
List<String> imgList, {
|
||||
String imgType = 'cover',
|
||||
}) async {
|
||||
BuildContext context, List<String> imgList) async {
|
||||
if (!await checkPermissionDependOnSdkInt(context)) return false;
|
||||
final cancelToken = CancelToken();
|
||||
SmartDialog.showLoading(
|
||||
@@ -238,4 +231,28 @@ class DownloadUtils {
|
||||
SmartDialog.dismiss(status: SmartStatus.loading);
|
||||
}
|
||||
}
|
||||
|
||||
static final regExp =
|
||||
RegExp(r'(@(\d+[a-z]_?)*)(\..*)?$', caseSensitive: false);
|
||||
|
||||
static String thumbnailUrl(String? src, [int? quality]) {
|
||||
if (src != null && quality != 100) {
|
||||
bool hasMatch = false;
|
||||
src = src.splitMapJoin(
|
||||
regExp,
|
||||
onMatch: (Match match) {
|
||||
hasMatch = true;
|
||||
String suffix = match.group(3) ?? '.webp';
|
||||
return '${match.group(1)}_${quality ?? GlobalData().imgQuality}q$suffix';
|
||||
},
|
||||
onNonMatch: (String str) {
|
||||
return str;
|
||||
},
|
||||
);
|
||||
if (!hasMatch) {
|
||||
src += '@${quality ?? GlobalData().imgQuality}q.webp';
|
||||
}
|
||||
}
|
||||
return src.http2https;
|
||||
}
|
||||
}
|
||||
62
lib/utils/num_util.dart
Normal file
62
lib/utils/num_util.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/foundation.dart' show kDebugMode, debugPrint;
|
||||
import 'package:get/get_utils/get_utils.dart';
|
||||
|
||||
class NumUtil {
|
||||
static final _numRegExp = RegExp(r'([\d\.]+)([千万亿])?');
|
||||
|
||||
static int _getUnit(String? unit) {
|
||||
switch (unit) {
|
||||
case '千':
|
||||
return 1000;
|
||||
case '万':
|
||||
return 10000;
|
||||
case '亿':
|
||||
return 100000000;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int parseNum(String numberStr) {
|
||||
if (numberStr == '-') return 0;
|
||||
try {
|
||||
final match = _numRegExp.firstMatch(numberStr)!;
|
||||
var number = double.parse(match.group(1)!);
|
||||
number *= _getUnit(match.group(2));
|
||||
return number.toInt();
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('parse failed: "$numberStr" : $e');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static String numFormat(dynamic number) {
|
||||
if (number == null) {
|
||||
return '0';
|
||||
}
|
||||
if (number is String) {
|
||||
number = int.tryParse(number) ?? number;
|
||||
if (number is String) {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
String format(first, second) {
|
||||
double result = ((number / first) as double).toPrecision(1);
|
||||
int intRes = result.toInt();
|
||||
if (result == intRes) {
|
||||
return '$intRes$second';
|
||||
} else {
|
||||
return '$result$second';
|
||||
}
|
||||
}
|
||||
|
||||
if (number >= 100000000) {
|
||||
return format(100000000, '亿');
|
||||
} else if (number >= 10000) {
|
||||
return format(10000, '万');
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
132
lib/utils/update.dart
Normal file
132
lib/utils/update.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
|
||||
class Update {
|
||||
// 检查更新
|
||||
static Future<void> checkUpdate([bool isAuto = true]) async {
|
||||
if (kDebugMode) return;
|
||||
SmartDialog.dismiss();
|
||||
try {
|
||||
final res = await Request().get(Api.latestApp, uaType: 'mob');
|
||||
if (res.data is Map || res.data.isEmpty) {
|
||||
if (!isAuto) {
|
||||
SmartDialog.showToast('检查更新失败,GitHub接口未返回数据,请检查网络');
|
||||
}
|
||||
return;
|
||||
}
|
||||
DateTime latest = DateTime.parse(res.data[0]['created_at']);
|
||||
DateTime current = DateTime.parse('${BuildConfig.buildTime}Z');
|
||||
current = current.copyWith(hour: current.hour - 8);
|
||||
if (current.compareTo(latest) >= 0) {
|
||||
if (!isAuto) {
|
||||
SmartDialog.showToast('已是最新版本');
|
||||
}
|
||||
} else {
|
||||
SmartDialog.show(
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return AlertDialog(
|
||||
title: const Text('🎉 发现新版本 '),
|
||||
content: SizedBox(
|
||||
height: 280,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${res.data[0]['tag_name']}',
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('${res.data[0]['body']}'),
|
||||
TextButton(
|
||||
onPressed: () => PageUtils.launchURL(
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPlus/commits/main'),
|
||||
child: Text(
|
||||
"点此查看完整更新(即commit)内容",
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SmartDialog.dismiss();
|
||||
GStorage.setting.put(SettingBoxKey.autoUpdate, false);
|
||||
},
|
||||
child: Text(
|
||||
'不再提醒',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: SmartDialog.dismiss,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => onDownload(res.data[0]),
|
||||
child: const Text('Github'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('failed to check update: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 下载适用于当前系统的安装包
|
||||
static Future<void> onDownload(data) async {
|
||||
SmartDialog.dismiss();
|
||||
try {
|
||||
void download(plat) {
|
||||
if (data['assets'].isNotEmpty) {
|
||||
for (dynamic i in data['assets']) {
|
||||
if (i['name'].contains(plat)) {
|
||||
PageUtils.launchURL(i['browser_download_url']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// 获取设备信息
|
||||
AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
// [arm64-v8a]
|
||||
download(androidInfo.supportedAbis.first);
|
||||
} else {
|
||||
download('ios');
|
||||
}
|
||||
} catch (_) {
|
||||
PageUtils.launchURL(
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPlus/releases/latest');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,31 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/models/home/rcmd/result.dart';
|
||||
import 'package:PiliPlus/models/model_rec_video_item.dart';
|
||||
import 'package:PiliPlus/models/model_video.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:uuid/v4.dart';
|
||||
|
||||
class Utils {
|
||||
static final Random random = Random();
|
||||
|
||||
static const channel = MethodChannel("PiliPlus");
|
||||
|
||||
static final _numRegExp = RegExp(r'([\d\.]+)([千万亿])?');
|
||||
|
||||
static final regExp =
|
||||
RegExp(r'(@(\d+[a-z]_?)*)(\..*)?$', caseSensitive: false);
|
||||
|
||||
static String thumbnailImgUrl(String? src, [int? quality]) {
|
||||
if (src != null && quality != 100) {
|
||||
bool hasMatch = false;
|
||||
src = src.splitMapJoin(
|
||||
regExp,
|
||||
onMatch: (Match match) {
|
||||
hasMatch = true;
|
||||
String suffix = match.group(3) ?? '.webp';
|
||||
return '${match.group(1)}_${quality ?? GlobalData().imgQuality}q$suffix';
|
||||
},
|
||||
onNonMatch: (String str) {
|
||||
return str;
|
||||
},
|
||||
);
|
||||
if (!hasMatch) {
|
||||
src += '@${quality ?? GlobalData().imgQuality}q.webp';
|
||||
}
|
||||
}
|
||||
return src.http2https;
|
||||
}
|
||||
|
||||
static bool? _isIpad;
|
||||
|
||||
static Future<bool> isIpad() async {
|
||||
if (!Platform.isIOS) {
|
||||
return false;
|
||||
}
|
||||
if (_isIpad != null) {
|
||||
return _isIpad!;
|
||||
}
|
||||
if (!Platform.isIOS) {
|
||||
return false;
|
||||
}
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
IosDeviceInfo info = await deviceInfo.iosInfo;
|
||||
_isIpad = info.model.toLowerCase().contains("ipad");
|
||||
@@ -99,24 +60,6 @@ class Utils {
|
||||
return numericRegex.hasMatch(str);
|
||||
}
|
||||
|
||||
static bool isDefaultFav(int? attr) {
|
||||
if (attr == null) {
|
||||
return false;
|
||||
}
|
||||
return (attr & 2) == 0;
|
||||
}
|
||||
|
||||
static String isPublicFavText(int? attr) {
|
||||
if (attr == null) {
|
||||
return '';
|
||||
}
|
||||
return isPublicFav(attr) ? '公开' : '私密';
|
||||
}
|
||||
|
||||
static bool isPublicFav(int attr) {
|
||||
return (attr & 1) == 0;
|
||||
}
|
||||
|
||||
static String generateRandomString(int length) {
|
||||
const characters = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
@@ -124,45 +67,6 @@ class Utils {
|
||||
(_) => characters.codeUnitAt(random.nextInt(characters.length))));
|
||||
}
|
||||
|
||||
static String genAuroraEid(int uid) {
|
||||
if (uid == 0) {
|
||||
return ''; // Return null for a UID of 0
|
||||
}
|
||||
|
||||
// 1. Convert UID to a byte array.
|
||||
var midByte = utf8.encode(uid.toString());
|
||||
|
||||
// 2. XOR each byte with the corresponding byte from the key.
|
||||
const key = 'ad1va46a7lza';
|
||||
for (int i = 0; i < midByte.length; i++) {
|
||||
midByte[i] ^= key.codeUnitAt(i % key.length);
|
||||
}
|
||||
|
||||
// 3. Perform Base64 encoding without padding.
|
||||
String base64Encoded =
|
||||
base64.encode(midByte).replaceAll('=', ''); // Remove padding
|
||||
|
||||
// Return the resulting x-bili-aurora-eid.
|
||||
return base64Encoded;
|
||||
}
|
||||
|
||||
static String genTraceId() {
|
||||
String randomId = generateRandomString(32);
|
||||
|
||||
StringBuffer randomTraceId = StringBuffer(randomId.substring(0, 24));
|
||||
|
||||
int ts = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
for (int i = 2; i >= 0; i--) {
|
||||
ts >>= 8;
|
||||
randomTraceId.write((ts & 0xFF).toRadixString(16).padLeft(2, '0'));
|
||||
}
|
||||
|
||||
randomTraceId.write(randomId.substring(30, 32));
|
||||
|
||||
return '${randomTraceId.toString()}:${randomTraceId.toString().substring(16, 32)}:0:0';
|
||||
}
|
||||
|
||||
static void copyText(
|
||||
String text, {
|
||||
bool needToast = true,
|
||||
@@ -174,313 +78,10 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
// static Future<String> getCookiePath() async {
|
||||
// final Directory tempDir = await getApplicationSupportDirectory();
|
||||
// final String tempPath = "${tempDir.path}/.plpl/";
|
||||
// final Directory dir = Directory(tempPath);
|
||||
// final bool b = await dir.exists();
|
||||
// if (!b) {
|
||||
// dir.createSync(recursive: true);
|
||||
// }
|
||||
// return tempPath;
|
||||
// }
|
||||
|
||||
static int getUnit(String? unit) {
|
||||
switch (unit) {
|
||||
case '千':
|
||||
return 1000;
|
||||
case '万':
|
||||
return 10000;
|
||||
case '亿':
|
||||
return 100000000;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int parseNum(String numberStr) {
|
||||
if (numberStr == '-') return 0;
|
||||
try {
|
||||
final match = _numRegExp.firstMatch(numberStr)!;
|
||||
var number = double.parse(match.group(1)!);
|
||||
number *= getUnit(match.group(2));
|
||||
return number.toInt();
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('parse failed: "$numberStr" : $e');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static String numFormat(dynamic number) {
|
||||
if (number == null) {
|
||||
return '0';
|
||||
}
|
||||
if (number is String) {
|
||||
number = int.tryParse(number) ?? number;
|
||||
if (number is String) {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
String format(first, second) {
|
||||
double result = ((number / first) as double).toPrecision(1);
|
||||
int intRes = result.toInt();
|
||||
if (result == intRes) {
|
||||
return '$intRes$second';
|
||||
} else {
|
||||
return '$result$second';
|
||||
}
|
||||
}
|
||||
|
||||
if (number >= 100000000) {
|
||||
return format(100000000, '亿');
|
||||
} else if (number >= 10000) {
|
||||
return format(10000, '万');
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static String durationReadFormat(String duration) {
|
||||
List<String> durationParts = duration.split(':');
|
||||
|
||||
if (durationParts.length == 3) {
|
||||
if (durationParts[0] != '00') {
|
||||
return '${int.parse(durationParts[0])}小时${durationParts[1]}分钟${durationParts[2]}秒';
|
||||
}
|
||||
durationParts.removeAt(0);
|
||||
}
|
||||
if (durationParts.length == 2) {
|
||||
if (durationParts[0] != '00') {
|
||||
return '${int.parse(durationParts[0])}分钟${durationParts[1]}秒';
|
||||
}
|
||||
durationParts.removeAt(0);
|
||||
}
|
||||
return '${int.parse(durationParts[0])}秒';
|
||||
}
|
||||
|
||||
static String videoItemSemantics(BaseVideoItemModel videoItem) {
|
||||
StringBuffer semanticsLabel = StringBuffer();
|
||||
bool emptyStatCheck(int? stat) {
|
||||
return stat == null || stat <= 0;
|
||||
}
|
||||
|
||||
if (videoItem is RecVideoItemAppModel) {
|
||||
if (videoItem.goto == 'picture') {
|
||||
semanticsLabel.write('动态,');
|
||||
} else if (videoItem.goto == 'bangumi') {
|
||||
semanticsLabel.write('番剧,');
|
||||
}
|
||||
}
|
||||
semanticsLabel.write(videoItem.title);
|
||||
|
||||
if (!emptyStatCheck(videoItem.stat.view)) {
|
||||
semanticsLabel
|
||||
..write(',${Utils.numFormat(videoItem.stat.view)}')
|
||||
..write(
|
||||
(videoItem is RecVideoItemAppModel && videoItem.goto == 'picture')
|
||||
? '浏览'
|
||||
: '播放');
|
||||
}
|
||||
if (!emptyStatCheck(videoItem.stat.danmu)) {
|
||||
semanticsLabel.write(',${Utils.numFormat(videoItem.stat.danmu)}弹幕');
|
||||
}
|
||||
if ((videoItem is BaseRecVideoItemModel) && videoItem.rcmdReason != null) {
|
||||
semanticsLabel.write(',${videoItem.rcmdReason}');
|
||||
}
|
||||
if (!emptyStatCheck(videoItem.duration) && videoItem.duration > 0) {
|
||||
semanticsLabel.write(
|
||||
',时长${Utils.durationReadFormat(Utils.timeFormat(videoItem.duration))}');
|
||||
}
|
||||
if (videoItem.pubdate != null) {
|
||||
semanticsLabel
|
||||
.write(',${Utils.dateFormat(videoItem.pubdate!, formatType: 'day')}');
|
||||
}
|
||||
if (videoItem.owner.name != '') {
|
||||
semanticsLabel.write(',Up主:${videoItem.owner.name}');
|
||||
}
|
||||
if (videoItem is BaseRecVideoItemModel && videoItem.isFollowed) {
|
||||
semanticsLabel.write(',已关注');
|
||||
}
|
||||
return semanticsLabel.toString();
|
||||
}
|
||||
|
||||
static String timeFormat(dynamic time) {
|
||||
if (time is String && time.contains(':')) {
|
||||
return time;
|
||||
}
|
||||
if (time == null || time == 0) {
|
||||
return '00:00';
|
||||
}
|
||||
return formatDuration(time);
|
||||
}
|
||||
|
||||
static String shortenChineseDateString(String date) {
|
||||
return date.contains("年")
|
||||
? RegExp(r'\d+')
|
||||
.allMatches(date)
|
||||
.map((match) => match.group(0)?.length == 4
|
||||
? match.group(0)!.substring(2)
|
||||
: match.group(0))
|
||||
.join('-')
|
||||
: date;
|
||||
// if (date.contains("年")) return '${date.split("年").first}年';
|
||||
// return date;
|
||||
}
|
||||
|
||||
// 完全相对时间显示
|
||||
static String formatTimestampToRelativeTime(timeStamp) {
|
||||
var difference = DateTime.now()
|
||||
.difference(DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000));
|
||||
|
||||
if (difference.inDays > 365) {
|
||||
return '${difference.inDays ~/ 365}年前';
|
||||
} else if (difference.inDays > 30) {
|
||||
return '${difference.inDays ~/ 30}个月前';
|
||||
} else if (difference.inDays > 0) {
|
||||
return '${difference.inDays}天前';
|
||||
} else if (difference.inHours > 0) {
|
||||
return '${difference.inHours}小时前';
|
||||
} else if (difference.inMinutes > 0) {
|
||||
return '${difference.inMinutes}分钟前';
|
||||
} else {
|
||||
return '刚刚';
|
||||
}
|
||||
}
|
||||
|
||||
// 时间显示,刚刚,x分钟前
|
||||
static String dateFormat(timeStamp, {formatType = 'list'}) {
|
||||
if (timeStamp == null || timeStamp == 0 || timeStamp == '') {
|
||||
return '';
|
||||
}
|
||||
// 当前时间
|
||||
int time = (DateTime.now().millisecondsSinceEpoch / 1000).round();
|
||||
// 对比
|
||||
int distance = (time - timeStamp).toInt();
|
||||
// 当前年日期
|
||||
String currentYearStr = 'MM月DD日 hh:mm';
|
||||
String lastYearStr = 'YY年MM月DD日 hh:mm';
|
||||
if (formatType == 'detail') {
|
||||
currentYearStr = 'MM-DD hh:mm';
|
||||
lastYearStr = 'YY-MM-DD hh:mm';
|
||||
return customStampStr(
|
||||
timestamp: timeStamp, date: lastYearStr, toInt: false);
|
||||
} else if (formatType == 'day') {
|
||||
if (distance <= 43200) {
|
||||
return customStampStr(
|
||||
timestamp: timeStamp,
|
||||
date: 'hh:mm',
|
||||
toInt: true,
|
||||
);
|
||||
}
|
||||
return customStampStr(
|
||||
timestamp: timeStamp,
|
||||
date: 'YY-MM-DD',
|
||||
toInt: true,
|
||||
);
|
||||
}
|
||||
if (distance <= 60) {
|
||||
return '刚刚';
|
||||
} else if (distance <= 3600) {
|
||||
return '${(distance / 60).floor()}分钟前';
|
||||
} else if (distance <= 43200) {
|
||||
return '${(distance / 60 / 60).floor()}小时前';
|
||||
} else if (DateTime.fromMillisecondsSinceEpoch(time * 1000).year ==
|
||||
DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000).year) {
|
||||
return customStampStr(
|
||||
timestamp: timeStamp, date: currentYearStr, toInt: false);
|
||||
} else {
|
||||
return customStampStr(
|
||||
timestamp: timeStamp, date: lastYearStr, toInt: false);
|
||||
}
|
||||
}
|
||||
|
||||
// 时间戳转时间
|
||||
static String customStampStr({
|
||||
int? timestamp, // 为空则显示当前时间
|
||||
String? date, // 显示格式,比如:'YY年MM月DD日 hh:mm:ss'
|
||||
bool toInt = true, // 去除0开头
|
||||
}) {
|
||||
timestamp ??= (DateTime.now().millisecondsSinceEpoch / 1000).round();
|
||||
String timeStr =
|
||||
(DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)).toString();
|
||||
|
||||
dynamic dateArr = timeStr.split(' ')[0];
|
||||
dynamic timeArr = timeStr.split(' ')[1];
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
String YY = dateArr.split('-')[0];
|
||||
// ignore: non_constant_identifier_names
|
||||
String MM = dateArr.split('-')[1];
|
||||
// ignore: non_constant_identifier_names
|
||||
String DD = dateArr.split('-')[2];
|
||||
|
||||
String hh = timeArr.split(':')[0];
|
||||
String mm = timeArr.split(':')[1];
|
||||
String ss = timeArr.split(':')[2];
|
||||
|
||||
ss = ss.split('.')[0];
|
||||
|
||||
// 去除0开头
|
||||
if (toInt) {
|
||||
MM = (int.parse(MM)).toString();
|
||||
DD = (int.parse(DD)).toString();
|
||||
hh = (int.parse(hh)).toString();
|
||||
// mm = (int.parse(mm)).toString();
|
||||
}
|
||||
|
||||
if (date == null) {
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
date = date
|
||||
.replaceAll('YY', YY)
|
||||
.replaceAll('MM', MM)
|
||||
.replaceAll('DD', DD)
|
||||
.replaceAll('hh', hh)
|
||||
.replaceAll('mm', mm)
|
||||
.replaceAll('ss', ss);
|
||||
// if (int.parse(YY) == DateTime.now().year &&
|
||||
// int.parse(MM) == DateTime.now().month) {
|
||||
// // 当天
|
||||
// if (int.parse(DD) == DateTime.now().day) {
|
||||
// return '今天';
|
||||
// }
|
||||
// }
|
||||
return date;
|
||||
}
|
||||
|
||||
static String makeHeroTag(v) {
|
||||
return v.toString() + random.nextInt(9999).toString();
|
||||
}
|
||||
|
||||
static String formatDuration(num seconds) {
|
||||
int h = seconds ~/ 3600;
|
||||
seconds %= 3600;
|
||||
int m = seconds ~/ 60;
|
||||
seconds %= 60;
|
||||
String sms = seconds is double
|
||||
? seconds.toStringAsFixed(3).padLeft(6, '0')
|
||||
: seconds.toString().padLeft(2, '0');
|
||||
return h == 0
|
||||
? "${m.toString().padLeft(2, '0')}:$sms"
|
||||
: "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms";
|
||||
}
|
||||
|
||||
static int duration(String duration) {
|
||||
List timeList = duration.split(':');
|
||||
int len = timeList.length;
|
||||
if (len == 2) {
|
||||
return int.parse(timeList[0]) * 60 + int.parse(timeList[1]);
|
||||
}
|
||||
if (len == 3) {
|
||||
return int.parse(timeList[0]) * 3600 +
|
||||
int.parse(timeList[1]) * 60 +
|
||||
int.parse(timeList[2]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int findClosestNumber(int target, List<int> numbers) {
|
||||
List<int> filterNums = numbers.where((number) => number <= target).toList();
|
||||
return filterNums.isNotEmpty
|
||||
@@ -488,138 +89,6 @@ class Utils {
|
||||
: numbers.reduce((a, b) => a > b ? b : a);
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
static Future<void> checkUpdate([bool isAuto = true]) async {
|
||||
if (kDebugMode) return;
|
||||
SmartDialog.dismiss();
|
||||
try {
|
||||
final res = await Request().get(Api.latestApp, uaType: 'mob');
|
||||
if (res.data is Map || res.data.isEmpty) {
|
||||
if (!isAuto) {
|
||||
SmartDialog.showToast('检查更新失败,GitHub接口未返回数据,请检查网络');
|
||||
}
|
||||
return;
|
||||
}
|
||||
DateTime latest = DateTime.parse(res.data[0]['created_at']);
|
||||
DateTime current = DateTime.parse('${BuildConfig.buildTime}Z');
|
||||
current = current.copyWith(hour: current.hour - 8);
|
||||
if (current.compareTo(latest) >= 0) {
|
||||
if (!isAuto) {
|
||||
SmartDialog.showToast('已是最新版本');
|
||||
}
|
||||
} else {
|
||||
SmartDialog.show(
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return AlertDialog(
|
||||
title: const Text('🎉 发现新版本 '),
|
||||
content: SizedBox(
|
||||
height: 280,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${res.data[0]['tag_name']}',
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('${res.data[0]['body']}'),
|
||||
TextButton(
|
||||
onPressed: () => PageUtils.launchURL(
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPlus/commits/main'),
|
||||
child: Text(
|
||||
"点此查看完整更新(即commit)内容",
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SmartDialog.dismiss();
|
||||
GStorage.setting.put(SettingBoxKey.autoUpdate, false);
|
||||
},
|
||||
child: Text(
|
||||
'不再提醒',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: SmartDialog.dismiss,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => onDownload(res.data[0]),
|
||||
child: const Text('Github'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('failed to check update: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 下载适用于当前系统的安装包
|
||||
static Future<void> onDownload(data) async {
|
||||
await SmartDialog.dismiss();
|
||||
try {
|
||||
void download(plat) {
|
||||
if (data['assets'].isNotEmpty) {
|
||||
for (dynamic i in data['assets']) {
|
||||
if (i['name'].contains(plat)) {
|
||||
PageUtils.launchURL(i['browser_download_url']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// 获取设备信息
|
||||
AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
// [arm64-v8a]
|
||||
download(androidInfo.supportedAbis.first);
|
||||
} else {
|
||||
download('ios');
|
||||
}
|
||||
} catch (_) {
|
||||
PageUtils.launchURL(
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPlus/releases/latest');
|
||||
}
|
||||
}
|
||||
|
||||
static void appSign(Map<String, dynamic> params,
|
||||
[String appkey = Constants.appKey, String appsec = Constants.appSec]) {
|
||||
params['appkey'] = appkey;
|
||||
var searchParams = Uri(
|
||||
queryParameters:
|
||||
params.map((key, value) => MapEntry(key, value.toString()))).query;
|
||||
var sortedQueryString = (searchParams.split('&')..sort()).join('&');
|
||||
|
||||
params['sign'] = md5
|
||||
.convert(utf8.encode(sortedQueryString + appsec))
|
||||
.toString(); // 获取MD5哈希值
|
||||
}
|
||||
|
||||
static List<int> generateRandomBytes(int minLength, int maxLength) {
|
||||
return List<int>.generate(
|
||||
minLength + random.nextInt(maxLength - minLength + 1),
|
||||
@@ -638,8 +107,4 @@ class Utils {
|
||||
final i1 = fileExt ? uri.length : uri.lastIndexOf('.');
|
||||
return uri.substring(i0, i1);
|
||||
}
|
||||
|
||||
static String genBuvid3() {
|
||||
return '${const UuidV4().generate().toUpperCase()}${random.nextInt(100000).toString().padLeft(5, "0")}infoc';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user