diff options
| author | Samantaz Fox <coding@samantaz.fr> | 2023-08-26 12:11:38 +0200 |
|---|---|---|
| committer | Samantaz Fox <coding@samantaz.fr> | 2023-08-26 12:11:38 +0200 |
| commit | a8295b452e73bed8c4943f3a124e5a180f076364 (patch) | |
| tree | 38eec333203520de9bad112cebd90f60aabbc42a /src | |
| parent | 95176a8eb40aa896f4642b2464bc7585d64f7d59 (diff) | |
| parent | 70b80ce8ad5ad9e5eb57a8f2f8e72a2274f8523f (diff) | |
| download | invidious-a8295b452e73bed8c4943f3a124e5a180f076364.tar.gz invidious-a8295b452e73bed8c4943f3a124e5a180f076364.tar.bz2 invidious-a8295b452e73bed8c4943f3a124e5a180f076364.zip | |
Search: Add hashtag result (#3989)
Diffstat (limited to 'src')
| -rw-r--r-- | src/invidious/helpers/serialized_yt_data.cr | 21 | ||||
| -rw-r--r-- | src/invidious/views/components/item.ecr | 26 | ||||
| -rw-r--r-- | src/invidious/yt_backend/extractors.cr | 53 |
3 files changed, 97 insertions, 3 deletions
diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index 7c12ad0e..e0bd7279 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -232,6 +232,25 @@ struct SearchChannel end end +struct SearchHashtag + include DB::Serializable + + property title : String + property url : String + property video_count : Int64 + property channel_count : Int64 + + def to_json(locale : String?, json : JSON::Builder) + json.object do + json.field "type", "hashtag" + json.field "title", self.title + json.field "url", self.url + json.field "videoCount", self.video_count + json.field "channelCount", self.channel_count + end + end +end + class Category include DB::Serializable @@ -274,4 +293,4 @@ struct Continuation end end -alias SearchItem = SearchVideo | SearchChannel | SearchPlaylist | Category +alias SearchItem = SearchVideo | SearchChannel | SearchPlaylist | SearchHashtag | Category diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 7ffd2d93..c29ec47b 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -1,6 +1,6 @@ <%- thin_mode = env.get("preferences").as(Preferences).thin_mode - item_watched = !item.is_a?(SearchChannel | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil + item_watched = !item.is_a?(SearchChannel | SearchHashtag | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil author_verified = item.responds_to?(:author_verified) && item.author_verified -%> @@ -29,6 +29,30 @@ <p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p> <% if !item.auto_generated %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %> <h5><%= item.description_html %></h5> + <% when SearchHashtag %> + <% if !thin_mode %> + <a tabindex="-1" href="<%= item.url %>"> + <center><img style="width:56.25%" src="/hashtag.svg" alt="" /></center> + </a> + <%- else -%> + <div class="thumbnail-placeholder" style="width:56.25%"></div> + <% end %> + + <div class="video-card-row"> + <div class="flex-left"><a href="<%= item.url %>"><%= HTML.escape(item.title) %></a></div> + </div> + + <div class="video-card-row"> + <%- if item.video_count != 0 -%> + <p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p> + <%- end -%> + </div> + + <div class="video-card-row"> + <%- if item.channel_count != 0 -%> + <p><%= translate_count(locale, "generic_channels_count", item.channel_count, NumberFormatting::Separator) %></p> + <%- end -%> + </div> <% when SearchPlaylist, InvidiousPlaylist %> <%- if item.id.starts_with? "RD" diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index 8cf59d50..aaf7772e 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -11,15 +11,16 @@ private ITEM_CONTAINER_EXTRACTOR = { } private ITEM_PARSERS = { + Parsers::RichItemRendererParser, Parsers::VideoRendererParser, Parsers::ChannelRendererParser, Parsers::GridPlaylistRendererParser, Parsers::PlaylistRendererParser, Parsers::CategoryRendererParser, - Parsers::RichItemRendererParser, Parsers::ReelItemRendererParser, Parsers::ItemSectionRendererParser, Parsers::ContinuationItemRendererParser, + Parsers::HashtagRendererParser, } private alias InitialData = Hash(String, JSON::Any) @@ -210,6 +211,56 @@ private module Parsers end end + # Parses an Innertube `hashtagTileRenderer` into a `SearchHashtag`. + # Returns `nil` when the given object is not a `hashtagTileRenderer`. + # + # A `hashtagTileRenderer` is a kind of search result. + # It can be found when searching for any hashtag (e.g "#hi" or "#shorts") + module HashtagRendererParser + def self.process(item : JSON::Any, author_fallback : AuthorFallback) + if item_contents = item["hashtagTileRenderer"]? + return self.parse(item_contents) + end + end + + private def self.parse(item_contents) + title = extract_text(item_contents["hashtag"]).not_nil! # E.g "#hi" + + # E.g "/hashtag/hi" + url = item_contents.dig?("onTapCommand", "commandMetadata", "webCommandMetadata", "url").try &.as_s + url ||= URI.encode_path("/hashtag/#{title.lchop('#')}") + + video_count_txt = extract_text(item_contents["hashtagVideoCount"]?) # E.g "203K videos" + channel_count_txt = extract_text(item_contents["hashtagChannelCount"]?) # E.g "81K channels" + + # Fallback for video/channel counts + if channel_count_txt.nil? || video_count_txt.nil? + # E.g: "203K videos • 81K channels" + info_text = extract_text(item_contents["hashtagInfoText"]?).try &.split(" • ") + + if info_text && info_text.size == 2 + video_count_txt ||= info_text[0] + channel_count_txt ||= info_text[1] + end + end + + return SearchHashtag.new({ + title: title, + url: url, + video_count: short_text_to_number(video_count_txt || ""), + channel_count: short_text_to_number(channel_count_txt || ""), + }) + rescue ex + LOGGER.debug("HashtagRendererParser: Failed to extract renderer.") + LOGGER.debug("HashtagRendererParser: Got exception: #{ex.message}") + return nil + end + + def self.parser_name + return {{@type.name}} + end + end + # Parses a InnerTube gridPlaylistRenderer into a SearchPlaylist. Returns nil when the given object isn't a gridPlaylistRenderer # # A gridPlaylistRenderer renders a playlist, that is located in a grid, to click on within the YouTube and Invidious UI. |
