Skip to content

SpaceSquare640/PokeTrack-App

Repository files navigation

PokéTrack

CI Release License: MIT

A community-activity monitor for Pokémon GO. It fetches upcoming and live events, stores them locally, and shows them through two interfaces that share one design system — a CustomTkinter desktop app and a Flask web app, both in a consistent Midnight Blue dark theme.

Important

Unofficial fan project. PokéTrack is not affiliated with, endorsed by, or sponsored by Niantic, Nintendo, The Pokémon Company, or Leek Duck. "Pokémon" and "Pokémon GO" are trademarks of their respective owners, used here for identification only. Event data is sourced from Leek Duck via ScrapedDuck for informational, non-commercial use. Software provided "AS IS", without warranty. See DISCLAIMER.md and LICENSE.


Features

Data source Robust parser over the ScrapedDuck JSON feed (a structured Leek Duck mirror), plus a best-effort official-blog HTML source behind the same interface.
Hybrid GUI Desktop (CustomTkinter) + Web (Flask + Tailwind), both Midnight Blue dark mode driven by one shared palette.
i18n English (default), Traditional Chinese, Simplified Chinese, Japanese, Korean — every string lives in languages.json.
Region filtering Pick the regions you care about; Global events always show. Rules live in editable data/regions_map.json.
Search & filters Live search + event-type filter + favorites in both UIs, on top of the region filter.
Rich cards & detail view Thumbnails, synthesised descriptions, live countdowns, a live/upcoming/total stats bar, and a click-through event detail view (desktop modal / web page).
Favorites Star event types; filter to favorites and optionally notify only for favorites.
Calendar export Per-event or filtered .ics export, plus a subscribable web feed at /calendar.ics.
Notifications Desktop + in-app alerts, webhooks (Discord/Slack/custom, optionally HMAC-signed), and Telegram — when new events appear in your regions.
Config import/export Back up or move your settings from either UI.
Async + responsive async/await (httpx) fetches off the GUI thread, with a skeleton loading screen.
Native fast path Optional Rust extension (PyO3) parses the feed + classifies regions in native code, with automatic pure-Python fallback when it isn't installed.
Persistence SQLite (indexed) caches events for offline viewing; old events auto-pruned.
Background updates APScheduler on a configurable interval; the web view auto-detects new events.
Distribution CI (pytest matrix), a Dockerfile for the web app, and a one-file Windows .exe built on release.
Graceful errors Network/format failures never crash the UI — cached data stays visible with a localized message.
Tested Deterministic, offline pytest suite (27 tests) covering core, service, web, calendar, favorites, HMAC, and the scheduler.

Project structure

PokéTrack App/
├── main.py              # entry point → desktop GUI
├── run_web.py           # entry point → web GUI
├── config.json          # user settings (language, regions, interval, webhook, …)
├── languages.json       # ALL UI strings (en / zh-Hant / zh-Hans)
├── requirements.txt
├── LICENSE              # MIT (original code only)
├── DISCLAIMER.md        # trademarks, data attribution, no-warranty notice
├── data/                # SQLite DB + image cache (auto-created)
├── tests/               # offline pytest suite
└── poketrack/
    ├── app_context.py   # builds the shared service (config+db+parser+scheduler)
    ├── config.py        # config.json manager
    ├── i18n.py          # Translator (dotted keys + fallback)
    ├── core/            # data layer — NO UI imports
    │   ├── models.py    # Event model + status/countdown logic
    │   ├── regions.py   # region constants + keyword classifier
    │   ├── database.py  # SQLite persistence (thread-safe, WAL, pruning, migrations)
    │   ├── http.py      # shared requests.Session with retries/backoff
    │   ├── parser.py    # LeekDuck (JSON) + Blog (HTML) sources
    │   ├── native.py    # loader for the optional Rust fast path (guarded)
    │   ├── notify.py    # optional desktop notifications (guarded)
    │   ├── webhook.py   # outgoing webhooks (Discord/Slack/generic)
    │   ├── scheduler.py # APScheduler wrapper
    │   └── service.py   # PokeTrackService — the shared controller
    ├── gui/             # desktop presentation
    │   ├── theme.py     # Midnight Blue palette — single source of truth
    │   ├── images.py    # async thumbnail loader (off-thread, cached)
    │   └── app.py       # CustomTkinter app
    └── web/             # web presentation
        ├── server.py    # Flask app + JSON API
        ├── templates/   # base.html (Tailwind config injected) + index.html
        └── static/      # css/style.css, js/app.js

