mod: app端推荐参数补充,统一部分代码位置

This commit is contained in:
orz12
2024-07-11 17:17:22 +08:00
parent 376dae8570
commit 212a87b84c
11 changed files with 290 additions and 306 deletions

View File

@@ -18,6 +18,13 @@ class Constants {
static const String thirdApi = static const String thirdApi =
'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png'; '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 //内容来自 https://passport.bilibili.com/web/generic/country/list
static const List<Map<String, dynamic>> internationalDialingPrefix = [ static const List<Map<String, dynamic>> internationalDialingPrefix = [
{"id": 1, "cname": "中国大陆", "country_id": "86"}, {"id": 1, "cname": "中国大陆", "country_id": "86"},

View File

@@ -187,8 +187,8 @@ class VideoCardV extends StatelessWidget {
), ),
if (videoItem.goto == 'av') if (videoItem.goto == 'av')
Positioned( Positioned(
right: 0, right: -5,
bottom: 0, bottom: -2,
child: VideoPopupMenu( child: VideoPopupMenu(
size: 29, size: 29,
iconSize: 17, iconSize: 17,
@@ -239,10 +239,9 @@ class VideoContent extends StatelessWidget {
fs: 9, fs: 9,
) )
], ],
if (videoItem.rcmdReason != null && if (videoItem.rcmdReason != null) ...[
videoItem.rcmdReason.content != '') ...[
PBadge( PBadge(
text: videoItem.rcmdReason.content, text: videoItem.rcmdReason,
stack: 'normal', stack: 'normal',
size: 'small', size: 'small',
type: 'color', 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, view: videoItem.stat.view,
goto: videoItem.goto, goto: videoItem.goto,
), ),
const SizedBox(width: 8), const SizedBox(width: 6),
if (videoItem.goto != 'picture') if (videoItem.goto != 'picture')
StatDanMu( StatDanMu(
theme: 'gray', theme: 'gray',
@@ -315,16 +314,23 @@ class VideoStat extends StatelessWidget {
), ),
if (videoItem is RecVideoItemModel) ...<Widget>[ if (videoItem is RecVideoItemModel) ...<Widget>[
const Spacer(), const Spacer(),
RichText( Expanded(
maxLines: 1, flex: 0,
text: TextSpan( child: RichText(
style: TextStyle( maxLines: 1,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, text: TextSpan(
color: Theme.of(context).colorScheme.outline.withOpacity(0.8), style: TextStyle(
), fontSize:
text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)), Theme.of(context).textTheme.labelSmall!.fontSize,
), color: Theme.of(context)
const SizedBox(width: 4), .colorScheme
.outline
.withOpacity(0.8),
),
text:
Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
)),
const SizedBox(width: 2),
] ]
], ],
); );

View File

@@ -5,19 +5,27 @@ import 'package:dio/dio.dart';
import 'package:encrypt/encrypt.dart'; import 'package:encrypt/encrypt.dart';
import '../common/constants.dart'; import '../common/constants.dart';
import '../models/login/index.dart'; import '../models/login/index.dart';
import '../utils/login.dart';
import '../utils/utils.dart'; import '../utils/utils.dart';
import 'index.dart'; import 'index.dart';
class LoginHttp { class LoginHttp {
static String deviceId = genDeviceId(); static String deviceId = LoginUtils.genDeviceId();
static String buvid = genBuvid(); static String buvid = LoginUtils.buvid();
static String host = 'passport.bilibili.com'; static String host = 'passport.bilibili.com';
static String traceId = static Map<String, String> headers = {
'11111111111111111111111111111111:1111111111111111:0:0'; 'Host': host,
static String statistics = Uri.encodeComponent( 'buvid': buvid,
'{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}'); 'env': 'prod',
static String userAgent = 'app-key': 'android_hd',
'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'; '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<Map<String, dynamic>> getHDcode() async { static Future<Map<String, dynamic>> getHDcode() async {
var params = { var params = {
'appkey': Constants.appKey, 'appkey': Constants.appKey,
@@ -117,7 +125,7 @@ class LoginHttp {
'platform': 'android', 'platform': 'android',
if (recaptcha_token != null) 'recaptcha_token': recaptcha_token, if (recaptcha_token != null) 'recaptcha_token': recaptcha_token,
's_locale': 'zh_CN', 's_locale': 'zh_CN',
'statistics': statistics, 'statistics': Constants.statistics,
'tel': tel, 'tel': tel,
'ts': (timestamp ~/ 1000).toString(), 'ts': (timestamp ~/ 1000).toString(),
}; };
@@ -126,18 +134,6 @@ class LoginHttp {
Constants.appKey, Constants.appKey,
Constants.appSec, 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( var res = await Request().post(
Api.appSmsCode, Api.appSmsCode,
@@ -177,7 +173,7 @@ class LoginHttp {
// 'mobi_app': 'android_hd', // 'mobi_app': 'android_hd',
// 'platform': 'android', // 'platform': 'android',
// 's_locale': 'zh_CN', // 's_locale': 'zh_CN',
// 'statistics': statistics, // 'statistics': Constants.statistics,
// 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), // 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
// }; // };
// String sign = Utils.appSign( // String sign = Utils.appSign(
@@ -185,18 +181,6 @@ class LoginHttp {
// Constants.appKey, // Constants.appKey,
// Constants.appSec, // 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, // var res = await Request().post(Api.getGuestId,
// queryParameters: {...params, 'sign': sign}, // queryParameters: {...params, 'sign': sign},
// options: Options( // options: Options(
@@ -211,59 +195,6 @@ class LoginHttp {
// } // }
// } // }
static String genBuvid() {
var mac = <String>[];
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<int> 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端密码登录 // app端密码登录
static Future loginByPwd({ static Future loginByPwd({
required String username, required String username,
@@ -294,7 +225,7 @@ class LoginHttp {
'device_platform': 'Android14vivo', 'device_platform': 'Android14vivo',
'disable_rcmd': '0', 'disable_rcmd': '0',
'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey)) 'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey))
.encrypt(generateRandomString(16)) .encrypt(LoginUtils.generateRandomString(16))
.base64), .base64),
'from_pv': 'main.homepage.avatar-nologin.all.click', 'from_pv': 'main.homepage.avatar-nologin.all.click',
'from_url': Uri.encodeComponent('bilibili://pegasus/promo'), 'from_url': Uri.encodeComponent('bilibili://pegasus/promo'),
@@ -308,7 +239,7 @@ class LoginHttp {
'platform': 'android', 'platform': 'android',
if (recaptcha_token != null) 'recaptcha_token': recaptcha_token, if (recaptcha_token != null) 'recaptcha_token': recaptcha_token,
's_locale': 'zh_CN', 's_locale': 'zh_CN',
'statistics': statistics, 'statistics': Constants.statistics,
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
'username': username, 'username': username,
}; };
@@ -322,18 +253,6 @@ class LoginHttp {
print('$key: $value'); print('$key: $value');
return MapEntry<String, dynamic>(key, value); return MapEntry<String, dynamic>(key, value);
}); });
final Map<String, String> 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( var res = await Request().post(
Api.loginByPwdApi, Api.loginByPwdApi,
data: data, data: data,
@@ -345,7 +264,11 @@ class LoginHttp {
); );
print(res); print(res);
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {
'status': true,
'data': res.data['data'],
'msg': res.data['message'],
};
} else { } else {
return { return {
'status': false, 'status': false,
@@ -383,7 +306,7 @@ class LoginHttp {
// 'device_tourist_id': '', // 'device_tourist_id': '',
'disable_rcmd': '0', 'disable_rcmd': '0',
'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey)) 'dt': Uri.encodeComponent(Encrypter(RSA(publicKey: publicKey))
.encrypt(generateRandomString(16)) .encrypt(LoginUtils.generateRandomString(16))
.base64), .base64),
'from_pv': 'main.my-information.my-login.0.click', 'from_pv': 'main.my-information.my-login.0.click',
'from_url': Uri.encodeComponent('bilibili://user_center/mine'), 'from_url': Uri.encodeComponent('bilibili://user_center/mine'),
@@ -391,7 +314,7 @@ class LoginHttp {
'mobi_app': 'android_hd', 'mobi_app': 'android_hd',
'platform': 'android', 'platform': 'android',
's_locale': 'zh_CN', 's_locale': 'zh_CN',
'statistics': statistics, 'statistics': Constants.statistics,
'tel': tel, 'tel': tel,
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
}; };
@@ -405,18 +328,6 @@ class LoginHttp {
print('$key: $value'); print('$key: $value');
return MapEntry<String, dynamic>(key, value); return MapEntry<String, dynamic>(key, value);
}); });
final Map<String, String> 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( var res = await Request().post(
Api.logInByAppSms, Api.logInByAppSms,
data: data, data: data,

View File

@@ -1,4 +1,6 @@
import 'dart:developer'; import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import '../common/constants.dart'; import '../common/constants.dart';
@@ -13,10 +15,12 @@ import '../models/video_detail_res.dart';
import '../utils/id_utils.dart'; import '../utils/id_utils.dart';
import '../utils/recommend_filter.dart'; import '../utils/recommend_filter.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
import '../utils/utils.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
import '../pages/mine/controller.dart'; import '../pages/mine/controller.dart';
import 'api.dart'; import 'api.dart';
import 'init.dart'; import 'init.dart';
import 'login.dart';
/// res.data['code'] == 0 请求正常返回结果 /// res.data['code'] == 0 请求正常返回结果
/// res.data['data'] 为结果 /// res.data['data'] 为结果
@@ -31,91 +35,130 @@ class VideoHttp {
// 首页推荐视频 // 首页推荐视频
static Future rcmdVideoList({required int ps, required int freshIdx}) async { static Future rcmdVideoList({required int ps, required int freshIdx}) async {
try { var res = await Request().get(
var res = await Request().get( Api.recommendListWeb,
Api.recommendListWeb, data: {
data: { 'version': 1,
'version': 1, 'feed_version': 'V8',
'feed_version': 'V8', 'homepage_ver': 1,
'homepage_ver': 1, 'ps': ps,
'ps': ps, 'fresh_idx': freshIdx,
'fresh_idx': freshIdx, 'brush': freshIdx,
'brush': freshIdx, 'fresh_type': 4
'fresh_type': 4 },
}, );
); if (res.data['code'] == 0) {
if (res.data['code'] == 0) { List<RecVideoItemModel> list = [];
List<RecVideoItemModel> list = []; List<int> blackMidsList = localCache
List<int> blackMidsList = localCache .get(LocalCacheKey.blackMidsList, defaultValue: [-1])
.get(LocalCacheKey.blackMidsList, defaultValue: [-1]) .map<int>((e) => e as int)
.map<int>((e) => e as int) .toList();
.toList(); for (var i in res.data['data']['item']) {
for (var i in res.data['data']['item']) { //过滤掉live与ad以及拉黑用户
//过滤掉live与ad以及拉黑用户 if (i['goto'] == 'av' &&
if (i['goto'] == 'av' && (i['owner'] != null &&
(i['owner'] != null && !blackMidsList.contains(i['owner']['mid']))) {
!blackMidsList.contains(i['owner']['mid']))) { RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i);
RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i); if (!RecommendFilter.filter(videoItem)) {
if (!RecommendFilter.filter(videoItem)) { list.add(videoItem);
list.add(videoItem);
}
} }
} }
return {'status': true, 'data': list};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} catch (err) { return {'status': true, 'data': list};
return {'status': false, 'data': [], 'msg': err.toString()}; } else {
return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} }
// 添加额外的loginState变量模拟未登录状态 // 添加额外的loginState变量模拟未登录状态
static Future rcmdVideoListApp( static Future rcmdVideoListApp(
{bool loginStatus = true, required int freshIdx}) async { {bool loginStatus = true, required int freshIdx}) async {
try { var data = {
var res = await Request().get( 'access_key': loginStatus
Api.recommendListApp, ? (localCache
data: { .get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
'idx': freshIdx, '')
'flush': '5', : '',
'column': '4', 'appkey': Constants.appKey,
'device': 'pad', 'build': '1462100',
'device_type': 0, 'c_locale': 'zh_CN',
'device_name': 'vivo', 'channel': 'yingyongbao',
'pull': freshIdx == 0 ? 'true' : 'false', 'column': '4',
'appkey': Constants.appKey, 'device': 'pad',
'access_key': loginStatus 'device_name': 'vivo',
? (localCache.get(LocalCacheKey.accessKey, 'device_type': '0',
defaultValue: {})['value'] ?? 'disable_rcmd': '0',
'') 'flush': '5',
: '' 'fnval': '976',
}, 'fnver': '0',
); 'force_host': '2', //使用https
if (res.data['code'] == 0) { 'fourk': '1',
List<RecVideoItemAppModel> list = []; 'guidance': '0',
List<int> blackMidsList = localCache 'https_url_req': '0',
.get(LocalCacheKey.blackMidsList, defaultValue: [-1]) 'idx': freshIdx.toString(),
.map<int>((e) => e as int) 'mobi_app': 'android_hd',
.toList(); 'network': 'wifi',
for (var i in res.data['data']['items']) { 'platform': 'android',
// 屏蔽推广和拉黑用户 'player_net': '1',
if (i['card_goto'] != 'ad_av' && 'pull': freshIdx == 0 ? 'true' : 'false',
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && 'qn': '32',
(i['args'] != null && 'recsys_mode': '0',
!blackMidsList.contains(i['args']['up_mid']))) { 's_locale': 'zh_CN',
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); 'splash_id': '',
if (!RecommendFilter.filter(videoItem)) { 'statistics': Constants.statistics,
list.add(videoItem); '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<RecVideoItemAppModel> list = [];
List<int> blackMidsList = localCache
.get(LocalCacheKey.blackMidsList, defaultValue: [-1])
.map<int>((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': true, 'data': list};
return {'status': false, 'data': [], 'msg': err.toString()}; } else {
return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} }

View File

@@ -35,7 +35,7 @@ class RecVideoItemAppModel {
String? title; String? title;
int? isFollowed; int? isFollowed;
RcmdOwner? owner; RcmdOwner? owner;
RcmdReason? rcmdReason; String? rcmdReason;
String? goto; String? goto;
int? param; int? param;
String? uri; String? uri;
@@ -66,14 +66,10 @@ class RecVideoItemAppModel {
//duration = json['cover_right_text']; //duration = json['cover_right_text'];
title = json['title']; title = json['title'];
owner = RcmdOwner.fromJson(json); owner = RcmdOwner.fromJson(json);
rcmdReason = json['rcmd_reason_style'] != null rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason'];
? RcmdReason.fromJson(json['rcmd_reason_style'])
: null;
// 由于app端api并不会直接返回与owner的关注状态 // 由于app端api并不会直接返回与owner的关注状态
// 所以借用推荐原因是否为“已关注”、“新关注”判别关注状态从而与web端接口等效 // 所以借用推荐原因是否为“已关注”、“新关注”判别关注状态从而与web端接口等效
String rcmdReasonContent = rcmdReason?.content ?? ''; isFollowed = (rcmdReason == '已关注') || (rcmdReason == '新关注') ? 1 : 0;
isFollowed =
(rcmdReasonContent == '已关注') || (rcmdReasonContent == '新关注') ? 1 : 0;
// 如果是就无需再显示推荐原因交由view统一处理即可 // 如果是就无需再显示推荐原因交由view统一处理即可
if (isFollowed == 1) { if (isFollowed == 1) {
rcmdReason = null; rcmdReason = null;
@@ -86,7 +82,7 @@ class RecVideoItemAppModel {
if (json['goto'] == 'bangumi') { if (json['goto'] == 'bangumi') {
bangumiView = json['cover_left_text_1']; bangumiView = json['cover_left_text_1'];
bangumiFollow = json['cover_left_text_2']; bangumiFollow = json['cover_left_text_2'];
bangumiBadge = json['badge']; bangumiBadge = json['cover_right_text'];
} }
cardType = json['card_type']; cardType = json['card_type'];
@@ -129,18 +125,6 @@ class RcmdOwner {
} }
} }
class RcmdReason {
RcmdReason({
this.content,
});
String? content;
RcmdReason.fromJson(Map<String, dynamic> json) {
content = json["text"] ?? '';
}
}
class ThreePoint { class ThreePoint {
ThreePoint({ ThreePoint({
this.dislikeReasons, this.dislikeReasons,

View File

@@ -46,7 +46,7 @@ class RecVideoItemModel {
@HiveField(11) @HiveField(11)
int? isFollowed; int? isFollowed;
@HiveField(12) @HiveField(12)
RcmdReason? rcmdReason; String? rcmdReason;
RecVideoItemModel.fromJson(Map<String, dynamic> json) { RecVideoItemModel.fromJson(Map<String, dynamic> json) {
id = json["id"]; id = json["id"];
@@ -61,9 +61,10 @@ class RecVideoItemModel {
owner = Owner.fromJson(json["owner"]); owner = Owner.fromJson(json["owner"]);
stat = Stat.fromJson(json["stat"]); stat = Stat.fromJson(json["stat"]);
isFollowed = json["is_followed"] ?? 0; isFollowed = json["is_followed"] ?? 0;
rcmdReason = json["rcmd_reason"] != null // rcmdReason = json["rcmd_reason"] != null
? RcmdReason.fromJson(json["rcmd_reason"]) // ? RcmdReason.fromJson(json["rcmd_reason"])
: RcmdReason(content: ''); // : RcmdReason(content: '');
rcmdReason = json["rcmd_reason"]?['content'];
} }
} }
@@ -89,19 +90,19 @@ class Stat {
} }
} }
@HiveType(typeId: 2) // @HiveType(typeId: 2)
class RcmdReason { // class RcmdReason {
RcmdReason({ // RcmdReason({
this.reasonType, // this.reasonType,
this.content, // this.content,
}); // });
@HiveField(0) // @HiveField(0)
int? reasonType; // int? reasonType;
@HiveField(1) // @HiveField(1)
String? content = ''; // String? content = '';
//
RcmdReason.fromJson(Map<String, dynamic> json) { // RcmdReason.fromJson(Map<String, dynamic> json) {
reasonType = json["reason_type"]; // reasonType = json["reason_type"];
content = json["content"] ?? ''; // content = json["content"] ?? '';
} // }
} // }

View File

@@ -29,7 +29,7 @@ class RecVideoItemModelAdapter extends TypeAdapter<RecVideoItemModel> {
owner: fields[9] as Owner?, owner: fields[9] as Owner?,
stat: fields[10] as Stat?, stat: fields[10] as Stat?,
isFollowed: fields[11] as int?, isFollowed: fields[11] as int?,
rcmdReason: fields[12] as RcmdReason?, rcmdReason: fields[12] as String?,
); );
} }
@@ -115,40 +115,40 @@ class StatAdapter extends TypeAdapter<Stat> {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
typeId == other.typeId; typeId == other.typeId;
} }
//
class RcmdReasonAdapter extends TypeAdapter<RcmdReason> { // class RcmdReasonAdapter extends TypeAdapter<RcmdReason> {
@override // @override
final int typeId = 2; // final int typeId = 2;
//
@override // @override
RcmdReason read(BinaryReader reader) { // RcmdReason read(BinaryReader reader) {
final numOfFields = reader.readByte(); // final numOfFields = reader.readByte();
final fields = <int, dynamic>{ // final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), // for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; // };
return RcmdReason( // return RcmdReason(
reasonType: fields[0] as int?, // reasonType: fields[0] as int?,
content: fields[1] as String?, // content: fields[1] as String?,
); // );
} // }
//
@override // @override
void write(BinaryWriter writer, RcmdReason obj) { // void write(BinaryWriter writer, RcmdReason obj) {
writer // writer
..writeByte(2) // ..writeByte(2)
..writeByte(0) // ..writeByte(0)
..write(obj.reasonType) // ..write(obj.reasonType)
..writeByte(1) // ..writeByte(1)
..write(obj.content); // ..write(obj.content);
} // }
//
@override // @override
int get hashCode => typeId.hashCode; // int get hashCode => typeId.hashCode;
//
@override // @override
bool operator ==(Object other) => // bool operator ==(Object other) =>
identical(this, other) || // identical(this, other) ||
other is RcmdReasonAdapter && // other is RcmdReasonAdapter &&
runtimeType == other.runtimeType && // runtimeType == other.runtimeType &&
typeId == other.typeId; // typeId == other.typeId;
} // }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:PiliPalaX/http/video.dart'; import 'package:PiliPalaX/http/video.dart';
@@ -11,7 +12,6 @@ class RcmdController extends GetxController {
int _currentPage = 0; int _currentPage = 0;
// RxList<RecVideoItemAppModel> appVideoList = <RecVideoItemAppModel>[].obs; // RxList<RecVideoItemAppModel> appVideoList = <RecVideoItemAppModel>[].obs;
// RxList<RecVideoItemModel> webVideoList = <RecVideoItemModel>[].obs; // RxList<RecVideoItemModel> webVideoList = <RecVideoItemModel>[].obs;
bool isLoadingMore = true;
OverlayEntry? popupDialog; OverlayEntry? popupDialog;
Box setting = GStorage.setting; Box setting = GStorage.setting;
RxInt crossAxisCount = 2.obs; RxInt crossAxisCount = 2.obs;
@@ -35,9 +35,6 @@ class RcmdController extends GetxController {
// 获取推荐 // 获取推荐
Future queryRcmdFeed(type) async { Future queryRcmdFeed(type) async {
if (isLoadingMore == false) {
return;
}
if (type == 'onRefresh') { if (type == 'onRefresh') {
_currentPage = 0; _currentPage = 0;
} }
@@ -75,17 +72,22 @@ class RcmdController extends GetxController {
_currentPage += 1; _currentPage += 1;
// 若videoList数量太小可能会影响翻页此时再次请求 // 若videoList数量太小可能会影响翻页此时再次请求
// 为避免请求到的数据太少时还在反复请求要求本次返回数据大于1条才触发 // 为避免请求到的数据太少时还在反复请求要求本次返回数据大于1条才触发
if (res['data'].length > 1 && videoList.length < 22) { if (res['data'].length > 1 && videoList.length < 24) {
queryRcmdFeed('onLoad'); 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; return res;
} }
// 下拉刷新 // 下拉刷新
Future onRefresh() async { Future onRefresh() async {
isLoadingMore = true;
queryRcmdFeed('onRefresh'); queryRcmdFeed('onRefresh');
} }

View File

@@ -46,7 +46,6 @@ class _RcmdPageState extends State<RcmdPage>
scrollController.position.maxScrollExtent - 200) { scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle( EasyThrottle.throttle(
'my-throttler', const Duration(milliseconds: 200), () { 'my-throttler', const Duration(milliseconds: 200), () {
_rcmdController.isLoadingMore = true;
_rcmdController.onLoad(); _rcmdController.onLoad();
}); });
} }
@@ -99,23 +98,17 @@ class _RcmdPageState extends State<RcmdPage>
Map data = snapshot.data as Map; Map data = snapshot.data as Map;
if (data['status']) { if (data['status']) {
return Obx( return Obx(
() { () => contentGrid(
if (_rcmdController.isLoadingMore && _rcmdController,
_rcmdController.videoList.isEmpty) { _rcmdController.videoList.isEmpty
return contentGrid(_rcmdController, []); ? []
} else { : _rcmdController.videoList),
// 显示视频列表
return contentGrid(
_rcmdController, _rcmdController.videoList);
}
},
); );
} else { } else {
return HttpError( return HttpError(
errMsg: data == null ? "" : data['msg'], errMsg: data == null ? "" : data['msg'],
fn: () { fn: () {
setState(() { setState(() {
_rcmdController.isLoadingMore = true;
_futureBuilderFuture = _futureBuilderFuture =
_rcmdController.queryRcmdFeed('init'); _rcmdController.queryRcmdFeed('init');
}); });

View File

@@ -57,4 +57,41 @@ class LoginUtils {
String uuid = getUUID() + getUUID(); String uuid = getUUID() + getUUID();
return 'XY${uuid.substring(0, 35).toUpperCase()}'; 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<int> 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();
}
} }

View File

@@ -100,8 +100,8 @@ class Utils {
if (!emptyStatCheck(videoItem.stat.danmu)) { if (!emptyStatCheck(videoItem.stat.danmu)) {
semanticsLabel += ',${Utils.numFormat(videoItem.stat.danmu)}弹幕'; semanticsLabel += ',${Utils.numFormat(videoItem.stat.danmu)}弹幕';
} }
if (videoItem.rcmdReason != null && videoItem.rcmdReason.content != '') { if (videoItem.rcmdReason != null) {
semanticsLabel += ',${videoItem.rcmdReason.content}'; semanticsLabel += ',${videoItem.rcmdReason}';
} }
if (!emptyStatCheck(videoItem.duration) && if (!emptyStatCheck(videoItem.duration) &&
(videoItem.duration is! int || videoItem.duration > 0)) { (videoItem.duration is! int || videoItem.duration > 0)) {