247 lines
7.5 KiB
Markdown
247 lines
7.5 KiB
Markdown
# Archive Bot
|
||
|
||
An AIO Discord bot foundation for The Mithral Archive. The first module is status monitoring: it checks configured URLs and keeps one live status message updated in the Discord `status` channel.
|
||
|
||
The message uses a summary embed plus one small embed per service, so each service gets its own green or red Discord color bar.
|
||
|
||
It does not need Discord gateway intents or slash commands for the status module. It only needs a bot token, the `status` channel ID, and permission to send/edit its own messages.
|
||
|
||
The bot also includes a small web dashboard for editing monitored services and forcing immediate Discord refreshes.
|
||
|
||
It also has a media catalog module. From the dashboard you can upload `Movies.csv` and/or `Shows.csv`, choose a Discord channel ID, and publish a formatted catalog embed set.
|
||
|
||
## Discord Bot Setup
|
||
|
||
Create a Discord application and bot in the Discord Developer Portal, then invite it with:
|
||
|
||
```text
|
||
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=84992&scope=bot
|
||
```
|
||
|
||
Required channel permissions for each dashboard-selected channel:
|
||
|
||
- View Channel
|
||
- Send Messages
|
||
- Embed Links
|
||
- Attach Files
|
||
- Read Message History
|
||
|
||
Channel IDs are configured from the dashboard and stored in `state/bot-settings.json`.
|
||
|
||
## Local Setup
|
||
|
||
```sh
|
||
cp .env.example .env
|
||
cp services.example.json services.json
|
||
```
|
||
|
||
Edit `.env` and set:
|
||
|
||
- `DISCORD_BOT_TOKEN`
|
||
- `DISCORD_GATEWAY_ENABLED=true`
|
||
- `DASHBOARD_USERNAME`
|
||
- `DASHBOARD_PASSWORD_HASH`
|
||
|
||
Use the raw Discord bot token value. Do not include a `Bot ` prefix.
|
||
|
||
Generate the dashboard password hash:
|
||
|
||
```sh
|
||
python3 status_bot.py --hash-password
|
||
```
|
||
|
||
Paste the output into `DASHBOARD_PASSWORD_HASH`. The dashboard does not store a reusable browser token; it uses an HttpOnly session cookie and CSRF token after login.
|
||
|
||
Dashboard auth includes:
|
||
|
||
- PBKDF2-SHA256 password hashing
|
||
- HttpOnly `SameSite=Strict` session cookie
|
||
- CSRF token required for write actions
|
||
- basic failed-login throttling
|
||
|
||
The bot keeps a Gateway connection open so Discord shows it as online even when no status refresh is happening.
|
||
|
||
Edit `services.json` with the URLs you want displayed. Keep private/internal URLs out of Git if they should not be shared.
|
||
|
||
Preview the Discord embed payload:
|
||
|
||
```sh
|
||
ARCHIVE_STATUS_CONFIG=services.json python3 status_bot.py --preview
|
||
```
|
||
|
||
Open `preview.html` in a browser to see an approximate Discord-style render. Paste the JSON from `--preview` into the textarea and render it to test changes before sending anything to Discord.
|
||
|
||
Run it:
|
||
|
||
```sh
|
||
python3 status_bot.py
|
||
```
|
||
|
||
Local runs automatically read `.env` from the current directory.
|
||
|
||
For dashboard testing without touching Discord, set:
|
||
|
||
```env
|
||
DISCORD_DRY_RUN=true
|
||
ARCHIVE_STATUS_STATE=state/status-message.json
|
||
```
|
||
|
||
Open the dashboard:
|
||
|
||
```text
|
||
http://127.0.0.1:8787
|
||
```
|
||
|
||
Sign in with `DASHBOARD_USERNAME` and the password you used when generating `DASHBOARD_PASSWORD_HASH`.
|
||
|
||
Set the Status and Media channel IDs from the dashboard before publishing Discord updates.
|
||
|
||
## Docker Setup
|
||
|
||
```sh
|
||
cp .env.deploy.example .env
|
||
cp services.example.json services.json
|
||
python3 status_bot.py --hash-password
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Paste the generated password hash into `.env` before starting the container.
|
||
|
||
Make sure Docker can read the service config and write runtime state:
|
||
|
||
```sh
|
||
chmod 600 .env
|
||
chmod 644 services.json
|
||
mkdir -p state
|
||
chmod 755 state
|
||
```
|
||
|
||
The container runs as `1001:1001` inside the image. If the mounted `services.json` or `state/` were created by another user, fix ownership once:
|
||
|
||
```sh
|
||
sudo chown -R 1001:1001 services.json state
|
||
```
|
||
|
||
If `state/` or `services.json` were created by a previous container as another user, fix ownership once:
|
||
|
||
```sh
|
||
sudo chown -R "$(id -u):$(id -g)" services.json state
|
||
```
|
||
|
||
The bot stores channel settings in `state/bot-settings.json` and Discord message IDs in `state/status-message.json` and `state/media-catalog.json`. Keep `state/` mounted so the bot edits the same messages after restarts.
|
||
|
||
The deploy compose joins your existing reverse-proxy network:
|
||
|
||
```yaml
|
||
networks:
|
||
mediaserver_default:
|
||
external: true
|
||
```
|
||
|
||
If that network does not already exist on the deploy host, create it once:
|
||
|
||
```sh
|
||
docker network create mediaserver_default
|
||
```
|
||
|
||
The dashboard is exposed inside Docker on port `8787` for your reverse proxy. It is not published directly to the host by default.
|
||
|
||
Use this target from your proxy:
|
||
|
||
```text
|
||
http://archive-status-bot:8787
|
||
```
|
||
|
||
For Nginx Proxy Manager, put the dashboard behind an Access List or basic auth on the proxy host and disable the app's own login:
|
||
|
||
```env
|
||
DASHBOARD_AUTH_DISABLED=true
|
||
DASHBOARD_COOKIE_SECURE=true
|
||
```
|
||
|
||
NPM’s Access Lists use browser `Authorization` headers, so the app never needs to receive your username/password directly. Leave `DASHBOARD_AUTH_DISABLED=false` only for direct localhost testing.
|
||
|
||
NPM’s own docs call out that Access List basic auth and app-side auth both use `Authorization`, so the app-side login is the one to disable in production.
|
||
|
||
If `DASHBOARD_USERNAME` and `DASHBOARD_PASSWORD_HASH` are omitted, the app now falls back to proxy-only mode automatically.
|
||
|
||
For direct local Docker testing without a proxy:
|
||
|
||
```sh
|
||
docker compose -f compose.yaml -f compose.local.yaml up -d --build
|
||
```
|
||
|
||
Then open:
|
||
|
||
```text
|
||
http://127.0.0.1:8787
|
||
```
|
||
|
||
## Dashboard
|
||
|
||
The dashboard currently supports:
|
||
|
||
- viewing monitored services
|
||
- selecting the Discord channel used by the status updater
|
||
- adding/removing service rows
|
||
- editing check URL, display URL, expected statuses, timeout, and keyword
|
||
- saving `services.json`
|
||
- forcing an immediate check and Discord message update
|
||
- uploading `Movies.csv` and `Shows.csv`
|
||
- editing imported movies and shows before publishing
|
||
- publishing a compact Markdown media catalog to a selected Discord channel
|
||
|
||
The sidebar leaves room for future modules like polls, webhooks, automations, and service integrations without changing the bot shape later.
|
||
|
||
## Media Catalog
|
||
|
||
Open the dashboard and switch to `Media`. Set the target Discord channel ID, choose `Movies.csv`, `Shows.csv`, or both, then import the CSVs into the library editor.
|
||
|
||
The editor supports adding, editing, and deleting movie/show rows before saving or publishing. Publishing sends the currently edited dashboard library, not the raw uploaded files.
|
||
|
||
Discord publishing uses one message with an attached `media-catalog.md` file so the channel does not get flooded by a long embed wall.
|
||
|
||
Channel selections are stored in:
|
||
|
||
```env
|
||
BOT_SETTINGS_STATE=state/bot-settings.json
|
||
```
|
||
|
||
The parser accepts common column names such as `title`, `name`, `year`, `genre`, `genres`, `rating`, `runtime`, `summary`, `overview`, `season`, and `episode`. Show exports that contain one row per episode are grouped by show title where possible.
|
||
|
||
The bot stores media catalog message IDs in:
|
||
|
||
```env
|
||
MEDIA_CATALOG_STATE=state/media-catalog.json
|
||
```
|
||
|
||
The editable media library is stored in:
|
||
|
||
```env
|
||
MEDIA_LIBRARY_STATE=state/media-library.json
|
||
```
|
||
|
||
Republishing deletes the previous media catalog message and posts a fresh compact Markdown attachment.
|
||
|
||
## Service Config
|
||
|
||
Each service supports:
|
||
|
||
- `name`: label shown in Discord
|
||
- `url`: URL checked by the bot
|
||
- `displayUrl`: URL linked in the embed
|
||
- `method`: optional, defaults to `GET`
|
||
- `timeoutSeconds`: optional, defaults to `10`
|
||
- `expectedStatuses`: optional list such as `["200-399"]` or `[200, 204]`
|
||
- `keyword`: optional text that must appear in the response body
|
||
|
||
Example:
|
||
|
||
```json
|
||
{
|
||
"name": "Jellyfin",
|
||
"url": "https://jellyfin.mithraic.cloud/health",
|
||
"displayUrl": "https://jellyfin.mithraic.cloud",
|
||
"expectedStatuses": ["200-399"]
|
||
}
|
||
```
|