# Voter-Uptime-Bot A Discord bot that monitors the availability of web services and reports uptime statistics directly in your server. Built with `discord.py`, `aiohttp`, and `pydantic-settings`, it goes beyond simple HTTP checks — detecting Cloudflare intercept pages and validating real page content so you know your sites are genuinely reachable, not just returning a 200. --- ## Features - **Periodic polling** — checks all configured sites every 15 minutes - **Cloudflare intercept detection** — identifies challenge/block pages that return HTTP 200 but serve no real content - **Keyword content validation** — confirms expected strings are present on the loaded page - **Retry logic** — re-attempts checks on transient CF intercepts before marking a site degraded - **Uptime history** — stores all check results in a local SQLite database - **Discord slash commands** — view live status, 24-hour bars, and monthly summaries per site --- ## Commands | Command | Description | |---|---| | `/uptime now` | Current status of all monitored sites | | `/uptime day ` | 24-hour bar chart (🟩🟨🟥) with uptime % | | `/uptime month ` | Current month summary, one square per day | | `/uptime summarize` | Monthly uptime % for all sites at once | --- ## Project Structure ``` app/ ├── __init__.py ├── bot.py # Discord client, slash commands, polling task ├── checker.py # SiteChecker — HTTP, CF detection, keyword validation ├── config.py # Pydantic Settings — loads all config from .env ├── db.py # SQLite init, insert, and fetch helpers └── utils.py # check_site wrapper, bar rendering, uptime maths ``` --- ## Configuration All configuration is managed via a `.env` file using `pydantic-settings`. Copy `.env.example` to `.env` and fill in your values. ```env DISCORD_SECRET_KEY=your-bot-token-here DISCORD_CLIENT_ID=123456789 DATABASE_PATH=uptime.db # How often to poll all sites, in minutes (default: 15) POLL_INTERVAL_MINUTES=15 # Discord channel ID to post alerts in. Set to 0 to disable alerts entirely. ALERT_CHANNEL_ID=1493840872146600036 # Minimum minutes between repeat incident alerts for the same site. # Recoveries always bypass this cooldown. ALERT_COOLDOWN_MINUTES=30 MONITORED_SITES='[ { "name": "MySite", "url": "https://example.com", "timeout_seconds": 10, "expected_keywords": ["Login", "Vote"], "max_retries": 1 } ]' ``` ### Site config fields | Field | Required | Default | Description | |---|---|---|---| | `name` | ✅ | — | Display name used in Discord | | `url` | ✅ | — | Full URL to check | | `timeout_seconds` | | `10` | Request timeout | | `expected_status` | | `200` | Expected HTTP status code | | `expected_keywords` | | `[]` | Strings that must appear in the page body | | `max_retries` | | `1` | Extra attempts on CF intercept before marking degraded | --- ## How Checks Work Each poll goes through three layers: 1. **HTTP status** — non-2xx/3xx responses are marked degraded or down 2. **Cloudflare detection** — response body is scanned for CF challenge fingerprints; a match is marked `degraded` even if status was 200 3. **Keyword validation** — any `expected_keywords` that are missing from the body mark the site `degraded` Results are stored with a `detection_reason` (`cf_intercept`, `missing_keywords`, `http_503`, `timeout`, etc.) so you can query historical failure causes. --- ## Result States | State | Emoji | Meaning | |---|---|---| | `up` | 🟩 | HTTP OK, no CF intercept, all keywords present, latency < 3s | | `degraded` | 🟨 | Reachable but CF intercept, missing content, slow, or 5xx | | `down` | 🟥 | Timeout, connection failure, SSL error, or unexpected status | --- ## Installation ```bash pip install -r requirements.txt ``` Run the bot: ```bash python -m app.bot ``` --- ## Requirements - Python 3.11+ - A Discord bot token with the `applications.commands` scope and `bot` scope enabled - The bot must be invited to your server with slash command permissions