Merge pull request #2859 from jonas-w/verified-badge

Youtube verification badge
This commit is contained in:
Samantaz Fox 2022-04-30 18:54:38 +02:00 committed by GitHub
commit 1d25c55c0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 77 additions and 26 deletions

View File

@ -12,7 +12,8 @@ record AboutChannel,
joined : Time, joined : Time,
is_family_friendly : Bool, is_family_friendly : Bool,
allowed_regions : Array(String), allowed_regions : Array(String),
tabs : Array(String) tabs : Array(String),
verified : Bool
record AboutRelatedChannel, record AboutRelatedChannel,
ucid : String, ucid : String,
@ -70,6 +71,9 @@ def get_about_info(ucid, locale) : AboutChannel
# if banner.includes? "channels/c4/default_banner" # if banner.includes? "channels/c4/default_banner"
# banner = nil # banner = nil
# end # end
# author_verified_badges = initdata["header"]?.try &.["c4TabbedHeaderRenderer"]?.try &.["badges"]?
author_verified_badge = initdata["header"].dig?("c4TabbedHeaderRenderer", "badges", 0, "metadataBadgeRenderer", "tooltip")
author_verified = (author_verified_badge && author_verified_badge == "Verified")
description = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?.try &.as_s? || "" description = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?.try &.as_s? || ""
description_html = HTML.escape(description) description_html = HTML.escape(description)
@ -128,6 +132,7 @@ def get_about_info(ucid, locale) : AboutChannel
is_family_friendly: is_family_friendly, is_family_friendly: is_family_friendly,
allowed_regions: allowed_regions, allowed_regions: allowed_regions,
tabs: tabs, tabs: tabs,
verified: author_verified || false,
) )
end end

View File

@ -146,6 +146,8 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
content_html = node_comment["contentText"]?.try { |t| parse_content(t) } || "" content_html = node_comment["contentText"]?.try { |t| parse_content(t) } || ""
author = node_comment["authorText"]?.try &.["simpleText"]? || "" author = node_comment["authorText"]?.try &.["simpleText"]? || ""
json.field "verified", (node_comment["authorCommentBadge"]? != nil)
json.field "author", author json.field "author", author
json.field "authorThumbnails" do json.field "authorThumbnails" do
json.array do json.array do
@ -329,7 +331,11 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
end end
author_name = HTML.escape(child["author"].as_s) author_name = HTML.escape(child["author"].as_s)
if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool
author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark-circle\"></i>"
elsif child["verified"]?.try &.as_bool
author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark\"></i>"
end
html << <<-END_HTML html << <<-END_HTML
<div class="pure-g" style="width:100%"> <div class="pure-g" style="width:100%">
<div class="channel-profile pure-u-4-24 pure-u-md-2-24"> <div class="channel-profile pure-u-4-24 pure-u-md-2-24">

View File

@ -12,6 +12,7 @@ struct SearchVideo
property live_now : Bool property live_now : Bool
property premium : Bool property premium : Bool
property premiere_timestamp : Time? property premiere_timestamp : Time?
property author_verified : Bool
def to_xml(auto_generated, query_params, xml : XML::Builder) def to_xml(auto_generated, query_params, xml : XML::Builder)
query_params["v"] = self.id query_params["v"] = self.id
@ -129,6 +130,7 @@ struct SearchPlaylist
property video_count : Int32 property video_count : Int32
property videos : Array(SearchPlaylistVideo) property videos : Array(SearchPlaylistVideo)
property thumbnail : String? property thumbnail : String?
property author_verified : Bool
def to_json(locale : String?, json : JSON::Builder) def to_json(locale : String?, json : JSON::Builder)
json.object do json.object do
@ -141,6 +143,8 @@ struct SearchPlaylist
json.field "authorId", self.ucid json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}" json.field "authorUrl", "/channel/#{self.ucid}"
json.field "authorVerified", self.author_verified
json.field "videoCount", self.video_count json.field "videoCount", self.video_count
json.field "videos" do json.field "videos" do
json.array do json.array do
@ -182,6 +186,7 @@ struct SearchChannel
property video_count : Int32 property video_count : Int32
property description_html : String property description_html : String
property auto_generated : Bool property auto_generated : Bool
property author_verified : Bool
def to_json(locale : String?, json : JSON::Builder) def to_json(locale : String?, json : JSON::Builder)
json.object do json.object do
@ -189,7 +194,7 @@ struct SearchChannel
json.field "author", self.author json.field "author", self.author
json.field "authorId", self.ucid json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}" json.field "authorUrl", "/channel/#{self.ucid}"
json.field "authorVerified", self.author_verified
json.field "authorThumbnails" do json.field "authorThumbnails" do
json.array do json.array do
qualities = {32, 48, 76, 100, 176, 512} qualities = {32, 48, 76, 100, 176, 512}

