AI-powered Discord bot for the Miners' Refuge community. Helps Minecraft server admins troubleshoot logs, diagnose performance with Spark, search documentation, and find plugins — all from Discord.
Built with discord.py + RAG (Retrieval-Augmented Generation) over multiple documentation sources.
- Features
- Slash Commands
- Passive Features
- AI & RAG System
- Documentation Sources
- Project Structure
- Installation
- Configuration
- Running the Bot
- Docker
| Category | Capability |
|---|---|
| Q&A (RAG) | Answers questions grounded in indexed docs (Miners' Refuge, PaperMC, PurpurMC, Spark) |
| General Chat | Open-ended assistant with web search, server/history context awareness |
| Log Analysis | AI analysis of mclo.gs/pastebin links, file uploads, and screenshots |
| Error Detection | Instant passive replies for 20+ known Minecraft error patterns |
| Spark Profiler | Full report parsing, bottleneck diagnosis, platform-aware recommendations |
| Plugin Search | Concurrent search across Modrinth, Hangar, SpigotMC |
| Server Tools | JVM flags, server status checks, docs changelog |
All AI responses are in Brazilian Portuguese and formatted for Discord embeds.
Ask any Minecraft server administration question. Uses an agentic RAG loop — the LLM automatically calls search_docs, search_plugins, search_history, and context tools to ground its answer in real documentation.
- Model:
CHAT_MODEL(default:qwen/qwen3.6-plus) - Tools available:
search_docs,search_plugins,get_channel_history,get_guild_info,search_history,get_message_context - Features: Image/screenshot analysis, 5-minute ephemeral prompt+report cache, paginated multi-embed output with source links, reply to continue conversation (30 min TTL, 200 conversations), cooldown per user
- Follow-up: Reply to the bot's response to continue the conversation with full history
General-purpose assistant (same agentic loop as /ask but with web search).
- Tools:
web_search,web_extract(via Tavily), plus the same context/history tools as/ask - Web search: Supports
search_depth(basic/advanced),time_range, domain filtering - Reply follow-up and @mention mode: Mention the bot (
@QuillBot <question>) to chat without a slash command — same rate-limit and conversation handling as/chat - Set
CHAT_MENTION_ENABLED=falseto disable mention mode
AI-powered log/crash-report analysis.
- Inputs (at least one required):
log_link— mclo.gs or pastebin.com URL (parsed to raw API endpoint)log_file—.logor.txtattachment (auto-uploaded to mclo.gs for sharing, streaming read up to 5 MB)image— screenshot of an error/log (vision model)
- Processing: Sanitizes non-printable chars, extracts relevant lines for large logs (keeps 60 header lines + error/warning/exception lines, truncates to 12k chars), sends to LLM with a structured diagnostic prompt
- Output: Sections — Resumo, Erros Encontrados, Avisos, Recomendações (only non-empty sections shown)
- Button fallback: When a pasted log has no regex pattern match, a "🔍 Analisar com IA" button is offered to trigger the same analysis
Analyze a Spark Profiler report with deep AI diagnosis.
- Input: Full
https://spark.lucko.me/<code>URL or bare report code - Parser (
spark_parser.py): Fetches fromspark-json-service.lucko.me, extracts server identity, JVM flags, TPS/MSPT, GC, CPU hotspots (chain-collapsed call tree withself_pct), world/entity stats, configs, game rules, plugins - LLM flow: Injects a compact summary (~1.7k chars) as synthetic prior exchange, follows an 8-step diagnostic protocol (TPS → MSPT → lag spike detection → waitForNextTick → GC → hotspots → config → grounded recommendation), uses
SPARK_MODEL(default:google/gemini-2.5-pro) - Spark tools:
get_spark_detail(sections:hotspots,jvm,profiler,plugins,world,game_rules,configs:<file>) andget_config_key(single-key lookup) - Platform awareness: Detects Paper/Purpur/Spigot/Forge/Fabric/Vanilla and never recommends configs that don't exist for that platform
- Passive detection: Any
spark.lucko.melink in chat triggers a "🔥 Analisar com IA" button
Re-index documentation vectors.
- Without
source: full reindex of allDOC_SOURCES - With
source: reindex a single source by label (e.g.PaperMC) - Shows
⏳ Indexando...guard — blocks/ask//docsqueries during indexing - Per-source commit SHA tracking for granular updates
| Command | Description | Input |
|---|---|---|
/docs [query] |
Search indexed docs (RAG) or show docs link. Supports all sources + source label prefix. | query (optional) |
/plugin <name> |
Search Modrinth, Hangar, SpigotMC concurrently. Paginated view (one source per page). Autocomplete via Modrinth. | name (required) |
/status <ip> |
Check Minecraft server status via mcsrvstat.us. Shows MOTD, player count, version. |
ip (hostname or IP, optional port) |
/changelog |
Last 5 commits to MinersRefuge/docs (SHA, message, author, date) via GitHub API. |
— |
/flags <ram> |
Generate Aikar's JVM flags for a given RAM amount in MB. Auto-tunes G1GC thresholds at >= 12 GB. Validates 512–65536 MB range. | ram (MB, integer) |
/plov |
Embed: PLOV info needed to choose hosting — Plano/Players, Localização, Orçamento, Versão. | — |
/plano |
Embed: info needed to recommend a plan — Version, Players, Mods/Plugins, Gamemode. | — |
/health |
(Admin) Bot diagnostics: uptime, concurrent API pings (OpenRouter, Tavily, GitHub, mclo.gs, Modrinth) with latency, vector store stats, web search status, conversation cache usage, WebSocket latency. | — |
/sync |
(Admin) Re-sync slash commands with Discord. | — |
/help |
Auto-generated list of all registered slash commands + passive feature summary. | — |
The bot monitors every non-bot message via on_message listeners (load order: log_analyzer → history_rag → commands → spark → docs_rag).
- Triggers on:
https://mclo.gs/<id>andhttps://pastebin.com/<id> - Behavior: Reacts with 👀, fetches raw content (streaming, 5 MB cap), checks against known error patterns
- If matched: Replies instantly with the pattern's troubleshooting response
- If no match + AI available: Replies with "🤔 Não reconheci nenhum erro" + "🔍 Analisar com IA" button
- Triggers on:
.log/.txtattachments - Behavior: Reads file (streaming), checks error patterns, uploads to
mclo.gs(POST /1/log), replies with the mclo.gs link (or a fallback message). If large (> 5 MB), notes truncation. Offers AI analysis button when no pattern matches.
25 pre-compiled regex patterns — first match wins (specific before generic):
| Category | Examples |
|---|---|
| Plugin | Ambiguous plugin name, missing dependency, Error occurred while enabling, Could not pass event |
| Startup / JAR | Unable to access jarfile, Current Java is X but we require Y, Unsupported Java, JNI error |
| EULA | You need to agree to the EULA |
| Memory | OutOfMemoryError, Can't keep up! Is the server overloaded? |
| Port / Network | FAILED TO BIND TO PORT, Perhaps a server is already running on that port |
| World / Data | Failed to load chunk, Region file is truncated, Session lock is no longer valid |
| Permissions | <player> was denied the command |
| Version | Outdated server, Outdated client |
| Crash | This crash report has been saved to, ---- Minecraft Crash Report ---- |
| Generic | The received string length is longer than maximum allowed, Connection throttled |
Regex https://spark.lucko.me/<code> → replies "🔥 Relatório Spark detectado!" with analysis button.
Reply to any /ask, /chat, or /spark bot response to continue the thread. The bot replays up to 16 history turns (for /ask//spark) with the Spark report carried forward. Per-user cooldown enforced. Supports image attachments in follow-ups.
Mention the bot with a question (@QuillBot como otimizar meu servidor?) — equivalent to /chat with the same tool loop and conversation storage.
Background RAG over the entire Discord server's message history:
- Backfill on startup: reads all accessible text channels/threads (oldest-first, configurable limit)
- Live ingestion: Every new message is chunked with a 5-message sliding window for local context, embedded, and persisted per-guild (
data/history/<guild_id>.json + .npy) - Dedup + deletion: Tracks
msg_id → index, handleson_message_delete - Search tool:
search_history/search_docsequivalent available to the LLM in all agentic loops
- Provider: Switchable via
EMBEDDING_PROVIDER—openai(remote, default) orlocal(sentence-transformers, e.g.all-MiniLM-L6-v2) - Models:
EMBEDDING_MODEL(defaultqwen/qwen3-embedding-8b),RERANK_MODEL(defaultcohere/rerank-4-fast) - Reranking: Enabled when
RERANK_ENABLED=trueandOPENAI_BASE_URLis OpenRouter — reranks top3×top_kcandidates via the OpenRouter/rerankendpoint, falls back to cosine similarity - Storage:
data/vectors.json(metadata) +data/vectors.npy(float32 binary embeddings) — migrated automatically from old JSON-embedded format - Chunking: Markdown split by headings (
##/#), then by1500-char paragraph windows; title extracted from frontmatter or first#
Shared loop used by /ask, /chat, and Spark analysis:
while tool_calls and rounds < MAX_TOOL_ROUNDS:
→ display status label on deferred interaction
→ execute each tool (deduplicate sources via dedup_key)
→ append tool results (truncated to 6000 chars)
→ final answer (max_tokens=2048)
→ paginated embeds + source pages
- Parallel tool calls per round when the LLM requests multiple
COOLDOWN_RATE / COOLDOWN_PER (default 1 per 30s) per user on /ask, /chat, /analyze. Follow-up replies share a TTLCache cooldown. Exceeding returns an ephemeral ⏳ Aguarde Xs message.
Every AI call receives a <contexto> block with user (display name, account age, join date, roles), guild (name, member count, channels, roles), channel, and temporal (BRT + UTC) context.
Configured in config.py:DOC_SOURCES. Each entry:
| Key | Required | Description |
|---|---|---|
repo |
Yes | GitHub owner/name |
branch |
Yes | Branch to index |
base_url |
Yes | Docs website base URL |
label |
Yes | Human-readable label shown in results |
summary |
No | Path to mdBook SUMMARY.md for discovery |
path_prefix |
No | Only index files under this prefix |
url_strip_prefix |
No | Strip prefix when building website URLs |
max_files |
No | Cap per source (default 200) |
| Source | Label | Discovery | Base URL |
|---|---|---|---|
MinersRefuge/docs |
Miners' Refuge | SUMMARY.md |
https://docs.minersrefuge.com.br |
PaperMC/docs |
PaperMC | Tree API (src/content/docs/paper/admin/) |
https://docs.papermc.io |
PurpurMC/PurpurDocs |
PurpurMC | Tree API (mkdocs/purpur/) |
https://purpurmc.org/docs/purpur/ |
lucko/spark-docs |
Spark | Tree API (docs/) |
https://spark.lucko.me/docs/ |
Periodic reindex every REINDEX_INTERVAL_HOURS (default 6h) via composite MD5 of all latest commit SHAs.
QuillBot/
├── main.py # Entrypoint — validates config, loads cogs in order, syncs slash commands
├── config.py # Centralized env-var config, DOC_SOURCES, feature flags
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── cogs/
│ ├── commands.py # /chat, /docs, /help, /flags, /plov, /plano, /health, /sync + @mention & reply follow-ups
│ ├── docs_rag.py # RAG pipeline — indexing, search, reranking, /ask, /reindex, agentic loop + Spark diagnosis
│ ├── history_rag.py # Server-wide history RAG — backfill, live ingestion, per-guild vector stores
│ ├── log_analyzer.py # Passive log detection, pattern matching, /analyze, file upload to mclo.gs
│ ├── plugins.py # /plugin, /status, /changelog — plugin search & server status
│ ├── spark.py # /spark command + passive spark.lucko.me detection
│ ├── spark_parser.py # Spark JSON parsing, summary/detail builders, call-tree rendering
│ ├── plugin_apis.py # Shared Modrinth/Hangar/SpigotMC API helpers
│ ├── tavily_tools.py # Tavily web_search / web_extract tool definitions & execution
│ └── utils.py # Shared: truncate_safe, split_response, PaginatedEmbedView, context builders, run_tool_loop
├── responses/
│ └── errors.py # 25 compiled Minecraft error regex patterns + pt-BR responses
└── data/
├── vectors.json/.npy # Docs RAG vector store
└── history/ # Per-guild history stores
Requires Python 3.11+
git clone https://github.com/Zeptiny/QuillBot.git
cd QuillBot
pip install -r requirements.txt- Create a bot at https://discord.com/developers/applications
- Under Bot → Privileged Gateway Intents enable Message Content Intent
- Invite with scopes
bot+applications.commandsand permissions: Send Messages, Embed Links, Read Message History, Add Reactions, Use Application Commands
Copy and fill in your environment file:
cp .env.example .env # if available, otherwise create .env manually| Variable | Description |
|---|---|
BOT_TOKEN |
Discord bot token — bot will not start without it |
OPENROUTER_API_KEY |
API key for OpenRouter (or any OpenAI-compatible provider). Required for all AI commands. Also aliased as OPENAI_API_KEY / LLM_API_KEY |
| Variable | Default | Description |
|---|---|---|
OPENAI_BASE_URL |
https://openrouter.ai/api/v1 |
OpenAI-compatible endpoint. Aliases: LLM_BASE_URL, OPENROUTER_BASE_URL |
CHAT_MODEL |
qwen/qwen3.6-plus |
Used for /ask, /chat, /analyze |
SPARK_MODEL |
google/gemini-2.5-pro |
Used only during active Spark sessions |
EMBEDDING_MODEL |
qwen/qwen3-embedding-8b |
Doc/history embedding model |
RERANK_MODEL |
cohere/rerank-4-fast |
Reranking model |
EMBEDDING_PROVIDER |
openai |
openai (remote) or local (sentence-transformers) |
LOCAL_EMBEDDING_MODEL |
sentence-transformers/all-MiniLM-L6-v2 |
Model when EMBEDDING_PROVIDER=local |
LOCAL_EMBEDDING_DEVICE |
cpu |
Device for local embeddings |
RERANK_ENABLED |
true |
Enable reranking (only when base URL is OpenRouter) |
| Variable | Default | Description |
|---|---|---|
WEB_SEARCH_ENABLED |
true |
Enable Tavily web search |
TAVILY_API_KEY |
— | Required when web search is enabled |
CHAT_MENTION_ENABLED |
true |
Enable @mention chat mode |
HISTORY_ENABLED |
true |
Enable server history RAG |
LOG_LEVEL |
INFO |
Python logging level |
| Variable | Default | Description |
|---|---|---|
HISTORY_VECTOR_STORE_DIR |
data/history |
Per-guild history storage dir |
HISTORY_WINDOW_SIZE |
5 |
Sliding window of prior messages per chunk |
HISTORY_BACKFILL_LIMIT |
(none) | Max messages to backfill per channel (unset = all) |
HISTORY_MAX_MSG_LENGTH |
800 |
Max chars per message in history chunks |
HISTORY_EXCLUDE_BOTS |
true |
Exclude bot messages from history |
| Variable | Default | Description |
|---|---|---|
VECTOR_STORE_PATH |
data/vectors.json |
Docs vector store path |
REINDEX_INTERVAL_HOURS |
6 |
Hours between automatic reindex checks |
| Variable | Default | Description |
|---|---|---|
COOLDOWN_RATE |
1 |
Allowed uses per cooldown window |
COOLDOWN_PER |
30 |
Cooldown window in seconds (per user) |
MAX_CONTENT_SIZE |
5242880 |
Max bytes fetched from paste services (5 MB) |
MAX_LOG_CONTEXT |
12000 |
Max chars sent to LLM for log analysis |
python main.pyOn startup the bot:
- Validates
BOT_TOKEN(exits if missing; warns ifTAVILY_API_KEYmissing while web search is enabled) - Loads cogs in order:
log_analyzer→history_rag→commands→plugins→spark→docs_rag - Loads cached vectors from disk or indexes all doc sources
- Starts periodic reindex loop + background history backfill
- Syncs slash commands with Discord
Cog load order matters —
log_analyzer'son_message(pattern matching) runs beforedocs_rag's (follow-up replies). Do not reorder without reviewing listener interactions.
docker compose up --build -d- Uses
python:3.11-slim, persistsdata/via volume mount, reads config from.env.
See repository license. Documentation sources retain their original licenses.