From 5bbeef802b2e4883df081fb3dd344cecc571f153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=85=B1=E5=A4=A9=E5=B0=8F=E7=A6=BD=E5=85=BD?= <jiangtian616@qq.com> Date: Tue, 24 Sep 2024 22:37:55 +0800 Subject: [PATCH] Support one-click refresh of igneous cookie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持一键刷新igneous cookie --- changelog/v8.0.2.md | 2 + lib/src/consts/eh_consts.dart | 2 + .../setting/account/cookie/cookie_page.dart | 164 +++++++++++++++++- .../setting/account/setting_account_page.dart | 3 - 4 files changed, 165 insertions(+), 6 deletions(-) diff --git a/changelog/v8.0.2.md b/changelog/v8.0.2.md index 774a5ed6..02752433 100644 --- a/changelog/v8.0.2.md +++ b/changelog/v8.0.2.md @@ -1,5 +1,7 @@ +- 支持一键刷新igneous cookie - 修复在部分场景下,注销后cookie仍然保留的bug - 修复E站更新后无法显示图片配额的bug;现在仅有捐赠者会显示图片配额;去除重置图片配额的功能 +- Support one-click refresh of igneous cookie - Fix the bug that cookies are still retained after logout in some scenarios - Fix the bug that the image quota cannot be displayed after updating of E-Hentai. Now only image quota will only be displayed for donators. Remove the function of resetting the image quota \ No newline at end of file diff --git a/lib/src/consts/eh_consts.dart b/lib/src/consts/eh_consts.dart index 6ed31eab..c3530c86 100644 --- a/lib/src/consts/eh_consts.dart +++ b/lib/src/consts/eh_consts.dart @@ -76,4 +76,6 @@ class EHConsts { static const String EX509ImageUrl = 'https://exhentai.org/img/509.gif'; static const String desktopWebviewDirectoryName = 'EBWebView'; + + static const String igneousCookieName = 'igneous'; } diff --git a/lib/src/pages/setting/account/cookie/cookie_page.dart b/lib/src/pages/setting/account/cookie/cookie_page.dart index 8f627b25..b82365bc 100644 --- a/lib/src/pages/setting/account/cookie/cookie_page.dart +++ b/lib/src/pages/setting/account/cookie/cookie_page.dart @@ -1,13 +1,37 @@ +import 'dart:io'; + import 'package:clipboard/clipboard.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:get/get_rx/get_rx.dart'; +import 'package:get/get_utils/get_utils.dart'; +import 'package:jhentai/src/consts/eh_consts.dart'; +import 'package:jhentai/src/extension/dio_exception_extension.dart'; import 'package:jhentai/src/extension/widget_extension.dart'; import 'package:jhentai/src/network/eh_request.dart'; +import 'package:jhentai/src/setting/user_setting.dart'; import 'package:jhentai/src/utils/cookie_util.dart'; +import 'package:jhentai/src/utils/snack_util.dart'; import 'package:jhentai/src/utils/toast_util.dart'; +import 'package:jhentai/src/widget/loading_state_indicator.dart'; + +import '../../../../exception/eh_site_exception.dart'; +import '../../../../network/eh_ip_provider.dart'; +import '../../../../network/eh_timeout_translator.dart'; +import '../../../../service/log.dart'; +import '../../../../setting/network_setting.dart'; -class CookiePage extends StatelessWidget { - const CookiePage({Key? key}) : super(key: key); +class CookiePage extends StatefulWidget { + const CookiePage({super.key}); + + @override + State<CookiePage> createState() => _CookiePageState(); +} + +class _CookiePageState extends State<CookiePage> { + Dio? _dio; + LoadingState _refreshIgneousState = LoadingState.idle; @override Widget build(BuildContext context) { @@ -26,6 +50,20 @@ class CookiePage extends StatelessWidget { (cookie) => ListTile( title: Text(cookie.name), subtitle: Text(cookie.value), + trailing: cookie.name == EHConsts.igneousCookieName + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + LoadingStateIndicator( + loadingState: _refreshIgneousState, + loadingWidgetBuilder: () => const CupertinoActivityIndicator().marginOnly(right: 10), + idleWidgetBuilder: () => IconButton(icon: const Icon(Icons.refresh), onPressed: _refreshIgneousCookie), + successWidgetSameWithIdle: true, + errorWidgetSameWithIdle: true, + ) + ], + ) + : null, onTap: _copyAllCookies, dense: true, ), @@ -52,4 +90,124 @@ class CookiePage extends StatelessWidget { ); toast('hasCopiedToClipboard'.tr); } + + Future<void> _refreshIgneousCookie() async { + if (!userSetting.hasLoggedIn()) { + return; + } + + await _initDio(); + + if (_refreshIgneousState == LoadingState.loading) { + return; + } + + setStateSafely(() { + _refreshIgneousState = LoadingState.loading; + }); + + try { + Response response = await _dio!.request( + EHConsts.EXIndex, + options: Options(headers: { + 'cookie': CookieUtil.parse2String( + [ + Cookie('ipb_member_id', userSetting.ipbMemberId.value.toString()), + Cookie('ipb_pass_hash', userSetting.ipbPassHash.value!), + ], + ) + }), + ); + + log.info('Refresh igneous cookie, set-cookie: ${response.headers.value('set-cookie')}'); + + response.headers.value('set-cookie')?.split(';').forEach((cookie) { + String name = cookie.split('=')[0]; + String value = cookie.split('=')[1]; + if (name == EHConsts.igneousCookieName) { + ehRequest.storeEHCookies([Cookie(name, value)]); + } + }); + + toast('success'.tr); + + setStateSafely(() { + _refreshIgneousState = LoadingState.success; + }); + } on DioException catch (e) { + log.error('Refresh igneous failed: ${e.message}'); + snack('refreshIgneousFailed'.tr, e.errorMsg ?? ''); + setStateSafely(() { + _refreshIgneousState = LoadingState.error; + }); + return; + } on EHSiteException catch (e) { + log.error('Refresh igneous failed: ${e.message}'); + snack('refreshIgneousFailed'.tr, e.message); + setStateSafely(() { + _refreshIgneousState = LoadingState.error; + }); + return; + } catch (e, s) { + log.error('Refresh igneous failed: $e'); + snack('refreshIgneousFailed'.tr, e.toString()); + setStateSafely(() { + _refreshIgneousState = LoadingState.error; + }); + return; + } + } + + Future<void> _initDio() async { + if (_dio != null) { + return; + } + + _dio = Dio(BaseOptions( + connectTimeout: Duration(milliseconds: networkSetting.connectTimeout.value), + receiveTimeout: Duration(milliseconds: networkSetting.receiveTimeout.value), + )); + + EHIpProvider _ehIpProvider = RoundRobinIpProvider(NetworkSetting.host2IPs); + + _dio!.interceptors.add(InterceptorsWrapper( + onRequest: (RequestOptions options, RequestInterceptorHandler handler) { + if (networkSetting.enableDomainFronting.isFalse) { + handler.next(options); + return; + } + + String rawPath = options.path; + String host = options.uri.host; + if (!_ehIpProvider.supports(host)) { + handler.next(options); + return; + } + + String ip = _ehIpProvider.nextIP(host); + handler.next(options.copyWith( + path: rawPath.replaceFirst(host, ip), + headers: {...options.headers, 'host': host}, + extra: options.extra..[EHRequest.domainFrontingExtraKey] = {'host': host, 'ip': ip}, + )); + }, + onError: (DioException e, ErrorInterceptorHandler handler) { + if (!e.requestOptions.extra.containsKey(EHRequest.domainFrontingExtraKey)) { + handler.next(e); + return; + } + + if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.badResponse || e.type == DioExceptionType.connectionError) { + String host = e.requestOptions.extra[EHRequest.domainFrontingExtraKey]['host']; + String ip = e.requestOptions.extra[EHRequest.domainFrontingExtraKey]['ip']; + _ehIpProvider.addUnavailableIp(host, ip); + log.info('Refresh igneous, add unavailable host-ip: $host-$ip'); + } + + handler.next(e); + }, + )); + + _dio!.interceptors.add(EHTimeoutTranslator()); + } } diff --git a/lib/src/pages/setting/account/setting_account_page.dart b/lib/src/pages/setting/account/setting_account_page.dart index 26f30a36..609771a3 100644 --- a/lib/src/pages/setting/account/setting_account_page.dart +++ b/lib/src/pages/setting/account/setting_account_page.dart @@ -35,9 +35,6 @@ class SettingAccountPage extends StatelessWidget { title: Text('login'.tr), trailing: IconButton(onPressed: () => toRoute(Routes.login), icon: const Icon(Icons.keyboard_arrow_right)), onTap: () => toRoute(Routes.login), - // onTap: () async { - // await ehRequest.requestLogout(); - // }, ); }