Merge pull request #2827 from SamantazFox/more-code-cleanup

More code cleanup
This commit is contained in:
Samantaz Fox 2022-02-02 00:36:19 +01:00 committed by GitHub
commit fc5f84a0cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 193 additions and 305 deletions

View file

@ -15,9 +15,3 @@ if [ ! -z "$changed_cr_files" ]; then
git add $changed_cr_files git add $changed_cr_files
fi fi
# Locale equalizer
if [ ! -z $(git diff --name-only --cached -- locales/) ]; then
crystal run scripts/propagate-new-locale-keys.cr
git add locales > /dev/null
fi

View file

@ -1,95 +0,0 @@
require "json"
require "../src/invidious/helpers/i18n.cr"
def locale_to_array(locale_name)
arrayifed_locale_data = [] of Tuple(String, JSON::Any | String)
keys_only_array = [] of String
LOCALES[locale_name].each do |k, v|
if v.as_h?
arrayifed_locale_data << {k, JSON.parse(v.as_h.to_json)}
elsif v.as_s?
arrayifed_locale_data << {k, v.as_s}
end
keys_only_array << k
end
return arrayifed_locale_data, keys_only_array
end
# Invidious currently has some unloaded localization files. We shouldn't need to propagate new keys onto those.
# We'll also remove the reference locale (english) from the list to process.
loaded_locales = LOCALES.keys.select! { |key| key != "en-US" }
english_locale, english_locale_keys = locale_to_array("en-US")
# In order to automatically propagate locale keys we're going to be needing two arrays.
# One is an array containing each locale data encoded as tuples. The other would contain
# sets of only the keys of each locale files.
#
# The second array is to make sure that an key from the english reference file is present
# in whatever the current locale we're scanning is.
locale_list = [] of Array(Tuple(String, JSON::Any | String))
locale_list_with_only_keys = [] of Array(String)
# Populates the created arrays from above
loaded_locales.each do |name|
arrayifed_locale_data, keys_only_locale = locale_to_array(name)
locale_list << arrayifed_locale_data
locale_list_with_only_keys << keys_only_locale
end
# Propagate additions
locale_list_with_only_keys.dup.each_with_index do |keys_of_locale_in_processing, index_of_locale_in_processing|
insert_at = {} of Int32 => Tuple(String, JSON::Any | String)
LOCALES["en-US"].each_with_index do |ref_locale_data, ref_locale_key_index|
ref_locale_key, ref_locale_value = ref_locale_data
# Found an new key that isn't present in the current locale..
if !keys_of_locale_in_processing.includes? ref_locale_key
# In terms of structure there's currently only two types; one for plural and the other for singular translations.
if ref_locale_value.as_h?
insert_at[ref_locale_key_index] = {ref_locale_key, JSON.parse({"([^.,0-9]|^)1([^.,0-9]|$)" => "", "" => ""}.to_json)}
else
insert_at[ref_locale_key_index] = {ref_locale_key, ""}
end
end
end
insert_at.each do |location_to_insert, data|
locale_list_with_only_keys[index_of_locale_in_processing].insert(location_to_insert, data[0])
locale_list[index_of_locale_in_processing].insert(location_to_insert, data)
end
end
# Propagate removals
locale_list_with_only_keys.dup.each_with_index do |keys_of_locale_in_processing, index_of_locale_in_processing|
remove_at = [] of Int32
keys_of_locale_in_processing.each_with_index do |current_key, current_key_index|
if !english_locale_keys.includes? current_key
remove_at << current_key_index
end
end
remove_at.each do |index_to_remove_at|
locale_list_with_only_keys[index_of_locale_in_processing].delete_at(index_to_remove_at)
locale_list[index_of_locale_in_processing].delete_at(index_to_remove_at)
end
end
# Now we convert back to our original format.
final_locale_list = [] of String
locale_list.each do |locale|
intermediate_hash = {} of String => (JSON::Any | String)
locale.each { |k, v| intermediate_hash[k] = v }
final_locale_list << intermediate_hash.to_pretty_json(indent = " ")
end
locale_map = Hash.zip(loaded_locales, final_locale_list)
# Export
locale_map.each do |locale_name, locale_contents|
File.write("locales/#{locale_name}.json", "#{locale_contents}\n")
end

View file

