Discord Quest
Automation

A minimal Node.js library for automating Discord Quests.
No discord.js. Pure HTTP. Full TypeScript support.

index.js
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`),
});
Discord

All Quest Types

WATCH_VIDEO, PLAY_ON_DESKTOP, PLAY_ACTIVITY, ACHIEVEMENT_IN_ACTIVITY, and more — all supported out of the box.

Extensible

Register custom solvers for any new quest type Discord adds. No need to wait for library updates.

Node.js

Event-Driven

Real-time progress, completion, and error events. Wire up callbacks or use EventEmitter directly.

Proxy Support

HTTP and SOCKS5 proxies with optional auth. Pass a string or a full config object.

Axios

Rate-Limit Aware

Reads Discord's x-ratelimit-* headers and retries 429/5xx automatically. Never gets stuck.

TypeScript

TypeScript-First

Full type definitions included. Works in plain JavaScript too. Only one runtime dependency: axios.

Installation

Requires Node.js 18 or higher.

npm install discord-quests
yarn add discord-quests
pnpm add discord-quests

Quick Start

Three ways to use the library, from simplest to most controlled.

One-liner
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}`),
});
Solve all with an instance
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));
Manual solver control
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();

API Reference

All methods return typed results and never throw silently.

new DiscordQuests(token, options?)

Creates a new client. Throws immediately if the token format is invalid.

ParameterTypeDescription
tokenstringDiscord user account token
options.proxystring | ProxyOptionsProxy server
options.proxyType"http" | "socks5"Proxy protocol (default: "http")
static async run(token, options?) → SolveResult[]

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) });
async validateToken() → boolean

Calls /users/@me to verify the token is working. Returns false if invalid, expired, or banned.

async fetchQuests() → QuestInfo[]

Returns all active (non-expired) quests. Expired quests are filtered out automatically.

async enroll(questId) → EnrollResult

Enroll in a quest. If already enrolled, returns immediately without an extra request.

FieldTypeDescription
successbooleantrue if enrolled or was already enrolled
alreadyEnrolledbooleantrue if the account was already enrolled
questQuestInfoFresh quest data after enrollment
async enrollAll() → EnrollResult[]

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)`);
async solve(questId, options?) → SolveResult

Full lifecycle for one quest: fetch → enroll → solve. Returns a typed result, never throws.

StatusMeaning
"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
async solveAll(options?) → SolveResult[]

Fetch all quests and solve every unsolved one. Accepts { parallel: true } to run simultaneously instead of sequentially.

async getStatus() → StatusEntry[]

Returns enrollment state, completion, solvability, and per-task progress for all active quests.

createSolver(quest, taskId?) → BaseSolver

Creates a solver instance without running it. The solver extends EventEmitter — wire up events before calling .start(). Call .stop() at any time to abort.

registerSolver(taskId, SolverClass) → void

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.

new QuestWatcher(client, options?)

Polls Discord on an interval and fires events when quests drop, expire, or complete.

OptionTypeDefaultDescription
intervalnumber300000Poll interval in ms
autoEnrollbooleanfalseEnroll in new quests automatically
autoSolvebooleanfalseEnroll 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();
Events
EventPayloadWhen fired
"new_quest"QuestInfoQuest appeared since last poll
"expired_quest"QuestInfoQuest was active, now gone
"completed"QuestInfoQuest transitioned to completed
"tick"QuestInfo[]Every poll cycle
"error"ErrorFetch failed

Custom Solvers

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
Property / Method Description
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()

Supported Quest Types

Task IDTypeIntervalMechanism
WATCH_VIDEOduration5sPOST /video-progress with incrementing timestamp
WATCH_VIDEO_ON_MOBILEduration5sSame as WATCH_VIDEO
PLAY_ON_DESKTOPduration30sPOST /heartbeat with stream_key: "call:{questId}:1"
STREAM_ON_DESKTOPduration30sSame heartbeat as PLAY_ON_DESKTOP
PLAY_ACTIVITYduration30sPOST /heartbeat with stream_key: "call:{userId}:1"
PLAY_ON_XBOXduration30sSame heartbeat as PLAY_ON_DESKTOP
PLAY_ON_PLAYSTATIONduration30sSame heartbeat as PLAY_ON_DESKTOP
ACHIEVEMENT_IN_ACTIVITYcountone-shotOAuth2 → discordsays token → trigger achievement