import 'dart:convert' show base64, jsonDecode, jsonEncode, utf8; import 'dart:io' show Platform; import 'package:PiliPlus/http/browser_ua.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/main.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:desktop_webview_window/desktop_webview_window.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 StatefulWidget { const GeetestWebviewDialog(this.gt, this.challenge, {super.key}); final String gt; final String challenge; @override State createState() => _GeetestWebviewDialogState(); static Future geetest(String gt, String challenge) { return showDialog( context: Get.context!, builder: (context) => GeetestWebviewDialog(gt, challenge), ); } } class _GeetestWebviewDialogState extends State { static const _geetestJsUri = 'https://static.geetest.com/static/js/fullpage.0.0.0.js'; late final Future> _future; Webview? _linuxWebview; late bool _linuxWebviewLoading = true; String _showJs(String response) => 't=Geetest($response).onSuccess(()=>R("success",t.getValidate())).onError(o=>R("error",o)).onClose(o=>R("close",o));t.onReady(()=>t.verify())'; @override void initState() { super.initState(); _future = _getConfig(widget.gt, widget.challenge); if (Platform.isLinux) { _initLinuxWebview(); } } 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 final 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']); } Future _initLinuxWebview() async { final config = await _future; if (!mounted) { return; } if (config is Error) { config.toast(); Get.back(); return; } final response = (config as Success).response; _linuxWebview = await WebviewWindow.create( configuration: const CreateConfiguration( windowWidth: 300, windowHeight: 400, title: "验证码", ), ); if (!mounted) { _closeLinuxWebview(); return; } _linuxWebview!.addOnWebMessageReceivedCallback((msg) { final msgStr = msg.toString(); if (msgStr.startsWith("success:")) { final dataStr = msgStr.substring("success:".length); try { final data = jsonDecode(dataStr); Get.back(result: data); } catch (e) { debugPrint('geetest decode error: $e'); } } else if (msgStr.startsWith("error:")) { debugPrint('geetest error: $msgStr'); } else if (msgStr.startsWith('close:')) { Get.back(); } }); _linuxWebview!.onClose.whenComplete(() { if (mounted) { Get.back(); } }); final html = ''' '''; _linuxWebview!.launch( 'data:text/html;base64,${base64.encode(utf8.encode(html))}', ); if (mounted) { setState(() { _linuxWebviewLoading = false; }); } } void _closeLinuxWebview() { _linuxWebview?.close(); _linuxWebview = null; } @override void dispose() { _closeLinuxWebview(); super.dispose(); } @override Widget build(BuildContext context) { if (Platform.isLinux) { return AlertDialog( title: const Text('验证码'), content: SizedBox( width: 300, height: 400, child: Center( child: _linuxWebviewLoading ? const CircularProgressIndicator() : const Text('请在弹出的新窗口中完成验证'), ), ), actions: [ TextButton( onPressed: Get.back, child: Text( '取消', style: TextStyle(color: ColorScheme.of(context).outline), ), ), ], ); } return AlertDialog( title: const Text('验证码'), content: SizedBox( width: 320, height: 400, child: InAppWebView( webViewEnvironment: webViewEnvironment, initialSettings: InAppWebViewSettings( clearCache: true, javaScriptEnabled: true, forceDark: ForceDark.AUTO, useHybridComposition: false, algorithmicDarkeningAllowed: true, useShouldOverrideUrlLoading: true, userAgent: BrowserUa.mob, mixedContentMode: .MIXED_CONTENT_ALWAYS_ALLOW, incognito: true, allowFileAccess: false, allowsLinkPreview: false, allowContentAccess: false, useOnDownloadStart: false, geolocationEnabled: false, thirdPartyCookiesEnabled: false, enterpriseAuthenticationAppLinkPolicyEnabled: false, saveFormData: false, safeBrowsingEnabled: false, isFraudulentWebsiteWarningEnabled: false, domStorageEnabled: false, databaseEnabled: false, cacheEnabled: false, cacheMode: .LOAD_NO_CACHE, horizontalScrollBarEnabled: false, verticalScrollBarEnabled: false, overScrollMode: .NEVER, ), 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'); }, ) ..addJavaScriptHandler( handlerName: 'close', callback: (args) => Get.back(), ); }, onLoadStop: (ctr, _) async { final config = await _future; if (!mounted) return; if (config case Success(:final response)) { ctr.evaluateJavascript(source: _showJs(response)); } else { config.toast(); Get.back(); } }, ), ), actions: [ TextButton( onPressed: Get.back, child: Text( '取消', style: TextStyle(color: ColorScheme.of(context).outline), ), ), ], ); } }