Ditto looks after the voice side of a server and the door in front of it: a
captcha for newcomers, music for everyone, and voice tools for moderators.
Everything is set up from Discord with /setup — Quick setup does it in one
click.
What changed in each version: CHANGELOG.md. The web dashboard and
its pages for the Pterodactyl panel live on the dashboard branch.
Newcomers only see a verification channel with a Verify me button. It opens a private challenge drawn exactly like a reCAPTCHA 4×4 image challenge — same header, same grid, same footer, measured pixel for pixel — with sixteen buttons laid out like the squares, and Skip turning into Verify as soon as a square is ticked.
- A photo database ships with Ditto: 111 hand-picked street photos from Open Images with the exact position of every object — traffic lights, bicycles, buses, cars, motorcycles, fire hydrants, stop signs, boats, palm trees, street lights and stairs. Nothing to download.
- Squares the object clearly fills must be ticked; squares it only brushes, or that show a look-alike (a taxi when asked for cars), are accepted either way. Some challenges have nothing to tick: the answer is Skip.
- Instant: a few challenges are always drawn in advance, so the picture appears the moment the button is pressed, and a miss swaps it at once.
- No challenge is served twice. Each one is fingerprinted, the least shown photos come first, and every photo is zoomed, shifted, mirrored and tinted.
- After a few misses (3 by default), a Discord timeout. Nobody is kicked.
- Quarantine: members brought in by a member-pushing bot are recognised from Discord's join source and get a quarantine role instead.
/playtakes a song name or a link. As you type a name, Ditto suggests songs from YouTube Music, the closest match first: pick one, or just send what you typed and it plays the best match, skipping remixes, live and sped-up versions you did not ask for.- Links from YouTube (Shorts and YouTube Music included), SoundCloud, Bandcamp, TikTok, Twitch, X and Instagram play directly. Spotify, Apple Music, Deezer and Tidal tracks are matched on YouTube Music; Spotify, Apple Music and Deezer albums and playlists too.
- Links are taken as people share them: wrapped in
< >, with a word next to them, shortened. A link copied from a YouTube mix or radio plays the song itself, and a playlist opened on one of its videos starts with that video. When a link cannot be played, Ditto says why. - Fast: Ditto joins the voice channel while it searches, gets the first suggestion ready while you look at the list, reads a YouTube link and its stream in one go, looks up the next two tracks while the current one plays, and remembers stream addresses, so tracks start straight away. SoundCloud, Bandcamp and plain audio files are read by Lavalink itself, in a fraction of a second. Nothing Ditto runs in the background can freeze the bot.
- Sturdy: every link and every track has more than one way through (yt-dlp, Lavalink, a downloaded copy). A stream that YouTube cuts is asked for again, and the track carries on from where it stopped.
- Smooth: the built-in Lavalink runs with a larger audio buffer and a low-pause garbage collector, so playback does not stutter on small hosts.
- A live player: cover, title, a progress bar that moves on its own, what comes next, and buttons for previous, pause, skip, stop, loop, volume, shuffle, queue and lyrics, plus a menu of filters (bass boost, nightcore, vaporwave, 8D, karaoke) that apply live.
- Whoever queued a track can skip it; otherwise half the listeners have to agree. Ditto leaves when the queue ends or the channel empties.
- Move, gather, split, disconnect and "shake" members across channels.
- Locks: members of a locked channel are pulled back if they leave it, for as long as the lock lasts. Staff are never pulled back.
- Rooms: the first person in an empty room owns it and can customise it; when the last person leaves, it goes back to its original name, limit and permissions.
- Members deafened for a while (10 minutes by default) go to the AFK channel, and joins, leaves and moves are written to a log channel.
| Command | What it does | Who |
|---|---|---|
/play query [next] |
Play a song, a link or a playlist — with suggestions as you type | Anyone in voice |
/skip · /previous · /stop |
Skip, go back, or stop and leave — straight away or by vote | Listeners |
/pause · /resume |
Pause and resume | Listeners |
/queue [page] · /nowplaying |
Show the queue, or post the player again | Anyone |
/volume level · /loop mode · /shuffle |
Volume 0–100, loop off / track / queue, shuffle | Listeners |
/remove position · /clear |
Remove one track, or empty the queue | Listeners |
/seek time · /filter effect |
Jump to a moment (1:30), or apply an audio filter |
Listeners |
/lyrics [song] |
Lyrics of the current track, or of any song | Anyone |
/help |
What Ditto can do | Anyone |
/setup |
Every setting, and Quick setup | Staff |
/captcha test |
Try the captcha without touching your roles, then see the expected squares | Staff |
/captcha reset <member> |
Clear a member's misses and timeout | Staff |
/captcha panel · /captcha reload |
Post the Verify panel again, or reload the photos | Staff |
/room name · limit · lock · unlock · invite · transfer |
Customise the room you own | Room owner |
/move to [member] [role] [from] |
Move one member, everyone in voice with a role, or a whole channel | Move Members |
/gather to |
Pull everyone in voice into one channel, with a button to send them back | Move Members |
/split teams [from] |
Shuffle a channel into 2–4 teams across empty rooms | Move Members |
/disconnect [member] [channel] |
Disconnect a member or a whole channel | Move Members |
/shake member [times] [to] |
Bounce a member through random channels, with a Stop button | Move Members |
/lock channel [duration] · /unlock · /locks |
Lock a channel (30m, 2h, 1h30), unlock, list locks |
Move Members |
Listeners are the people in Ditto's voice channel. Staff means a staff
role picked in /setup, Administrator, or the server owner; staff can control the
music from anywhere and never need a vote. The owner — and the user in OWNER_ID
— pass every check.
-
Invite Ditto — replace
YOUR_CLIENT_IDwith your application ID:https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot+applications.commands&permissions=1099800103952That grants View Channels, Send Messages, Embed Links, Attach Files, Read Message History, Manage Channels, Manage Roles, Connect, Speak, Move Members and Timeout Members.
-
Run
/setupand press ⚡ Quick setup. Ditto creates what is missing — an Unverified role, a#verifychannel and a private#ditto-logs— hides every other channel from Unverified, and posts the Verify panel. Existing members keep their access; deleting the Unverified role undoes it all. -
That's it.
/setupalso has pages for the captcha (attempts, pause), the quarantine, the staff roles, and the voice rooms and logs. Auto-detect fills empty settings from common role and channel names and never overwrites a choice.
Ditto's role has to sit above the roles it hands out (Server Settings →
Roles); /setup warns you when it does not.
Needs Node.js 20+ and the Server Members intent (Developer Portal → Bot → Privileged Gateway Intents).
git clone https://github.com/da0t-exe/Ditto.git
cd Ditto
npm install
cp .env.example .env # then put your bot token in it
npm startSlash commands are registered on every server each time the bot starts, so they
show up instantly. On the first start Ditto fetches what music needs, into data/:
a Java 21 runtime (unless Java 17+ is installed), the newest Lavalink 4.x
with its YouTube plugin, and yt-dlp — all kept up to date on their own.
Plan about 1 GB of RAM (Lavalink uses 512 MB). On Linux, Discord's voice encryption (DAVE) needs glibc 2.35 or newer (Debian 12, Ubuntu 22.04 and later).
Pterodactyl: use a Node.js 20+ image (22 recommended) with npm start as the
startup command. To update, set the startup command once to
git fetch origin && git reset --hard origin/main && npm install --omit=dev && npm start,
start, then set it back — .env and data/ are never touched.
| Variable | |
|---|---|
BOT_TOKEN |
The bot's token — required |
OWNER_ID |
A user who passes every permission check |
GUILD_IDS |
Comma-separated servers to run on (empty = all) |
DATA_DIR |
Where state is kept (default data/) |
LAVALINK_HOST · LAVALINK_PORT · LAVALINK_PASSWORD |
Use an external Lavalink node instead of the built-in one |
LAVALINK_MEMORY |
Memory for the built-in Lavalink (default 512M) |
| Script | |
|---|---|
npm start |
Run the bot |
npm run dev |
Run and restart on file changes |
npm run typecheck |
Type-check without running |
npm run selftest |
Offline checks: captcha, message layouts, search ranking, settings, the music queue |
npm run captcha:fetch [n] |
Add up to n more photos per category from Open Images, then /captcha reload |
TypeScript on discord.js 14 (messages laid out with Components V2), run directly
with tsx (no build step), state in SQLite through better-sqlite3, images with
sharp, audio through Lavalink (lavalink-client), with yt-dlp for everything
Lavalink cannot read.
apps/bot/
├── assets/
│ ├── captcha/ The photo database: photos, their boxes, and credits
│ └── fonts/ Roboto, used to draw the captcha
└── src/
├── index.ts Client, intents, per-server command registration
├── env.ts .env loading
├── core/ Settings, SQLite, dispatcher, permissions, logs, layout helpers
├── features/
│ ├── setup.ts /setup, quicksetup.ts the one-click setup, help.ts /help
│ ├── captcha/ Photo database, reCAPTCHA renderer, challenges, verification flow
│ ├── music/ Built-in Lavalink, search and links, player, player message, lyrics
│ └── voice/ Voice commands, locks, rooms, auto-AFK, voice log
└── scripts/ selftest, captcha:fetch, music-check, lavalink-check, music-lab, play-lab
.github/workflows/ release.yml: publishes a GitHub release from CHANGELOG.md (Actions → Release)
- Features each export their slash commands, button and menu handlers, event
listeners and a per-server start hook. Buttons carry ids like
captcha:ok:<id>ormusic:skip, and the dispatcher routes them by prefix. - Captcha:
render.tsdraws the reCAPTCHA card — the parts that never change once, then only the photo and the title per challenge.grid.tspicks the least-shown photo, crops, mirrors and tints it, works out which of the 16 squares hold the object, and keeps a stock of ready challenges.build.tsbuilds photo databases from Open Images, preferring street scenes. - Music:
search.tssuggests songs as a name is typed, turns text or a link into tracks — asking yt-dlp and Lavalink in turn, so that one of them failing is not the end of it — and refuses links to the host's own network.player.tshands each track to Lavalink, through the direct stream address yt-dlp finds or a downloaded copy when that fails, and prepares what comes next.views.tsdraws the player. - Storage (
data/, git-ignored):ditto.dbholds settings, captcha attempts, locks and rooms;lavalink/holds Java, Lavalink and its config;bin/holds yt-dlp;captcha/holds photos added withcaptcha:fetch.
MIT. The Ditto artwork is a fan drawing of a Pokémon © Nintendo / Creatures / GAME FREAK and is not covered by this license. The captcha photos come from Open Images, each under CC BY 2.0 — authors are listed in apps/bot/assets/captcha/CREDITS.md. The captcha's layout reproduces Google reCAPTCHA's look; Ditto is not affiliated with Google. The Roboto font is under the SIL Open Font License 1.1, the icons are Material Icons (Apache 2.0), music plays through Lavalink, and lyrics come from LRCLIB.

