Dansday

bot.dansday.com

bot.dansday.com

This is my Discord bot platform: a single deployment that runs many bots across many servers, with everything configured from a web panel instead of slash commands. The source is public and MIT licensed. Version 26.5.6 as I write this.

Some scale for the rest of the page to sit against:

Repository    dansday-com/dansday-discord-bot (public, MIT)
First commit  26 August 2025
Commits       959 of mine, excluding merges
Source        63,847 lines across 315 files
Panel         54 pages, 63 API endpoints, 55 database tables

I rewrote this page from the repository rather than from memory, and two things the previous version claimed turned out to be wrong. It was written in April 2026 and had gone four months stale, so I want to start with the corrections rather than bury them.

Two corrections to what this page used to say

It said the project was fully open-source. That sentence went up on 9 April 2026. The MIT LICENSE file was added on 24 August 2026, together with a contributing guide, a code of conduct and a security policy. For four and a half months the source was public and unlicensed, which is not the same thing as open source — a reader who wanted to use it had no permission to. Public and licensed are two separate decisions and I had only made the first one.

It also led with a selfbot manager and an embed builder, and never mentioned that the bot now has an economy, a crypto market, a wagering game, a task engine and a live voice. That is not a stale detail, it is most of the product.

Where it came from

The first commit is 26 August 2025, and its message is GO BLOX v1.2.0. It was a single bot for a single Indonesian Roblox community, written in vanilla JavaScript. The control panel arrived on 3 November 2025 as a hand-written index.html, and most of that month's 113 commits went into it.

The panel became a real application across the turn of March into April 2026, and it is worth being exact about which month that was, because the answer depends on whether you count when a thing was written or when it landed. The pull request titled Migrating to use sveltekit 5 opened on 30 March, carried 126 commits, and merged on 4 April: 271 files, 31,604 additions and 25,294 deletions, of which exactly one added line sits in a generated file. It is the largest change in the project's history and it is almost entirely hand-written source. So March supplied the start and April the finish, and if I have to pick one, the platform is April — that is when it existed for anyone but me.

That rewrite is what made it multi-tenant: one process supervisor, many bot processes, per-server configuration and per-server data, with Drizzle and MySQL underneath. April is the biggest month in the repository at 374 commits. Then May has exactly one, because my day job took the whole month. June, July and August produced 97, 176 and 69.

The panel is the product

The premise is that server owners should not have to learn slash commands to run a community. /setup creates every channel and wires each one to its module. After that, everything happens on the web: which features are on, who on staff may change what, embed style and colours, welcome and booster messages, giveaways, moderation, AFK, staff ratings, message forwarding, and an embed builder with a live preview. There is a demo mode so you can walk through the whole panel before authenticating.

Members get their own public pages per server, with no login required to read them: live statistics, a leaderboard filterable across roughly twenty metrics and three periods, and a searchable member directory. Leaderboards stream over Server-Sent Events rather than polling.

June: XP became a currency

Until June, experience points were a score. The shop made them money. That pull request is 7,625 additions against 374 deletions across 57 files, and unusually for a number that size there is no lockfile in it — it is all product code.

Each server gets its own shop, priced in XP, with a 50-slot bag and optional timed availability. Thirteen effect types: steal, bomb, leech, bounty, shield, reflect, insurance, boost, gift, spy, disguise, purifier and luck. Most of them point at other members. A levelling bot that started by counting messages now sells you a way to take a percentage of someone else's earnings with your name hidden, and that is a real change in what the software is for, not a feature bullet.

July: a market, and a game with no house edge

The pull request is titled Adding stock & vault system. There are no stocks in it and there is no vault. It is a cryptocurrency market — if (assetType !== 'crypto') return null; — where members lock XP into positions at live CoinGecko prices and sell whenever they like. No real money is involved in either direction.

The parts I would defend: prices are polled every thirty minutes behind a Redis NX lock so exactly one process polls no matter how many are running, a 429 from CoinGecko starts a cooling-off period instead of a retry storm, topping up a position takes a weighted average of your entry prices, and coins held by members but outside the cached top ranks get fetched individually so a portfolio never shows a stale number.

