quizbase
Skip to content
Command Palette
Search for a command to run...
QuizBase · Docs
by Maciej Dzierżek · published May 15, 2026 · 45 min read · Beginner
reddit-weekly-trivia-bot hero illustration
Illustration for: Reddit weekly trivia bot tutorial for 2026 API · Generated with Nano Banana, brand style

Reddit weekly trivia bot tutorial for 2026 API#

A weekly trivia thread is the highest-engagement community ritual for game-dev (and adjacent) subreddits. Tuesday post asks the question, Thursday post reveals the answer with a leaderboard of first-correct commenters. The subreddit gets recurring activity, your bigger project (the game you’re actually building) gets a soft promotion in the bot’s signature line. This guide ships the bot in ~120 lines of Node using snoowrap (the standard Reddit API library) + node-cron.

The 2023 Reddit API pricing drama left most “Reddit bot tutorial” blog posts stale — they used pre-pricing free quotas. This guide is current as of 2026: you’ll need a developer account, a small-tier API access key, and you should run the bot on your own infrastructure (Reddit doesn’t host this for you). Cost: $5/mo for a small droplet + Reddit API base tier (currently free under 100 req/min for personal-use bots).

What you’ll build#

A Reddit bot that:

  • Tuesday 10am UTC — posts a new thread in your target subreddit titled “Weekly Trivia: $TOPIC”
  • Reads top-level comments for the next 48 hours, scores them by first-correct
  • Thursday 4pm UTC — replies to the thread with the answer + a leaderboard of top 5 commenters
  • No-repeat dedup — tracks question IDs in a JSON file so the subreddit never sees the same question twice
  • Per-question attribution — Source / Author / License surfaced in the post body
  • Configurable category?tags=programming for r/gamedev, ?category=film for r/movies

The mechanic — week-long round + stable IDs#

The bot has two cron jobs (Tuesday post + Thursday reveal) and an in-memory currentRound: { id, correctAnswer, postId, scores: Map }. Tuesday it fetches GET /api/v1/questions/random, posts to Reddit, saves the round to disk (in case the bot restarts mid-week). Wednesday-Thursday it polls the thread’s comments via subreddit.submission.comments, scoring the first-correct per user.

The stable QuizBase id is persisted across the week — if the bot reboots Wednesday at 3am, it reads round.json from disk, knows it’s mid-round, knows which question is currently live. Without the stable ID we’d lose state on every restart. See /docs/api/questions-by-id for the full stable-ID pattern catalogue.

Stack#

  • Node.js 20+ + snoowrap (Reddit API client) + node-cron + fs/promises for the JSON store
  • Reddit app credentials — create at reddit.com/prefs/apps (developer category: “script”)
  • QuizBase publishable keyqb_pk_*, free tier covers weekly cadence trivially
  • Small VM — DigitalOcean droplet, Railway worker, Fly machine. Needs to run 24/7 to make the cron triggers fire.

Step 1 — Reddit app credentials + dependencies#

Go to reddit.com/prefs/apps, scroll to “Are you a developer? create an app…”, choose script type. You get a client ID (under the app name) and a client secret.

mkdir reddit-trivia && cd reddit-trivia
npm init -y
npm install snoowrap node-cron dotenv

.env:

REDDIT_USER=yourbotaccount
REDDIT_PASS=password
REDDIT_CLIENT_ID=abc123
REDDIT_CLIENT_SECRET=secret456
SUBREDDIT=gamedev
QUIZBASE_KEY=qb_pk_...

The bot account is a normal Reddit account you create — give it some karma first (post a couple times manually) so it’s not shadowbanned for fresh-account behaviour.

Step 2 — Wire QuizBase fetch + round state#

// state.ts
import fs from 'node:fs/promises';

const STATE_FILE = './round.json';

export interface Round {
	id: string;
	correctAnswer: string;
	choicesText: string; // pre-formatted A/B/C/D string
	postId: string; // Reddit submission id
	scores: Record<string, number>; // username → wins this week
}

export async function loadState(): Promise<Round | null> {
	try {
		const raw = await fs.readFile(STATE_FILE, 'utf-8');
		return JSON.parse(raw);
	} catch {
		return null;
	}
}

export async function saveState(round: Round | null) {
	if (!round) {
		await fs.unlink(STATE_FILE).catch(() => {});
		return;
	}
	await fs.writeFile(STATE_FILE, JSON.stringify(round, null, 2));
}
// quizbase.ts
const KEY = process.env.QUIZBASE_KEY!;
const seenIds = new Set<string>(); // load from disk on startup if you want long-term dedup

export async function fetchUnique(retries = 3) {
	for (let i = 0; i < retries; i++) {
		const r = await fetch(
			`https://quizbase.runriva.com/api/v1/questions/random?category=general-knowledge`,
			{ headers: { 'X-API-Key': KEY } }
		);
		const { data } = (await r.json()) as { data: Array<{ id: string; text: string; correctAnswer: string; incorrectAnswers: string[]; attribution: { author: string; license: string } }> };
		const q = data[0];
		if (q && !seenIds.has(q.id)) {
			seenIds.add(q.id);
			return q;
		}
	}
	throw new Error('No fresh question after 3 retries');
}