poketrack-native/        # optional Rust extension (PyO3) — feed parse + region classify
├── Cargo.toml
├── pyproject.toml       # maturin build backend
├── src/lib.rs
└── benchmark.py         # native vs pure-Python benchmark

web-frontend/            # TypeScript interactive layer for the web UI (Vite build)
├── package.json
├── tsconfig.json
├── vite.config.ts
└── src/                 # main.ts, api.ts, countdown.ts, search.ts, favorites.ts …
                         # → compiles to poketrack/web/static/dist/app.js (committed)

Polyglot by design. Python orchestrates and renders; an optional Rust extension accelerates the data path; the web front-end is TypeScript. The Rust and TypeScript layers each degrade/rebuild independently, and the app runs from a plain checkout with neither a Rust nor a Node toolchain — those are only needed to rebuild the compiled artifacts, which are committed.

Architecture at a glance

            ┌──────────────────────────────┐
            │     PokeTrackService          │  ← the only thing both UIs touch
            │  (core/service.py)            │
            └───┬───────────┬───────────┬───┘
   parser.py    │  database │  scheduler│
 (LeekDuck/Blog)│  (SQLite) │ (APSched) │
                ▼           ▼           ▼
        ┌───────────────┐   ┌──────────────────┐
        │ gui/app.py    │   │ web/server.py    │
        │ CustomTkinter │   │ Flask + Tailwind │
        └───────────────┘   └──────────────────┘
                 ▲                   ▲
                 └──── theme.py ─────┘   (one Midnight Blue palette)

The fetching/parsing logic is fully separated from presentation: both front-ends call PokeTrackService only, and neither imports the parser or database directly.


Setup

pip install -r requirements.txt

Python 3.10+ recommended (tested on 3.13 and 3.14). Pillow (thumbnails) and plyer (desktop notifications) are used with graceful fallback — if either is missing the app still runs, just without that one feature.

Multiple Python installs? Make sure you install into the same interpreter you run with. A virtual environment avoids the mix-up entirely:

python -m venv .venv && .venv\Scripts\activate   # Windows
pip install -r requirements.txt

Run

Desktop app

python main.py

Web app

python run_web.py
# then open http://127.0.0.1:5000/

The desktop app's “Open Web View” button launches the web server for you and opens your browser.

Test

python -m pytest -q

The suite is fully offline (a fake source + temp DB) and won't touch your real config.json or data/. It also runs in CI on every push (Python 3.11–3.13).

Build & deploy

Docker (web app)

docker build -t poketrack .
docker run -p 5000:5000 poketrack      # http://localhost:5000/

Standalone Windows executable

pip install pyinstaller
pyinstaller --noconfirm PokeTrack.spec  # -> dist/PokeTrack.exe

On GitHub, publishing a Release triggers .github/workflows/release.yml, which builds PokeTrack.exe and attaches it to that release automatically.

Native fast path (Rust, optional)

PokéTrack has an optional Rust extension (poketrack-native/, built with PyO3 + maturin) that parses the ScrapedDuck feed and classifies regions in native code. It is not requiredpoketrack/core/native.py imports it if present and transparently falls back to the pure-Python path if not, so the app (and the whole test suite) behaves identically either way.

pip install maturin
pip install ./poketrack-native        # compile + install the extension
python poketrack-native/benchmark.py  # compare against the pure-Python path

The build produces an abi3 wheel (cp39-abi3), so a single wheel works on any CPython ≥ 3.9 — no rebuild per interpreter. Measured on the live feed, the native JSON→structured-data step is ~3–5× faster; end-to-end the gain is smaller because building Python Event objects (unavoidable Python work) dominates — the benchmark reports both numbers honestly. Region classification and datetime normalisation keep identical semantics to the Python path (verified by a parity test in tests/).

