Skip to content

edriso/0.1x-bot

Repository files navigation

0.1x developer

A small Discord bot for a study server. It rewards you for showing up. No streaks, no pressure.

The unit is a cup. One cup is the amount of coding you get done in the time it takes to drink a cup of coffee or tea, around 10 minutes. A cup of TypeScript. A cup of NestJS. A cup of whatever you are learning.

You open the server, click a button, write one short line about what you did, and post it. The first cup of the day gives you XP. XP unlocks roles. If you show up 5 days or more in a week, you get a bonus. You can also set a quest to track a focus area and celebrate when you finish it.

Stack

  • TypeScript
  • discord.js v14
  • Prisma + MySQL
  • Vitest for tests
  • ESLint, Prettier, Husky for code quality

How the server looks

You only need two channels:

  1. #cuppa (locked). The bot pins one message here with three buttons:
    • Code Session
    • Study Session
    • Set Quest
  2. #cups (read only for humans). The bot posts every cup here as a clean embed, plus shoutouts for quest completions and the weekly bonus.

The rules

  • One cup is about 10 minutes of real coding.
  • Your first Code Session of the day gives you +50 XP.
  • A second code session the same day still posts in the feed but gives 0 XP. The goal is to show up, not to grind.
  • A Study Session (videos, docs, reading) also posts in the feed, but it never gives XP.
  • If you have 5 days or more with at least one earning cup in a week, the bot gives you +150 XP and shouts you out in the feed at the start of the next week.
  • The week starts on Saturday by default, but every member can change their own with /settings week-start.
  • Every member can set their own timezone with /settings timezone. Your daily XP boundary resets at your own local midnight, so a member in Tokyo and a member in Cairo each get the right "new day" at the right moment.

Quests

A quest is what you are currently focused on. Examples: NestJS, AWS exam, a side project, anything. Quests are optional. New users start with no quest set.

How to use them:

  • Click 🎯 Set Quest on the panel, or run /quest name:... target:....
  • You can give a quest an optional target number of cups. When you hit the target, the bot celebrates it in #cups. There is no penalty for missing.
  • Want to change the target later? Run /quest again with the same name and a new target. Your cup count is kept. (This only works while the quest is still active. After it is completed, the same name starts a fresh cycle.)
  • You can keep logging past your target. If you set target 2 and end up doing 5 cups, you will see 5/2 ✅ on the cup. The extra cups are not lost: they help you plan a better target next time.
  • Cups you log get tagged with your active quest. The tag is frozen at log time, so renaming or switching later does not rewrite history.
  • Past quests stay in your history, even ones you abandoned by switching to something else.

What happens when you set a quest

You are on... You submit... What happens
Nothing A name New quest, count starts at 0.
NestJS x 10, 4 cups NestJS x 10 (no change) Nothing changes.
NestJS x 10, 4 cups NestJS x 15 Same quest, target updated, your 4 cups stay (now 4/15).
NestJS x 10, 4 cups NestJS x 3 Same quest, target shrunk, completes right away because 4 >= 3.
NestJS x 10, 4 cups React x 5 Old NestJS quest paused (kept in your history). New React quest starts.
NestJS x 10, completed NestJS x 10 New cycle. Fresh quest row, count restarts.
Anything Blank name Quest cleared. The row stays in your history.

Levels and roles

Level XP needed Role
0 0 Novice
1 100 Apprentice
2 300 Crafter
3 700 Wizard
4 1500 1x Developer
5 3000 Archmage

The names play on the project's name: the bot is "0.1x developer," so reaching "1x Developer" at level 4 is the wink. Archmage is the peak tier, the master mage who sees beyond.

The bot adds the role to you when you level up. You need to create these roles in your Discord server (names are matched case-insensitively). Place them BELOW the bot's own role, so it has permission to assign them.

Slash commands

