Search: Add hashtag result (#3989)

This commit is contained in:
Samantaz Fox 2023-08-26 12:11:38 +02:00
commit a8295b452e
No known key found for this signature in database
GPG Key ID: F42821059186176E
6 changed files with 110 additions and 3 deletions

9
assets/hashtag.svg Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="128" height="128" viewBox="0 0 128 128" version="1.1" id="svg5" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g>
<rect fill="#c84fff" width="128" height="128" x="0" y="0" />
<g aria-label="#" transform="matrix(1.1326954,0,0,1.1326954,-20.255282,-23.528147)">
<path d="m 87.780593,70.524217 -2.624999,13.666661 h 11.666662 v 5.708331 H 84.030595 L 80.61393,107.73253 H 74.488932 L 77.988931,89.899209 H 65.863936 L 62.447271,107.73253 H 56.447273 L 59.697272,89.899209 H 48.947276 V 84.190878 H 60.822271 L 63.530603,70.524217 H 52.113942 V 64.815886 H 64.57227 l 3.416665,-17.999993 h 6.124997 l -3.416665,17.999993 h 12.208328 l 3.499999,-17.999993 h 5.999997 l -3.499998,17.999993 h 10.916662 v 5.708331 z M 66.947269,84.190878 H 79.072264 L 81.738929,70.524217 H 69.613934 Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@ -1,4 +1,6 @@
{
"generic_channels_count": "{{count}} channel",
"generic_channels_count_plural": "{{count}} channels",
"generic_views_count": "{{count}} view",
"generic_views_count_plural": "{{count}} views",
"generic_videos_count": "{{count}} video",

View File

@ -1,4 +1,6 @@
{
"generic_channels_count": "{{count}} chaîne",
"generic_channels_count_plural": "{{count}} chaînes",
"generic_views_count": "{{count}} vue",
"generic_views_count_plural": "{{count}} vues",
"generic_videos_count": "{{count}} vidéo",

View File

@ -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

View File

@ -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"

View File

@ -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.