A minimal Node.js library for automating Discord Quests.
No discord.js. Pure HTTP. Full TypeScript support.
import { DiscordQuests } from "discord-quests";
const dq = new DiscordQuests("YOUR_TOKEN");
const results = await dq.solveAll({
onProgress: ({ taskId, percent }) =>
console.log(`[${taskId}] ${percent}%`),
onCompleted: (id) =>
console.log(`Quest ${id} completed`),
});
WATCH_VIDEO, PLAY_ON_DESKTOP, PLAY_ACTIVITY, ACHIEVEMENT_IN_ACTIVITY, and more — all supported out of the box.
Register custom solvers for any new quest type Discord adds. No need to wait for library updates.
Real-time progress, completion, and error events. Wire up callbacks or use EventEmitter directly.
HTTP and SOCKS5 proxies with optional auth. Pass a string or a full config object.
Reads Discord's x-ratelimit-* headers and retries 429/5xx automatically. Never gets stuck.
Full type definitions included. Works in plain JavaScript too. Only one runtime dependency: axios.
Requires Node.js 18 or higher.
npm install discord-quests
yarn add discord-quests
pnpm add discord-quests
Three ways to use the library, from simplest to most controlled.
import { DiscordQuests } from "discord-quests";
await DiscordQuests.run("YOUR_TOKEN", {
onProgress: ({ taskId, percent }) => console.log(`[${taskId}] ${percent}%`),
onCompleted: (id) => console.log(`Completed: ${id}`),
onError: (id, err) => console.error(`Error: ${err.message}`),
});
import { DiscordQuests } from "discord-quests";
const dq = new DiscordQuests("YOUR_TOKEN");
// validate before running
if (!await dq.validateToken()) throw new Error("Invalid token");
const results = await dq.solveAll();
results.forEach(r => console.log(r.questName, r.status));
const quest = await dq.fetchQuest("1234567890");
const solver = dq.createSolver(quest);
solver.on("progress", ({ current, target, percent }) => {
process.stdout.write(`\r${percent}% (${current}/${target}s)`);
});
solver.on("completed", () => console.log("\nDone!"));
// stop after 2 hours if needed
setTimeout(() => solver.stop(), 2 * 60 * 60 * 1000);
await solver.start();
All methods return typed results and never throw silently.
Creates a new client. Throws immediately if the token format is invalid.
| Parameter | Type | Description |
|---|---|---|
token | string | Discord user account token |
options.proxy | string | ProxyOptions | Proxy server |
options.proxyType | "http" | "socks5" | Proxy protocol (default: "http") |
Creates an instance and calls solveAll() in one call. The fastest way to use the library.
await DiscordQuests.run("TOKEN", { onCompleted: (id) => console.log(id) });
Calls /users/@me to verify the token is working. Returns false if invalid, expired, or banned.
Returns all active (non-expired) quests. Expired quests are filtered out automatically.
Enroll in a quest. If already enrolled, returns immediately without an extra request.
| Field | Type | Description |
|---|---|---|
success | boolean | true if enrolled or was already enrolled |
alreadyEnrolled | boolean | true if the account was already enrolled |
quest | QuestInfo | Fresh quest data after enrollment |
Enroll in all available quests at once. Skips quests already enrolled in without making extra requests.
const results = await dq.enrollAll();
const fresh = results.filter(r => r.success && !r.alreadyEnrolled);
console.log(`Enrolled in ${fresh.length} new quest(s)`);
Full lifecycle for one quest: fetch → enroll → solve. Returns a typed result, never throws.
| Status | Meaning |
|---|---|
"completed" | Solver ran and quest is done |
"already_completed" | Quest was already done |
"enroll_failed" | Could not enroll |
"unsupported" | No solver for this quest type |
"error" | Solver threw an error |
Fetch all quests and solve every unsolved one. Accepts { parallel: true } to run simultaneously instead of sequentially.
Returns enrollment state, completion, solvability, and per-task progress for all active quests.
Creates a solver instance without running it. The solver extends EventEmitter — wire up events before calling .start(). Call .stop() at any time to abort.
Register a custom solver for any task ID. Once registered, it is used automatically by solve(), solveAll(), and createSolver(). Can also override a built-in solver.
Polls Discord on an interval and fires events when quests drop, expire, or complete.
| Option | Type | Default | Description |
|---|---|---|---|
interval | number | 300000 | Poll interval in ms |
autoEnroll | boolean | false | Enroll in new quests automatically |
autoSolve | boolean | false | Enroll and solve new quests automatically |
const watcher = new QuestWatcher(dq, { interval: 3 * 60_000, autoSolve: true });
watcher.on("new_quest", (quest) => console.log("New:", quest.name));
watcher.on("completed", (quest) => console.log("Done:", quest.name));
watcher.start();
| Event | Payload | When fired |
|---|---|---|
"new_quest" | QuestInfo | Quest appeared since last poll |
"expired_quest" | QuestInfo | Quest was active, now gone |
"completed" | QuestInfo | Quest transitioned to completed |
"tick" | QuestInfo[] | Every poll cycle |
"error" | Error | Fetch failed |
If Discord adds a new quest type, write your own solver and register it — no library update needed.
import { BaseSolver, DiscordQuests } from "discord-quests";
import type { SolverConstructorArgs } from "discord-quests";
class MySolver extends BaseSolver {
constructor(config: SolverConstructorArgs) {
super(config);
// config.task — QuestTask being solved
// config.quest — full QuestInfo
// config.api — axios instance (token + headers pre-configured)
}
async run(): Promise<void> {
while (!this.stopped) {
const res = await this.api
.post(`/quests/${this.questId}/some-endpoint`, {})
.catch(err => err?.response);
const { value, completed } = this.extractProgress(res.data);
this.emitProgress(value, this.target);
if (completed || value >= this.target) {
this.emitCompleted();
break;
}
await this.delay(30000);
}
}
}
const dq = new DiscordQuests("TOKEN");
dq.registerSolver("NEW_QUEST_TYPE", MySolver);
await dq.solveAll(); // NEW_QUEST_TYPE is now handled automatically
this.api
Axios instance with token, headers, and rate-limit handling
this.questId
Current quest ID
this.taskId
Current task ID (e.g. "NEW_QUEST_TYPE")
this.current / this.target
Starting progress and target value
this.task / this.quest
Full QuestTask and QuestInfo objects
this.stopped
true if stop() was called
this.emitProgress(current, target)
Fires the "progress" event
this.emitCompleted()
Fires the "completed" event
this.extractProgress(data)
Extracts { value, completed } from a Discord quest response
this.delay(ms)
Sleep that respects stop()
| Task ID | Type | Interval | Mechanism |
|---|---|---|---|
WATCH_VIDEO | duration | 5s | POST /video-progress with incrementing timestamp |
WATCH_VIDEO_ON_MOBILE | duration | 5s | Same as WATCH_VIDEO |
PLAY_ON_DESKTOP | duration | 30s | POST /heartbeat with stream_key: "call:{questId}:1" |
STREAM_ON_DESKTOP | duration | 30s | Same heartbeat as PLAY_ON_DESKTOP |
PLAY_ACTIVITY | duration | 30s | POST /heartbeat with stream_key: "call:{userId}:1" |
PLAY_ON_XBOX | duration | 30s | Same heartbeat as PLAY_ON_DESKTOP |
PLAY_ON_PLAYSTATION | duration | 30s | Same heartbeat as PLAY_ON_DESKTOP |
ACHIEVEMENT_IN_ACTIVITY | count | one-shot | OAuth2 → discordsays token → trigger achievement |