mirror of
https://github.com/iv-org/invidious.git
synced 2024-11-22 05:27:21 +00:00
Search: Add hashtag result (#3989)
This commit is contained in:
commit
a8295b452e
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": "{{count}} view",
|
||||||
"generic_views_count_plural": "{{count}} views",
|
"generic_views_count_plural": "{{count}} views",
|
||||||
"generic_videos_count": "{{count}} video",
|
"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": "{{count}} vue",
|
||||||
"generic_views_count_plural": "{{count}} vues",
|
"generic_views_count_plural": "{{count}} vues",
|
||||||
"generic_videos_count": "{{count}} vidéo",
|
"generic_videos_count": "{{count}} vidéo",
|
||||||
|
@ -232,6 +232,25 @@ struct SearchChannel
|
|||||||
end
|
end
|
||||||
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
|
class Category
|
||||||
include DB::Serializable
|
include DB::Serializable
|
||||||
|
|
||||||
@ -274,4 +293,4 @@ struct Continuation
|
|||||||
end
|
end
|
||||||
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
|
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
|
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>
|
<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 %>
|
<% if !item.auto_generated %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %>
|
||||||
<h5><%= item.description_html %></h5>
|
<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 %>
|
<% when SearchPlaylist, InvidiousPlaylist %>
|
||||||
<%-
|
<%-
|
||||||
if item.id.starts_with? "RD"
|
if item.id.starts_with? "RD"
|
||||||
|
@ -11,15 +11,16 @@ private ITEM_CONTAINER_EXTRACTOR = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ITEM_PARSERS = {
|
private ITEM_PARSERS = {
|
||||||
|
Parsers::RichItemRendererParser,
|
||||||
Parsers::VideoRendererParser,
|
Parsers::VideoRendererParser,
|
||||||
Parsers::ChannelRendererParser,
|
Parsers::ChannelRendererParser,
|
||||||
Parsers::GridPlaylistRendererParser,
|
Parsers::GridPlaylistRendererParser,
|
||||||
Parsers::PlaylistRendererParser,
|
Parsers::PlaylistRendererParser,
|
||||||
Parsers::CategoryRendererParser,
|
Parsers::CategoryRendererParser,
|
||||||
Parsers::RichItemRendererParser,
|
|
||||||
Parsers::ReelItemRendererParser,
|
Parsers::ReelItemRendererParser,
|
||||||
Parsers::ItemSectionRendererParser,
|
Parsers::ItemSectionRendererParser,
|
||||||
Parsers::ContinuationItemRendererParser,
|
Parsers::ContinuationItemRendererParser,
|
||||||
|
Parsers::HashtagRendererParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
private alias InitialData = Hash(String, JSON::Any)
|
private alias InitialData = Hash(String, JSON::Any)
|
||||||
@ -210,6 +211,56 @@ private module Parsers
|
|||||||
end
|
end
|
||||||
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
|
# 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.
|
# A gridPlaylistRenderer renders a playlist, that is located in a grid, to click on within the YouTube and Invidious UI.
|
||||||
|
Loading…
Reference in New Issue
Block a user