35 Commits

Author SHA1 Message Date
3099244614 feat: add 'Ask' quality option, language auto-select, and m4a audio remux
All checks were successful
CI / build (push) Successful in 47s
- Add 'ask' option to audio/video quality settings (stored as '' in DB)
- Auto-select when both media type AND quality are set; show filtered
  format picker when quality is Ask
- Auto-skip language picker when user has a matching language preference
- Remux audio-only downloads to m4a instead of webm for Telegram compat
- Update README to document the new flow and m4a audio output
2026-06-28 12:17:08 +03:30
d0be877c0e fix: make cookies.txt writable so yt-dlp can save refreshed cookies
All checks were successful
CI / build (push) Successful in 46s
2026-06-28 12:03:44 +03:30
315107839c chore: remove cookies.txt from repo, add to gitignore
All checks were successful
CI / build (push) Successful in 50s
2026-06-28 12:01:01 +03:30
4af8a95af6 chore: add COOKIES_FILE to docker-compose environment 2026-06-28 12:00:53 +03:30
b11b5649a5 feat: add auto-select from preferences and type picker
All checks were successful
CI / build (push) Successful in 52s
- Add MediaTypeAsk (0) as the default, mapping old video_audio to Ask
- Show Video/Audio/Ask type picker when no default media type is set
- Auto-select matching format and skip picker when defaults are configured
- Add autoSelectVideoFormat / autoSelectAudioFormat helpers with closest-match
- Add buildFilteredFormatList for type-filtered format lists
- Add Media Type setting in settings UI (Ask/Video/Audio)
- Add --merge-output-format mp4 for video downloads via yt-dlp
- Fix formatLabelForDisplay to show Height fallback and Extension
- Add cookies volume mount to docker-compose.yml
- Document cookies setup and new features in README
2026-06-28 11:28:53 +03:30
6bbc87a7c8 fix: prevent context cancellation from blocking file delivery on cancel race
All checks were successful
CI / build (push) Successful in 35s
When a download completes but the user clicks Cancel at the same time,
handleDownloadCompletion's Telegram calls used the cancelled download
context and failed. Use context.Background() for all notification calls
so the file is always delivered regardless of download state.
2026-06-26 13:01:25 +03:30
d0291d1e9e refactor: remove all playlist support, fix message ordering
All checks were successful
CI / build (push) Successful in 54s
- Remove playlist handler, keyboard, and all related callback routing
- Strip playlist parameter from YouTube Music radio URLs, reject pure playlists
- Send media preview before format selection prompt (correct ordering)
- Remove DetectPlaylist/GetPlaylistInfo methods (no longer needed)
- Remove IsPlaylist, PlaylistCount, PlaylistEntries from MediaInfo
- Remove PlaylistState struct, keep PlaylistEntry for search results
- Update README to reflect removal of playlist feature
2026-06-26 12:56:10 +03:30
3f56ffbba9 fix: resolve download failures and duplicate messages, add fast playlist detection
All checks were successful
CI / build (push) Successful in 39s
- Remove --audio-multi-streams flag (not supported by Alpine yt-dlp 2026.03.17)
- Reuse the fetching message as the format selection message to avoid duplicate text
- Add DetectPlaylist method for fast flat playlist detection before expensive GetMediaInfo
- This avoids stalls on YouTube Music radio URLs with large playlists
2026-06-26 12:39:11 +03:30
f5204b9554 fix: show bitrate for audio-only formats, filter subtitle languages, improve feedback
All checks were successful
CI / build (push) Successful in 50s
- Prioritize IsAudioOnly check in formatLabelForDisplay so bitrate
  is shown instead of yt-dlp's generic 'audio only' resolution
- Only collect languages from formats with audio/video to prevent
  subtitle-only tracks from triggering the language selection prompt
- Show 'Fetching metadata...' message before GetMediaInfo and edit
  with results, improving UX for URL input
