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
This commit is contained in:
2026-06-26 12:56:10 +03:30
parent 3f56ffbba9
commit d0291d1e9e
7 changed files with 61 additions and 491 deletions

View File

@@ -151,19 +151,11 @@ func (c *Client) GetMediaInfo(url string) (*domain.MediaInfo, error) {
}
mi := &domain.MediaInfo{
URL: url,
Title: info.Title,
Uploader: info.Uploader,
Thumbnail: info.Thumbnail,
Duration: info.Duration,
IsPlaylist: info.Playlist != "",
}
if mi.IsPlaylist {
mi.PlaylistCount = info.PlaylistCount
mi.PlaylistEntries = c.parsePlaylistEntries(info.Entries)
c.setCache(url, mi)
return mi, nil
URL: url,
Title: info.Title,
Uploader: info.Uploader,
Thumbnail: info.Thumbnail,
Duration: info.Duration,
}
langSet := make(map[string]bool)
@@ -171,8 +163,6 @@ func (c *Client) GetMediaInfo(url string) (*domain.MediaInfo, error) {
for _, f := range info.Formats {
ff := c.convertFormat(f)
mi.Formats = append(mi.Formats, ff)
// Only consider languages from formats with audio or video to avoid
// prompting for subtitle-only or auto-generated caption languages.
if (ff.HasAudio || ff.HasVideo) && ff.Language != "" && !langSet[ff.Language] {
langSet[ff.Language] = true
mi.Languages = append(mi.Languages, ff.Language)
@@ -187,68 +177,6 @@ func (c *Client) GetMediaInfo(url string) (*domain.MediaInfo, error) {
return mi, nil
}
// GetPlaylistInfo fetches a flat playlist listing (fast, no format detail).
func (c *Client) GetPlaylistInfo(url string) (*domain.MediaInfo, error) {
if cached := c.getCached(url); cached != nil {
slog.Debug("cache hit", "url", url)
return cached, nil
}
isPlaylist, mi, err := c.DetectPlaylist(url)
if err != nil {
return nil, err
}
if !isPlaylist {
mi.IsPlaylist = false
mi.PlaylistCount = 0
mi.PlaylistEntries = nil
}
c.setCache(url, mi)
return mi, nil
}
// DetectPlaylist runs a fast flat yt-dlp check to determine if the URL is a playlist.
// It returns basic info without format details. For non-playlist URLs it returns false.
// Does NOT cache the result so a subsequent GetMediaInfo call fetches full format info.
func (c *Client) DetectPlaylist(url string) (bool, *domain.MediaInfo, error) {
args := []string{
"-J", "--flat-playlist", "--no-download",
"--no-warnings",
url,
}
if c.cookiesFile != "" {
args = append(args, "--cookies", c.cookiesFile)
}
if c.proxyURL != "" {
args = append(args, "--proxy", c.proxyURL)
}
slog.Info("detecting playlist", "url", url)
out, err := c.run(args)
if err != nil {
return false, nil, fmt.Errorf("yt-dlp playlist detection failed: %w", err)
}
var rawInfo ytdlpInfo
if err := json.Unmarshal(out, &rawInfo); err != nil {
return false, nil, fmt.Errorf("parse yt-dlp output: %w", err)
}
mi := &domain.MediaInfo{
URL: url,
Title: rawInfo.Title,
Uploader: rawInfo.Uploader,
Thumbnail: rawInfo.Thumbnail,
Duration: rawInfo.Duration,
IsPlaylist: rawInfo.Playlist != "",
PlaylistCount: rawInfo.PlaylistCount,
PlaylistEntries: c.parsePlaylistEntries(rawInfo.Entries),
}
return mi.IsPlaylist, mi, nil
}
func (c *Client) parsePlaylistEntries(entries []json.RawMessage) []domain.PlaylistEntry {
var result []domain.PlaylistEntry
for _, raw := range entries {