diff --git a/README.md b/README.md
index abf57e38..602ad2e2 100644
--- a/README.md
+++ b/README.md
@@ -149,7 +149,7 @@ Weblate also allows you to log-in with major SSO providers like Github, Gitlab,
- [CloudTube](https://sr.ht/~cadence/tube/): A JavaScript-rich alternate YouTube player.
- [PeerTubeify](https://gitlab.com/Cha_de_L/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists.
- [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A material design music player that streams music from YouTube.
-- [HoloPlay](https://github.com/stephane-r/HoloPlay): Funny Android application connecting on Invidious API's with search, playlists and favorites.
+- [HoloPlay](https://github.com/stephane-r/holoplay-wa): Progressive Web App connecting on Invidious API's with search, playlists and favorites.
- [WatchTube](https://github.com/WatchTubeTeam/WatchTube): Powerful YouTube client for Apple Watch.
- [Yattee](https://github.com/yattee/yattee): Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV.
- [TubiTui](https://codeberg.org/777/TubiTui): A lightweight, libre, TUI-based YouTube client.
diff --git a/assets/css/default.css b/assets/css/default.css
index 1b01c6a7..6159fb14 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -583,3 +583,7 @@ p,
/* Wider settings name to less word wrap */
.pure-form-aligned .pure-control-group label { width: 19em; }
+
+.channel-emoji {
+ margin: 0 2px;
+}
diff --git a/locales/en-US.json b/locales/en-US.json
index 86b83a23..fee0b037 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -190,6 +190,7 @@
"Blacklisted regions: ": "Blacklisted regions: ",
"Music in this video": "Music in this video",
"Artist: ": "Artist: ",
+ "Song: ": "Song: ",
"Album: ": "Album: ",
"Shared `x`": "Shared `x`",
"Premieres in `x`": "Premieres in `x`",
@@ -405,6 +406,7 @@
"YouTube comment permalink": "YouTube comment permalink",
"permalink": "permalink",
"`x` marked it with a ❤": "`x` marked it with a ❤",
+ "Channel Sponsor": "Channel Sponsor",
"Audio mode": "Audio mode",
"Video mode": "Video mode",
"Playlists": "Playlists",
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index 13af2d8b..ce34ff82 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -1,3 +1,5 @@
+private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000}
+
# TODO: Add "sort_by"
def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
response = YT_POOL.client &.get("/channel/#{ucid}/community?gl=US&hl=en")
@@ -108,6 +110,8 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
like_count = post["actionButtons"]["commentActionButtonsRenderer"]["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"]
.try &.as_s.gsub(/\D/, "").to_i? || 0
+ reply_count = short_text_to_number(post.dig?("actionButtons", "commentActionButtonsRenderer", "replyButton", "buttonRenderer", "text", "simpleText").try &.as_s || "0")
+
json.field "content", html_to_content(content_html)
json.field "contentHtml", content_html
@@ -115,6 +119,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
json.field "likeCount", like_count
+ json.field "replyCount", reply_count
json.field "commentId", post["postId"]? || post["commentId"]? || ""
json.field "authorIsChannelOwner", post["authorEndpoint"]["browseEndpoint"]["browseId"] == ucid
@@ -174,9 +179,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
aspect_ratio = (width.to_f / height.to_f)
url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
- qualities = {320, 560, 640, 1280, 2000}
-
- qualities.each do |quality|
+ IMAGE_QUALITIES.each do |quality|
json.object do
json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
json.field "width", quality
@@ -185,10 +188,39 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
end
end
end
- # TODO
- # when .has_key?("pollRenderer")
- # attachment = attachment["pollRenderer"]
- # json.field "type", "poll"
+ when .has_key?("pollRenderer")
+ attachment = attachment["pollRenderer"]
+ json.field "type", "poll"
+ json.field "totalVotes", short_text_to_number(attachment["totalVotes"]["simpleText"].as_s.split(" ")[0])
+ json.field "choices" do
+ json.array do
+ attachment["choices"].as_a.each do |choice|
+ json.object do
+ json.field "text", choice.dig("text", "runs", 0, "text").as_s
+ # A choice can have an image associated with it.
+ # Ex post: https://www.youtube.com/post/UgkxD4XavXUD4NQiddJXXdohbwOwcVqrH9Re
+ if choice["image"]?
+ thumbnail = choice["image"]["thumbnails"][0].as_h
+ width = thumbnail["width"].as_i
+ height = thumbnail["height"].as_i
+ aspect_ratio = (width.to_f / height.to_f)
+ url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
+ json.field "image" do
+ json.array do
+ IMAGE_QUALITIES.each do |quality|
+ json.object do
+ json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
+ json.field "width", quality
+ json.field "height", (quality / aspect_ratio).ceil.to_i
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
when .has_key?("postMultiImageRenderer")
attachment = attachment["postMultiImageRenderer"]
json.field "type", "multiImage"
@@ -202,9 +234,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
aspect_ratio = (width.to_f / height.to_f)
url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
- qualities = {320, 560, 640, 1280, 2000}
-
- qualities.each do |quality|
+ IMAGE_QUALITIES.each do |quality|
json.object do
json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
json.field "width", quality
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index 357a461c..b15d63d4 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -182,7 +182,11 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
json.field "contentHtml", content_html
json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil)
-
+ json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil)
+ if node_comment["sponsorCommentBadge"]?
+ # Sponsor icon thumbnails always have one object and there's only ever the url property in it
+ json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s
+ end
json.field "published", published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
@@ -324,11 +328,21 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
end
author_name = HTML.escape(child["author"].as_s)
+ sponsor_icon = ""
if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool
author_name += " "
elsif child["verified"]?.try &.as_bool
author_name += " "
end
+
+ if child["isSponsor"]?.try &.as_bool
+ sponsor_icon = String.build do |str|
+ str << %()
+ end
+ end
html << <<-END_HTML
#{child["contentHtml"]}
END_HTML @@ -675,6 +690,27 @@ def content_to_comment_html(content, video_id : String? = "") text = "<%= translate(locale, "Artist: ") %><%= music.artist %>
-<%= translate(locale, "Album: ") %><%= music.album %>
-<%= translate(locale, "License: ") %><%= music.license %>
+<%= translate(locale, "Song: ") %><%= music.song %>
+<%= translate(locale, "Artist: ") %><%= music.artist %>
+<%= translate(locale, "Album: ") %><%= music.album %>