Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-29 20:43:17 +08:00
parent 9b5628cb65
commit 4abffeed32
6 changed files with 238 additions and 5 deletions

View File

@@ -789,4 +789,8 @@ class Api {
static const String articleView = '/x/article/view'; static const String articleView = '/x/article/view';
static const String opusDetail = '/x/polymer/web-dynamic/v1/opus/detail'; 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';
} }

View File

@@ -14,8 +14,13 @@ import 'api.dart';
import 'init.dart'; import 'init.dart';
class LiveHttp { class LiveHttp {
static Future<LoadingState<List<LiveItemModel>?>> liveList( static Future<LoadingState<List<LiveItemModel>?>> liveList({
{int? vmid, int? pn, int? ps, String? orderType}) async { int? vmid,
int? pn,
int? ps,
String? orderType,
String? gaiaVtoken,
}) async {
var res = await Request().get( var res = await Request().get(
Api.liveList, Api.liveList,
queryParameters: await WbiSign.makSign({ queryParameters: await WbiSign.makSign({
@@ -23,6 +28,7 @@ class LiveHttp {
'page_size': 30, 'page_size': 30,
'platform': 'web', 'platform': 'web',
'web_location': 0.0, 'web_location': 0.0,
if (gaiaVtoken != null) 'gaia_vtoken': gaiaVtoken,
}), }),
options: Options( options: Options(
headers: { headers: {
@@ -30,6 +36,9 @@ class LiveHttp {
'referer': 'https://live.bilibili.com/', 'referer': 'https://live.bilibili.com/',
'user-agent': Request.headerUa(type: 'pc'), 'user-agent': Request.headerUa(type: 'pc'),
}, },
extra: gaiaVtoken == null
? null
: {'cookie': 'x-bili-gaia-vtoken=$gaiaVtoken'},
), ),
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
@@ -38,7 +47,11 @@ class LiveHttp {
.toList(); .toList();
return LoadingState.success(list); return LoadingState.success(list);
} else { } 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']);
} }
} }

42
lib/http/validate.dart Normal file
View File

@@ -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']};
}
}
}

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/models/live/follow.dart';
import 'package:PiliPlus/models/live/item.dart'; import 'package:PiliPlus/models/live/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
@@ -18,9 +19,27 @@ class LiveController
} }
} }
String? gaiaVtoken;
@override @override
Future<LoadingState<List<LiveItemModel>?>> customGetData() => Future<LoadingState<List<LiveItemModel>?>> 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 @override
Future onRefresh() { Future onRefresh() {
@@ -28,6 +47,17 @@ class LiveController
return super.onRefresh(); 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 RxBool isLogin = Accounts.main.isLogin.obs;
late Rx<LoadingState> followListState = LoadingState.loading().obs; late Rx<LoadingState> followListState = LoadingState.loading().obs;
late int followPage = 1; late int followPage = 1;

View File

@@ -129,13 +129,20 @@ class AccountManager extends Interceptor {
account.cookieJar.loadForRequest(options.uri).then((cookies) { account.cookieJar.loadForRequest(options.uri).then((cookies) {
final previousCookies = final previousCookies =
options.headers[HttpHeaders.cookieHeader] as String?; options.headers[HttpHeaders.cookieHeader] as String?;
final newCookies = getCookies([ String newCookies = getCookies([
...?previousCookies ...?previousCookies
?.split(';') ?.split(';')
.where((e) => e.isNotEmpty) .where((e) => e.isNotEmpty)
.map((c) => Cookie.fromSetCookieValue(c)), .map((c) => Cookie.fromSetCookieValue(c)),
...cookies, ...cookies,
]); ]);
if (options.extra['cookie'] != null) {
if (newCookies.isEmpty) {
newCookies = '${options.extra['cookie']}';
} else {
newCookies += ';${options.extra['cookie']}';
}
}
options.headers[HttpHeaders.cookieHeader] = options.headers[HttpHeaders.cookieHeader] =
newCookies.isNotEmpty ? newCookies : ''; newCookies.isNotEmpty ? newCookies : '';
handler.next(options); handler.next(options);

View File

@@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:PiliPlus/common/widgets/radio_widget.dart'; 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/member.dart';
import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/validate.dart';
import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/dynamics/result.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/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/dynamics/tab/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/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
class RequestUtils { class RequestUtils {
// 1小视频已弃用 // 1小视频已弃用
@@ -429,4 +433,137 @@ class RequestUtils {
} }
}); });
} }
static Future validate(
String vVoucher, ValueChanged<String> 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<String, dynamic> message) async {
SmartDialog.showToast('关闭验证');
},
onResult: (Map<String, dynamic> 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<String, dynamic> 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);
}
} }