From 25abfc6d7787657106b247004e9971046957fffd Mon Sep 17 00:00:00 2001 From: realth000 Date: Sun, 24 Dec 2023 06:13:30 +0800 Subject: [PATCH] fix(*): Fix unexpected time offset in html doc parsing --- CHANGELOG.md | 4 +++ lib/extensions/date_time.dart | 7 +++-- lib/providers/auth_provider.dart | 9 ++++--- lib/providers/check_in_provider.dart | 4 +-- lib/providers/html_parser_provider.dart | 30 ++++++++++++++++++++++ lib/providers/root_content_provider.dart | 7 ++--- lib/providers/search_provider.dart | 4 +-- lib/providers/small_providers.dart | 2 ++ lib/screens/forum/forum_page.dart | 4 +-- lib/screens/notice/notice_detail_page.dart | 5 ++-- lib/screens/notice/notice_page.dart | 4 +-- lib/screens/profile/profile_page.dart | 5 ++-- lib/screens/thread/post_list.dart | 4 +-- lib/widgets/forum_card.dart | 2 +- lib/widgets/post_card.dart | 2 +- lib/widgets/refresh_list.dart | 6 ++--- lib/widgets/reply_bar.dart | 5 ++-- lib/widgets/thread_card.dart | 2 +- 18 files changed, 75 insertions(+), 31 deletions(-) create mode 100644 lib/providers/html_parser_provider.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index fe749232..cb4cb7aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## Fixed + +- 修复页面中时间显示和网页端有误差的问题。 + ## [0.2.0] - 2023-12-22 ### Added diff --git a/lib/extensions/date_time.dart b/lib/extensions/date_time.dart index a311f48f..2fb30737 100644 --- a/lib/extensions/date_time.dart +++ b/lib/extensions/date_time.dart @@ -1,10 +1,13 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:tsdm_client/providers/small_providers.dart'; + extension DateTimeExtension on DateTime { String yyyyMMDD() { return '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; } - String elapsedTillNow() { - final duration = DateTime.now().difference(this); + String elapsedTillNow(WidgetRef ref) { + final duration = ref.read(serverDateTimeProvider).difference(this); return duration.inDays > 0 ? '${duration.inDays}天' : duration.inHours > 0 diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart index e41251ec..1839f118 100644 --- a/lib/providers/auth_provider.dart +++ b/lib/providers/auth_provider.dart @@ -4,11 +4,11 @@ import 'package:dio/dio.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tsdm_client/constants/url.dart'; import 'package:tsdm_client/extensions/universal_html.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/providers/settings_provider.dart'; import 'package:tsdm_client/utils/debug.dart'; import 'package:universal_html/html.dart' as uh; -import 'package:universal_html/parsing.dart'; part '../generated/providers/auth_provider.g.dart'; @@ -146,7 +146,7 @@ class Auth extends _$Auth { return (LoginResult.requestFailed, '$message'); } - final document = parseHtmlDocument(resp.data as String); + final document = ref.read(htmlParserProvider.notifier).parseResp(resp); final messageNode = document.getElementById('messagetext'); if (messageNode == null) { // Impossible. @@ -188,7 +188,7 @@ class Auth extends _$Auth { ); return false; } - final document = parseHtmlDocument(resp.data as String); + final document = ref.read(htmlParserProvider.notifier).parseResp(resp); final uid = await _parseUidInDocument(document); if (uid == null) { debug('unnecessary logout: not authed'); @@ -216,7 +216,8 @@ class Auth extends _$Auth { return false; } - final logoutDocument = parseHtmlDocument(logoutResp.data as String); + final logoutDocument = + ref.read(htmlParserProvider.notifier).parseResp(logoutResp); final logoutMessage = logoutDocument.getElementById('messagetext'); if (logoutMessage == null || !logoutMessage.innerHtmlEx().contains('已退出')) { debug('failed to logout: logout message not found'); diff --git a/lib/providers/check_in_provider.dart b/lib/providers/check_in_provider.dart index c46a19b9..86c294b2 100644 --- a/lib/providers/check_in_provider.dart +++ b/lib/providers/check_in_provider.dart @@ -6,10 +6,10 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tsdm_client/constants/url.dart'; import 'package:tsdm_client/models/check_in_feeling.dart'; import 'package:tsdm_client/providers/auth_provider.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/providers/small_providers.dart'; import 'package:tsdm_client/utils/debug.dart'; -import 'package:universal_html/parsing.dart'; part '../generated/providers/check_in_provider.g.dart'; @@ -65,7 +65,7 @@ class CheckIn extends _$CheckIn { return (CheckInResult.webRequestFailed, '${resp.statusCode}'); } - final document = parseHtmlDocument(resp.data as String); + final document = ref.read(htmlParserProvider.notifier).parseResp(resp); final re = RegExp(r'formhash" value="(?\w+)"'); final formHashMatch = re.firstMatch(document.body?.innerHtml ?? ''); final formHash = formHashMatch?.namedGroup('FormHash'); diff --git a/lib/providers/html_parser_provider.dart b/lib/providers/html_parser_provider.dart new file mode 100644 index 00000000..b4b0e7d8 --- /dev/null +++ b/lib/providers/html_parser_provider.dart @@ -0,0 +1,30 @@ +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:tsdm_client/extensions/string.dart'; +import 'package:tsdm_client/providers/small_providers.dart'; +import 'package:universal_html/html.dart' as uh; +import 'package:universal_html/parsing.dart'; + +part '../generated/providers/html_parser_provider.g.dart'; + +@Riverpod() +class HtmlParser extends _$HtmlParser { + @override + void build() {} + + uh.HtmlDocument parseResp(Response resp) { + final doc = parseHtmlDocument((resp.data ?? '') as String); + final serverTime = doc + .querySelector('p.xs0') + ?.childNodes + .elementAtOrNull(0) + ?.text + ?.split(',') + .lastOrNull + ?.trim() + .parseToDateTimeUtc8() ?? + DateTime.now(); + ref.read(serverDateTimeProvider.notifier).state = serverTime; + return doc; + } +} diff --git a/lib/providers/root_content_provider.dart b/lib/providers/root_content_provider.dart index e1c9609d..8879c95c 100644 --- a/lib/providers/root_content_provider.dart +++ b/lib/providers/root_content_provider.dart @@ -3,12 +3,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:tsdm_client/constants/url.dart'; import 'package:tsdm_client/extensions/universal_html.dart'; import 'package:tsdm_client/providers/auth_provider.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/providers/settings_provider.dart'; import 'package:tsdm_client/providers/small_providers.dart'; import 'package:tsdm_client/utils/debug.dart'; import 'package:universal_html/html.dart'; -import 'package:universal_html/parsing.dart'; part '../generated/providers/root_content_provider.g.dart'; @@ -101,7 +101,7 @@ class RootContent extends _$RootContent { while (i < 3) { final resp = await ref.read(netClientProvider()).get(_rootPage); if (resp.statusCode == HttpStatus.ok) { - _doc = parseHtmlDocument(resp.data as String); + _doc = ref.read(htmlParserProvider.notifier).parseResp(resp); await ref.read(authProvider.notifier).loginFromDocument(_doc); final username = ref.read(authProvider.notifier).loggedUsername; await _cache.analyze(_doc, username); @@ -124,7 +124,8 @@ class RootContent extends _$RootContent { final profileResp = await ref.read(netClientProvider()).get('$_profilePage$uid'); if (profileResp.statusCode == HttpStatus.ok) { - _profileDoc = parseHtmlDocument(profileResp.data as String); + _profileDoc = + ref.read(htmlParserProvider.notifier).parseResp(profileResp); _avatarUrl = _profileDoc! .querySelector('div#wp.wp div#ct.ct2 div.sd div.hm > p > a > img') ?.attributes['src']; diff --git a/lib/providers/search_provider.dart b/lib/providers/search_provider.dart index 89d06921..6e31ae3c 100644 --- a/lib/providers/search_provider.dart +++ b/lib/providers/search_provider.dart @@ -6,8 +6,8 @@ import 'package:tsdm_client/constants/url.dart'; import 'package:tsdm_client/extensions/string.dart'; import 'package:tsdm_client/extensions/universal_html.dart'; import 'package:tsdm_client/models/searched_thread.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; -import 'package:universal_html/parsing.dart'; part '../generated/providers/search_provider.g.dart'; @@ -115,7 +115,7 @@ class Search extends _$Search { return const SearchResult.empty(); } - final document = parseHtmlDocument(resp.data as String); + final document = ref.read(htmlParserProvider.notifier).parseResp(resp); final threadList = document .querySelectorAll('div#ct > div#ct_shell > div#left_s > div.ts_se_rs') diff --git a/lib/providers/small_providers.dart b/lib/providers/small_providers.dart index 0f6a9e42..2e97ddab 100644 --- a/lib/providers/small_providers.dart +++ b/lib/providers/small_providers.dart @@ -5,3 +5,5 @@ final appNavigationBarIndexProvider = StateProvider((ref) => 0); final isCheckingInProvider = StateProvider((ref) => false); final topicsTabBarIndexProvider = StateProvider((ref) => 0); + +final serverDateTimeProvider = StateProvider((ref) => DateTime.now()); diff --git a/lib/screens/forum/forum_page.dart b/lib/screens/forum/forum_page.dart index 2c1fa371..1d2e119d 100644 --- a/lib/screens/forum/forum_page.dart +++ b/lib/screens/forum/forum_page.dart @@ -13,6 +13,7 @@ import 'package:tsdm_client/generated/i18n/strings.g.dart'; import 'package:tsdm_client/models/forum.dart'; import 'package:tsdm_client/models/normal_thread.dart'; import 'package:tsdm_client/packages/html_muncher/lib/src/html_muncher.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/jump_page_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/routes/screen_paths.dart'; @@ -22,7 +23,6 @@ import 'package:tsdm_client/widgets/forum_card.dart'; import 'package:tsdm_client/widgets/list_app_bar.dart'; import 'package:tsdm_client/widgets/thread_card.dart'; import 'package:universal_html/html.dart' as uh; -import 'package:universal_html/parsing.dart'; /// Forum page. class ForumPage extends ConsumerStatefulWidget { @@ -185,7 +185,7 @@ class _ForumPageState extends ConsumerState '${widget._fetchUrl}&page=$_pageNumber', ); if (d1.statusCode == HttpStatus.ok) { - document = parseHtmlDocument(d1.data as String); + document = ref.read(htmlParserProvider.notifier).parseResp(d1); break; } diff --git a/lib/screens/notice/notice_detail_page.dart b/lib/screens/notice/notice_detail_page.dart index 5bc4f763..d1f9871a 100644 --- a/lib/screens/notice/notice_detail_page.dart +++ b/lib/screens/notice/notice_detail_page.dart @@ -8,13 +8,13 @@ import 'package:tsdm_client/generated/i18n/strings.g.dart'; import 'package:tsdm_client/models/notice.dart'; import 'package:tsdm_client/models/post.dart'; import 'package:tsdm_client/models/reply_parameters.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/utils/debug.dart'; import 'package:tsdm_client/utils/show_toast.dart'; import 'package:tsdm_client/widgets/post_card.dart'; import 'package:tsdm_client/widgets/reply_bar.dart'; import 'package:universal_html/html.dart' as uh; -import 'package:universal_html/parsing.dart'; /// Show details for a single notice, also provides interaction: /// * Reply to the notice if notice type is [NoticeType.reply] or [NoticeType.mention]. @@ -183,7 +183,8 @@ class _NoticeDetailPage extends ConsumerState { } if (snapshot.hasData) { final data = snapshot.data!; - final document = parseHtmlDocument(data.data as String); + final document = + ref.read(htmlParserProvider.notifier).parseResp(data); return _buildBody(context, document); } return Center( diff --git a/lib/screens/notice/notice_page.dart b/lib/screens/notice/notice_page.dart index 03d135dd..b7f9a6d4 100644 --- a/lib/screens/notice/notice_page.dart +++ b/lib/screens/notice/notice_page.dart @@ -7,12 +7,12 @@ import 'package:tsdm_client/constants/layout.dart'; import 'package:tsdm_client/constants/url.dart'; import 'package:tsdm_client/generated/i18n/strings.g.dart'; import 'package:tsdm_client/models/notice.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/utils/debug.dart'; import 'package:tsdm_client/utils/show_toast.dart'; import 'package:tsdm_client/widgets/notice_card.dart'; import 'package:universal_html/html.dart' as uh; -import 'package:universal_html/parsing.dart'; /// Notice page, shows [Notice] and PrivateMessage of current user. class NoticePage extends ConsumerStatefulWidget { @@ -39,7 +39,7 @@ class _NoticePageState extends ConsumerState { while (true) { final resp = await ref.read(netClientProvider()).get(url); if (resp.statusCode == HttpStatus.ok) { - document = parseHtmlDocument(resp.data as String); + document = ref.read(htmlParserProvider.notifier).parseResp(resp); break; } diff --git a/lib/screens/profile/profile_page.dart b/lib/screens/profile/profile_page.dart index 9f6c579c..37cc8d3b 100644 --- a/lib/screens/profile/profile_page.dart +++ b/lib/screens/profile/profile_page.dart @@ -6,6 +6,7 @@ import 'package:tsdm_client/constants/url.dart'; import 'package:tsdm_client/extensions/universal_html.dart'; import 'package:tsdm_client/generated/i18n/strings.g.dart'; import 'package:tsdm_client/providers/auth_provider.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/providers/root_content_provider.dart'; import 'package:tsdm_client/routes/screen_paths.dart'; @@ -17,7 +18,6 @@ import 'package:tsdm_client/widgets/check_in_button.dart'; import 'package:tsdm_client/widgets/debounce_buttons.dart'; import 'package:tsdm_client/widgets/obscure_list_tile.dart'; import 'package:universal_html/html.dart' as uh; -import 'package:universal_html/parsing.dart'; class ProfilePage extends ConsumerStatefulWidget { const ProfilePage({this.uid, super.key}); @@ -275,7 +275,8 @@ class _ProfilePageState extends ConsumerState { if (snapshot.hasData) { final resp = snapshot.data; - final document = parseHtmlDocument(resp!.data as String); + final document = + ref.read(htmlParserProvider.notifier).parseResp(resp!); return _buildProfile(context, document); } diff --git a/lib/screens/thread/post_list.dart b/lib/screens/thread/post_list.dart index 2a1a91d7..225cf8f8 100644 --- a/lib/screens/thread/post_list.dart +++ b/lib/screens/thread/post_list.dart @@ -13,6 +13,7 @@ import 'package:tsdm_client/generated/i18n/strings.g.dart'; import 'package:tsdm_client/models/normal_thread.dart'; import 'package:tsdm_client/models/reply_parameters.dart'; import 'package:tsdm_client/packages/html_muncher/lib/html_muncher.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/jump_page_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/providers/screen_state_provider.dart'; @@ -21,7 +22,6 @@ import 'package:tsdm_client/utils/debug.dart'; import 'package:tsdm_client/utils/show_toast.dart'; import 'package:tsdm_client/widgets/list_app_bar.dart'; import 'package:universal_html/html.dart' as uh; -import 'package:universal_html/parsing.dart'; // enum _MenuActions { // refresh, @@ -147,7 +147,7 @@ class _PostListState extends ConsumerState> { '${widget.fetchUrl}${widget.canFetchMorePages ? "&page=$_pageNumber" : ""}', ); if (d1.statusCode == HttpStatus.ok) { - document = parseHtmlDocument(d1.data as String); + document = ref.read(htmlParserProvider.notifier).parseResp(d1); break; } if (!mounted) { diff --git a/lib/widgets/forum_card.dart b/lib/widgets/forum_card.dart index 885010c4..d0e63f96 100644 --- a/lib/widgets/forum_card.dart +++ b/lib/widgets/forum_card.dart @@ -151,7 +151,7 @@ class _ForumCardState extends ConsumerState { maxLines: 2, ), subtitle: widget.forum.latestThreadTime != null - ? Text(widget.forum.latestThreadTime!.elapsedTillNow()) + ? Text(widget.forum.latestThreadTime!.elapsedTillNow(ref)) : null, ), if (showShortCut) diff --git a/lib/widgets/post_card.dart b/lib/widgets/post_card.dart index 85dd71bd..971edc24 100644 --- a/lib/widgets/post_card.dart +++ b/lib/widgets/post_card.dart @@ -50,7 +50,7 @@ class _PostCardState extends ConsumerState ), ), title: Text(widget.post.author.name), - subtitle: Text('${widget.post.publishTime?.elapsedTillNow()}'), + subtitle: Text('${widget.post.publishTime?.elapsedTillNow(ref)}'), trailing: widget.post.postFloor == null ? null : Text('#${widget.post.postFloor}'), diff --git a/lib/widgets/refresh_list.dart b/lib/widgets/refresh_list.dart index 220fb279..8bc389cc 100644 --- a/lib/widgets/refresh_list.dart +++ b/lib/widgets/refresh_list.dart @@ -4,9 +4,9 @@ import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:tsdm_client/constants/layout.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:universal_html/html.dart' as uh; -import 'package:universal_html/parsing.dart'; /// A refreshable [ListView]. /// @@ -84,7 +84,7 @@ class _RefreshListState extends ConsumerState> { if (!mounted) { return; } - final document = parseHtmlDocument(resp.data as String); + final document = ref.read(htmlParserProvider.notifier).parseResp(resp); final data = await widget.buildDataCallback(document); setState(() { _allData.addAll(data); @@ -101,7 +101,7 @@ class _RefreshListState extends ConsumerState> { return; } final resp = await ref.read(netClientProvider()).get(_nextPageUrl!); - final document = parseHtmlDocument(resp.data as String); + final document = ref.read(htmlParserProvider.notifier).parseResp(resp); final data = await widget.buildDataCallback(document); setState(() { _allData.addAll(data); diff --git a/lib/widgets/reply_bar.dart b/lib/widgets/reply_bar.dart index 6b66185f..dec2a94c 100644 --- a/lib/widgets/reply_bar.dart +++ b/lib/widgets/reply_bar.dart @@ -8,10 +8,10 @@ import 'package:tsdm_client/constants/layout.dart'; import 'package:tsdm_client/constants/url.dart'; import 'package:tsdm_client/generated/i18n/strings.g.dart'; import 'package:tsdm_client/models/reply_parameters.dart'; +import 'package:tsdm_client/providers/html_parser_provider.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/utils/debug.dart'; import 'package:tsdm_client/utils/show_dialog.dart'; -import 'package:universal_html/parsing.dart'; class ReplyBar extends ConsumerStatefulWidget { const ReplyBar({required this.controller, super.key}); @@ -133,7 +133,8 @@ class _ReplyBarState extends ConsumerState { return false; } - final replyWindowDoc = parseHtmlDocument(replyWindowResp.data as String); + final replyWindowDoc = + ref.read(htmlParserProvider.notifier).parseResp(replyWindowResp); final inputList = replyWindowDoc.querySelectorAll('input'); String? formHash; diff --git a/lib/widgets/thread_card.dart b/lib/widgets/thread_card.dart index 7b13fe07..fe3d730f 100644 --- a/lib/widgets/thread_card.dart +++ b/lib/widgets/thread_card.dart @@ -50,7 +50,7 @@ class _CardLayout extends ConsumerWidget { if (latestReplyTime != null) ( Icons.timelapse_outlined, - latestReplyTime!.elapsedTillNow(), + latestReplyTime!.elapsedTillNow(ref), ), if ((price ?? 0) > 0) (FontAwesomeIcons.coins, '$price'), ];