chore: update to ameba 1.7.0 and adapt most of the code to it (#6082)

* chore: update to ameba 1.7.0 and adapt most of the code to it

* forgot to add this to build ameba into the ./bin directory for use, as ameba does not longer offer a postinstall script

* forgot to update ameba in the shards files

* remove leftover : in rescue

* Use ameba linter action instead of installing it with shards
This commit is contained in:
Fijxu 2026-10-13 06:14:16 -03:00 committed by GitHub
parent 7286c6dea4
commit 907fe54aab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
78 changed files with 691 additions and 685 deletions

View File

@ -8,6 +8,12 @@ Lint/UselessAssign:
- src/invidious.cr
- src/invidious/helpers/errors.cr
- src/invidious/routes/*.cr
- src/invidious/views/**/**
# Exclude unused arguments for ECR files
Lint/UnusedArgument:
Excluded:
- src/invidious/views/**/**
# Ignore false negative (if !db.query_one?...)
Lint/UnreachableCode:
@ -27,7 +33,6 @@ Lint/SpecFilename:
Excluded:
- spec/parsers_helper.cr
#
# Style
#
@ -44,6 +49,15 @@ Style/RedundantNext:
Style/ParenthesesAroundCondition:
Enabled: false
# Disable this as it's really useful for functions and variables
# that belong to a same module, struct or class, and it can be confused
# by a global function.
# This is subject to change with https://github.com/iv-org/invidious/pull/6046
# where there will not be more global variables or functions, well, that's
# the goal.
Style/RedundantSelf:
Enabled: false
# This requires a rewrite of most data structs (and their usage) in Invidious.
Naming/QueryBoolMethods:
Enabled: false
@ -56,12 +70,11 @@ Naming/BlockParameterName:
# Hides TODO comment warnings.
#
# Call `bin/ameba --only Documentation/DocumentationAdmonition` to
# Call `bin/ameba --only Documentation/Admonition` to
# list them
Documentation/DocumentationAdmonition:
Documentation/Admonition:
Enabled: false
#
# Metrics
#

View File

@ -146,7 +146,6 @@ jobs:
with:
path: |
./lib
./bin
key: shards-${{ hashFiles('shard.lock') }}-${{ steps.lint_step_install_crystal.outputs.crystal }}
- name: Install Shards
@ -164,4 +163,4 @@ jobs:
fi
- name: Run Ameba linter
run: bin/ameba
uses: crystal-ameba/github-action@v1

View File

@ -2,7 +2,7 @@ version: 2.0
shards:
ameba:
git: https://github.com/crystal-ameba/ameba.git
version: 1.6.4
version: 1.7.0
athena-negotiation:
git: https://github.com/athena-framework/negotiation.git

View File

@ -8,6 +8,8 @@ authors:
targets:
invidious:
main: src/invidious.cr
ameba:
main: lib/ameba/bin/ameba.cr
description: |
Invidious is an alternative front-end to YouTube
@ -38,7 +40,7 @@ development_dependencies:
version: ~> 0.12.4
ameba:
github: crystal-ameba/ameba
version: ~> 1.6.4
version: ~> 1.7.0
crystal: ">= 1.14.1, < 2.0.0"

View File

@ -59,7 +59,7 @@ end
# Get relative file path to a file within the static_assets_handler folder
macro get_file_path(basename)
"spec/http_server/handlers/static_assets_handler/#{ {{basename}} }"
"spec/http_server/handlers/static_assets_handler/#{ {{ basename }} }"
end
Spectator.describe StaticAssetsHandler do

View File

@ -123,7 +123,7 @@ Kemal.config.extra_options do |parser|
parser.on("-c THREADS", "--channel-threads=THREADS", "Number of threads for refreshing channels (default: #{CONFIG.channel_threads})") do |number|
begin
CONFIG.channel_threads = number.to_i
rescue ex
rescue
puts "THREADS must be integer"
exit
end
@ -131,7 +131,7 @@ Kemal.config.extra_options do |parser|
parser.on("-f THREADS", "--feed-threads=THREADS", "Number of threads for refreshing feeds (default: #{CONFIG.feed_threads})") do |number|
begin
CONFIG.feed_threads = number.to_i
rescue ex
rescue
puts "THREADS must be integer"
exit
end

View File

@ -93,7 +93,7 @@ struct ChannelVideo
def to_tuple
{% begin %}
{
{{@type.instance_vars.map(&.name).splat}}
{{ @type.instance_vars.map(&.name).splat }}
}
{% end %}
end
@ -107,7 +107,7 @@ class ChannelRedirect < Exception
end
def get_batch_channels(channels)
finished_channel = Channel(String | Nil).new
finished_channel = Channel(String?).new
max_threads = 10
spawn do
@ -125,7 +125,7 @@ def get_batch_channels(channels)
begin
get_channel(ucid)
finished_channel.send(ucid)
rescue ex
rescue
finished_channel.send(nil)
ensure
active_channel.send(nil)

View File

@ -15,7 +15,7 @@ module Invidious::Comments
if url.host.nil? || url.host.not_nil!.ends_with?("youtube.com") || url.host.not_nil!.ends_with?("youtu.be")
if url.host.try &.ends_with? "youtu.be"
url = "/watch?v=#{url.path.lstrip('/')}#{url.query_params}"
anchor["href"] = "/watch?v=#{url.path.lstrip('/')}#{url.query_params}"
else
if url.path == "/redirect"
params = HTTP::Params.parse(url.query.not_nil!)
@ -27,7 +27,7 @@ module Invidious::Comments
elsif url.to_s == "#"
begin
length_seconds = decode_length_seconds(anchor.content)
rescue ex
rescue
length_seconds = decode_time(anchor.content)
end

View File

@ -59,7 +59,7 @@ struct ConfigPreferences
def to_tuple
{% begin %}
{
{{(@type.instance_vars.map { |var| "#{var.name}: #{var.name}".id }).splat}}
{{ (@type.instance_vars.map { |var| "#{var.name}: #{var.name}".id }).splat }}
}
{% end %}
end
@ -237,14 +237,14 @@ class Config
{% for ivar in Config.instance_vars %}
{% env_id = "INVIDIOUS_#{ivar.id.upcase}" %}
if ENV.has_key?({{env_id}})
env_value = ENV.fetch({{env_id}})
if ENV.has_key?({{ env_id }})
env_value = ENV.fetch({{ env_id }})
success = false
# Use YAML converter if specified
{% ann = ivar.annotation(::YAML::Field) %}
{% if ann && ann[:converter] %}
config.{{ivar.id}} = {{ann[:converter]}}.from_yaml(YAML::ParseContext.new, YAML::Nodes.parse(ENV.fetch({{env_id}})).nodes[0])
config.{{ ivar.id }} = {{ ann[:converter] }}.from_yaml(YAML::ParseContext.new, YAML::Nodes.parse(ENV.fetch({{ env_id }})).nodes[0])
success = true
# Use regular YAML parser otherwise
@ -252,10 +252,10 @@ class Config
{% ivar_types = ivar.type.union? ? ivar.type.union_types : [ivar.type] %}
# Sort types to avoid parsing nulls and numbers as strings
{% ivar_types = ivar_types.sort_by { |ivar_type| ivar_type == Nil ? 0 : ivar_type == Int32 ? 1 : 2 } %}
{{ivar_types}}.each do |ivar_type|
{{ ivar_types }}.each do |ivar_type|
if !success
begin
config.{{ivar.id}} = ivar_type.from_yaml(env_value)
config.{{ ivar.id }} = ivar_type.from_yaml(env_value)
success = true
rescue
# nop
@ -266,14 +266,14 @@ class Config
# Exit on fail
if !success
puts %(Config.{{ivar.id}} failed to parse #{env_value} as {{ivar.type}})
puts %(Config.{{ ivar.id }} failed to parse #{env_value} as {{ ivar.type }})
exit(1)
end
end
# Warn when any config attribute is set to "CHANGE_ME!!"
if config.{{ivar.id}} == "CHANGE_ME!!"
puts "Config: The value of '#{ {{ivar.stringify}} }' needs to be changed!!"
if config.{{ ivar.id }} == "CHANGE_ME!!"
puts "Config: The value of '#{ {{ ivar.stringify }} }' needs to be changed!!"
exit(1)
end
{% end %}

View File

@ -45,7 +45,7 @@ module Invidious::Database
# Create table if it doesn't exist
begin
PG_DB.exec("SELECT * FROM #{table_name} LIMIT 0")
rescue ex
rescue
LOGGER.info("check_table: check_table: CREATE TABLE #{table_name}")
PG_DB.using_connection do |conn|

View File

@ -141,7 +141,6 @@ module Invidious::Frontend::Comments
<div id="skip-#{child["commentId"]}"></div>
</section>
END_HTML
else nil # Ignore
end
end

View File

@ -41,11 +41,11 @@ module Invidious::Frontend::SearchFilters
str << "\t\t\t\t<div class=\"filter-column\"><fieldset>\n"
str << "\t\t\t\t\t<legend><div class=\"filter-name underlined\">"
str << I18n.translate(locale, "search_filters_{{name}}_label")
str << I18n.translate(locale, "search_filters_{{ name }}_label")
str << "</div></legend>\n"
str << "\t\t\t\t\t<div class=\"filter-options\">\n"
make_{{name}}_filter_options(str, filters.{{name}}, locale)
make_{{ name }}_filter_options(str, filters.{{ name }}, locale)
str << "\t\t\t\t\t</div>"
str << "\t\t\t\t</fieldset></div>\n"
@ -57,12 +57,12 @@ module Invidious::Frontend::SearchFilters
{% date = value.underscore %}
str << "\t\t\t\t\t\t<div>"
str << "<input type='radio' name='date' id='filter-date-{{date}}' value='{{date}}'"
str << " checked" if value.{{date}}?
str << "<input type='radio' name='date' id='filter-date-{{ date }}' value='{{ date }}'"
str << " checked" if value.{{ date }}?
str << '>'
str << "<label for='filter-date-{{date}}'>"
str << I18n.translate(locale, "search_filters_date_option_{{date}}")
str << "<label for='filter-date-{{ date }}'>"
str << I18n.translate(locale, "search_filters_date_option_{{ date }}")
str << "</label></div>\n"
{% end %}
end
@ -73,12 +73,12 @@ module Invidious::Frontend::SearchFilters
{% type = value.underscore %}
str << "\t\t\t\t\t\t<div>"
str << "<input type='radio' name='type' id='filter-type-{{type}}' value='{{type}}'"
str << " checked" if value.{{type}}?
str << "<input type='radio' name='type' id='filter-type-{{ type }}' value='{{ type }}'"
str << " checked" if value.{{ type }}?
str << '>'
str << "<label for='filter-type-{{type}}'>"
str << I18n.translate(locale, "search_filters_type_option_{{type}}")
str << "<label for='filter-type-{{ type }}'>"
str << I18n.translate(locale, "search_filters_type_option_{{ type }}")
str << "</label></div>\n"
{% end %}
end
@ -89,12 +89,12 @@ module Invidious::Frontend::SearchFilters
{% duration = value.underscore %}
str << "\t\t\t\t\t\t<div>"
str << "<input type='radio' name='duration' id='filter-duration-{{duration}}' value='{{duration}}'"
str << " checked" if value.{{duration}}?
str << "<input type='radio' name='duration' id='filter-duration-{{ duration }}' value='{{ duration }}'"
str << " checked" if value.{{ duration }}?
str << '>'
str << "<label for='filter-duration-{{duration}}'>"
str << I18n.translate(locale, "search_filters_duration_option_{{duration}}")
str << "<label for='filter-duration-{{ duration }}'>"
str << I18n.translate(locale, "search_filters_duration_option_{{ duration }}")
str << "</label></div>\n"
{% end %}
end
@ -106,12 +106,12 @@ module Invidious::Frontend::SearchFilters
{% feature = value.underscore %}
str << "\t\t\t\t\t\t<div>"
str << "<input type='checkbox' name='features' id='filter-feature-{{feature}}' value='{{feature}}'"
str << " checked" if value.{{feature}}?
str << "<input type='checkbox' name='features' id='filter-feature-{{ feature }}' value='{{ feature }}'"
str << " checked" if value.{{ feature }}?
str << '>'
str << "<label for='filter-feature-{{feature}}'>"
str << I18n.translate(locale, "search_filters_features_option_{{feature}}")
str << "<label for='filter-feature-{{ feature }}'>"
str << I18n.translate(locale, "search_filters_features_option_{{ feature }}")
str << "</label></div>\n"
{% end %}
{% end %}
@ -123,12 +123,12 @@ module Invidious::Frontend::SearchFilters
{% sort = value.underscore %}
str << "\t\t\t\t\t\t<div>"
str << "<input type='radio' name='sort' id='filter-sort-{{sort}}' value='{{sort}}'"
str << " checked" if value.{{sort}}?
str << "<input type='radio' name='sort' id='filter-sort-{{ sort }}' value='{{ sort }}'"
str << " checked" if value.{{ sort }}?
str << '>'
str << "<label for='filter-sort-{{sort}}'>"
str << I18n.translate(locale, "search_filters_sort_option_{{sort}}")
str << "<label for='filter-sort-{{ sort }}'>"
str << I18n.translate(locale, "search_filters_sort_option_{{ sort }}")
str << "</label></div>\n"
{% end %}
end

View File

@ -68,7 +68,7 @@ class HTTP::Server::Response
class Output
private def unbuffered_flush
@io.flush
rescue ex : IO::Error
rescue IO::Error
unbuffered_close
end
end

View File

@ -3,7 +3,7 @@
# -------------------
macro error_template(*args)
error_template_helper(env, {{args.splat}})
error_template_helper(env, {{ args.splat }})
end
def github_details(summary : String, content : String)
@ -106,7 +106,7 @@ end
# -------------------
macro error_atom(*args)
error_atom_helper(env, {{args.splat}})
error_atom_helper(env, {{ args.splat }})
end
def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, exception : Exception)
@ -132,14 +132,14 @@ end
# -------------------
macro error_json(*args)
error_json_helper(env, {{args.splat}})
error_json_helper(env, {{ args.splat }})
end
def error_json_helper(
env : HTTP::Server::Context,
status_code : Int32,
exception : Exception,
additional_fields : Hash(String, Object) | Nil = nil,
additional_fields : Hash(String, Object)? = nil,
)
if exception.is_a?(InfoException)
return error_json_helper(env, status_code, exception.message || "", additional_fields)
@ -161,7 +161,7 @@ def error_json_helper(
env : HTTP::Server::Context,
status_code : Int32,
message : String,
additional_fields : Hash(String, Object) | Nil = nil,
additional_fields : Hash(String, Object)? = nil,
)
env.response.content_type = "application/json"
env.response.status_code = status_code

View File

@ -2,10 +2,10 @@ module HTTP::Handler
@@exclude_routes_tree = Radix::Tree(String).new
macro exclude(paths, method = "GET")
class_name = {{@type.name}}
method_downcase = {{method.downcase}}
class_name = {{ @type.name }}
method_downcase = {{ method.downcase }}
class_name_method = "#{class_name}/#{method_downcase}"
({{paths}}).each do |path|
({{ paths }}).each do |path|
@@exclude_routes_tree.add class_name_method + path, '/' + method_downcase + path
end
end
@ -20,8 +20,8 @@ module HTTP::Handler
end
class Kemal::RouteHandler
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
exclude ["/api/v1/*"], {{method}}
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
exclude ["/api/v1/*"], {{ method }}
{% end %}
# Processes the route if it's a match. Otherwise renders 404.
@ -44,8 +44,8 @@ class Kemal::RouteHandler
end
class Kemal::ExceptionHandler
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
exclude ["/api/v1/*"], {{method}}
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
exclude ["/api/v1/*"], {{ method }}
{% end %}
private def call_exception_with_status_code(context : HTTP::Server::Context, exception : Exception, status_code : Int32)
@ -72,8 +72,8 @@ class FilteredCompressHandler < HTTP::CompressHandler
end
class AuthHandler < Kemal::Handler
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
only ["/api/v1/auth/*"], {{method}}
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
only ["/api/v1/auth/*"], {{ method }}
{% end %}
def call(env)
@ -121,8 +121,8 @@ class AuthHandler < Kemal::Handler
end
class APIHandler < Kemal::Handler
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
only ["/api/v1/*"], {{method}}
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
only ["/api/v1/*"], {{ method }}
{% end %}
exclude ["/api/v1/auth/notifications"], "GET"
exclude ["/api/v1/auth/notifications"], "POST"
@ -134,9 +134,9 @@ class APIHandler < Kemal::Handler
end
class DisableAbusableAPIHandler < Kemal::Handler
{% for method in %w(GET HEAD) %}
{% for method in %w[GET HEAD] %}
# This endpoints make a video request to Invidious companion.
{% for endpoint in %w(videos clips transcripts) %}
{% for endpoint in %w[videos clips transcripts] %}
only ["/api/v1/{{ endpoint.id }}/:id"], {{ method }}
{% end %}
{% end %}

View File

@ -93,7 +93,7 @@ module Helpers
sleep 1.minute
Fiber.yield
end
rescue ex
rescue
end
end
end
@ -149,7 +149,7 @@ module Helpers
id += 1
end
rescue ex
rescue
ensure
connection_channel.send({false, connection})
end
@ -163,7 +163,7 @@ module Helpers
env.response.flush
sleep (20 + rand(11)).seconds
end
rescue ex
rescue
ensure
connection_channel.send({false, connection})
end

View File

@ -103,7 +103,7 @@ module I18n
return locales
end
def translate(locale : String?, key : String, text : String | Hash(String, String) | Nil = nil) : String
def translate(locale : String?, key : String, text : String? | Hash(String, String)? = nil) : String
# Log a warning if "key" doesn't exist in en-US locale and return
# that key as the text, so this is more or less transparent to the user.
if !LOCALES["en-US"].has_key?(key)

View File

@ -55,10 +55,10 @@ class Invidious::LogHandler < Kemal::BaseLogHandler
end
end
{% for level in %w(trace debug info warn error fatal) %}
def {{level.id}}(message : String)
if LogLevel::{{level.id.capitalize}} >= @level
puts("#{Time.utc} [{{level.id}}] #{message}".colorize(color(LogLevel::{{level.id.capitalize}})))
{% for level in %w[trace debug info warn error fatal] %}
def {{ level.id }}(message : String)
if LogLevel::{{ level.id.capitalize }} >= @level
puts("#{Time.utc} [{{ level.id }}] #{message}".colorize(color(LogLevel::{{ level.id.capitalize }})))
end
end
{% end %}

View File

@ -49,24 +49,24 @@ module JSON::Serializable
end
macro templated(_filename, template = "template", navbar_search = true)
navbar_search = {{navbar_search}}
navbar_search = {{ navbar_search }}
{{ filename = "src/invidious/views/" + _filename + ".ecr" }}
{{ layout = "src/invidious/views/" + template + ".ecr" }}
__content_filename__ = {{filename}}
render {{filename}}, {{layout}}
__content_filename__ = {{ filename }}
render {{ filename }}, {{ layout }}
end
macro rendered(filename)
render("src/invidious/views/#{{{filename}}}.ecr")
render("src/invidious/views/#{{{ filename }}}.ecr")
end
# Similar to Kemals halt method but works in a
# method.
macro haltf(env, status_code = 200, response = "")
{{env}}.response.status_code = {{status_code}}
{{env}}.response.print {{response}}
{{env}}.response.close
{{ env }}.response.status_code = {{ status_code }}
{{ env }}.response.print {{ response }}
{{ env }}.response.close
return
end

View File

@ -116,7 +116,7 @@ def scope_includes_scope(scope, subset)
subset_endpoint = subset_endpoint.downcase
if methods.empty?
methods = %w(GET POST PUT HEAD DELETE PATCH OPTIONS)
methods = %w[GET POST PUT HEAD DELETE PATCH OPTIONS]
end
if methods & subset_methods != subset_methods

View File

@ -102,7 +102,7 @@ def decode_date(string : String)
# Try to parse as format Jul 10, 2000
begin
return Time.parse(string, "%b %-d, %Y", Time::Location.local)
rescue ex
rescue
end
case string
@ -110,7 +110,6 @@ def decode_date(string : String)
return Time.utc
when "yesterday"
return Time.utc - 1.day
else nil # Continue
end
# String matches format "20 hours ago", "4 months ago", "20s ago", "15min ago"...
@ -177,7 +176,7 @@ def short_text_to_number(short_text : String) : Int64
end
return number.to_i64
rescue ex
rescue
return 0_i64
end

View File

@ -106,7 +106,7 @@ module Invidious::HttpServer
# Can be removed once https://github.com/crystal-lang/crystal/issues/15817 is fixed.
private def serve_file_range(context : HTTP::Server::Context, file : IO, range_header : String, file_info)
# Paste in the body of inherited serve_file_range
{{@type.superclass.methods.select(&.name.==("serve_file_range"))[0].body}}
{{ @type.superclass.methods.select(&.name.==("serve_file_range"))[0].body }}
end
# Clear cached files.

View File

@ -69,7 +69,7 @@ class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
raw_instance_list = JSON.parse(instance_api_client.get("/instances.json").body).as_a
instance_api_client.close
rescue ex : Socket::ConnectError | IO::TimeoutError | JSON::ParseException
rescue Socket::ConnectError | IO::TimeoutError | JSON::ParseException
raw_instance_list = [] of JSON::Any
end

View File

@ -138,7 +138,7 @@ module Invidious::JSONify::APIv1
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
json.field "container", fmt_info["ext"]
json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"]
json.field("encoding", fmt_info["vcodec"]? || fmt_info["acodec"])
end
# Livestream chunk infos
@ -198,8 +198,8 @@ module Invidious::JSONify::APIv1
end
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
json.field "container", fmt_info["ext"]
json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"]
json.field("container", fmt_info["ext"])
json.field("encoding", fmt_info["vcodec"]? || fmt_info["acodec"])
end
end
end

View File

@ -500,10 +500,10 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
# Find the metadataParts with commandRuns inside, which contains author
# information.
metadata_parts = metadata_rows.try &.find { |row|
metadata_parts = metadata_rows.try &.find do |row|
parts = row["metadataParts"]?.try &.as_a
parts && parts.any? { |item2| item2.dig?("text", "commandRuns").try &.as_a }
}.try &.["metadataParts"].as_a
end.try &.["metadataParts"].as_a
if author_info = metadata_parts.try &.find(&.dig?("text", "commandRuns"))
.try &.["text"]

View File

@ -19,9 +19,9 @@ module Invidious::Routes::API::Manifest
begin
video = get_video(id, region: region)
rescue ex : NotFoundException
rescue NotFoundException
haltf env, status_code: 404
rescue ex
rescue
haltf env, status_code: 403
end

View File

@ -42,10 +42,10 @@ module Invidious::Routes::API::V1::Authenticated
user = env.get("user").as(User)
begin
if body = env.request.body
body = env.request.body.not_nil!.gets_to_end
body = if raw_body = env.request.body
raw_body.gets_to_end
else
body = "{}"
"{}"
end
Invidious::User::Import.from_invidious(user, body)
rescue

View File

@ -31,7 +31,7 @@ module Invidious::Routes::API::V1::Channels
begin
playlist = get_playlist(channel.ucid.sub("UC", "UULF"))
videos = get_playlist_videos(playlist, offset: 0)
rescue ex : InfoException
rescue InfoException
# playlist doesnt exist.
videos = [] of PlaylistVideo
end
@ -118,7 +118,7 @@ module Invidious::Routes::API::V1::Channels
# Fetch related channels
begin
related_channels, _ = fetch_related_channels(channel)
rescue ex
rescue
related_channels = [] of SearchChannel
end
@ -157,7 +157,7 @@ module Invidious::Routes::API::V1::Channels
begin
playlist = get_playlist(channel.ucid.sub("UC", "UULF"))
videos = get_playlist_videos(playlist, offset: 0)
rescue ex : InfoException
rescue InfoException
# playlist doesnt exist.
videos = [] of PlaylistVideo
end
@ -203,7 +203,7 @@ module Invidious::Routes::API::V1::Channels
begin
playlist = get_playlist(channel.ucid.sub("UC", "UUSH"))
videos = get_playlist_videos(playlist, offset: 0)
rescue ex : InfoException
rescue InfoException
# playlist doesnt exist.
videos = [] of PlaylistVideo
end
@ -249,7 +249,7 @@ module Invidious::Routes::API::V1::Channels
begin
playlist = get_playlist(channel.ucid.sub("UC", "UULV"))
videos = get_playlist_videos(playlist, offset: 0)
rescue ex : InfoException
rescue InfoException
# playlist doesnt exist.
videos = [] of PlaylistVideo
end

View File

@ -53,7 +53,7 @@ module Invidious::Routes::API::V1::Misc
playlist = get_playlist(plid)
rescue ex : InfoException
return error_json(404, ex)
rescue ex
rescue
return error_json(404, "Playlist does not exist.")
end

View File

@ -47,9 +47,9 @@ module Invidious::Routes::API::V1::Videos
begin
video = get_video(id, region: region)
rescue ex : NotFoundException
rescue NotFoundException
haltf env, 404
rescue ex
rescue
haltf env, 500
end
@ -187,9 +187,9 @@ module Invidious::Routes::API::V1::Videos
begin
video = get_video(id, region: region)
rescue ex : NotFoundException
rescue NotFoundException
haltf env, 404
rescue ex
rescue
haltf env, 500
end
@ -265,8 +265,6 @@ module Invidious::Routes::API::V1::Videos
haltf env, 400
end
annotations = ""
case source
when "archive"
if CONFIG.cache_annotations && (cached_annotation = Invidious::Database::Annotations.select(video_id))
@ -365,7 +363,7 @@ module Invidious::Routes::API::V1::Videos
begin
comments, reddit_thread = Comments.fetch_reddit(id, sort_by: sort_by)
rescue ex
rescue
comments = nil
reddit_thread = nil
end

View File

@ -44,7 +44,7 @@ module Invidious::Routes::Channels
begin
playlist = get_playlist(channel.ucid.sub("UC", "UULF"))
items = get_playlist_videos(playlist, offset: 0)
rescue ex : InfoException
rescue InfoException
# playlist doesnt exist.
items = [] of PlaylistVideo
end
@ -79,7 +79,7 @@ module Invidious::Routes::Channels
begin
playlist = get_playlist(channel.ucid.sub("UC", "UUSH"))
items = get_playlist_videos(playlist, offset: 0)
rescue ex : InfoException
rescue InfoException
# playlist doesnt exist.
items = [] of PlaylistVideo
end
@ -114,7 +114,7 @@ module Invidious::Routes::Channels
begin
playlist = get_playlist(channel.ucid.sub("UC", "UULV"))
items = get_playlist_videos(playlist, offset: 0)
rescue ex : InfoException
rescue InfoException
# playlist doesnt exist.
items = [] of PlaylistVideo
end
@ -353,7 +353,7 @@ module Invidious::Routes::Channels
begin
resolved_url = YoutubeAPI.resolve_url("https://www.youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}")
ucid = resolved_url["endpoint"]["browseEndpoint"]["browseId"]
rescue ex : InfoException | KeyError
rescue InfoException | KeyError
return error_template(404, I18n.translate(locale, "This channel does not exist."))
end

View File

@ -12,7 +12,7 @@ module Invidious::Routes::Companion
return self.proxy_companion(env, resp)
end
end
rescue ex
rescue
end
end
@ -29,7 +29,7 @@ module Invidious::Routes::Companion
return self.proxy_companion(env, resp)
end
end
rescue ex
rescue
end
end
@ -45,7 +45,7 @@ module Invidious::Routes::Companion
return self.proxy_companion(env, resp)
end
end
rescue ex
rescue
end
end

View File

@ -119,7 +119,6 @@ module Invidious::Routes::Embed
end
return env.redirect url
else nil # Continue
end
params = Invidious::Videos.process_video_params(env.params.query, preferences)
@ -182,14 +181,14 @@ module Invidious::Routes::Embed
captions = video.captions
preferred_captions = captions.select { |caption|
preferred_captions = captions.select do |caption|
params.preferred_captions.includes?(caption.name) ||
params.preferred_captions.includes?(caption.language_code.split("-")[0])
}
preferred_captions.sort_by! { |caption|
end
preferred_captions.sort_by! do |caption|
(params.preferred_captions.index(caption.name) ||
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
}
end
captions = captions - preferred_captions
aspect_ratio = nil

View File

@ -327,7 +327,6 @@ module Invidious::Routes::Feeds
request_target = URI.parse(node[attribute.name]).request_target
query_string_opt = request_target.starts_with?("/watch?v=") ? ("&#{params}" if !params.empty?) : ""
node[attribute.name] = "#{HOST_URL}#{request_target}#{query_string_opt}"
else nil # Skip
end
end
end

View File

@ -15,7 +15,7 @@ module Invidious::Routes::Images
GGPHT_POOL.client &.get(url, headers) do |resp|
return self.proxy_image(env, resp)
end
rescue ex
rescue
end
end
@ -46,7 +46,7 @@ module Invidious::Routes::Images
env.response.headers["Connection"] = "close"
return self.proxy_image(env, resp)
end
rescue ex
rescue
end
end
@ -68,7 +68,7 @@ module Invidious::Routes::Images
get_ytimg_pool(authority).client &.get(url, headers) do |resp|
return self.proxy_image(env, resp)
end
rescue ex
rescue
end
end
@ -108,7 +108,7 @@ module Invidious::Routes::Images
Helpers.proxy_file(response, env)
end
rescue ex
rescue
end
end
@ -140,7 +140,7 @@ module Invidious::Routes::Images
get_ytimg_pool("i").client &.get(url, headers) do |resp|
return self.proxy_image(env, resp)
end
rescue ex
rescue
end
end

View File

@ -103,7 +103,7 @@ module Invidious::Routes::Login
begin
validate_request(tokens[0], answer, env.request, HMAC_KEY, locale)
rescue ex : InfoException
rescue InfoException
return error_template(400, InfoException.new("Erroneous CAPTCHA"))
rescue ex
return error_template(400, ex)

View File

@ -164,7 +164,7 @@ module Invidious::Routes::Playlists
begin
items = get_playlist_videos(playlist, offset: (page - 1) * 100)
rescue ex
rescue
items = [] of PlaylistVideo
end
@ -255,7 +255,7 @@ module Invidious::Routes::Playlists
begin
query = Invidious::Search::Query.new(env.params.query, :playlist, region)
items = query.process.select(SearchVideo).map(&.as(SearchVideo))
rescue ex
rescue
items = [] of SearchVideo
end

View File

@ -362,7 +362,6 @@ module Invidious::Routes::PreferencesRoute
response: error_template(415, "Uploaded file is too large")
)
end
else nil # Ignore
end
end
end

View File

@ -115,7 +115,7 @@ module Invidious::Routes::VideoPlayback
IO.copy(resp.body_io, env.response)
end
rescue ex
rescue
end
else
if query_params["title"]? && CONFIG.disabled?("downloads") ||

View File

@ -82,7 +82,7 @@ module Invidious::Routes::Watch
if source == "youtube"
begin
comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"]
rescue ex
rescue
if preferences.comments[1] == "reddit"
comments, reddit_thread = Comments.fetch_reddit(id)
comment_html = Frontend::Comments.template_reddit(comments, locale)
@ -98,7 +98,7 @@ module Invidious::Routes::Watch
comment_html = Comments.fill_links(comment_html, "https", "www.reddit.com")
comment_html = Comments.replace_links(comment_html)
rescue ex
rescue
if preferences.comments[1] == "youtube"
comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"]
end
@ -143,14 +143,14 @@ module Invidious::Routes::Watch
captions = video.captions
preferred_captions = captions.select { |caption|
preferred_captions = captions.select do |caption|
params.preferred_captions.includes?(caption.name) ||
params.preferred_captions.includes?(caption.language_code.split("-")[0])
}
preferred_captions.sort_by! { |caption|
end
preferred_captions.sort_by! do |caption|
(params.preferred_captions.index(caption.name) ||
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
}
end
captions = captions - preferred_captions
aspect_ratio = "16:9"

View File

@ -3,12 +3,12 @@ module Invidious::Routing
{% for http_method in {"get", "post", "delete", "options", "patch", "put"} %}
macro {{http_method.id}}(path, controller, method = :handle)
macro {{ http_method.id }}(path, controller, method = :handle)
unless Kemal::Utils.path_starts_with_slash?(\{{path}})
raise Kemal::Exceptions::InvalidPathStartException.new({{http_method}}, \{{path}})
raise Kemal::Exceptions::InvalidPathStartException.new({{ http_method }}, \{{path}})
end
Kemal::RouteHandler::INSTANCE.add_route({{http_method.upcase}}, \{{path}}) do |env|
Kemal::RouteHandler::INSTANCE.add_route({{ http_method.upcase }}, \{{path}}) do |env|
\{{ controller }}.\{{ method.id }}(env)
end
end
@ -241,91 +241,91 @@ module Invidious::Routing
def register_api_v1_routes
{% begin %}
{{namespace = Routes::API::V1}}
{{ namespace = Routes::API::V1 }}
# Videos
get "/api/v1/videos/:id", {{namespace}}::Videos, :videos
get "/api/v1/storyboards/:id", {{namespace}}::Videos, :storyboards
get "/api/v1/captions/:id", {{namespace}}::Videos, :captions
get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations
get "/api/v1/comments/:id", {{namespace}}::Videos, :comments
get "/api/v1/clips/:id", {{namespace}}::Videos, :clips
get "/api/v1/transcripts/:id", {{namespace}}::Videos, :transcripts
get "/api/v1/videos/:id", {{ namespace }}::Videos, :videos
get "/api/v1/storyboards/:id", {{ namespace }}::Videos, :storyboards
get "/api/v1/captions/:id", {{ namespace }}::Videos, :captions
get "/api/v1/annotations/:id", {{ namespace }}::Videos, :annotations
get "/api/v1/comments/:id", {{ namespace }}::Videos, :comments
get "/api/v1/clips/:id", {{ namespace }}::Videos, :clips
get "/api/v1/transcripts/:id", {{ namespace }}::Videos, :transcripts
# Feeds
get "/api/v1/trending", {{namespace}}::Feeds, :trending
get "/api/v1/popular", {{namespace}}::Feeds, :popular
get "/api/v1/trending", {{ namespace }}::Feeds, :trending
get "/api/v1/popular", {{ namespace }}::Feeds, :popular
# Channels
get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
get "/api/v1/channels/:ucid/latest", {{namespace}}::Channels, :latest
get "/api/v1/channels/:ucid/videos", {{namespace}}::Channels, :videos
get "/api/v1/channels/:ucid/shorts", {{namespace}}::Channels, :shorts
get "/api/v1/channels/:ucid/streams", {{namespace}}::Channels, :streams
get "/api/v1/channels/:ucid/podcasts", {{namespace}}::Channels, :podcasts
get "/api/v1/channels/:ucid/releases", {{namespace}}::Channels, :releases
get "/api/v1/channels/:ucid/courses", {{namespace}}::Channels, :courses
get "/api/v1/channels/:ucid/playlists", {{namespace}}::Channels, :playlists
get "/api/v1/channels/:ucid/community", {{namespace}}::Channels, :community
get "/api/v1/channels/:ucid/posts", {{namespace}}::Channels, :community
get "/api/v1/channels/:ucid/channels", {{namespace}}::Channels, :channels
get "/api/v1/channels/:ucid/search", {{namespace}}::Channels, :search
get "/api/v1/channels/:ucid", {{ namespace }}::Channels, :home
get "/api/v1/channels/:ucid/latest", {{ namespace }}::Channels, :latest
get "/api/v1/channels/:ucid/videos", {{ namespace }}::Channels, :videos
get "/api/v1/channels/:ucid/shorts", {{ namespace }}::Channels, :shorts
get "/api/v1/channels/:ucid/streams", {{ namespace }}::Channels, :streams
get "/api/v1/channels/:ucid/podcasts", {{ namespace }}::Channels, :podcasts
get "/api/v1/channels/:ucid/releases", {{ namespace }}::Channels, :releases
get "/api/v1/channels/:ucid/courses", {{ namespace }}::Channels, :courses
get "/api/v1/channels/:ucid/playlists", {{ namespace }}::Channels, :playlists
get "/api/v1/channels/:ucid/community", {{ namespace }}::Channels, :community
get "/api/v1/channels/:ucid/posts", {{ namespace }}::Channels, :community
get "/api/v1/channels/:ucid/channels", {{ namespace }}::Channels, :channels
get "/api/v1/channels/:ucid/search", {{ namespace }}::Channels, :search
# Posts
get "/api/v1/post/:id", {{namespace}}::Channels, :post
get "/api/v1/post/:id/comments", {{namespace}}::Channels, :post_comments
get "/api/v1/post/:id", {{ namespace }}::Channels, :post
get "/api/v1/post/:id/comments", {{ namespace }}::Channels, :post_comments
# 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community
get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect
get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect
get "/api/v1/channels/comments/:ucid", {{ namespace }}::Channels, :channel_comments_redirect
get "/api/v1/channels/:ucid/comments", {{ namespace }}::Channels, :channel_comments_redirect
# Search
get "/api/v1/search", {{namespace}}::Search, :search
get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions
get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag
get "/api/v1/search", {{ namespace }}::Search, :search
get "/api/v1/search/suggestions", {{ namespace }}::Search, :search_suggestions
get "/api/v1/hashtag/:hashtag", {{ namespace }}::Search, :hashtag
# Authenticated
get "/api/v1/auth/preferences", {{namespace}}::Authenticated, :get_preferences
post "/api/v1/auth/preferences", {{namespace}}::Authenticated, :set_preferences
get "/api/v1/auth/preferences", {{ namespace }}::Authenticated, :get_preferences
post "/api/v1/auth/preferences", {{ namespace }}::Authenticated, :set_preferences
get "/api/v1/auth/export/invidious", {{namespace}}::Authenticated, :export_invidious
post "/api/v1/auth/import/invidious", {{namespace}}::Authenticated, :import_invidious
get "/api/v1/auth/export/invidious", {{ namespace }}::Authenticated, :export_invidious
post "/api/v1/auth/import/invidious", {{ namespace }}::Authenticated, :import_invidious
get "/api/v1/auth/history", {{namespace}}::Authenticated, :get_history
post "/api/v1/auth/history/:id", {{namespace}}::Authenticated, :mark_watched
delete "/api/v1/auth/history/:id", {{namespace}}::Authenticated, :mark_unwatched
delete "/api/v1/auth/history", {{namespace}}::Authenticated, :clear_history
get "/api/v1/auth/history", {{ namespace }}::Authenticated, :get_history
post "/api/v1/auth/history/:id", {{ namespace }}::Authenticated, :mark_watched
delete "/api/v1/auth/history/:id", {{ namespace }}::Authenticated, :mark_unwatched
delete "/api/v1/auth/history", {{ namespace }}::Authenticated, :clear_history
get "/api/v1/auth/feed", {{namespace}}::Authenticated, :feed
get "/api/v1/auth/feed", {{ namespace }}::Authenticated, :feed
get "/api/v1/auth/subscriptions", {{namespace}}::Authenticated, :get_subscriptions
post "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :subscribe_channel
delete "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :unsubscribe_channel
get "/api/v1/auth/subscriptions", {{ namespace }}::Authenticated, :get_subscriptions
post "/api/v1/auth/subscriptions/:ucid", {{ namespace }}::Authenticated, :subscribe_channel
delete "/api/v1/auth/subscriptions/:ucid", {{ namespace }}::Authenticated, :unsubscribe_channel
get "/api/v1/auth/playlists", {{namespace}}::Authenticated, :list_playlists
post "/api/v1/auth/playlists", {{namespace}}::Authenticated, :create_playlist
patch "/api/v1/auth/playlists/:plid",{{namespace}}:: Authenticated, :update_playlist_attribute
delete "/api/v1/auth/playlists/:plid", {{namespace}}::Authenticated, :delete_playlist
post "/api/v1/auth/playlists/:plid/videos", {{namespace}}::Authenticated, :insert_video_into_playlist
delete "/api/v1/auth/playlists/:plid/videos/:index", {{namespace}}::Authenticated, :delete_video_in_playlist
get "/api/v1/auth/playlists", {{ namespace }}::Authenticated, :list_playlists
post "/api/v1/auth/playlists", {{ namespace }}::Authenticated, :create_playlist
patch "/api/v1/auth/playlists/:plid",{{ namespace }}:: Authenticated, :update_playlist_attribute
delete "/api/v1/auth/playlists/:plid", {{ namespace }}::Authenticated, :delete_playlist
post "/api/v1/auth/playlists/:plid/videos", {{ namespace }}::Authenticated, :insert_video_into_playlist
delete "/api/v1/auth/playlists/:plid/videos/:index", {{ namespace }}::Authenticated, :delete_video_in_playlist
get "/api/v1/auth/tokens", {{namespace}}::Authenticated, :get_tokens
post "/api/v1/auth/tokens/register", {{namespace}}::Authenticated, :register_token
post "/api/v1/auth/tokens/unregister", {{namespace}}::Authenticated, :unregister_token
get "/api/v1/auth/tokens", {{ namespace }}::Authenticated, :get_tokens
post "/api/v1/auth/tokens/register", {{ namespace }}::Authenticated, :register_token
post "/api/v1/auth/tokens/unregister", {{ namespace }}::Authenticated, :unregister_token
if CONFIG.enable_user_notifications
get "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
post "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
get "/api/v1/auth/notifications", {{ namespace }}::Authenticated, :notifications
post "/api/v1/auth/notifications", {{ namespace }}::Authenticated, :notifications
end
# Misc
get "/api/v1/stats", {{namespace}}::Misc, :stats
get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist
get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist
get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes
get "/api/v1/resolveurl", {{namespace}}::Misc, :resolve_url
get "/api/v1/stats", {{ namespace }}::Misc, :stats
get "/api/v1/playlists/:plid", {{ namespace }}::Misc, :get_playlist
get "/api/v1/auth/playlists/:plid", {{ namespace }}::Misc, :get_playlist
get "/api/v1/mixes/:rdid", {{ namespace }}::Misc, :mixes
get "/api/v1/resolveurl", {{ namespace }}::Misc, :resolve_url
{% end %}
end
end

View File

@ -39,7 +39,7 @@ module Invidious::Search
def subscriptions(query : Query, user : Invidious::User) : Array(ChannelVideo)
view_name = "subscriptions_#{sha256(user.email)}"
return PG_DB.query_all("
return PG_DB.query_all(<<-SQL,
SELECT id,title,published,updated,ucid,author,length_seconds
FROM (
SELECT *,
@ -47,7 +47,8 @@ module Invidious::Search
to_tsvector(#{view_name}.author)
as document
FROM #{view_name}
) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;",
) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;
SQL
query.text, (query.page - 1) * 20,
as: ChannelVideo
)

View File

@ -2,10 +2,10 @@ def fetch_trending(trending_type, region, locale)
region ||= "US"
region = region.upcase
# 2026-10-12: plid is only used in line 47
# so we can remove this later.
plid = nil
browse_id = ""
case trending_type.try &.downcase
when "gaming"
browse_id = "UCOpNcN46UbXVtpKMrmU4Abg"

View File

@ -56,7 +56,7 @@ struct Invidious::User
begin
video = get_video(video_id)
rescue ex
rescue
next
end
@ -130,7 +130,7 @@ struct Invidious::User
begin
video = get_video(video_id, false)
rescue ex
rescue
next
end

View File

@ -73,7 +73,7 @@ struct Preferences
else
result
end
rescue ex
rescue
if value.read_bool
"dark"
else
@ -199,7 +199,7 @@ struct Preferences
value.read_array do
result << HTML.escape(value.read_string[0, 100])
end
rescue ex
rescue
result = [HTML.escape(value.read_string[0, 100]), ""]
end
@ -228,7 +228,7 @@ struct Preferences
result << HTML.escape(item.value[0, 100])
end
rescue ex
rescue
if node.is_a?(YAML::Nodes::Scalar)
result = [HTML.escape(node.value[0, 100]), ""]
else

View File

@ -19,7 +19,7 @@ struct Invidious::User
def self.from_rs(rs)
begin
Preferences.from_json(rs.read(String))
rescue ex
rescue
Preferences.from_json("{}")
end
end

View File

@ -45,7 +45,6 @@ def get_subscription_feed(user, max_results = 40, page = 1)
notifications.sort_by!(&.author)
when "channel name - reverse"
notifications.sort_by!(&.author).reverse!
else nil # Ignore
end
else
if user.preferences.latest_only
@ -94,7 +93,6 @@ def get_subscription_feed(user, max_results = 40, page = 1)
videos.sort_by!(&.author)
when "channel name - reverse"
videos.sort_by!(&.author).reverse!
else nil # Ignore
end
notifications = Invidious::Database::Users.select_notifications(user)

View File

@ -48,7 +48,7 @@ struct Video
end
end
def to_json(json : JSON::Builder | Nil = nil)
def to_json(json : JSON::Builder? = nil)
to_json(nil, json)
end
@ -150,7 +150,7 @@ struct Video
def dash_manifest_url : String?
raw_dash_url = info.dig?("streamingData", "dashManifestUrl").try &.as_s
return nil if raw_dash_url.nil?
return if raw_dash_url.nil?
# Use manifest v5 parameter to reduce file size
# See https://github.com/iv-org/invidious/issues/4186
@ -183,14 +183,14 @@ struct Video
end
def music : Array(VideoMusic)
info["music"].as_a.map { |music_json|
info["music"].as_a.map do |music_json|
VideoMusic.new(
music_json["song"].as_s,
music_json["album"].as_s,
music_json["artist"].as_s,
music_json["license"].as_s
)
}
end
end
# Returns true if comments are enabled on the video
@ -201,37 +201,37 @@ struct Video
# Macros defining getters/setters for various types of data
private macro getset_string(name)
# Return {{name.stringify}} from `info`
def {{name.id.underscore}} : String
return info[{{name.stringify}}]?.try &.as_s || ""
# Return {{ name.stringify }} from `info`
def {{ name.id.underscore }} : String
return info[{{ name.stringify }}]?.try &.as_s || ""
end
# Update {{name.stringify}} into `info`
def {{name.id.underscore}}=(value : String)
info[{{name.stringify}}] = JSON::Any.new(value)
# Update {{ name.stringify }} into `info`
def {{ name.id.underscore }}=(value : String)
info[{{ name.stringify }}] = JSON::Any.new(value)
end
{% if flag?(:debug_macros) %} {{debug}} {% end %}
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
end
private macro getset_string_array(name)
# Return {{name.stringify}} from `info`
def {{name.id.underscore}} : Array(String)
return info[{{name.stringify}}]?.try &.as_a.map &.as_s || [] of String
# Return {{ name.stringify }} from `info`
def {{ name.id.underscore }} : Array(String)
return info[{{ name.stringify }}]?.try &.as_a.map &.as_s || [] of String
end
# Update {{name.stringify}} into `info`
def {{name.id.underscore}}=(value : Array(String))
info[{{name.stringify}}] = JSON::Any.new(value)
# Update {{ name.stringify }} into `info`
def {{ name.id.underscore }}=(value : Array(String))
info[{{ name.stringify }}] = JSON::Any.new(value)
end
{% if flag?(:debug_macros) %} {{debug}} {% end %}
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
end
{% for op, type in {i32: Int32, i64: Int64} %}
private macro getset_{{op}}(name)
def \{{name.id.underscore}} : {{type}}
return info[\{{name.stringify}}]?.try &.as_i64.to_{{op}} || 0_{{op}}
private macro getset_{{ op }}(name)
def \{{name.id.underscore}} : {{ type }}
return info[\{{name.stringify}}]?.try &.as_i64.to_{{ op }} || 0_{{ op }}
end
def \{{name.id.underscore}}=(value : Int)
@ -243,32 +243,32 @@ struct Video
{% end %}
private macro getset_bool(name)
# Return {{name.stringify}} from `info`
def {{name.id.underscore}} : Bool
return info[{{name.stringify}}]?.try &.as_bool || false
# Return {{ name.stringify }} from `info`
def {{ name.id.underscore }} : Bool
return info[{{ name.stringify }}]?.try &.as_bool || false
end
# Update {{name.stringify}} into `info`
def {{name.id.underscore}}=(value : Bool)
info[{{name.stringify}}] = JSON::Any.new(value)
# Update {{ name.stringify }} into `info`
def {{ name.id.underscore }}=(value : Bool)
info[{{ name.stringify }}] = JSON::Any.new(value)
end
{% if flag?(:debug_macros) %} {{debug}} {% end %}
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
end
# Macro to generate ? and = accessor methods for attributes in `info`
private macro predicate_bool(method_name, name)
# Return {{name.stringify}} from `info`
def {{method_name.id.underscore}}? : Bool
return info[{{name.stringify}}]?.try &.as_bool || false
# Return {{ name.stringify }} from `info`
def {{ method_name.id.underscore }}? : Bool
return info[{{ name.stringify }}]?.try &.as_bool || false
end
# Update {{name.stringify}} into `info`
def {{method_name.id.underscore}}=(value : Bool)
info[{{name.stringify}}] = JSON::Any.new(value)
# Update {{ name.stringify }} into `info`
def {{ method_name.id.underscore }}=(value : Bool)
info[{{ name.stringify }}] = JSON::Any.new(value)
end
{% if flag?(:debug_macros) %} {{debug}} {% end %}
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
end
# Method definitions, using the macros above

View File

@ -10,7 +10,7 @@ module Invidious::Videos::Parser
# TODO: "compactRadioRenderer" (Mix) and
# TODO: Use a proper struct/class instead of a hacky JSON object
def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
return nil if !related["videoId"]?
return if !related["videoId"]?
# The compact renderer has video length in seconds, where the end
# screen rendered has a full text version ("42:40")
@ -60,7 +60,7 @@ module Invidious::Videos::Parser
player_response = YoutubeAPI.player(video_id: video_id)
if player_response.nil?
return nil
return
end
playability_status = player_response.dig?("playabilityStatus", "status").try &.as_s
@ -150,7 +150,7 @@ module Invidious::Videos::Parser
elsif playability_status == "OK"
return response
else
return nil
return
end
end

View File

@ -25,8 +25,7 @@
<% end %>
<% if captcha %>
<% captcha = captcha.not_nil! %>
<img style="width:50%" src='<%= captcha[:question] %>'/>
<img src='<%= captcha[:question] %>' style="width:50%"/>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
<% end %>

View File

@ -20,7 +20,7 @@ struct YoutubeConnectionPool
begin
response = yield conn
rescue ex
rescue
conn.close
conn = make_client(url, force_resolve: true)
@ -88,7 +88,7 @@ struct CompanionConnectionPool
begin
response = yield wrapper
rescue ex
rescue
wrapper.close
companion = CONFIG.invidious_companion.sample

View File

@ -40,8 +40,8 @@ private module Parsers
begin
return parse_internal(*args)
rescue ex
LOGGER.debug("#{{{@type.name}}}: Failed to render item.")
LOGGER.debug("#{{{@type.name}}}: Got exception: #{ex.message}")
LOGGER.debug("#{{{ @type.name }}}: Failed to render item.")
LOGGER.debug("#{{{ @type.name }}}: Got exception: #{ex.message}")
ProblematicTimelineItem.new(
parse_exception: ex
)
@ -152,7 +152,6 @@ private module Parsers
when "Premium"
# TODO: Potentially available as item_contents["topStandaloneBadge"]["metadataBadgeRenderer"]
badges |= VideoBadges::Premium
else nil # Ignore
end
end
@ -173,7 +172,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -240,7 +239,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -289,7 +288,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -334,7 +333,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -395,7 +394,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -467,7 +466,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -496,7 +495,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -527,7 +526,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -626,7 +625,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -655,6 +654,10 @@ private module Parsers
thumbnail_view_model = item_contents.dig(
"contentImage", "thumbnailViewModel"
)
# TODO: This is an useless assignment, is not used anywhere
# so check if it can be used somewhere later.
#
# ameba:disable Lint/UselessAssign
thumbnail = thumbnail_view_model.dig("image", "sources", 0, "url").as_s
video_id = item_contents["contentId"].as_s
@ -739,9 +742,9 @@ private module Parsers
video_count = thumbnail_view_model.dig("overlays").as_a
.compact_map(&.dig?("thumbnailOverlayBadgeViewModel", "thumbnailBadges").try &.as_a)
.flatten
.find(nil, &.dig?("thumbnailBadgeViewModel", "text").try { |node|
.find(nil, &.dig?("thumbnailBadgeViewModel", "text").try do |node|
{"episodes", "videos", "lessons"}.any? { |str| node.as_s.ends_with?(str) }
})
end)
.try &.dig("thumbnailBadgeViewModel", "text").as_s.to_i(strict: false)
metadata = item_contents.dig("metadata", "lockupMetadataViewModel")
@ -790,10 +793,10 @@ private module Parsers
# ]
#
metadata_rows = metadata.dig?("metadata", "contentMetadataViewModel", "metadataRows").try &.as_a
metadata_parts = metadata_rows.try &.find { |row|
metadata_parts = metadata_rows.try &.find do |row|
parts = row["metadataParts"]?.try &.as_a
parts && !parts.any? { |item| item.dig?("text", "content").try &.as_s == "View full playlist" }
}.try &.["metadataParts"].as_a
end.try &.["metadataParts"].as_a
if author_info = metadata_parts.try &.find(&.dig?("text", "commandRuns"))
.try &.["text"]
@ -831,7 +834,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -889,7 +892,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -918,7 +921,7 @@ private module Parsers
end
def self.parser_name
return {{@type.name}}
return {{ @type.name }}
end
end
end
@ -1005,7 +1008,7 @@ private module Extractors
end
def self.extractor_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -1046,7 +1049,7 @@ private module Extractors
end
def self.extractor_name
return {{@type.name}}
return {{ @type.name }}
end
end
@ -1080,7 +1083,7 @@ private module Extractors
end
def self.extractor_name
return {{@type.name}}
return {{ @type.name }}
end
end
end

View File

@ -17,15 +17,13 @@
# another nil will be returned.
def extract_text(item : JSON::Any?) : String?
if item.nil?
return nil
return
end
if text_container = item["simpleText"]?
return text_container.as_s
elsif text_container = item["runs"]?
return text_container.as_a.map(&.["text"].as_s).join("")
else
nil
end
end

View File

@ -46,7 +46,7 @@ module UrlSanitizer
when "results", "search"
return :search
else # hashtag, post, trending, brand URLs, etc..
return nil
return
end
end

View File

@ -210,7 +210,7 @@ module YoutubeAPI
# Region to provide to youtube, e.g to alter search results
# (this is passed as the `gl` parameter).
property region : String | Nil
property region : String?
# Initialization function
def initialize(
@ -286,7 +286,7 @@ module YoutubeAPI
# Return, as a Hash, the "context" data required to request the
# youtube API endpoints.
#
private def make_context(client_config : ClientConfig | Nil, video_id = "dQw4w9WgXcQ") : Hash
private def make_context(client_config : ClientConfig?, video_id = "dQw4w9WgXcQ") : Hash
# Use the default client config if nil is passed
client_config ||= DEFAULT_CLIENT_CONFIG
@ -356,7 +356,7 @@ module YoutubeAPI
#
# - A playlist ID (parameters MUST be an empty string)
#
def browse(continuation : String, client_config : ClientConfig | Nil = nil)
def browse(continuation : String, client_config : ClientConfig? = nil)
# JSON Request data, required by the API
data = {
"context" => self.make_context(client_config),
@ -371,7 +371,7 @@ module YoutubeAPI
browse_id : String,
*, # Force the following parameters to be passed by name
params : String,
client_config : ClientConfig | Nil = nil,
client_config : ClientConfig? = nil,
)
# JSON Request data, required by the API
data = {
@ -424,7 +424,7 @@ module YoutubeAPI
# })
# ```
#
def next(continuation : String, *, client_config : ClientConfig | Nil = nil)
def next(continuation : String, *, client_config : ClientConfig? = nil)
# JSON Request data, required by the API
data = {
"context" => self.make_context(client_config),
@ -435,7 +435,7 @@ module YoutubeAPI
end
# :ditto:
def next(data : Hash, *, client_config : ClientConfig | Nil = nil)
def next(data : Hash, *, client_config : ClientConfig? = nil)
# JSON Request data, required by the API
data2 = data.merge({
"context" => self.make_context(client_config),
@ -445,7 +445,7 @@ module YoutubeAPI
end
# Allow a NamedTuple to be passed, too.
def next(data : NamedTuple, *, client_config : ClientConfig | Nil = nil)
def next(data : NamedTuple, *, client_config : ClientConfig? = nil)
return self.next(data.to_h, client_config: client_config)
end
@ -466,7 +466,7 @@ module YoutubeAPI
if CONFIG.invidious_companion.present?
return self._post_invidious_companion("/youtubei/v1/player", data)
else
return nil
return
end
end
@ -498,7 +498,7 @@ module YoutubeAPI
# channel_b = YoutubeAPI.resolve_url("https://www.youtube.com/c/invalid")
# ```
#
def resolve_url(url : String, client_config : ClientConfig | Nil = nil)
def resolve_url(url : String, client_config : ClientConfig? = nil)
data = {
"context" => self.make_context(nil),
"url" => url,
@ -524,7 +524,7 @@ module YoutubeAPI
def search(
search_query : String,
params : String,
client_config : ClientConfig | Nil = nil,
client_config : ClientConfig? = nil,
)
# JSON Request data, required by the API
data = {
@ -550,7 +550,7 @@ module YoutubeAPI
def get_transcript(
params : String,
client_config : ClientConfig | Nil = nil,
client_config : ClientConfig? = nil,
) : Hash(String, JSON::Any)
data = {
"context" => self.make_context(client_config),
@ -572,7 +572,7 @@ module YoutubeAPI
def _post_json(
endpoint : String,
data : Hash,
client_config : ClientConfig | Nil,
client_config : ClientConfig?,
) : Hash(String, JSON::Any)
# Use the default client config if nil is passed
client_config ||= DEFAULT_CLIENT_CONFIG