mirror of
				https://gitea.invidious.io/iv-org/invidious
				synced 2025-06-05 23:29:12 +02:00 
			
		
		
		
	Merge pull request #2827 from SamantazFox/more-code-cleanup
More code cleanup
This commit is contained in:
		| @@ -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 } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return items.size, items |  | ||||||
|   end |   end | ||||||
|  |  | ||||||
| def search(query, search_params = produce_search_params(content_type: "all"), region = nil) |   return items | ||||||
|   return 0, [] of SearchItem if query.empty? | end | ||||||
|  |  | ||||||
|  | def search(query, search_params = produce_search_params(content_type: "all"), region = nil) : Array(SearchItem) | ||||||
|  |   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| | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user