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();
-      // },
     );
   }