The part I would flag: the day after those 83 files merged, the next pull request rewrote a good deal of them — 847 additions against 1,179 deletions. When one person merges 83 files with no reviewer, the review happens in production the following morning.

The wagering game is deliberately zero-edge. You pick a multiplier up to ten and your chance of winning is 100 divided by it, so the expected return is exactly your stake. Nothing is skimmed and the economy is not drained. It is still a variance machine that moves XP from the unlucky to the lucky, the roll is Math.random() and therefore not verifiable by any player, and seven seconds after each round the result is posted in the channel with the member tagged — wins celebrated and losses published by name. There is a per-server switch, and that switch is the thing I would point to if asked to defend it.

Also in July: a rebuild of the visual foundation, 80 files and 6,691 deletions, opened and then closed without merging.

The task engine

Eighteen daily and eighteen weekly objectives per member, with streaks, two freezes for missed days, and a seven-day check-in. The generator is the piece of engineering I am most pleased with in the project.

Tasks are not rolled and stored. They are derived from a seeded generator keyed on member, server, period and slot, so the same member on the same day gets the same list on any machine without the choice ever being persisted. Goals are sized from that member's own previous seven days of the exact metric and capped by what the period physically allows, so they measure effort rather than rank, and no two members get the same list. The pool is filtered by what the server has switched on, so a server with wagering disabled never generates a wagering task.

Two follow-up pull requests are titled Improving task algo and Improve task algo and daily claim, which is an honest signal that the first version's numbers were wrong.

The AI, and the boundary I did get right

Mention the bot and it answers; reply to it and the conversation continues without mentioning it again, in a private session per member per server that expires thirty minutes after they stop talking. It works against any OpenAI-compatible endpoint, configured per bot in the panel.

Ask it in chat and it joins your voice channel for a spoken conversation over the Gemini Live API. Discord speaks 48 kHz stereo in twenty-millisecond frames; the model listens at 16 kHz and answers at 24 kHz, so there is a hand-written converter in between. It is nearest-neighbour resampling with no filter, which will alias — a library would do better, it is about thirty lines, and it is good enough for speech. Sessions are capped, and when the cap is reached the bot does not hang up: it is asked to say a short natural goodbye and not to explain why. I think that is the right product decision and I also think it should be written down as a small instructed vagueness rather than described as a graceful exit.

It wakes on a spoken phrase detected by an on-device model — three ONNX files, about 2.8 MB, running in a worker thread — so a busy channel never sets it off and there is no per-utterance API cost for listening.

Its tools are web search, page fetch, image generation, any MediaWiki site including Fandom (with a small relay for wikis that block datacenter IP ranges), the server's own live data, the server's own XP rates, and the asker's own account.

The boundary is the part worth reading. The account tools take no parameters at all — the member is resolved from the conversation context, so the model cannot ask about somebody else even if it decides to. Disguised members are filtered out in code in the leaderboard, the roster and member lookup, the same way they are on the public pages. Which tools exist at all is computed from the server's module toggles in code. None of those three protections is a sentence in a prompt asking the model to behave, and that distinction is the whole point: a privacy rule that lives in a prompt is a request, not a boundary.

August: setup, legal, retention, languages

The setup flow was rewritten across two pull requests, the first of which is net negative — 1,735 additions against 2,594 deletions. Deleting more than you add while making a thing easier to start is usually the good kind of month.

Then a privacy policy and terms, and a data retention path to go with them: deletions are soft, and a purge removes them for good after seven days. It processes at most 50 servers and 5,000 members per run, and if it finds more expired servers than the cap it stops and asks for manual review instead of deleting them. A destructive job that refuses to run at unexpected scale is a small thing that has saved me before.

German and Spanish joined English and Indonesian for every button, select and label the bot renders.

Where I would fail this in review

This is the section I would want to read if I were assessing somebody else's project, so it is the section I owe a reader here.

