Conversation
…shReporter Package skeleton (analysis_options, pubspec, main library file, abstractions test) plus a clean rename of the existing CrashReporter from lib/api/ to the package's lib/src/abstractions/. Consumers in helpers / parsers / firebase adapter import from package:nkust_crawler directly — no shim file left behind at lib/api/crash_reporter.dart.
…lities, models) to nkust_crawler All 26 .dart files (plus 17 .g.dart counterparts and 6 capability interfaces) move as git renames. Storage-coupled models route their save/load through the new KeyValueStore abstraction; PreferenceUtilKeyValueStore is wired at app bootstrap. Consumers across lib/pages/, lib/widgets/, lib/extensions/, lib/utils/, and lib/api/ are updated in this same commit to import from `package:nkust_crawler/nkust_crawler.dart` directly — no shim files left behind at lib/api/ or lib/models/.
…e shim Parsers (ap/stdsys/leave/vms_bus/nkust) all appear as git renames. build_mode.dart is a new pure-Dart kDebugMode shim that lets parser code stay independent of package:flutter/foundation.dart. Consumers updated to import from package:nkust_crawler — no shim files.
…aSolver/PdfTextExtractor seams api_config, helper (facade), and the 5 helper classes (ap/leave/nkust/stdsys/vms_bus) all appear as git renames. New seams introduced: - CaptchaSolver — webap/nkust login no longer hard-codes the EucDist solver - PdfTextExtractor — stdsys transcript parsing routes through this abstraction so syncfusion stays in the host app Consumers updated to import from package:nkust_crawler — no shim files at lib/api/.
…cted templates eucdist.dart moves as a git rename. captcha_utils.dart is replaced by captcha_solver_impl.dart, which is a substantial rewrite (now a CaptchaSolver-implementing class with injected CaptchaTemplateProvider) — honestly recorded as delete+add rather than a forced rename. AssetCaptchaTemplateProvider on the host side reads BMP templates from rootBundle and is wired at app bootstrap.
Single entry point for wiring the crawler's host-side dependencies (platform Dio adapter, KeyValueStore, CrashReporter, CaptchaSolver, PdfTextExtractor, onLogout). main.dart calls it once after PreferenceUtil init.
|
Failed to generate code suggestions for PR |
There was a problem hiding this comment.
Code Review
This pull request extracts the NKUST crawler logic into a standalone pure-Dart package, nkust_crawler, to facilitate code reuse across different platforms. The refactoring involves moving core business logic, models, and parsers to the new package while introducing abstraction interfaces for host-side dependencies like storage and crash reporting. Feedback focuses on consolidating redundant imports across several page files, using dynamic filenames instead of hardcoded strings for proof image uploads, and refactoring extension methods to remove dependencies on static global state for improved maintainability.
| import 'package:nkust_crawler/nkust_crawler.dart'; | ||
| import 'package:nkust_crawler/nkust_crawler.dart'; | ||
| import 'package:nkust_crawler/nkust_crawler.dart'; |
There was a problem hiding this comment.
There are multiple redundant imports of package:nkust_crawler/nkust_crawler.dart. Please consolidate them into a single import statement to improve code clarity and maintainability.
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; |
| if (image != null) { | ||
| proof = ( | ||
| bytes: await image!.readAsBytes(), | ||
| filename: 'proof_image.jpg', |
There was a problem hiding this comment.
| import 'package:nkust_crawler/nkust_crawler.dart'; | ||
| import 'package:nkust_crawler/nkust_crawler.dart'; | ||
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; | ||
| import 'package:nkust_ap/api/stdsys_helper.dart'; | ||
| import 'package:nkust_crawler/nkust_crawler.dart'; |
There was a problem hiding this comment.
There are multiple redundant imports of package:nkust_crawler/nkust_crawler.dart. Please consolidate them into a single import statement to improve code clarity.
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; | |
| import 'package:nkust_ap/api/stdsys_helper.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; |
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; | ||
| import 'package:nkust_ap/api/helper.dart'; | ||
| import 'package:nkust_crawler/nkust_crawler.dart'; | ||
| import 'package:nkust_ap/config/constants.dart'; |
There was a problem hiding this comment.
There are multiple redundant imports of package:nkust_crawler/nkust_crawler.dart. Please consolidate them into a single import statement to improve code clarity.
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; | |
| import 'package:nkust_ap/api/helper.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_ap/config/constants.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; |
| import 'package:nkust_crawler/nkust_crawler.dart'; | ||
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; | ||
| import 'package:nkust_ap/api/helper.dart'; | ||
| import 'package:nkust_ap/models/room_data.dart'; | ||
| import 'package:nkust_crawler/nkust_crawler.dart'; |
There was a problem hiding this comment.
There are multiple redundant imports of package:nkust_crawler/nkust_crawler.dart. Please consolidate them into a single import statement to improve code clarity.
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; | |
| import 'package:nkust_ap/api/helper.dart'; | |
| import 'package:nkust_ap/models/room_data.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_crawler/nkust_crawler.dart'; | |
| import 'package:nkust_ap/api/exceptions/api_exception_l10n.dart'; |
| } | ||
|
|
||
| extension SemesterExtension on Semester { | ||
| String get cacheSaveTag => '${Helper.username}_$code'; |
There was a problem hiding this comment.
This extension method relies on the static field Helper.username. Using static fields for user-specific data can be problematic, especially in scenarios involving user logout and login. While the PR description notes this as future work, consider making this dependency explicit to improve maintainability. For example, the method could accept the username as a parameter: String cacheSaveTag(String username) => '${username}_$code';
Live tests previously printed real student id / name / dept / class / course title / score detail when run with NKUST_USER+NKUST_PASS, which would leak the running account's identity if the output was pasted into PR comments, screenshots, CI logs, or chat. Mask via redact() so e.g. 'C112151117' shows as 'C••••••••7' and '梁晨恩' shows as '梁•恩'. Picture URLs (which embed the student id) collapse to '<set>'. Score detail (conduct, average) is hidden entirely; only the row count is printed. NKUST_HTTP_LOG=1 dumps response bodies + cookies — emit a loud warning when that flag is on so callers know the output must stay private. .gitignore picks up .env / *.har / *.dump / nkust_*_dump patterns to block accidental commits.
The remaining lib/api/{helper,ap_helper,stdsys_helper}.dart shims were
removed in the package extraction, so this test had stale imports CI
flagged as URI doesn't exist. Replace them with the single
`package:nkust_crawler/nkust_crawler.dart` facade import (which
re-exports Helper, WebApHelper, StdsysHelper, and the data types this
file uses).
- Dedup duplicate package:nkust_crawler/nkust_crawler.dart imports across 9 page files (reviewer flagged 4; the same sed regex regression hit 5 more I missed). - Use image.name as the multipart filename for leave proof uploads instead of hardcoded 'proof_image.jpg', so the server (and any log/audit downstream of it) sees what the user actually picked. Skipped: the cacheSaveTag-on-Helper.username comment. That static dependency is pre-existing in master and removing it would require threading the username through every page's score/course cache call. PR description already lists 'drop Helper static fields' as future work.
Root-level flutter analyze was picking up packages/nkust_crawler/test/*.dart and failing on package:test/test.dart URI-not-exist — that import resolves via the sub-package's dev_dependencies, which are not part of the main app's pub resolution. The sub-package is still analysed (and its tests still run) via 'cd packages/nkust_crawler && dart analyze && dart test' from inside its own directory. Add the gotcha to the extraction guide.
Issue #331's '方案 B' (scheduled live monitor) was workflow-passing but test-failing because flutter test in crawler-monitor.yml could not resolve native_dio_adapter / syncfusion_flutter_pdf / image_picker plugins on a Linux runner. Now that the crawler is a pure-Dart package, that whole class of failure goes away. ci.yml — Analyze & Test job: - watch packages/** on push/PR - analyze + test the sub-package via dart analyze / dart test inside packages/nkust_crawler (no Flutter SDK needed) - drop '|| true' from 'flutter test' so main-app fixture test failures block PR merge again (Issue #331 方案 A is now actually enforced) crawler-monitor.yml: - replace flutter test with dart test -P live against the package - map existing NKUST_USERNAME / NKUST_PASSWORD secrets to the package's expected NKUST_USER / NKUST_PASS env vars - swap subosito/flutter-action for dart-lang/setup-dart New Health Check suite in packages/nkust_crawler/test/live/ covers the 6 endpoints the old test had (Oosaf supersedes the retired leave.nkust.edu.tw DNS record). Test names still start with 'Health Check' so the classify-failures step can distinguish server-down from parser-drift. Delete test/crawler_monitor_test.dart — its coverage is now in packages/nkust_crawler/test/live/{health_check,authenticated}_live_test.dart.
dart analyze defaults to --fatal-warnings, so the new 'Analyze (nkust_crawler package)' CI step was failing on 9 unused imports (helpers, parsers, facade) plus one unnecessary null-aware '?.' operator in the live test. Drop them — these were leftovers from the import-shuffle during the package extraction.
摘要
把
lib/api/、lib/models/、lib/utils/eucdist.dart、lib/utils/captcha_utils.dart整套抽到新建的純 Dart 套件packages/nkust_crawler/。merge 之後這個套件可以單獨dart test(不需要 Flutter SDK)、可以 server-side 拿來用、也能給未來的 native 客戶端共用。-c diff.renames=true log --diff-filter=R可以看到)lib/api/shim 檔:0 個 — 所有 consumer(pages / widgets / extensions / tests)在搬檔的同一個 commit 直接改 import 為package:nkust_crawler/nkust_crawler.dart。lib/api/exceptions/api_exception_l10n.dart是唯一留下的,因為它依賴BuildContext+NkustLocalizationsCrashReporter、KeyValueStore、CaptchaSolver+CaptchaTemplateProvider、PdfTextExtractor。Host adapter 放在lib/integrations/crawler/,由main.dart的bootstrapCrawler()一次接好規模
git mvcrash_reporterbuild_mode.dart(純 DartkDebugModeshim)CaptchaSolver/PdfTextExtractor注入點eucdist、captcha_utils→captcha_solver_impl)bootstrapCrawler()集中 wiringgetSemesters加 antiforgery、wall-clock 學期 helper、fixture 更新)架構說明
packages/nkust_crawler/— 純 Dart,依賴只有ap_common_core、dio、cookie_jar、html、http、http_parser、image、intl、json_annotation、sprintf。沒有 Flutter、Firebase、ImagePicker、native_dio_adapter、syncfusion。Host 端 adapter(在
lib/integrations/crawler/):firebase_crash_reporter.dart—CrashReporter接 Firebasepreference_util_key_value_store.dart—KeyValueStore接PreferenceUtilasset_captcha_template_provider.dart—CaptchaTemplateProvider接rootBundlesyncfusion_pdf_text_extractor.dart—PdfTextExtractor接syncfusion_flutter_pdf(這套 transitively 依賴dart:ui,所以需要這個介面隔離)crawler_bootstrap.dart— 整合所有 wiring 的單一進入點,main.dart啟動時呼叫有持久化狀態的 model(
bus_reservations_data、crawler_selector、leave_data)的save/load走crawlerStorage,bootstrap 時用PreferenceUtilKeyValueStore接起來。驗證
flutter analyze lib/flutter test test/cd packages/nkust_crawler && dart analyzecd packages/nkust_crawler && dart testdart test -P live-anonymousacad.nkust.edu.tw+ 6 個 health check endpoint 全部通過dart test -P live(帶NKUST_USER/NKUST_PASS)CI 整合(順手解掉 #331)
Issue #331「方案 B 定期 cron 監控」原本因為原生相依(
native_dio_adapter、syncfusion、image_picker)跑不起來,這個 PR 順便修:crawler-monitor.yml:flutter test→cd packages/nkust_crawler && dart test -P live,secrets 對應,subosito/flutter-action → dart-lang/setup-dartci.yml:加 package 的dart analyze+dart test、移除flutter test \|\| true的\|\| true(讓 main test 失敗也擋 PR)test/crawler_monitor_test.dart— coverage 全部搬到packages/nkust_crawler/test/live/{health_check,notifications,authenticated}_live_test.dart文件
docs/下新增兩份:crawler-package-migration-plan.md— 這個 PR 的逐步實作計畫extracting-flutter-crawler-as-dart-package.md— 通用版 cookbook,給之後其他專案做類似抽取時參考,省掉重複調查測試計畫
flutter run— 登入 + 抓課表 + 抓成績 smoke test(驗native_dio_adapter注入 + stdsys SSO 還能跑)flutter run— 同上flutter build web— 確認沒裝NativeAdapter的 fallback path 也能跑基本匿名 endpoint未來工作(不在本次 PR 範圍)
getRoomList、getRoomCourseTables、getEnrollmentLetter、getRewardAndPenalty、getMidtermAlerts加進 live integration test(read-only,風險低)Helper.username/Helper.password靜態欄位拿掉,把 Helper 改成 instance-based(reviewer 已點到cacheSaveTag用到這個 static field)nkust_crawler到 pub.dev