diff --git a/lib/models/login/model.dart b/lib/models/login/model.dart index 2f044ac42..14f135695 100644 --- a/lib/models/login/model.dart +++ b/lib/models/login/model.dart @@ -28,18 +28,16 @@ class CaptchaDataModel { } class GeetestData { - GeetestData({ - this.challenge, - this.gt, + const GeetestData({ + required this.challenge, + required this.gt, }); - String? challenge; - String? gt; + final String challenge; + final String gt; - GeetestData.fromJson(Map json) { - challenge = json["challenge"]; - gt = json["gt"]; - } + factory GeetestData.fromJson(Map json) => + GeetestData(challenge: json["challenge"], gt: json["gt"]); } class Tencent { diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index b8e6587e8..9930640b6 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -9,6 +9,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/login.dart'; import 'package:PiliPlus/models/common/account_type.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/account.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -118,7 +119,31 @@ class LoginPageController extends GetxController } // 申请极验验证码 - void getCaptcha(String? geeGt, String? geeChallenge, VoidCallback onSuccess) { + Future getCaptcha( + String geeGt, + String geeChallenge, + VoidCallback onSuccess, + ) async { + void updateCaptchaData(Map 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>( + GeetestWebviewDialog(geeGt, geeChallenge), + ); + if (res != null) { + updateCaptchaData(res); + onSuccess(); + } + return; + } var registerData = Gt3RegisterData( challenge: geeChallenge, gt: geeGt, @@ -137,13 +162,7 @@ class LoginPageController extends GetxController if (code == "1") { // 发送 message["result"] 中的数据向 B 端的业务服务接口进行查询 SmartDialog.showToast('验证成功'); - captchaData - ..validate = message['result']['geetest_validate'] - ..seccode = message['result']['geetest_seccode'] - ..geetest = GeetestData( - challenge: message['result']['geetest_challenge'], - gt: geeGt, - ); + updateCaptchaData(message['result']); onSuccess(); } else { // 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried. @@ -293,7 +312,7 @@ class LoginPageController extends GetxController } if (data['status'] == 2) { SmartDialog.showToast(data['message']); - if (!Utils.isMobile) { + if (Platform.isLinux) { return; } // return; @@ -381,8 +400,8 @@ class LoginPageController extends GetxController "(${preCaptureRes['code']}) ${preCaptureRes['msg']} ${preCaptureRes['data']}", ); } - String? geeGt = preCaptureRes['data']['gee_gt']; - String? geeChallenge = preCaptureRes['data']['gee_challenge']; + String geeGt = preCaptureRes['data']['gee_gt']; + String geeChallenge = preCaptureRes['data']['gee_challenge']; captchaData.token = preCaptureRes['data']['recaptcha_token']; if (!isGeeArgumentValid(geeGt, geeChallenge)) { SmartDialog.showToast( @@ -500,7 +519,7 @@ class LoginPageController extends GetxController case 0: // login success break; - case -105 when (Utils.isMobile): + case -105 when (!Platform.isLinux): String captureUrl = res['data']['url']; Uri captureUri = Uri.parse(captureUrl); captchaData.token = captureUri.queryParameters['recaptcha_token']!; @@ -670,7 +689,7 @@ class LoginPageController extends GetxController return; } - getCaptcha(geeGt, geeChallenge, sendSmsCode); + getCaptcha(geeGt!, geeChallenge!, sendSmsCode); break; default: SmartDialog.showToast(res['msg']); diff --git a/lib/pages/login/geetest/geetest_webview_dialog.dart b/lib/pages/login/geetest/geetest_webview_dialog.dart new file mode 100644 index 000000000..a04771607 --- /dev/null +++ b/lib/pages/login/geetest/geetest_webview_dialog.dart @@ -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> _getConfig( + String gt, + String challenge, + ) async { + final res = await Request().get( + '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 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..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: + '', + ), + onWebViewCreated: (ctr) { + ctr + ..addJavaScriptHandler( + handlerName: 'success', + callback: (args) { + if (args.isNotEmpty) { + if (args[0] case Map 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), + ), + ), + ], + ); + } +} diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index c922dd046..db661fca9 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:ui'; import 'package:PiliPlus/common/constants.dart'; @@ -30,7 +31,6 @@ class _LoginPageState extends State { // 二维码生成时间 bool showPassword = false; GlobalKey globalKey = GlobalKey(); - bool get isMobile => kDebugMode || Utils.isMobile; Widget loginByQRCode(ThemeData theme) { return Column( @@ -75,7 +75,7 @@ class _LoginPageState extends State { icon: const Icon(Icons.save), label: const Text('保存至相册'), ), - if (isMobile) + if (kDebugMode || Utils.isMobile) TextButton.icon( onPressed: () => PageUtils.launchURL( _loginPageCtr.codeInfo.value.data.url, @@ -374,7 +374,7 @@ class _LoginPageState extends State { Builder( builder: (context) { return PopupMenuButton( - enabled: isMobile, + enabled: !Platform.isLinux, padding: EdgeInsets.zero, tooltip: '选择国际冠码,' @@ -423,7 +423,7 @@ class _LoginPageState extends State { const SizedBox(width: 6), Expanded( child: TextField( - enabled: isMobile, + enabled: !Platform.isLinux, controller: _loginPageCtr.telTextController, keyboardType: TextInputType.number, inputFormatters: [ @@ -455,7 +455,7 @@ class _LoginPageState extends State { children: [ Expanded( child: TextField( - enabled: isMobile, + enabled: !Platform.isLinux, controller: _loginPageCtr.smsCodeTextController, decoration: const InputDecoration( prefixIcon: Icon(Icons.sms_outlined), @@ -470,10 +470,10 @@ class _LoginPageState extends State { ), Obx( () => TextButton.icon( - onPressed: isMobile - ? (_loginPageCtr.smsSendCooldown > 0 + onPressed: !Platform.isLinux + ? _loginPageCtr.smsSendCooldown > 0 ? null - : _loginPageCtr.sendSmsCode) + : _loginPageCtr.sendSmsCode : null, icon: const Icon(Icons.send), label: Text( @@ -489,7 +489,7 @@ class _LoginPageState extends State { ), const SizedBox(height: 20), OutlinedButton.icon( - onPressed: isMobile ? _loginPageCtr.loginBySmsCode : null, + onPressed: !Platform.isLinux ? _loginPageCtr.loginBySmsCode : null, icon: const Icon(Icons.login), label: const Text('登录'), ), diff --git a/lib/utils/accounts/account_manager/account_mgr.dart b/lib/utils/accounts/account_manager/account_mgr.dart index bd85ce582..dd6e3a7dc 100644 --- a/lib/utils/accounts/account_manager/account_mgr.dart +++ b/lib/utils/accounts/account_manager/account_mgr.dart @@ -218,22 +218,28 @@ class AccountManager extends Interceptor { @override void onResponse(Response response, ResponseInterceptorHandler handler) { - final path = response.requestOptions.path; - if (path.startsWith(HttpString.appBaseUrl) || _skipCookie(path)) { + final options = response.requestOptions; + final path = options.path; + if (path.startsWith(HttpString.appBaseUrl) || + _skipCookie(path) || + options.extra['account'] is NoAccount) { return handler.next(response); } else { - _saveCookies( + final future = _saveCookies( response, - ).whenComplete(() => handler.next(response)).catchError( - (dynamic e, StackTrace s) { - final error = DioException( - requestOptions: response.requestOptions, - error: e, - stackTrace: s, - ); - handler.reject(error, true); - }, - ); + ).whenComplete(() => handler.next(response)); + assert(() { + future.catchError( + (Object e, StackTrace s) { + throw DioException( + requestOptions: response.requestOptions, + error: e, + stackTrace: s, + ); + }, + ); + return true; + }()); } }