Get notified when your favorite streamers go live on SOOP. Fast, reliable alerts with easy setup.
flowchart TD
A[Discord Admin link command] --> B[Store guild_streamers]
A2[Default channel or template commands] --> B2[Store guild_settings]
subgraph Polling Loop
C[Load all links] --> D[Collect unique streamer IDs]
D --> E[SOOP /broad/list]
E --> F{Is streamer live?}
F -->|No| G[Update live_status is_live=false]
F -->|Yes| H[Fetch channel broad info]
H --> I{broadNo changed?}
I -->|No| J[Skip notify]
I -->|Yes| K[Build message + embed]
K --> L[Queue notification]
L --> M[Notifier sends with retry and burst]
M --> N[Update live_status with broadNo]
end
- Multi-server support with per-guild configuration.
- Notifies once per live session (tracks
broadNoto avoid spam). - Discord embeds with title, category, viewers, and thumbnail.
- Postgres or SQLite storage, FastAPI health endpoints, and a built-in send queue.
- Discord bot: Slash commands, admin-only safeguards, and optional sharding.
- SOOP polling: Polls
/broad/listand verifies live sessions viabroadNo. - Storage: Postgres (recommended) or SQLite for local dev.
- Notifier: In-process queue with burst mode and retry/backoff.
- API: FastAPI health endpoints (
/,/healthz,/readyz).
-
Go to the Discord Developer Portal and create a new application.
-
In Bot:
- Click Add Bot.
- Copy Bot Token and Application ID.
-
In OAuth2 → URL Generator:
- Scopes:
bot,applications.commands - Bot Permissions: View Channels, Send Messages, Read Message History
- Scopes:
-
Use the generated invite URL to add the bot to your server.
-
Create and fill
.env(see Configuration).- You can start from
.env.example.
- You can start from
-
Install dependencies and run:
uv sync
uv run uvicorn soupnotify.app.main:app --host 0.0.0.0 --port 8000- Start the Discord bot worker (requires Discord env vars):
uv run python -m soupnotify.botLive detection uses the channel endpoint and returns a live payload per streamer. Adjust parsing in
soupnotify/soop/client.pyif needed.
Minimum env values for API only (Postgres via docker-compose):
DATABASE_URL=postgresql+psycopg://root:change_me@db:5432/soupnotify
You can also run SQLite locally:
DATABASE_URL=sqlite:///soop.db
Additional required env values to run the bot:
DISCORD_TOKEN=your_bot_token
DISCORD_APPLICATION_ID=your_app_id
| Variable | Description | Required |
|---|---|---|
| DISCORD_TOKEN | Discord bot token | Yes |
| DISCORD_APPLICATION_ID | Discord application ID | Yes |
| DISCORD_GUILD_ID | Dev-only: enable instant slash command sync | No |
| SOOP_CHANNEL_API_BASE_URL | Channel API base URL (single streamer) | No |
| SOOP_CHANNEL_HEADERS | Optional JSON headers for channel API | No |
| SOOP_HARDCODE_STREAMER_ID | Force-check one streamer via channel API | No |
| SOOP_STREAM_URL_BASE | Base URL for streamer pages | No |
| SOOP_THUMBNAIL_URL_TEMPLATE | Thumbnail template (uses {broad_no}) | No |
| NOTIFY_CHANNEL_ID | Default Discord channel for notifications | No |
| DATABASE_URL | Database connection string | Yes |
| POLL_INTERVAL_SECONDS | Polling interval in seconds | No |
| SOOP_RETRY_MAX | SOOP request retry attempts | No |
| SOOP_RETRY_BACKOFF | Base seconds for retry backoff | No |
| SOOP_INFO_COOLDOWN_SECONDS | Cache cooldown for channel info | No |
| NOTIFY_RATE_PER_SECOND | Max notification send rate | No |
| NOTIFY_BURST_RATE_PER_SECOND | Burst send rate when queue is large | No |
| NOTIFY_BURST_THRESHOLD | Queue size that triggers burst mode | No |
| SHARD_COUNT | Discord shard count (scale) | No |
| LOG_LEVEL | Logging level (info, debug) | No |
SOOP API docs (reference):
https://openapi.sooplive.co.kr/apidoc#broadcast-filtering-registrationmodificationpost
Live detection uses the channel endpoint:
https://api-channel.sooplive.co.kr/v1.1/channel/<soop_channel_id>/home/section/broad
If some channels return empty JSON, set optional headers:
SOOP_CHANNEL_HEADERS={"User-Agent":"Mozilla/5.0","Referer":"https://www.sooplive.co.kr/","Origin":"https://www.sooplive.co.kr"}
/link soop_channel:<id> [notify_channel:<#channel|id>](uses default if set)/unlink soop_channel:<id>/unlink_all/status(lists linked streamers)/test [soop_channel:<id>]/template action:<set|clear|list> soop_channel:<id> message_template:<text>/embed_template action:<set|clear|show> title:<text> description:<text> color:<hex>/default_channel action:<set|clear> channel:<#channel|id>/mention action:<set|clear|show> type:<none|everyone|role> role:<@role>/link_list [page:<n>] [soop_channel:<id>] [notify_channel:<#channel|id>]/config/metrics/help/debug_live_status/reset_live_status/admin_role/audit_channel/rate_limit/sync/preview
Template variables:
{soop_channel_id}{soop_url}(e.g.https://play.sooplive.co.kr/<id>){notify_channel}{guild}
Embed template supports the same variables for title/description.
Notifications include a Discord embed (title, category, viewers) when SOOP channel info is available.
Embed image is built from SOOP_THUMBNAIL_URL_TEMPLATE using the broadNo value.
Admin-only commands: /link, /unlink, /unlink_all, /template set/clear, /embed_template, /default_channel, /mention, /config, /metrics, /sync, /debug_live_status, /reset_live_status, /admin_role, /audit_channel, /rate_limit.
Admin access is granted to users with Manage Server, Administrator, or the role set by /admin_role.
Commands are organized into separate cogs (Linking, Notifications, Templates, Admin) for easier maintenance.
Live notifications are de-duplicated per streamer using the latest broadNo so restarts do not spam.
- Set the default notify channel:
/default_channel action:set channel:#channel-you-want
- Link a SOOP channel:
/link soop_channel:<id>
Example:
/link soop_channel:gssspotted
Build the container:
docker build -t soupnotify:latest .Run with environment variables:
docker run --rm -p 8000:8000 \
--env-file .env \
soupnotify:latestFor production, run the bot and API as separate processes (recommended):
- API:
uv run uvicorn soupnotify.app.main:app --host 0.0.0.0 --port 8000 - Bot worker:
uv run python -m soupnotify.bot
docker compose up --buildDefaults (from docker-compose.yml):
POSTGRES_USER=root
POSTGRES_PASSWORD=change_me
POSTGRES_DB=soupnotify
DATABASE_URL=postgresql+psycopg://root:change_me@db:5432/soupnotify
The compose file runs two services (api + bot) plus Postgres, and validates required env vars on startup.
This project uses a bot process group in fly.toml, so Fly runs the bot even though the
Dockerfile defaults to the API.
[processes]
bot = "uv run python -m soupnotify.bot"
Deploy:
flyctl deploy -a soup-notify
Scale the bot process:
fly scale count 1 --process bot
Run database migrations with Alembic (Postgres or SQLite). The app requires migrations before startup:
uv run alembic upgrade headThis project is migrations-only. Alembic versions in alembic/versions/ are the single source of truth
for schema changes. SQLAlchemy models are not used to define tables.
When you change the schema:
- Create a new Alembic version file with imperative operations.
- Run
uv run alembic upgrade head. - Update
Storagetable definitions to match the new schema (queries only).
- Start only the database:
docker compose up -d db- Install dependencies (includes Postgres driver):
uv sync- Run migrations against the Postgres container:
docker compose exec api uv run alembic upgrade head- Optional: run API + bot after migrations:
docker compose up --buildUse a strong password and update both .env and docker-compose.yml.
Example (generate 24 random characters):
python -c "import secrets; print(secrets.token_urlsafe(24))"Then set:
POSTGRES_PASSWORD=your_generated_password
DATABASE_URL=postgresql+psycopg://root:your_generated_password@db:5432/soupnotify
uv run pytestSee CONTRIBUTING.md.
MIT. See LICENSE.
