From da20cb537b5c11699eb3c673db6154a359d94f10 Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Sun, 27 Sep 2026 15:24:22 +0530 Subject: [PATCH] feat: add CI workflow, Docker deployment, and comprehensive documentation --- .dockerignore | 5 ++ .env.example | 14 ++++ .github/workflows/ci.yml | 35 +++++++++ .gitignore | 2 + Dockerfile | 29 ++++++++ README.md | 151 ++++++++++++++++++++++++++++++++++++--- docker-compose.yml | 13 ++++ 7 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..18666c1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +target/ +.git/ +state*.json +.env +*.tmp diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..593b225 --- /dev/null +++ b/.env.example @@ -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= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..efc590a --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.gitignore b/.gitignore index ab3da08..566293b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /target .env .env.* +!.env.example /state.json /*.tmp + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2b5071b --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md index 8e76ed0..3fd5673 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,147 @@ -# model-radar +# Model Radar ๐Ÿ“ก๐Ÿค– -> **Status:** Work in Progress (WIP) +[![CI](https://github.com/oiupoyt/model-radar/actions/workflows/ci.yml/badge.svg)](https://github.com/oiupoyt/model-radar/actions) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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`) -- Tracks pricing and availability diffs -- Discord slash commands via poise & serenity -- Automated role/user pings on model drops +## โœจ Features -## 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 [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). diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a8c5cc6 --- /dev/null +++ b/docker-compose.yml @@ -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