- Fix progress template: remove spurious stderr: prefix and switch to
_percent_str/_speed_str/_eta_str keys (progress.percent outputs NA
for HLS streams with unknown total size)
- Update progress regex to match new template format:
DLPROG| 45.2%| 1.05MiB/s|00:45
- Fix context cancellation bug: clearActiveJob was cancelling dlCtx
before handleDownloadCompletion could send the document
- Update progress test cases to match new template output format
- Remove container picker UI, keyboard, settings, and ContainerOptions
entirely — yt-dlp handles container format automatically
- Remove Container field from DownloadJob struct and related references
- Extract buildFormatList helper to deduplicate format list building
between handleURLInput and handleBackStep
- Break runDownload into trackDownloadProgress + handleDownloadCompletion
- Add HTTP timeout (30s) to downloadFile helper
- Log applyQuota and AddHistory errors instead of discarding
- Log nil stepState warnings in all handler entry points
- Include job.StderrLog in download failure messages
- Add tests for buildFormatList, findFormatByID, determineMediaType,
and buildFormatString
- Update README with search, DRM fallback, and quota feature docs
- Remove unused strings import from keyboard.go
- Split single sync.Mutex into per-map mutexes (activeJobs, stepStates,
rateLimiter, pendingInput, cancelFuncs) to reduce contention
- Add download semaphore (channel-buffered, 3 concurrent) as worker pool
- Add cancelFuncs map with context cancellation wired into download
goroutines for clean shutdown
- Replace playlist busy-poll (500ms sleep loop) with synchronous
runDownload + Done channel for completion tracking
- Use full UUID for job IDs instead of 8-char prefix
- Download thumbnail URL to temp file before sending (InputFileUpload)
- Change MAX_FILE_SIZE_MB default from 2000 to 50
- Clean up cancel funcs after normal download completion
- Add in-memory LRU cache (200 entries, 30 min TTL) to yt-dlp Client for
GetMediaInfo and GetPlaylistInfo results, avoiding redundant yt-dlp calls
for the same URL within a session
- Add YouTube (/search) and YouTube Music (/music) search with paginated
results (5 per page), user selects a result to start the download flow
- Add SearchState type, new search.go handler, search results keyboard with
< > navigation and New Search button
- Add Search button to main menu keyboard
- Update help text with new commands
- Fix handleBackStep to redirect to download prompt when step state is nil
(previously silent no-op after container selection cleared the state)
- Improve formatLabel for audio-only entries without bitrate: use ID + extension
instead of generic 'audio', fixing duplicate entries in format list
- Add yt-dlp --progress-template for reliable JSON progress parsing across all
sites (fixes 0.0% progress on sites like xnxx.com where [download] lines
are absent); keep regex parser as fallback for older yt-dlp versions
- Add file size pre-check in handleContainerSelection before download starts
(previously checked only after download completed, wasting bandwidth)
- Clear stale active jobs and step state on /start for a clean slate
Pre-check now skips periods where limit is 0, allowing users to have
unlimited quota by setting daily_limit_mb/weekly_limit_mb/monthly_limit_mb
to 0 in the database.
Changed pre-check to only reject when used >= limit (already exhausted),
not when the max format size alone would exceed the limit. The actual
quota is applied at 50% progress using the selected format's size.
- Always show the first nonzero progress update so users don't see 0% for
extended periods on slow downloads or fragment-based downloads
- Keep 5% throttle for subsequent updates to avoid excessive API calls
- Add readProgress summary logging (lines, matches, stderr length)
- Fix progressKeyboard not being updated until progress >= 5%
When SearchYoutube with the Spotify URL fails for individual tracks,
offer a 'Search on YouTube' button that lets the user enter a search
query (artist/song name) to find the content on YouTube.
- When content is audio-only (IsAudioOnlyContent), 'best' format is marked as
IsAudioOnly so determineMediaType returns MediaTypeAudio
- Previously 'best' was hardcoded with HasVideo:true, causing video containers
(mp4/mkv) to be shown instead of audio containers (mp3/m4a/opus)
- Remove --no-progress from yt-dlp args that suppressed all progress output
- Add --merge-output-format when FormatString is set (container was ignored for combined formats)
- Improve formatLabelForDisplay for audio-only formats missing bitrate (show ID+extension)
- Detect DRM errors (Spotify) and fall back to YouTube Music search via ytsearch1:
- Replace type/quality selection flow with unified format list (video+audio combined)
- Add independent audio/video format picking: video-only formats prompt for audio track,
audio-only formats offer optional video addition (skip when content is audio-only)
- Fix formatKeyboard to use format IDs as callback data instead of display labels
- Remove dead mediaTypeKeyboard function
- Fix double-close panic in readProgress (caller is sole owner of updates channel)
- Add FormatString field to DownloadJob for composite format specs (e.g. '137+140')
- Change quota calculation from sum of all format sizes to max single format size
- Fix 'Delete all data' button to use danger style with proper text
- Clean up routePrefixedCallback: remove type_/quality_, add format_/secondary_ routes
- Fix back navigation to rebuild format IDs list
Telegram bot for downloading media from any site using yt-dlp.
Supports audio/video/both, quality selection, language picker,
container format selection, playlist pagination, progress updates,
per-user quotas, user preferences, download history, cancel at any step,
polling and webhook modes, proxy support, and SQLite persistence.