@ -96,7 +96,7 @@ def get_about_info(ucid, locale) : AboutChannel
total_views = channel_about_meta["viewCountText"]?.try &.["simpleText"]?.try &.as_s.gsub(/\D/, "").to_i64? || 0_i64 total_views = channel_about_meta["viewCountText"]?.try &.["simpleText"]?.try &.as_s.gsub(/\D/, "").to_i64? || 0_i64
# The joined text is split to several sub strings. The reduce joins those strings before parsing the date. # The joined text is split to several sub strings. The reduce joins those strings before parsing the date.
joined = channel_about_meta["joinedDateText"]?.try &.["runs"]?.try &.as_a.reduce("") { |acc, node| acc + node["text"].as_s } joined = channel_about_meta["joinedDateText"]?.try &.["runs"]?.try &.as_a.reduce("") { |acc, nd| acc + nd["text"].as_s }
.try { |text| Time.parse(text, "Joined %b %-d, %Y", Time::Location.local) } || Time.unix(0) .try { |text| Time.parse(text, "Joined %b %-d, %Y", Time::Location.local) } || Time.unix(0)
# Normal Auto-generated channels # Normal Auto-generated channels
@ -136,7 +136,8 @@ def fetch_related_channels(about_channel : AboutChannel) : Array(AboutRelatedCha
channels = YoutubeAPI.browse(browse_id: about_channel.ucid, params: "EghjaGFubmVscw%3D%3D") channels = YoutubeAPI.browse(browse_id: about_channel.ucid, params: "EghjaGFubmVscw%3D%3D")
tabs = channels.dig?("contents", "twoColumnBrowseResultsRenderer", "tabs").try(&.as_a?) || [] of JSON::Any tabs = channels.dig?("contents", "twoColumnBrowseResultsRenderer", "tabs").try(&.as_a?) || [] of JSON::Any
tab = tabs.find { |tab| tab.dig?("tabRenderer", "title").try(&.as_s?) == "Channels" } tab = tabs.find(&.dig?("tabRenderer", "title").try(&.as_s?).try(&.== "Channels"))
return [] of AboutRelatedChannel if tab.nil? return [] of AboutRelatedChannel if tab.nil?
items = tab.dig?("tabRenderer", "content", "sectionListRenderer", "contents", 0, "itemSectionRenderer", "contents", 0, "gridRenderer", "items").try(&.as_a?) || [] of JSON::Any items = tab.dig?("tabRenderer", "content", "sectionListRenderer", "contents", 0, "itemSectionRenderer", "contents", 0, "gridRenderer", "items").try(&.as_a?) || [] of JSON::Any

View file

@ -44,13 +44,9 @@ struct ChannelVideo
end end
end end
def to_json(locale, json : JSON::Builder | Nil = nil) def to_json(locale, _json : Nil = nil)
if json JSON.build do |json|
to_json(locale, json) to_json(locale, json)
else
JSON.build do |json|
to_json(locale, json)
end
end end
end end
@ -88,13 +84,9 @@ struct ChannelVideo
end end
end end
def to_xml(locale, xml : XML::Builder | Nil = nil) def to_xml(locale, _xml : Nil = nil)
if xml XML.build do |xml|
to_xml(locale, xml) to_xml(locale, xml)
else
XML.build do |xml|
to_xml(locale, xml)
end
end end
end end

View file

@ -93,10 +93,6 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
end end
contents = body["contents"]? contents = body["contents"]?
header = body["header"]? header = body["header"]?
if body["continuations"]?
# Removable? Doesn't seem like this is used.
more_replies_continuation = body["continuations"][0]["nextContinuationData"]["continuation"].as_s
end
else else
raise InfoException.new("Could not fetch comments") raise InfoException.new("Could not fetch comments")
end end

View file

@ -6,8 +6,12 @@
class InfoException < Exception class InfoException < Exception
end end
# -------------------
# Issue template
# -------------------
macro error_template(*args) macro error_template(*args)
error_template_helper(env, locale, {{*args}}) error_template_helper(env, {{*args}})
end end
def github_details(summary : String, content : String) def github_details(summary : String, content : String)
@ -22,11 +26,13 @@ def github_details(summary : String, content : String)
return HTML.escape(details) return HTML.escape(details)
end end
def error_template_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception) def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exception : Exception)
if exception.is_a?(InfoException) if exception.is_a?(InfoException)
return error_template_helper(env, locale, status_code, exception.message || "") return error_template_helper(env, status_code, exception.message || "")
end end
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "text/html" env.response.content_type = "text/html"
env.response.status_code = status_code env.response.status_code = status_code
@ -77,71 +83,101 @@ def error_template_helper(env : HTTP::Server::Context, locale : String?, status_
return templated "error" return templated "error"
end end
def error_template_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String) def error_template_helper(env : HTTP::Server::Context, status_code : Int32, message : String)
env.response.content_type = "text/html" env.response.content_type = "text/html"
env.response.status_code = status_code env.response.status_code = status_code
locale = env.get("preferences").as(Preferences).locale
error_message = translate(locale, message) error_message = translate(locale, message)
next_steps = error_redirect_helper(env, locale) next_steps = error_redirect_helper(env)
return templated "error" return templated "error"
end end
# -------------------
# Atom feeds
# -------------------
macro error_atom(*args) macro error_atom(*args)
error_atom_helper(env, locale, {{*args}}) error_atom_helper(env, {{*args}})
end end
def error_atom_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception) def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, exception : Exception)
if exception.is_a?(InfoException) if exception.is_a?(InfoException)
return error_atom_helper(env, locale, status_code, exception.message || "") return error_atom_helper(env, status_code, exception.message || "")
end end
env.response.content_type = "application/atom+xml" env.response.content_type = "application/atom+xml"
env.response.status_code = status_code env.response.status_code = status_code
return "<error>#{exception.inspect_with_backtrace}</error>" return "<error>#{exception.inspect_with_backtrace}</error>"
end end
def error_atom_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String) def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, message : String)
env.response.content_type = "application/atom+xml" env.response.content_type = "application/atom+xml"
env.response.status_code = status_code env.response.status_code = status_code
return "<error>#{message}</error>" return "<error>#{message}</error>"
end end
# -------------------
# JSON
# -------------------
macro error_json(*args) macro error_json(*args)
error_json_helper(env, locale, {{*args}}) error_json_helper(env, {{*args}})
end end
def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception, additional_fields : Hash(String, Object) | Nil) def error_json_helper(
env : HTTP::Server::Context,
status_code : Int32,
exception : Exception,
additional_fields : Hash(String, Object) | Nil = nil
)
if exception.is_a?(InfoException) if exception.is_a?(InfoException)
return error_json_helper(env, locale, status_code, exception.message || "", additional_fields) return error_json_helper(env, status_code, exception.message || "", additional_fields)
end end
env.response.content_type = "application/json" env.response.content_type = "application/json"
env.response.status_code = status_code env.response.status_code = status_code
error_message = {"error" => exception.message, "errorBacktrace" => exception.inspect_with_backtrace} error_message = {"error" => exception.message, "errorBacktrace" => exception.inspect_with_backtrace}
if additional_fields if additional_fields
error_message = error_message.merge(additional_fields) error_message = error_message.merge(additional_fields)
end end
return error_message.to_json return error_message.to_json
end end
def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, exception : Exception) def error_json_helper(
return error_json_helper(env, locale, status_code, exception, nil) env : HTTP::Server::Context,
end status_code : Int32,
message : String,
def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String, additional_fields : Hash(String, Object) | Nil) additional_fields : Hash(String, Object) | Nil = nil
)
env.response.content_type = "application/json" env.response.content_type = "application/json"
env.response.status_code = status_code env.response.status_code = status_code
error_message = {"error" => message} error_message = {"error" => message}
if additional_fields if additional_fields
error_message = error_message.merge(additional_fields) error_message = error_message.merge(additional_fields)
end end
return error_message.to_json return error_message.to_json
end end
def error_json_helper(env : HTTP::Server::Context, locale : String?, status_code : Int32, message : String) # -------------------
error_json_helper(env, locale, status_code, message, nil) # Redirect
end # -------------------
def error_redirect_helper(env : HTTP::Server::Context, locale : String?) def error_redirect_helper(env : HTTP::Server::Context)
request_path = env.request.path request_path = env.request.path
locale = env.get("preferences").as(Preferences).locale
if request_path.starts_with?("/search") || request_path.starts_with?("/watch") || if request_path.starts_with?("/search") || request_path.starts_with?("/watch") ||
request_path.starts_with?("/channel") || request_path.starts_with?("/playlist?list=PL") request_path.starts_with?("/channel") || request_path.starts_with?("/playlist?list=PL")
next_steps_text = translate(locale, "next_steps_error_message") next_steps_text = translate(locale, "next_steps_error_message")

View file

