Merge pull request #2827 from SamantazFox/more-code-cleanup
More code cleanup
This commit is contained in:
commit
fc5f84a0cd
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -44,15 +44,11 @@ struct ChannelVideo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_json(locale, json : JSON::Builder | Nil = nil)
|
def to_json(locale, _json : Nil = nil)
|
||||||
if json
|
|
||||||
to_json(locale, json)
|
|
||||||
else
|
|
||||||
JSON.build do |json|
|
JSON.build do |json|
|
||||||
to_json(locale, json)
|
to_json(locale, json)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def to_xml(locale, query_params, xml : XML::Builder)
|
def to_xml(locale, query_params, xml : XML::Builder)
|
||||||
query_params["v"] = self.id
|
query_params["v"] = self.id
|
||||||
|
@ -88,15 +84,11 @@ struct ChannelVideo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_xml(locale, xml : XML::Builder | Nil = nil)
|
def to_xml(locale, _xml : Nil = nil)
|
||||||
if xml
|
|
||||||
to_xml(locale, xml)
|
|
||||||
else
|
|
||||||
XML.build do |xml|
|
XML.build do |xml|
|
||||||
to_xml(locale, xml)
|
to_xml(locale, xml)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def to_tuple
|
def to_tuple
|
||||||
{% begin %}
|
{% begin %}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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
|
|
||||||
to_json(offset, locale, json, video_id: video_id)
|
|
||||||
else
|
|
||||||
JSON.build do |json|
|
JSON.build do |json|
|
||||||
to_json(offset, locale, json, video_id: video_id)
|
to_json(offset, 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
|
|
||||||
to_json(offset, locale, json, video_id: video_id)
|
|
||||||
else
|
|
||||||
JSON.build do |json|
|
JSON.build do |json|
|
||||||
to_json(offset, locale, json, video_id: video_id)
|
to_json(offset, 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 || ""
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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|
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ? "®ion=#{region}" : ""}"
|
location = "#{location.request_target}&host=#{location.host}#{region ? "®ion=#{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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')}"
|
||||||
|
|
|
@ -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 &.["playerStoryboardSpecRenderer"]?
|
|
||||||
.try &.["spec"]?
|
|
||||||
.try &.as_s.split("|")
|
.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?
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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|
|
||||||
|
|
Loading…
Reference in a new issue