# RepoRadar 🛰 A standalone Discord bot that **scrapes a git server for the newest projects** and drops them into Discord — with descriptions, community voting, and admin-curated highlights. RepoRadar continually monitors a self-hosted or hosted git server (Gitea / Forgejo, GitLab, or GitHub), and as soon as a *new* project appears it posts a rich card linking the repo with its description, language, stars/forks, topics and a live community vote tally. Existing repositories are **baselined silently** the moment a watch is added, so you only ever see brand-new drops — in chronological order, freshest last. > RepoRadar is a **separate bot** from VulnForge/THREATLENS. It runs on its own > token, with its own database and Docker stack. --- ## Features - **Continual monitoring** of a git server — scoped to specific users/orgs *or* server-wide. - **Newest-only drops** — existing repos are baselined on first sight; only new projects are announced afterwards. - **Chronological order** — new projects post oldest → newest so the freshest drop is always the most recent message. - **Rich drop cards** — link, description, owner, language, stars, forks, topics and a live vote tally. - **Community voting, three ways** - 👍 / 👎 buttons on each drop - 💬 *Vote + Comment* modal - `/radar_vote owner/repo up|down` slash command - **Highlights** — projects matching configured keywords or a star threshold are re-posted to a highlight channel with an optional role ping. - **Admin modal editing** — admins (Manage Server) can edit a drop's description and curator note in place, and configure everything via modals. --- ## Slash commands | Command | Who | What it does | | ----------------- | ----------- | ------------------------------------------------------- | | `/radar_config` | Admin | Configure drop/highlight channels, role, keywords, stars (modal) | | `/radar_watch` | Admin | Watch a git user/org or the whole server (modal) | | `/radar_unwatch` | Admin | Remove a watch | | `/radar_status` | Everyone | Show current monitor configuration and active watches | | `/radar_latest` | Everyone | Show the newest projects on demand | | `/radar_scan` | Admin | Force a scan cycle right now | | `/radar_vote` | Everyone | Vote on a dropped project by `owner/repo` | | `/radar_top` | Everyone | Community leaderboard of top-voted projects | "Admin" = members with the **Manage Server** permission. --- ## Quick start ### 1. Create the Discord application 1. Go to → **New Application**. 2. **Bot** tab → **Add Bot** → copy the **token**. 3. **OAuth2 → URL Generator**: scopes `bot` + `applications.commands`; bot permissions: *Send Messages*, *Embed Links*, *Read Message History*. Invite the bot with the generated URL. ### 2. Configure ```bash cp .env.example .env # edit .env — set at minimum DISCORD_TOKEN, GIT_PROVIDER / GIT_BASE_URL, and # (optionally) GUILD_ID for instant command sync. ``` ### 3. Run with Docker (recommended) ```bash docker compose up -d --build docker compose logs -f reporadar ``` ### 4. Run locally (without Docker) ```bash python -m venv .venv . .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r requirements.txt python main.py ``` ### 5. Configure in Discord 1. `/radar_config` — set the drop channel (and optionally a highlight channel, ping role, star threshold, and keywords). 2. `/radar_watch` — add a watch (`user`, `org`, or `server`). Existing repos are baselined silently. 3. `/radar_scan` — force a scan to verify, or just wait for the poll loop. --- ## Configuration reference All values can be set in `.env`; channels/roles/keywords are also settable in Discord via `/radar_config` (per-guild settings take precedence). | Env var | Default | Description | | -------------------------- | ---------------- | ------------------------------------------------------- | | `DISCORD_TOKEN` | — | Bot token (required) | | `GUILD_ID` | — | Restrict command sync to one guild (instant updates) | | `GIT_PROVIDER` | `auto` | `auto` \| `gitea` \| `gitlab` \| `github` | | `GIT_BASE_URL` | — | Git server URL (blank for GitHub) | | `GIT_TOKEN` | — | Read-only PAT (optional for public servers) | | `GIT_DROP_CHANNEL_ID` | — | Default drop channel | | `GIT_HIGHLIGHT_CHANNEL_ID` | — | Featured-drop channel | | `GIT_HIGHLIGHT_ROLE_ID` | — | Role pinged on highlights | | `GIT_POLL_INTERVAL` | `300` | Poll interval in seconds (min 60) | | `GIT_MAX_DROPS_PER_CYCLE` | `10` | Burst guard — larger batches are baselined silently | | `LOG_LEVEL` | `INFO` | `DEBUG` \| `INFO` \| `WARNING` \| `ERROR` | --- ## Supported git providers | Provider | `GIT_PROVIDER` | Notes | | ----------------- | -------------- | ------------------------------------------------------ | | Gitea / Forgejo | `gitea` | Typical self-hosted git server (REST API v1) | | GitLab | `gitlab` | self-hosted or gitlab.com (REST API v4) | | GitHub | `github` | Leave `GIT_BASE_URL` blank (uses api.github.com) | | Auto-detect | `auto` | Probes `/api/v1/version` then `/api/v4/version` | --- ## Under the hood 1. **`utils/git_api.py`** — a provider-agnostic async client (single shared `aiohttp` session, retry/backoff) that normalises every provider's payload into a simple `Repo` dataclass. `created_at` drives the "newest-only" logic. 2. **`utils/helpers.py`** — an async SQLite `Store` (config, watches, seen-repo de-dup, drop anchors, community votes) + small formatting helpers. 3. **`utils/embeds.py`** — all the cyberpunk-styled embed builders. 4. **`cogs/gitscan.py`** — the monitor loop, drop logic, voting UI and slash commands. 5. **`main.py`** — Nextcord 2.6 boot (bot built inside the loop, manual extension load + command rollout, graceful shutdown). State persists in `data/reporadar.db` (mounted as a Docker volume), so seen repos and votes survive restarts. --- ## Project structure ``` reporadar/ ├── main.py # entrypoint / bot bootstrap ├── config.py # env-driven settings + colour palette ├── requirements.txt ├── Dockerfile ├── docker-compose.yml ├── .env.example ├── .dockerignore ├── cogs/ │ └── gitscan.py # monitor + voting + slash commands └── utils/ ├── git_api.py # provider-agnostic git client ├── helpers.py # async SQLite store + formatting └── embeds.py # embed builders ```