Files
PiliPlus/lib/pages/login/geetest/geetest_webview_dialog.dart
My-Responsitories b67756c152 fix: mob geetest (#2341)
* fix: mob geetest

* geetest ios patch

---------

Co-authored-by: dom <githubaccount56556@proton.me>
2026-06-07 10:54:44 +08:00

295 lines
8.3 KiB
Dart

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<GeetestWebviewDialog> createState() => _GeetestWebviewDialogState();
static Future geetest(String gt, String challenge) {
return showDialog(
context: Get.context!,
builder: (context) => GeetestWebviewDialog(gt, challenge),
);
}
}
class _GeetestWebviewDialogState extends State<GeetestWebviewDialog> {
static const _geetestJsUri =
'https://static.geetest.com/static/js/fullpage.0.0.0.js';
late final Future<LoadingState<String>> _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<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 final 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']);
}
Future<void> _initLinuxWebview() async {
final config = await _future;
if (!mounted) {
return;
}
if (config is Error) {
config.toast();
Get.back();
return;
}
final response = (config as Success<String>).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 =
'''
<!DOCTYPE html><html><head></head><body>
<script src="$_geetestJsUri"></script>
<script>
R=(n,o)=>webkit.messageHandlers.msgToNative.postMessage(n+':'+JSON.stringify(o))
${_showJs(response)}
</script>
</body></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:
'<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width"></head><body><script src="$_geetestJsUri"></script><script>R=flutter_inappwebview.callHandler</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');
},
)
..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),
),
),
],
);
}
}