Type these in either #cuppa or #cups. The channels are locked for free-form messages but slash commands work there because they use a separate permission. Every reply is ephemeral (only you see it), so commands never clutter the channel for other members.

  • /stats shows your level, XP, current quest with progress, this week's days, and a small breakdown of this week's cups by quest.
  • /quest name:... target:... sets or changes your quest. Both options are optional. Leave name blank to clear. Autocomplete on name suggests common focuses, but you can type anything you want.
  • /quests lists your quest history: active, completed, and paused, each with cup counts and when you started them.
  • /last count:... shows your most recent cups (default 5, max 15). Useful when you want to pick up where you left off without scrolling the feed.
  • /settings view shows your current timezone, week start, and digest opt-in. The one-stop place to audit your preferences.
  • /settings timezone name:Asia/Tokyo sets your IANA timezone so your daily XP boundary resets at your own local midnight. Autocomplete suggests common zones. Default is Africa/Cairo until you set it.
  • /settings week-start day:Monday picks the day your week resets on. Affects only what you see in /stats and the feed embed footer. Default is Saturday.
  • /settings digest state:on|off turns on or off a weekly DM that recaps your week. The DM arrives on your week-start day (set via /settings week-start, default Saturday) in your timezone. Off by default.
  • /settings nudge state:on|off turns on or off a one-time "we missed you" DM after a 30-day quiet stretch. No streak language, no threat, just a warm note. Off by default.
  • /setup panel (admin only) posts the panel buttons into #cuppa. /setup welcome posts the welcome and house-rules messages into your welcome channel. Both subcommands are idempotent: re-running deletes the bot's previous messages first.

Little touches the bot does

  • Milestone reactions. When you log your 25th, 50th, 100th, 250th, 500th, or 1000th cup, the bot drops a 🏆 reaction on the feed embed. Sparse on purpose so the reaction stays meaningful.
  • Quest completion shoutout. When a quest with a target hits its cup count, the bot calls it out in #cups once.
  • Weekly digest DM (opt-in). Run /settings digest state:on and you get a short DM each week with your last seven days. The DM arrives on your own week-start day in your own timezone, so a Monday-start user gets a Monday recap and a Saturday-start user gets a Saturday recap. Stays in DMs, never bloats the server. Off by default. The bot never DMs unless you ask.
  • Comeback nudge DM (opt-in). Run /settings nudge state:on and the bot will send one warm DM if you go quiet for 30 days. Never a second DM in the same absence cycle, never any "log or else" language. Off by default.
  • Quest paused, not lost. Switching to a new quest while a non-completed one is active does not delete the old quest. You can see it under "Paused" in /quests and pick a similar focus up later.

First time setup

  1. Copy the env file:

    cp .env.example .env

    Fill in the values. You need a bot token, a client ID, the two channel IDs, and a MySQL connection string.

  2. Install dependencies. The Prisma client is generated automatically after install.

    pnpm install
  3. Create the database tables:

    pnpm prisma:migrate
  4. Register the slash commands with Discord:

    pnpm deploy:commands
  5. Start the bot:

    pnpm dev
  6. In your Discord server, type /setup panel to drop the pinned panel into #cuppa, then /setup welcome to post the three welcome messages into #welcome (set WELCOME_CHANNEL_ID in .env first). Pin each bot message so it stays at the top.

In the Discord Developer Portal, also turn on the Server Members Intent under "Bot". The bot needs this to assign roles.

What gets stored