Web front-end (TypeScript, web-frontend/)

The web UI's interactive layer is TypeScript, bundled by Vite into poketrack/web/static/dist/app.js. It progressively enhances the Flask server-rendered page — the page works with JavaScript disabled; the bundle adds live-ticking countdowns, instant client-side search, no-reload favorite toggling, and the async refresh/poller.

cd web-frontend
npm install
npm run build     # type-check + emit the committed bundle
npm run dev       # optional: Vite dev server

The compiled bundle is committed, so running the app needs no Node — only rebuilding does. CI type-checks and builds it on every push.


Configuration — config.json

{
  "language": "en",                  // "en" | "zh-Hant" | "zh-Hans"
  "regions": ["Global"],             // see region list below
  "source": "leekduck",              // "leekduck" (default) | "blog"
  "refresh_interval_minutes": 60,    // APScheduler interval
  "notifications": true,             // desktop/in-app alerts for new events
  "webhook_url": "",                 // POST new-event alerts here (Discord/Slack/custom)
  "prune_after_days": 45,            // drop events that ended over N days ago
  "database_path": "data/poketrack.db",
  "web": { "host": "127.0.0.1", "port": 5000, "debug": false }
}

Webhooks

Set webhook_url (in config.json or the desktop Settings tab) to receive a POST whenever new events appear in your selected regions. The payload is shaped automatically for the destination:

URL contains Format sent
discord.com/api/webhooks/… Discord { content, embeds[] }
hooks.slack.com/… Slack { text }
anything else Generic { content, text, title, events[] }

The Settings tab has a Send test button to verify your URL.

Everything here is also editable in the app's Settings tab.

config.json is git-ignored (it can hold a private webhook URL). A config.example.json is provided for reference; the app auto-creates config.json from defaults on first run.


Internationalisation — languages.json

All UI text is loaded from languages.json, grouped by language then by section:

{ "en": { "events": { "view_details": "View Details" } } }

Code looks strings up by dotted key with graceful fallback (current language → English → the key itself):

service.t("events.view_details")
service.t("events.last_updated", time="…")   # supports {placeholders}

To add a language: copy the "en" block to a new top-level key (e.g. "ja"), translate the values, and add its display name under every languages block. No code changes needed.


Regions

Global, North America, South America, Europe, Asia, Oceania, Africa.

Leek Duck data isn't region-tagged, so PokéTrack infers a region from the event name using a keyword map (core/regions.py) — Safari Zones / GO Tour stops map to their city/continent; everything else stays Global and shows for everyone. Selecting a region (e.g. Asia) shows Global + Asia. Extend the keyword map freely as new regional events appear.


Design system — Midnight Blue

Deep blues, charcoal grays, and slate accents, defined once in poketrack/gui/theme.py. The desktop UI reads the hex values directly; the web layer injects the same values into its Tailwind config and CSS variables (web/templates/base.html), so the two interfaces stay identical.

Token Hex Use
bg #0B1120 App background
surface #111827 Cards / panels
surface_alt #1E293B Elevated surfaces
border #334155 Slate borders
primary #3B82F6 Primary actions
accent #38BDF8 Highlights
text / text_muted #E2E8F0 / #94A3B8 Text
success / warning / danger #34D399 / #FBBF24 / #F87171 LIVE / SOON / errors

Notes & troubleshooting

  • First launch shows an empty list for a second while the initial fetch runs in the background; it fills in automatically.
  • Offline? Cached events from the last successful fetch remain visible and a localized "showing cached data" message appears.
  • The default data source depends on a public community feed; if it's unreachable, switch "source" to "blog" in config.json.

Credits

License & legal

  • Original code: released under the MIT License. The MIT grant covers this project's source code only.
  • Trademarks & third-party data: Pokémon / Pokémon GO intellectual property and Leek Duck event data are not covered by the MIT license and remain the property of their respective owners. This is an unofficial, non-commercial fan project with no affiliation or endorsement, provided "AS IS" without warranty.
  • Full terms: DISCLAIMER.md.

Replace the copyright holder in LICENSE with your name/handle before publishing if you'd like attribution.