A guard I wrote and never connected. There is a function that computes how much XP a member can spend without dropping below their current level. It returns both the total and that safe, spendable figure. The safe figure is read nowhere in the repository. All four callers — buying an item, buying a position in the market, placing a wager, and having XP stolen by another member — read the total instead, and the level is then recomputed and written, downwards. The README says a loss never costs a level. It can. The interface clamps to the total as well, so it is not even a client-side rule that the server forgets to enforce. The guard exists, is correct, and is not plugged in.

Indonesian is now the least complete language. English, German and Spanish carry 354 keys. Indonesian has 309, so it is 45 behind. German and Spanish were added on one day in August and are complete; the language of the community this bot was originally built for is the one with gaps.

The migrations folder is not a ledger. Across the project's history 83 migration files have been added and 64 deleted, leaving 19 on disk. Applied SQL gets removed rather than recorded, so the folder describes what is pending, not what a database has had done to it. Fourteen of the nineteen are dated between the 32nd and the 45th of July, dates that do not exist, because the prefix is being hand-typed as a counter.

Two of my own titles are unreliable. Improved security audit touches five files, of which 1,499 additions are package-lock.json; the actual change is about eighty lines tightening validation on a user-supplied outbound URL. Improve page visibility reads as 7,932 additions until you notice 6,987 of them are a minified Font Awesome stylesheet, leaving 945. I quote line counts on this page with generated files taken out, because I have been wrong about my own work by trusting the raw number.

The quest feature. A member presses a button, a modal opens, and they paste their own Discord account token; the bot then drives Discord's quest endpoints as that user. That is automation of a user account, which Discord's terms prohibit, and the credential involved is equivalent to their whole account. What the code does about it: the token is not stored, the risk is spelled out before any work begins, the warning states that the account may be restricted or banned, and the Indonesian version says the same thing rather than a softened one. A server administrator can disable the automation entirely. That is the most that can be said in its favour, and it does not make it a good idea — the best case is a small reward and the worst case is somebody else's account being taken away, with a paragraph of text as the mitigation.

Wagering, in a community with young members. Zero edge, one switch, no gambling tasks generated when the module is off. Set against that: losses are announced by name, and eight of the task metrics are about wagering. Each decision is defensible on its own. I did not sit down and decide to build a retention loop around a wagering mechanic, which is roughly the problem — nobody does, it assembles itself out of reasonable steps.

One member-facing sign-in path on the public pages is weaker than it should be. I am not describing the mechanism here, because a public page is not the place to publish a live weakness in software other people are running. It is on my list to replace, and the specifics belong in a direct conversation.

One reviewer. Every pull request in this repository was merged by the person who wrote it. The clearest evidence is the 83-file release followed by a rewrite the next day. A migration to a component library is open and unmerged as I write this, so it is not part of the product no matter how good the branch looks.

Stack

Language      TypeScript, SvelteKit 2 / Svelte 5, Vite, adapter-node
Styling       Tailwind CSS 4
Discord       discord.js 14; a separate user-token path for the optional flows
Data          MySQL via Drizzle ORM, 55 tables
Cache         Redis for sessions, voice coordination and AI memory, optional
AI            any OpenAI-compatible endpoint for chat; Gemini Live for voice
Voice         @discordjs/voice, opus, sodium-native, prism-media, ffmpeg
On-device     ONNX wake-word models in a worker thread
Ops           Docker, OpenTelemetry, process supervision per bot

What this project is evidence of

Multi-tenancy done as process supervision rather than as a tenant column bolted onto a single bot. Real-time delivery over SSE instead of polling. A generated system whose output is deterministic from a seed rather than stored. Third-party integrations that assume the other side will rate-limit, block, or return nothing useful, and keep working. Privacy boundaries placed in code where the model cannot argue with them. Legal, retention and licensing treated as work rather than paperwork, even though they arrived late.

And a habit that matters more to me than any of the features: I go back and read the diffs before describing my own work, including on this page, and I publish the parts that do not flatter me. The guard that is not wired up, the language I left behind, and the impossible dates in the migration filenames were all found that way. A project page that only lists what works is a brochure. This one is meant to be a record.