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, WATCH_VIDEO_ON_MOBILE, PLAY_ON_DESKTOP, STREAM_ON_DESKTOP, and PLAY_ACTIVITY — 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.

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 /quests/{id}/video-progress with incrementing timestamp
WATCH_VIDEO_ON_MOBILEduration5sSame as WATCH_VIDEO
PLAY_ON_DESKTOPduration30sPOST /quests/{id}/heartbeat with stream_key: "call:{questId}:1"
STREAM_ON_DESKTOPduration30sSame heartbeat as PLAY_ON_DESKTOP
PLAY_ACTIVITYduration30sPOST /quests/{id}/heartbeat with stream_key: "call:{userId}:1"