Skip to content

Releases: da0t-exe/Ditto

Ditto v0.6.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 18:38
d46cdc2

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:fetch only 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: /play takes 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: /previous and /play … next.
  • Links to the host or its local network are refused before anything fetches them.

Setup

  • /setup opens on an overview with a ⚡ Quick setup button: it creates an
    Unverified role, #verify and #ditto-logs, hides the other channels from
    Unverified and posts the Verify panel. Deleting the role undoes it.
  • /help lists what Ditto can do.

Web dashboard

  • Served by Ditto itself, on the server's own port on Pterodactyl
    (SERVER_PORT), else DASHBOARD_PORT or 3000.
  • Every /setup setting, 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.sh adds 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=0 turns 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. --remove restores
    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 in allowScripts, 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.sh

Tested

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

Choose a tag to compare

@da0t-exe da0t-exe released this 23 Sep 22:07

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

Choose a tag to compare

@da0t-exe da0t-exe released this 23 Sep 21:36

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_PORT to 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 on 127.0.0.1:2333 and 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's SERVER_*, 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 audit is 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

Choose a tag to compare

@da0t-exe da0t-exe released this 23 Sep 21:27

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 on 127.0.0.1 with 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_PORT and LAVALINK_PASSWORD point 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

Choose a tag to compare

@da0t-exe da0t-exe released this 23 Sep 21:26

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

  • /play suggests 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.
  • /skip and /stop go straight through for the requester, staff or a small room, and by vote otherwise. Also /pause, /resume, /queue, /nowplaying, /volume, /loop, /shuffle, /remove, /clear and /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

Choose a tag to compare

@da0t-exe da0t-exe released this 23 Sep 17:32

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 /setup is 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 /setup pages, 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

Choose a tag to compare

@da0t-exe da0t-exe released this 23 Sep 17:32

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

Choose a tag to compare

@da0t-exe da0t-exe released this 23 Sep 17:32

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 test runs 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. /roles brings 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_TOKEN is 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; /setup re-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 start

Needs 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.