@ -94,8 +94,8 @@ def translate(locale : String?, key : String, text : String | Nil = nil) : Strin
translation = "" translation = ""
match_length = 0 match_length = 0
raw_data.as_h.each do |key, value| raw_data.as_h.each do |hash_key, value|
if md = text.try &.match(/#{key}/) if md = text.try &.match(/#{hash_key}/)
if md[0].size >= match_length if md[0].size >= match_length
translation = value.as_s translation = value.as_s
match_length = md[0].size match_length = md[0].size

View file

@ -98,9 +98,9 @@ module JSONFilter
end end
end end
group_name.split('/').each do |group_name| group_name.split('/').each do |name|
nest_stack.push({ nest_stack.push({
group_name: group_name, group_name: name,
closing_bracket_index: closing_bracket_index, closing_bracket_index: closing_bracket_index,
}) })
end end

View file

@ -175,9 +175,8 @@ module Kemal
if @cached_files.sum(&.[1][:data].bytesize) + (size = File.size(file_path)) < CACHE_LIMIT if @cached_files.sum(&.[1][:data].bytesize) + (size = File.size(file_path)) < CACHE_LIMIT
data = Bytes.new(size) data = Bytes.new(size)
File.open(file_path) do |file| File.open(file_path, &.read(data))
file.read(data)
end
filestat = File.info(file_path) filestat = File.info(file_path)
@cached_files[file_path] = {data: data, filestat: filestat} @cached_files[file_path] = {data: data, filestat: filestat}

View file

@ -42,6 +42,9 @@ end
def sign_token(key, hash) def sign_token(key, hash)
string_to_sign = [] of String string_to_sign = [] of String
# TODO: figure out which "key" variable is used
# Ameba reports a warning for "Lint/ShadowingOuterLocalVar" on this
# variable, but its preferrable to not touch that (works fine atm).
hash.each do |key, value| hash.each do |key, value|
next if key == "signature" next if key == "signature"

View file

@ -292,8 +292,8 @@ def parse_range(range)
end end
ranges = range.lchop("bytes=").split(',') ranges = range.lchop("bytes=").split(',')
ranges.each do |range| ranges.each do |r|
start_range, end_range = range.split('-') start_range, end_range = r.split('-')
start_range = start_range.to_i64? || 0_i64 start_range = start_range.to_i64? || 0_i64
end_range = end_range.to_i64? end_range = end_range.to_i64?

View file

@ -90,7 +90,7 @@ struct Playlist
property updated : Time property updated : Time
property thumbnail : String? property thumbnail : String?
def to_json(offset, locale, json : JSON::Builder, video_id : String? = nil) def to_json(offset, json : JSON::Builder, video_id : String? = nil)
json.object do json.object do
json.field "type", "playlist" json.field "type", "playlist"
json.field "title", self.title json.field "title", self.title
@ -125,7 +125,7 @@ struct Playlist
json.field "videos" do json.field "videos" do
json.array do json.array do
videos = get_playlist_videos(self, offset: offset, locale: locale, video_id: video_id) videos = get_playlist_videos(self, offset: offset, video_id: video_id)
videos.each do |video| videos.each do |video|
video.to_json(json) video.to_json(json)
end end
@ -134,13 +134,9 @@ struct Playlist
end end
end end
def to_json(offset, locale, json : JSON::Builder? = nil, video_id : String? = nil) def to_json(offset, _json : Nil = nil, video_id : String? = nil)
if json JSON.build do |json|
to_json(offset, locale, json, video_id: video_id) to_json(offset, json, video_id: video_id)
else
JSON.build do |json|
to_json(offset, locale, json, video_id: video_id)
end
end end
end end
@ -179,7 +175,7 @@ struct InvidiousPlaylist
end end
end end
def to_json(offset, locale, json : JSON::Builder, video_id : String? = nil) def to_json(offset, json : JSON::Builder, video_id : String? = nil)
json.object do json.object do
json.field "type", "invidiousPlaylist" json.field "type", "invidiousPlaylist"
json.field "title", self.title json.field "title", self.title
@ -205,22 +201,18 @@ struct InvidiousPlaylist
offset = self.index.index(index) || 0 offset = self.index.index(index) || 0
end end
videos = get_playlist_videos(self, offset: offset, locale: locale, video_id: video_id) videos = get_playlist_videos(self, offset: offset, video_id: video_id)
videos.each_with_index do |video, index| videos.each_with_index do |video, idx|
video.to_json(json, offset + index) video.to_json(json, offset + idx)
end end
end end
end end
end end
end end
def to_json(offset, locale, json : JSON::Builder? = nil, video_id : String? = nil) def to_json(offset, _json : Nil = nil, video_id : String? = nil)
if json JSON.build do |json|
to_json(offset, locale, json, video_id: video_id) to_json(offset, json, video_id: video_id)
else
JSON.build do |json|
to_json(offset, locale, json, video_id: video_id)
end
end end
end end
@ -320,7 +312,7 @@ def produce_playlist_continuation(id, index)
return continuation return continuation
end end
def get_playlist(plid, locale, refresh = true, force_refresh = false) def get_playlist(plid : String)
if plid.starts_with? "IV" if plid.starts_with? "IV"
if playlist = Invidious::Database::Playlists.select(id: plid) if playlist = Invidious::Database::Playlists.select(id: plid)
return playlist return playlist
@ -328,21 +320,21 @@ def get_playlist(plid, locale, refresh = true, force_refresh = false)
raise InfoException.new("Playlist does not exist.") raise InfoException.new("Playlist does not exist.")
end end
else else
return fetch_playlist(plid, locale) return fetch_playlist(plid)
end end
end end
def fetch_playlist(plid, locale) def fetch_playlist(plid : String)
if plid.starts_with? "UC" if plid.starts_with? "UC"
plid = "UU#{plid.lchop("UC")}" plid = "UU#{plid.lchop("UC")}"
end end
initial_data = YoutubeAPI.browse("VL" + plid, params: "") initial_data = YoutubeAPI.browse("VL" + plid, params: "")
playlist_sidebar_renderer = initial_data["sidebar"]?.try &.["playlistSidebarRenderer"]?.try &.["items"]? playlist_sidebar_renderer = initial_data.dig?("sidebar", "playlistSidebarRenderer", "items")
raise InfoException.new("Could not extract playlistSidebarRenderer.") if !playlist_sidebar_renderer raise InfoException.new("Could not extract playlistSidebarRenderer.") if !playlist_sidebar_renderer
playlist_info = playlist_sidebar_renderer[0]["playlistSidebarPrimaryInfoRenderer"]? playlist_info = playlist_sidebar_renderer.dig?(0, "playlistSidebarPrimaryInfoRenderer")
raise InfoException.new("Could not extract playlist info") if !playlist_info raise InfoException.new("Could not extract playlist info") if !playlist_info
title = playlist_info.dig?("title", "runs", 0, "text").try &.as_s || "" title = playlist_info.dig?("title", "runs", 0, "text").try &.as_s || ""
@ -355,12 +347,15 @@ def fetch_playlist(plid, locale)
description_html = desc_item.try &.["runs"]?.try &.as_a description_html = desc_item.try &.["runs"]?.try &.as_a
.try { |run| content_to_comment_html(run).try &.to_s } || "<p></p>" .try { |run| content_to_comment_html(run).try &.to_s } || "<p></p>"
thumbnail = playlist_info["thumbnailRenderer"]?.try &.["playlistVideoThumbnailRenderer"]? thumbnail = playlist_info.dig?(
.try &.["thumbnail"]["thumbnails"][0]["url"]?.try &.as_s "thumbnailRenderer", "playlistVideoThumbnailRenderer",
"thumbnail", "thumbnails", 0, "url"
).try &.as_s
views = 0_i64 views = 0_i64
updated = Time.utc updated = Time.utc
video_count = 0 video_count = 0
playlist_info["stats"]?.try &.as_a.each do |stat| playlist_info["stats"]?.try &.as_a.each do |stat|
text = stat["runs"]?.try &.as_a.map(&.["text"].as_s).join("") || stat["simpleText"]?.try &.as_s text = stat["runs"]?.try &.as_a.map(&.["text"].as_s).join("") || stat["simpleText"]?.try &.as_s
next if !text next if !text
@ -379,12 +374,15 @@ def fetch_playlist(plid, locale)
author_thumbnail = "" author_thumbnail = ""
ucid = "" ucid = ""
else else
author_info = playlist_sidebar_renderer[1]["playlistSidebarSecondaryInfoRenderer"]?.try &.["videoOwner"]["videoOwnerRenderer"]? author_info = playlist_sidebar_renderer[1].dig?(
"playlistSidebarSecondaryInfoRenderer", "videoOwner", "videoOwnerRenderer"
)
raise InfoException.new("Could not extract author info") if !author_info raise InfoException.new("Could not extract author info") if !author_info
author = author_info["title"]["runs"][0]["text"]?.try &.as_s || "" author = author_info.dig?("title", "runs", 0, "text").try &.as_s || ""
author_thumbnail = author_info["thumbnail"]["thumbnails"][0]["url"]?.try &.as_s || "" author_thumbnail = author_info.dig?("thumbnail", "thumbnails", 0, "url").try &.as_s || ""
ucid = author_info["title"]["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"]?.try &.as_s || "" ucid = author_info.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
end end
return Playlist.new({ return Playlist.new({
@ -402,7 +400,7 @@ def fetch_playlist(plid, locale)
}) })
end end
def get_playlist_videos(playlist, offset, locale = nil, video_id = nil) def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32, video_id = nil)
# Show empy playlist if requested page is out of range # Show empy playlist if requested page is out of range
# (e.g, when a new playlist has been created, offset will be negative) # (e.g, when a new playlist has been created, offset will be negative)
if offset >= playlist.video_count || offset < 0 if offset >= playlist.video_count || offset < 0
@ -465,7 +463,6 @@ def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
plid = i["navigationEndpoint"]["watchEndpoint"]["playlistId"].as_s plid = i["navigationEndpoint"]["watchEndpoint"]["playlistId"].as_s
index = i["navigationEndpoint"]["watchEndpoint"]["index"].as_i64 index = i["navigationEndpoint"]["watchEndpoint"]["index"].as_i64
thumbnail = i["thumbnail"]["thumbnails"][0]["url"].as_s
title = i["title"].try { |t| t["simpleText"]? || t["runs"]?.try &.[0]["text"]? }.try &.as_s || "" title = i["title"].try { |t| t["simpleText"]? || t["runs"]?.try &.[0]["text"]? }.try &.as_s || ""
author = i["shortBylineText"]?.try &.["runs"][0]["text"].as_s || "" author = i["shortBylineText"]?.try &.["runs"][0]["text"].as_s || ""
ucid = i["shortBylineText"]?.try &.["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s || "" ucid = i["shortBylineText"]?.try &.["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s || ""

View file

@ -98,7 +98,7 @@ module Invidious::Routes::API::Manifest
height = fmt["height"].as_i height = fmt["height"].as_i
# Resolutions reported by YouTube player (may not accurately reflect source) # Resolutions reported by YouTube player (may not accurately reflect source)
height = potential_heights.min_by { |i| (height - i).abs } height = potential_heights.min_by { |x| (height - x).abs }
next if unique_res && heights.includes? height next if unique_res && heights.includes? height
heights << height heights << height

View file

@ -115,8 +115,6 @@ module Invidious::Routes::API::V1::Authenticated
end end
def self.list_playlists(env) def self.list_playlists(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
@ -125,7 +123,7 @@ module Invidious::Routes::API::V1::Authenticated
JSON.build do |json| JSON.build do |json|
json.array do json.array do
playlists.each do |playlist| playlists.each do |playlist|
playlist.to_json(0, locale, json) playlist.to_json(0, json)
end end
end end
end end
@ -134,14 +132,13 @@ module Invidious::Routes::API::V1::Authenticated
def self.create_playlist(env) def self.create_playlist(env)
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
locale = env.get("preferences").as(Preferences).locale
title = env.params.json["title"]?.try &.as(String).delete("<>").byte_slice(0, 150) title = env.params.json["title"]?.try &.as(String).delete("<>").byte_slice(0, 150)
if !title if !title
return error_json(400, "Invalid title.") return error_json(400, "Invalid title.")
end end
privacy = env.params.json["privacy"]?.try { |privacy| PlaylistPrivacy.parse(privacy.as(String).downcase) } privacy = env.params.json["privacy"]?.try { |p| PlaylistPrivacy.parse(p.as(String).downcase) }
if !privacy if !privacy
return error_json(400, "Invalid privacy setting.") return error_json(400, "Invalid privacy setting.")
end end
@ -160,8 +157,6 @@ module Invidious::Routes::API::V1::Authenticated
end end
def self.update_playlist_attribute(env) def self.update_playlist_attribute(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
@ -180,7 +175,7 @@ module Invidious::Routes::API::V1::Authenticated
end end
title = env.params.json["title"].try &.as(String).delete("<>").byte_slice(0, 150) || playlist.title title = env.params.json["title"].try &.as(String).delete("<>").byte_slice(0, 150) || playlist.title
privacy = env.params.json["privacy"]?.try { |privacy| PlaylistPrivacy.parse(privacy.as(String).downcase) } || playlist.privacy privacy = env.params.json["privacy"]?.try { |p| PlaylistPrivacy.parse(p.as(String).downcase) } || playlist.privacy
description = env.params.json["description"]?.try &.as(String).delete("\r") || playlist.description description = env.params.json["description"]?.try &.as(String).delete("\r") || playlist.description
if title != playlist.title || if title != playlist.title ||
@ -197,8 +192,6 @@ module Invidious::Routes::API::V1::Authenticated
end end
def self.delete_playlist(env) def self.delete_playlist(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
@ -219,8 +212,6 @@ module Invidious::Routes::API::V1::Authenticated
end end
def self.insert_video_into_playlist(env) def self.insert_video_into_playlist(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
@ -274,8 +265,6 @@ module Invidious::Routes::API::V1::Authenticated
end end
def self.delete_video_in_playlist(env) def self.delete_video_in_playlist(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
@ -389,8 +378,8 @@ module Invidious::Routes::API::V1::Authenticated
end end
def self.unregister_token(env) def self.unregister_token(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
scopes = env.get("scopes").as(Array(String)) scopes = env.get("scopes").as(Array(String))

View file

@ -254,7 +254,7 @@ module Invidious::Routes::API::V1::Channels
page = env.params.query["page"]?.try &.to_i? page = env.params.query["page"]?.try &.to_i?
page ||= 1 page ||= 1
count, search_results = channel_search(query, page, ucid) search_results = channel_search(query, page, ucid)
JSON.build do |json| JSON.build do |json|
json.array do json.array do
search_results.each do |item| search_results.each do |item|

View file

@ -1,7 +1,6 @@
module Invidious::Routes::API::V1::Misc module Invidious::Routes::API::V1::Misc
# Stats API endpoint for Invidious # Stats API endpoint for Invidious
def self.stats(env) def self.stats(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
if !CONFIG.statistics_enabled if !CONFIG.statistics_enabled
@ -14,9 +13,7 @@ module Invidious::Routes::API::V1::Misc
# APIv1 currently uses the same logic for both # APIv1 currently uses the same logic for both
# user playlists and Invidious playlists. This means that we can't # user playlists and Invidious playlists. This means that we can't
# reasonably split them yet. This should be addressed in APIv2 # reasonably split them yet. This should be addressed in APIv2
def self.get_playlist(env) def self.get_playlist(env : HTTP::Server::Context)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
plid = env.params.url["plid"] plid = env.params.url["plid"]
@ -34,7 +31,7 @@ module Invidious::Routes::API::V1::Misc
end end
begin begin
playlist = get_playlist(plid, locale) playlist = get_playlist(plid)
rescue ex : InfoException rescue ex : InfoException
return error_json(404, ex) return error_json(404, ex)
rescue ex rescue ex
@ -49,7 +46,7 @@ module Invidious::Routes::API::V1::Misc
# includes into the playlist a maximum of 20 videos, before the offset # includes into the playlist a maximum of 20 videos, before the offset
if offset > 0 if offset > 0
lookback = offset < 50 ? offset : 50 lookback = offset < 50 ? offset : 50
response = playlist.to_json(offset - lookback, locale) response = playlist.to_json(offset - lookback)
json_response = JSON.parse(response) json_response = JSON.parse(response)
else else
# Unless the continuation is really the offset 0, it becomes expensive. # Unless the continuation is really the offset 0, it becomes expensive.
@ -58,13 +55,13 @@ module Invidious::Routes::API::V1::Misc
# it shouldn't happen often though # it shouldn't happen often though
lookback = 0 lookback = 0
response = playlist.to_json(offset, locale, video_id: video_id) response = playlist.to_json(offset, video_id: video_id)
json_response = JSON.parse(response) json_response = JSON.parse(response)
if json_response["videos"].as_a[0]["index"] != offset if json_response["videos"].as_a[0]["index"] != offset
offset = json_response["videos"].as_a[0]["index"].as_i offset = json_response["videos"].as_a[0]["index"].as_i
lookback = offset < 50 ? offset : 50 lookback = offset < 50 ? offset : 50
response = playlist.to_json(offset - lookback, locale) response = playlist.to_json(offset - lookback)
json_response = JSON.parse(response) json_response = JSON.parse(response)
end end
end end

View file

@ -32,7 +32,7 @@ module Invidious::Routes::API::V1::Search
return error_json(400, ex) return error_json(400, ex)
end end
count, search_results = search(query, search_params, region).as(Tuple) search_results = search(query, search_params, region)
JSON.build do |json| JSON.build do |json|
json.array do json.array do
search_results.each do |item| search_results.each do |item|

View file

@ -20,8 +20,6 @@ module Invidious::Routes::API::V1::Videos
end end
def self.captions(env) def self.captions(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
id = env.params.url["id"] id = env.params.url["id"]
@ -73,9 +71,9 @@ module Invidious::Routes::API::V1::Videos
env.response.content_type = "text/vtt; charset=UTF-8" env.response.content_type = "text/vtt; charset=UTF-8"
if lang if lang
caption = captions.select { |caption| caption.language_code == lang } caption = captions.select(&.language_code.== lang)
else else
caption = captions.select { |caption| caption.name == label } caption = captions.select(&.name.== label)
end end
if caption.empty? if caption.empty?
@ -149,8 +147,6 @@ module Invidious::Routes::API::V1::Videos
# thumbnails for individual scenes in a video. # thumbnails for individual scenes in a video.
# See https://support.jwplayer.com/articles/how-to-add-preview-thumbnails # See https://support.jwplayer.com/articles/how-to-add-preview-thumbnails
def self.storyboards(env) def self.storyboards(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "application/json" env.response.content_type = "application/json"
id = env.params.url["id"] id = env.params.url["id"]
@ -183,7 +179,7 @@ module Invidious::Routes::API::V1::Videos
env.response.content_type = "text/vtt" env.response.content_type = "text/vtt"
storyboard = storyboards.select { |storyboard| width == "#{storyboard[:width]}" || height == "#{storyboard[:height]}" } storyboard = storyboards.select { |sb| width == "#{sb[:width]}" || height == "#{sb[:height]}" }
if storyboard.empty? if storyboard.empty?
haltf env, 404 haltf env, 404
@ -223,8 +219,6 @@ module Invidious::Routes::API::V1::Videos
end end
def self.annotations(env) def self.annotations(env)
locale = env.get("preferences").as(Preferences).locale
env.response.content_type = "text/xml" env.response.content_type = "text/xml"
id = env.params.url["id"] id = env.params.url["id"]

View file

@ -2,13 +2,11 @@
module Invidious::Routes::Embed module Invidious::Routes::Embed
def self.redirect(env) def self.redirect(env)
locale = env.get("preferences").as(Preferences).locale
if plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "") if plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "")
begin begin
playlist = get_playlist(plid, locale: locale) playlist = get_playlist(plid)
offset = env.params.query["index"]?.try &.to_i? || 0 offset = env.params.query["index"]?.try &.to_i? || 0
videos = get_playlist_videos(playlist, offset: offset, locale: locale) videos = get_playlist_videos(playlist, offset: offset)
rescue ex rescue ex
return error_template(500, ex) return error_template(500, ex)
end end
@ -26,7 +24,6 @@ module Invidious::Routes::Embed
end end
def self.show(env) def self.show(env)
locale = env.get("preferences").as(Preferences).locale
id = env.params.url["id"] id = env.params.url["id"]
plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "") plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "")
@ -60,9 +57,9 @@ module Invidious::Routes::Embed
if plid if plid
begin begin
playlist = get_playlist(plid, locale: locale) playlist = get_playlist(plid)
offset = env.params.query["index"]?.try &.to_i? || 0 offset = env.params.query["index"]?.try &.to_i? || 0
videos = get_playlist_videos(playlist, offset: offset, locale: locale) videos = get_playlist_videos(playlist, offset: offset)
rescue ex rescue ex
return error_template(500, ex) return error_template(500, ex)
end end

View file

@ -265,7 +265,7 @@ module Invidious::Routes::Feeds
if plid.starts_with? "IV" if plid.starts_with? "IV"
if playlist = Invidious::Database::Playlists.select(id: plid) if playlist = Invidious::Database::Playlists.select(id: plid)
videos = get_playlist_videos(playlist, offset: 0, locale: locale) videos = get_playlist_videos(playlist, offset: 0)
return XML.build(indent: " ", encoding: "UTF-8") do |xml| return XML.build(indent: " ", encoding: "UTF-8") do |xml|
xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015",

View file

@ -425,9 +425,9 @@ module Invidious::Routes::Login
found_valid_captcha = false found_valid_captcha = false
error_exception = Exception.new error_exception = Exception.new
tokens.each do |token| tokens.each do |tok|
begin begin
validate_request(token, answer, env.request, HMAC_KEY, locale) validate_request(tok, answer, env.request, HMAC_KEY, locale)
found_valid_captcha = true found_valid_captcha = true
rescue ex rescue ex
error_exception = ex error_exception = ex

View file

@ -66,7 +66,7 @@ module Invidious::Routes::Playlists
user = user.as(User) user = user.as(User)
playlist_id = env.params.query["list"] playlist_id = env.params.query["list"]
playlist = get_playlist(playlist_id, locale) playlist = get_playlist(playlist_id)
subscribe_playlist(user, playlist) subscribe_playlist(user, playlist)
env.redirect "/playlist?list=#{playlist.id}" env.redirect "/playlist?list=#{playlist.id}"
@ -157,7 +157,7 @@ module Invidious::Routes::Playlists
end end
begin begin
videos = get_playlist_videos(playlist, offset: (page - 1) * 100, locale: locale) videos = get_playlist_videos(playlist, offset: (page - 1) * 100)
rescue ex rescue ex
videos = [] of PlaylistVideo videos = [] of PlaylistVideo
end end
@ -239,15 +239,13 @@ module Invidious::Routes::Playlists
query = env.params.query["q"]? query = env.params.query["q"]?
if query if query
begin begin
search_query, count, items, operators = process_search_query(query, page, user, region: nil) search_query, items, operators = process_search_query(query, page, user, region: nil)
videos = items.select(SearchVideo).map(&.as(SearchVideo)) videos = items.select(SearchVideo).map(&.as(SearchVideo))
rescue ex rescue ex
videos = [] of SearchVideo videos = [] of SearchVideo
count = 0
end end
else else
videos = [] of SearchVideo videos = [] of SearchVideo
count = 0
end end
env.set "add_playlist_items", plid env.set "add_playlist_items", plid
@ -306,7 +304,7 @@ module Invidious::Routes::Playlists
begin begin
playlist_id = env.params.query["playlist_id"] playlist_id = env.params.query["playlist_id"]
playlist = get_playlist(playlist_id, locale).as(InvidiousPlaylist) playlist = get_playlist(playlist_id).as(InvidiousPlaylist)
raise "Invalid user" if playlist.author != user.email raise "Invalid user" if playlist.author != user.email
rescue ex rescue ex
if redirect if redirect
@ -397,7 +395,7 @@ module Invidious::Routes::Playlists
end end
begin begin
playlist = get_playlist(plid, locale) playlist = get_playlist(plid)
rescue ex rescue ex
return error_template(500, ex) return error_template(500, ex)
end end
@ -414,7 +412,7 @@ module Invidious::Routes::Playlists
end end
begin begin
videos = get_playlist_videos(playlist, offset: (page - 1) * 100, locale: locale) videos = get_playlist_videos(playlist, offset: (page - 1) * 100)
rescue ex rescue ex
return error_template(500, "Error encountered while retrieving playlist videos.<br>#{ex.message}") return error_template(500, "Error encountered while retrieving playlist videos.<br>#{ex.message}")
end end

View file

@ -54,7 +54,7 @@ module Invidious::Routes::Search
user = env.get? "user" user = env.get? "user"
begin begin
search_query, count, videos, operators = process_search_query(query, page, user, region: region) search_query, videos, operators = process_search_query(query, page, user, region: region)
rescue ex : ChannelSearchException rescue ex : ChannelSearchException
return error_template(404, "Unable to find channel with id of '#{HTML.escape(ex.channel)}'. Are you sure that's an actual channel id? It should look like 'UC4QobU6STFB0P71PMvOGN5A'.") return error_template(404, "Unable to find channel with id of '#{HTML.escape(ex.channel)}'. Are you sure that's an actual channel id? It should look like 'UC4QobU6STFB0P71PMvOGN5A'.")
rescue ex rescue ex

View file

@ -75,8 +75,8 @@ module Invidious::Routes::VideoPlayback
end end
begin begin
client.get(url, headers) do |response| client.get(url, headers) do |resp|
response.headers.each do |key, value| resp.headers.each do |key, value|
if !RESPONSE_HEADERS_BLACKLIST.includes?(key.downcase) if !RESPONSE_HEADERS_BLACKLIST.includes?(key.downcase)
env.response.headers[key] = value env.response.headers[key] = value
end end
@ -84,7 +84,7 @@ module Invidious::Routes::VideoPlayback
env.response.headers["Access-Control-Allow-Origin"] = "*" env.response.headers["Access-Control-Allow-Origin"] = "*"
if location = response.headers["Location"]? if location = resp.headers["Location"]?
location = URI.parse(location) location = URI.parse(location)
location = "#{location.request_target}&host=#{location.host}" location = "#{location.request_target}&host=#{location.host}"
@ -95,7 +95,7 @@ module Invidious::Routes::VideoPlayback
return env.redirect location return env.redirect location
end end
IO.copy(response.body_io, env.response) IO.copy(resp.body_io, env.response)
end end
rescue ex rescue ex
end end
@ -132,15 +132,15 @@ module Invidious::Routes::VideoPlayback
headers["Range"] = "bytes=#{chunk_start}-#{chunk_end}" headers["Range"] = "bytes=#{chunk_start}-#{chunk_end}"
begin begin
client.get(url, headers) do |response| client.get(url, headers) do |resp|
if first_chunk if first_chunk
if !env.request.headers["Range"]? && response.status_code == 206 if !env.request.headers["Range"]? && resp.status_code == 206
env.response.status_code = 200 env.response.status_code = 200
else else
env.response.status_code = response.status_code env.response.status_code = resp.status_code
end end
response.headers.each do |key, value| resp.headers.each do |key, value|
if !RESPONSE_HEADERS_BLACKLIST.includes?(key.downcase) && key.downcase != "content-range" if !RESPONSE_HEADERS_BLACKLIST.includes?(key.downcase) && key.downcase != "content-range"
env.response.headers[key] = value env.response.headers[key] = value
end end
@ -148,7 +148,7 @@ module Invidious::Routes::VideoPlayback
env.response.headers["Access-Control-Allow-Origin"] = "*" env.response.headers["Access-Control-Allow-Origin"] = "*"
if location = response.headers["Location"]? if location = resp.headers["Location"]?
location = URI.parse(location) location = URI.parse(location)
location = "#{location.request_target}&host=#{location.host}#{region ? "&region=#{region}" : ""}" location = "#{location.request_target}&host=#{location.host}#{region ? "&region=#{region}" : ""}"
@ -161,8 +161,8 @@ module Invidious::Routes::VideoPlayback
env.response.headers["Content-Disposition"] = "attachment; filename=\"#{URI.encode_www_form(title)}\"; filename*=UTF-8''#{URI.encode_www_form(title)}" env.response.headers["Content-Disposition"] = "attachment; filename=\"#{URI.encode_www_form(title)}\"; filename*=UTF-8''#{URI.encode_www_form(title)}"
end end
if !response.headers.includes_word?("Transfer-Encoding", "chunked") if !resp.headers.includes_word?("Transfer-Encoding", "chunked")
content_length = response.headers["Content-Range"].split("/")[-1].to_i64 content_length = resp.headers["Content-Range"].split("/")[-1].to_i64
if env.request.headers["Range"]? if env.request.headers["Range"]?
env.response.headers["Content-Range"] = "bytes #{range_start}-#{range_end || (content_length - 1)}/#{content_length}" env.response.headers["Content-Range"] = "bytes #{range_start}-#{range_end || (content_length - 1)}/#{content_length}"
env.response.content_length = ((range_end.try &.+ 1) || content_length) - range_start env.response.content_length = ((range_end.try &.+ 1) || content_length) - range_start
@ -172,7 +172,7 @@ module Invidious::Routes::VideoPlayback
end end
end end
proxy_file(response, env) proxy_file(resp, env)
end end
rescue ex rescue ex
if ex.message != "Error reading socket: Connection reset by peer" if ex.message != "Error reading socket: Connection reset by peer"

View file

@ -5,7 +5,7 @@ class ChannelSearchException < InfoException
end end
end end
def channel_search(query, page, channel) def channel_search(query, page, channel) : Array(SearchItem)
response = YT_POOL.client &.get("/channel/#{channel}") response = YT_POOL.client &.get("/channel/#{channel}")
if response.status_code == 404 if response.status_code == 404
@ -24,25 +24,23 @@ def channel_search(query, page, channel)
continuation_items = response_json["onResponseReceivedActions"]? continuation_items = response_json["onResponseReceivedActions"]?
.try &.[0]["appendContinuationItemsAction"]["continuationItems"] .try &.[0]["appendContinuationItemsAction"]["continuationItems"]
return 0, [] of SearchItem if !continuation_items return [] of SearchItem if !continuation_items
items = [] of SearchItem items = [] of SearchItem
continuation_items.as_a.select(&.as_h.has_key?("itemSectionRenderer")).each { |item| continuation_items.as_a.select(&.as_h.has_key?("itemSectionRenderer")).each do |item|
extract_item(item["itemSectionRenderer"]["contents"].as_a[0]) extract_item(item["itemSectionRenderer"]["contents"].as_a[0]).try { |t| items << t }
.try { |t| items << t } end
}
return items.size, items return items
end end
def search(query, search_params = produce_search_params(content_type: "all"), region = nil) def search(query, search_params = produce_search_params(content_type: "all"), region = nil) : Array(SearchItem)
return 0, [] of SearchItem if query.empty? return [] of SearchItem if query.empty?
client_config = YoutubeAPI::ClientConfig.new(region: region) client_config = YoutubeAPI::ClientConfig.new(region: region)
initial_data = YoutubeAPI.search(query, search_params, client_config: client_config) initial_data = YoutubeAPI.search(query, search_params, client_config: client_config)
items = extract_items(initial_data)
return items.size, items return extract_items(initial_data)
end end
def produce_search_params(page = 1, sort : String = "relevance", date : String = "", content_type : String = "", def produce_search_params(page = 1, sort : String = "relevance", date : String = "", content_type : String = "",
@ -217,7 +215,7 @@ def process_search_query(query, page, user, region)
search_query = (query.split(" ") - operators).join(" ") search_query = (query.split(" ") - operators).join(" ")
if channel if channel
count, items = channel_search(search_query, page, channel) items = channel_search(search_query, page, channel)
elsif subscriptions elsif subscriptions
if view_name if view_name
items = PG_DB.query_all("SELECT id,title,published,updated,ucid,author,length_seconds FROM ( items = PG_DB.query_all("SELECT id,title,published,updated,ucid,author,length_seconds FROM (
@ -227,16 +225,14 @@ def process_search_query(query, page, user, region)
as document as document
FROM #{view_name} FROM #{view_name}
) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;", search_query, (page - 1) * 20, as: ChannelVideo) ) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;", search_query, (page - 1) * 20, as: ChannelVideo)
count = items.size
else else
items = [] of ChannelVideo items = [] of ChannelVideo
count = 0
end end
else else
search_params = produce_search_params(page: page, sort: sort, date: date, content_type: content_type, search_params = produce_search_params(page: page, sort: sort, date: date, content_type: content_type,
duration: duration, features: features) duration: duration, features: features)
count, items = search(search_query, search_params, region).as(Tuple) items = search(search_query, search_params, region)
end end
# Light processing to flatten search results out of Categories. # Light processing to flatten search results out of Categories.
@ -254,5 +250,5 @@ def process_search_query(query, page, user, region)
end end
end end
{search_query, items_without_category.size, items_without_category, operators} {search_query, items_without_category, operators}
end end

View file

@ -65,7 +65,6 @@ def fetch_user(sid, headers)
feed = YT_POOL.client &.get("/subscription_manager?disable_polymer=1", headers) feed = YT_POOL.client &.get("/subscription_manager?disable_polymer=1", headers)
feed = XML.parse_html(feed.body) feed = XML.parse_html(feed.body)
channels = [] of String
channels = feed.xpath_nodes(%q(//ul[@id="guide-channels"]/li/a)).compact_map do |channel| channels = feed.xpath_nodes(%q(//ul[@id="guide-channels"]/li/a)).compact_map do |channel|
if {"Popular on YouTube", "Music", "Sports", "Gaming"}.includes? channel["title"] if {"Popular on YouTube", "Music", "Sports", "Gaming"}.includes? channel["title"]
nil nil
@ -157,12 +156,11 @@ def generate_captcha(key)
</svg> </svg>
END_SVG END_SVG
image = "" image = "data:image/png;base64,"
convert = Process.run(%(rsvg-convert -w 400 -h 400 -b none -f png), shell: true, image += Process.run(%(rsvg-convert -w 400 -h 400 -b none -f png), shell: true,
input: IO::Memory.new(clock_svg), output: Process::Redirect::Pipe) do |proc| input: IO::Memory.new(clock_svg), output: Process::Redirect::Pipe
image = proc.output.gets_to_end ) do |proc|
image = Base64.strict_encode(image) Base64.strict_encode(proc.output.gets_to_end)
image = "data:image/png;base64,#{image}"
end end
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}" answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"

View file

@ -497,7 +497,7 @@ struct Video
end end
def length_seconds : Int32 def length_seconds : Int32
info["microformat"]?.try &.["playerMicroformatRenderer"]?.try &.["lengthSeconds"]?.try &.as_s.to_i || info.dig?("microformat", "playerMicroformatRenderer", "lengthSeconds").try &.as_s.to_i ||
info["videoDetails"]["lengthSeconds"]?.try &.as_s.to_i || 0 info["videoDetails"]["lengthSeconds"]?.try &.as_s.to_i || 0
end end
@ -519,7 +519,9 @@ struct Video
end end
def published : Time def published : Time
info["microformat"]?.try &.["playerMicroformatRenderer"]?.try &.["publishDate"]?.try { |t| Time.parse(t.as_s, "%Y-%m-%d", Time::Location::UTC) } || Time.utc info
.dig?("microformat", "playerMicroformatRenderer", "publishDate")
.try { |t| Time.parse(t.as_s, "%Y-%m-%d", Time::Location::UTC) } || Time.utc
end end
def published=(other : Time) def published=(other : Time)
@ -545,8 +547,9 @@ struct Video
end end
def premiere_timestamp : Time? def premiere_timestamp : Time?
info["microformat"]?.try &.["playerMicroformatRenderer"]? info
.try &.["liveBroadcastDetails"]?.try &.["startTimestamp"]?.try { |t| Time.parse_rfc3339(t.as_s) } .dig?("microformat", "playerMicroformatRenderer", "liveBroadcastDetails", "startTimestamp")
.try { |t| Time.parse_rfc3339(t.as_s) }
end end
def keywords def keywords
@ -558,8 +561,9 @@ struct Video
end end
def allowed_regions def allowed_regions
info["microformat"]?.try &.["playerMicroformatRenderer"]? info
.try &.["availableCountries"]?.try &.as_a.map &.as_s || [] of String .dig("microformat", "playerMicroformatRenderer", "availableCountries")
.try &.as_a.map &.as_s || [] of String
end end
def author_thumbnail : String def author_thumbnail : String
@ -621,18 +625,11 @@ struct Video
end end
def storyboards def storyboards
storyboards = info["storyboards"]? storyboards = info.dig?("storyboards", "playerStoryboardSpecRenderer", "spec")
.try &.as_h .try &.as_s.split("|")
.try &.["playerStoryboardSpecRenderer"]?
.try &.["spec"]?
.try &.as_s.split("|")
if !storyboards if !storyboards
if storyboard = info["storyboards"]? if storyboard = info.dig?("storyboards", "playerLiveStoryboardSpecRenderer", "spec").try &.as_s
.try &.as_h
.try &.["playerLiveStoryboardSpecRenderer"]?
.try &.["spec"]?
.try &.as_s
return [{ return [{
url: storyboard.split("#")[0], url: storyboard.split("#")[0],
width: 106, width: 106,
@ -661,8 +658,8 @@ struct Video
url = URI.parse(storyboards.shift) url = URI.parse(storyboards.shift)
params = HTTP::Params.parse(url.query || "") params = HTTP::Params.parse(url.query || "")
storyboards.each_with_index do |storyboard, i| storyboards.each_with_index do |sb, i|
width, height, count, storyboard_width, storyboard_height, interval, _, sigh = storyboard.split("#") width, height, count, storyboard_width, storyboard_height, interval, _, sigh = sb.split("#")
params["sigh"] = sigh params["sigh"] = sigh
url.query = params.to_s url.query = params.to_s
@ -690,9 +687,8 @@ struct Video
end end
def paid def paid
reason = info["playabilityStatus"]?.try &.["reason"]? reason = info.dig?("playabilityStatus", "reason").try &.as_s || ""
paid = reason == "This video requires payment to watch." ? true : false return reason.includes? "requires payment"
paid
end end
def premium def premium
@ -716,8 +712,9 @@ struct Video
end end
def description def description
description = info["microformat"]?.try &.["playerMicroformatRenderer"]? description = info
.try &.["description"]?.try &.["simpleText"]?.try &.as_s || "" .dig?("microformat", "playerMicroformatRenderer", "description", "simpleText")
.try &.as_s || ""
end end
# TODO # TODO
@ -738,11 +735,11 @@ struct Video
end end
def hls_manifest_url : String? def hls_manifest_url : String?
info["streamingData"]?.try &.["hlsManifestUrl"]?.try &.as_s info.dig?("streamingData", "hlsManifestUrl").try &.as_s
end end
def dash_manifest_url def dash_manifest_url
info["streamingData"]?.try &.["dashManifestUrl"]?.try &.as_s info.dig?("streamingData", "dashManifestUrl").try &.as_s
end end
def genre : String def genre : String
@ -758,7 +755,7 @@ struct Video
end end
def is_family_friendly : Bool def is_family_friendly : Bool
info["microformat"]?.try &.["playerMicroformatRenderer"]["isFamilySafe"]?.try &.as_bool || false info.dig?("microformat", "playerMicroformatRenderer", "isFamilySafe").try &.as_bool || false
end end
def is_vr : Bool? def is_vr : Bool?

View file

@ -48,7 +48,7 @@
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if count >= 20 %> <% if videos.size >= 20 %>
<a href="/add_playlist_items?list=<%= plid %>&q=<%= URI.encode_www_form(query.not_nil!) %>&page=<%= page + 1 %>"> <a href="/add_playlist_items?list=<%= plid %>&q=<%= URI.encode_www_form(query.not_nil!) %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>

View file

@ -5,7 +5,7 @@
<% search_query_encoded = env.get?("search").try { |x| URI.encode_www_form(x.as(String), space_to_plus: true) } %> <% search_query_encoded = env.get?("search").try { |x| URI.encode_www_form(x.as(String), space_to_plus: true) } %>
<!-- Search redirection and filtering UI --> <!-- Search redirection and filtering UI -->
<% if count == 0 %> <% if videos.size == 0 %>
<h3 style="text-align: center"> <h3 style="text-align: center">
<a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Broken? Try another Invidious Instance!") %></a> <a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Broken? Try another Invidious Instance!") %></a>
</h3> </h3>
@ -98,7 +98,7 @@
</details> </details>
<% end %> <% end %>
<% if count == 0 %> <% if videos.size == 0 %>
<hr style="margin: 0;"/> <hr style="margin: 0;"/>
<% else %> <% else %>
<hr/> <hr/>
@ -114,7 +114,7 @@
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if count >= 20 %> <% if videos.size >= 20 %>
<a href="/search?q=<%= search_query_encoded %>&page=<%= page + 1 %>"> <a href="/search?q=<%= search_query_encoded %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>
@ -138,7 +138,7 @@
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if count >= 20 %> <% if videos.size >= 20 %>
<a href="/search?q=<%= search_query_encoded %>&page=<%= page + 1 %>"> <a href="/search?q=<%= search_query_encoded %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>

View file

@ -416,10 +416,9 @@ module YoutubeAPI
# Send the POST request # Send the POST request
if {{ !flag?(:disable_quic) }} && CONFIG.use_quic if {{ !flag?(:disable_quic) }} && CONFIG.use_quic
# Using QUIC client # Using QUIC client
response = YT_POOL.client(client_config.proxy_region, body = YT_POOL.client(client_config.proxy_region,
&.post(url, headers: headers, body: data.to_json) &.post(url, headers: headers, body: data.to_json)
) ).body
body = response.body
else else
# Using HTTP client # Using HTTP client
body = YT_POOL.client(client_config.proxy_region) do |client| body = YT_POOL.client(client_config.proxy_region) do |client|