From f9d4efe9612c379630d6c0d5e477461f2b973c10 Mon Sep 17 00:00:00 2001 From: alexey-shary Date: Sun, 8 Feb 2026 22:51:08 -0800 Subject: [PATCH] 910: when YouTube feed fails display link to the channel --- internal/glance/widget-videos.go | 50 +++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/internal/glance/widget-videos.go b/internal/glance/widget-videos.go index ff7986403..9a16471d6 100644 --- a/internal/glance/widget-videos.go +++ b/internal/glance/widget-videos.go @@ -12,7 +12,11 @@ import ( "time" ) -const videosWidgetPlaylistPrefix = "playlist:" +const ( + videosWidgetPlaylistPrefix = "playlist:" + videosWidgetChannelIDNameDelim = "@" // channel format: "channelId@channelName" (e.g. UCXuqSBlHAE6Xw-yeJA0Tunw@linustechtips); name is used as fallback link when feed fails + placeholderThumbnail = "https://i3.ytimg.com/vi/QMKM4d1ly88/hqdefault.jpg" // TODO: find a better placeholder +) var ( videosWidgetTemplate = mustParseTemplate("videos.html", "widget-base.html", "video-card-contents.html") @@ -138,19 +142,30 @@ func (v videoList) sortByNewest() videoList { return v } -func fetchYoutubeChannelUploads(channelOrPlaylistIDs []string, videoUrlTemplate string, includeShorts bool) (videoList, error) { - requests := make([]*http.Request, 0, len(channelOrPlaylistIDs)) +// splits "channelId@channelName" into (id, name). If no delimiter, returns (s, ""). +func parseChannelIDAndName(s string) (id, name string) { + i := strings.Index(s, videosWidgetChannelIDNameDelim) + if i < 0 { + return s, "" + } + return s[:i], strings.TrimSpace(s[i+len(videosWidgetChannelIDNameDelim):]) +} + +func fetchYoutubeChannelUploads(channelOrPlaylistEntries []string, videoUrlTemplate string, includeShorts bool) (videoList, error) { + requests := make([]*http.Request, 0, len(channelOrPlaylistEntries)) + + for i := range channelOrPlaylistEntries { + id, _ := parseChannelIDAndName(channelOrPlaylistEntries[i]) - for i := range channelOrPlaylistIDs { var feedUrl string - if strings.HasPrefix(channelOrPlaylistIDs[i], videosWidgetPlaylistPrefix) { + if strings.HasPrefix(id, videosWidgetPlaylistPrefix) { feedUrl = "https://www.youtube.com/feeds/videos.xml?playlist_id=" + - strings.TrimPrefix(channelOrPlaylistIDs[i], videosWidgetPlaylistPrefix) - } else if !includeShorts && strings.HasPrefix(channelOrPlaylistIDs[i], "UC") { - playlistId := strings.Replace(channelOrPlaylistIDs[i], "UC", "UULF", 1) + strings.TrimPrefix(id, videosWidgetPlaylistPrefix) + } else if !includeShorts && strings.HasPrefix(id, "UC") { + playlistId := strings.Replace(id, "UC", "UULF", 1) feedUrl = "https://www.youtube.com/feeds/videos.xml?playlist_id=" + playlistId } else { - feedUrl = "https://www.youtube.com/feeds/videos.xml?channel_id=" + channelOrPlaylistIDs[i] + feedUrl = "https://www.youtube.com/feeds/videos.xml?channel_id=" + id } request, _ := http.NewRequest("GET", feedUrl, nil) @@ -163,13 +178,26 @@ func fetchYoutubeChannelUploads(channelOrPlaylistIDs []string, videoUrlTemplate return nil, fmt.Errorf("%w: %v", errNoContent, err) } - videos := make(videoList, 0, len(channelOrPlaylistIDs)*15) + videos := make(videoList, 0, len(channelOrPlaylistEntries)*15) var failed int for i := range responses { if errs[i] != nil { failed++ - slog.Error("Failed to fetch youtube feed", "channel", channelOrPlaylistIDs[i], "error", errs[i]) + id, name := parseChannelIDAndName(channelOrPlaylistEntries[i]) + slog.Error("Failed to fetch youtube feed", "channel", id, "error", errs[i]) + if name != "" { + handle := strings.TrimPrefix(strings.TrimSpace(name), videosWidgetChannelIDNameDelim) + channelLink := "https://www.youtube.com/@" + handle + videos = append(videos, video{ + ThumbnailUrl: placeholderThumbnail, + Title: "Visit channel", + Url: channelLink, + Author: handle, + AuthorUrl: channelLink + "/videos", + TimePosted: time.Now(), + }) + } continue }