View File

@ -182,6 +182,7 @@ module Invidious::Routes::Feeds
paid: false, paid: false,
premium: false, premium: false,
premiere_timestamp: nil, premiere_timestamp: nil,
author_verified: false, # ¯\_(ツ)_/¯
}) })
end end

View File

@ -609,6 +609,10 @@ struct Video
info["authorThumbnail"]?.try &.as_s || "" info["authorThumbnail"]?.try &.as_s || ""
end end
def author_verified : Bool
info["authorVerified"].try &.as_bool || false
end
def sub_count_text : String def sub_count_text : String
info["subCountText"]?.try &.as_s || "-" info["subCountText"]?.try &.as_s || "-"
end end
@ -860,6 +864,12 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
.try &.dig?("runs", 0) .try &.dig?("runs", 0)
author = channel_info.try &.dig?("text") author = channel_info.try &.dig?("text")
author_verified_badge = related["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0).to_s
ucid = channel_info.try { |ci| HelperExtractors.get_browse_id(ci) } ucid = channel_info.try { |ci| HelperExtractors.get_browse_id(ci) }
# "4,088,033 views", only available on compact renderer # "4,088,033 views", only available on compact renderer
@ -883,6 +893,7 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
"length_seconds" => JSON::Any.new(length || "0"), "length_seconds" => JSON::Any.new(length || "0"),
"view_count" => JSON::Any.new(view_count || "0"), "view_count" => JSON::Any.new(view_count || "0"),
"short_view_count" => JSON::Any.new(short_view_count || "0"), "short_view_count" => JSON::Any.new(short_view_count || "0"),
"author_verified" => JSON::Any.new(author_verified),
} }
end end
@ -1077,6 +1088,9 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_
author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer") author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer")
author_thumbnail = author_info.try &.dig?("thumbnail", "thumbnails", 0, "url") author_thumbnail = author_info.try &.dig?("thumbnail", "thumbnails", 0, "url")
author_verified_badge = author_info.try &.dig?("badges", 0, "metadataBadgeRenderer", "tooltip")
params["authorVerified"] = JSON::Any.new((author_verified_badge && author_verified_badge == "Verified"))
params["authorThumbnail"] = JSON::Any.new(author_thumbnail.try &.as_s || "") params["authorThumbnail"] = JSON::Any.new(author_thumbnail.try &.as_s || "")
params["subCountText"] = JSON::Any.new(author_info.try &.["subscriberCountText"]? params["subCountText"] = JSON::Any.new(author_info.try &.["subscriberCountText"]?

View File

@ -20,7 +20,7 @@
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<div class="channel-profile"> <div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>"> <img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
<span><%= author %></span> <span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div> </div>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">

View File

@ -19,7 +19,7 @@
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<div class="channel-profile"> <div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>"> <img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
<span><%= author %></span> <span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div> </div>
</div> </div>
<div class="pure-u-1-3" style="text-align:right"> <div class="pure-u-1-3" style="text-align:right">

View File

@ -8,7 +8,7 @@
<img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>"/> <img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>"/>
</center> </center>
<% end %> <% end %>
<p dir="auto"><%= HTML.escape(item.author) %></p> <p dir="auto"><%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
</a> </a>
<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 %>
@ -30,7 +30,7 @@
<p dir="auto"><%= HTML.escape(item.title) %></p> <p dir="auto"><%= HTML.escape(item.title) %></p>
</a> </a>
<a href="/channel/<%= item.ucid %>"> <a href="/channel/<%= item.ucid %>">
<p dir="auto"><b><%= HTML.escape(item.author) %></b></p> <p dir="auto"><b><%= HTML.escape(item.author) %><% if !item.is_a?(InvidiousPlaylist) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b></p>
</a> </a>
<% when MixVideo %> <% when MixVideo %>
<a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>"> <a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>">
@ -142,7 +142,7 @@
<div class="video-card-row flexible"> <div class="video-card-row flexible">
<div class="flex-left"><a href="/channel/<%= item.ucid %>"> <div class="flex-left"><a href="/channel/<%= item.ucid %>">
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %></p> <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %><% if !item.is_a?(ChannelVideo) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
</a></div> </a></div>
<% endpoint_params = "?v=#{item.id}" %> <% endpoint_params = "?v=#{item.id}" %>

View File

@ -19,7 +19,7 @@
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<div class="channel-profile"> <div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>"> <img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
<span><%= author %></span> <span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div> </div>
</div> </div>
<div class="pure-u-1-3" style="text-align:right"> <div class="pure-u-1-3" style="text-align:right">

View File

@ -207,7 +207,7 @@ we're going to need to do it here in order to allow for translations.
<% if !video.author_thumbnail.empty? %> <% if !video.author_thumbnail.empty? %>
<img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>"> <img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>">
<% end %> <% end %>
<span id="channel-name"><%= author %></span> <span id="channel-name"><%= author %><% if !video.author_verified.nil? && video.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></span>
</div> </div>
</a> </a>
@ -281,9 +281,9 @@ we're going to need to do it here in order to allow for translations.
<h5 class="pure-g"> <h5 class="pure-g">
<div class="pure-u-14-24"> <div class="pure-u-14-24">
<% if rv["ucid"]? %> <% if rv["ucid"]? %>
<b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %></a></b> <b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b>
<% else %> <% else %>
<b style="width:100%"><%= rv["author"]? %></b> <b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b>
<% end %> <% end %>
</div> </div>

View File

@ -102,7 +102,11 @@ private module Parsers
premium = false premium = false
premiere_timestamp = item_contents.dig?("upcomingEventData", "startTime").try { |t| Time.unix(t.as_s.to_i64) } premiere_timestamp = item_contents.dig?("upcomingEventData", "startTime").try { |t| Time.unix(t.as_s.to_i64) }
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
item_contents["badges"]?.try &.as_a.each do |badge| item_contents["badges"]?.try &.as_a.each do |badge|
b = badge["metadataBadgeRenderer"] b = badge["metadataBadgeRenderer"]
case b["label"].as_s case b["label"].as_s
@ -129,6 +133,7 @@ private module Parsers
live_now: live_now, live_now: live_now,
premium: premium, premium: premium,
premiere_timestamp: premiere_timestamp, premiere_timestamp: premiere_timestamp,
author_verified: author_verified || false,
}) })
end end
@ -156,7 +161,11 @@ private module Parsers
private def self.parse(item_contents, author_fallback) private def self.parse(item_contents, author_fallback)
author = extract_text(item_contents["title"]) || author_fallback.name author = extract_text(item_contents["title"]) || author_fallback.name
author_id = item_contents["channelId"]?.try &.as_s || author_fallback.id author_id = item_contents["channelId"]?.try &.as_s || author_fallback.id
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
author_thumbnail = HelperExtractors.get_thumbnails(item_contents) author_thumbnail = HelperExtractors.get_thumbnails(item_contents)
# When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube. # When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube.
# Always simpleText # Always simpleText
@ -179,6 +188,7 @@ private module Parsers
video_count: video_count, video_count: video_count,
description_html: description_html, description_html: description_html,
auto_generated: auto_generated, auto_generated: auto_generated,
author_verified: author_verified || false,
}) })
end end
@ -206,7 +216,11 @@ private module Parsers
private def self.parse(item_contents, author_fallback) private def self.parse(item_contents, author_fallback)
title = extract_text(item_contents["title"]) || "" title = extract_text(item_contents["title"]) || ""
plid = item_contents["playlistId"]?.try &.as_s || "" plid = item_contents["playlistId"]?.try &.as_s || ""
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
video_count = HelperExtractors.get_video_count(item_contents) video_count = HelperExtractors.get_video_count(item_contents)
playlist_thumbnail = HelperExtractors.get_thumbnails(item_contents) playlist_thumbnail = HelperExtractors.get_thumbnails(item_contents)
@ -218,6 +232,7 @@ private module Parsers
video_count: video_count, video_count: video_count,
videos: [] of SearchPlaylistVideo, videos: [] of SearchPlaylistVideo,
thumbnail: playlist_thumbnail, thumbnail: playlist_thumbnail,
author_verified: author_verified || false,
}) })
end end
@ -251,7 +266,11 @@ private module Parsers
author_info = item_contents.dig?("shortBylineText", "runs", 0) author_info = item_contents.dig?("shortBylineText", "runs", 0)
author = author_info.try &.["text"].as_s || author_fallback.name author = author_info.try &.["text"].as_s || author_fallback.name
author_id = author_info.try { |x| HelperExtractors.get_browse_id(x) } || author_fallback.id author_id = author_info.try { |x| HelperExtractors.get_browse_id(x) } || author_fallback.id
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
end
author_verified = (author_verified_badge && author_verified_badge.size > 0)
videos = item_contents["videos"]?.try &.as_a.map do |v| videos = item_contents["videos"]?.try &.as_a.map do |v|
v = v["childVideoRenderer"] v = v["childVideoRenderer"]
v_title = v.dig?("title", "simpleText").try &.as_s || "" v_title = v.dig?("title", "simpleText").try &.as_s || ""
@ -274,6 +293,7 @@ private module Parsers
video_count: video_count, video_count: video_count,
videos: videos, videos: videos,
thumbnail: playlist_thumbnail, thumbnail: playlist_thumbnail,
author_verified: author_verified || false,
}) })
end end