Search: Add hashtag result (#3989)
This commit is contained in:
commit
a8295b452e
6 changed files with 110 additions and 3 deletions
9
assets/hashtag.svg
Normal file
9
assets/hashtag.svg
Normal 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 |
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue