summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/css/default.css24
-rw-r--r--config/config.example.yml10
-rw-r--r--locales/cs.json2
-rw-r--r--locales/es.json21
-rw-r--r--locales/ko.json15
-rw-r--r--src/invidious/channels/channels.cr36
-rw-r--r--src/invidious/comments.cr73
-rw-r--r--src/invidious/database/users.cr2
-rw-r--r--src/invidious/helpers/utils.cr53
-rw-r--r--src/invidious/routes/watch.cr6
-rw-r--r--src/invidious/trending.cr19
-rw-r--r--src/invidious/user/imports.cr2
-rw-r--r--src/invidious/videos/description.cr53
-rw-r--r--src/invidious/videos/parser.cr2
-rw-r--r--src/invidious/yt_backend/extractors_utils.cr13
15 files changed, 179 insertions, 152 deletions
diff --git a/assets/css/default.css b/assets/css/default.css
index 42f6958f..23649f8f 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -322,6 +322,30 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
/*
+ * Comments & community posts
+ */
+
+#comments {
+ max-width: 800px;
+ margin: auto;
+}
+
+.video-iframe-wrapper {
+ position: relative;
+ height: 0;
+ padding-bottom: 56.25%;
+}
+
+.video-iframe {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: none;
+}
+
+/*
* Footer
*/
diff --git a/config/config.example.yml b/config/config.example.yml
index 8abe1b9e..7ea80017 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -817,6 +817,16 @@ default_user_preferences:
## Default: true
##
#vr_mode: true
+
+ ##
+ ## Save the playback position
+ ## Allow to continue watching at the previous position when
+ ## watching the same video.
+ ##
+ ## Accepted values: true, false
+ ## Default: false
+ ##
+ #save_player_pos: false
# -----------------------------
# Subscription feed
diff --git a/locales/cs.json b/locales/cs.json
index 0e8610bf..d9e5b4d5 100644
--- a/locales/cs.json
+++ b/locales/cs.json
@@ -13,7 +13,7 @@
"Previous page": "Předchozí strana",
"Clear watch history?": "Smazat historii?",
"New password": "Nové heslo",
- "New passwords must match": "Hesla se musí schodovat",
+ "New passwords must match": "Hesla se musí shodovat",
"Cannot change password for Google accounts": "Nelze změnit heslo pro účty Google",
"Authorize token?": "Autorizovat token?",
"Authorize token for `x`?": "Autorizovat token pro `x`?",
diff --git a/locales/es.json b/locales/es.json
index 09f510a7..68ff0170 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -398,25 +398,24 @@
"search_filters_features_option_three_sixty": "360°",
"videoinfo_watch_on_youTube": "Ver en YouTube",
"preferences_save_player_pos_label": "Guardar posición de reproducción: ",
- "generic_views_count": "{{count}} visualización",
- "generic_views_count_plural": "{{count}} visualizaciones",
+ "generic_views_count": "{{count}} vista",
+ "generic_views_count_plural": "{{count}} vistas",
"generic_subscribers_count": "{{count}} suscriptor",
"generic_subscribers_count_plural": "{{count}} suscriptores",
"generic_subscriptions_count": "{{count}} suscripción",
"generic_subscriptions_count_plural": "{{count}} suscripciones",
- "subscriptions_unseen_notifs_count": "{{count}} notificación no vista",
- "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas",
+ "subscriptions_unseen_notifs_count": "{{count}} notificación sin ver",
+ "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones sin ver",
"generic_count_days": "{{count}} día",
"generic_count_days_plural": "{{count}} días",
"comments_view_x_replies": "Ver {{count}} respuesta",
"comments_view_x_replies_plural": "Ver {{count}} respuestas",
"generic_count_weeks": "{{count}} semana",
"generic_count_weeks_plural": "{{count}} semanas",
- "generic_playlists_count": "{{count}} lista de reproducción",
- "generic_playlists_count_plural": "{{count}} listas de reproducción",
- "generic_videos_count_0": "{{count}} video",
- "generic_videos_count_1": "{{count}} videos",
- "generic_videos_count_2": "{{count}} videos",
+ "generic_playlists_count": "{{count}} reproducción",
+ "generic_playlists_count_plural": "{{count}} reproducciones",
+ "generic_videos_count": "{{count}} video",
+ "generic_videos_count_plural": "{{count}} videos",
"generic_count_months": "{{count}} mes",
"generic_count_months_plural": "{{count}} meses",
"comments_points_count": "{{count}} punto",
@@ -469,8 +468,8 @@
"search_filters_duration_option_none": "Cualquier duración",
"search_filters_features_option_vr180": "VR180",
"search_filters_apply_button": "Aplicar filtros",
- "tokens_count": "{{count}} ficha",
- "tokens_count_plural": "{{count}} fichas",
+ "tokens_count": "{{count}} token",
+ "tokens_count_plural": "{{count}} tokens",
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
"Popular enabled: ": "¿Habilitar la sección popular? ",
"error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
diff --git a/locales/ko.json b/locales/ko.json
index d4f3a711..2b454add 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -46,7 +46,7 @@
"Log in/register": "로그인/회원가입",
"Log in": "로그인",
"source": "출처",
- "JavaScript license information": "자바스크립트 라이센스 정보",
+ "JavaScript license information": "자바스크립트 라이선스 정보",
"An alternative front-end to YouTube": "유튜브의 프론트엔드 대안",
"History": "역사",
"Delete account?": "계정을 삭제 하시겠습니까?",
@@ -116,7 +116,7 @@
"Show replies": "댓글 보기",
"Hide replies": "댓글 숨기기",
"Incorrect password": "잘못된 비밀번호",
- "License: ": "라이센스: ",
+ "License: ": "라이선스: ",
"Genre: ": "장르: ",
"Editing playlist `x`": "재생목록 `x` 수정하기",
"Playlist privacy": "재생목록 공개 범위",
@@ -135,7 +135,7 @@
"Unlisted": "목록에 없음",
"Public": "공개",
"View privacy policy.": "개인정보 처리방침 보기.",
- "View JavaScript license information.": "자바스크립트 라이센스 정보 보기.",
+ "View JavaScript license information.": "자바스크립트 라이선스 정보 보기.",
"Source available here.": "소스는 여기에서 사용할 수 있습니다.",
"Log out": "로그아웃",
"search": "검색",
@@ -460,5 +460,12 @@
"channel_tab_shorts_label": "쇼츠",
"channel_tab_streams_label": "실시간 스트리밍",
"channel_tab_channels_label": "채널",
- "channel_tab_playlists_label": "재생목록"
+ "channel_tab_playlists_label": "재생목록",
+ "Standard YouTube license": "표준 유튜브 라이선스",
+ "Song: ": "제목: ",
+ "Channel Sponsor": "채널 스폰서",
+ "Album: ": "앨범: ",
+ "Music in this video": "동영상 속 음악",
+ "Artist: ": "아티스트: ",
+ "Download is disabled": "다운로드가 비활성화 되어있음"
}
diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr
index 63dd2194..c3d6124f 100644
--- a/src/invidious/channels/channels.cr
+++ b/src/invidious/channels/channels.cr
@@ -159,12 +159,18 @@ def fetch_channel(ucid, pull_all_videos : Bool)
LOGGER.debug("fetch_channel: #{ucid}")
LOGGER.trace("fetch_channel: #{ucid} : pull_all_videos = #{pull_all_videos}")
+ namespaces = {
+ "yt" => "http://www.youtube.com/xml/schemas/2015",
+ "media" => "http://search.yahoo.com/mrss/",
+ "default" => "http://www.w3.org/2005/Atom",
+ }
+
LOGGER.trace("fetch_channel: #{ucid} : Downloading RSS feed")
rss = YT_POOL.client &.get("/feeds/videos.xml?channel_id=#{ucid}").body
LOGGER.trace("fetch_channel: #{ucid} : Parsing RSS feed")
- rss = XML.parse_html(rss)
+ rss = XML.parse(rss)
- author = rss.xpath_node(%q(//feed/title))
+ author = rss.xpath_node("//default:feed/default:title", namespaces)
if !author
raise InfoException.new("Deleted or invalid channel")
end
@@ -192,15 +198,23 @@ def fetch_channel(ucid, pull_all_videos : Bool)
videos, continuation = IV::Channel::Tabs.get_videos(channel)
LOGGER.trace("fetch_channel: #{ucid} : Extracting videos from channel RSS feed")
- rss.xpath_nodes("//feed/entry").each do |entry|
- video_id = entry.xpath_node("videoid").not_nil!.content
- title = entry.xpath_node("title").not_nil!.content
- published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content)
- updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content)
- author = entry.xpath_node("author/name").not_nil!.content
- ucid = entry.xpath_node("channelid").not_nil!.content
- views = entry.xpath_node("group/community/statistics").try &.["views"]?.try &.to_i64?
- views ||= 0_i64
+ rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry|
+ video_id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content
+ title = entry.xpath_node("default:title", namespaces).not_nil!.content
+
+ published = Time.parse_rfc3339(
+ entry.xpath_node("default:published", namespaces).not_nil!.content
+ )
+ updated = Time.parse_rfc3339(
+ entry.xpath_node("default:updated", namespaces).not_nil!.content
+ )
+
+ author = entry.xpath_node("default:author/default:name", namespaces).not_nil!.content
+ ucid = entry.xpath_node("yt:channelId", namespaces).not_nil!.content
+
+ views = entry
+ .xpath_node("media:group/media:community/media:statistics", namespaces)
+ .try &.["views"]?.try &.to_i64? || 0_i64
channel_video = videos
.select(SearchVideo)
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index fd2be73d..01556099 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -372,32 +372,25 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
</div>
END_HTML
when "video"
- html << <<-END_HTML
- <div class="pure-g">
- <div class="pure-u-1 pure-u-md-1-2">
- <div style="position:relative;width:100%;height:0;padding-bottom:56.25%;margin-bottom:5px">
- END_HTML
-
if attachment["error"]?
html << <<-END_HTML
+ <div class="pure-g video-iframe-wrapper">
<p>#{attachment["error"]}</p>
+ </div>
END_HTML
else
html << <<-END_HTML
- <iframe id='ivplayer' style='position:absolute;width:100%;height:100%;left:0;top:0' src='/embed/#{attachment["videoId"]?}?autoplay=0' style='border:none;'></iframe>
+ <div class="pure-g video-iframe-wrapper">
+ <iframe class="video-iframe" src='/embed/#{attachment["videoId"]?}?autoplay=0'></iframe>
+ </div>
END_HTML
end
-
- html << <<-END_HTML
- </div>
- </div>
- </div>
- END_HTML
else nil # Ignore
end
end
html << <<-END_HTML
+ <p>
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
END_HTML
@@ -416,6 +409,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
html << <<-END_HTML
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
+ </p>
END_HTML
if child["creatorHeart"]?
@@ -604,7 +598,7 @@ def text_to_parsed_content(text : String) : JSON::Any
currentNode = {"text" => urlMatch[0], "navigationEndpoint" => {"urlEndpoint" => {"url" => urlMatch[0]}}}
currentNodes << (JSON.parse(currentNode.to_json))
# If text remain after match create new simple node with text after match
- afterNode = {"text" => splittedLastNode.size > 0 ? splittedLastNode[1] : ""}
+ afterNode = {"text" => splittedLastNode.size > 1 ? splittedLastNode[1] : ""}
currentNodes << (JSON.parse(afterNode.to_json))
end
@@ -635,55 +629,8 @@ def content_to_comment_html(content, video_id : String? = "")
text = HTML.escape(run["text"].as_s)
- if run["navigationEndpoint"]?
- if url = run["navigationEndpoint"]["urlEndpoint"]?.try &.["url"].as_s
- url = URI.parse(url)
- displayed_url = text
-
- if url.host == "youtu.be"
- url = "/watch?v=#{url.request_target.lstrip('/')}"
- elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com")
- if url.path == "/redirect"
- # Sometimes, links can be corrupted (why?) so make sure to fallback
- # nicely. See https://github.com/iv-org/invidious/issues/2682
- url = url.query_params["q"]? || ""
- displayed_url = url
- else
- url = url.request_target
- displayed_url = "youtube.com#{url}"
- end
- end
-
- text = %(<a href="#{url}">#{reduce_uri(displayed_url)}</a>)
- elsif watch_endpoint = run["navigationEndpoint"]["watchEndpoint"]?
- start_time = watch_endpoint["startTimeSeconds"]?.try &.as_i
- link_video_id = watch_endpoint["videoId"].as_s
-
- url = "/watch?v=#{link_video_id}"
- url += "&t=#{start_time}" if !start_time.nil?
-
- # If the current video ID (passed through from the caller function)
- # is the same as the video ID in the link, add HTML attributes for
- # the JS handler function that bypasses page reload.
- #
- # See: https://github.com/iv-org/invidious/issues/3063
- if link_video_id == video_id
- start_time ||= 0
- text = %(<a href="#{url}" data-onclick="jump_to_time" data-jump-time="#{start_time}">#{reduce_uri(text)}</a>)
- else
- text = %(<a href="#{url}">#{text}</a>)
- end
- elsif url = run.dig?("navigationEndpoint", "commandMetadata", "webCommandMetadata", "url").try &.as_s
- if text.starts_with?(/\s?[@#]/)
- # Handle "pings" in comments and hasthags differently
- # See:
- # - https://github.com/iv-org/invidious/issues/3038
- # - https://github.com/iv-org/invidious/issues/3062
- text = %(<a href="#{url}">#{text}</a>)
- else
- text = %(<a href="#{url}">#{reduce_uri(url)}</a>)
- end
- end
+ if navigationEndpoint = run.dig?("navigationEndpoint")
+ text = parse_link_endpoint(navigationEndpoint, text, video_id)
end
text = "<b>#{text}</b>" if run["bold"]?
diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr
index 0a4a4fd8..d54e6a76 100644
--- a/src/invidious/database/users.cr
+++ b/src/invidious/database/users.cr
@@ -52,7 +52,7 @@ module Invidious::Database::Users
def mark_watched(user : User, vid : String)
request = <<-SQL
UPDATE users
- SET watched = array_append(watched, $1)
+ SET watched = array_append(array_remove(watched, $1), $1)
WHERE email = $2
SQL
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index 500a2582..bcf7c963 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -389,3 +389,56 @@ def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "
end
return str
end
+
+# Get the html link from a NavigationEndpoint or an innertubeCommand
+def parse_link_endpoint(endpoint : JSON::Any, text : String, video_id : String)
+ if url = endpoint.dig?("urlEndpoint", "url").try &.as_s
+ url = URI.parse(url)
+ displayed_url = text
+
+ if url.host == "youtu.be"
+ url = "/watch?v=#{url.request_target.lstrip('/')}"
+ elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com")
+ if url.path == "/redirect"
+ # Sometimes, links can be corrupted (why?) so make sure to fallback
+ # nicely. See https://github.com/iv-org/invidious/issues/2682
+ url = url.query_params["q"]? || ""
+ displayed_url = url
+ else
+ url = url.request_target
+ displayed_url = "youtube.com#{url}"
+ end
+ end
+
+ text = %(<a href="#{url}">#{reduce_uri(displayed_url)}</a>)
+ elsif watch_endpoint = endpoint.dig?("watchEndpoint")
+ start_time = watch_endpoint["startTimeSeconds"]?.try &.as_i
+ link_video_id = watch_endpoint["videoId"].as_s
+
+ url = "/watch?v=#{link_video_id}"
+ url += "&t=#{start_time}" if !start_time.nil?
+
+ # If the current video ID (passed through from the caller function)
+ # is the same as the video ID in the link, add HTML attributes for
+ # the JS handler function that bypasses page reload.
+ #
+ # See: https://github.com/iv-org/invidious/issues/3063
+ if link_video_id == video_id
+ start_time ||= 0
+ text = %(<a href="#{url}" data-onclick="jump_to_time" data-jump-time="#{start_time}">#{reduce_uri(text)}</a>)
+ else
+ text = %(<a href="#{url}">#{text}</a>)
+ end
+ elsif url = endpoint.dig?("commandMetadata", "webCommandMetadata", "url").try &.as_s
+ if text.starts_with?(/\s?[@#]/)
+ # Handle "pings" in comments and hasthags differently
+ # See:
+ # - https://github.com/iv-org/invidious/issues/3038
+ # - https://github.com/iv-org/invidious/issues/3062
+ text = %(<a href="#{url}">#{text}</a>)
+ else
+ text = %(<a href="#{url}">#{reduce_uri(url)}</a>)
+ end
+ end
+ return text
+end
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index 5d3845c3..813cb0f4 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -76,7 +76,7 @@ module Invidious::Routes::Watch
end
env.params.query.delete_all("iv_load_policy")
- if watched && preferences.watch_history && !watched.includes? id
+ if watched && preferences.watch_history
Invidious::Database::Users.mark_watched(user.as(User), id)
end
@@ -259,9 +259,7 @@ module Invidious::Routes::Watch
case action
when "action_mark_watched"
- if !user.watched.includes? id
- Invidious::Database::Users.mark_watched(user, id)
- end
+ Invidious::Database::Users.mark_watched(user, id)
when "action_mark_unwatched"
Invidious::Database::Users.mark_unwatched(user, id)
else
diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr
index 74bab1bd..2d9f8a83 100644
--- a/src/invidious/trending.cr
+++ b/src/invidious/trending.cr
@@ -20,6 +20,21 @@ def fetch_trending(trending_type, region, locale)
items, _ = extract_items(initial_data)
- # Return items, but ignore categories (e.g featured content)
- return items.reject!(Category), plid
+ extracted = [] of SearchItem
+
+ items.each do |itm|
+ if itm.is_a?(Category)
+ # Ignore the smaller categories, as they generally contain a sponsored
+ # channel, which brings a lot of noise on the trending page.
+ # See: https://github.com/iv-org/invidious/issues/2989
+ next if itm.contents.size < 24
+
+ extracted.concat extract_category(itm)
+ else
+ extracted << itm
+ end
+ end
+
+ # Deduplicate items before returning results
+ return extracted.select(SearchVideo).uniq!(&.id), plid
end
diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr
index 20ae0d47..aa947456 100644
--- a/src/invidious/user/imports.cr
+++ b/src/invidious/user/imports.cr
@@ -48,7 +48,7 @@ struct Invidious::User
if data["watch_history"]?
user.watched += data["watch_history"].as_a.map(&.as_s)
- user.watched.uniq!
+ user.watched.reverse!.uniq!.reverse!
Invidious::Database::Users.update_watch_history(user)
end
diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr
index 2017955d..542cb416 100644
--- a/src/invidious/videos/description.cr
+++ b/src/invidious/videos/description.cr
@@ -1,51 +1,6 @@
require "json"
require "uri"
-def parse_command(command : JSON::Any?, string : String) : String?
- on_tap = command.dig?("onTap", "innertubeCommand")
-
- # 3rd party URL, extract original URL from YouTube tracking URL
- if url_endpoint = on_tap.try &.["urlEndpoint"]?
- youtube_url = URI.parse url_endpoint["url"].as_s
-
- original_url = youtube_url.query_params["q"]?
- if original_url.nil?
- return ""
- else
- return "<a href=\"#{original_url}\">#{original_url}</a>"
- end
- # 1st party watch URL
- elsif watch_endpoint = on_tap.try &.["watchEndpoint"]?
- video_id = watch_endpoint["videoId"].as_s
- time = watch_endpoint["startTimeSeconds"].as_i
-
- url = "/watch?v=#{video_id}&t=#{time}s"
-
- # if string is a timestamp, use the string instead
- # this is a lazy regex for validating timestamps
- if /(?:\d{1,2}:){1,2}\d{2}/ =~ string
- return "<a href=\"#{url}\">#{string}</a>"
- else
- return "<a href=\"#{url}\">#{url}</a>"
- end
- # hashtag/other browse URLs
- elsif browse_endpoint = on_tap.try &.dig?("commandMetadata", "webCommandMetadata")
- url = browse_endpoint["url"].try &.as_s
-
- # remove unnecessary character in a channel name
- if browse_endpoint["webPageType"]?.try &.as_s == "WEB_PAGE_TYPE_CHANNEL"
- name = string.match(/@[\w\d.-]+/)
- if name.try &.[0]?
- return "<a href=\"#{url}\">#{name.try &.[0]}</a>"
- end
- end
-
- return "<a href=\"#{url}\">#{string}</a>"
- end
-
- return "(unknown YouTube desc command)"
-end
-
private def copy_string(str : String::Builder, iter : Iterator, count : Int) : Int
copied = 0
while copied < count
@@ -62,7 +17,7 @@ private def copy_string(str : String::Builder, iter : Iterator, count : Int) : I
return copied
end
-def parse_description(desc : JSON::Any?) : String?
+def parse_description(desc, video_id : String) : String?
return "" if desc.nil?
content = desc["content"].as_s
@@ -94,7 +49,11 @@ def parse_description(desc : JSON::Any?) : String?
copy_string(str2, iter, cmd_length)
end
- str << parse_command(command, cmd_content)
+ link = cmd_content
+ if on_tap = command.dig?("onTap", "innertubeCommand")
+ link = parse_link_endpoint(on_tap, cmd_content, video_id)
+ end
+ str << link
index += cmd_length
end
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 1c6d118d..2e8eecc3 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -287,7 +287,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
# description_html = video_secondary_renderer.try &.dig?("description", "runs")
# .try &.as_a.try { |t| content_to_comment_html(t, video_id) }
- description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription"))
+ description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription"), video_id)
# Video metadata
diff --git a/src/invidious/yt_backend/extractors_utils.cr b/src/invidious/yt_backend/extractors_utils.cr
index b247dca8..11d95958 100644
--- a/src/invidious/yt_backend/extractors_utils.cr
+++ b/src/invidious/yt_backend/extractors_utils.cr
@@ -68,16 +68,17 @@ rescue ex
return false
end
-# This function extracts the SearchItems from a Category.
+# This function extracts SearchVideo items from a Category.
# Categories are commonly returned in search results and trending pages.
def extract_category(category : Category) : Array(SearchVideo)
- items = [] of SearchItem
+ return category.contents.select(SearchVideo)
+end
- category.contents.each do |item|
- target << cate_i if item.is_a?(SearchItem)
+# :ditto:
+def extract_category(category : Category, &)
+ category.contents.select(SearchVideo).each do |item|
+ yield item
end
-
- return items
end
def extract_selected_tab(tabs)