Repository navigation
Releases: da0t-exe/Ditto
Release list
Ditto v0.6.0
The biggest release so far: a captcha that looks exactly like reCAPTCHA, faster
music with a new player, a web dashboard, and Ditto's own pages inside the
Pterodactyl panel. Ditto now speaks English only.
Captcha
- Drawn exactly like a reCAPTCHA 4×4 image challenge: header, grid and footer
measured pixel for pixel, with sixteen buttons laid out like the squares and
Skip turning into Verify once a square is ticked. - A photo database ships with Ditto: 111 hand-picked Open Images street photos
(traffic lights, bicycles, buses, cars, motorcycles, fire hydrants and more)
with the position of every object. Nothing is downloaded or built on first start anymore;
captcha:fetchonly adds extra photos. The photo folder of older versions is
removed on start. - Instant: a stock of challenges is drawn in advance, so the picture shows the
moment Verify me is pressed and a miss swaps it at once. No challenge is
served twice. - Some challenges have nothing to tick: the answer is Skip.
- The number of attempts and the pause after the last miss are settings, and the
member role is optional: the pending role alone can gate the server.
Music
- No suggestion list:
/playtakes a song name or a link and keeps the best
match, leaving out remixes, live and sped-up versions nobody asked for. - Faster: Ditto joins the voice channel while it searches, looks up the next
two tracks ahead of time and remembers stream addresses. - No freezes: nothing Ditto runs in the background (Java checks, archives,
yt-dlp) blocks the bot anymore. - Smoother: Lavalink runs with a larger audio buffer and a low-pause garbage
collector, and backs off if it keeps crashing. - A new live player: cover, a progress bar that moves on its own, up next,
previous, pause, skip, stop, loop, volume, shuffle, queue, lyrics, and a filter
menu that applies live. New:/previousand/play … next. - Links to the host or its local network are refused before anything fetches them.
Setup
/setupopens on an overview with a ⚡ Quick setup button: it creates an
Unverified role,#verifyand#ditto-logs, hides the other channels from
Unverified and posts the Verify panel. Deleting the role undoes it./helplists what Ditto can do.
Web dashboard
- Served by Ditto itself, on the server's own port on Pterodactyl
(SERVER_PORT), elseDASHBOARD_PORTor 3000. - Every
/setupsetting, Quick setup and Auto-detect; captcha statistics over
14 days; the music player and its queue; locked channels; the live activity log. - Log in with the admin password printed in the console (or
DASHBOARD_PASSWORD),
or with a one-time link from/dashboard, which opens only that Discord server
for its staff.
Pterodactyl and Luna
- Luna theme:
extras/luna/install.shadds a Ditto group to each server's
sidebar, under Overview: Overview, Captcha, Music, Voice, Bot settings, Logs.
The pages are built from Luna's own cards, stat blocks, switches and dialogs,
in your theme's colours, and work on HTTPS panels. - No password in the panel: anyone with console access is logged in to Ditto
by the panel, through a one-time code sent to the server's console. Ditto only
accepts a code from its own console, once, within a minute, and keeps it out of
the console output.DASHBOARD_CONSOLE_LOGIN=0turns this off. - The installer asks once which eggs run Ditto, keeps what you change in the
theme editor, and must be run again after each Luna update.--removerestores
the panel's files exactly. - Stock panels and Blueprint: see
extras/pterodactyl.
Also
- English only: the French translations and the language setting are gone.
- npm 12: the install scripts Ditto needs (better-sqlite3, ffmpeg-static,
esbuild) are listed inallowScripts, so a fresh install with npm 12, which
blocks unlisted ones, still works, and npm 11 stops warning. - The dashboard returns an invite link, shown when the bot is in no server yet.
Upgrading
git fetch origin && git reset --hard origin/main && npm install --omit=dev && npm start
.env and data/ are kept. The captcha photos built by older versions are
removed on the first start (they now ship with Ditto). For the Luna pages, run on
the panel machine:
cd /var/www/pterodactyl && bash /path/to/Ditto/extras/luna/install.shTested
Type-checked, and npm run selftest passes: 60 unique captcha challenges, every
message layout within Discord's limits, the search ranking and the private-link
guard. The bot started on Pterodactyl (Node 22) on four servers: 111 captcha
photos ready, yt-dlp ready, Lavalink 4.2.2 with its YouTube plugin connected.
The Luna pages were built into Luna and used through its sidebar against a
simulated panel, including the automatic login and the password fallback; the
installer was run end to end on a mock panel. They have not yet run on a real
panel.
Ditto v0.5.2
YouTube now plays reliably, and a track that breaks while playing is retried instead of skipped.
Fixed
- YouTube tracks were skipped. Lavalink's YouTube plugin could load a video, then fail once playback began — "All clients failed to load the item… This video requires login" — and the track was dropped, because the yt-dlp fallbacks only covered loading. A playback error now moves the same track to the next route (direct address from yt-dlp, then a downloaded copy); Ditto only gives up when every route fails.
- Events carry the Lavalink track id, so the end event of an abandoned attempt no longer skips the next track.
Changed
- YouTube goes through yt-dlp first. From a home connection, YouTube refused the plugin for 7 of 8 videos while yt-dlp got every one through in about 3 seconds. Lavalink's plugin is now the fallback for YouTube; SoundCloud, Bandcamp, Twitch and direct links still go straight to Lavalink.
- The next track is loaded while the current one plays, so moving through a queue is immediate, and a looped track is replayed without reloading.
- The plugin also tries more sign-in-free YouTube clients:
TVHTML5_SIMPLY,IOS,ANDROID_MUSIC.
New
npx tsx src/scripts/music-lab.ts [link…] resolves links the way /play does and has the built-in Lavalink really play each one, route by route, then reports what worked.
Tested
With music-lab, from the same network as the production server and with SERVER_PORT set as on Pterodactyl, 12 of 13 links played on the first route:
| Source | Route |
|---|---|
| YouTube ×2, YouTube Music searches ×2 | yt-dlp direct |
| Spotify, Deezer, Apple Music tracks | yt-dlp direct (matched on YouTube Music) |
| SoundCloud, Bandcamp, direct MP3 | Lavalink |
| TikTok | downloaded copy |
| Twitch clip | yt-dlp direct, after Lavalink failed |
| X | not verified — the test post no longer exists |
Upgrading
git fetch origin && git reset --hard origin/main && npm install --omit=dev && npm start
Ditto v0.5.1
Fixes the built-in Lavalink on Pterodactyl, where music was unavailable in v0.5.0.
Fixed
- Lavalink never answered on Pterodactyl. The panel sets
SERVER_PORTto the server's public port; Spring Boot reads it as Lavalink's own port and it wins over the config file, so Lavalink listened there while Ditto waited on127.0.0.1:2333and gave up after 90 seconds with "Lavalink did not start in time". Port and address are now passed on the command line, which beats any environment variable, and Lavalink starts without the host'sSERVER_*,SPRING_*and similar variables. - If Lavalink still does not answer, it is stopped instead of being left running in the background.
Changed
- Auto-updates stay on Lavalink 4.x. A new major version could break the client library, so moving to it will be a deliberate release.
- sharp 0.35. 0.33 bundled libvips and libheif versions with known vulnerabilities (GHSA-f88m-g3jw-g9cj, GHSA-rgj7-g3m4-5g8c);
npm auditis clean again.
Upgrading
git fetch origin && git reset --hard origin/main && npm install --omit=dev && npm start
Java and Lavalink downloaded by v0.5.0 are reused.
Note
The fix was checked by running the built-in Lavalink with SERVER_PORT=25567 set, as on Pterodactyl: it started in 25 s and loaded YouTube, YouTube Music and SoundCloud tracks, and both yt-dlp fallbacks played. Playback in a Discord voice channel is still to be confirmed.
Ditto v0.5.0
Music now plays through Lavalink — started by Ditto itself, in the same server. No second server, no custom egg.
Built-in Lavalink
- On first start Ditto downloads a Java 21 runtime (unless Java 17+ is installed) and the latest Lavalink with its YouTube plugin into
data/lavalink, writes the config, and runs it on127.0.0.1with 512 MB of memory. - Once a day it checks for new Lavalink and plugin releases, and restarts onto them as soon as nobody is listening.
- Lavalink 4.2 supports Discord's voice encryption (DAVE), required for bots since March 2026. On Linux it needs glibc 2.35 or newer.
LAVALINK_HOST,LAVALINK_PORTandLAVALINK_PASSWORDpoint Ditto at an external node instead.
Playback that falls back
Each track goes to Lavalink first. If Lavalink cannot load it, yt-dlp finds the direct media address; failing that, yt-dlp downloads a copy that Lavalink plays from disk. TikTok, X and Instagram always take the download route.
New commands
/seek time |
Jump to a moment — 1:30 or 90 |
/filter effect |
Bass boost, nightcore, vaporwave, 8D, karaoke — or none |
Removed
@discordjs/voice, opusscript and libsodium-wrappers: Lavalink handles the voice connection now.
Upgrading
git fetch origin && git reset --hard origin/main && npm install --omit=dev && npm start
The first start takes longer: Java, Lavalink and yt-dlp are downloaded (about 250 MB). Plan about 1 GB of RAM for the bot.
Note
Checked locally against the real services: Lavalink 4.2.2 with YouTube plugin 1.18.2 started and loaded a YouTube video, a YouTube Music track and a SoundCloud track, and both yt-dlp fallbacks — direct address and downloaded copy — played through Lavalink. Playback in a Discord voice channel has not been tested yet.
Ditto v0.4.0
Two big changes: the captcha now looks like reCAPTCHA, and Ditto plays music.
Captcha
- One photo cut into a 4×4 grid, the instruction drawn on the picture — "Select all squares with BUSES" — and sixteen buttons laid out like the squares. The embed carries nothing but the picture.
- The photo pool now keeps the position of every object, so each square is scored: 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.
- Every challenge gets a random zoom, offset and mirror, and is fingerprinted so none is served twice.
- Roboto is bundled, so the instruction renders even on hosts without fonts.
- The pool is rebuilt in the new format on first start, reusing the Open Images annotations already downloaded.
Music
/playsuggests songs from YouTube Music as you type; searches return songs only.- YouTube (Shorts and YouTube Music included), SoundCloud, Bandcamp, TikTok, Twitch, X and Instagram links play directly. Spotify, Apple Music, Deezer and Tidal tracks are matched on YouTube Music, as are Spotify, Apple Music and Deezer albums and playlists.
- A player message with pause, skip, stop, loop and queue buttons and a progress bar.
/skipand/stopgo straight through for the requester, staff or a small room, and by vote otherwise. Also/pause,/resume,/queue,/nowplaying,/volume,/loop,/shuffle,/remove,/clearand/lyrics(LRCLIB).- In this version audio goes yt-dlp → FFmpeg → Discord. v0.5.0 moves playback to Lavalink.
Also
- The invite link now asks for Speak, which music needs.
Note
The lookups — YouTube Music search, Spotify, Apple Music and Deezer links, lyrics — and the captcha self-test were checked. Playback in a voice channel was not tested before v0.5.0 replaced the audio path.
Ditto v0.3.0
Ditto now sticks to two things: voice tools, and the captcha at the door. Everything about cosmetic roles is gone.
Removed
- The colour and game picker shown after the captcha, and
/roles. - Tier titles: Ditto no longer adds or removes
━━ … ━━roles on members.
The only roles Ditto hands out now are the captcha's: member, pending and quarantine.
Changed
- The Roles page of
/setupis now a Staff page, with staff roles only. - Auto-detect finds staff roles from their moderation permissions — Manage Server, Kick, Ban, Timeout or Move Members — instead of a tier title.
- After passing the captcha, members get a short welcome message instead of the role menus.
- The README documents every command with its options, the
/setuppages, the invite permissions and the code layout. The Ditto artwork is back as the logo, with rounded corners and badges in its colours. - License: "Copyright (c) 2026 da0t-exe and the Ditto contributors".
Upgrading
git fetch origin && git reset --hard origin/main && npm install --omit=dev && npm start
Colour, game and tier-title roles stay on your server as ordinary roles; Ditto simply stops managing them. Open /setup and check the Staff page.
Note
Type-checked, and the captcha self-test passes (50 unique, consistent grids). Ditto has still not run against Discord: the first start will be the first end-to-end test.
Ditto v0.2.0
Ditto no longer depends on how one particular server is built. Every server now sets it up from Discord with /setup, and the bot speaks English and French.
/setup
/setup opens a panel with four pages of role, channel and user menus:
| Page | What you pick |
|---|---|
| Verification | Member role, pending role, verification channel |
| Quarantine | Quarantine role, and the bots whose arrivals go to quarantine |
| Roles | Staff roles, colour roles, game roles |
| Voice & logs | Rooms, log channel, voice log and auto-AFK, language |
Auto-detect fills empty settings from common English and French names — Members/Membres, Verification, Quarantine/Quarantaine, logs, tier titles — and never overwrites a choice you already made. A warning shows when Ditto's role sits below a role it has to hand out.
English and French
- Replies follow each person's Discord language; the verification panel and the logs follow the server's. A server can force one language in
/setup. - Command and option names are English, with French names shown to French clients —
/move to:reads/move vers:. - The captcha asks for traffic lights or des feux tricolores.
Other changes
- The role for members brought in by a member-pushing bot is now a configurable quarantine role.
- Tier titles accept any line character:
━,─,═,▬. - Rooms only reset while they are picked in
/setup. - New README: invite link with the exact permissions Ditto needs, role order, and how to put the captcha in front of a server.
- New original logo and banner. The previous artwork was fan art of a Nintendo character.
Upgrading
git fetch origin && git reset --hard origin/main && npm install --omit=dev && npm start
Then run /setup once on each server and check what was detected. Settings saved by v0.1.0 are not carried over — Auto-detect fills them back in.
Command options are now English (vers → to, membre → member…); French clients still see the French names.
Note
Type-checked, and the captcha self-test passes (50 unique, consistent grids). Like v0.1.0, this version has not run against Discord yet: /setup and the translations get their first real test on the first start.
Ditto v0.1.0
First release of Ditto: a Discord bot for the voice side of a server and the door in front of it. It puts a picture captcha at the entrance, makes roles read cleanly on a profile, and gives staff a set of voice tools.
Music is on the way. It will run on Lavalink and is not in this release.
Verification
New members see one channel with a Verify button. It opens a private 3×3 grid of photos — "select every image with traffic lights" — and nine buttons laid out like the grid.
- Photos come from Open Images (CC BY 2.0): traffic lights, bicycles, buses, cars, motorcycles, fire hydrants, stop signs, boats, palm trees, street lights and stairs. Drawings are excluded.
- A tile is a right answer only when the object fills enough of it. A photo where the object shows up small is never used as a wrong answer either, so no tile is a trick question.
- No grid is served twice. Each grid's fingerprint is stored, the least shown photos are picked first, and every tile is cropped, flipped and tinted at random.
- Three wrong answers mean a Discord timeout (10 minutes). Nobody is kicked.
- Members brought in by a member-pushing bot are recognised from Discord's join source and get a hidden role instead of the captcha.
/captcha testruns the real flow without touching your roles, then shows which tiles were expected.
Roles
- After the captcha, members pick a colour and their games from two menus.
/rolesbrings them back. - Tier titles. A role named like
━━ Games ━━titles every role below it. Ditto gives a member the title only while they hold a role in that tier, so a profile reads as clean floors with no empty headings.
Voice
Requires Move Members.
/move |
One member, everyone in voice with a role, or a whole channel |
/gather |
Pull everyone in voice into one channel, with a button to send them all back |
/split |
Shuffle a channel into 2–4 teams across empty rooms |
/disconnect |
A member or a whole channel |
/shake |
Bounce a member through random channels, with a Stop button |
/lock · /unlock · /locks |
Lock a channel, optionally for a while (30m, 2h); members who leave are pulled back |
Rooms. The first person in an empty room owns it and can rename, cap, lock or hand it over with /room. When it empties, it goes back to its original name, limit and permissions.
Members deafened for 10 minutes are moved to AFK, and joins, leaves and moves go to the log channel. Staff are never pulled back by a lock.
Setup
BOT_TOKENis the only required variable.- Slash commands register per server on every start: they appear instantly, with no separate deploy step.
- Roles and channels are found by name;
/setupre-detects them. - On first start the captcha image pool builds itself in the background: about 40 MB of annotations plus the photos, ~35 MB kept in
data/captcha/. Until it is ready, the Verify button tells newcomers that staff will let them in.
git clone https://github.com/da0t-exe/Ditto.git
cd Ditto
npm install
cp .env.example .env
npm startNeeds Node.js 20+, the Server Members intent, and the bot's role at the top of the role list.
Note
Type-checked, and the grid generator was tested locally against a stand-in pool: 50 grids, all unique, every answer consistent with its tiles. This release has not yet run against Discord, and the real Open Images pool has not been built yet — the first start will be the first end-to-end run.