diff --git a/lib/common/constants.dart b/lib/common/constants.dart index 7a0b31d5..1640da49 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -18,6 +18,13 @@ class Constants { static const String thirdApi = 'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png'; + static const String traceId = + '11111111111111111111111111111111:1111111111111111:0:0'; + static const String userAgent = + 'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/1462100 channel/yingyongbao innerVer/1462100 osVer/14 network/2'; + static const String statistics = '%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D'; + //Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}'); + //内容来自 https://passport.bilibili.com/web/generic/country/list static const List> internationalDialingPrefix = [ {"id": 1, "cname": "中国大陆", "country_id": "86"}, diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 704232bc..ee3537bf 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -187,8 +187,8 @@ class VideoCardV extends StatelessWidget { ), if (videoItem.goto == 'av') Positioned( - right: 0, - bottom: 0, + right: -5, + bottom: -2, child: VideoPopupMenu( size: 29, iconSize: 17, @@ -239,10 +239,9 @@ class VideoContent extends StatelessWidget { fs: 9, ) ], - if (videoItem.rcmdReason != null && - videoItem.rcmdReason.content != '') ...[ + if (videoItem.rcmdReason != null) ...[ PBadge( - text: videoItem.rcmdReason.content, + text: videoItem.rcmdReason, stack: 'normal', size: 'small', type: 'color', @@ -280,7 +279,7 @@ class VideoContent extends StatelessWidget { ), ), ), - if (videoItem.goto == 'av') const SizedBox(width: 24) + if (videoItem.goto == 'av') const SizedBox(width: 10) ], ), ], @@ -307,7 +306,7 @@ class VideoStat extends StatelessWidget { view: videoItem.stat.view, goto: videoItem.goto, ), - const SizedBox(width: 8), + const SizedBox(width: 6), if (videoItem.goto != 'picture') StatDanMu( theme: 'gray', @@ -315,16 +314,23 @@ class VideoStat extends StatelessWidget { ), if (videoItem is RecVideoItemModel) ...[ const Spacer(), - RichText( - maxLines: 1, - text: TextSpan( - style: TextStyle( - fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, - color: Theme.of(context).colorScheme.outline.withOpacity(0.8), - ), - text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)), - ), - const SizedBox(width: 4), + Expanded( + flex: 0, + child: RichText( + maxLines: 1, + text: TextSpan( + style: TextStyle( + fontSize: + Theme.of(context).textTheme.labelSmall!.fontSize, + color: Theme.of(context) + .colorScheme + .outline + .withOpacity(0.8), + ), + text: + Utils.formatTimestampToRelativeTime(videoItem.pubdate)), + )), + const SizedBox(width: 2), ] ], ); diff --git a/lib/http/login.dart b/lib/http/login.dart index 72b4fa69..316ce39a 100644 --- a/lib/http/login.dart +++ b/lib/http/login.dart @@ -5,19 +5,27 @@ import 'package:dio/dio.dart'; import 'package:encrypt/encrypt.dart'; import '../common/constants.dart'; import '../models/login/index.dart'; +import '../utils/login.dart'; import '../utils/utils.dart'; import 'index.dart'; class LoginHttp { - static String deviceId = genDeviceId(); - static String buvid = genBuvid(); + static String deviceId = LoginUtils.genDeviceId(); + static String buvid = LoginUtils.buvid(); static String host = 'passport.bilibili.com'; - static String traceId = - '11111111111111111111111111111111:1111111111111111:0:0'; - static String statistics = Uri.encodeComponent( - '{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}'); - static String userAgent = - 'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/1462100 channel/yingyongbao innerVer/1462100 osVer/14 network/2'; + static Map headers = { + 'Host': host, + 'buvid': buvid, + 'env': 'prod', + 'app-key': 'android_hd', + 'user-agent': Constants.userAgent, + 'x-bili-trace-id': Constants.traceId, + 'x-bili-aurora-eid': '', + 'x-bili-aurora-zone': '', + 'bili-http-engine': 'cronet', + 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', + }; + static Future> getHDcode() async { var params = { 'appkey': Constants.appKey, @@ -117,7 +125,7 @@ class LoginHttp { 'platform': 'android', if (recaptcha_token != null) 'recaptcha_token': recaptcha_token, 's_locale': 'zh_CN', - 'statistics': statistics, + 'statistics': Constants.statistics, 'tel': tel, 'ts': (timestamp ~/ 1000).toString(), }; @@ -126,18 +134,6 @@ class LoginHttp { Constants.appKey, Constants.appSec, ); - var headers = { - 'Host': host, - 'buvid': buvid, - 'env': 'prod', - 'app-key': 'android_hd', - 'user-agent': userAgent, - 'x-bili-trace-id': traceId, - 'x-bili-aurora-eid': '', - 'x-bili-aurora-zone': '', - 'bili-http-engine': 'cronet', - 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', - }; var res = await Request().post( Api.appSmsCode, @@ -177,7 +173,7 @@ class LoginHttp { // 'mobi_app': 'android_hd', // 'platform': 'android', // 's_locale': 'zh_CN', - // 'statistics': statistics, + // 'statistics': Constants.statistics, // 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), // }; // String sign = Utils.appSign( @@ -185,18 +181,6 @@ class LoginHttp { // Constants.appKey, // Constants.appSec, // ); - // var headers = { - // 'Host': host, - // 'buvid': buvid, - // 'env': 'prod', - // 'app-key': 'android_hd', - // 'user-agent': userAgent, - // 'x-bili-trace-id': traceId, - // 'x-bili-aurora-eid': '', - // 'x-bili-aurora-zone': '', - // 'bili-http-engine': 'cronet', - // 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', - // }; // var res = await Request().post(Api.getGuestId, // queryParameters: {...params, 'sign': sign}, // options: Options( @@ -211,59 +195,6 @@ class LoginHttp { // } // } - static String genBuvid() { - var mac = []; - var random = Random(); - - for (var i = 0; i < 6; i++) { - var min = 0; - var max = 0xff; - var num = (random.nextInt(max - min + 1) + min).toRadixString(16); - mac.add(num); - } - - var md5Str = md5.convert(utf8.encode(mac.join(':'))).toString(); - var md5Arr = md5Str.split(''); - return 'XY${md5Arr[2]}${md5Arr[12]}${md5Arr[22]}$md5Str'; - } - - static String genDeviceId() { - // https://github.com/bilive/bilive_client/blob/2873de0532c54832f5464a4c57325ad9af8b8698/bilive/lib/app_client.ts#L62 - final String yyyyMMddHHmmss = DateTime.now() - .toIso8601String() - .replaceAll(RegExp(r'[-:TZ]'), '') - .substring(0, 14); - - final Random random = Random(); // Random.secure(); - final String randomHex32 = - List.generate(32, (index) => random.nextInt(16).toRadixString(16)) - .join(); - final String randomHex16 = - List.generate(16, (index) => random.nextInt(16).toRadixString(16)) - .join(); - - final String deviceID = randomHex32 + yyyyMMddHHmmss + randomHex16; - - final List bytes = RegExp(r'\w{2}') - .allMatches(deviceID) - .map((match) => int.parse(match.group(0)!, radix: 16)) - .toList(); - final int checksumValue = bytes.reduce((a, b) => a + b); - final String check = checksumValue - .toRadixString(16) - .substring(checksumValue.toRadixString(16).length - 2); - - return deviceID + check; - } - - static String generateRandomString(int length) { - const chars = - '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - final Random random = Random(); // Random.secure(); - return List.generate(length, (index) => chars[random.nextInt(chars.length)]) - .join(); - } - // app端密码登录 static Future loginByPwd({ required String username, @@ -294,7 +225,7 @@ class LoginHttp { 'device_platform': 'Android14vivo', 'disable_rcmd': '0', 'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey)) - .encrypt(generateRandomString(16)) + .encrypt(LoginUtils.generateRandomString(16)) .base64), 'from_pv': 'main.homepage.avatar-nologin.all.click', 'from_url': Uri.encodeComponent('bilibili://pegasus/promo'), @@ -308,7 +239,7 @@ class LoginHttp { 'platform': 'android', if (recaptcha_token != null) 'recaptcha_token': recaptcha_token, 's_locale': 'zh_CN', - 'statistics': statistics, + 'statistics': Constants.statistics, 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), 'username': username, }; @@ -322,18 +253,6 @@ class LoginHttp { print('$key: $value'); return MapEntry(key, value); }); - final Map headers = { - 'Host': host, - 'buvid': buvid, - 'env': 'prod', - 'app-key': 'android_hd', - 'user-agent': userAgent, - 'x-bili-trace-id': traceId, - 'x-bili-aurora-eid': '', - 'x-bili-aurora-zone': '', - 'bili-http-engine': 'cronet', - 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', - }; var res = await Request().post( Api.loginByPwdApi, data: data, @@ -345,7 +264,11 @@ class LoginHttp { ); print(res); if (res.data['code'] == 0) { - return {'status': true, 'data': res.data['data']}; + return { + 'status': true, + 'data': res.data['data'], + 'msg': res.data['message'], + }; } else { return { 'status': false, @@ -383,7 +306,7 @@ class LoginHttp { // 'device_tourist_id': '', 'disable_rcmd': '0', 'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey)) - .encrypt(generateRandomString(16)) + .encrypt(LoginUtils.generateRandomString(16)) .base64), 'from_pv': 'main.my-information.my-login.0.click', 'from_url': Uri.encodeComponent('bilibili://user_center/mine'), @@ -391,7 +314,7 @@ class LoginHttp { 'mobi_app': 'android_hd', 'platform': 'android', 's_locale': 'zh_CN', - 'statistics': statistics, + 'statistics': Constants.statistics, 'tel': tel, 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), }; @@ -405,18 +328,6 @@ class LoginHttp { print('$key: $value'); return MapEntry(key, value); }); - final Map headers = { - 'Host': host, - 'buvid': buvid, - 'env': 'prod', - 'app-key': 'android_hd', - 'user-agent': userAgent, - 'x-bili-trace-id': traceId, - 'x-bili-aurora-eid': '', - 'x-bili-aurora-zone': '', - 'bili-http-engine': 'cronet', - 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', - }; var res = await Request().post( Api.logInByAppSms, data: data, diff --git a/lib/http/video.dart b/lib/http/video.dart index 89a61b0c..9ac839a7 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -1,4 +1,6 @@ import 'dart:developer'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; import '../common/constants.dart'; @@ -13,10 +15,12 @@ import '../models/video_detail_res.dart'; import '../utils/id_utils.dart'; import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; +import '../utils/utils.dart'; import '../utils/wbi_sign.dart'; import '../pages/mine/controller.dart'; import 'api.dart'; import 'init.dart'; +import 'login.dart'; /// res.data['code'] == 0 请求正常返回结果 /// res.data['data'] 为结果 @@ -31,91 +35,130 @@ class VideoHttp { // 首页推荐视频 static Future rcmdVideoList({required int ps, required int freshIdx}) async { - try { - var res = await Request().get( - Api.recommendListWeb, - data: { - 'version': 1, - 'feed_version': 'V8', - 'homepage_ver': 1, - 'ps': ps, - 'fresh_idx': freshIdx, - 'brush': freshIdx, - 'fresh_type': 4 - }, - ); - if (res.data['code'] == 0) { - List list = []; - List blackMidsList = localCache - .get(LocalCacheKey.blackMidsList, defaultValue: [-1]) - .map((e) => e as int) - .toList(); - for (var i in res.data['data']['item']) { - //过滤掉live与ad,以及拉黑用户 - if (i['goto'] == 'av' && - (i['owner'] != null && - !blackMidsList.contains(i['owner']['mid']))) { - RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i); - if (!RecommendFilter.filter(videoItem)) { - list.add(videoItem); - } + var res = await Request().get( + Api.recommendListWeb, + data: { + 'version': 1, + 'feed_version': 'V8', + 'homepage_ver': 1, + 'ps': ps, + 'fresh_idx': freshIdx, + 'brush': freshIdx, + 'fresh_type': 4 + }, + ); + if (res.data['code'] == 0) { + List list = []; + List blackMidsList = localCache + .get(LocalCacheKey.blackMidsList, defaultValue: [-1]) + .map((e) => e as int) + .toList(); + for (var i in res.data['data']['item']) { + //过滤掉live与ad,以及拉黑用户 + if (i['goto'] == 'av' && + (i['owner'] != null && + !blackMidsList.contains(i['owner']['mid']))) { + RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i); + if (!RecommendFilter.filter(videoItem)) { + list.add(videoItem); } } - return {'status': true, 'data': list}; - } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; } - } catch (err) { - return {'status': false, 'data': [], 'msg': err.toString()}; + return {'status': true, 'data': list}; + } else { + return {'status': false, 'data': [], 'msg': res.data['message']}; } } // 添加额外的loginState变量模拟未登录状态 static Future rcmdVideoListApp( {bool loginStatus = true, required int freshIdx}) async { - try { - var res = await Request().get( - Api.recommendListApp, - data: { - 'idx': freshIdx, - 'flush': '5', - 'column': '4', - 'device': 'pad', - 'device_type': 0, - 'device_name': 'vivo', - 'pull': freshIdx == 0 ? 'true' : 'false', - 'appkey': Constants.appKey, - 'access_key': loginStatus - ? (localCache.get(LocalCacheKey.accessKey, - defaultValue: {})['value'] ?? - '') - : '' - }, - ); - if (res.data['code'] == 0) { - List list = []; - List blackMidsList = localCache - .get(LocalCacheKey.blackMidsList, defaultValue: [-1]) - .map((e) => e as int) - .toList(); - for (var i in res.data['data']['items']) { - // 屏蔽推广和拉黑用户 - if (i['card_goto'] != 'ad_av' && - (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && - (i['args'] != null && - !blackMidsList.contains(i['args']['up_mid']))) { - RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); - if (!RecommendFilter.filter(videoItem)) { - list.add(videoItem); - } + var data = { + 'access_key': loginStatus + ? (localCache + .get(LocalCacheKey.accessKey, defaultValue: {})['value'] ?? + '') + : '', + 'appkey': Constants.appKey, + 'build': '1462100', + 'c_locale': 'zh_CN', + 'channel': 'yingyongbao', + 'column': '4', + 'device': 'pad', + 'device_name': 'vivo', + 'device_type': '0', + 'disable_rcmd': '0', + 'flush': '5', + 'fnval': '976', + 'fnver': '0', + 'force_host': '2', //使用https + 'fourk': '1', + 'guidance': '0', + 'https_url_req': '0', + 'idx': freshIdx.toString(), + 'mobi_app': 'android_hd', + 'network': 'wifi', + 'platform': 'android', + 'player_net': '1', + 'pull': freshIdx == 0 ? 'true' : 'false', + 'qn': '32', + 'recsys_mode': '0', + 's_locale': 'zh_CN', + 'splash_id': '', + 'statistics': Constants.statistics, + 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + 'voice_balance': '0' + }; + String sign = Utils.appSign( + data, + Constants.appKey, + Constants.appSec, + ); + data['sign'] = sign; + + var res = await Request().get( + Api.recommendListApp, + data: data, + options: Options(headers: { + 'Host': 'app.bilibili.com', + 'buvid': LoginHttp.buvid, + 'fp_local': + '1111111111111111111111111111111111111111111111111111111111111111', + 'fp_remote': + '1111111111111111111111111111111111111111111111111111111111111111', + 'session_id': '11111111', + 'env': 'prod', + 'app-key': 'android_hd', + 'User-Agent': Constants.userAgent, + 'x-bili-trace-id': Constants.traceId, + 'x-bili-aurora-eid': '', + 'x-bili-aurora-zone': '', + 'bili-http-engine': 'cronet', + }), + ); + if (res.data['code'] == 0) { + List list = []; + List blackMidsList = localCache + .get(LocalCacheKey.blackMidsList, defaultValue: [-1]) + .map((e) => e as int) + .toList(); + for (var i in res.data['data']['items']) { + // 屏蔽推广和拉黑用户 + if (i['card_goto'] != 'ad_av' && + i['card_goto'] != 'ad_web_s' && + i['ad_info'] == null && + (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && + (i['args'] != null && + !blackMidsList.contains(i['args']['up_id']))) { + RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); + if (!RecommendFilter.filter(videoItem)) { + list.add(videoItem); } } - return {'status': true, 'data': list}; - } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; } - } catch (err) { - return {'status': false, 'data': [], 'msg': err.toString()}; + return {'status': true, 'data': list}; + } else { + return {'status': false, 'data': [], 'msg': res.data['message']}; } } diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index 79a1f7d7..2a4acf03 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -35,7 +35,7 @@ class RecVideoItemAppModel { String? title; int? isFollowed; RcmdOwner? owner; - RcmdReason? rcmdReason; + String? rcmdReason; String? goto; int? param; String? uri; @@ -66,14 +66,10 @@ class RecVideoItemAppModel { //duration = json['cover_right_text']; title = json['title']; owner = RcmdOwner.fromJson(json); - rcmdReason = json['rcmd_reason_style'] != null - ? RcmdReason.fromJson(json['rcmd_reason_style']) - : null; + rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason']; // 由于app端api并不会直接返回与owner的关注状态 // 所以借用推荐原因是否为“已关注”、“新关注”判别关注状态,从而与web端接口等效 - String rcmdReasonContent = rcmdReason?.content ?? ''; - isFollowed = - (rcmdReasonContent == '已关注') || (rcmdReasonContent == '新关注') ? 1 : 0; + isFollowed = (rcmdReason == '已关注') || (rcmdReason == '新关注') ? 1 : 0; // 如果是,就无需再显示推荐原因,交由view统一处理即可 if (isFollowed == 1) { rcmdReason = null; @@ -86,7 +82,7 @@ class RecVideoItemAppModel { if (json['goto'] == 'bangumi') { bangumiView = json['cover_left_text_1']; bangumiFollow = json['cover_left_text_2']; - bangumiBadge = json['badge']; + bangumiBadge = json['cover_right_text']; } cardType = json['card_type']; @@ -129,18 +125,6 @@ class RcmdOwner { } } -class RcmdReason { - RcmdReason({ - this.content, - }); - - String? content; - - RcmdReason.fromJson(Map json) { - content = json["text"] ?? ''; - } -} - class ThreePoint { ThreePoint({ this.dislikeReasons, diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart index 1503f192..8c0bea44 100644 --- a/lib/models/model_rec_video_item.dart +++ b/lib/models/model_rec_video_item.dart @@ -46,7 +46,7 @@ class RecVideoItemModel { @HiveField(11) int? isFollowed; @HiveField(12) - RcmdReason? rcmdReason; + String? rcmdReason; RecVideoItemModel.fromJson(Map json) { id = json["id"]; @@ -61,9 +61,10 @@ class RecVideoItemModel { owner = Owner.fromJson(json["owner"]); stat = Stat.fromJson(json["stat"]); isFollowed = json["is_followed"] ?? 0; - rcmdReason = json["rcmd_reason"] != null - ? RcmdReason.fromJson(json["rcmd_reason"]) - : RcmdReason(content: ''); + // rcmdReason = json["rcmd_reason"] != null + // ? RcmdReason.fromJson(json["rcmd_reason"]) + // : RcmdReason(content: ''); + rcmdReason = json["rcmd_reason"]?['content']; } } @@ -89,19 +90,19 @@ class Stat { } } -@HiveType(typeId: 2) -class RcmdReason { - RcmdReason({ - this.reasonType, - this.content, - }); - @HiveField(0) - int? reasonType; - @HiveField(1) - String? content = ''; - - RcmdReason.fromJson(Map json) { - reasonType = json["reason_type"]; - content = json["content"] ?? ''; - } -} +// @HiveType(typeId: 2) +// class RcmdReason { +// RcmdReason({ +// this.reasonType, +// this.content, +// }); +// @HiveField(0) +// int? reasonType; +// @HiveField(1) +// String? content = ''; +// +// RcmdReason.fromJson(Map json) { +// reasonType = json["reason_type"]; +// content = json["content"] ?? ''; +// } +// } diff --git a/lib/models/model_rec_video_item.g.dart b/lib/models/model_rec_video_item.g.dart index dc614354..7c526e5f 100644 --- a/lib/models/model_rec_video_item.g.dart +++ b/lib/models/model_rec_video_item.g.dart @@ -29,7 +29,7 @@ class RecVideoItemModelAdapter extends TypeAdapter { owner: fields[9] as Owner?, stat: fields[10] as Stat?, isFollowed: fields[11] as int?, - rcmdReason: fields[12] as RcmdReason?, + rcmdReason: fields[12] as String?, ); } @@ -115,40 +115,40 @@ class StatAdapter extends TypeAdapter { runtimeType == other.runtimeType && typeId == other.typeId; } - -class RcmdReasonAdapter extends TypeAdapter { - @override - final int typeId = 2; - - @override - RcmdReason read(BinaryReader reader) { - final numOfFields = reader.readByte(); - final fields = { - for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), - }; - return RcmdReason( - reasonType: fields[0] as int?, - content: fields[1] as String?, - ); - } - - @override - void write(BinaryWriter writer, RcmdReason obj) { - writer - ..writeByte(2) - ..writeByte(0) - ..write(obj.reasonType) - ..writeByte(1) - ..write(obj.content); - } - - @override - int get hashCode => typeId.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is RcmdReasonAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; -} +// +// class RcmdReasonAdapter extends TypeAdapter { +// @override +// final int typeId = 2; +// +// @override +// RcmdReason read(BinaryReader reader) { +// final numOfFields = reader.readByte(); +// final fields = { +// for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), +// }; +// return RcmdReason( +// reasonType: fields[0] as int?, +// content: fields[1] as String?, +// ); +// } +// +// @override +// void write(BinaryWriter writer, RcmdReason obj) { +// writer +// ..writeByte(2) +// ..writeByte(0) +// ..write(obj.reasonType) +// ..writeByte(1) +// ..write(obj.content); +// } +// +// @override +// int get hashCode => typeId.hashCode; +// +// @override +// bool operator ==(Object other) => +// identical(this, other) || +// other is RcmdReasonAdapter && +// runtimeType == other.runtimeType && +// typeId == other.typeId; +// } diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index bbe07bd0..c1b19b9a 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:PiliPalaX/http/video.dart'; @@ -11,7 +12,6 @@ class RcmdController extends GetxController { int _currentPage = 0; // RxList appVideoList = [].obs; // RxList webVideoList = [].obs; - bool isLoadingMore = true; OverlayEntry? popupDialog; Box setting = GStorage.setting; RxInt crossAxisCount = 2.obs; @@ -35,9 +35,6 @@ class RcmdController extends GetxController { // 获取推荐 Future queryRcmdFeed(type) async { - if (isLoadingMore == false) { - return; - } if (type == 'onRefresh') { _currentPage = 0; } @@ -75,17 +72,22 @@ class RcmdController extends GetxController { _currentPage += 1; // 若videoList数量太小,可能会影响翻页,此时再次请求 // 为避免请求到的数据太少时还在反复请求,要求本次返回数据大于1条才触发 - if (res['data'].length > 1 && videoList.length < 22) { - queryRcmdFeed('onLoad'); + if (res['data'].length > 1 && videoList.length < 24) { + Future.delayed(const Duration(milliseconds: 300), () { + if (videoList.length < 24) queryRcmdFeed('onLoad'); + }); } + if (res['data'].length < 5) { + SmartDialog.showToast("仅请求到${res['data'].length}条"); + } + } else { + SmartDialog.showToast("${res['msg']},请尝试(重新)登录"); } - isLoadingMore = false; return res; } // 下拉刷新 Future onRefresh() async { - isLoadingMore = true; queryRcmdFeed('onRefresh'); } diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index cb3ad215..25c47331 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -46,7 +46,6 @@ class _RcmdPageState extends State scrollController.position.maxScrollExtent - 200) { EasyThrottle.throttle( 'my-throttler', const Duration(milliseconds: 200), () { - _rcmdController.isLoadingMore = true; _rcmdController.onLoad(); }); } @@ -99,23 +98,17 @@ class _RcmdPageState extends State Map data = snapshot.data as Map; if (data['status']) { return Obx( - () { - if (_rcmdController.isLoadingMore && - _rcmdController.videoList.isEmpty) { - return contentGrid(_rcmdController, []); - } else { - // 显示视频列表 - return contentGrid( - _rcmdController, _rcmdController.videoList); - } - }, + () => contentGrid( + _rcmdController, + _rcmdController.videoList.isEmpty + ? [] + : _rcmdController.videoList), ); } else { return HttpError( errMsg: data == null ? "" : data['msg'], fn: () { setState(() { - _rcmdController.isLoadingMore = true; _futureBuilderFuture = _rcmdController.queryRcmdFeed('init'); }); diff --git a/lib/utils/login.dart b/lib/utils/login.dart index b141e44b..3a6a2ae8 100644 --- a/lib/utils/login.dart +++ b/lib/utils/login.dart @@ -57,4 +57,41 @@ class LoginUtils { String uuid = getUUID() + getUUID(); return 'XY${uuid.substring(0, 35).toUpperCase()}'; } + + static String genDeviceId() { + // https://github.com/bilive/bilive_client/blob/2873de0532c54832f5464a4c57325ad9af8b8698/bilive/lib/app_client.ts#L62 + final String yyyyMMddHHmmss = DateTime.now() + .toIso8601String() + .replaceAll(RegExp(r'[-:TZ]'), '') + .substring(0, 14); + + final Random random = Random(); // Random.secure(); + final String randomHex32 = + List.generate(32, (index) => random.nextInt(16).toRadixString(16)) + .join(); + final String randomHex16 = + List.generate(16, (index) => random.nextInt(16).toRadixString(16)) + .join(); + + final String deviceID = randomHex32 + yyyyMMddHHmmss + randomHex16; + + final List bytes = RegExp(r'\w{2}') + .allMatches(deviceID) + .map((match) => int.parse(match.group(0)!, radix: 16)) + .toList(); + final int checksumValue = bytes.reduce((a, b) => a + b); + final String check = checksumValue + .toRadixString(16) + .substring(checksumValue.toRadixString(16).length - 2); + + return deviceID + check; + } + + static String generateRandomString(int length) { + const chars = + '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + final Random random = Random(); // Random.secure(); + return List.generate(length, (index) => chars[random.nextInt(chars.length)]) + .join(); + } } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 70cee6c6..1dc4cfed 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -100,8 +100,8 @@ class Utils { if (!emptyStatCheck(videoItem.stat.danmu)) { semanticsLabel += ',${Utils.numFormat(videoItem.stat.danmu)}弹幕'; } - if (videoItem.rcmdReason != null && videoItem.rcmdReason.content != '') { - semanticsLabel += ',${videoItem.rcmdReason.content}'; + if (videoItem.rcmdReason != null) { + semanticsLabel += ',${videoItem.rcmdReason}'; } if (!emptyStatCheck(videoItem.duration) && (videoItem.duration is! int || videoItem.duration > 0)) {