A Rust-based Discord Quest Notifier that automatically fetches available quests from Discord's API and sends notifications via Discord Webhooks.
Warning
This app uses your Discord user token, which technically violates Discord's Terms of Service. Use at your own risk.
- Your account could be suspended or banned
- Keep your token private and secure
- Do not share your token with anyone
- Use responsibly with reasonable check intervals
- Authentication: Uses your Discord user token to access the Quest API
- Quest Fetching: Polls Discord's
/api/v10/quests/@meendpoint at configured intervals - Storage: Tracks previously seen quests in a JSON file (or memory)
- Detection: Compares fetched quests with stored ones to find new quests
- Notification: Sends Discord webhook embeds for new quests only
- Multi-locale: Optionally checks all 33+ Discord locales to find region-specific quests
- Prerequisites: Rust 1.70+ (Install)
- Setup config:
cp example.config.toml config.toml
- Edit config:
- Add Discord user token (How to get your token)
- For single-instance setup, set
[mode].role = "collector" - Add webhook URLs under
[[discord.webhooks]]
Tip
You can add multiple webhooks to send notifications to different Discord channels.
- Run:
cargo run
- Fetches quests from Discord API (
https://discord.com/api/v10/quests/@me) - Multi-webhook support with optional names
- Reward filtering:
all,orbs, ordecor - Persistent quest storage (JSON) or in-memory
- Configurable check intervals
- Multi-locale support (single or all 33+ locales)
- Docker-ready
For collector mode (single-instance/default):
[discord]
token = "your_discord_user_token"
[mode]
role = "collector"
[[discord.webhooks]]
url = "https://discordapp.com/api/webhooks/YOUR_ID/YOUR_TOKEN"
name = "Optional webhook name" # optionalFor agent mode:
[discord]
token = "your_discord_user_token"
[mode]
role = "agent"
collector_url = "https://your-collector.example.com/ingest"
collector_token = "your-shared-secret"Note
Agent mode does not require [[discord.webhooks]].
Note
The super_properties field is optional. You can generate it using the browser console if needed.
How to generate super_properties
- Open Discord in your browser
- Press
F12to open Developer Tools - Go to the Console tab
- Paste this code and press Enter:
(() => {
const info = {
os: navigator.platform || "",
browser: navigator.userAgent.includes("Chrome") ? "Chrome" :
navigator.userAgent.includes("Firefox") ? "Firefox" :
navigator.userAgent.includes("Safari") ? "Safari" :
"Unknown",
device: "",
system_locale: navigator.language || "",
has_client_mods: false,
browser_user_agent: navigator.userAgent,
browser_version: (navigator.userAgent.match(/(Chrome|Firefox|Safari)\/([\d.]+)/) || [undefined,'',''])[2],
os_version: navigator.userAgent.match(/(Windows NT|Mac OS X|Android|CPU iPhone OS) ([\d_]+)/)?.[2]?.replace(/_/g,".") || "",
referrer: document.referrer || "",
referring_domain: document.referrer ? (new URL(document.referrer)).hostname : "",
referrer_current: location.href,
referring_domain_current: location.hostname,
release_channel: "stable",
client_build_number: Math.floor(Math.random()*900000 + 100000),
client_event_source: null
};
const json = JSON.stringify(info);
const encoded = btoa(json);
console.log(encoded);
})();- Copy the base64 string that appears in the console
- Add it to your
config.tomlassuper_properties = "your_base64_string"
| Field | Default | Description |
|---|---|---|
reward_filter |
all |
Quest reward type: all, orbs, or decor |
fetch_interval_minutes |
30 |
Check interval in minutes |
locale_mode |
single |
single or all (33+ locales) |
run_once |
false |
Exit after first check (useful for cron) |
storage_type |
json |
json or memory |
storage_path |
./known-quests.json |
Where to store quest data |
initial_send_all |
false |
Send all currently available quests on first run |
RUST_LOG=info cargo runcargo build --release
./target/release/qwestydocker build -t qwesty .
docker run -v $(pwd)/config.toml:/app/config.toml:ro -v /data:/data qwestyQwesty supports distributed multi-region quest monitoring through an Agent/Collector architecture. This allows you to monitor quests from multiple geographic regions (e.g., Korea, US, Europe) while centralizing notifications.
-
Collector (Host A): Central hub that receives quest data from agents and sends notifications
- Runs ingest server on port 8080 (configurable)
- Also acts as agent for its local region
- Sends unified notifications via configured webhooks
-
Agent (Hosts B, C, ...): Lightweight workers in different regions
- Fetches quests for assigned region
- Sends quest data to Collector
- No webhooks required
[mode]
role = "collector"
accept_token = "your-shared-secret" # Agents must use this token
ingest_port = 8080 # Optional, defaults to 8080
[region]
code = "id-ID" # Your collector's local region
[discord]
token = "your_discord_token"
[[discord.webhooks]]
name = "Global Notifications"
url = "https://discordapp.com/api/webhooks/ID/TOKEN"Run Collector:
cargo run
# or with Docker, expose port 8080
docker run -p 8080:8080 -v $(pwd)/config.toml:/app/config.toml:ro qwestyImportant
Put HTTPS reverse proxy (Caddy/Nginx) in front of the collector for secure communication.
[mode]
role = "agent"
collector_url = "https://your-collector.example.com/ingest"
collector_token = "your-shared-secret" # Must match collector's accept_token
[region]
code = "ko-KR" # Agent's region (ko-KR, en-US, ja-JP, etc.)
[discord]
token = "your_discord_token" # Still required to fetch quests
# No webhooks needed for agentsRun Agent:
cargo run
# or
docker run -v $(pwd)/config.toml:/app/config.toml:ro qwestyCollector exposes:
POST /ingest- Receives quest payloads from agents- Auth:
Authorization: Bearer <token> - Body:
{ "region": "ko-KR", "quests": [...], "source": "agent" } - Returns:
{ "accepted": N, "deduped": M }
- Auth:
GET /health- Health check endpoint
- Agent fetches quests for its configured region
- Agent POSTs quest data to Collector's
/ingestendpoint with bearer token - Collector validates token, deduplicates quests by
(region, id) - Collector sends Discord notifications for new quests
- Collector persists deduplicated quests to storage
- Use HTTPS with reverse proxy (Caddy/Nginx recommended)
- Keep
accept_token/collector_tokensecret and complex - Rotate tokens periodically
- Optional: IP allowlist for known agents
When locale_mode = "all":
- Checks all configured Discord locales sequentially (currently 31)
- 60-70s random delay between checks (rate limiting)
- ~33-38 minutes per full cycle
- Quests deduplicated by ID across locales
Notifications are sent as Discord embeds with:
- Quest name and game title
- Reward details
- Expiration time
- Fixed accent color (Discord blurple)
| Issue | Solution |
|---|---|
config.toml not found |
Run cp example.config.toml config.toml |
| No token configured | Add token = "..." in [discord] section |
| No webhooks configured | In collector mode, add [[discord.webhooks]] with url field |
| Failed to fetch quests | Verify token validity with Discord API |
| No notifications sent | Check if webhook URLs are correct and reward filter matches |
src/
├── main.rs # Entry point, main loop
├── lib.rs # Library exports
├── models/
│ ├── config.rs # Config loading & parsing
│ ├── quest.rs # Quest data structures
│ ├── errors.rs # Error types
│ └── mod.rs
├── services/
│ ├── client.rs # Discord API client
│ ├── ingest.rs # Agent ingest HTTP server (collector mode)
│ ├── webhook.rs # Webhook sender
│ ├── storage.rs # Quest persistence
│ └── mod.rs
└── utils/
└── mod.rs # Utilities
- Endpoint:
GET https://discord.com/api/v10/quests/@me - Auth: Discord user token in Authorization header
- Returns: Array of quest objects with metadata
This project is provided as-is for research and educational purposes only.
This project is licensed under the GNU Affero General Public License v3.0.