- Ruby 41.4%
- C++ 27.9%
- Kotlin 24.2%
- Python 3.2%
- Shell 1.7%
- Other 1.6%
|
All checks were successful
/ Build arm64 package (cross-compiled) (push) Successful in 1m0s
/ Build x86_64 package (push) Successful in 1m41s
/ Run cppcheck static analysis (push) Successful in 38s
/ Check for duplicate code (push) Successful in 39s
/ Build Android APK (push) Successful in 4m11s
/ Ruby syntax check (push) Successful in 42s
/ Run server tests (push) Successful in 3m35s
/ Run gcc static analyzer to detect possible bugs (push) Successful in 1m22s
/ Run end-to-end client-server test (push) Successful in 3m35s
|
||
|---|---|---|
| .forgejo/workflows | ||
| android | ||
| client | ||
| doc | ||
| scripts | ||
| server | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Dockerfile.cross-aarch64 | ||
| duplo.ignore | ||
| PKGBUILD | ||
| README.md | ||
NovelReader
An offline-first web novel reader. It consists of three components in one monorepo:
client/— Qt5 C++ reader for mobile Linux phones (PinePhone running Arch Linux ARM). x86_64 builds are maintained for debugging during development.server/— Lightweight Ruby API server (WEBrick + Sequel + ERB) with auth, catalog/chapter/update endpoints, a polite scraping pipeline, search, per-user sync endpoints (reading progress, library, time tracking), and admin tooling (takedown, novelpack import/export, registrations).android/— Feature-parity Android reader (Kotlin + Jetpack Compose).
Features
Client (PinePhone Qt5 + Android)
- Library – Browse novels organized in a hierarchical group/leaf structure (SQLite-backed)
- Reader – Rich-text chapter view with scroll-position persistence and progress tracking
- Automatic updates – Pulls novel updates from a configurable server via API (
GET /api/v1/head+GET /api/v1/updates) - Offline-first caching – Opportunistic download on unmetered connections, LRU eviction within a configurable storage limit, favorites protection
- Novelpack import – Import
.novelpackarchives from local/USB storage - Time tracking – Per-novel and per-day reading statistics (opt-in, local-first)
- Reading history – Daily activity view
- Touch-friendly – Custom
TouchScrollAreaandTouchButtonwidgets for finger scrolling
Server (Ruby)
- Auth with bcrypt, session/bearer tokens, registration requiring admin approval (first user auto-activated)
- Catalog, chapter, update, search, and per-user sync endpoints (
/api/v1/...) - Per-endpoint rate limiting (login, chapter downloads, search, general API abuse)
- Polite scraping pipeline (
Scraper::Base) with rate limiting, robots.txt respect, caching, backoff, and DB-backed retry state - Admin endpoints: trigger scraping, view logs, approve/reject registrations, toggle registration, content takedown (410 Gone), per-user data export, novelpack export/import
- Data retention pruning (configurable period, default 365 days) and user account deletion
bin/novelpackCLI for creating/inspecting/extracting.novelpackarchives
Building the PinePhone client
Native build (x86_64)
cd client/src && make -j$(nproc)
Cross-compilation (aarch64)
The PKGBUILD supports cross-compilation for aarch64 (PinePhone) using the Arch Linux cross-toolchain. Set CROSS_COMPILE=1 to enable:
CROSS_COMPILE=1 makepkg
See Dockerfile.cross-aarch64 for the full cross-compilation environment setup.
Building the Android client
cd android && ./build-android.sh
This builds and signs the release APK entirely inside a Docker container (JDK + Android SDK + Gradle); the host is never polluted with a toolchain.
Android Docker images
Two images are used:
novelreader-android-build— the SDK/Gradle toolchain, built locally bybuild-android.shfor APK builds and tests. It installs Build Tools 34 and 35.git.kargulok.net/brad/novelreader-android-ci:latest— the CI image. It extends the build image and pre-warms the Gradle and Robolectric caches so the CIandroid-buildjob can run fully offline. It is a multi-stage build that copies only the offline-essential caches (~/.gradle/caches/modules-2and~/.m2) into separate layers, keeping each image layer small for reliable registry pushes. The Gradle transform cache is intentionally omitted: Gradle re-transforms the cached jars locally, so it is not needed offline.
Rebuild and publish the CI image whenever Gradle dependencies change (build.gradle.kts / settings.gradle.kts), otherwise the offline CI steps fail on missing artifacts:
cd android && ./build-ci-image.sh
This builds both images and pushes the CI image to the registry. The signing keystore is excluded from the image via android/.dockerignore; CI uses the keystore from the checked-out repo, never the image.
Running the server
cd server && bundle exec ruby app.rb
See server/README.md for details.
Testing
- Server integration/unit tests:
cd server && bundle exec ruby test/all_tests.rb && bundle exec ruby test/rate_limit_tests.rb - Android JVM unit/integration tests:
gradle :app:testDebugUnitTest(in theandroid/Docker build container) - PinePhone end-to-end test:
scripts/e2e-test
Building the duplo-check image
scripts/duplo-check wraps the duplo duplicate-code checker with an ignore list (duplo.ignore) so approved blocks can be excluded from CI failures. It runs inside a dedicated image:
docker build -f scripts/Dockerfile.duplo -t novelreader-duplo .
docker tag novelreader-duplo git.kargulok.net/brad/novelreader-duplo:latest
docker push git.kargulok.net/brad/novelreader-duplo:latest
To seed the ignore list with the current findings:
find ./client/src -name '*.cpp' -o -name '*.hpp' | grep -v moc_ | python3 scripts/duplo-check --list-keys
Installing the PinePhone package
Build the Arch Linux package with makepkg and install it:
makepkg -si
The binary is installed to /opt/novel-reader/NovelReader.
Project structure
├── client/
│ └── src/ # Qt5 C++ client
│ ├── NovelReader.pro # qmake project file
│ ├── main.cpp # Entry point
│ ├── top_level.cpp/hpp # Main window with bottom tab navigation
│ ├── library.cpp/hpp # Novel library browser
│ ├── reader.cpp/hpp # Chapter reader view with time tracking
│ ├── updates.cpp/hpp # Update fetcher and resolver
│ ├── history.cpp/hpp # Daily reading history
│ ├── more.cpp/hpp # Settings (server URL configuration)
│ └── widgets/ # Touch-friendly UI widgets
│ ├── touch_button.cpp/hpp
│ └── touch_scroll_area.cpp/hpp
├── server/ # Ruby API server
│ ├── app.rb # WEBrick entry point + routes
│ ├── app/ # controllers (incl. admin), models, scrapers, pipeline, scheduler, novelpack
│ ├── lib/ # Router, ControllerBase, rate limiter, retention
│ ├── db/migrations/ # Sequel migrations
│ └── test/ # Integration + unit tests
├── android/ # Kotlin + Jetpack Compose client
│ ├── app/src/main/ # Android app source
│ ├── app/src/test/ # JVM unit/integration tests
│ └── build-android.sh # Dockerized APK build
├── doc/ # Spec, plan, requirements, testing matrix
└── PKGBUILD # Arch Linux package definition
License
GPL-3.0-or-later