Four small tables:

  • users holds your XP, level, a pointer (current_quest_id) to your active quest (or null), opt-ins for the two optional DMs (weekly_digest_opt_in, absence_nudge_opt_in), your IANA timezone and week_start day, a prefs_confirmed flag (true once you've run any /settings subcommand, used to silence the one-time first-cup nudge), a last_cup_at timestamp (denormalized snapshot of your most recent cup), idempotency timestamps for the DMs (weekly_digest_sent_at, absence_nudged_at), and a dms_blocked_at flag set automatically when a DM fails because you have the bot blocked or your DMs closed (cleared the next time you interact with the bot, so future DMs resume automatically).
  • quests is an append-only history of every quest you have ever set. The state of a quest is read from its columns: it is active if users.current_quest_id points at it, completed if completed_at is set, and paused if neither.
  • cups is one row per session. Each cup carries a quest_id pointing to the quest active when it was logged (or null if you had no quest set).
  • weekly_bonus_logs is one row per paid-out week. It keeps the bonus from paying twice if the cron runs again or the bot restarts.

Scripts

Command What it does
pnpm dev Start the bot in watch mode
pnpm build Generate the Prisma client, compile to dist
pnpm start Run the compiled bot
pnpm test Run all tests
pnpm test:watch Tests in watch mode
pnpm test:coverage Tests with coverage report
pnpm typecheck TypeScript with no emit
pnpm lint Run ESLint
pnpm lint:fix ESLint with auto fix
pnpm format Prettier write
pnpm check Typecheck + lint + tests (used in CI)
pnpm deploy:commands Push slash commands to Discord
pnpm prisma:migrate Run DB migrations in dev
pnpm prisma:deploy Apply DB migrations in prod
pnpm prisma:studio Open Prisma Studio

Forking it

Every user-facing string lives in src/lib/copy.ts. If you want to rebrand for a different theme (workout tracker, language learning, whatever), that file is the main place to edit. Two adjacent files hold the rest of the brand surface: src/lib/levels.ts for XP tiers and role names, src/lib/quests.ts for the autocomplete suggestions. The data layer and routing are already abstract, so no schema migration is needed for a rebrand.

Code quality

  • Husky pre-commit runs lint-staged. It formats and lints only the files you changed.
  • Husky pre-push runs pnpm typecheck && pnpm test. So broken code never leaves your machine.

Troubleshooting

My editor says Module '"@prisma/client"' has no exported member 'PrismaClient'.

Run pnpm prisma:generate once. The Prisma client is generated, not committed. After that, restart your editor's TypeScript server (in VS Code: Ctrl+Shift+P then "TypeScript: Restart TS Server").

The weekly bonus did not run because my bot was offline.

The bot also tries to run a catch up once on startup. If it sees the previous week was never paid out, it pays out then. Safe to restart any time.

I want the week to start on Monday instead of Saturday.

If you want your personal "this week" counter to reset on Monday, run /settings week-start day:Monday. That only affects what you see. The server-wide weekly bonus cron still fires on the day set by WEEK_START in .env (default Saturday). To shift the whole server bonus payout, set WEEK_START=1 in .env. 0 is Sunday, 6 is Saturday.

I want to use a different timezone than Africa/Cairo.

Run /settings timezone name:America/New_York (or any IANA name). That changes only your daily-cup boundary. The SERVER_TIMEZONE env var still controls the timing of the weekly bonus cron only.

I added or renamed a slash command, and Discord still shows the old one.

Run pnpm deploy:commands again so Discord picks up the new command list. If you scoped commands to a guild (via DISCORD_GUILD_ID, your server ID), they update instantly. Global commands can take up to an hour. In production you can instead trigger the register-commands GitHub Action (Actions → Run workflow); it needs the DISCORD_TOKEN, DISCORD_CLIENT_ID, and DISCORD_GUILD_ID repo secrets.

I pulled new code and the bot crashes about missing columns.

A migration changed in place. Run pnpm prisma migrate reset to wipe your dev database and replay every migration. This only deletes data in dev. Never run reset in production.

Roadmap ideas

Things that are intentionally not built yet, to keep the system tiny:

  • Leaderboards. They cause comparison anxiety. The feed already shows social proof.
  • Public daily streaks. Punishing missed days kills motivation. The weekly bonus is the only public reward.
  • Multiple XP types. Keep it one currency.
  • Comments under cups. The feed is read only on purpose.
  • Daily hall-of-fame channels. Naming who showed up creates implicit shame for who did not.
  • Tomorrow-I-will channel. The quest mechanic already covers per-cycle intentions.
  • Karma XP for reactions. It cheapens XP and turns genuine reactions into a points game.

About

A small Discord bot for a study server. It rewards you for showing up. No streaks, no pressure.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages