feat: add CI workflow, Docker deployment, and comprehensive documentation
This commit is contained in:
parent
f08ec5c02c
commit
da20cb537b
7 changed files with 239 additions and 10 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
target/
|
||||||
|
.git/
|
||||||
|
state*.json
|
||||||
|
.env
|
||||||
|
*.tmp
|
||||||
14
.env.example
Normal file
14
.env.example
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Discord Bot Token (Required)
|
||||||
|
DISCORD_TOKEN=your_discord_bot_token_here
|
||||||
|
|
||||||
|
# Catalog poll interval in seconds (60-86400, default: 600)
|
||||||
|
POLL_SECONDS=600
|
||||||
|
|
||||||
|
# Path to state persistence JSON (default: state.json, or /data/state.json in Docker)
|
||||||
|
STATE_PATH=state.json
|
||||||
|
|
||||||
|
# Optional HTTPS donation/support link shown in /about
|
||||||
|
SUPPORT_URL=https://ko-fi.com/yourname
|
||||||
|
|
||||||
|
# Optional Discord Guild ID for instantaneous slash command registration during development
|
||||||
|
DISCORD_GUILD_ID=
|
||||||
35
.github/workflows/ci.yml
vendored
Normal file
35
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test & Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: clippy, rustfmt
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
|
- name: Check code formatting
|
||||||
|
run: cargo fmt --check
|
||||||
|
|
||||||
|
- name: Clippy linting
|
||||||
|
run: cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
|
- name: Run unit and integration tests
|
||||||
|
run: cargo test --locked
|
||||||
|
|
||||||
|
- name: Run catalog check dry-run
|
||||||
|
run: cargo run --locked -- --check
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
/target
|
/target
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
!.env.example
|
||||||
/state.json
|
/state.json
|
||||||
/*.tmp
|
/*.tmp
|
||||||
|
|
||||||
|
|
|
||||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Build stage
|
||||||
|
FROM rust:1-bookworm AS builder
|
||||||
|
|
||||||
|
WORKDIR /usr/src/model-radar
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
RUN cargo build --release --locked && \
|
||||||
|
strip target/release/model-radar
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM debian:bookworm-slim AS runtime
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends ca-certificates && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
useradd -u 10001 -m -d /home/modelradar -s /usr/sbin/nologin modelradar && \
|
||||||
|
mkdir -p /data && \
|
||||||
|
chown -R modelradar:modelradar /data
|
||||||
|
|
||||||
|
COPY --from=builder /usr/src/model-radar/target/release/model-radar /usr/local/bin/model-radar
|
||||||
|
|
||||||
|
USER modelradar
|
||||||
|
WORKDIR /data
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENV STATE_PATH=/data/state.json
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/model-radar"]
|
||||||
151
README.md
151
README.md
|
|
@ -1,16 +1,147 @@
|
||||||
# model-radar
|
# Model Radar 📡🤖
|
||||||
|
|
||||||
> **Status:** Work in Progress (WIP)
|
[](https://github.com/oiupoyt/model-radar/actions)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
A Discord bot that monitors [OpenRouter](https://openrouter.ai) for free LLM models and alerts/pings users when new free models become available.
|
A lightweight, crash-resilient Discord bot built in Rust with [poise](https://github.com/serenity-rs/poise) and [serenity](https://github.com/serenity-rs/serenity). Model Radar monitors [OpenRouter](https://openrouter.ai)'s live model catalog for free LLMs (`:free` and exact $0 pricing) and dispatches configurable release alerts and role pings directly to your Discord server.
|
||||||
|
|
||||||
## Features (WIP)
|
---
|
||||||
|
|
||||||
- Polls OpenRouter model catalog API for free tiers (`:free`)
|
## ✨ Features
|
||||||
- Tracks pricing and availability diffs
|
|
||||||
- Discord slash commands via poise & serenity
|
|
||||||
- Automated role/user pings on model drops
|
|
||||||
|
|
||||||
## License
|
- **Automated OpenRouter Monitoring:** Periodically polls the public OpenRouter models API (`/api/v1/models`) with configurable intervals.
|
||||||
|
- **Accurate Zero-Price Detection:** String-based decimal parsing identifies genuine free-tier models (including scientific notation like `0e-9999`) while rejecting precision artifacts, non-zero pricing, and tiered overrides.
|
||||||
|
- **Flexible Subscription Filters:**
|
||||||
|
- `free_only`: Target free models or watch all new catalog entries.
|
||||||
|
- `search`: Filter releases by model identifier or name (e.g., `qwen`, `llama`, `coder`).
|
||||||
|
- `min_context`: Ignore models with context windows below a given token threshold (e.g. `32768`).
|
||||||
|
- `compact`: Dense notification rows for high-volume channels.
|
||||||
|
- **Controlled Opt-In Role Pings:** Mentionable role pings configured per server. Explicitly disallows `@everyone` and `@here` mentions to protect against ping abuse.
|
||||||
|
- **Crash-Resilient State:** Subscriptions and catalog states are stored atomically (`.tmp` write followed by atomic filesystem rename) to guarantee state integrity across crashes and container reboots.
|
||||||
|
- **Queue Bounds:** Unreachable alert channels (e.g., deleted channel or revoked bot permissions) are capped at 50 queued events, preventing unbounded state growth.
|
||||||
|
- **Discord API Safety:** Strict UTF-16 character limit enforcement and markdown character escaping prevent broken embeds or Discord 400 Bad Request rejections.
|
||||||
|
- **Dry-Run Diagnostic Mode:** Test public catalog availability and parsing without needing a Discord token (`--check`).
|
||||||
|
|
||||||
MIT
|
---
|
||||||
|
|
||||||
|
## 📋 Slash Commands
|
||||||
|
|
||||||
|
All interactions use Discord Slash Commands:
|
||||||
|
|
||||||
|
| Command | Scope | Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `/models [search] [page]` | Public / Ephemeral | Browse all currently available free models in OpenRouter's catalog with pagination and optional search filter. |
|
||||||
|
| `/status [free_only] [search] [min_context] [page] [page_size] [compact] [public]` | Configurable | View a catalog freshness snapshot with model counts, active search queries, and token constraints. |
|
||||||
|
| `/model <id> [compact] [show_pricing] [public]` | Configurable | Inspect detailed specifications for an exact model ID (context window length, full description, prompt/completion token pricing). |
|
||||||
|
| `/watch [role] [free_only] [search] [min_context] [ping_enabled] [compact] [clear_role]` | Manage Server | Set up or update automatic catalog release alerts in the current channel. Omitted options retain existing settings. |
|
||||||
|
| `/unwatch` | Manage Server | Remove the server's subscription and delete all queued pending alerts. |
|
||||||
|
| `/stats` | Ephemeral | View catalog freshness, last successful fetch timestamp, polling interval, and complete server subscription settings. |
|
||||||
|
| `/testping` | Manage Server | Dispatch a labeled test notification to verify channel permissions and role mentionability without modifying catalog state. |
|
||||||
|
| `/about` | Ephemeral | Display bot information, command guide, poll interval, and optional operator support link. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
### 1. Create a Discord Bot Application
|
||||||
|
|
||||||
|
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and create a **New Application**.
|
||||||
|
2. Navigate to the **Bot** tab:
|
||||||
|
- Click **Reset Token** to generate a bot token. Copy this token for your `DISCORD_TOKEN` environment variable.
|
||||||
|
- *Note:* **No Privileged Gateway Intents** are required (Model Radar does not require Message Content, Server Members, or Presence intents).
|
||||||
|
3. Navigate to **OAuth2 → URL Generator**:
|
||||||
|
- **Scopes:** Select `bot` and `applications.commands`.
|
||||||
|
- **Bot Permissions:** Select:
|
||||||
|
- `View Channels`
|
||||||
|
- `Send Messages`
|
||||||
|
- `Embed Links`
|
||||||
|
4. Open the generated invite URL in your browser to invite the bot to your server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Configuration & Environment Variables
|
||||||
|
|
||||||
|
| Variable | Required | Default | Description |
|
||||||
|
| :--- | :---: | :---: | :--- |
|
||||||
|
| `DISCORD_TOKEN` | **Yes** | — | Discord Bot Token from the Developer Portal. |
|
||||||
|
| `POLL_SECONDS` | No | `600` | Catalog polling interval in seconds (allowed: `60` to `86400`). |
|
||||||
|
| `STATE_PATH` | No | `state.json` | Path where subscription settings and catalog state are stored. |
|
||||||
|
| `SUPPORT_URL` | No | — | Optional HTTPS URL (e.g. Ko-fi, GitHub Sponsors) shown in `/about`. |
|
||||||
|
| `DISCORD_GUILD_ID` | No | — | Guild ID for instant slash command registration during testing (global registration takes up to an hour). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Deployment Options
|
||||||
|
|
||||||
|
#### Option A: Docker Compose (Recommended)
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/oiupoyt/model-radar.git
|
||||||
|
cd model-radar
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a `.env` file from the example:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
Edit `.env` and fill in your `DISCORD_TOKEN`.
|
||||||
|
|
||||||
|
3. Launch the container:
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
State will be persisted in the `./data` directory.
|
||||||
|
|
||||||
|
#### Option B: Native Cargo Run
|
||||||
|
|
||||||
|
Prerequisites: [Rust 1.85+ (Edition 2024)](https://rustup.rs/)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify catalog connection and parsing (no token required)
|
||||||
|
cargo run --locked -- --check
|
||||||
|
|
||||||
|
# Run the bot
|
||||||
|
export DISCORD_TOKEN="your_bot_token"
|
||||||
|
export POLL_SECONDS=600
|
||||||
|
cargo run --release --locked
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Setting Up Alerts in Discord
|
||||||
|
|
||||||
|
1. In your Discord server, create an opt-in role for notifications (e.g. `@ModelAlerts`).
|
||||||
|
2. Ensure the role is **mentionable** in Server Settings → Roles.
|
||||||
|
3. In the channel where alerts should be posted, run:
|
||||||
|
```
|
||||||
|
/watch role:@ModelAlerts ping_enabled:true
|
||||||
|
```
|
||||||
|
4. Verify the setup by running:
|
||||||
|
```
|
||||||
|
/testping
|
||||||
|
```
|
||||||
|
5. Check your active server configuration at any time with `/stats`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Development & Testing
|
||||||
|
|
||||||
|
Model Radar includes a comprehensive test suite covering catalog parsing, pricing edge cases, store migrations, and Discord embed safety:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run unit and integration tests
|
||||||
|
cargo test --locked
|
||||||
|
|
||||||
|
# Run linter
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
|
# Check code formatting
|
||||||
|
cargo fmt --check
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT License](LICENSE).
|
||||||
|
|
|
||||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
services:
|
||||||
|
model-radar:
|
||||||
|
build: .
|
||||||
|
container_name: model-radar
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- DISCORD_TOKEN=${DISCORD_TOKEN}
|
||||||
|
- POLL_SECONDS=${POLL_SECONDS:-600}
|
||||||
|
- STATE_PATH=/data/state.json
|
||||||
|
- SUPPORT_URL=${SUPPORT_URL:-}
|
||||||
|
- DISCORD_GUILD_ID=${DISCORD_GUILD_ID:-}
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
Loading…
Add table
Reference in a new issue