- Add test coverage for audio-only format labels
2026-06-26 12:12:33 +03:30
464f20a46c feat: bundle hanime-plugin and Deno JS runtime in Docker image
All checks were successful
CI / build (push) Successful in 56s
- Add PIP_BREAK_SYSTEM_PACKAGES=1 to install hanime-plugin via pip
- Install Deno JS runtime required by the plugin's extractors
- Document plugin setup and JS runtime requirement in README
- Note plugin support in feature list
2026-06-26 01:27:10 +03:30
609237f4e3 fix: resolve 8 architecture flaws across download, playlist, and webhook
All checks were successful
CI / build (push) Successful in 52s
- Add --continue and --max-filesize to yt-dlp for download resilience
- Route thumbnail downloads through HTTP_PROXY
- Show per-video progress (i/N) with cancel for playlist downloads
- Run playlist asynchronously so cancel callbacks can be processed
- Guard handlePlaylistConfirm against concurrent active jobs
- Close Done channel in playlist entry lifecycle
- Add stepState TTL (30 min) to prevent stale states
- Wrap webhook server for graceful shutdown on SIGTERM
- Refactor formatBytes to loop-based implementation
- Show first line of stderr in user-facing error messages
- Fix format pointer reuse in playlist loop
- Add CreatedAt field to stepState for TTL tracking
- Add MaxFileSize and ProgressPrefix fields to DownloadJob
2026-06-26 01:04:34 +03:30
57dffc4194 fix: correct progress display (remove duplicate /s, handle HLS reset)
All checks were successful
CI / build (push) Successful in 47s
2026-06-26 00:46:26 +03:30
58baa1b769 fix: read yt-dlp progress from stdout instead of stderr; add explicit --proxy support
All checks were successful
CI / build (push) Successful in 52s
2026-06-26 00:41:04 +03:30
df08a8840f fix: add --progress flag to force progress in non-TTY pipes
All checks were successful
CI / build (push) Successful in 50s
yt-dlp suppresses progress output by default when not in a TTY.
The --progress flag overrides this. Without it, readProgress sees
only 4 stderr lines (no [download] progress) and reports 0 matches.
This was the root cause of progress always showing 0.0%.
2026-06-26 00:09:41 +03:30
959b7e388d fix: remove --progress-template, parse default [download] output instead
All checks were successful
CI / build (push) Successful in 40s
--progress-template output is suppressed in non-TTY subprocesses
(yt-dlp issue #13649). The default [download] progress format
works reliably in pipes. Changes:

- Remove --progress-template from yt-dlp args entirely
- Keep --newline for newline-delimited output in pipe
- Fix progressRE regex to handle HLS format: ~ with leading
  spaces before size (e.g. '~   1.00KiB')
- Fix speed parsing in [download] fallback to handle multiple
  leading spaces (e.g. 'at    976.20B/s')
- Fix ETA parsing to strip (frag n/m) suffix
- Filter Unknown/N/A speed/eta in [download] fallback
- Add test cases for real HLS fragment output format
2026-06-26 00:01:35 +03:30
7c02cd3d89 fix: progress always at 0% and context cancelled before sendDocument
All checks were successful
CI / build (push) Successful in 50s
- 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
2026-06-25 23:49:27 +03:30
37e7f918d0 refactor: remove container selection, extract helpers, add logging and tests
All checks were successful
CI / build (push) Successful in 51s
- 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
2026-06-25 23:41:26 +03:30
dfe946a41b refactor: split mutex, add download queue, wire context cancellation
All checks were successful
CI / build (push) Successful in 53s
- 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
2026-06-25 23:23:19 +03:30
3290bb55c0 feat: add in-memory media info cache and YouTube search feature
- 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
2026-06-25 22:58:20 +03:30
66a8cd1128 fix: resolve back step, audio labels, progress parsing, file size pre-check, and stale job cleanup
All checks were successful
CI / build (push) Successful in 36s
- 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
2026-06-25 22:45:25 +03:30
8bcb0094b2 feat: treat quota limit of 0 as unlimited
All checks were successful
CI / build (push) Successful in 41s
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.
2026-06-25 22:09:36 +03:30
f39e4e6fb0 fix: quota pre-check used max format size, blocked before format selection
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.
2026-06-25 22:08:05 +03:30
f8f9d99e69 fix: set PYTHONUNBUFFERED=1 to force real-time progress output
All checks were successful
CI / build (push) Successful in 58s
yt-dlp is a Python script. When stderr is a pipe (not a TTY), Python
buffers output, causing progress updates to arrive in large chunks or
only at the end. Setting PYTHONUNBUFFERED=1 forces line-buffered output,
giving real-time progress.
2026-06-25 16:24:07 +03:30
e8120b9529 fix: show first progress update immediately instead of waiting for 5%
- 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%
2026-06-25 16:23:07 +03:30
cd7aaeebc2 feat: capture yt-dlp stderr on download failures for debugging
Add StderrLog field to DownloadJob and collect all stderr lines during
download. Log the captured stderr output when a download fails to help
diagnose exit code 2 errors (geo-restrictions, auth, etc.).
2026-06-25 16:12:03 +03:30
8408b17ca0 feat: add DRM search-by-name fallback for individual Spotify tracks
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.
2026-06-25 16:10:37 +03:30
0c27822871 fix: 'best' format on audio-only sites (SoundCloud) now shows audio containers
All checks were successful
CI / build (push) Successful in 51s
- 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)
2026-06-25 16:06:57 +03:30
785e20bf94 refactor: rename Format to Quality, add container Audio/Video sub-pickers
- Rename 'Format' button to 'Quality' in settings (audio_quality/video_quality)
- Add DefaultAudioContainer and DefaultVideoContainer to UserPreferences
- Container settings now has Audio/Video sub-pickers (mp3/m4a/opus vs mp4/mkv/webm)
- Add migration 003 for new container columns
- Update repo layer and playlist flow for new fields
- Update back routing: back_quality, back_container
2026-06-25 16:03:03 +03:30
e08ed77884 refactor: settings format picker redesign with Audio/Video sub-pickers, fix delete routing
All checks were successful
CI / build (push) Successful in 48s
- Replace Type/Quality settings with single Format button -> Audio/Video sub-pickers
- Each sub-picker shows format options (bitrates for audio, resolutions for video)
- Replace DefaultQuality with DefaultAudioFormat/DefaultVideoFormat in UserPreferences
- Add DB migration 002 for new preference columns
- Update repo layer SQL queries for new schema
- Update playlist flow to use new preference fields
- Fix missing deleteaccount and delaccount_ callback routing
- Add back_format routing for format picker navigation
2026-06-25 15:55:59 +03:30
30a99ac2fd fix: progress stuck at 0%, container not respected, audio display, Spotify DRM fallback
- 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:
2026-06-25 15:50:43 +03:30
e65ade6c3c refactor: redesign format selection with unified list and independent audio/video picking
All checks were successful
CI / build (push) Successful in 52s
- 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
2026-06-25 15:37:47 +03:30
7de79b50e1 chore: add .gitea CI/CD workflows
All checks were successful
CI / build (push) Successful in 1m4s
2026-06-25 15:11:23 +03:30
a7dc5531e4 chore: remove TODO.md from gitignore (file already deleted) 2026-06-25 15:10:27 +03:30
cd27b4d28d feat: add thumbnail preview, audio-only detection, /download command, tests, and bugfixes
- Add thumbnail preview with metadata (title, uploader, duration) before download
- Auto-detect audio-only sites (Spotify, SoundCloud, etc.) and skip type selection
- Add /download command for feature parity with UI button
- Fix yt-dlp defer bug: StatusFailed was overwritten to StatusCompleted
- Fix handleURLInput using editText with msgID=0 (now uses sendWithKB)
- Fix filename detection: use job ID prefix for reliable file finding
- Fix double answerCb between handleBackStep and handleDownloadPrompt
- Fix runDownload unused msgID parameter
- Fix context for download goroutine (use context.Background)
- Remove unused startWebhook h parameter
- Remove dead buildToggleKeyboard function
- Add formatBytes, formatDuration, formatLabelForDisplay tests
- Add IsAudioOnlyContent, MediaType.String tests
- Add repo integration tests (users, preferences, quotas, history, delete)
- Add cmd/bot tests (parseAllowedUsers, env helpers)
- Add edge case tests for progress parser
- Add zero/negative safety guard in formatDuration
- Add .gitea, TODO.md, opencode.json to gitignore
2026-06-25 15:07:31 +03:30
05fcdf7458 feat: initial implementation of uptodownbot
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.
2026-06-25 14:22:48 +03:30