From 4abffeed32ea595c902217455f1381683476378f Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Tue, 29 Apr 2025 20:43:17 +0800 Subject: [PATCH] fix: #753 Signed-off-by: bggRGjQaUbCoE --- lib/http/api.dart | 4 + lib/http/live.dart | 19 ++- lib/http/validate.dart | 42 ++++++ lib/pages/live/controller.dart | 32 +++- .../accounts/account_manager/account_mgr.dart | 9 +- lib/utils/request_utils.dart | 137 ++++++++++++++++++ 6 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 lib/http/validate.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index c5982f67..0026129f 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -789,4 +789,8 @@ class Api { static const String articleView = '/x/article/view'; static const String opusDetail = '/x/polymer/web-dynamic/v1/opus/detail'; + + static const String gaiaVgateRegister = '/x/gaia-vgate/v1/register'; + + static const String gaiaVgateValidate = '/x/gaia-vgate/v1/validate'; } diff --git a/lib/http/live.dart b/lib/http/live.dart index 37326fcf..6a549e51 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -14,8 +14,13 @@ import 'api.dart'; import 'init.dart'; class LiveHttp { - static Future?>> liveList( - {int? vmid, int? pn, int? ps, String? orderType}) async { + static Future?>> liveList({ + int? vmid, + int? pn, + int? ps, + String? orderType, + String? gaiaVtoken, + }) async { var res = await Request().get( Api.liveList, queryParameters: await WbiSign.makSign({ @@ -23,6 +28,7 @@ class LiveHttp { 'page_size': 30, 'platform': 'web', 'web_location': 0.0, + if (gaiaVtoken != null) 'gaia_vtoken': gaiaVtoken, }), options: Options( headers: { @@ -30,6 +36,9 @@ class LiveHttp { 'referer': 'https://live.bilibili.com/', 'user-agent': Request.headerUa(type: 'pc'), }, + extra: gaiaVtoken == null + ? null + : {'cookie': 'x-bili-gaia-vtoken=$gaiaVtoken'}, ), ); if (res.data['code'] == 0) { @@ -38,7 +47,11 @@ class LiveHttp { .toList(); return LoadingState.success(list); } else { - return LoadingState.error(res.data['message']); + String? vVoucher; + if (gaiaVtoken == null && res.data['code'] == -352) { + vVoucher = res.headers['x-bili-gaia-vvoucher']?.firstOrNull; + } + return LoadingState.error(vVoucher ?? res.data['message']); } } diff --git a/lib/http/validate.dart b/lib/http/validate.dart new file mode 100644 index 00000000..21aa7195 --- /dev/null +++ b/lib/http/validate.dart @@ -0,0 +1,42 @@ +import 'package:PiliPlus/http/api.dart'; +import 'package:PiliPlus/http/init.dart'; +import 'package:dio/dio.dart'; + +class ValidateHttp { + static Future gaiaVgateRegister(String vVoucher) async { + final res = await Request().post( + Api.gaiaVgateRegister, + data: {'v_voucher': vVoucher}, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + + static Future gaiaVgateValidate({ + required challenge, + required seccode, + required token, + required validate, + }) async { + final res = await Request().post( + Api.gaiaVgateValidate, + data: { + 'challenge': challenge, + 'seccode': seccode, + 'token': token, + 'validate': validate, + }, + ); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } +} diff --git a/lib/pages/live/controller.dart b/lib/pages/live/controller.dart index 47800de9..7b0a3651 100644 --- a/lib/pages/live/controller.dart +++ b/lib/pages/live/controller.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/models/live/follow.dart'; import 'package:PiliPlus/models/live/item.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; @@ -18,9 +19,27 @@ class LiveController } } + String? gaiaVtoken; + @override Future?>> customGetData() => - LiveHttp.liveList(pn: currentPage); + LiveHttp.liveList(pn: currentPage, gaiaVtoken: gaiaVtoken); + + @override + bool handleError(String? errMsg) { + if (errMsg?.startsWith('voucher') == true) { + RequestUtils.validate( + errMsg!, + (gaiaVtoken) { + this.gaiaVtoken = gaiaVtoken; + onReload(); + }, + ); + loadingState.value = LoadingState.error(' -352 '); + return true; + } + return false; + } @override Future onRefresh() { @@ -28,6 +47,17 @@ class LiveController return super.onRefresh(); } + @override + Future onReload() { + if (loadingState.value is Error) { + String? errMsg = (loadingState.value as Error).errMsg; + if (errMsg == '-352') { + gaiaVtoken = null; + } + } + return super.onReload(); + } + late RxBool isLogin = Accounts.main.isLogin.obs; late Rx followListState = LoadingState.loading().obs; late int followPage = 1; diff --git a/lib/utils/accounts/account_manager/account_mgr.dart b/lib/utils/accounts/account_manager/account_mgr.dart index 298fdf5a..7889cfbf 100644 --- a/lib/utils/accounts/account_manager/account_mgr.dart +++ b/lib/utils/accounts/account_manager/account_mgr.dart @@ -129,13 +129,20 @@ class AccountManager extends Interceptor { account.cookieJar.loadForRequest(options.uri).then((cookies) { final previousCookies = options.headers[HttpHeaders.cookieHeader] as String?; - final newCookies = getCookies([ + String newCookies = getCookies([ ...?previousCookies ?.split(';') .where((e) => e.isNotEmpty) .map((c) => Cookie.fromSetCookieValue(c)), ...cookies, ]); + if (options.extra['cookie'] != null) { + if (newCookies.isEmpty) { + newCookies = '${options.extra['cookie']}'; + } else { + newCookies += ';${options.extra['cookie']}'; + } + } options.headers[HttpHeaders.cookieHeader] = newCookies.isNotEmpty ? newCookies : ''; handler.next(options); diff --git a/lib/utils/request_utils.dart b/lib/utils/request_utils.dart index d7c386ac..234d496a 100644 --- a/lib/utils/request_utils.dart +++ b/lib/utils/request_utils.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'dart:math'; import 'package:PiliPlus/common/widgets/radio_widget.dart'; @@ -10,8 +11,10 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/http/user.dart'; +import 'package:PiliPlus/http/validate.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/models/login/index.dart'; import 'package:PiliPlus/models/user/fav_folder.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:PiliPlus/pages/dynamics/tab/controller.dart'; @@ -23,6 +26,7 @@ import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart'; class RequestUtils { // 1:小视频(已弃用) @@ -429,4 +433,137 @@ class RequestUtils { } }); } + + static Future validate( + String vVoucher, ValueChanged onSuccess) async { + final res = await ValidateHttp.gaiaVgateRegister(vVoucher); + if (!res['status']) { + SmartDialog.showToast("${res['msg']}"); + return; + } + + if (res['data'] == null) { + SmartDialog.showToast("null data"); + return; + } + + CaptchaDataModel captchaData = CaptchaDataModel(); + + String? geeGt = res['data']?['geetest']?['gt']; + String? geeChallenge = res['data']?['geetest']?['challenge']; + captchaData.token = res['data']?['token']; + + bool isGeeArgumentValid() { + return geeGt?.isNotEmpty == true && + geeChallenge?.isNotEmpty == true && + captchaData.token?.isNotEmpty == true; + } + + if (!isGeeArgumentValid()) { + SmartDialog.showToast("参数为空"); + return; + } + + var registerData = Gt3RegisterData( + challenge: geeChallenge, + gt: geeGt, + success: true, + ); + + final Gt3FlutterPlugin captcha = Gt3FlutterPlugin(); + + captcha.addEventHandler( + onClose: (Map message) async { + SmartDialog.showToast('关闭验证'); + }, + onResult: (Map message) async { + debugPrint("Captcha result: $message"); + String code = message["code"]; + if (code == "1") { + // 发送 message["result"] 中的数据向 B 端的业务服务接口进行查询 + SmartDialog.showToast('验证成功'); + captchaData.validate = message['result']?['geetest_validate']; + captchaData.seccode = message['result']?['geetest_seccode']; + String? challenge = message['result']?['geetest_challenge']; + final res = await ValidateHttp.gaiaVgateValidate( + challenge: challenge, + seccode: captchaData.seccode, + token: captchaData.token, + validate: captchaData.validate, + ); + if (res['status']) { + onSuccess.call(captchaData.token!); + } else { + SmartDialog.showToast(res['msg']); + } + } else { + // 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried. + debugPrint("Captcha result code : $code"); + } + }, + onError: (Map message) async { + SmartDialog.showToast("Captcha onError: $message"); + String code = message["code"]; + // 处理验证中返回的错误 Handling errors returned in verification + if (Platform.isAndroid) { + // Android 平台 + if (code == "-2") { + // Dart 调用异常 Call exception + } else if (code == "-1") { + // Gt3RegisterData 参数不合法 Parameter is invalid + } else if (code == "201") { + // 网络无法访问 Network inaccessible + } else if (code == "202") { + // Json 解析错误 Analysis error + } else if (code == "204") { + // WebView 加载超时,请检查是否混淆极验 SDK Load timed out + } else if (code == "204_1") { + // WebView 加载前端页面错误,请查看日志 Error loading front-end page, please check the log + } else if (code == "204_2") { + // WebView 加载 SSLError + } else if (code == "206") { + // gettype 接口错误或返回为 null API error or return null + } else if (code == "207") { + // getphp 接口错误或返回为 null API error or return null + } else if (code == "208") { + // ajax 接口错误或返回为 null API error or return null + } else { + // 更多错误码参考开发文档 More error codes refer to the development document + // https://docs.geetest.com/sensebot/apirefer/errorcode/android + } + } + + if (Platform.isIOS) { + // iOS 平台 + if (code == "-1009") { + // 网络无法访问 Network inaccessible + } else if (code == "-1004") { + // 无法查找到 HOST Unable to find HOST + } else if (code == "-1002") { + // 非法的 URL Illegal URL + } else if (code == "-1001") { + // 网络超时 Network timeout + } else if (code == "-999") { + // 请求被意外中断, 一般由用户进行取消操作导致 The interrupted request was usually caused by the user cancelling the operation + } else if (code == "-21") { + // 使用了重复的 challenge Duplicate challenges are used + // 检查获取 challenge 是否进行了缓存 Check if the fetch challenge is cached + } else if (code == "-20") { + // 尝试过多, 重新引导用户触发验证即可 Try too many times, lead the user to request verification again + } else if (code == "-10") { + // 预判断时被封禁, 不会再进行图形验证 Banned during pre-judgment, and no more image captcha verification + } else if (code == "-2") { + // Dart 调用异常 Call exception + } else if (code == "-1") { + // Gt3RegisterData 参数不合法 Parameter is invalid + } else { + // 更多错误码参考开发文档 More error codes refer to the development document + // https://docs.geetest.com/sensebot/apirefer/errorcode/ios + } + } + }, + ); + + captcha.startCaptcha(registerData); + } }