mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-31 16:18:22 +08:00
feat: webview geetest (#1342)
* feat: webview geetest * opt: geetest * fix: linux * remove pwd mobile check * fix linux check
This commit is contained in:
committed by
GitHub
parent
ee8af925be
commit
e3e6bb0e39
@@ -28,18 +28,16 @@ class CaptchaDataModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GeetestData {
|
class GeetestData {
|
||||||
GeetestData({
|
const GeetestData({
|
||||||
this.challenge,
|
required this.challenge,
|
||||||
this.gt,
|
required this.gt,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? challenge;
|
final String challenge;
|
||||||
String? gt;
|
final String gt;
|
||||||
|
|
||||||
GeetestData.fromJson(Map<String, dynamic> json) {
|
factory GeetestData.fromJson(Map<String, dynamic> json) =>
|
||||||
challenge = json["challenge"];
|
GeetestData(challenge: json["challenge"], gt: json["gt"]);
|
||||||
gt = json["gt"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tencent {
|
class Tencent {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/http/login.dart';
|
import 'package:PiliPlus/http/login.dart';
|
||||||
import 'package:PiliPlus/models/common/account_type.dart';
|
import 'package:PiliPlus/models/common/account_type.dart';
|
||||||
import 'package:PiliPlus/models/login/model.dart';
|
import 'package:PiliPlus/models/login/model.dart';
|
||||||
|
import 'package:PiliPlus/pages/login/geetest/geetest_webview_dialog.dart';
|
||||||
import 'package:PiliPlus/utils/accounts.dart';
|
import 'package:PiliPlus/utils/accounts.dart';
|
||||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
@@ -118,7 +119,31 @@ class LoginPageController extends GetxController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 申请极验验证码
|
// 申请极验验证码
|
||||||
void getCaptcha(String? geeGt, String? geeChallenge, VoidCallback onSuccess) {
|
Future<void> getCaptcha(
|
||||||
|
String geeGt,
|
||||||
|
String geeChallenge,
|
||||||
|
VoidCallback onSuccess,
|
||||||
|
) async {
|
||||||
|
void updateCaptchaData(Map<String, dynamic> json) {
|
||||||
|
captchaData
|
||||||
|
..validate = json['geetest_validate']
|
||||||
|
..seccode = json['geetest_seccode']
|
||||||
|
..geetest = GeetestData(
|
||||||
|
challenge: json['geetest_challenge'],
|
||||||
|
gt: geeGt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils.isDesktop) {
|
||||||
|
final res = await Get.dialog<Map<String, dynamic>>(
|
||||||
|
GeetestWebviewDialog(geeGt, geeChallenge),
|
||||||
|
);
|
||||||
|
if (res != null) {
|
||||||
|
updateCaptchaData(res);
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
var registerData = Gt3RegisterData(
|
var registerData = Gt3RegisterData(
|
||||||
challenge: geeChallenge,
|
challenge: geeChallenge,
|
||||||
gt: geeGt,
|
gt: geeGt,
|
||||||
@@ -137,13 +162,7 @@ class LoginPageController extends GetxController
|
|||||||
if (code == "1") {
|
if (code == "1") {
|
||||||
// 发送 message["result"] 中的数据向 B 端的业务服务接口进行查询
|
// 发送 message["result"] 中的数据向 B 端的业务服务接口进行查询
|
||||||
SmartDialog.showToast('验证成功');
|
SmartDialog.showToast('验证成功');
|
||||||
captchaData
|
updateCaptchaData(message['result']);
|
||||||
..validate = message['result']['geetest_validate']
|
|
||||||
..seccode = message['result']['geetest_seccode']
|
|
||||||
..geetest = GeetestData(
|
|
||||||
challenge: message['result']['geetest_challenge'],
|
|
||||||
gt: geeGt,
|
|
||||||
);
|
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} else {
|
} else {
|
||||||
// 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried.
|
// 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried.
|
||||||
@@ -293,7 +312,7 @@ class LoginPageController extends GetxController
|
|||||||
}
|
}
|
||||||
if (data['status'] == 2) {
|
if (data['status'] == 2) {
|
||||||
SmartDialog.showToast(data['message']);
|
SmartDialog.showToast(data['message']);
|
||||||
if (!Utils.isMobile) {
|
if (Platform.isLinux) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// return;
|
// return;
|
||||||
@@ -381,8 +400,8 @@ class LoginPageController extends GetxController
|
|||||||
"(${preCaptureRes['code']}) ${preCaptureRes['msg']} ${preCaptureRes['data']}",
|
"(${preCaptureRes['code']}) ${preCaptureRes['msg']} ${preCaptureRes['data']}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
String? geeGt = preCaptureRes['data']['gee_gt'];
|
String geeGt = preCaptureRes['data']['gee_gt'];
|
||||||
String? geeChallenge = preCaptureRes['data']['gee_challenge'];
|
String geeChallenge = preCaptureRes['data']['gee_challenge'];
|
||||||
captchaData.token = preCaptureRes['data']['recaptcha_token'];
|
captchaData.token = preCaptureRes['data']['recaptcha_token'];
|
||||||
if (!isGeeArgumentValid(geeGt, geeChallenge)) {
|
if (!isGeeArgumentValid(geeGt, geeChallenge)) {
|
||||||
SmartDialog.showToast(
|
SmartDialog.showToast(
|
||||||
@@ -500,7 +519,7 @@ class LoginPageController extends GetxController
|
|||||||
case 0:
|
case 0:
|
||||||
// login success
|
// login success
|
||||||
break;
|
break;
|
||||||
case -105 when (Utils.isMobile):
|
case -105 when (!Platform.isLinux):
|
||||||
String captureUrl = res['data']['url'];
|
String captureUrl = res['data']['url'];
|
||||||
Uri captureUri = Uri.parse(captureUrl);
|
Uri captureUri = Uri.parse(captureUrl);
|
||||||
captchaData.token = captureUri.queryParameters['recaptcha_token']!;
|
captchaData.token = captureUri.queryParameters['recaptcha_token']!;
|
||||||
@@ -670,7 +689,7 @@ class LoginPageController extends GetxController
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCaptcha(geeGt, geeChallenge, sendSmsCode);
|
getCaptcha(geeGt!, geeChallenge!, sendSmsCode);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
SmartDialog.showToast(res['msg']);
|
SmartDialog.showToast(res['msg']);
|
||||||
|
|||||||
135
lib/pages/login/geetest/geetest_webview_dialog.dart
Normal file
135
lib/pages/login/geetest/geetest_webview_dialog.dart
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/http/init.dart';
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/http/ua_type.dart';
|
||||||
|
import 'package:PiliPlus/main.dart';
|
||||||
|
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class GeetestWebviewDialog extends StatelessWidget {
|
||||||
|
const GeetestWebviewDialog(this.gt, this.challenge, {super.key});
|
||||||
|
|
||||||
|
final String gt;
|
||||||
|
final String challenge;
|
||||||
|
|
||||||
|
static const _geetestJsUri =
|
||||||
|
'https://static.geetest.com/static/js/fullpage.0.0.0.js';
|
||||||
|
|
||||||
|
static Future<LoadingState<String>> _getConfig(
|
||||||
|
String gt,
|
||||||
|
String challenge,
|
||||||
|
) async {
|
||||||
|
final res = await Request().get<String>(
|
||||||
|
'https://api.geetest.com/gettype.php',
|
||||||
|
queryParameters: {'gt': gt},
|
||||||
|
options: Options(
|
||||||
|
responseType: ResponseType.plain,
|
||||||
|
extra: {'account': const NoAccount()},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (res.data case String data) {
|
||||||
|
if (data.startsWith('(') && data.endsWith(')')) {
|
||||||
|
final Map<String, dynamic> config;
|
||||||
|
try {
|
||||||
|
config = jsonDecode(data.substring(1, data.length - 1));
|
||||||
|
} catch (e) {
|
||||||
|
return Error(e.toString());
|
||||||
|
}
|
||||||
|
if (config['status'] == 'success') {
|
||||||
|
return Success(
|
||||||
|
jsonEncode(
|
||||||
|
config['data'] as Map<String, dynamic>..addAll({
|
||||||
|
"gt": gt,
|
||||||
|
"challenge": challenge,
|
||||||
|
"offline": false,
|
||||||
|
"new_captcha": true,
|
||||||
|
"product": "bind",
|
||||||
|
"width": "100%",
|
||||||
|
"https": true,
|
||||||
|
"protocol": "https://",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Error(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Error(res.data['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final future = _getConfig(gt, challenge);
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('验证码'),
|
||||||
|
content: SizedBox(
|
||||||
|
width: 300,
|
||||||
|
height: 400,
|
||||||
|
child: InAppWebView(
|
||||||
|
webViewEnvironment: webViewEnvironment,
|
||||||
|
initialSettings: InAppWebViewSettings(
|
||||||
|
clearCache: true,
|
||||||
|
javaScriptEnabled: true,
|
||||||
|
forceDark: ForceDark.AUTO,
|
||||||
|
useHybridComposition: false,
|
||||||
|
algorithmicDarkeningAllowed: true,
|
||||||
|
useShouldOverrideUrlLoading: true,
|
||||||
|
userAgent: UaType.mob.ua,
|
||||||
|
mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW,
|
||||||
|
),
|
||||||
|
initialData: InAppWebViewInitialData(
|
||||||
|
data:
|
||||||
|
'<!DOCTYPE html><html><head></head><body><script src="$_geetestJsUri"></script><script>function R(n,o){flutter_inappwebview.callHandler(n,o)}</script></body></html>',
|
||||||
|
),
|
||||||
|
onWebViewCreated: (ctr) {
|
||||||
|
ctr
|
||||||
|
..addJavaScriptHandler(
|
||||||
|
handlerName: 'success',
|
||||||
|
callback: (args) {
|
||||||
|
if (args.isNotEmpty) {
|
||||||
|
if (args[0] case Map<String, dynamic> data) {
|
||||||
|
Get.back(result: data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debugPrint('geetest invalid result: $args');
|
||||||
|
},
|
||||||
|
)
|
||||||
|
..addJavaScriptHandler(
|
||||||
|
handlerName: 'error',
|
||||||
|
callback: (args) {
|
||||||
|
debugPrint('geetest error: $args');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onLoadStop: (ctr, _) async {
|
||||||
|
final config = await future;
|
||||||
|
if (config.isSuccess) {
|
||||||
|
ctr.evaluateJavascript(
|
||||||
|
source:
|
||||||
|
'let t=Geetest(${config.data}).onSuccess(()=>R("success",t.getValidate())).onError((o)=>R("error",o));t.onReady(()=>t.verify());',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
config.toast();
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: ColorScheme.of(context).outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
@@ -30,7 +31,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
// 二维码生成时间
|
// 二维码生成时间
|
||||||
bool showPassword = false;
|
bool showPassword = false;
|
||||||
GlobalKey globalKey = GlobalKey();
|
GlobalKey globalKey = GlobalKey();
|
||||||
bool get isMobile => kDebugMode || Utils.isMobile;
|
|
||||||
|
|
||||||
Widget loginByQRCode(ThemeData theme) {
|
Widget loginByQRCode(ThemeData theme) {
|
||||||
return Column(
|
return Column(
|
||||||
@@ -75,7 +75,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
label: const Text('保存至相册'),
|
label: const Text('保存至相册'),
|
||||||
),
|
),
|
||||||
if (isMobile)
|
if (kDebugMode || Utils.isMobile)
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => PageUtils.launchURL(
|
onPressed: () => PageUtils.launchURL(
|
||||||
_loginPageCtr.codeInfo.value.data.url,
|
_loginPageCtr.codeInfo.value.data.url,
|
||||||
@@ -374,7 +374,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return PopupMenuButton(
|
return PopupMenuButton(
|
||||||
enabled: isMobile,
|
enabled: !Platform.isLinux,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
tooltip:
|
tooltip:
|
||||||
'选择国际冠码,'
|
'选择国际冠码,'
|
||||||
@@ -423,7 +423,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
enabled: isMobile,
|
enabled: !Platform.isLinux,
|
||||||
controller: _loginPageCtr.telTextController,
|
controller: _loginPageCtr.telTextController,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: <TextInputFormatter>[
|
inputFormatters: <TextInputFormatter>[
|
||||||
@@ -455,7 +455,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
enabled: isMobile,
|
enabled: !Platform.isLinux,
|
||||||
controller: _loginPageCtr.smsCodeTextController,
|
controller: _loginPageCtr.smsCodeTextController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
prefixIcon: Icon(Icons.sms_outlined),
|
prefixIcon: Icon(Icons.sms_outlined),
|
||||||
@@ -470,10 +470,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => TextButton.icon(
|
() => TextButton.icon(
|
||||||
onPressed: isMobile
|
onPressed: !Platform.isLinux
|
||||||
? (_loginPageCtr.smsSendCooldown > 0
|
? _loginPageCtr.smsSendCooldown > 0
|
||||||
? null
|
? null
|
||||||
: _loginPageCtr.sendSmsCode)
|
: _loginPageCtr.sendSmsCode
|
||||||
: null,
|
: null,
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
label: Text(
|
label: Text(
|
||||||
@@ -489,7 +489,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: isMobile ? _loginPageCtr.loginBySmsCode : null,
|
onPressed: !Platform.isLinux ? _loginPageCtr.loginBySmsCode : null,
|
||||||
icon: const Icon(Icons.login),
|
icon: const Icon(Icons.login),
|
||||||
label: const Text('登录'),
|
label: const Text('登录'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -218,22 +218,28 @@ class AccountManager extends Interceptor {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
final path = response.requestOptions.path;
|
final options = response.requestOptions;
|
||||||
if (path.startsWith(HttpString.appBaseUrl) || _skipCookie(path)) {
|
final path = options.path;
|
||||||
|
if (path.startsWith(HttpString.appBaseUrl) ||
|
||||||
|
_skipCookie(path) ||
|
||||||
|
options.extra['account'] is NoAccount) {
|
||||||
return handler.next(response);
|
return handler.next(response);
|
||||||
} else {
|
} else {
|
||||||
_saveCookies(
|
final future = _saveCookies(
|
||||||
response,
|
response,
|
||||||
).whenComplete(() => handler.next(response)).catchError(
|
).whenComplete(() => handler.next(response));
|
||||||
(dynamic e, StackTrace s) {
|
assert(() {
|
||||||
final error = DioException(
|
future.catchError(
|
||||||
requestOptions: response.requestOptions,
|
(Object e, StackTrace s) {
|
||||||
error: e,
|
throw DioException(
|
||||||
stackTrace: s,
|
requestOptions: response.requestOptions,
|
||||||
);
|
error: e,
|
||||||
handler.reject(error, true);
|
stackTrace: s,
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user