Changelog

What's changed

Every release, in order. See About for the story behind Downl.one.

All notable changes to Downl.one are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and since v26.10.22 this project uses [Date Versioning](https://datevers.ing) (YY.MM.DD, the UTC release date, with -1, -2… for later releases the same day); earlier releases used Semantic Versioning. This is Downl.one's third rebuild — see Earlier history at the bottom for the full story, and Before this changelog below for this codebase's own early development.

v26.10.25-2

Changed
  • Video thumbnails now come through Downl.one (api/thumb.php) instead of loading in the visitor's browser straight from YouTube, X and the rest, so those sites never see the visitor. Only images Downl.one signed, from public addresses, up to 5 MB, are passed on, and nothing is stored
  • Ordinary visitors get no cookies at all: a PHP session (and its cookie) is now only started for the admin preview it exists for
  • The privacy and cookies policies now match what the site does: a new Cloudflare section (the site runs behind Cloudflare, the download form uses Turnstile, and YouTube and X requests leave through Cloudflare's WARP network), how links needing a signed-in viewer are fetched as Downl.one's own accounts, that thumbnails come through Downl.one, that error reports go through an email provider, that server logs normally show Cloudflare's address rather than the visitor's, and the strictly necessary admin-session and Cloudflare security cookies
Security
  • The download proxy, the merger and the new thumbnail proxy only fetch the public internet. A host must resolve to public addresses only (loopback, private, link-local, carrier-grade NAT, IPv6-local, localhost and .internal names are refused), each request is pinned to the address that was checked, only http(s) on the standard ports is allowed, and redirects are followed by hand with every hop checked again (includes/NetGuard.php)
  • Media, merge and thumbnail links are signed with a random secret made once per server (storage/proxy.key), instead of a key derived from the server's name, with longer signatures, and each kind of link has its own
  • yt-dlp's generic extractor, which reads whatever page it's given, is switched off for lookups and playlists, except for the four sites that need it (Kwai, Streamff, fb.watch and v.redd.it links). A redirect from an allowed site, such as a t.co link, can no longer lead yt-dlp to an arbitrary page. A redd.it link's redirect is read rather than followed, and only a Reddit address is used
  • The rate limits now go by the visitor's real address only when the request really comes from Cloudflare, and never by X-Forwarded-For, which visitors can set themselves; the admin sign-in limit could be sidestepped that way

v26.10.25-1

Added
  • Error reports by email: when a lookup fails because of a fault on Downl.one's side, the real error is emailed to the alert address set on Admin → Config, with the link that failed, where in the code it happened, the version and the time, but nothing about the visitor (no IP address or browser details). The same fault is emailed at most once an hour, saying how many more times it happened in between, and no more than 10 error emails go out an hour (includes/ErrorReport.php)
  • Plain-English messages for more problems that are about the link rather than Downl.one: private, deleted or unavailable videos, links that aren't to a video (profiles, channels, searches), videos blocked in the server's country, and live streams or premieres that haven't started
Changed
  • Faults on Downl.one's side no longer show the raw technical error (such as python3: can't open file '/usr/local/bin/yt-dlp': [Errno 13] Permission denied). The visitor now sees "Sorry, something went wrong on our side while fetching this link. We've been told about it and will look into it, so please try again a little later." Problems with the link itself still get their own explanation, and YouTube blocking the server as a bot is both explained to the visitor and reported
  • The privacy policy now says that a link which fails because of a fault on Downl.one's side is included in the error report to the team, and what else the report does and doesn't contain
  • Portrait videos are labelled by their shorter side, as video resolutions normally are: a 1080×1920 X video is now 1080p rather than 1920p (and 720p, 480p, 320p rather than 1280p, 852p, 568p)
Fixed
  • On phones, the "Last used" badge squeezed the format it marked: it sat between the format's details and the Save button, on a row that stays on one line, so the size broke in two ("5.6 / MB") and the badge overlapped the MP4 tag. It now sits next to the resolution, and the size never breaks across lines
  • X video titles ended with the post's own https://t.co/… link, which cluttered them and could stretch a narrow screen. Those links are now left out of the title
  • tests/test_php_pages.sh used the same default port as Stuxs.Tools' copy (8098), so running both at once sent one project's requests to the other's server and failed. Downl.one's now uses 8097

v26.10.25

Fixed
  • Downloads failed with "python3: can't open file '/usr/local/bin/yt-dlp': Permission denied" after bin/update-ytdlp.sh installed a new yt-dlp. The script downloads to a mktemp file (mode 600) and marked it executable with chmod +x, which gives 711: runnable, but readable only by root, and yt-dlp is a Python zip app that has to be read to run. Its own check of the new version runs as root, so it passed, and every earlier weekly run had found nothing new to install, so this only showed when yt-dlp 2026.10.21 came out. The new binary is now installed with mode 755
  • bin/update-ytdlp.sh is now executable in the repo itself, as the cron job runs it directly; the server's checkout had been carrying that as a local change, which would have blocked a deploy that changed the script

v26.10.23-1

Fixed
  • CONTRIBUTING.md said the project has no automated test suite, right after the line describing it. It now says to run PHPUnit and tests/test_php_pages.sh before committing, and to also try the download paths you touched on a local server, since the tests don't cover real downloads

v26.10.23

Changed
  • CONTRIBUTING.md's manual smoke-test advice now just says to check the pages you touched on a local PHP server

v26.10.22-8

Added
  • Discord component embeds, as on Stuxs.Tools: links to Downl.one pasted into Discord show a custom preview in Downl.one green instead of the standard card. The homepage shows what Downl.one does and every highlighted platform, with Download something, FAQ and Stux.Dev buttons; the old per-platform pages (/tiktok, /twitter and so on) show "Download from …" with the platform's note; other pages show their name and description. It's built server-side (discord_* helpers in includes/helpers.php, set per view as $discordEmbed), left out if it would ever pass Discord's 3,000-byte limit, and the Open Graph tags remain as the fallback. Page names and descriptions are escaped so Discord shows them as plain text

v26.10.22-7

Added
  • A full favicon set, built from the icon by bin/make-icons.php: /favicon.ico (16, 32 and 48 px) at the root, 32 and 192 px PNGs, a 180 px apple-touch-icon.png on the site's dark background (iOS fills a transparent one with black), a maskable 512 px icon for Android home screens, and a web manifest (/site.webmanifest). Every page, the admin pages included, now carries the same icon tags (icon_tags() in includes/helpers.php)
Fixed
  • The favicon was missing in places, and on mobile. Only a single 512 px PNG was declared, with no sizes; there was no /favicon.ico, which browsers, bookmarks and search engines ask for whatever the page says (it was a 404); the admin pages had no icon at all; and there was no web manifest for Android to take its icons from

v26.10.22-6

Fixed
  • A second click while a sign-in session was starting (e.g. *Sign in* straight after *Add account*) restarted the session with a new password, so noVNC answered the link already opened with "password check failed". Asking for the session that's already open now leaves it alone (in the admin page and in the root request handler), and *Sign in* and *Add account* stay disabled until a starting session is ready

v26.10.22-5

Fixed
  • Removing a browser account while its sign-in session was open deleted the profile under Chrome's feet, and Chrome wrote it back as it closed, leaving an orphaned profile. A profile in use is now left until the session closes, and profiles no account owns are cleaned up by the daily check and the Automated accounts page
  • The Automated accounts page could show the sign-in link a moment before the remote browser was reachable. The session is now announced only once noVNC is listening, and the VNC server's and noVNC's own messages go to the service's journal (journalctl -u 'downlone-signin@*') instead of being discarded

v26.10.22-4

Added
  • Automated accounts for YouTube as well as X, on a new Admin → Automated accounts page that replaces Admin → X accounts (the old address redirects). YouTube accounts are a last resort, used only when YouTube still blocks an anonymous lookup as a bot or hides an age-restricted video; X accounts are used for posts X hides from logged-out visitors, as before
  • Sign in once, in a browser on the server. Each account can be its own Chrome profile on the server, signed into through a remote browser session (a virtual display with noVNC, listening on localhost only and reached through an SSH tunnel, with a password made up for each session and a 30-minute limit). yt-dlp then reads the session straight from the profile, so there are no cookies to copy. The sign-in service is in deploy/signin/ with an installer; pasting cookies remains available as a fallback
  • The daily check (bin/check-accounts.php, replacing bin/check-x-accounts.php, which still works) opens each browser account's profile so the site can refresh the login, reads whether it's still signed in and when the login expires, and tests every account. Alert emails cover YouTube accounts too, including a warning 14 days before a login expires
  • OUTBOUND_PROXY setting: YouTube and X lookups and YouTube media downloads can leave through a proxy, such as Cloudflare WARP in proxy mode, instead of the server's own datacenter IP, which YouTube increasingly blocks as a bot. Lookups and downloads always use the same exit, since YouTube's media links only work from the IP that asked for them
  • api/health.php reports YouTube and X accounts separately (youtube_accounts, x_accounts) and whether the outbound proxy is on
  • INSTALL.md covers setting up the proxy, a JavaScript runtime and the bgutil PO token provider for yt-dlp, and the sign-in browser
Changed
  • When YouTube blocks the server as a bot, the error now says so, instead of yt-dlp's raw message or "This video is age-restricted or private", which the native lookup used to show for YouTube's bot check
Fixed
  • Account ids made only of digits (about one in 15) became integer array keys, which broke comparisons with them; ids now always contain a letter
Security
  • deploy/ is blocked from the web like bin/ and storage/ (in .htaccess, the dev server and the nginx example). The sign-in request handler that runs as root is installed as a root-owned copy rather than run from the site's directory, which the site's user can write to

v26.10.22-3

Changed
  • Admin alert emails are now a branded HTML email (the Downl.one logo, an "Admin alert" label, the message with clickable links, and an *Open the admin panel* button), with the plain-text version still included for mail clients that don't show HTML

v26.10.22-2

Changed
  • Production runs PHP 8.3, so that's now the baseline everywhere instead of 7.4: INSTALL.md, README.md, CONTRIBUTING.md, DEV_GUIDE.md, composer.json (php >=8.3), the nginx example's PHP-FPM socket, and api/health.php's PHP check (now 8.3+)
  • CI lints, runs PHPUnit and requests every page on PHP 8.3 (it ran the main jobs on 8.1 and a separate production job on 7.4)
  • dev-server.sh / dev-server.bat use PHP_BIN or the php on your PATH, and only warn when it's older than 8.3, instead of looking for a PHP 7.4 install and warning about anything else
Fixed
  • composer.lock had been out of date since v3.12.0, when composer.json's PHP requirement changed without the lock being refreshed, so composer install warned about it on every CI run
  • dev-server.bat showed a blank PHP version (and so always warned) when PHP_BIN was a full path to php.exe
Removed
  • includes/compat.php, the PHP 7.4 polyfills for str_contains(), str_starts_with() and str_ends_with(), which are built into PHP 8
  • CI's separate "PHP 7.4 (production)" job

v26.10.22-1

Fixed
  • Switching on Cloudflare Turnstile (now a tickbox on the admin Config page) would have left the download form and admin sign-in without a challenge, failing every check: the site's Content-Security-Policy blocked Cloudflare's Turnstile script and frame. It now allows challenges.cloudflare.com for both. If you run nginx, copy the updated header into your server block too (INSTALL.md section 6)

v26.10.22

Added
  • X (Twitter) posts marked as sensitive or age-restricted can now be downloaded, the same way cobalt does it. X hides these from logged-out visitors: both its guest API and its embed API return an empty placeholder, although the video files themselves are public. When a post comes back hidden, Downl.one now asks again as a logged-in X account, then through X's embed API, which still serves some posts the guest API drops. Without an account, behaviour is unchanged apart from the extra embed API attempt
  • Admin → X accounts page for those accounts: paste a logged-in account's cookies (never its password), and it's tested straight away against an age-restricted post. Shows each account's status (working, rate-limited, logged out / expired, can't see sensitive media), when it was last checked and used, and its expiry date; the cookies are stored in storage/x-accounts.json and only a masked auth_token is ever shown back
  • Every request saves the cookies X refreshes, so accounts stay logged in without being re-added. With several accounts, the least recently used working one is picked, a rate-limited one sits out for 15 minutes, and a logged-out one is skipped
  • Alert emails to the admin (ALERT_EMAIL) when an X account stops working, can't see sensitive media, or is 14 days from its auth_token expiry date, once per problem. They're sent through any SMTP server (or PHP's mail()) via a small built-in SMTP client, configured on the admin Config page (new ALERT_EMAIL, MAIL_FROM and SMTP_* settings), with *Send a test email* buttons there and on the X accounts page
  • bin/check-x-accounts.php, a daily cron job that tests every X account and checks expiry dates, so a dropped login is noticed even on days nobody downloads a hidden post (INSTALL.md section 7)
  • X account status on the admin dashboard and in api/health.php (x_accounts, not fatal to the overall status)
  • The admin Config page now also edits the site URL, the alert email / SMTP settings and Cloudflare Turnstile (switch and keys). The SMTP password and Turnstile secret key are write-only: never shown back, and an empty box keeps the current one. Settings an older config.php doesn't have yet are added to it on save. The yt-dlp and ffmpeg paths stay hand-edited only, since they're run as shell commands
Changed
  • Downl.one now uses [Date Versioning](https://datevers.ing) instead of Semantic Versioning. Versions are the UTC release date as YY.MM.DD, with -1, -2… for further releases the same day, so this release, which would have been v3.15.0, is v26.10.22 and follows v3.14.11. commit.sh / commit.bat refuse a VERSION.md that isn't a valid Date Version
Security
  • Settings entered on the admin Config page can't contain line breaks, which could otherwise have injected extra headers into alert emails
  • The local dev server now refuses requests for storage/, includes/ and bin/, as .htaccess already does in production, since storage/ now holds X account cookies. It previously served them as plain files (e.g. storage/hits/hits.json)

v3.14.11

Changed
  • The logos and icons in the Markdown docs (README and the like) follow GitHub's light or dark theme, using each brand's logo-light/logo-dark and icon-light/icon-dark files

v3.14.10

Changed
  • The logos and icons in the Markdown docs (README and the like) follow GitHub's light or dark theme, using each brand's logo-light/logo-dark and icon-light/icon-dark files

v3.14.9

Fixed
  • X (Twitter) videos were all listed as "Audio only" even though they have sound. yt-dlp gives X's direct MP4s no video or audio codec fields at all, and a missing codec was read as "no video"; a missing codec now means "unknown", so a format with a resolution or a video container counts as video, and as video with sound unless it's explicitly marked as silent. X videos now show as Video & Audio at each resolution (1080p, 720p, 360p…) instead of as audio named http-10368 and the like
  • YouTube's 360p format with sound (when Downl.one reads YouTube directly) was listed as "Video only", because only an audio/ MIME type counted as having sound. A video MIME type that lists an audio codec too (avc1…, mp4a…) is now Video & Audio
  • X posts that X hides from logged-out visitors (marked as sensitive or age-restricted, from protected accounts, or deleted) failed with yt-dlp's raw "No video could be found in this tweet". X returns an empty placeholder for these instead of the post, so Downl.one can't fetch them without an X login; it now says so in plain English, and also covers posts that genuinely have no video

v3.14.8

Changed
  • The footer's Created-with icons (heart, code, coffee) no longer carry a vertical-align nudge, on the main site and on the maintenance and coming-soon pages; they keep their optical sizes

v3.14.7

Changed
  • The highlighted platforms are laid out for phones: the "35 highlighted platforms" badge and "Plus 1000+ more" line are centred under the heading, each platform shows its note again (cut to two lines, since there's no hover tooltip on a phone), icons sit in a fixed column so the names line up, and very narrow screens (≤340px) get a single column

v3.14.6

Fixed
  • The line between a result's two columns stopped part-way down (where the thumbnail and title ended); it now runs the card's full height
  • The thumbnail no longer pulls away from the top of the card while the page scrolls past it

v3.14.5

Added
  • A note across the bottom of each result: "Need this in a different format? You can use our partner platform Stuxs.Tools to format it after download.", linking to [Stuxs.Tools](https://stuxs.tools)
Changed
  • Each format's file type (MP4, WebM, M4A…) is shown as a badge next to its size instead of as ".mp4" text

v3.14.4

Changed
  • The three download modes are called Video & Audio (the default), Video only and Audio only, matching the labels on the formats themselves (which now read "Video & Audio" instead of "Video + Audio")

v3.14.3

Changed
  • The download mode has three options: Video (with audio) (the default), Video (no audio) and Audio only, each listing only that kind of format. Video (with audio) used to list video-only formats too
  • On phones the three mode buttons are equal tiles with the icon above the label, so no label wraps unevenly

v3.14.2

Changed
  • Results are laid out in two columns on desktop and tablet: the thumbnail, title and channel on the left, the formats on the right. On phones they stack in one column with the title and channel centred
  • The whole thumbnail shows (16:9, portrait thumbnails fitted rather than cropped) instead of a short cropped strip
  • On phones each format stays on one line (type and quality on the left, Save on the right) instead of putting Save on a line of its own
  • The format type labels share one width, so the resolutions line up down the list
Fixed
  • The results (and error messages) sat right under the "By continuing, you agree to…" line; they now have room above them

v3.14.1

Changed
  • The Video / Audio only switch and Playlist mode now sit under the URL bar in a collapsed Advanced options section, so the link gets the bar's full width. While it's collapsed, the label names any option that's switched on (e.g. "Advanced options · Audio only")
  • Video mode lists only Video + Audio and Video only formats, and Audio only mode only audio formats (whichever there are, if a link has none of the chosen kind)
  • The chosen mode is remembered in your browser
Fixed
  • Switching between Video and Audio only did nothing until the link was fetched again; it now re-filters the formats on screen straight away
  • The "Last used" star lit up both the Video + Audio and the Video only row of the same resolution, and never appeared for formats whose size has a decimal point (e.g. "80.5 MB")

v3.14.0

Added
  • Video + Audio downloads at every YouTube resolution up to 4K. YouTube only serves sound and picture together up to 360p, so each resolution now pairs the best video with the best audio in the same container (MP4 + M4A, or WebM + Opus) and [api/merge.php](api/merge.php) joins them with ffmpeg as they download: no re-encoding, nothing kept on the server. H.264 is preferred (plays everywhere), then AV1, then VP9. At most MAX_CONCURRENT merges run at once; past that, visitors are asked to try again in a minute
  • FFMPEG_PATH in config.example.php (default /usr/bin/ffmpeg), and an ffmpeg entry in /api/health.php
Changed
  • The built-in YouTube lookup uses YouTube's visionOS client, which returns every format up to 4K as direct links. If it ever stops working, links fall back to yt-dlp as before; INSTALL.md explains how to update it from yt-dlp's client list
Fixed
  • The built-in YouTube lookup failed on every video (its Android client had been retired), so every YouTube link took the slower yt-dlp path
  • YouTube downloads ran at roughly 0.7 MB/s, because YouTube throttles one long request. They're now fetched in 10 MB ranges, which YouTube serves at full speed (about 10 MB/s and up), and seeking/resuming still works
  • With yt-dlp, YouTube's video-only formats were dropped whenever a combined format existed, leaving only 360p

v3.13.7

Changed
  • Formats are listed best first (Video + Audio, then Video only, then Audio only, tallest or highest bitrate first within each), and the list keeps the top 12 video and top 5 audio formats instead of the first 15 of everything, so audio is never pushed off the end
  • YouTube (and TikTok, SoundCloud, X, LinkedIn) show their proper names on the platform badge instead of "Youtube"
Fixed
  • YouTube results only offered the 15 lowest-quality video-only formats (144p–360p, no audio) when the fallback extractor was used
  • Storyboard thumbnails no longer appear as "Audio only · 0kbps" formats, and HLS playlist entries (which downloaded a playlist file rather than the media) are no longer listed when direct files exist
  • Quality labels line up on the left instead of drifting with the width of the size text

v3.13.6

Fixed
  • Fetching a URL no longer fails with "getFormatPref is not defined": the helpers that remember your last-used format (and mark it "Last used" on the next result) were called but never defined

v3.13.5

Changed
  • The site's timezone is one line in config.php (date_default_timezone_set('Europe/London'), UK time by default), in its own Timezone section at the top; change it there and every date and time the site shows follows

v3.13.4

Changed
  • The default timezone in config.example.php is UK time (Europe/London) instead of UTC
Fixed
  • The admin pages labelled every time "UTC" whatever the configured timezone; they now show the real zone (e.g. BST or GMT)

v3.13.3

Fixed
  • The Content Security Policy's img-src allows blob: images, so previews the browser builds from a file or download (URL.createObjectURL) are no longer blocked

v3.13.2

Fixed
  • Link preview images (og.php) returned a 500 error on PHP 8: imagedashedline() was called with an extra argument that PHP 7 ignored

v3.13.1

Changed
  • The copyright line reads Stux.Group instead of Stux Group Ltd
Fixed
  • The footer's Created-with icons are optically sized, so the heart no longer looks bigger than the code and coffee icons

v3.13.0

Added
  • /sitemap (an HTML page in the site layout listing every public page) and /sitemap.xml (for crawlers), built from one page list in includes/sitemap_pages.php with the fixed site URL and each page's source-file modification date as lastmod; the page test checks both
  • A Created with love / code / coffee by Stux.Dev line in the footer, on the coming-soon and maintenance pages too
  • In dev mode, ?banner=soon,maintenance,site previews the other site banners (production ignores it)
Changed
  • The footer links to the Sitemap next to Boring Legal Stuff
  • The dev-mode and admin-override banners are the shared Stux site banner (assets/css/site-banner.css + assets/js/site-banner.js): a muted tint of each variant's accent with a faint icon pattern and a label chip. The header and mobile menu move down by the banners' measured height, so nothing is covered or cut off, even when a banner wraps on a phone
  • The footer's Stux.Dev logo, "A Stux.Dev Service" and the Powered by Stuxedo badge are grey and dimmed until hovered or focused, on every page including coming-soon and maintenance, and the hover animates smoothly
  • The footer uses Stuxedo's text badge instead of the rectangle one, and the Stux.Dev logo and the badge are both 28px tall
  • The copyright line uses Font Awesome's copyright icon instead of the © sign (screen readers still read "Copyright")
  • dev-server.sh/.bat run on PHP 7.4, like production (PHP_BIN, then php74/php7.4, then %LOCALAPPDATA%\Programs\PHP\7.4), and warn when only another version is available
Fixed
  • Turning on coming-soon or maintenance mode crashed every page with "Call to undefined function asset()": those pages were shown before includes/helpers.php was loaded. It now loads first
  • The maintenance page's theme button showed its icon markup as text after it was clicked

v3.12.1

Fixed
  • /api/health's PHP check reported PHP 7.4 as not OK (it required PHP 8); it now passes on 7.4 and newer, the versions Downl.one supports
  • The page smoke test required /api/health to answer 200, but it rightly answers 503 when yt-dlp isn't installed, as on CI runners, so the new PHP 7.4 CI job failed; it now accepts 200 or 503 there and still fails on any PHP error in the response

v3.12.0

Added
  • A PHP 7.4 (production) CI job: production runs PHP 7.4, so CI now lints every file, runs the PHPUnit suite (on PHPUnit 9.6) and requests every page on PHP 7.4, alongside the existing PHP 8.1 jobs
  • tests/test_php_pages.sh, the page smoke test that job runs: it serves the site with PHP's built-in server (using config.example.php when there's no config.php, with DEV_MODE on and coming-soon/maintenance mode off) and fails on an unexpected status or any PHP error, warning, notice or deprecation on every page, including /legal and its six sub-pages
  • includes/compat.php, which provides str_contains(), str_starts_with() and str_ends_with() on PHP 7.4 (skipped on PHP 8, where they're built in)
Changed
  • composer.json, the README, CONTRIBUTING, INSTALL and DEV_GUIDE now say PHP 7.4+ instead of 8.1+; the INSTALL nginx example uses the PHP 7.4 FPM socket
  • Tests that run in a separate process carry a @runInSeparateProcess annotation next to the attribute, so PHPUnit 9.6 (PHP 7.4) honours it too; the test bootstrap only loads vendor/ when PHPUnit isn't already loaded
Fixed
  • The site didn't run on PHP 7.4: 14 calls to PHP 8's str_contains/str_starts_with/str_ends_with (in the downloader, the proxy, the admin rate-limit page and the router) had no fallback, and the YouTube thumbnail lookup passed an expression to end(), which PHP 7.4 rejects outright

v3.11.9

Added
  • HelpersTest coverage for the changelog section ordering
Changed
  • The /changelog page (render_changelog() in includes/helpers.php) now sorts each release's ### sections into a fixed order — Added, Changed, Fixed, Removed, Security, Deprecated — at render time, rather than trusting the order CHANGELOG.md lists them in; unknown section types go last
  • Changelog type badges now use the fixed family palette — Added #2ecc71, Changed #3ba7ff, Fixed #ffa64d, Removed #ff4d4d, Security #b06bff, Deprecated #8a8a94 — as tinted badges (coloured text on a light tint of the same hue), with darker variants of each for the light theme
  • CHANGELOG sections reordered to Added, Changed, Fixed, Removed, Security, Deprecated

v3.11.8

Changed
  • Footer/coming-soon credit changed from "A Stux.Dev Project" to "A Stux.Dev Service" (linking to services.stux.dev instead of projects.stux.dev) — Downl.one isn't a StuxieDev, Ridgwell Family, or Stux.Dev Labs project, so it gets the standard service credit like the rest of the family.

v3.11.7

Changed
  • README.md's tagline was wrapped in bold — changed to the ### *italic* heading style used for taglines elsewhere in the family.

v3.11.6

Changed
  • The coming-soon and maintenance splash pages' icon used fa-user-shield for both — changed to fa-rocket (coming soon) and fa-screwdriver-wrench (maintenance) so each page's icon matches its meaning.

v3.11.5

Fixed
  • The remaining Stux.Group mentions missed by v3.11.4: LICENSE.md's copyright holder now names the actual legal entity, Stux Group Ltd; CONTRIBUTING.md's "is a Stux.Group project" line and every admin-panel footer (admin/*.php) now credit Stux.Dev, linking to https://stux.dev.

v3.11.4

Fixed
  • The Imprint page's Operator section incorrectly said Downl.one is "built and maintained by Stux.Group" directly — it's operated by Stux.Dev, which is in turn operated by Stux Group Ltd (company no. 13160574). Corrected the Operator section to name Stux.Dev.
  • The legal contact address across views/legal-imprint.php, legal-cookies.php, legal-opt-out.php, and legal.php was [email protected] — unified to [email protected] to match the other Stux.Dev-operated products. [email protected] (general contact) is unchanged.
  • The "Powered by" footer badge (index.php) and the About/coming-soon pages named and linked Stux.Group directly — now read "A Stux.Dev Project" (linking to https://projects.stux.dev), with the brand logo itself linking to https://stux.dev and pointing at the new https://global.media.stux.dev/logo.png. The About page's creator card was updated the same way (name, avatar icon, handle link, bio text), and its "Powered by Stuxedo" mentions now link as one phrase instead of just the word "Stuxedo".
  • The coming-soon page's brand credit was text-only, unlike Stuxs.Tools' equivalent — added the same small Stux.Dev logo image and the Stuxedo rectangle badge image alongside the text.

v3.11.3

Fixed
  • README.md, index.php's footer, and views/about.php's creator card Stux.Group logo/icon URLs had a leftover duplicated /global/ path segment (global.media.stux.group/global/logo.png and /icon.png) — corrected to https://global.media.stux.group/logo.png and /icon.png

v3.11.2

Fixed
  • HelpersTest::testRenderChangelogH3BecomesH4 still asserted the pre-v3.11.0 plain <h4> output for ### Added/### Changed/etc. headings, so CI has been failing on every push since the v3.11.0 pill-badge change to render_changelog() landed — updated the test to assert the changelog-label/changelog-label-suffix badge markup instead, and split it into separate cases for the badge, the suffix, and non-category headings (which still become <h4>)

v3.11.1

Fixed
  • The self-hosted Gontserrat/Creato Display/JetBrains Mono @font-face url()s in assets/css/app.css were the one remaining static asset without any cache-busting query — everything else on the site (CSS/JS entry points, logos, icons) already goes through asset()'s ?v=<version>, but a static .css file can't have a PHP-computed version injected into it. Added a manual ?v=1 to each font url() instead, to be bumped only when a font file itself is replaced.

v3.11.0

Added
  • The changelog page's ### Added/### Changed/### Fixed/### Removed/### Deprecated/### Security section headings (parsed by render_changelog() in includes/helpers.php) now render as colored pill badges — green/blue/amber/red/red/pink respectively — instead of plain uppercase gray text, matching the badge treatment on TIGHC's changelog page. A trailing parenthetical on a heading (e.g. ### Added (Turnstile)) still renders, shown as muted text next to the badge. New .changelog-label/.changelog-label-* rules in assets/css/app.css.

v3.10.6

Added
  • asset() helper in includes/helpers.php, modeled on the one already used by Stuxs.Tools — returns /assets/<path>?v=<version>
Changed
  • Every hardcoded /assets/... reference (favicons, FontAwesome, the self-hosted Gontserrat/Creato Display @font-face fonts, logo.png, icon.png, the Stuxs.Tools cross-promo icons, app.css, app.js) now goes through the new asset() helper instead of a bare path, so every static asset gets ?v=<version> and browser/CDN caches invalidate on every release instead of serving a stale file indefinitely — brings this project in line with Stuxs.Tools' existing convention

v3.10.5

Changed
  • Footer's "Built & Maintained by Stux.Dev" now links to https://github.com/StuxDev instead of https://stux.dev, matching the GitHub-org link convention used for the other brand footers (StuxAPIs, Stux.Cloud, Stuxedo)

v3.10.4

Changed
  • Footer's version number is now a link to /changelog, replacing the separate "Changelog" link that used to sit in the footer's link list

v3.10.3

Fixed
  • "Built and maintained by Stux.Group" (intro sentence and Credits section) corrected to Stux.Dev — Downl.one is made by Stux.Dev, which is part of the Stux.Group Brand of Companies, not built directly by Stux.Group itself; the closing brand-of-companies disclaimer now reflects this too

v3.10.2

Added
  • A "Downl.one is part of the Stux.Group Brand of Companies" line to README.md's License section, matching the disclaimer already rolled out across the StuxAPIs repos

v3.10.1

Fixed
  • Changelog intro didn't state the Keep a Changelog format or that versions follow Semantic Versioning, despite VERSION.md/commit.sh/commit.bat already enforcing MAJOR.MINOR.PATCH and the entries below already following the Keep a Changelog Added/Changed/Fixed structure — added both lines

v3.10.0

Added
  • DEV_GUIDE.md — a dedicated local-development guide (cloning, config.php, the dev server and its flags, yt-dlp — it's optional, the dev server handles it — and verifying the install via api/health.php), split out of CONTRIBUTING.md's old "Local setup" section
  • A "Fixing a bare PHP install" section in DEV_GUIDE.md for a manual winget/scoop-style PHP install that ships with no php.ini at all (no extensions, no CA bundle for cURL's SSL verification)
  • README.md now has a dedicated Contact section ([email protected] / [email protected]); the same addresses were added to CONTRIBUTING.md and as a "still stuck?" line at the end of INSTALL.md/DEV_GUIDE.md's troubleshooting tables — the site itself already used these addresses everywhere, the docs just didn't mention them
Changed
  • CONTRIBUTING.md's "Local setup" section is now a short pointer to DEV_GUIDE.md/INSTALL.md instead of duplicating setup steps inline (and no longer implies yt-dlp needs installing manually for local dev)
  • README.md/INSTALL.md now cross-link to DEV_GUIDE.md
Fixed
  • api/proxy.php called urldecode($_GET['url']), but PHP already URL-decodes $_GET once — this double-decode corrupted both the HMAC signature check and the actual fetch URL whenever the underlying media URL contained a percent-encoded character in its own query string, which is true for effectively every real CDN URL (confirmed via git history: present since v2.7.0, so likely broken in production too, not just locally)
  • .gitignore was missing /storage/hits/ (present on Stuxs.Tools' equivalent file but never added here), so the runtime-generated hit-counter file showed up as an untracked change after any local session
  • INSTALL.md's version header had been stuck at v3.5.2 for the last 8 releases (v3.6.0–v3.8.4) — it's meant to be bumped on every release (and was, consistently, up through v3.5.2) but nothing enforces that; bumped to current

v3.9.0

Changed
  • Cross-branded Stuxs.Tools assets and colors updated to match its new logo/icon/accent: replaced the stale stuxs-tools-icon.svg with stuxs-tools-icon.png, added stuxs-tools-logo.png, and updated every hardcoded #7C6EFF reference to the new #6B00A2 across app.css and about.php
  • About page's "sister project — Stuxs.Tools" card now shows the full Stuxs.Tools logo image, matching the simpler logo-only treatment Stuxs.Tools already uses for its own Downl.one card, instead of a separate icon + name + URL header block
Fixed
  • Cleaned up mojibake-prone em dashes in commit.sh/commit.bat/bin/update-ytdlp.sh comments and echo/log text, matching the same cleanup applied to Stuxs.Tools' scripts

v3.8.4

Changed
  • The dev-mode/admin-override banner icon now sits inline with the title text ("Development Mode", "Coming Soon Mode", "Maintenance Mode") instead of floating beside the whole two-line block
  • The coming-soon and maintenance pages now use the same icon as the admin-override banner instead of a generic download icon

v3.8.3

Changed
  • Updated the site logo (assets/img/logo.png) to the new design
  • commit.sh/commit.bat no longer add a commit-authorship trailer
Fixed
  • With DEV_MODE on, the homepage showed two PHP warnings ("Constant YTDLP_PATH already defined" and a resulting "session_start(): Session cannot be started after headers have already been sent") — config.php's DEV_MODE error-display toggle ran before the later YTDLP_PATH define, which dev-server.sh's router also pre-defines; moved the toggle to the end of the file so it can't unmask that harmless collision again

v3.8.2

Fixed
  • commit.sh/commit.bat had a hardcoded version and message left over from the v3.5.2 release, so they'd tag any future commit as "v3.5.2" if run — now read the version from VERSION.md dynamically, so they can't go stale like this again. Also backfilled the missing v3.6.0–v3.8.1 git tags this staleness had caused to be skipped.

v3.8.1

Fixed
  • dev-server.sh/.bat weren't actually forcing DEV_MODE on, so the dev banner and local Stuxs.Tools links required manually flipping DEV_MODE in config.php every time — they now force it on by default for every run, with --no-dev-mode to opt out and test production behavior locally

v3.8.0

Added
  • Dedicated /faq page — 10 common questions in an accordion, linked from the header, footer and mobile nav
  • /platforms redirects to the platform grid on the homepage
  • Legacy per-platform pages from the old site (e.g. /tiktok, /snapchat) now show a "this has moved to the homepage" page instead of a bare 404 — covers renamed platforms too (/twitter → X)
  • "Boring Legal Stuff" added back to the mobile nav (it had been dropped entirely)
Fixed
  • The "Why Downl.one" feature card icons (VPS-powered speed, Zero data stored, Video & audio, platform count) had a full icon-color palette defined in CSS that was never applied to the markup, so they rendered in flat default text color — now a consistent green accent
  • Footer's Stux.Group logo and Stuxedo badge were noticeably small (14px/20px) — bumped to 18px/26px

v3.7.0

Added
  • Development mode (DEV_MODE in config.php) now shows a small banner on every page and points Stuxs.Tools links at your local dev server — port set via STUXSTOOLS_DEV_PORT, overridable per-run with dev-server.sh/.bat --sister-port <port>
  • ?admin_token=<token> now bypasses coming-soon/maintenance mode for the rest of the browser session, with a banner marking the override while it's active; ?admin_preview=off clears it
  • ADMIN_TOKEN is now a 128-character token that regenerates itself automatically every ADMIN_TOKEN_ROTATE_HOURS (3h floor enforced regardless of what it's set to) — rewritten straight into config.php so the current token is always readable there. New ADMIN_TOKEN_DISABLED option fully blocks token-based bypass even if a token leaks
Added(admin panel)
  • A full admin panel: dashboard (environment status, admin-token rotate/disable, analytics snapshot), analytics (platform/type/preference breakdowns, request→download conversion rate), config editor, cache clear, a rate-limit viewer, and a yt-dlp health check
  • Anonymous, aggregate-only download analytics (storage/hits/hits.json, fed by api/info.php and api/proxy.php) — no IPs or per-visitor data ever recorded
  • Optional Cloudflare Turnstile challenge on the admin login form (TURNSTILE_ENABLED in config.php) — off by default, fails closed on any verification error
  • Every admin page now shows a footer with the current version, copyright range, and a Stux.Group credit, plus Changelog/About links
Added(Turnstile)
  • The homepage download form can also require a Cloudflare Turnstile challenge (same TURNSTILE_ENABLED switch) — the Download button stays disabled until the widget completes, and api/info.php rejects the request server-side if the token is missing or invalid
Fixed
  • The dev-server router pre-defined its CLI-flag overrides before checking whether the request was for a real file, so any direct request to admin/*.php or api/*.php collided with config.php's own define() calls ("constant already defined" warnings) — the overrides now only run when actually falling through to index.php

v3.6.0

Added
  • A consent line above the homepage paste box: "By continuing, you agree to our Terms and Ethics of Use"
  • Dedicated /changelog, /donate and /settings pages — the changelog is no longer folded into the About page, and Settings holds real quality/codec/bitrate download preferences
  • Header, footer and mobile nav now link to Changelog, Donate and Settings; the standalone "Boring Legal Stuff" nav link was replaced by these
Changed
  • Settings page preferences (max quality, YouTube codec, max audio bitrate) are now real — saved to localStorage and sent with every download request, which filters the returned stream list server-side. A preference is skipped rather than applied if it would leave no streams at all
  • Selecting "Audio only" now actually filters results down to audio streams
  • Consolidated the two separate, independently-wired mobile navigation panels into one — removes a duplicate hamburger button and ~90 lines of dead drawer CSS
  • The paste-a-link box now stacks vertically on mobile (input, then Video/Audio toggle, then a full-width Download button) instead of cramming everything onto one row
  • Theme toggle button widened from 34px to 38px
  • Mobile nav icons for Donate and Settings given their own colors (rose/indigo), matching the existing blue/teal/amber set
  • A full-width divider now separates "More free tools at Stuxs.Tools" from the rest of the mobile nav, matching the header divider
  • .input-mode-toggle no longer stretches full-width or carries an unneeded bottom margin on mobile — it stays sized to its content
Fixed
  • Mobile nav's "About" link read "About Downl.one" instead of just "About"
  • setMode() and the playlistMode variable were referenced by the download-mode and playlist-mode UI but never defined, so switching to Audio-only or toggling playlist mode silently did nothing
  • Between 641–768px, neither the desktop nav links nor the mobile hamburger button was visible — the two breakpoints that hide/show them didn't match
  • The Download button collapsed into an isolated circular icon on mobile once the paste box was stacked; it's now a full-width labeled button
  • The paste box's mobile padding was asymmetric, left over from a duplicate rule at a different breakpoint

v3.5.2

Added
  • A little something for the curious. You'll know it when you find it.

v3.5.1

Changed
  • More spacing below the homepage's Stuxs.Tools partner card before "Supported Platforms" (32px → 80px)
Fixed
  • The footer's "More tools at Stuxs.Tools" link wasn't rendering in Stuxs.Tools' purple — .footer-nav a's color rule has higher CSS specificity than the plain .footer-stuxs class, so it silently won regardless of what was set on .footer-stuxs. Rescoped to .footer-nav .footer-stuxs so the whole link (icon, lead-in text, and bold name) renders in purple with a purple-tinted background/border

v3.5.0

Added
  • Header rebuilt to match Stuxs.Tools' layout: centered container, logo left, icon+text nav center (Platforms, About, and a new Boring Legal Stuff link, each with a distinct icon color), theme toggle right — the standalone "More tools at Stuxs.Tools" header button is gone (footer and mobile nav links are unaffected)
  • A purple "Partner platform" card promoting Stuxs.Tools on the homepage, mirroring the existing Downl.one card on Stuxs.Tools' site
  • The hero heading's accent text ("Fast, free, private.") is now an animated gradient, with a plain-color fallback via @supports and animation: none under prefers-reduced-motion
  • Legal hub page ("Boring Legal Stuff"): its 6 sections now show in a responsive grid (3 columns on desktop, 2 on tablet, 1 on mobile) with each icon colored distinctly, and the icon sits to the right of the text — built on new standalone .legal-grid/.legal-card classes, fully independent of the shared .features-grid/.feature-card system
Changed
  • The download button's icon is now Downl.one's own icon rendered in white, instead of a generic FontAwesome download glyph
  • Added spacing above the playlist mode toggle — it was sitting flush against the input row
Fixed
  • The URL input box showed a "double border" on focus — a box-shadow ring stacked on top of the border-color change, and a separate native :focus-visible outline landing on the <input> itself. Both removed; only a clean border-color change remains

v3.4.1

Changed
  • About page: the Stuxs.Tools card's icon now sits above its name/URL/description again instead of beside it
Fixed
  • The download button's loading spinner (#btnSpin) was showing permanently regardless of loading state — its .spin class set display: inline-block unconditionally, which overrode the browser's default [hidden] { display: none } rule for the same element. Scoped the rule to .spin:not([hidden]) so it only displays while actually loading

v3.4.0

Added
  • Self-hosted Creato Display (headings/logo) and Gontserrat (body) fonts — the Google Fonts CDN <link> is gone entirely; JetBrains Mono is now self-hosted too
  • Every platform icon is now colored with that service's real brand color instead of a single flat icon color
  • Easter egg: holding Shift swaps the X icon and label for the old Twitter bird and label, reverting when Shift is released
  • Kickstarter added as a supported platform (kickstarter.com, ksr.com), bringing the highlighted platform count to 35
  • About page: the Stux.Group and Stuxedo cards are now themed with each company's real brand color (sampled directly from their logos), and links inside those cards — plus the Stuxs.Tools card — now inherit the matching brand color instead of the site's default link color
  • About page: the Stuxs.Tools card now shows an icon, name, and URL, matching the Stuxedo card's layout, instead of a bare description paragraph
Changed
  • The Stuxs.Tools button in the header, footer, and mobile nav now uses Stuxs.Tools' own purple brand color instead of Downl.one's green
  • Footer: the About link moved next to "Boring Legal Stuff" instead of sitting next to the Stuxs.Tools button
  • Dailymotion, OK.ru, and Odysee now use their real Font Awesome brand icons instead of generic fallback icons
  • Headings that directly follow an about-page info card now sit close to it instead of inheriting the same 2rem top margin used for regular prose headings
  • coming-soon.php's "N+ more platforms" copy is now computed from the actual platform count instead of a hardcoded number
  • Font Awesome upgraded from 7.2.0 to 7.3.1 (self-hosted)
Fixed
  • The link-box placeholder text on the homepage no longer overflows its box

v3.3.0

Added
  • dev-server.sh / dev-server.bat — local dev server scripts that start PHP's built-in server with a router shim (so static assets actually load), auto-bootstrap config.php from config.example.php on first run, and support --no-coming-soon / --no-maintenance flags to bypass those modes for a single run without touching config.php. They also detect whether a working yt-dlp is on PATH and, if not, download a copy into .dev-ytdlp/ (gitignored) and use it for that run — regardless of YTDLP_PATH in config.php
  • null_device() helper in includes/helpers.php — picks NUL or /dev/null based on the OS
Changed
  • Every hardcoded https://stuxs.tools literal (8 occurrences across index.php and four view files) now uses the existing STUXSTOOLS_URL constant instead — the constant was already defined but wasn't actually being used anywhere
Fixed
  • api/health.php and api/info.php both shelled out to yt-dlp with 2>/dev/null, which silently breaks under Windows' cmd.exe (no /dev/null) — this was causing health.php to report yt-dlp's version as empty even when it was working correctly. Both now use null_device()

v3.2.3

Added
  • The about page's "The name" section now shows the real Downl.one icon instead of a generic FontAwesome download icon
  • The "sister project — Stuxs.Tools" card now shows the real Stuxs.Tools icon (was missing entirely)
Changed
  • The about page's "Who built it" section no longer credits the personal "Stux / StuxieDev" handle — it now credits Stux.Group throughout (name, bio, and creator-card icon), consistent with how the rest of the site attributes the project
  • The about page header corrected from "A project by Stuxedo" to "A Stux.Group project · Powered by Stuxedo" — Stuxedo hosts the project, it doesn't own it
  • The coming-soon.php credit line changed from "Built by Stux.Group · Hosted by Stuxedo" to "A Stux.Group Project · Powered by Stuxedo", matching the footer's established phrasing

v3.2.2

Added
  • CONTRIBUTING.md — local setup, project conventions, and how to add platform support, linked from README.md
Changed
  • Brand name stylization made consistent throughout: "Downl.one" and "Stuxs.Tools" everywhere either is referred to in prose, page titles, meta tags, alt text, and OG image text (URLs, email addresses, User-Agent strings, and CSS/JS identifiers are unaffected — those stay lowercase, since they're literal technical values, not stylized references)
  • DOWNLONE_NAME now defined as 'Downl.one' (was 'downl.one')

v3.2.1

Changed
  • The version number itself now lives in VERSION.md (a bare version string, safe to commit) — version.php is now just a thin reader (trim(file_get_contents(...))) instead of hardcoding the version
  • config.php no longer pulls in version.php — each real entry point (index.php, api/health.php) requires it directly instead, keeping deployment config and release version decoupled

v3.2.0

Added
  • Changelog now rendered on the about page via a small dependency-free Markdown renderer
  • config.example.php as the committed config template — config.php itself is now gitignored, never committed
  • version.php split out of config.php to hold just the version constant (safe to commit; config.php requires it)
Changed
  • All DOWNL_ constants renamed to DOWNLONE_ throughout, and the STUXS_URL cross-reference constant renamed to STUXSTOOLS_URL
  • "Hosted & provided by Stuxedo" card now shows Stuxedo's real icon instead of a generic server icon; "Stuxedo by Stux.Cloud" corrected to just "Stuxedo" throughout about.php
  • og.php now composites the real logo.png wordmark instead of drawing "DOWNL.ONE" as text
  • INSTALL.md updated for the new config.example.php / version.php setup step
Fixed
  • The mobile menu was completely non-functional at every screen width — the same missing @media (min-width: 769px) wrapper bug as Stuxs.Tools, leaving .mobile-drawer and .mobile-overlay permanently display: none !important

v3.1.0

Added
  • "Boring Legal Stuff" hub at /legal, linking to six documents: Privacy Policy, Terms and Ethics, Cookies Policy, Imprint, Disclaimer, and Opt-Out Preferences (previously just Privacy and Terms, now moved under /legal/)
Changed
  • Credited to Stux.Group throughout (previously StuxieDev/Stux); copyright range corrected to 2021–present
  • Footer restructured: version number is now followed by a "Boring Legal Stuff" link, and the Stux.Group / Stuxedo attribution reads "A Stux.Group Project" alongside the Powered by Stuxedo badge
  • Header, coming-soon, and maintenance pages now use the real logo image instead of constructing a logo from an icon plus coloured text
  • Stuxs.Tools now shown with its real icon everywhere it's referenced (header, footer, mobile nav) instead of a generic bolt icon
Fixed
  • The mobile menu's logo was missing a wrapping element the other two logo instances had, causing a stray gap in "DOWNL.ONE"

v3.0.0

Added
  • README.md and a new INSTALL.md covering VPS deployment, yt-dlp installation, web server config, and a going-live checklist
  • bin/update-ytdlp.sh — downloads and verifies the latest yt-dlp release before replacing the pinned binary, intended for a weekly cron; INSTALL.md documents pinning a specific version instead if you'd rather not auto-update
  • api/cache-prune.php cron script for rate-limit file cleanup
  • Documentation on the YouTube InnerTube client key/version and why it occasionally needs manual rotation
Fixed
  • Every 404 response was returning HTTP 200 instead of 404
  • The native YouTube path didn't fall back to yt-dlp when the InnerTube API returned a bad response (the exact failure mode a rotated InnerTube key causes) — it would silently "succeed" with nothing playable instead
  • A PHP Error (not just an Exception) in the native YouTube/Reddit paths would crash the request instead of falling through to yt-dlp
  • The stream proxy always reported HTTP 206 whenever a Range request came in, regardless of whether the upstream host actually honoured it — could mismatch and break video seeking

Before this changelog

Development before v3.0.0 wasn't tracked with version numbers in this document. For reference, roughly in order: the initial build (35+ highlighted platforms, feature parity with the well-known "cobalt" downloader, HMAC-signed stream proxy so nothing is written to disk); an audio-only download mode with a live progress bar and a /api/health endpoint; security hardening alongside sitemap.xml/robots.txt; playlist support via yt-dlp's flat-playlist mode with per-item format memory; the official icon and logo; and long-cache, immutable headers for static assets.

Earlier history

Downl.one has existed in three very different forms:

  • v1.0.0 — the original Downl.one: a basic standalone download script, not a website
  • v2.0.0 — a separate, now-retired WordPress site (frontend theme + backend)
  • v3.0.0 onwards — this codebase: a standalone, dependency-free PHP application (no CMS, no Composer, own router), unrelated to either of the above