Step 3 — Tuesday: post the trivia thread#

import snoowrap from 'snoowrap';
import cron from 'node-cron';
import { fetchUnique } from './quizbase';
import { saveState } from './state';

const reddit = new snoowrap({
	userAgent: 'weekly-trivia-bot v1.0',
	clientId: process.env.REDDIT_CLIENT_ID!,
	clientSecret: process.env.REDDIT_CLIENT_SECRET!,
	username: process.env.REDDIT_USER!,
	password: process.env.REDDIT_PASS!
});

cron.schedule('0 10 * * 2', async () => {
	// Tuesday 10am UTC
	const q = await fetchUnique();
	const choices = [q.correctAnswer, ...q.incorrectAnswers].sort(() => Math.random() - 0.5);
	const choicesText = choices.map((c, i) => `${'ABCD'[i]}. ${c}`).join('\n\n');

	const body = `**Weekly Trivia**

${q.text}

${choicesText}

Reply with the letter (A/B/C/D). First correct answer per user counts. Reveal Thursday 4pm UTC.

---

*Source: ${q.attribution.author} (${q.attribution.license})*
*Trivia content from [QuizBase](https://quizbase.runriva.com) — open API, free tier for personal use.*`;

	const post = await reddit
		.getSubreddit(process.env.SUBREDDIT!)
		.submitSelfpost({ title: `Weekly Trivia — ${new Date().toLocaleDateString()}`, text: body });

	await saveState({
		id: q.id,
		correctAnswer: q.correctAnswer,
		choicesText,
		postId: post.name,
		scores: {}
	});

	console.log(`Posted to r/${process.env.SUBREDDIT}: ${post.url}`);
});

The Source: ... line + the QuizBase backlink in the post body are required. CC BY-SA / CC BY / MIT compliance — see /data. Skipping these puts your bot at risk of moderator removal for “uncredited content”.

Step 4 — Continuously score comments#

snoowrap’s inbox stream isn’t real-time, so we poll comments every 15 minutes. Top-level comments only (no replies); first correct per user counts:

import { loadState, saveState } from './state';

cron.schedule('*/15 * * * *', async () => {
	const round = await loadState();
	if (!round) return; // No active round

	const submission = reddit.getSubmission(round.postId);
	const comments = await submission.comments.fetchAll({ amount: 200 });
	// Recover the shuffled choices actually shown to users (strip the "A. " prefix)
	const choices = round.choicesText.split('\n\n').map((s) => s.slice(3));
	const correctIndex = choices.indexOf(round.correctAnswer);
	const correctLetter = `abcd`[correctIndex];

	for (const c of comments) {
		const author = (c.author as any).name;
		if (author === '[deleted]' || round.scores[author]) continue;
		const lower = c.body.trim().toLowerCase();
		if (lower === correctLetter || lower === round.correctAnswer.toLowerCase()) {
			round.scores[author] = (round.scores[author] ?? 0) + 1;
		}
	}

	await saveState(round);
});

Step 5 — Thursday: reveal + leaderboard#

cron.schedule('0 16 * * 4', async () => {
	// Thursday 4pm UTC
	const round = await loadState();
	if (!round) return;

	const top = Object.entries(round.scores)
		.sort(([, a], [, b]) => b - a)
		.slice(0, 5)
		.map(([user, score], i) => `${i + 1}. /u/${user} — ${score}`)
		.join('\n');

	const replyBody = `**Answer:** ${round.correctAnswer}

**This week's leaderboard:**

${top || '(No correct answers this week)'}

---

Next round Tuesday. Trivia from [QuizBase](https://quizbase.runriva.com).`;

	const submission = reddit.getSubmission(round.postId);
	await submission.reply(replyBody);

	await saveState(null); // Clear round
	console.log('Reveal posted, week reset.');
});

Pitfalls#

  • Reddit shadowbans aggressive bot behaviour — your bot account needs natural karma history (post some genuine comments before launch). Fresh accounts posting bot output get filtered out of the subreddit’s view automatically.
  • Subreddit moderators must approve bots — message the mods of your target sub explaining what the bot does before launching. Most game-dev subs are bot-friendly with permission.
  • Bot uptime requirement — cron jobs only fire if the process is running. Use a VM with systemd auto-restart or a managed worker (Fly Machines, Railway Worker tier).
  • 15-minute scoring poll means rapid back-to-back comments after Tuesday post might be missed if your bot crashes mid-poll. Solution: track lastSeenCommentId per scoring run, paginate via Reddit’s before parameter.

What next#

  • Multi-subreddit deployment — same bot in r/movies, r/history, r/gamedev with different ?category= per subreddit
  • Persistent all-time leaderboard — track wins across weeks in a SQLite file, surface monthly champions
  • Weekly digest cross-post — auto-cross-post the Thursday reveal to your own subreddit / Discord / Slack
  • Pair with Slack/Discord variant — same trivia content, different community surface

Ready to ship? Grab a free publishable key, set up Reddit app credentials, copy the snippets, deploy to a small VM. Bug in a question? a Reddit-side POST /api/v1/report integration is one extra cron job — listen for /u/yourbot !report mentions and forward the report by question ID.