Archipelago Alerts (formerly AP Tracker) is a tracking and notification service for Archipelago Multiworld games. It polls the rooms you follow and pushes a notification to your phone the moment something you care about happens โ so you can play an async multiworld without babysitting a tracker page.
The project is a monorepo containing a Python backend (Flask API + background poller) and a native Android app (Kotlin / Jetpack Compose).
๐ฑ Google Play ยท ๐ฆ APK downloads (GitHub Releases) ยท ๐ฌ Discord thread
Tracking
- Room management โ add, rename, customize (icon and color), archive, and remove tracked rooms.
- Player selection โ pick exactly which slots in a room you want to watch.
- Per-slot preferences โ override your global notification defaults for any individual slot.
- Snooze โ mute notifications globally or per slot for a set window.
- Guest mode โ start tracking immediately without an account, then upgrade to Discord later without losing your data.
Notifications
- Push via Firebase Cloud Messaging for progression items, useful/filler items, and newly revealed hints.
- Android notification channels โ progression, non-progression, hints, and general each map to their own OS channel, so you can set distinct sounds, vibration, priority, and Do Not Disturb bypass in Android settings.
- Ignore list โ mute specific items or item groups, globally or per game.
- Whitelist โ items that always notify, bypassing ignore rules and category mutes.
Milestones
- Milestone groups โ define a named goal made of several items or item groups; the notification fires only when every requirement is satisfied (AND logic).
- Progress tracking โ server-side counts keep progress accurate even for item-group requirements and for history older than the retention window.
- Milestone templates โ save a milestone as a reusable template, start new ones from a template picker, and export/import templates to share them.
History and widgets
- Activity feed and per-slot history with filtering, backed by delta sync so only new events are downloaded.
- Background sync via WorkManager, with live progress reporting in the UI.
- Home screen widgets โ a Recent Items widget and a Milestones widget, both configurable per room with layout and font-density options.
Integrations and account
- Cheese Tracker sync โ import tracked rooms, sync slot claims bidirectionally, and read slot notes, statuses, and ping preferences. API keys are stored encrypted.
- Discord OAuth 2.0 login (Authorization Code + PKCE).
- In-app What's New sheet driven by the server changelog.
- Self-service account deletion in the app and via a web flow at
/delete-account.
backend/ Python service
app/
routes/ Domain REST blueprints (auth, user, rooms, slots,
thresholds, templates, history, game, whats_new)
services/ Poller engine and workers (poller, threshold, notification,
cheese, datapackage, filtering, retention, redis)
data/ changelog.json โ single source of truth for versions
models.py SQLAlchemy models and composite indexes
poller.py Poller supervisor / room setup engine
db_migrations.py Runs Alembic on startup for Postgres deployments
tests/ unittest suite (run in CI)
run.py API + poller in one process (local dev)
run_api_only.py API container entrypoint
run_poller_only.py Poller container entrypoint
android/ Kotlin / Jetpack Compose app
app/src/main/java/com/jones/aptracker/
network/ Retrofit API, DTOs, DAOs, token/session management
database/ Room database, migrations, milestone cache
repository/ Data layer, history sync manager and worker
ui/ Compose screens and ViewModels
widget/ Glance home screen widgets
alembic/ Database migrations
scripts/ generate_changelog.py
Further reading: architecture.md for system design, Redis pub/sub events, composite indexes, and container topology; LLM.md for a component-by-component overview and the project's gotchas; backend/SECURITY.md for the security model.
Backend (Python 3.11+)
- Flask served by Waitress, split into an API process and a poller process.
- SQLAlchemy 2 ORM over PostgreSQL 15 (UAT/production) or SQLite (local dev only).
- Redis 7 for the
immediate_pollpub/sub bus and datapackage name caching. - Alembic for migrations, applied automatically on startup against Postgres.
- firebase-admin for FCM push (separate Android and iOS credentials supported).
- aiohttp and websockets for Archipelago polling and datapackage fetches.
- Docker Compose for local dev and production deployment on a GCP VM.
Android app (Kotlin)
- Jetpack Compose + Material 3 for the UI, MVVM with a repository layer.
- Glance for home screen widgets.
- Retrofit / OkHttp for the API, with an auth interceptor and a 401 re-authenticator.
- Room for local caching of rooms, history, hints, datapackages, and milestones.
- WorkManager for background history sync.
- AppAuth for Discord OAuth 2.0, EncryptedSharedPreferences for token storage, DataStore for settings.
- Firebase Cloud Messaging for push delivery.
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
pip install -r backend/requirements.txtbackend/requirements.txt is what the container installs (it adds psycopg2-binary and redis); the root file is the local and CI environment.
Generate a service account private key in your Firebase project settings and save the JSON as service-account-key.json in backend/. Override the path with FIREBASE_KEY_FILE_ANDROID if you keep it elsewhere. An iOS key (service-account-key-ios.json, or FIREBASE_KEY_FILE_IOS) is optional โ the backend initializes a second Firebase app only if the file exists.
Create backend/.env:
# Database โ omit entirely to fall back to a local SQLite file
DATABASE_URL=postgresql://ap_user:ap_password@localhost:5432/ap_tracker_dev
REDIS_URL=redis://localhost:6379/0
# Flask
FLASK_ENV=development # development | uat | production
SECRET_KEY=change-me # JWT signing key
LOG_LEVEL=DEBUG # optional override
# Discord OAuth
DISCORD_CLIENT_ID=your-client-id
DISCORD_CLIENT_SECRET=your-client-secret
DISCORD_REDIRECT_URI=http://localhost:5000/web/callback
# Encryption key for stored Cheese Tracker API keys (Fernet key)
ENCRYPTION_KEY=your-fernet-key
# Optional
CHEESE_BASE_URL=https://cheesetrackers.theincrediblewheelofchee.seThe dev compose file brings up PostgreSQL 15 and Redis 7, plus API and poller containers with the app package bind-mounted so backend edits do not need a rebuild:
docker-compose -f docker-compose.dev.yml up -dTo run only the datastores and keep the Python process local, start just the postgres and redis services and point DATABASE_URL at localhost:5432.
-
PostgreSQL: migrations run automatically on startup โ every entrypoint goes through
create_app(), which takes an advisory lock and upgrades to head. To apply them by hand instead:alembic upgrade heads
-
SQLite: no migrations. Tables are created directly from the models on startup.
python backend/run.pyThis serves the API on http://0.0.0.0:5000 and runs the poller in the same process. The containers split these into run_api_only.py and run_poller_only.py.
Each test module owns its own engine and database, so modules must run in separate processes:
PYTHONPATH=backend:. python -m unittest backend.tests.test_cheese_sync -v$env:PYTHONPATH="backend;."; python -m unittest backend.tests.test_cheese_sync -vCI (backend-tests.yml) runs every module in backend/tests/ this way on pushes to main and on PRs touching the backend, and also verifies the changelog is in sync.
-
Open the
android/folder in Android Studio. -
Add
google-services.jsonfrom your Firebase project's Android app settings toandroid/app/. -
Create
android/app/local.properties. Secrets are read from here bybuild.gradle.ktsand are never committed:DISCORD_CLIENT_ID=your-discord-client-id # Your machine's LAN IP, so a physical device can reach your local backend. # Defaults to http://10.0.2.2:5000/ (the emulator's host loopback) if omitted. DEV_API_BASE_URL=http://192.168.1.100:5000/ # Required only when building those flavors โ the build fails without them. UAT_API_BASE_URL=https://uat.example.com/ PROD_API_BASE_URL=https://prod.example.com/
-
Pick a build variant and run. Flavors are
dev,uat, andprod; each installs under its own application id suffix, so they coexist on one device.Build type Purpose debugEveryday development. releaseMinified and shrunk, signed for distribution. minifiedThe release R8 pipeline, but debuggable and debug-signed โ use it to catch missing keep rules before they reach the Play Store. Use
devDebugfor normal local work against your own backend.
The app and the backend are versioned independently. backend/app/data/changelog.json is the single source of truth โ it holds two newest-first arrays, app_releases and server_releases. The CHANGELOG.md files, the landing page version badges, and GET /api/whats_new are all derived from it.
To cut a release:
-
Prepend an entry to the relevant array in
changelog.json. -
For an Android release, bump
versionNameandversionCodeinandroid/app/build.gradle.ktsto match. -
Regenerate the markdown:
python scripts/generate_changelog.py
-
Commit.
scripts/generate_changelog.py --checkruns in CI and fails if the markdown is stale or if the gradleversionNamedisagrees with the newestapp_releasesentry.
Do not hand-edit android/CHANGELOG.md or backend/CHANGELOG.md โ they are generated.
Licensed under the Apache License 2.0.