Merge 540b65a332b869dc42b2427ee285554589e3ffd5 into d51a7a44ad91d2fa7d1330970a15a0d8f365f250

This commit is contained in:
Sijawusz Pur Rahnama 2026-01-24 12:53:00 +09:00 committed by GitHub
commit 5a4ba13228
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
109 changed files with 1284 additions and 1314 deletions

View File

@ -2,23 +2,15 @@
# Lint
#
Lint/UnusedArgument:
Excluded:
- "**/*.ecr"
# Exclude assigns for ECR files
Lint/UselessAssign:
Excluded:
- src/invidious.cr
- src/invidious/helpers/errors.cr
- src/invidious/routes/**/*.cr
# Ignore false negative (if !db.query_one?...)
Lint/UnreachableCode:
Excluded:
- src/invidious/database/base.cr
# Ignore shadowed variable `key` (it works for now, and that's
# a sensitive part of the code)
Lint/ShadowingOuterLocalVar:
Excluded:
- src/invidious/helpers/tokens.cr
- "**/*.ecr"
- src/invidious/routes/**/*.cr
Lint/NotNil:
Enabled: false
@ -27,41 +19,17 @@ Lint/SpecFilename:
Excluded:
- spec/parsers_helper.cr
#
# Style
#
Style/RedundantBegin:
Enabled: false
Style/RedundantReturn:
Enabled: false
Style/RedundantNext:
Enabled: false
Style/ParenthesesAroundCondition:
Enabled: false
# This requires a rewrite of most data structs (and their usage) in Invidious.
Naming/QueryBoolMethods:
Enabled: false
Naming/AccessorMethodName:
Enabled: false
Naming/BlockParameterName:
Enabled: false
# Hides TODO comment warnings.
#
# Call `bin/ameba --only Documentation/DocumentationAdmonition` to
# list them
Documentation/DocumentationAdmonition:
Enabled: false
#
# Metrics
#

19
.github/workflows/ameba.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Ameba
on:
push:
pull_request:
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Download source
uses: actions/checkout@v6
- name: Run Ameba Linter
uses: crystal-ameba/github-action@master

View File

@ -26,7 +26,6 @@ on:
jobs:
build:
runs-on: ubuntu-latest
name: "build - crystal: ${{ matrix.crystal }}, stable: ${{ matrix.stable }}"
@ -122,7 +121,6 @@ jobs:
run: docker compose logs
lint:
runs-on: ubuntu-latest
continue-on-error: true
@ -159,6 +157,3 @@ jobs:
git diff
exit 1
fi
- name: Run Ameba linter
run: bin/ameba

View File

@ -2,7 +2,7 @@ version: 2.0
shards:
ameba:
git: https://github.com/crystal-ameba/ameba.git
version: 1.6.1
version: 1.7.0-dev+git.commit.9dbeb92f89d7a668940029bd7b935bef370f26c1
athena-negotiation:
git: https://github.com/athena-framework/negotiation.git

View File

@ -6,7 +6,7 @@ authors:
- Contributors!
description: |
Invidious is an alternative front-end to YouTube
Invidious is an alternative front-end to YouTube
dependencies:
pg:
@ -34,7 +34,7 @@ development_dependencies:
version: ~> 0.10.4
ameba:
github: crystal-ameba/ameba
version: ~> 1.6.1
branch: master
crystal: ">= 1.10.0, < 2.0.0"

View File

@ -14,7 +14,7 @@ require "spectator"
require "../../../src/invidious/http_server/static_assets_handler.cr"
private def get_static_assets_handler
return Invidious::HttpServer::StaticAssetsHandler.new "spec/http_server/handlers/static_assets_handler", directory_listing: false
Invidious::HttpServer::StaticAssetsHandler.new "spec/http_server/handlers/static_assets_handler", directory_listing: false
end
# Slightly modified version of `handle` function from
@ -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
@ -125,7 +125,7 @@ Spectator.describe StaticAssetsHandler do
gzip.gets_to_end
end
return expect(decompressed)
expect(decompressed)
end
it "For full file requests" do

View File

@ -7,22 +7,22 @@ Spectator.configure do |config|
end
def csv_sample
return <<-CSV
Kanal-ID,Kanal-URL,Kanaltitel
UC0hHW5Y08ggq-9kbrGgWj0A,http://www.youtube.com/channel/UC0hHW5Y08ggq-9kbrGgWj0A,Matias Marolla
UC0vBXGSyV14uvJ4hECDOl0Q,http://www.youtube.com/channel/UC0vBXGSyV14uvJ4hECDOl0Q,Techquickie
UC1sELGmy5jp5fQUugmuYlXQ,http://www.youtube.com/channel/UC1sELGmy5jp5fQUugmuYlXQ,Minecraft
UC9kFnwdCRrX7oTjqKd6-tiQ,http://www.youtube.com/channel/UC9kFnwdCRrX7oTjqKd6-tiQ,LUMOX - Topic
UCBa659QWEk1AI4Tg--mrJ2A,http://www.youtube.com/channel/UCBa659QWEk1AI4Tg--mrJ2A,Tom Scott
UCGu6_XQ64rXPR6nuitMQE_A,http://www.youtube.com/channel/UCGu6_XQ64rXPR6nuitMQE_A,Callcenter Fun
UCGwu0nbY2wSkW8N-cghnLpA,http://www.youtube.com/channel/UCGwu0nbY2wSkW8N-cghnLpA,Jaiden Animations
UCQ0OvZ54pCFZwsKxbltg_tg,http://www.youtube.com/channel/UCQ0OvZ54pCFZwsKxbltg_tg,Methos
UCRE6itj4Jte4manQEu3Y7OA,http://www.youtube.com/channel/UCRE6itj4Jte4manQEu3Y7OA,Chipflake
UCRLc6zsv_d0OEBO8OOkz-DA,http://www.youtube.com/channel/UCRLc6zsv_d0OEBO8OOkz-DA,Kegy
UCSl5Uxu2LyaoAoMMGp6oTJA,http://www.youtube.com/channel/UCSl5Uxu2LyaoAoMMGp6oTJA,Atomic Shrimp
UCXuqSBlHAE6Xw-yeJA0Tunw,http://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw,Linus Tech Tips
UCZ5XnGb-3t7jCkXdawN2tkA,http://www.youtube.com/channel/UCZ5XnGb-3t7jCkXdawN2tkA,Discord
CSV
<<-CSV
Kanal-ID,Kanal-URL,Kanaltitel
UC0hHW5Y08ggq-9kbrGgWj0A,http://www.youtube.com/channel/UC0hHW5Y08ggq-9kbrGgWj0A,Matias Marolla
UC0vBXGSyV14uvJ4hECDOl0Q,http://www.youtube.com/channel/UC0vBXGSyV14uvJ4hECDOl0Q,Techquickie
UC1sELGmy5jp5fQUugmuYlXQ,http://www.youtube.com/channel/UC1sELGmy5jp5fQUugmuYlXQ,Minecraft
UC9kFnwdCRrX7oTjqKd6-tiQ,http://www.youtube.com/channel/UC9kFnwdCRrX7oTjqKd6-tiQ,LUMOX - Topic
UCBa659QWEk1AI4Tg--mrJ2A,http://www.youtube.com/channel/UCBa659QWEk1AI4Tg--mrJ2A,Tom Scott
UCGu6_XQ64rXPR6nuitMQE_A,http://www.youtube.com/channel/UCGu6_XQ64rXPR6nuitMQE_A,Callcenter Fun
UCGwu0nbY2wSkW8N-cghnLpA,http://www.youtube.com/channel/UCGwu0nbY2wSkW8N-cghnLpA,Jaiden Animations
UCQ0OvZ54pCFZwsKxbltg_tg,http://www.youtube.com/channel/UCQ0OvZ54pCFZwsKxbltg_tg,Methos
UCRE6itj4Jte4manQEu3Y7OA,http://www.youtube.com/channel/UCRE6itj4Jte4manQEu3Y7OA,Chipflake
UCRLc6zsv_d0OEBO8OOkz-DA,http://www.youtube.com/channel/UCRLc6zsv_d0OEBO8OOkz-DA,Kegy
UCSl5Uxu2LyaoAoMMGp6oTJA,http://www.youtube.com/channel/UCSl5Uxu2LyaoAoMMGp6oTJA,Atomic Shrimp
UCXuqSBlHAE6Xw-yeJA0Tunw,http://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw,Linus Tech Tips
UCZ5XnGb-3t7jCkXdawN2tkA,http://www.youtube.com/channel/UCZ5XnGb-3t7jCkXdawN2tkA,Discord
CSV
end
Spectator.describe Invidious::User::Import do

View File

@ -26,7 +26,7 @@ def load_mock(file) : Hash(String, JSON::Any)
file = File.join(__DIR__, "..", "mocks", file + ".json")
content = File.read(file)
return JSON.parse(content).as_h
JSON.parse(content).as_h
end
Spectator.configure do |config|

View File

@ -161,7 +161,7 @@ def get_about_info(ucid, locale) : AboutChannel
sub_count = 0
if (metadata_rows = initdata.dig?("header", "pageHeaderRenderer", "content", "pageHeaderViewModel", "metadata", "contentMetadataViewModel", "metadataRows").try &.as_a)
if metadata_rows = initdata.dig?("header", "pageHeaderRenderer", "content", "pageHeaderViewModel", "metadata", "contentMetadataViewModel", "metadataRows").try &.as_a
metadata_rows.each do |row|
metadata_part = row.dig?("metadataParts").try &.as_a.find { |i| i.dig?("text", "content").try &.as_s.includes?("subscribers") }
if !metadata_part.nil?

View File

@ -26,21 +26,21 @@ struct ChannelVideo
json.object do
json.field "type", "shortVideo"
json.field "title", self.title
json.field "videoId", self.id
json.field "title", title
json.field "videoId", id
json.field "videoThumbnails" do
Invidious::JSONify::APIv1.thumbnails(json, self.id)
Invidious::JSONify::APIv1.thumbnails(json, id)
end
json.field "lengthSeconds", self.length_seconds
json.field "lengthSeconds", length_seconds
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "published", self.published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
json.field "author", author
json.field "authorId", ucid
json.field "authorUrl", "/channel/#{ucid}"
json.field "published", published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
json.field "viewCount", self.views
json.field "viewCount", views
end
end
@ -51,34 +51,34 @@ struct ChannelVideo
end
def to_xml(locale, query_params, xml : XML::Builder)
query_params["v"] = self.id
query_params["v"] = id
xml.element("entry") do
xml.element("id") { xml.text "yt:video:#{self.id}" }
xml.element("yt:videoId") { xml.text self.id }
xml.element("yt:channelId") { xml.text self.ucid }
xml.element("title") { xml.text self.title }
xml.element("id") { xml.text "yt:video:#{id}" }
xml.element("yt:videoId") { xml.text id }
xml.element("yt:channelId") { xml.text ucid }
xml.element("title") { xml.text title }
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}")
xml.element("author") do
xml.element("name") { xml.text self.author }
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" }
xml.element("name") { xml.text author }
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
end
xml.element("content", type: "xhtml") do
xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do
xml.element("a", href: "#{HOST_URL}/watch?#{query_params}") do
xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg")
xml.element("img", src: "#{HOST_URL}/vi/#{id}/mqdefault.jpg")
end
end
end
xml.element("published") { xml.text self.published.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("updated") { xml.text self.updated.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("published") { xml.text published.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("updated") { xml.text updated.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("media:group") do
xml.element("media:title") { xml.text self.title }
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg",
xml.element("media:title") { xml.text title }
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{id}/mqdefault.jpg",
width: "320", height: "180")
end
end
@ -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
@ -141,7 +141,7 @@ def get_batch_channels(channels)
end
end
return final
final
end
def get_channel(id) : InvidiousChannel
@ -152,7 +152,7 @@ def get_channel(id) : InvidiousChannel
Invidious::Database::Channels.insert(channel, update_on_conflict: true)
end
return channel
channel
end
def fetch_channel(ucid, pull_all_videos : Bool)
@ -292,5 +292,5 @@ def fetch_channel(ucid, pull_all_videos : Bool)
end
channel.updated = Time.utc
return channel
channel
end

View File

@ -21,7 +21,7 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
items = container.as_a
end
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
end
def decode_ucid_from_post_protobuf(params)
@ -30,7 +30,7 @@ def decode_ucid_from_post_protobuf(params)
.try { |i| IO::Memory.new(i) }
.try { |i| Protodec::Any.parse(i) }
return decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
end
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
@ -53,7 +53,7 @@ def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
items << item
end
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode, is_single_post: true)
extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode, is_single_post: true)
end
def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_single_post : Bool = false)
@ -294,7 +294,7 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_sing
end
end
return response
response
end
def produce_channel_community_continuation(ucid, cursor)
@ -310,7 +310,7 @@ def produce_channel_community_continuation(ucid, cursor)
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
return continuation
continuation
end
def extract_channel_community_cursor(continuation)

View File

@ -24,7 +24,7 @@ def fetch_channel_playlists(ucid, author, continuation, sort_by)
initial_data = YoutubeAPI.browse(ucid, params: params || "")
end
return extract_items(initial_data, author, ucid)
extract_items(initial_data, author, ucid)
end
def fetch_channel_podcasts(ucid, author, continuation)
@ -33,7 +33,7 @@ def fetch_channel_podcasts(ucid, author, continuation)
else
initial_data = YoutubeAPI.browse(ucid, params: "Eghwb2RjYXN0c_IGBQoDugEA")
end
return extract_items(initial_data, author, ucid)
extract_items(initial_data, author, ucid)
end
def fetch_channel_releases(ucid, author, continuation)
@ -42,7 +42,7 @@ def fetch_channel_releases(ucid, author, continuation)
else
initial_data = YoutubeAPI.browse(ucid, params: "EghyZWxlYXNlc_IGBQoDsgEA")
end
return extract_items(initial_data, author, ucid)
extract_items(initial_data, author, ucid)
end
def fetch_channel_courses(ucid, author, continuation)
@ -51,5 +51,5 @@ def fetch_channel_courses(ucid, author, continuation)
else
initial_data = YoutubeAPI.browse(ucid, params: "Egdjb3Vyc2Vz8gYFCgPCAQA%3D")
end
return extract_items(initial_data, author, ucid)
extract_items(initial_data, author, ucid)
end

View File

@ -9,7 +9,7 @@ module Invidious::Channel::Tabs
# an author name and ucid directly (e.g in RSS feeds).
# TODO: figure out how to get rid of that
def get_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
return get_videos(
get_videos(
channel.author, channel.ucid,
continuation: continuation, sort_by: sort_by
)
@ -19,7 +19,7 @@ module Invidious::Channel::Tabs
# an author name and ucid directly (e.g in RSS feeds).
# TODO: figure out how to get rid of that
def get_videos(channel : InvidiousChannel, *, continuation : String? = nil, sort_by = "newest")
return get_videos(
get_videos(
channel.author, channel.id,
continuation: continuation, sort_by: sort_by
)
@ -29,7 +29,7 @@ module Invidious::Channel::Tabs
continuation ||= make_initial_videos_ctoken(ucid, sort_by)
initial_data = YoutubeAPI.browse(continuation: continuation)
return extract_items(initial_data, author, ucid)
extract_items(initial_data, author, ucid)
end
def get_60_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
@ -59,7 +59,7 @@ module Invidious::Channel::Tabs
continuation ||= make_initial_shorts_ctoken(channel.ucid, sort_by)
initial_data = YoutubeAPI.browse(continuation: continuation)
return extract_items(initial_data, channel.author, channel.ucid)
extract_items(initial_data, channel.author, channel.ucid)
end
# -------------------
@ -70,7 +70,7 @@ module Invidious::Channel::Tabs
continuation ||= make_initial_livestreams_ctoken(channel.ucid, sort_by)
initial_data = YoutubeAPI.browse(continuation: continuation)
return extract_items(initial_data, channel.author, channel.ucid)
extract_items(initial_data, channel.author, channel.ucid)
end
def get_60_livestreams(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
@ -98,10 +98,10 @@ module Invidious::Channel::Tabs
private def sort_options_videos_short(sort_by : String)
case sort_by
when "newest" then return 4_i64
when "popular" then return 2_i64
when "oldest" then return 5_i64
else return 4_i64 # Fallback to "newest"
when "newest" then 4_i64
when "popular" then 2_i64
when "oldest" then 5_i64
else 4_i64 # Fallback to "newest"
end
end
@ -118,7 +118,7 @@ module Invidious::Channel::Tabs
},
}
return channel_ctoken_wrap(ucid, object)
channel_ctoken_wrap(ucid, object)
end
# Generate the initial "continuation token" to get the first page of the
@ -134,7 +134,7 @@ module Invidious::Channel::Tabs
},
}
return channel_ctoken_wrap(ucid, object)
channel_ctoken_wrap(ucid, object)
end
# Generate the initial "continuation token" to get the first page of the
@ -158,7 +158,7 @@ module Invidious::Channel::Tabs
},
}
return channel_ctoken_wrap(ucid, object)
channel_ctoken_wrap(ucid, object)
end
# The protobuf structure common between videos/shorts/livestreams
@ -187,6 +187,6 @@ module Invidious::Channel::Tabs
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
return continuation
continuation
end
end

View File

@ -37,7 +37,7 @@ def text_to_parsed_content(text : String) : JSON::Any
nodes << (node)
end
end
return JSON.parse({"runs" => nodes}.to_json)
JSON.parse({"runs" => nodes}.to_json)
end
def parse_content(content : JSON::Any, video_id : String? = "") : String
@ -85,5 +85,5 @@ def content_to_comment_html(content, video_id : String? = "")
text
end
return html_array.join("").delete('\ufeff')
html_array.join("").delete('\ufeff')
end

View File

@ -45,7 +45,7 @@ module Invidious::Comments
html = node
end
return html.to_xml(options: XML::SaveOptions::NO_DECL)
html.to_xml(options: XML::SaveOptions::NO_DECL)
end
def fill_links(html, scheme, host)
@ -71,6 +71,6 @@ module Invidious::Comments
html = html.xpath_node(%q(//body/p)).not_nil!
end
return html.to_xml(options: XML::SaveOptions::NO_DECL)
html.to_xml(options: XML::SaveOptions::NO_DECL)
end
end

View File

@ -13,7 +13,7 @@ module Invidious::Comments
client_config = YoutubeAPI::ClientConfig.new(region: region)
response = YoutubeAPI.next(continuation: ctoken, client_config: client_config)
return parse_youtube(id, response, format, locale, thin_mode, sort_by)
parse_youtube(id, response, format, locale, thin_mode, sort_by)
end
def fetch_community_post_comments(ucid, post_id, sort_by = "top")
@ -58,7 +58,7 @@ module Invidious::Comments
.try { |i| URI.encode_www_form(i) }
initial_data = YoutubeAPI.browse(continuation: continuation)
return initial_data
initial_data
end
def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", is_post = false)
@ -320,7 +320,7 @@ module Invidious::Comments
end
end
return response
response
end
def produce_continuation(video_id, cursor = "", sort_by = "top")
@ -364,6 +364,6 @@ module Invidious::Comments
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
return continuation
continuation
end
end

View File

@ -58,7 +58,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
@ -183,15 +183,15 @@ class Config
def disabled?(option)
case disabled = CONFIG.disable_proxy
when Bool
return disabled
disabled
when Array
if disabled.includes? option
return true
true
else
return false
false
end
else
return false
false
end
end
@ -212,14 +212,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
@ -227,10 +227,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
@ -241,14 +241,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 %}
@ -318,6 +318,6 @@ class Config
end
end
return config
config
end
end

View File

@ -8,7 +8,7 @@ module Invidious::Database::Annotations
INSERT INTO annotations
VALUES ($1, $2)
ON CONFLICT DO NOTHING
SQL
SQL
PG_DB.exec(request, id, annotations)
end
@ -17,8 +17,8 @@ module Invidious::Database::Annotations
request = <<-SQL
SELECT * FROM annotations
WHERE id = $1
SQL
SQL
return PG_DB.query_one?(request, id, as: Annotation)
PG_DB.query_one?(request, id, as: Annotation)
end
end

View File

@ -32,6 +32,7 @@ module Invidious::Database
def check_enum(enum_name, struct_type = nil)
return # TODO
# ameba:disable Lint/UnreachableCode
if !PG_DB.query_one?("SELECT true FROM pg_type WHERE typname = $1", enum_name, as: Bool)
LOGGER.info("check_enum: CREATE TYPE #{enum_name}")
@ -131,6 +132,6 @@ module Invidious::Database
end
end
return column_array
column_array
end
end

View File

@ -16,13 +16,13 @@ module Invidious::Database::Channels
request = <<-SQL
INSERT INTO channels
VALUES (#{arg_array(channel_array)})
SQL
SQL
if update_on_conflict
request += <<-SQL
ON CONFLICT (id) DO UPDATE
SET author = $2, updated = $3
SQL
SQL
end
PG_DB.exec(request, args: channel_array)
@ -37,7 +37,7 @@ module Invidious::Database::Channels
UPDATE channels
SET updated = now(), author = $1, deleted = false
WHERE id = $2
SQL
SQL
PG_DB.exec(request, author, id)
end
@ -47,7 +47,7 @@ module Invidious::Database::Channels
UPDATE channels
SET subscribed = now()
WHERE id = $1
SQL
SQL
PG_DB.exec(request, id)
end
@ -57,7 +57,7 @@ module Invidious::Database::Channels
UPDATE channels
SET updated = now(), deleted = true
WHERE id = $1
SQL
SQL
PG_DB.exec(request, id)
end
@ -70,9 +70,9 @@ module Invidious::Database::Channels
request = <<-SQL
SELECT * FROM channels
WHERE id = $1
SQL
SQL
return PG_DB.query_one?(request, id, as: InvidiousChannel)
PG_DB.query_one?(request, id, as: InvidiousChannel)
end
def select(ids : Array(String)) : Array(InvidiousChannel)?
@ -81,9 +81,9 @@ module Invidious::Database::Channels
request = <<-SQL
SELECT * FROM channels
WHERE id = ANY($1)
SQL
SQL
return PG_DB.query_all(request, ids, as: InvidiousChannel)
PG_DB.query_all(request, ids, as: InvidiousChannel)
end
end
@ -112,9 +112,9 @@ module Invidious::Database::ChannelVideos
SET title = $2, published = $3, updated = $4, ucid = $5,
author = $6, length_seconds = $7, live_now = $8, #{last_items}
RETURNING (xmax=0) AS was_insert
SQL
SQL
return PG_DB.query_one(request, *video.to_tuple, as: Bool)
PG_DB.query_one(request, *video.to_tuple, as: Bool)
end
# -------------------
@ -128,9 +128,9 @@ module Invidious::Database::ChannelVideos
SELECT * FROM channel_videos
WHERE id = ANY($1)
ORDER BY published DESC
SQL
SQL
return PG_DB.query_all(request, ids, as: ChannelVideo)
PG_DB.query_all(request, ids, as: ChannelVideo)
end
def select_notfications(ucid : String, since : Time) : Array(ChannelVideo)
@ -139,9 +139,9 @@ module Invidious::Database::ChannelVideos
WHERE ucid = $1 AND published > $2
ORDER BY published DESC
LIMIT 15
SQL
SQL
return PG_DB.query_all(request, ucid, since, as: ChannelVideo)
PG_DB.query_all(request, ucid, since, as: ChannelVideo)
end
def select_popular_videos : Array(ChannelVideo)
@ -151,7 +151,7 @@ module Invidious::Database::ChannelVideos
WHERE ucid IN (SELECT channel FROM (SELECT UNNEST(subscriptions) AS channel FROM users) AS d
GROUP BY channel ORDER BY COUNT(channel) DESC LIMIT 40)
ORDER BY ucid, published DESC
SQL
SQL
PG_DB.query_all(request, as: ChannelVideo)
end

View File

@ -4,27 +4,27 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.channels
(
id text NOT NULL,
author text,
updated timestamp with time zone,
deleted boolean,
subscribed timestamp with time zone,
CONSTRAINT channels_id_key UNIQUE (id)
);
SQL
CREATE TABLE IF NOT EXISTS public.channels
(
id text NOT NULL,
author text,
updated timestamp with time zone,
deleted boolean,
subscribed timestamp with time zone,
CONSTRAINT channels_id_key UNIQUE (id)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.channels TO current_user;
SQL
GRANT ALL ON TABLE public.channels TO current_user;
SQL
conn.exec <<-SQL
CREATE INDEX IF NOT EXISTS channels_id_idx
ON public.channels
USING btree
(id COLLATE pg_catalog."default");
SQL
CREATE INDEX IF NOT EXISTS channels_id_idx
ON public.channels
USING btree
(id COLLATE pg_catalog."default");
SQL
end
end
end

View File

@ -4,25 +4,25 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE UNLOGGED TABLE IF NOT EXISTS public.videos
(
id text NOT NULL,
info text,
updated timestamp with time zone,
CONSTRAINT videos_pkey PRIMARY KEY (id)
);
SQL
CREATE UNLOGGED TABLE IF NOT EXISTS public.videos
(
id text NOT NULL,
info text,
updated timestamp with time zone,
CONSTRAINT videos_pkey PRIMARY KEY (id)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.videos TO current_user;
SQL
GRANT ALL ON TABLE public.videos TO current_user;
SQL
conn.exec <<-SQL
CREATE UNIQUE INDEX IF NOT EXISTS id_idx
ON public.videos
USING btree
(id COLLATE pg_catalog."default");
SQL
CREATE UNIQUE INDEX IF NOT EXISTS id_idx
ON public.videos
USING btree
(id COLLATE pg_catalog."default");
SQL
end
end
end

View File

@ -4,32 +4,32 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.channel_videos
(
id text NOT NULL,
title text,
published timestamp with time zone,
updated timestamp with time zone,
ucid text,
author text,
length_seconds integer,
live_now boolean,
premiere_timestamp timestamp with time zone,
views bigint,
CONSTRAINT channel_videos_id_key UNIQUE (id)
);
SQL
CREATE TABLE IF NOT EXISTS public.channel_videos
(
id text NOT NULL,
title text,
published timestamp with time zone,
updated timestamp with time zone,
ucid text,
author text,
length_seconds integer,
live_now boolean,
premiere_timestamp timestamp with time zone,
views bigint,
CONSTRAINT channel_videos_id_key UNIQUE (id)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.channel_videos TO current_user;
SQL
GRANT ALL ON TABLE public.channel_videos TO current_user;
SQL
conn.exec <<-SQL
CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx
ON public.channel_videos
USING btree
(ucid COLLATE pg_catalog."default");
SQL
CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx
ON public.channel_videos
USING btree
(ucid COLLATE pg_catalog."default");
SQL
end
end
end

View File

@ -4,31 +4,31 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.users
(
updated timestamp with time zone,
notifications text[],
subscriptions text[],
email text NOT NULL,
preferences text,
password text,
token text,
watched text[],
feed_needs_update boolean,
CONSTRAINT users_email_key UNIQUE (email)
);
SQL
CREATE TABLE IF NOT EXISTS public.users
(
updated timestamp with time zone,
notifications text[],
subscriptions text[],
email text NOT NULL,
preferences text,
password text,
token text,
watched text[],
feed_needs_update boolean,
CONSTRAINT users_email_key UNIQUE (email)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.users TO current_user;
SQL
GRANT ALL ON TABLE public.users TO current_user;
SQL
conn.exec <<-SQL
CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx
ON public.users
USING btree
(lower(email) COLLATE pg_catalog."default");
SQL
CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx
ON public.users
USING btree
(lower(email) COLLATE pg_catalog."default");
SQL
end
end
end

View File

@ -4,25 +4,25 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.session_ids
(
id text NOT NULL,
email text,
issued timestamp with time zone,
CONSTRAINT session_ids_pkey PRIMARY KEY (id)
);
SQL
CREATE TABLE IF NOT EXISTS public.session_ids
(
id text NOT NULL,
email text,
issued timestamp with time zone,
CONSTRAINT session_ids_pkey PRIMARY KEY (id)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.session_ids TO current_user;
SQL
GRANT ALL ON TABLE public.session_ids TO current_user;
SQL
conn.exec <<-SQL
CREATE INDEX IF NOT EXISTS session_ids_id_idx
ON public.session_ids
USING btree
(id COLLATE pg_catalog."default");
SQL
CREATE INDEX IF NOT EXISTS session_ids_id_idx
ON public.session_ids
USING btree
(id COLLATE pg_catalog."default");
SQL
end
end
end

View File

@ -4,24 +4,24 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.nonces
(
nonce text,
expire timestamp with time zone,
CONSTRAINT nonces_id_key UNIQUE (nonce)
);
SQL
CREATE TABLE IF NOT EXISTS public.nonces
(
nonce text,
expire timestamp with time zone,
CONSTRAINT nonces_id_key UNIQUE (nonce)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.nonces TO current_user;
SQL
GRANT ALL ON TABLE public.nonces TO current_user;
SQL
conn.exec <<-SQL
CREATE INDEX IF NOT EXISTS nonces_nonce_idx
ON public.nonces
USING btree
(nonce COLLATE pg_catalog."default");
SQL
CREATE INDEX IF NOT EXISTS nonces_nonce_idx
ON public.nonces
USING btree
(nonce COLLATE pg_catalog."default");
SQL
end
end
end

View File

@ -4,17 +4,17 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.annotations
(
id text NOT NULL,
annotations xml,
CONSTRAINT annotations_id_key UNIQUE (id)
);
SQL
CREATE TABLE IF NOT EXISTS public.annotations
(
id text NOT NULL,
annotations xml,
CONSTRAINT annotations_id_key UNIQUE (id)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.annotations TO current_user;
SQL
GRANT ALL ON TABLE public.annotations TO current_user;
SQL
end
end
end

View File

@ -5,33 +5,33 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
if !privacy_type_exists?(conn)
conn.exec <<-SQL
CREATE TYPE public.privacy AS ENUM
(
'Public',
'Unlisted',
'Private'
);
SQL
CREATE TYPE public.privacy AS ENUM
(
'Public',
'Unlisted',
'Private'
);
SQL
end
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.playlists
(
title text,
id text primary key,
author text,
description text,
video_count integer,
created timestamptz,
updated timestamptz,
privacy privacy,
index int8[]
);
SQL
CREATE TABLE IF NOT EXISTS public.playlists
(
title text,
id text primary key,
author text,
description text,
video_count integer,
created timestamptz,
updated timestamptz,
privacy privacy,
index int8[]
);
SQL
conn.exec <<-SQL
GRANT ALL ON public.playlists TO current_user;
SQL
GRANT ALL ON public.playlists TO current_user;
SQL
end
private def privacy_type_exists?(conn : DB::Connection) : Bool
@ -42,7 +42,7 @@ module Invidious::Database::Migrations
WHERE pg_namespace.nspname = 'public'
AND pg_type.typname = 'privacy'
LIMIT 1;
SQL
SQL
!conn.query_one?(request, as: Int32).nil?
end

View File

@ -4,24 +4,24 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
CREATE TABLE IF NOT EXISTS public.playlist_videos
(
title text,
id text,
author text,
ucid text,
length_seconds integer,
published timestamptz,
plid text references playlists(id),
index int8,
live_now boolean,
PRIMARY KEY (index,plid)
);
SQL
CREATE TABLE IF NOT EXISTS public.playlist_videos
(
title text,
id text,
author text,
ucid text,
length_seconds integer,
published timestamptz,
plid text references playlists(id),
index int8,
live_now boolean,
PRIMARY KEY (index,plid)
);
SQL
conn.exec <<-SQL
GRANT ALL ON TABLE public.playlist_videos TO current_user;
SQL
GRANT ALL ON TABLE public.playlist_videos TO current_user;
SQL
end
end
end

View File

@ -4,8 +4,8 @@ module Invidious::Database::Migrations
def up(conn : DB::Connection)
conn.exec <<-SQL
ALTER TABLE public.videos SET UNLOGGED;
SQL
ALTER TABLE public.videos SET UNLOGGED;
SQL
end
end
end

View File

@ -44,6 +44,6 @@ class Invidious::Database::Migrator
id bigserial PRIMARY KEY,
version bigint NOT NULL
)
SQL
SQL
end
end

View File

@ -12,7 +12,7 @@ module Invidious::Database::Nonces
INSERT INTO nonces
VALUES ($1, $2)
ON CONFLICT DO NOTHING
SQL
SQL
PG_DB.exec(request, nonce, expire)
end
@ -21,7 +21,7 @@ module Invidious::Database::Nonces
request = <<-SQL
DELETE FROM nonces *
WHERE expire < now()
SQL
SQL
PG_DB.exec(request)
end
@ -35,7 +35,7 @@ module Invidious::Database::Nonces
UPDATE nonces
SET expire = $1
WHERE nonce = $2
SQL
SQL
PG_DB.exec(request, Time.utc(1990, 1, 1), nonce)
end
@ -48,8 +48,8 @@ module Invidious::Database::Nonces
request = <<-SQL
SELECT * FROM nonces
WHERE nonce = $1
SQL
SQL
return PG_DB.query_one?(request, nonce, as: {String, Time})
PG_DB.query_one?(request, nonce, as: {String, Time})
end
end

View File

@ -16,7 +16,7 @@ module Invidious::Database::Playlists
request = <<-SQL
INSERT INTO playlists
VALUES (#{arg_array(playlist_array)})
SQL
SQL
PG_DB.exec(request, args: playlist_array)
end
@ -27,7 +27,7 @@ module Invidious::Database::Playlists
request = <<-SQL
DELETE FROM playlists *
WHERE id = $1
SQL
SQL
PG_DB.exec(request, id)
end
@ -41,7 +41,7 @@ module Invidious::Database::Playlists
UPDATE playlists
SET title = $1, privacy = $2, description = $3, updated = $4
WHERE id = $5
SQL
SQL
PG_DB.exec(request, title, privacy, description, updated, id)
end
@ -51,7 +51,7 @@ module Invidious::Database::Playlists
UPDATE playlists
SET description = $1
WHERE id = $2
SQL
SQL
PG_DB.exec(request, description, id)
end
@ -61,7 +61,7 @@ module Invidious::Database::Playlists
UPDATE playlists
SET subscribed = now()
WHERE id = $1
SQL
SQL
PG_DB.exec(request, id)
end
@ -73,7 +73,7 @@ module Invidious::Database::Playlists
video_count = cardinality(index) + 1,
updated = now()
WHERE id = $2
SQL
SQL
PG_DB.exec(request, index, id)
end
@ -85,7 +85,7 @@ module Invidious::Database::Playlists
video_count = cardinality(index) - 1,
updated = now()
WHERE id = $2
SQL
SQL
PG_DB.exec(request, index, id)
end
@ -98,18 +98,18 @@ module Invidious::Database::Playlists
request = <<-SQL
SELECT * FROM playlists
WHERE id = $1
SQL
SQL
return PG_DB.query_one?(request, id, as: InvidiousPlaylist)
PG_DB.query_one?(request, id, as: InvidiousPlaylist)
end
def select_all(*, author : String) : Array(InvidiousPlaylist)
request = <<-SQL
SELECT * FROM playlists
WHERE author = $1
SQL
SQL
return PG_DB.query_all(request, author, as: InvidiousPlaylist)
PG_DB.query_all(request, author, as: InvidiousPlaylist)
end
# -------------------
@ -121,7 +121,7 @@ module Invidious::Database::Playlists
SELECT * FROM playlists
WHERE author = $1 AND id LIKE 'IV%'
ORDER BY created
SQL
SQL
PG_DB.query_all(request, email, as: InvidiousPlaylist)
end
@ -131,7 +131,7 @@ module Invidious::Database::Playlists
SELECT * FROM playlists
WHERE author = $1 AND id NOT LIKE 'IV%'
ORDER BY created
SQL
SQL
PG_DB.query_all(request, email, as: InvidiousPlaylist)
end
@ -141,7 +141,7 @@ module Invidious::Database::Playlists
SELECT id,title FROM playlists
WHERE author = $1 AND id LIKE 'IV%'
ORDER BY title
SQL
SQL
PG_DB.query_all(request, email, as: {String, String})
end
@ -155,9 +155,9 @@ module Invidious::Database::Playlists
request = <<-SQL
SELECT id FROM playlists
WHERE id = $1
SQL
SQL
return PG_DB.query_one?(request, id, as: String).nil?
PG_DB.query_one?(request, id, as: String).nil?
end
# Count how many playlist a user has created.
@ -165,9 +165,9 @@ module Invidious::Database::Playlists
request = <<-SQL
SELECT count(*) FROM playlists
WHERE author = $1
SQL
SQL
return PG_DB.query_one?(request, author, as: Int64) || 0_i64
PG_DB.query_one?(request, author, as: Int64) || 0_i64
end
end
@ -189,7 +189,7 @@ module Invidious::Database::PlaylistVideos
request = <<-SQL
INSERT INTO playlist_videos
VALUES (#{arg_array(video_array)})
SQL
SQL
PG_DB.exec(request, args: video_array)
end
@ -198,7 +198,7 @@ module Invidious::Database::PlaylistVideos
request = <<-SQL
DELETE FROM playlist_videos *
WHERE index = $1
SQL
SQL
PG_DB.exec(request, index)
end
@ -207,7 +207,7 @@ module Invidious::Database::PlaylistVideos
request = <<-SQL
DELETE FROM playlist_videos *
WHERE plid = $1
SQL
SQL
PG_DB.exec(request, plid)
end
@ -223,9 +223,9 @@ module Invidious::Database::PlaylistVideos
ORDER BY array_position($2, index)
LIMIT $3
OFFSET $4
SQL
SQL
return PG_DB.query_all(request, plid, index, limit, offset, as: PlaylistVideo)
PG_DB.query_all(request, plid, index, limit, offset, as: PlaylistVideo)
end
def select_index(plid : String, vid : String) : Int64?
@ -233,9 +233,9 @@ module Invidious::Database::PlaylistVideos
SELECT index FROM playlist_videos
WHERE plid = $1 AND id = $2
LIMIT 1
SQL
SQL
return PG_DB.query_one?(request, plid, vid, as: Int64)
PG_DB.query_one?(request, plid, vid, as: Int64)
end
def select_one_id(plid : String, index : VideoIndex) : String?
@ -244,9 +244,9 @@ module Invidious::Database::PlaylistVideos
WHERE plid = $1
ORDER BY array_position($2, index)
LIMIT 1
SQL
SQL
return PG_DB.query_one?(request, plid, index, as: String)
PG_DB.query_one?(request, plid, index, as: String)
end
def select_ids(plid : String, index : VideoIndex, limit = 500) : Array(String)
@ -255,8 +255,8 @@ module Invidious::Database::PlaylistVideos
WHERE plid = $1
ORDER BY array_position($2, index)
LIMIT $3
SQL
SQL
return PG_DB.query_all(request, plid, index, limit, as: String)
PG_DB.query_all(request, plid, index, limit, as: String)
end
end

View File

@ -11,7 +11,7 @@ module Invidious::Database::SessionIDs
request = <<-SQL
INSERT INTO session_ids
VALUES ($1, $2, now())
SQL
SQL
request += " ON CONFLICT (id) DO NOTHING" if handle_conflicts
@ -26,7 +26,7 @@ module Invidious::Database::SessionIDs
request = <<-SQL
DELETE FROM session_ids *
WHERE id = $1
SQL
SQL
PG_DB.exec(request, sid)
end
@ -35,7 +35,7 @@ module Invidious::Database::SessionIDs
request = <<-SQL
DELETE FROM session_ids *
WHERE email = $1
SQL
SQL
PG_DB.exec(request, email)
end
@ -44,7 +44,7 @@ module Invidious::Database::SessionIDs
request = <<-SQL
DELETE FROM session_ids *
WHERE id = $1 AND email = $2
SQL
SQL
PG_DB.exec(request, sid, email)
end
@ -57,7 +57,7 @@ module Invidious::Database::SessionIDs
request = <<-SQL
SELECT email FROM session_ids
WHERE id = $1
SQL
SQL
PG_DB.query_one?(request, sid, as: String)
end
@ -67,7 +67,7 @@ module Invidious::Database::SessionIDs
SELECT id, issued FROM session_ids
WHERE email = $1
ORDER BY issued DESC
SQL
SQL
PG_DB.query_all(request, email, as: {session: String, issued: Time})
end

View File

@ -10,7 +10,7 @@ module Invidious::Database::Statistics
def count_users_total : Int64
request = <<-SQL
SELECT count(*) FROM users
SQL
SQL
PG_DB.query_one(request, as: Int64)
end
@ -19,7 +19,7 @@ module Invidious::Database::Statistics
request = <<-SQL
SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '6 months'
SQL
SQL
PG_DB.query_one(request, as: Int64)
end
@ -28,7 +28,7 @@ module Invidious::Database::Statistics
request = <<-SQL
SELECT count(*) FROM users
WHERE CURRENT_TIMESTAMP - updated < '1 month'
SQL
SQL
PG_DB.query_one(request, as: Int64)
end
@ -42,7 +42,7 @@ module Invidious::Database::Statistics
SELECT updated FROM channels
ORDER BY updated DESC
LIMIT 1
SQL
SQL
PG_DB.query_one?(request, as: Time)
end

View File

@ -14,13 +14,13 @@ module Invidious::Database::Users
request = <<-SQL
INSERT INTO users
VALUES (#{arg_array(user_array)})
SQL
SQL
if update_on_conflict
request += <<-SQL
ON CONFLICT (email) DO UPDATE
SET updated = $1, subscriptions = $3
SQL
SQL
end
PG_DB.exec(request, args: user_array)
@ -30,7 +30,7 @@ module Invidious::Database::Users
request = <<-SQL
DELETE FROM users *
WHERE email = $1
SQL
SQL
PG_DB.exec(request, user.email)
end
@ -44,7 +44,7 @@ module Invidious::Database::Users
UPDATE users
SET watched = $1
WHERE email = $2
SQL
SQL
PG_DB.exec(request, user.watched, user.email)
end
@ -54,7 +54,7 @@ module Invidious::Database::Users
UPDATE users
SET watched = array_append(array_remove(watched, $1), $1)
WHERE email = $2
SQL
SQL
PG_DB.exec(request, vid, user.email)
end
@ -64,7 +64,7 @@ module Invidious::Database::Users
UPDATE users
SET watched = array_remove(watched, $1)
WHERE email = $2
SQL
SQL
PG_DB.exec(request, vid, user.email)
end
@ -74,7 +74,7 @@ module Invidious::Database::Users
UPDATE users
SET watched = '{}'
WHERE email = $1
SQL
SQL
PG_DB.exec(request, user.email)
end
@ -88,7 +88,7 @@ module Invidious::Database::Users
UPDATE users
SET feed_needs_update = true, subscriptions = $1
WHERE email = $2
SQL
SQL
PG_DB.exec(request, user.subscriptions, user.email)
end
@ -99,7 +99,7 @@ module Invidious::Database::Users
SET feed_needs_update = true,
subscriptions = array_append(subscriptions,$1)
WHERE email = $2
SQL
SQL
PG_DB.exec(request, ucid, user.email)
end
@ -110,7 +110,7 @@ module Invidious::Database::Users
SET feed_needs_update = true,
subscriptions = array_remove(subscriptions, $1)
WHERE email = $2
SQL
SQL
PG_DB.exec(request, ucid, user.email)
end
@ -125,7 +125,7 @@ module Invidious::Database::Users
SET notifications = array_cat(notifications, $1),
feed_needs_update = true
WHERE $2 = ANY(subscriptions)
SQL
SQL
PG_DB.exec(request, video_ids, channel_id)
end
@ -135,7 +135,7 @@ module Invidious::Database::Users
UPDATE users
SET notifications = array_remove(notifications, $1)
WHERE email = $2
SQL
SQL
PG_DB.exec(request, vid, user.email)
end
@ -145,7 +145,7 @@ module Invidious::Database::Users
UPDATE users
SET notifications = '{}', updated = now()
WHERE email = $1
SQL
SQL
PG_DB.exec(request, user.email)
end
@ -159,7 +159,7 @@ module Invidious::Database::Users
UPDATE users
SET feed_needs_update = true
WHERE $1 = ANY(subscriptions)
SQL
SQL
PG_DB.exec(request, channel_id)
end
@ -169,7 +169,7 @@ module Invidious::Database::Users
UPDATE users
SET preferences = $1
WHERE email = $2
SQL
SQL
PG_DB.exec(request, user.preferences.to_json, user.email)
end
@ -179,7 +179,7 @@ module Invidious::Database::Users
UPDATE users
SET password = $1
WHERE email = $2
SQL
SQL
PG_DB.exec(request, pass, user.email)
end
@ -192,9 +192,9 @@ module Invidious::Database::Users
request = <<-SQL
SELECT * FROM users
WHERE email = $1
SQL
SQL
return PG_DB.query_one?(request, email, as: User)
PG_DB.query_one?(request, email, as: User)
end
# Same as select, but can raise an exception
@ -202,18 +202,18 @@ module Invidious::Database::Users
request = <<-SQL
SELECT * FROM users
WHERE email = $1
SQL
SQL
return PG_DB.query_one(request, email, as: User)
PG_DB.query_one(request, email, as: User)
end
def select(*, token : String) : User?
request = <<-SQL
SELECT * FROM users
WHERE token = $1
SQL
SQL
return PG_DB.query_one?(request, token, as: User)
PG_DB.query_one?(request, token, as: User)
end
def select_notifications(user : User) : Array(String)
@ -221,8 +221,8 @@ module Invidious::Database::Users
SELECT notifications
FROM users
WHERE email = $1
SQL
SQL
return PG_DB.query_one(request, user.email, as: Array(String))
PG_DB.query_one(request, user.email, as: Array(String))
end
end

View File

@ -8,7 +8,7 @@ module Invidious::Database::Videos
INSERT INTO videos
VALUES ($1, $2, $3)
ON CONFLICT (id) DO NOTHING
SQL
SQL
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
end
@ -17,7 +17,7 @@ module Invidious::Database::Videos
request = <<-SQL
DELETE FROM videos *
WHERE id = $1
SQL
SQL
PG_DB.exec(request, id)
end
@ -26,7 +26,7 @@ module Invidious::Database::Videos
request = <<-SQL
DELETE FROM videos *
WHERE updated < (now() - interval '6 hours')
SQL
SQL
PG_DB.exec(request)
end
@ -36,7 +36,7 @@ module Invidious::Database::Videos
UPDATE videos
SET (id, info, updated) = ($1, $2, $3)
WHERE id = $1
SQL
SQL
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
end
@ -45,8 +45,8 @@ module Invidious::Database::Videos
request = <<-SQL
SELECT * FROM videos
WHERE id = $1
SQL
SQL
return PG_DB.query_one?(request, id, as: Video)
PG_DB.query_one?(request, id, as: Video)
end
end

View File

@ -23,7 +23,7 @@ class BrokenTubeException < Exception
end
def message
return "Missing JSON element \"#{@element}\""
"Missing JSON element \"#{@element}\""
end
end

View File

@ -14,12 +14,12 @@ module Invidious::Frontend::ChannelPage
end
def generate_tabs_links(locale : String, channel : AboutChannel, selected_tab : TabsAvailable)
return String.build(1500) do |str|
String.build(1500) do |str|
base_url = "/channel/#{channel.ucid}"
TabsAvailable.each do |tab|
# Ignore playlists, as it is not supported for auto-generated channels yet
next if (tab.playlists? && channel.auto_generated)
next if tab.playlists? && channel.auto_generated
tab_name = tab.to_s.downcase

View File

@ -11,38 +11,38 @@ module Invidious::Frontend::Comments
replies_html = ""
if child.replies.is_a?(RedditThing)
replies = child.replies.as(RedditThing)
replies_html = self.template_reddit(replies.data.as(RedditListing).children, locale)
replies_html = template_reddit(replies.data.as(RedditListing).children, locale)
end
if child.depth > 0
html << <<-END_HTML
<div class="pure-g">
<div class="pure-u-1-24">
</div>
<div class="pure-u-23-24">
END_HTML
html << <<-HTML
<div class="pure-g">
<div class="pure-u-1-24">
</div>
<div class="pure-u-23-24">
HTML
else
html << <<-END_HTML
<div class="pure-g">
<div class="pure-u-1">
END_HTML
html << <<-HTML
<div class="pure-g">
<div class="pure-u-1">
HTML
end
html << <<-END_HTML
<p>
<a href="javascript:void(0)" data-onclick="toggle_parent">[ ]</a>
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
#{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)}
<span title="#{child.created_utc.to_s("%a %B %-d %T %Y UTC")}">#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span>
<a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a>
</p>
<div>
#{body_html}
#{replies_html}
</div>
</div>
</div>
END_HTML
html << <<-HTML
<p>
<a href="javascript:void(0)" data-onclick="toggle_parent">[ ]</a>
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
#{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)}
<span title="#{child.created_utc.to_s("%a %B %-d %T %Y UTC")}">#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span>
<a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a>
</p>
<div>
#{body_html}
#{replies_html}
</div>
</div>
</div>
HTML
end
end
end

View File

@ -12,17 +12,17 @@ module Invidious::Frontend::Comments
NumberFormatting::Separator
)
replies_html = <<-END_HTML
<div id="replies" class="pure-g">
<div class="pure-u-1-24"></div>
<div class="pure-u-23-24">
<p>
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
data-onclick="get_youtube_replies" data-load-replies>#{replies_count_text}</a>
</p>
replies_html = <<-HTML
<div id="replies" class="pure-g">
<div class="pure-u-1-24"></div>
<div class="pure-u-23-24">
<p>
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
data-onclick="get_youtube_replies" data-load-replies>#{replies_count_text}</a>
</p>
</div>
</div>
</div>
END_HTML
HTML
elsif comments["authorId"]? && !comments["singlePost"]?
# for posts we should display a link to the post
replies_count_text = translate_count(locale,
@ -31,16 +31,16 @@ module Invidious::Frontend::Comments
NumberFormatting::Separator
)
replies_html = <<-END_HTML
<div class="pure-g">
<div class="pure-u-1-24"></div>
<div class="pure-u-23-24">
<p>
<a href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
</p>
replies_html = <<-HTML
<div class="pure-g">
<div class="pure-u-1-24"></div>
<div class="pure-u-23-24">
<p>
<a href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
</p>
</div>
</div>
</div>
END_HTML
HTML
end
if !thin_mode
@ -65,19 +65,19 @@ module Invidious::Frontend::Comments
str << %(width="16" height="16" />)
end
end
html << <<-END_HTML
<div class="pure-g" style="width:100%">
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
<img loading="lazy" style="margin-right:1em;margin-top:1em;width:90%" src="#{author_thumbnail}" alt="" />
</div>
<div class="pure-u-20-24 pure-u-md-22-24">
<p>
<b>
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
</b>
#{sponsor_icon}
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
END_HTML
html << <<-HTML
<div class="pure-g" style="width:100%">
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
<img loading="lazy" style="margin-right:1em;margin-top:1em;width:90%" src="#{author_thumbnail}" alt="" />
</div>
<div class="pure-u-20-24 pure-u-md-22-24">
<p>
<b>
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
</b>
#{sponsor_icon}
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
HTML
if child["attachment"]?
attachment = child["attachment"]
@ -86,82 +86,81 @@ module Invidious::Frontend::Comments
when "image"
attachment = attachment["imageThumbnails"][1]
html << <<-END_HTML
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<img loading="lazy" style="width:100%" src="/ggpht#{URI.parse(attachment["url"].as_s).request_target}" alt="" />
html << <<-HTML
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<img loading="lazy" style="width:100%" src="/ggpht#{URI.parse(attachment["url"].as_s).request_target}" alt="" />
</div>
</div>
</div>
END_HTML
HTML
when "video"
if attachment["error"]?
html << <<-END_HTML
<div class="pure-g video-iframe-wrapper">
<p>#{attachment["error"]}</p>
</div>
END_HTML
html << <<-HTML
<div class="pure-g video-iframe-wrapper">
<p>#{attachment["error"]}</p>
</div>
HTML
else
html << <<-END_HTML
<div class="pure-g video-iframe-wrapper">
<iframe class="video-iframe" src='/embed/#{attachment["videoId"]?}?autoplay=0'></iframe>
</div>
END_HTML
html << <<-HTML
<div class="pure-g video-iframe-wrapper">
<iframe class="video-iframe" src='/embed/#{attachment["videoId"]?}?autoplay=0'></iframe>
</div>
HTML
end
when "multiImage"
html << <<-END_HTML
html << <<-HTML
<section class="carousel">
<a class="skip-link" href="#skip-#{child["commentId"]}">#{translate(locale, "carousel_skip")}</a>
<div class="slides">
END_HTML
HTML
image_array = attachment["images"].as_a
image_array.each_index do |i|
html << <<-END_HTML
html << <<-HTML
<div class="slides-item slide-#{i + 1}" id="#{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_slide", {"current" => (i + 1).to_s, "total" => image_array.size.to_s})}" tabindex="0">
<img loading="lazy" src="/ggpht#{URI.parse(image_array[i][1]["url"].as_s).request_target}" alt="" />
</div>
END_HTML
HTML
end
html << <<-END_HTML
html << <<-HTML
</div>
<div class="carousel__nav">
END_HTML
HTML
attachment["images"].as_a.each_index do |i|
html << <<-END_HTML
html << <<-HTML
<a class="slider-nav" href="##{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_go_to", (i + 1).to_s)}" tabindex="-1" aria-hidden="true">#{i + 1}</a>
END_HTML
HTML
end
html << <<-END_HTML
</div>
<div id="skip-#{child["commentId"]}"></div>
</section>
END_HTML
else nil # Ignore
html << <<-HTML
</div>
<div id="skip-#{child["commentId"]}"></div>
</section>
HTML
end
end
html << <<-END_HTML
<p>
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
END_HTML
html << <<-HTML
<p>
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
HTML
if comments["videoId"]?
html << <<-END_HTML
html << <<-HTML
<a rel="noreferrer noopener" href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
END_HTML
HTML
elsif comments["authorId"]?
html << <<-END_HTML
html << <<-HTML
<a rel="noreferrer noopener" href="https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
END_HTML
HTML
end
html << <<-END_HTML
html << <<-HTML
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
END_HTML
HTML
if child["creatorHeart"]?
if !thin_mode
@ -170,7 +169,7 @@ module Invidious::Frontend::Comments
creator_thumbnail = ""
end
html << <<-END_HTML
html << <<-HTML
&nbsp;
<span class="creator-heart-container" title="#{translate(locale, "`x` marked it with a ❤", child["creatorHeart"]["creatorName"].as_s)}">
<span class="creator-heart">
@ -180,28 +179,28 @@ module Invidious::Frontend::Comments
</span>
</span>
</span>
END_HTML
HTML
end
html << <<-END_HTML
</p>
#{replies_html}
html << <<-HTML
</p>
#{replies_html}
</div>
</div>
</div>
END_HTML
HTML
end
if comments["continuation"]?
html << <<-END_HTML
<div class="pure-g">
<div class="pure-u-1">
<p>
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
data-onclick="get_youtube_replies" data-load-more #{"data-load-replies" if is_replies}>#{translate(locale, "Load more")}</a>
</p>
html << <<-HTML
<div class="pure-g">
<div class="pure-u-1">
<p>
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
data-onclick="get_youtube_replies" data-load-more #{"data-load-replies" if is_replies}>#{translate(locale, "Load more")}</a>
</p>
</div>
</div>
</div>
END_HTML
HTML
end
end
end

View File

@ -6,9 +6,9 @@ module Invidious::Frontend::Misc
if preferences.automatic_instance_redirect
current_page = env.get?("current_page").as(String)
return "/redirect?referer=#{current_page}"
"/redirect?referer=#{current_page}"
else
return "https://redirect.invidious.io#{env.request.resource}"
"https://redirect.invidious.io#{env.request.resource}"
end
end
end

View File

@ -60,7 +60,7 @@ module Invidious::Frontend::Pagination
end
def nav_numeric(locale : String?, *, base_url : String | URI, current_page : Int, show_next : Bool = true)
return String.build do |str|
String.build do |str|
str << %(<div class="h-box">\n)
str << %(<div class="page-nav-container flexible">\n)
@ -70,7 +70,7 @@ module Invidious::Frontend::Pagination
params_prev = URI::Params{"page" => (current_page - 1).to_s}
url_prev = HttpServer::Utils.add_params_to_url(base_url, params_prev)
self.previous_page(str, locale, url_prev.to_s)
previous_page(str, locale, url_prev.to_s)
end
str << %(</div>\n)
@ -80,7 +80,7 @@ module Invidious::Frontend::Pagination
params_next = URI::Params{"page" => (current_page + 1).to_s}
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
self.next_page(str, locale, url_next.to_s)
next_page(str, locale, url_next.to_s)
end
str << %(</div>\n)
@ -91,7 +91,7 @@ module Invidious::Frontend::Pagination
end
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?, first_page : Bool, params : URI::Params)
return String.build do |str|
String.build do |str|
str << %(<div class="h-box">\n)
str << %(<div class="page-nav-container flexible">\n)
@ -109,7 +109,7 @@ module Invidious::Frontend::Pagination
params["continuation"] = ctoken
url_next = HttpServer::Utils.add_params_to_url(base_url, params)
self.next_page(str, locale, url_next.to_s)
next_page(str, locale, url_next.to_s)
end
str << %(</div>\n)

View File

@ -3,7 +3,7 @@ module Invidious::Frontend::SearchFilters
# Generate the search filters collapsable widget.
def generate(filters : Search::Filters, query : String, page : Int, locale : String) : String
return String.build(8000) do |str|
String.build(8000) do |str|
str << "<div id='filters'>\n"
str << "\t<details id='filters-collapse'>"
str << "\t\t<summary>" << translate(locale, "search_filters_title") << "</summary>\n"
@ -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 << translate(locale, "search_filters_{{name}}_label")
str << 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 << translate(locale, "search_filters_date_option_{{date}}")
str << "<label for='filter-date-{{ date }}'>"
str << 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 << translate(locale, "search_filters_type_option_{{type}}")
str << "<label for='filter-type-{{ type }}'>"
str << 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 << translate(locale, "search_filters_duration_option_{{duration}}")
str << "<label for='filter-duration-{{ duration }}'>"
str << 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 << translate(locale, "search_filters_features_option_{{feature}}")
str << "<label for='filter-feature-{{ feature }}'>"
str << 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 << translate(locale, "search_filters_sort_option_{{sort}}")
str << "<label for='filter-sort-{{ sort }}'>"
str << translate(locale, "search_filters_sort_option_{{ sort }}")
str << "</label></div>\n"
{% end %}
end

View File

@ -28,12 +28,12 @@ module Invidious::Frontend::WatchPage
end
url = "/download"
if (CONFIG.invidious_companion.present?)
if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample
url = "#{invidious_companion.public_url}/download?check=#{invidious_companion_encrypt(video.id)}"
end
return String.build(4000) do |str|
String.build(4000) do |str|
str << "<form"
str << " class=\"pure-form pure-form-stacked\""
str << " action='#{url}'"

View File

@ -9,7 +9,7 @@ module Invidious::Hashtag
response = YoutubeAPI.browse(continuation: ctoken, client_config: client_config)
items, _ = extract_items(response)
return items
items
end
def generate_continuation(hashtag : String, cursor : Int)
@ -37,6 +37,6 @@ module Invidious::Hashtag
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
return continuation
continuation
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)
@ -15,19 +15,19 @@ def github_details(summary : String, content : String)
details += %(\n```)
details += %(\n</p>)
details += %(\n</details>)
return HTML.escape(details)
HTML.escape(details)
end
def get_issue_template(env : HTTP::Server::Context, exception : Exception) : Tuple(String, String)
issue_title = "#{exception.message} (#{exception.class})"
issue_template = <<-TEXT
Title: `#{HTML.escape(issue_title)}`
Date: `#{Time::Format::ISO_8601_DATE_TIME.format(Time.utc)}`
Route: `#{HTML.escape(env.request.resource)}`
Version: `#{SOFTWARE["version"]} @ #{SOFTWARE["branch"]}`
Title: `#{HTML.escape(issue_title)}`
Date: `#{Time::Format::ISO_8601_DATE_TIME.format(Time.utc)}`
Route: `#{HTML.escape(env.request.resource)}`
Version: `#{SOFTWARE["version"]} @ #{SOFTWARE["branch"]}`
TEXT
TEXT
issue_template += github_details("Backtrace", exception.inspect_with_backtrace)
@ -61,7 +61,8 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
url_new_issue += "?labels=bug&template=bug_report.md&title="
url_new_issue += URI.encode_www_form("[Bug] " + issue_title)
error_message = <<-END_HTML
# ameba:disable Lint/UselessAssign
error_message = <<-HTML
<div class="error_message">
<h2>#{translate(locale, "crash_page_you_found_a_bug")}</h2>
<br/><br/>
@ -80,13 +81,14 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
<!-- TODO: Add a "copy to clipboard" button -->
<pre class="error-issue-template">#{issue_template}</pre>
</div>
END_HTML
HTML
# Don't show the usual "next steps" widget. The same options are
# proposed above the error message, just worded differently.
# ameba:disable Lint/UselessAssign
next_steps = ""
return templated "error"
templated "error"
end
def error_template_helper(env : HTTP::Server::Context, status_code : Int32, message : String)
@ -95,10 +97,12 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, mess
locale = env.get("preferences").as(Preferences).locale
# ameba:disable Lint/UselessAssign
error_message = translate(locale, message)
# ameba:disable Lint/UselessAssign
next_steps = error_redirect_helper(env)
return templated "error"
templated "error"
end
# -------------------
@ -106,7 +110,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)
@ -117,14 +121,14 @@ def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, exceptio
env.response.content_type = "application/atom+xml"
env.response.status_code = status_code
return "<error>#{exception.inspect_with_backtrace}</error>"
"<error>#{exception.inspect_with_backtrace}</error>"
end
def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, message : String)
env.response.content_type = "application/atom+xml"
env.response.status_code = status_code
return "<error>#{message}</error>"
"<error>#{message}</error>"
end
# -------------------
@ -132,14 +136,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)
@ -154,14 +158,14 @@ def error_json_helper(
error_message = error_message.merge(additional_fields)
end
return error_message.to_json
error_message.to_json
end
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
@ -172,7 +176,7 @@ def error_json_helper(
error_message = error_message.merge(additional_fields)
end
return error_message.to_json
error_message.to_json
end
# -------------------
@ -191,7 +195,7 @@ def error_redirect_helper(env : HTTP::Server::Context)
go_to_youtube = translate(locale, "next_steps_error_message_go_to_youtube")
switch_instance = translate(locale, "Switch Invidious Instance")
return <<-END_HTML
<<-HTML
<p style="margin-bottom: 4px;">#{next_steps_text}</p>
<ul>
<li>
@ -204,8 +208,8 @@ def error_redirect_helper(env : HTTP::Server::Context)
<a rel="noreferrer noopener" href="https://youtube.com#{env.request.resource}">#{go_to_youtube}</a>
</li>
</ul>
END_HTML
HTML
else
return ""
""
end
end

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"

View File

@ -32,7 +32,7 @@ def html_to_content(description_html : String)
description = XML.parse_html(description).content.strip("\n ")
end
return description
description
end
def cache_annotation(id, annotations)
@ -165,7 +165,7 @@ def create_notification_stream(env, topics, connection_channel)
end
def extract_initial_data(body) : Hash(String, JSON::Any)
return JSON.parse(body.match(/(window\["ytInitialData"\]|var\s*ytInitialData)\s*=\s*(?<info>{.*?});<\/script>/mx).try &.["info"] || "{}").as_h
JSON.parse(body.match(/(window\["ytInitialData"\]|var\s*ytInitialData)\s*=\s*(?<info>{.*?});<\/script>/mx).try &.["info"] || "{}").as_h
end
def proxy_file(response, env)
@ -196,5 +196,5 @@ def get_playback_statistic
Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"] = tracker
end
return tracker.as(Hash(String, Int64 | Float64))
tracker.as(Hash(String, Int64 | Float64))
end

View File

@ -91,10 +91,10 @@ def load_all_locales
locales[name] = JSON.parse(File.read("locales/#{name}.json")).as_h
end
return locales
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)
@ -141,7 +141,7 @@ def translate(locale : String?, key : String, text : String | Hash(String, Strin
end
end
return translation
translation
end
def translate_count(locale : String, key : String, count : Int, format = NumberFormatting::None) : String
@ -177,15 +177,15 @@ def translate_count(locale : String, key : String, count : Int, format = NumberF
else count_txt = count.to_s
end
return translation.gsub("{{count}}", count_txt)
translation.gsub("{{count}}", count_txt)
end
def translate_bool(locale : String?, translation : Bool)
case translation
when true
return translate(locale, "Yes")
translate(locale, "Yes")
when false
return translate(locale, "No")
translate(locale, "No")
end
end
@ -195,5 +195,5 @@ def locale_is_rtl?(locale : String?)
# Arabic, Persian, Hebrew
# See https://en.wikipedia.org/wiki/Right-to-left_script#List_of_RTL_scripts
return {"ar", "fa", "he"}.includes? locale
{"ar", "fa", "he"}.includes? locale
end

View File

@ -151,17 +151,17 @@ module I18next::Plurals
@version = version.to_u8
end
self.init_rules
init_rules
end
def init_rules
# Look into sets
PLURAL_SETS.each do |form, langs|
langs.each { |lang| self.forms[lang] = form }
langs.each { |lang| forms[lang] = form }
end
# Add plurals from the "singles" set
self.forms.merge!(PLURAL_SINGLES)
forms.merge!(PLURAL_SINGLES)
end
def get_plural_form(locale : String) : PluralForms
@ -170,12 +170,12 @@ module I18next::Plurals
locale = locale.split('-')[0]
end
return self.forms[locale] if self.forms[locale]?
return forms[locale] if forms[locale]?
# If nothing was found, then use the most common form, i.e
# one singular and one plural, as in english. Not perfect,
# but better than yielding an exception at the user.
return PluralForms::Single_not_one
PluralForms::Single_not_one
end
def get_suffix(locale : String, count : Int) : String
@ -183,19 +183,19 @@ module I18next::Plurals
# determine if comparison should be done on a signed or unsigned integer,
# but this variable is never set, resulting in the comparison always
# being done on absolute numbers.
return get_suffix_retrocompat(locale, count.abs)
get_suffix_retrocompat(locale, count.abs)
end
# Emulate the `rule.numbers.size == 2 && rule.numbers[0] == 1` check
# from original i18next code
private def simple_plural?(form : PluralForms) : Bool
case form
when .single_gt_one? then return true
when .single_not_one? then return true
when .special_icelandic? then return true
when .special_macedonian? then return true
when .single_gt_one? then true
when .single_not_one? then true
when .special_icelandic? then true
when .special_macedonian? then true
else
return false
false
end
end
@ -226,7 +226,7 @@ module I18next::Plurals
# when 2
# return "_#{suffix}"
# else # v3
return "_#{idx}"
"_#{idx}"
# end
end
end
@ -238,35 +238,35 @@ module I18next::Plurals
module SuffixIndex
def self.get_index(plural_form : PluralForms, count : Int) : UInt8
case plural_form
when .single_gt_one? then return (count > 1) ? 1_u8 : 0_u8
when .single_not_one? then return (count != 1) ? 1_u8 : 0_u8
when .none? then return 0_u8
when .dual_slavic? then return dual_slavic(count)
when .special_arabic? then return special_arabic(count)
when .special_czech_slovak? then return special_czech_slovak(count)
when .special_polish_kashubian? then return special_polish_kashubian(count)
when .special_welsh? then return special_welsh(count)
when .special_irish? then return special_irish(count)
when .special_scottish_gaelic? then return special_scottish_gaelic(count)
when .special_icelandic? then return special_icelandic(count)
when .special_javanese? then return special_javanese(count)
when .special_cornish? then return special_cornish(count)
when .special_lithuanian? then return special_lithuanian(count)
when .special_latvian? then return special_latvian(count)
when .special_macedonian? then return special_macedonian(count)
when .special_mandinka? then return special_mandinka(count)
when .special_maltese? then return special_maltese(count)
when .special_romanian? then return special_romanian(count)
when .special_slovenian? then return special_slovenian(count)
when .special_hebrew? then return special_hebrew(count)
when .special_odia? then return special_odia(count)
when .single_gt_one? then (count > 1) ? 1_u8 : 0_u8
when .single_not_one? then (count != 1) ? 1_u8 : 0_u8
when .none? then 0_u8
when .dual_slavic? then dual_slavic(count)
when .special_arabic? then special_arabic(count)
when .special_czech_slovak? then special_czech_slovak(count)
when .special_polish_kashubian? then special_polish_kashubian(count)
when .special_welsh? then special_welsh(count)
when .special_irish? then special_irish(count)
when .special_scottish_gaelic? then special_scottish_gaelic(count)
when .special_icelandic? then special_icelandic(count)
when .special_javanese? then special_javanese(count)
when .special_cornish? then special_cornish(count)
when .special_lithuanian? then special_lithuanian(count)
when .special_latvian? then special_latvian(count)
when .special_macedonian? then special_macedonian(count)
when .special_mandinka? then special_mandinka(count)
when .special_maltese? then special_maltese(count)
when .special_romanian? then special_romanian(count)
when .special_slovenian? then special_slovenian(count)
when .special_hebrew? then special_hebrew(count)
when .special_odia? then special_odia(count)
# Mixed v3/v4 forms
when .special_spanish_italian? then return special_cldr_spanish_italian(count)
when .special_french_portuguese? then return special_cldr_french_portuguese(count)
when .special_hungarian_serbian? then return special_cldr_hungarian_serbian(count)
when .special_spanish_italian? then special_cldr_spanish_italian(count)
when .special_french_portuguese? then special_cldr_french_portuguese(count)
when .special_hungarian_serbian? then special_cldr_hungarian_serbian(count)
else
# default, if nothing matched above
return 0_u8
0_u8
end
end
@ -280,11 +280,11 @@ module I18next::Plurals
n_mod_100 = count % 100
if n_mod_10 == 1 && n_mod_100 != 11
return 0_u8
0_u8
elsif n_mod_10 >= 2 && n_mod_10 <= 4 && (n_mod_100 < 10 || n_mod_100 >= 20)
return 1_u8
1_u8
else
return 2_u8
2_u8
end
end
@ -294,13 +294,13 @@ module I18next::Plurals
# Rule: (n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5)
#
def self.special_arabic(count : Int) : UInt8
return count.to_u8 if (count == 0 || count == 1 || count == 2)
return count.to_u8 if count == 0 || count == 1 || count == 2
n_mod_100 = count % 100
return 3_u8 if (n_mod_100 >= 3 && n_mod_100 <= 10)
return 4_u8 if (n_mod_100 >= 11)
return 5_u8
return 3_u8 if n_mod_100 >= 3 && n_mod_100 <= 10
return 4_u8 if n_mod_100 >= 11
5_u8
end
# Plural form for Czech and Slovak languages
@ -309,9 +309,9 @@ module I18next::Plurals
# Rule: ((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)
#
def self.special_czech_slovak(count : Int) : UInt8
return 0_u8 if (count == 1)
return 1_u8 if (count >= 2 && count <= 4)
return 2_u8
return 0_u8 if count == 1
return 1_u8 if count >= 2 && count <= 4
2_u8
end
# Plural form for Polish and Kashubian languages
@ -320,15 +320,15 @@ module I18next::Plurals
# Rule: (n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)
#
def self.special_polish_kashubian(count : Int) : UInt8
return 0_u8 if (count == 1)
return 0_u8 if count == 1
n_mod_10 = count % 10
n_mod_100 = count % 100
if n_mod_10 >= 2 && n_mod_10 <= 4 && (n_mod_100 < 10 || n_mod_100 >= 20)
return 1_u8
1_u8
else
return 2_u8
2_u8
end
end
@ -338,10 +338,10 @@ module I18next::Plurals
# Rule: ((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3)
#
def self.special_welsh(count : Int) : UInt8
return 0_u8 if (count == 1)
return 1_u8 if (count == 2)
return 2_u8 if (count != 8 && count != 11)
return 3_u8
return 0_u8 if count == 1
return 1_u8 if count == 2
return 2_u8 if count != 8 && count != 11
3_u8
end
# Plural form for Irish language
@ -350,11 +350,11 @@ module I18next::Plurals
# Rule: (n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4)
#
def self.special_irish(count : Int) : UInt8
return 0_u8 if (count == 1)
return 1_u8 if (count == 2)
return 2_u8 if (count < 7)
return 3_u8 if (count < 11)
return 4_u8
return 0_u8 if count == 1
return 1_u8 if count == 2
return 2_u8 if count < 7
return 3_u8 if count < 11
4_u8
end
# Plural form for Gaelic language
@ -363,10 +363,10 @@ module I18next::Plurals
# Rule: ((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3)
#
def self.special_scottish_gaelic(count : Int) : UInt8
return 0_u8 if (count == 1 || count == 11)
return 1_u8 if (count == 2 || count == 12)
return 2_u8 if (count > 2 && count < 20)
return 3_u8
return 0_u8 if count == 1 || count == 11
return 1_u8 if count == 2 || count == 12
return 2_u8 if count > 2 && count < 20
3_u8
end
# Plural form for Icelandic language
@ -376,9 +376,9 @@ module I18next::Plurals
#
def self.special_icelandic(count : Int) : UInt8
if (count % 10) != 1 || (count % 100) == 11
return 1_u8
1_u8
else
return 0_u8
0_u8
end
end
@ -388,7 +388,7 @@ module I18next::Plurals
# Rule: (n !== 0)
#
def self.special_javanese(count : Int) : UInt8
return (count != 0) ? 1_u8 : 0_u8
(count != 0) ? 1_u8 : 0_u8
end
# Plural form for Cornish language
@ -400,7 +400,7 @@ module I18next::Plurals
return 0_u8 if count == 1
return 1_u8 if count == 2
return 2_u8 if count == 3
return 3_u8
3_u8
end
# Plural form for Lithuanian language
@ -413,11 +413,11 @@ module I18next::Plurals
n_mod_100 = count % 100
if n_mod_10 == 1 && n_mod_100 != 11
return 0_u8
0_u8
elsif n_mod_10 >= 2 && (n_mod_100 < 10 || n_mod_100 >= 20)
return 1_u8
1_u8
else
return 2_u8
2_u8
end
end
@ -428,11 +428,11 @@ module I18next::Plurals
#
def self.special_latvian(count : Int) : UInt8
if (count % 10) == 1 && (count % 100) != 11
return 0_u8
0_u8
elsif count != 0
return 1_u8
1_u8
else
return 2_u8
2_u8
end
end
@ -443,9 +443,9 @@ module I18next::Plurals
#
def self.special_macedonian(count : Int) : UInt8
if count == 1 || ((count % 10) == 1 && (count % 100) != 11)
return 0_u8
0_u8
else
return 1_u8
1_u8
end
end
@ -455,7 +455,7 @@ module I18next::Plurals
# Rule: (n==0 ? 0 : n==1 ? 1 : 2)
#
def self.special_mandinka(count : Int) : UInt8
return (count == 0 || count == 1) ? count.to_u8 : 2_u8
(count == 0 || count == 1) ? count.to_u8 : 2_u8
end
# Plural form for Maltese language
@ -468,9 +468,9 @@ module I18next::Plurals
return 1_u8 if count == 0
n_mod_100 = count % 100
return 1_u8 if (n_mod_100 > 1 && n_mod_100 < 11)
return 2_u8 if (n_mod_100 > 10 && n_mod_100 < 20)
return 3_u8
return 1_u8 if n_mod_100 > 1 && n_mod_100 < 11
return 2_u8 if n_mod_100 > 10 && n_mod_100 < 20
3_u8
end
# Plural form for Romanian language
@ -483,8 +483,8 @@ module I18next::Plurals
return 1_u8 if count == 0
n_mod_100 = count % 100
return 1_u8 if (n_mod_100 > 0 && n_mod_100 < 20)
return 2_u8
return 1_u8 if n_mod_100 > 0 && n_mod_100 < 20
2_u8
end
# Plural form for Slovenian language
@ -494,10 +494,10 @@ module I18next::Plurals
#
def self.special_slovenian(count : Int) : UInt8
n_mod_100 = count % 100
return 1_u8 if (n_mod_100 == 1)
return 2_u8 if (n_mod_100 == 2)
return 3_u8 if (n_mod_100 == 3 || n_mod_100 == 4)
return 0_u8
return 1_u8 if n_mod_100 == 1
return 2_u8 if n_mod_100 == 2
return 3_u8 if n_mod_100 == 3 || n_mod_100 == 4
0_u8
end
# Plural form for Hebrew language
@ -506,13 +506,13 @@ module I18next::Plurals
# Rule: (n==1 ? 0 : n==2 ? 1 : (n<0 || n>10) && n%10==0 ? 2 : 3)
#
def self.special_hebrew(count : Int) : UInt8
return 0_u8 if (count == 1)
return 1_u8 if (count == 2)
return 0_u8 if count == 1
return 1_u8 if count == 2
if (count < 0 || count > 10) && (count % 10) == 0
return 2_u8
2_u8
else
return 3_u8
3_u8
end
end
@ -523,7 +523,7 @@ module I18next::Plurals
# special rule for it.
#
def self.special_odia(count : Int) : UInt8
return (count == 1) ? 0_u8 : 1_u8
(count == 1) ? 0_u8 : 1_u8
end
# -------------------
@ -535,9 +535,9 @@ module I18next::Plurals
# This rule is mostly compliant to CLDR v42
#
def self.special_cldr_spanish_italian(count : Int) : UInt8
return 0_u8 if (count == 1) # one
return 1_u8 if (count != 0 && count % 1_000_000 == 0) # many
return 2_u8 # other
return 0_u8 if count == 1 # one
return 1_u8 if count != 0 && count % 1_000_000 == 0 # many
2_u8 # other
end
# Plural form for French and Portuguese
@ -545,9 +545,9 @@ module I18next::Plurals
# This rule is mostly compliant to CLDR v42
#
def self.special_cldr_french_portuguese(count : Int) : UInt8
return 0_u8 if (count == 0 || count == 1) # one
return 1_u8 if (count % 1_000_000 == 0) # many
return 2_u8 # other
return 0_u8 if count == 0 || count == 1 # one
return 1_u8 if count % 1_000_000 == 0 # many
2_u8 # other
end
# Plural form for Hungarian and Serbian
@ -558,9 +558,9 @@ module I18next::Plurals
n_mod_10 = count % 10
n_mod_100 = count % 100
return 0_u8 if (n_mod_10 == 1 && n_mod_100 != 11) # one
return 1_u8 if (2 <= n_mod_10 <= 4 && (n_mod_100 < 12 || 14 < n_mod_100)) # few
return 2_u8 # other
return 0_u8 if n_mod_10 == 1 && n_mod_100 != 11 # one
return 1_u8 if 2 <= n_mod_10 <= 4 && (n_mod_100 < 12 || 14 < n_mod_100) # few
2_u8 # other
end
end
end

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

@ -28,19 +28,19 @@ struct SearchVideo
property badges : VideoBadges
def to_xml(auto_generated, query_params, xml : XML::Builder)
query_params["v"] = self.id
query_params["v"] = id
xml.element("entry") do
xml.element("id") { xml.text "yt:video:#{self.id}" }
xml.element("yt:videoId") { xml.text self.id }
xml.element("yt:channelId") { xml.text self.ucid }
xml.element("title") { xml.text self.title }
xml.element("id") { xml.text "yt:video:#{id}" }
xml.element("yt:videoId") { xml.text id }
xml.element("yt:channelId") { xml.text ucid }
xml.element("title") { xml.text title }
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}")
xml.element("author") do
if auto_generated
xml.element("name") { xml.text self.author }
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" }
xml.element("name") { xml.text author }
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
else
xml.element("name") { xml.text author }
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
@ -50,24 +50,24 @@ struct SearchVideo
xml.element("content", type: "xhtml") do
xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do
xml.element("a", href: "#{HOST_URL}/watch?#{query_params}") do
xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg")
xml.element("img", src: "#{HOST_URL}/vi/#{id}/mqdefault.jpg")
end
xml.element("p", style: "word-break:break-word;white-space:pre-wrap") { xml.text html_to_content(self.description_html) }
xml.element("p", style: "word-break:break-word;white-space:pre-wrap") { xml.text html_to_content(description_html) }
end
end
xml.element("published") { xml.text self.published.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("published") { xml.text published.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("media:group") do
xml.element("media:title") { xml.text self.title }
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg",
xml.element("media:title") { xml.text title }
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{id}/mqdefault.jpg",
width: "320", height: "180")
xml.element("media:description") { xml.text html_to_content(self.description_html) }
xml.element("media:description") { xml.text html_to_content(description_html) }
end
xml.element("media:community") do
xml.element("media:statistics", views: self.views)
xml.element("media:statistics", views: views)
end
end
end
@ -81,13 +81,13 @@ struct SearchVideo
def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "video"
json.field "title", self.title
json.field "videoId", self.id
json.field "title", title
json.field "videoId", id
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "authorVerified", self.author_verified
json.field "author", author
json.field "authorId", ucid
json.field "authorUrl", "/channel/#{ucid}"
json.field "authorVerified", author_verified
author_thumbnail = self.author_thumbnail
@ -108,31 +108,31 @@ struct SearchVideo
end
json.field "videoThumbnails" do
Invidious::JSONify::APIv1.thumbnails(json, self.id)
Invidious::JSONify::APIv1.thumbnails(json, id)
end
json.field "description", html_to_content(self.description_html)
json.field "descriptionHtml", self.description_html
json.field "description", html_to_content(description_html)
json.field "descriptionHtml", description_html
json.field "viewCount", self.views
json.field "viewCountText", translate_count(locale, "generic_views_count", self.views, NumberFormatting::Short)
json.field "published", self.published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
json.field "lengthSeconds", self.length_seconds
json.field "liveNow", self.badges.live_now?
json.field "premium", self.badges.premium?
json.field "isUpcoming", self.upcoming?
json.field "viewCount", views
json.field "viewCountText", translate_count(locale, "generic_views_count", views, NumberFormatting::Short)
json.field "published", published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
json.field "lengthSeconds", length_seconds
json.field "liveNow", badges.live_now?
json.field "premium", badges.premium?
json.field "isUpcoming", upcoming?
if self.premiere_timestamp
json.field "premiereTimestamp", self.premiere_timestamp.try &.to_unix
if premiere_timestamp
json.field "premiereTimestamp", premiere_timestamp.try &.to_unix
end
json.field "isNew", self.badges.new?
json.field "is4k", self.badges.four_k?
json.field "is8k", self.badges.eight_k?
json.field "isVr180", self.badges.vr180?
json.field "isVr360", self.badges.vr360?
json.field "is3d", self.badges.three_d?
json.field "hasCaptions", self.badges.closed_captions?
json.field "isNew", badges.new?
json.field "is4k", badges.four_k?
json.field "is8k", badges.eight_k?
json.field "isVr180", badges.vr180?
json.field "isVr360", badges.vr360?
json.field "is3d", badges.three_d?
json.field "hasCaptions", badges.closed_captions?
end
end
@ -175,20 +175,20 @@ struct SearchPlaylist
def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "playlist"
json.field "title", self.title
json.field "playlistId", self.id
json.field "playlistThumbnail", self.thumbnail
json.field "title", title
json.field "playlistId", id
json.field "playlistThumbnail", thumbnail
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "author", author
json.field "authorId", ucid
json.field "authorUrl", "/channel/#{ucid}"
json.field "authorVerified", self.author_verified
json.field "authorVerified", author_verified
json.field "videoCount", self.video_count
json.field "videoCount", video_count
json.field "videos" do
json.array do
self.videos.each do |video|
videos.each do |video|
json.object do
json.field "title", video.title
json.field "videoId", video.id
@ -232,17 +232,17 @@ struct SearchChannel
def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "channel"
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "authorVerified", self.author_verified
json.field "author", author
json.field "authorId", ucid
json.field "authorUrl", "/channel/#{ucid}"
json.field "authorVerified", author_verified
json.field "authorThumbnails" do
json.array do
qualities = {32, 48, 76, 100, 176, 512}
qualities.each do |quality|
json.object do
json.field "url", self.author_thumbnail.gsub(/=s\d+/, "=s#{quality}")
json.field "url", author_thumbnail.gsub(/=s\d+/, "=s#{quality}")
json.field "width", quality
json.field "height", quality
end
@ -250,13 +250,13 @@ struct SearchChannel
end
end
json.field "autoGenerated", self.auto_generated
json.field "subCount", self.subscriber_count
json.field "videoCount", self.video_count
json.field "channelHandle", self.channel_handle
json.field "autoGenerated", auto_generated
json.field "subCount", subscriber_count
json.field "videoCount", video_count
json.field "channelHandle", channel_handle
json.field "description", html_to_content(self.description_html)
json.field "descriptionHtml", self.description_html
json.field "description", html_to_content(description_html)
json.field "descriptionHtml", description_html
end
end
@ -283,10 +283,10 @@ struct SearchHashtag
def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "hashtag"
json.field "title", self.title
json.field "url", self.url
json.field "videoCount", self.video_count
json.field "channelCount", self.channel_count
json.field "title", title
json.field "url", url
json.field "videoCount", video_count
json.field "channelCount", channel_count
end
end
end
@ -315,7 +315,7 @@ struct ProblematicTimelineItem
# Provides compatibility with PlaylistVideo
def to_json(json : JSON::Builder, *args, **kwargs)
return to_json("", json)
to_json("", json)
end
def to_xml(env, locale, xml : XML::Builder)
@ -352,10 +352,10 @@ class Category
def to_json(locale : String?, json : JSON::Builder)
json.object do
json.field "type", "category"
json.field "title", self.title
json.field "title", title
json.field "contents" do
json.array do
self.contents.each do |item|
contents.each do |item|
item.to_json(locale, json)
end
end

View File

@ -16,7 +16,7 @@ def generate_token(email, scopes, expire, key)
token["signature"] = sign_token(key, token)
return token.to_json
token.to_json
end
def generate_response(session, scopes, key, expire = 6.hours, use_nonce = false)
@ -36,7 +36,7 @@ def generate_response(session, scopes, key, expire = 6.hours, use_nonce = false)
token["signature"] = sign_token(key, token)
return token.to_json
token.to_json
end
def sign_token(key, hash)
@ -45,6 +45,7 @@ def sign_token(key, hash)
# TODO: figure out which "key" variable is used
# Ameba reports a warning for "Lint/ShadowingOuterLocalVar" on this
# variable, but it's preferable to not touch that (works fine atm).
# ameba:disable Lint/ShadowingOuterLocalVar
hash.each do |key, value|
next if key == "signature"
@ -63,7 +64,7 @@ def sign_token(key, hash)
end
string_to_sign = string_to_sign.sort.join("\n")
return Base64.urlsafe_encode(OpenSSL::HMAC.digest(:sha256, key, string_to_sign)).strip
Base64.urlsafe_encode(OpenSSL::HMAC.digest(:sha256, key, string_to_sign)).strip
end
def validate_request(token, session, request, key, locale = nil)
@ -116,7 +117,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
@ -131,7 +132,7 @@ def scope_includes_scope(scope, subset)
return false
end
return true
true
end
def scopes_include_scope(scopes, subset)
@ -141,5 +142,5 @@ def scopes_include_scope(scopes, subset)
end
end
return false
false
end

View File

@ -8,7 +8,7 @@ def ci_lower_bound(pos, n)
z = 1.96
phat = 1.0*pos/n
return (phat + z*z/(2*n) - z * Math.sqrt((phat*(1 - phat) + z*z/(4*n))/n))/(1 + z*z/n)
(phat + z*z/(2*n) - z * Math.sqrt((phat*(1 - phat) + z*z/(4*n))/n))/(1 + z*z/n)
end
def elapsed_text(elapsed)
@ -31,12 +31,12 @@ def decode_length_seconds(string)
seconds: length_seconds[2]
).total_seconds.to_i32
return length_seconds
length_seconds
end
def recode_length_seconds(time)
if time <= 0
return ""
""
else
time = time.seconds
text = "#{time.minutes.to_s.rjust(2, '0')}:#{time.seconds.to_s.rjust(2, '0')}"
@ -47,7 +47,7 @@ def recode_length_seconds(time)
text = text.lchop('0')
return text
text
end
end
@ -66,7 +66,7 @@ def decode_interval(string : String) : Time::Span
time = Time::Span.new(minutes: raw_minutes)
end
return time
time
end
def decode_time(string)
@ -88,7 +88,7 @@ def decode_time(string)
time = hours * 3600 + minutes * 60 + seconds + millis // 1000
end
return time
time
end
def decode_date(string : String)
@ -108,7 +108,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"...
@ -137,26 +136,26 @@ def decode_date(string : String)
raise "Could not parse #{string}"
end
return Time.utc - delta
Time.utc - delta
end
def recode_date(time : Time, locale)
span = Time.utc - time
if span.total_days > 365.0
return translate_count(locale, "generic_count_years", span.total_days.to_i // 365)
translate_count(locale, "generic_count_years", span.total_days.to_i // 365)
elsif span.total_days > 30.0
return translate_count(locale, "generic_count_months", span.total_days.to_i // 30)
translate_count(locale, "generic_count_months", span.total_days.to_i // 30)
elsif span.total_days > 7.0
return translate_count(locale, "generic_count_weeks", span.total_days.to_i // 7)
translate_count(locale, "generic_count_weeks", span.total_days.to_i // 7)
elsif span.total_hours > 24.0
return translate_count(locale, "generic_count_days", span.total_days.to_i)
translate_count(locale, "generic_count_days", span.total_days.to_i)
elsif span.total_minutes > 60.0
return translate_count(locale, "generic_count_hours", span.total_hours.to_i)
translate_count(locale, "generic_count_hours", span.total_hours.to_i)
elsif span.total_seconds > 60.0
return translate_count(locale, "generic_count_minutes", span.total_minutes.to_i)
translate_count(locale, "generic_count_minutes", span.total_minutes.to_i)
else
return translate_count(locale, "generic_count_seconds", span.total_seconds.to_i)
translate_count(locale, "generic_count_seconds", span.total_seconds.to_i)
end
end
@ -174,9 +173,9 @@ def short_text_to_number(short_text : String) : Int64
when "b" then number *= 1_000_000_000
end
return number.to_i64
number.to_i64
rescue ex
return 0_i64
0_i64
end
def number_to_short_text(number)
@ -209,7 +208,7 @@ def arg_array(array, start = 1)
args = args.join(",")
end
return args
args
end
def make_host_url(kemal_config)
@ -235,7 +234,7 @@ def make_host_url(kemal_config)
host = CONFIG.domain.not_nil!.lchop(".")
return "#{scheme}#{host}#{port}"
"#{scheme}#{host}#{port}"
end
def get_referer(env, fallback = "/", unroll = true)
@ -268,13 +267,13 @@ def get_referer(env, fallback = "/", unroll = true)
referer = fallback
end
return referer
referer
end
def sha256(text)
digest = OpenSSL::Digest.new("SHA256")
digest << text
return digest.final.hexstring
digest.final.hexstring
end
def subscribe_pubsub(topic, key)
@ -302,7 +301,7 @@ def subscribe_pubsub(topic, key)
"hub.secret" => key.to_s,
}
return make_client(PUBSUB_URL, &.post("/subscribe", form: body))
make_client(PUBSUB_URL, &.post("/subscribe", form: body))
end
def parse_range(range)
@ -328,7 +327,7 @@ def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = "
if str.size > max_length
str = "#{str[0, max_length]}#{suffix}"
end
return str
str
end
# Get the html link from a NavigationEndpoint or an innertubeCommand
@ -381,7 +380,7 @@ def parse_link_endpoint(endpoint : JSON::Any, text : String, video_id : String)
text = %(<a href="#{url}">#{reduce_uri(text)}</a>)
end
end
return text
text
end
def encrypt_ecb_without_salt(data, key)
@ -394,11 +393,11 @@ def encrypt_ecb_without_salt(data, key)
io.write(cipher.final)
io.rewind
return io
io
end
def invidious_companion_encrypt(data)
timestamp = Time.utc.to_unix
encrypted_data = encrypt_ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key)
return Base64.urlsafe_encode(encrypted_data)
Base64.urlsafe_encode(encrypted_data)
end

View File

@ -20,7 +20,7 @@ module WebVTT
# Writes an vtt cue with the specified time stamp and contents
def cue(start_time : Time::Span, end_time : Time::Span, text : String)
timestamp(start_time, end_time)
@io << self.escape(text)
@io << escape(text)
@io << "\n\n"
end
@ -40,7 +40,7 @@ module WebVTT
end
private def escape(text : String) : String
return text.gsub(ESCAPE_SUBSTITUTIONS)
text.gsub(ESCAPE_SUBSTITUTIONS)
end
def document(setting_fields : Hash(String, String)? = nil, &)

View File

@ -68,7 +68,7 @@ module Invidious::HttpServer
end
end
return flush_io_to_cache(retrieve_bytes_from, file_path, file_info)
flush_io_to_cache(retrieve_bytes_from, file_path, file_info)
end
# Writes file data to the cache
@ -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.
@ -114,7 +114,7 @@ module Invidious::HttpServer
# This is only used in the specs to clear the cache before each handler test
def self.clear_cache
@@current_cache_size = 0
return @@cached_files.clear
@@cached_files.clear
end
end
end

View File

@ -14,9 +14,9 @@ module Invidious::HttpServer
url.query_params = params
if absolute
return "#{HOST_URL}#{url.request_target}"
"#{HOST_URL}#{url.request_target}"
else
return url.request_target
url.request_target
end
end
@ -35,7 +35,7 @@ module Invidious::HttpServer
str << params
end
return url
url
end
end
end

View File

@ -25,7 +25,7 @@ class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
# - Is it an instance with a good uptime?
# - Is it an updated instance?
private def refresh_instances
raw_instance_list = self.fetch_instances
raw_instance_list = fetch_instances
filtered_instance_list = [] of Tuple(String, String)
raw_instance_list.each do |instance_data|
@ -73,7 +73,7 @@ class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
raw_instance_list = [] of JSON::Any
end
return raw_instance_list
raw_instance_list
end
# Checks if the given target instance is outdated
@ -84,7 +84,7 @@ class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
remote_commit_date = Time.parse(remote_commit_date[0], "%Y.%m.%d", Time::Location::UTC)
local_commit_date = Time.parse(CURRENT_VERSION, "%Y.%m.%d", Time::Location::UTC)
return (remote_commit_date - local_commit_date).abs.days > 30
(remote_commit_date - local_commit_date).abs.days > 30
end
# Checks if the uptime of the target instance is greater than 90% over a 30 day period
@ -92,6 +92,6 @@ class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
return true if !target_instance_health_monitor["down"].as_bool == false
return true if target_instance_health_monitor["uptime"].as_f < 90
return false
false
end
end

View File

@ -13,10 +13,10 @@ module Invidious::JSONify::APIv1
json.field "error", video.info["reason"] if video.info["reason"]?
json.field "videoThumbnails" do
self.thumbnails(json, video.id)
thumbnails(json, video.id)
end
json.field "storyboards" do
self.storyboards(json, video.id, video.storyboards)
storyboards(json, video.id, video.storyboards)
end
json.field "description", video.description
@ -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
@ -199,7 +199,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
end
end
@ -241,7 +241,7 @@ module Invidious::JSONify::APIv1
json.field "videoId", rv["id"]
json.field "title", rv["title"]
json.field "videoThumbnails" do
self.thumbnails(json, rv["id"])
thumbnails(json, rv["id"])
end
json.field "author", rv["author"]

View File

@ -74,7 +74,7 @@ def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
videos.uniq!(&.id)
videos = videos.first(50)
return Mix.new({
Mix.new({
title: mix_title,
id: rdid,
videos: videos,
@ -82,18 +82,18 @@ def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
end
def template_mix(mix, listen)
html = <<-END_HTML
<h3>
<a href="/mix?list=#{mix["mixId"]}">
#{mix["title"]}
</a>
</h3>
<div class="pure-menu pure-menu-scrollable playlist-restricted">
<ol class="pure-menu-list">
END_HTML
html = <<-HTML
<h3>
<a href="/mix?list=#{mix["mixId"]}">
#{mix["title"]}
</a>
</h3>
<div class="pure-menu pure-menu-scrollable playlist-restricted">
<ol class="pure-menu-list">
HTML
mix["videos"].as_a.each do |video|
html += <<-END_HTML
html += <<-HTML
<li class="pure-menu-item">
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}#{listen ? "&listen=1" : ""}">
<div class="thumbnail">
@ -106,14 +106,14 @@ def template_mix(mix, listen)
</p>
</a>
</li>
END_HTML
HTML
end
html += <<-END_HTML
</ol>
</div>
<hr>
END_HTML
html += <<-HTML
</ol>
</div>
<hr>
HTML
html
end

View File

@ -13,30 +13,30 @@ struct PlaylistVideo
def to_xml(xml : XML::Builder)
xml.element("entry") do
xml.element("id") { xml.text "yt:video:#{self.id}" }
xml.element("yt:videoId") { xml.text self.id }
xml.element("yt:channelId") { xml.text self.ucid }
xml.element("title") { xml.text self.title }
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?v=#{self.id}")
xml.element("id") { xml.text "yt:video:#{id}" }
xml.element("yt:videoId") { xml.text id }
xml.element("yt:channelId") { xml.text ucid }
xml.element("title") { xml.text title }
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?v=#{id}")
xml.element("author") do
xml.element("name") { xml.text self.author }
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" }
xml.element("name") { xml.text author }
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
end
xml.element("content", type: "xhtml") do
xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do
xml.element("a", href: "#{HOST_URL}/watch?v=#{self.id}") do
xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg")
xml.element("a", href: "#{HOST_URL}/watch?v=#{id}") do
xml.element("img", src: "#{HOST_URL}/vi/#{id}/mqdefault.jpg")
end
end
end
xml.element("published") { xml.text self.published.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("published") { xml.text published.to_s("%Y-%m-%dT%H:%M:%S%:z") }
xml.element("media:group") do
xml.element("media:title") { xml.text self.title }
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg",
xml.element("media:title") { xml.text title }
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{id}/mqdefault.jpg",
width: "320", height: "180")
end
end
@ -54,15 +54,15 @@ struct PlaylistVideo
json.object do
json.field "type", "video"
json.field "title", self.title
json.field "videoId", self.id
json.field "title", title
json.field "videoId", id
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "author", author
json.field "authorId", ucid
json.field "authorUrl", "/channel/#{ucid}"
json.field "videoThumbnails" do
Invidious::JSONify::APIv1.thumbnails(json, self.id)
Invidious::JSONify::APIv1.thumbnails(json, id)
end
if index
@ -72,8 +72,8 @@ struct PlaylistVideo
json.field "index", self.index
end
json.field "lengthSeconds", self.length_seconds
json.field "liveNow", self.live_now
json.field "lengthSeconds", length_seconds
json.field "liveNow", live_now
end
end
@ -101,14 +101,14 @@ struct Playlist
def to_json(offset, json : JSON::Builder, video_id : String? = nil)
json.object do
json.field "type", "playlist"
json.field "title", self.title
json.field "playlistId", self.id
json.field "playlistThumbnail", self.thumbnail
json.field "title", title
json.field "playlistId", id
json.field "playlistThumbnail", thumbnail
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
json.field "subtitle", self.subtitle
json.field "author", author
json.field "authorId", ucid
json.field "authorUrl", "/channel/#{ucid}"
json.field "subtitle", subtitle
json.field "authorThumbnails" do
json.array do
@ -116,7 +116,7 @@ struct Playlist
qualities.each do |quality|
json.object do
json.field "url", self.author_thumbnail.not_nil!.gsub(/=\d+/, "=s#{quality}")
json.field "url", author_thumbnail.not_nil!.gsub(/=\d+/, "=s#{quality}")
json.field "width", quality
json.field "height", quality
end
@ -124,13 +124,13 @@ struct Playlist
end
end
json.field "description", self.description
json.field "descriptionHtml", self.description_html
json.field "videoCount", self.video_count
json.field "description", description
json.field "descriptionHtml", description_html
json.field "videoCount", video_count
json.field "viewCount", self.views
json.field "updated", self.updated.to_unix
json.field "isListed", self.privacy.public?
json.field "viewCount", views
json.field "updated", updated.to_unix
json.field "isListed", privacy.public?
json.field "videos" do
json.array do
@ -180,33 +180,33 @@ struct InvidiousPlaylist
module PlaylistPrivacyConverter
def self.from_rs(rs)
return PlaylistPrivacy.parse(String.new(rs.read(Slice(UInt8))))
PlaylistPrivacy.parse(String.new(rs.read(Slice(UInt8))))
end
end
def to_json(offset, json : JSON::Builder, video_id : String? = nil)
json.object do
json.field "type", "invidiousPlaylist"
json.field "title", self.title
json.field "playlistId", self.id
json.field "title", title
json.field "playlistId", id
json.field "author", self.author
json.field "authorId", self.ucid
json.field "author", author
json.field "authorId", ucid
json.field "authorUrl", nil
json.field "authorThumbnails", [] of String
json.field "description", html_to_content(self.description_html)
json.field "descriptionHtml", self.description_html
json.field "videoCount", self.video_count
json.field "description", html_to_content(description_html)
json.field "descriptionHtml", description_html
json.field "videoCount", video_count
json.field "viewCount", self.views
json.field "updated", self.updated.to_unix
json.field "isListed", self.privacy.public?
json.field "viewCount", views
json.field "updated", updated.to_unix
json.field "isListed", privacy.public?
json.field "videos" do
json.array do
if (!offset || offset == 0) && !video_id.nil?
index = Invidious::Database::PlaylistVideos.select_index(self.id, video_id)
index = Invidious::Database::PlaylistVideos.select_index(id, video_id)
offset = self.index.index(index) || 0
end
@ -227,7 +227,7 @@ struct InvidiousPlaylist
def thumbnail
# TODO: Get playlist thumbnail from playlist data rather than first video
@thumbnail_id ||= Invidious::Database::PlaylistVideos.select_one_id(self.id, self.index) || "-----------"
@thumbnail_id ||= Invidious::Database::PlaylistVideos.select_one_id(id, index) || "-----------"
"/vi/#{@thumbnail_id}/mqdefault.jpg"
end
@ -244,7 +244,7 @@ struct InvidiousPlaylist
end
def description_html
HTML.escape(self.description)
HTML.escape(description)
end
end
@ -265,7 +265,7 @@ def create_playlist(title, privacy, user)
Invidious::Database::Playlists.insert(playlist)
return playlist
playlist
end
def subscribe_playlist(user, playlist)
@ -283,7 +283,7 @@ def subscribe_playlist(user, playlist)
Invidious::Database::Playlists.insert(playlist)
return playlist
playlist
end
def produce_playlist_continuation(id, index)
@ -318,18 +318,18 @@ def produce_playlist_continuation(id, index)
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
return continuation
continuation
end
def get_playlist(plid : String)
if plid.starts_with? "IV"
if playlist = Invidious::Database::Playlists.select(id: plid)
return playlist
playlist
else
raise NotFoundException.new("Playlist does not exist.")
end
else
return fetch_playlist(plid)
fetch_playlist(plid)
end
end
@ -398,7 +398,7 @@ def fetch_playlist(plid : String)
ucid = author_info.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
end
return Playlist.new({
Playlist.new({
title: title,
id: plid,
author: author,
@ -443,7 +443,7 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
offset += 100
end
return videos
videos
end
end
@ -504,22 +504,22 @@ def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
videos << ProblematicTimelineItem.new(parse_exception: ex)
end
return videos
videos
end
def template_playlist(playlist, listen)
html = <<-END_HTML
<h3>
<a href="/playlist?list=#{playlist["playlistId"]}">
#{playlist["title"]}
</a>
</h3>
<div class="pure-menu pure-menu-scrollable playlist-restricted">
<ol class="pure-menu-list">
END_HTML
html = <<-HTML
<h3>
<a href="/playlist?list=#{playlist["playlistId"]}">
#{playlist["title"]}
</a>
</h3>
<div class="pure-menu pure-menu-scrollable playlist-restricted">
<ol class="pure-menu-list">
HTML
playlist["videos"].as_a.each do |video|
html += <<-END_HTML
html += <<-HTML
<li class="pure-menu-item" id="#{video["videoId"]}">
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}#{listen ? "&listen=1" : ""}">
<div class="thumbnail">
@ -532,14 +532,14 @@ def template_playlist(playlist, listen)
</p>
</a>
</li>
END_HTML
HTML
end
html += <<-END_HTML
</ol>
</div>
<hr>
END_HTML
html += <<-HTML
</ol>
</div>
<hr>
HTML
html
end

View File

@ -337,10 +337,10 @@ module Invidious::Routes::Account
end
if redirect
return env.redirect referer
env.redirect referer
else
env.response.content_type = "application/json"
return "{}"
"{}"
end
end
end

View File

@ -140,7 +140,7 @@ module Invidious::Routes::API::Manifest
end
end
return manifest
manifest
end
# /api/manifest/dash/id/videoplayback

View File

@ -17,6 +17,7 @@ module Invidious::Routes::API::V1::Authenticated
user.preferences.to_json
end
# ameba:disable Naming/AccessorMethodName
def self.set_preferences(env)
env.response.content_type = "application/json"
user = env.get("user").as(User)
@ -35,7 +36,7 @@ module Invidious::Routes::API::V1::Authenticated
env.response.content_type = "application/json"
user = env.get("user").as(User)
return Invidious::User::Export.to_invidious(user)
Invidious::User::Export.to_invidious(user)
end
def self.import_invidious(env)
@ -71,7 +72,7 @@ module Invidious::Routes::API::V1::Authenticated
end
watched ||= [] of String
return watched.to_json
watched.to_json
end
def self.mark_watched(env)
@ -423,7 +424,7 @@ module Invidious::Routes::API::V1::Authenticated
env.response.content_type = "text/html"
csrf_token = generate_response(sid, {":authorize_token"}, HMAC_KEY, use_nonce: true)
return templated "user/authorize_token"
templated "user/authorize_token"
else
env.response.content_type = "application/json"
@ -482,7 +483,7 @@ module Invidious::Routes::API::V1::Authenticated
env.response.content_type = "text/event-stream"
raw_topics = env.params.body["topics"]? || env.params.query["topics"]?
topics = raw_topics.try &.split(",").uniq.first(1000)
topics = raw_topics.try &.split(",").uniq!.first(1000)
topics ||= [] of String
create_notification_stream(env, topics, CONNECTION_CHANNEL)

View File

@ -137,7 +137,7 @@ module Invidious::Routes::API::V1::Channels
env.params.query.delete("sort_by") if env.params.query.has_key?("sort_by")
env.params.query.delete("continuation") if env.params.query.has_key?("continuation")
return self.videos(env)
videos(env)
end
def self.videos(env)
@ -173,7 +173,7 @@ module Invidious::Routes::API::V1::Channels
end
end
return JSON.build do |json|
JSON.build do |json|
json.object do
json.field "videos" do
json.array do
@ -219,7 +219,7 @@ module Invidious::Routes::API::V1::Channels
end
end
return JSON.build do |json|
JSON.build do |json|
json.object do
json.field "videos" do
json.array do
@ -265,7 +265,7 @@ module Invidious::Routes::API::V1::Channels
end
end
return JSON.build do |json|
JSON.build do |json|
json.object do
json.field "videos" do
json.array do
@ -416,7 +416,7 @@ module Invidious::Routes::API::V1::Channels
begin
fetch_channel_community(ucid, continuation, locale, format, thin_mode)
rescue ex
return error_json(500, ex)
error_json(500, ex)
end
end
@ -444,7 +444,7 @@ module Invidious::Routes::API::V1::Channels
begin
fetch_channel_community_post(ucid, id, locale, format, thin_mode)
rescue ex
return error_json(500, ex)
error_json(500, ex)
end
end
@ -472,7 +472,7 @@ module Invidious::Routes::API::V1::Channels
else
comments = YoutubeAPI.browse(continuation: continuation)
end
return Comments.parse_youtube(id, comments, format, locale, thin_mode, is_post: true)
Comments.parse_youtube(id, comments, format, locale, thin_mode, is_post: true)
end
def self.channels(env)

View File

@ -4,10 +4,10 @@ module Invidious::Routes::API::V1::Misc
env.response.content_type = "application/json"
if !CONFIG.statistics_enabled
return {"software" => SOFTWARE}.to_json
{"software" => SOFTWARE}.to_json
else
# Calculate playback success rate
if (tracker = Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"]?)
if tracker = Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"]?
tracker = tracker.as(Hash(String, Int64 | Float64))
if !tracker.empty?
@ -22,7 +22,7 @@ module Invidious::Routes::API::V1::Misc
end
end
return Invidious::Jobs::StatisticsRefreshJob::STATISTICS.to_json
Invidious::Jobs::StatisticsRefreshJob::STATISTICS.to_json
end
end

View File

@ -53,7 +53,7 @@ module Invidious::Routes::API::V1::Search
end
end
rescue ex
return error_json(500, ex)
error_json(500, ex)
end
end

View File

@ -18,7 +18,7 @@ module Invidious::Routes::API::V1::Videos
return error_json(500, ex)
end
return JSON.build do |json|
JSON.build do |json|
Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy)
end
end
@ -247,7 +247,7 @@ module Invidious::Routes::API::V1::Videos
# videojs-vtt-thumbnails is not compliant to the VTT specification, it
# doesn't unescape the HTML entities, so we have to do it here:
# TODO: remove this when we migrate to VideoJS 8
return HTML.unescape(vtt_file)
HTML.unescape(vtt_file)
end
def self.annotations(env)
@ -352,7 +352,7 @@ module Invidious::Routes::API::V1::Videos
return error_json(500, ex)
end
return comments
comments
elsif source == "reddit"
sort_by ||= "confidence"
@ -418,7 +418,7 @@ module Invidious::Routes::API::V1::Videos
return error_json(500, ex)
end
return JSON.build do |json|
JSON.build do |json|
json.object do
json.field "startTime", start_time
json.field "endTime", end_time
@ -513,6 +513,6 @@ module Invidious::Routes::API::V1::Videos
return error_json(500, ex)
end
return transcript.to_json
transcript.to_json
end
end

View File

@ -4,15 +4,15 @@ module Invidious::Routes::Channels
# Redirection for unsupported routes ("tabs")
def self.redirect_home(env)
ucid = env.params.url["ucid"]
return env.redirect "/channel/#{URI.encode_www_form(ucid)}"
env.redirect "/channel/#{URI.encode_www_form(ucid)}"
end
def self.home(env)
self.videos(env)
videos(env)
end
def self.videos(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -64,7 +64,7 @@ module Invidious::Routes::Channels
end
def self.shorts(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -99,7 +99,7 @@ module Invidious::Routes::Channels
end
def self.streams(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -134,7 +134,7 @@ module Invidious::Routes::Channels
end
def self.playlists(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -158,7 +158,7 @@ module Invidious::Routes::Channels
end
def self.podcasts(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -178,7 +178,7 @@ module Invidious::Routes::Channels
end
def self.releases(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -198,7 +198,7 @@ module Invidious::Routes::Channels
end
def self.courses(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -220,7 +220,7 @@ module Invidious::Routes::Channels
def self.community(env)
return env.redirect env.request.path.sub("posts", "community") if env.request.path.split("/").last == "posts"
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
if !data.is_a?(Tuple)
return data
end
@ -298,7 +298,7 @@ module Invidious::Routes::Channels
end
def self.channels(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
return data if !data.is_a?(Tuple)
locale, user, subscriptions, continuation, ucid, channel = data
@ -318,7 +318,7 @@ module Invidious::Routes::Channels
end
def self.about(env)
data = self.fetch_basic_information(env)
data = fetch_basic_information(env)
if !data.is_a?(Tuple)
return data
end
@ -365,7 +365,7 @@ module Invidious::Routes::Channels
url += "?#{invidious_url_params}" if !invidious_url_params.empty?
return env.redirect url
env.redirect url
end
# Handles redirects for the /profile endpoint
@ -378,7 +378,7 @@ module Invidious::Routes::Channels
user = env.params.query["user"]?
if !user
return error_template(404, "This channel does not exist.")
error_template(404, "This channel does not exist.")
else
env.redirect "/user/#{user}#{uri_params}"
end

View File

@ -9,7 +9,7 @@ module Invidious::Routes::Companion
begin
COMPANION_POOL.client do |wrapper|
wrapper.client.get(url, env.request.headers) do |resp|
return self.proxy_companion(env, resp)
return proxy_companion(env, resp)
end
end
rescue ex
@ -26,7 +26,7 @@ module Invidious::Routes::Companion
begin
COMPANION_POOL.client do |wrapper|
wrapper.client.post(url, env.request.headers, env.request.body) do |resp|
return self.proxy_companion(env, resp)
return proxy_companion(env, resp)
end
end
rescue ex
@ -42,7 +42,7 @@ module Invidious::Routes::Companion
begin
COMPANION_POOL.client do |wrapper|
wrapper.client.options(url, env.request.headers) do |resp|
return self.proxy_companion(env, resp)
return proxy_companion(env, resp)
end
end
rescue ex
@ -55,6 +55,6 @@ module Invidious::Routes::Companion
env.response.headers[key] = value
end
return IO.copy response.body_io, env.response
IO.copy response.body_io, env.response
end
end

View File

@ -119,7 +119,6 @@ module Invidious::Routes::Embed
end
return env.redirect url
else nil # Continue
end
params = 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

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

View File

@ -13,7 +13,7 @@ module Invidious::Routes::Images
begin
GGPHT_POOL.client &.get(url, headers) do |resp|
return self.proxy_image(env, resp)
return proxy_image(env, resp)
end
rescue ex
end
@ -44,7 +44,7 @@ module Invidious::Routes::Images
begin
get_ytimg_pool(authority).client &.get(url, headers) do |resp|
env.response.headers["Connection"] = "close"
return self.proxy_image(env, resp)
return proxy_image(env, resp)
end
rescue ex
end
@ -66,7 +66,7 @@ module Invidious::Routes::Images
begin
get_ytimg_pool("i9").client &.get(url, headers) do |resp|
return self.proxy_image(env, resp)
return proxy_image(env, resp)
end
rescue ex
end
@ -128,7 +128,7 @@ module Invidious::Routes::Images
begin
get_ytimg_pool("i").client &.get(url, headers) do |resp|
return self.proxy_image(env, resp)
return proxy_image(env, resp)
end
rescue ex
end
@ -148,6 +148,6 @@ module Invidious::Routes::Images
return env.response.headers.delete("Transfer-Encoding")
end
return proxy_file(response, env)
proxy_file(response, env)
end
end

View File

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

View File

@ -241,7 +241,7 @@ module Invidious::Routes::VideoPlayback
query_params = HTTP::Params.new(raw_params)
env.response.headers["Access-Control-Allow-Origin"] = "*"
return env.redirect "/videoplayback?#{query_params}"
env.redirect "/videoplayback?#{query_params}"
end
# /videoplayback/* && /videoplayback/*
@ -307,6 +307,6 @@ module Invidious::Routes::VideoPlayback
url += "&title=#{URI.encode_www_form(title, space_to_plus: false)}" if title
end
return env.redirect url
env.redirect url
end
end

View File

@ -145,14 +145,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"
@ -215,7 +215,7 @@ module Invidious::Routes::Watch
url += "&#{env.params.query}"
end
return env.redirect url
env.redirect url
end
def self.mark_watched(env)
@ -289,9 +289,9 @@ module Invidious::Routes::Watch
env.params.query["end"] = end_time.to_s if end_time != nil
end
return env.redirect "/watch?v=#{video_id}&#{env.params.query}"
env.redirect "/watch?v=#{video_id}&#{env.params.query}"
else
return error_template(404, "The requested clip doesn't exist")
error_template(404, "The requested clip doesn't exist")
end
end
@ -330,16 +330,16 @@ module Invidious::Routes::Watch
env.params.query["title"] = filename
env.params.query["label"] = URI.decode_www_form(label.as_s)
return Invidious::Routes::API::V1::Videos.captions(env)
Invidious::Routes::API::V1::Videos.captions(env)
elsif itag = download_widget["itag"]?.try &.as_i.to_s
# URL params specific to /latest_version
env.params.query["id"] = video_id
env.params.query["title"] = filename
env.params.query["local"] = "true"
return Invidious::Routes::VideoPlayback.latest_version(env)
Invidious::Routes::VideoPlayback.latest_version(env)
else
return error_template(400, "Invalid label or itag")
error_template(400, "Invalid label or itag")
end
end
end

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
@ -42,11 +42,11 @@ module Invidious::Routing
end
{% end %}
self.register_image_routes
self.register_api_v1_routes
self.register_api_manifest_routes
self.register_video_playback_routes
self.register_companion_routes
register_image_routes
register_api_v1_routes
register_api_manifest_routes
register_video_playback_routes
register_companion_routes
end
# -------------------
@ -238,91 +238,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

@ -28,5 +28,5 @@ def produce_channel_search_continuation(ucid, query, page)
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
return continuation
continuation
end

View File

@ -80,7 +80,7 @@ module Invidious::Search
end
def default? : Bool
return @date.none? && @type.all? && @duration.none? && \
@date.none? && @type.all? && @duration.none? && \
@features.none? && @sort.relevance?
end
@ -110,7 +110,7 @@ module Invidious::Search
end
end
return features
features
end
def self.format_features(features : Features) : String
@ -132,7 +132,7 @@ module Invidious::Search
str << "location" if features.location?
str << "purchased" if features.purchased?
return str.join(',')
str.join(',')
end
def self.from_legacy_filters(str : String) : {Filters, String, String, Bool}
@ -230,7 +230,7 @@ module Invidious::Search
params.delete("sort")
end
return filters
filters
end
def to_iv_params : HTTP::Params
@ -249,7 +249,7 @@ module Invidious::Search
raw_params["features"] = [Filters.format_features(@features)]
end
return HTTP::Params.new(raw_params)
HTTP::Params.new(raw_params)
end
# -------------------
@ -304,7 +304,7 @@ module Invidious::Search
# See https://github.com/iv-org/invidious/issues/4398
object["30:varint"] = 1.to_i64
return object
object
.try { |i| Protodec::Any.cast_json(i) }
.try { |i| Protodec::Any.from_json(i) }
.try { |i| Base64.urlsafe_encode(i) }
@ -370,7 +370,7 @@ module Invidious::Search
# Remove URL parameter and return result
params.delete("sp")
return filters
filters
end
end
end

View File

@ -10,7 +10,7 @@ module Invidious::Search
initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config)
items, _ = extract_items(initial_data)
return items.reject!(Category)
items.reject!(Category)
end
# Search a youtube channel
@ -32,14 +32,14 @@ module Invidious::Search
response_json = YoutubeAPI.browse(continuation)
items, _ = extract_items(response_json, "", ucid)
return items.reject!(Category)
items.reject!(Category)
end
# Search inside of user subscriptions
def subscriptions(query : Query, user : Invidious::User) : Array(ChannelVideo)
view_name = "subscriptions_#{sha256(user.email)}"
return PG_DB.query_all("
PG_DB.query_all(<<-SQL, query.text, (query.page - 1) * 20, as: ChannelVideo)
SELECT id,title,published,updated,ucid,author,length_seconds
FROM (
SELECT *,
@ -47,10 +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;",
query.text, (query.page - 1) * 20,
as: ChannelVideo
)
) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;
SQL
end
end
end

View File

@ -25,19 +25,19 @@ module Invidious::Search
# Return true if @raw_query is either `nil` or empty
private def empty_raw_query?
return @raw_query.empty?
@raw_query.empty?
end
# Same as `empty_raw_query?`, but named for external use
def empty?
return self.empty_raw_query?
empty_raw_query?
end
# Getter for the query string.
# It is named `text` to reduce confusion (`search_query.text` makes more
# sense than `search_query.query`)
def text
return @query
@query
end
# Initialize a new search query.
@ -70,7 +70,7 @@ module Invidious::Search
# Stop here if raw query is empty
# NOTE: maybe raise in the future?
return if self.empty_raw_query?
return if empty_raw_query?
# Specific handling
case @type
@ -120,7 +120,7 @@ module Invidious::Search
items = [] of SearchItem
# Don't bother going further if search query is empty
return items if self.empty_raw_query?
return items if empty_raw_query?
case @type
when .regular?, .playlist?
@ -135,7 +135,7 @@ module Invidious::Search
end
end
return items
items
end
# Return the HTTP::Params corresponding to this Query (invidious format)
@ -145,7 +145,7 @@ module Invidious::Search
params["q"] = @query
params["channel"] = @channel if !@channel.empty?
return params
params
end
# Checks if the query is a standalone URL
@ -160,7 +160,7 @@ module Invidious::Search
return false if !@filters.default?
# Simple heuristics: domain name
return @raw_query.starts_with?(
@raw_query.starts_with?(
/(https?:\/\/)?(www\.)?(m\.)?youtu(\.be|be\.com)\//
)
end

View File

@ -35,7 +35,7 @@ def fetch_trending(trending_type, region, locale)
# Ignore the smaller categories, as they generally contain a sponsored
# channel, which brings a lot of noise on the trending page.
# See: https://github.com/iv-org/invidious/issues/2989
next if (itm.contents.size < 24 && deduplicate)
next if itm.contents.size < 24 && deduplicate
extracted.concat itm.contents.select(SearchItem)
else

View File

@ -19,29 +19,29 @@ struct Invidious::User
hour = 12
end
clock_svg = <<-END_SVG
<svg viewBox="0 0 100 100" width="200px" height="200px">
<circle cx="50" cy="50" r="45" fill="#eee" stroke="black" stroke-width="2"></circle>
clock_svg = <<-SVG
<svg viewBox="0 0 100 100" width="200px" height="200px">
<circle cx="50" cy="50" r="45" fill="#eee" stroke="black" stroke-width="2"></circle>
<text x="69" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 1</text>
<text x="82.909" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 2</text>
<text x="88" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 3</text>
<text x="82.909" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 4</text>
<text x="69" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 5</text>
<text x="50" y="91" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 6</text>
<text x="31" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 7</text>
<text x="17.091" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 8</text>
<text x="12" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 9</text>
<text x="17.091" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">10</text>
<text x="31" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">11</text>
<text x="50" y="15" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">12</text>
<text x="69" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 1</text>
<text x="82.909" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 2</text>
<text x="88" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 3</text>
<text x="82.909" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 4</text>
<text x="69" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 5</text>
<text x="50" y="91" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 6</text>
<text x="31" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 7</text>
<text x="17.091" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 8</text>
<text x="12" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 9</text>
<text x="17.091" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">10</text>
<text x="31" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">11</text>
<text x="50" y="15" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">12</text>
<circle cx="50" cy="50" r="3" fill="black"></circle>
<line id="second" transform="rotate(#{second_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="12" fill="black" stroke="black" stroke-width="1"></line>
<line id="minute" transform="rotate(#{minute_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="16" fill="black" stroke="black" stroke-width="2"></line>
<line id="hour" transform="rotate(#{hour_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="24" fill="black" stroke="black" stroke-width="2"></line>
</svg>
END_SVG
<circle cx="50" cy="50" r="3" fill="black"></circle>
<line id="second" transform="rotate(#{second_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="12" fill="black" stroke="black" stroke-width="1"></line>
<line id="minute" transform="rotate(#{minute_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="16" fill="black" stroke="black" stroke-width="2"></line>
<line id="hour" transform="rotate(#{hour_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="24" fill="black" stroke="black" stroke-width="2"></line>
</svg>
SVG
image = "data:image/png;base64,"
image += Process.run(%(rsvg-convert -w 400 -h 400 -b none -f png), shell: true,
@ -53,7 +53,7 @@ struct Invidious::User
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
return {
{
question: image,
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
}

View File

@ -11,7 +11,7 @@ struct Invidious::User
# Session ID (SID) cookie
# Parameter "domain" comes from the global config
def sid(domain : String?, sid) : HTTP::Cookie
return HTTP::Cookie.new(
HTTP::Cookie.new(
name: "SID",
domain: domain,
value: sid,
@ -25,7 +25,7 @@ struct Invidious::User
# Preferences (PREFS) cookie
# Parameter "domain" comes from the global config
def prefs(domain : String?, preferences : Preferences) : HTTP::Cookie
return HTTP::Cookie.new(
HTTP::Cookie.new(
name: "PREFS",
domain: domain,
value: URI.encode_www_form(preferences.to_json),

View File

@ -5,7 +5,7 @@ struct Invidious::User
def to_invidious(user : User)
playlists = Invidious::Database::Playlists.select_like_iv(user.email)
return JSON.build do |json|
JSON.build do |json|
json.object do
json.field "subscriptions", user.subscriptions
json.field "watch_history", user.watched

View File

@ -27,7 +27,7 @@ struct Invidious::User
subscriptions << channel_id
end
return subscriptions
subscriptions
end
def parse_playlist_export_csv(user : User, raw_input : String)
@ -81,7 +81,7 @@ struct Invidious::User
end
end
return playlist
playlist
end
# -------------------
@ -171,7 +171,7 @@ struct Invidious::User
opml_extensions = ["xml", "opml"]
return opml_mimetypes.any?(&.== mimetype) || opml_extensions.any?(&.== extension)
opml_mimetypes.any?(&.== mimetype) || opml_extensions.any?(&.== extension)
end
# Import subscribed channels from Youtube
@ -200,7 +200,7 @@ struct Invidious::User
user.subscriptions = get_batch_channels(user.subscriptions)
Invidious::Database::Users.update_subscriptions(user)
return true
true
end
def from_youtube_pl(user : User, body : String, filename : String, type : String) : Bool
@ -209,12 +209,12 @@ struct Invidious::User
if extension == "csv" || type == "text/csv"
playlist = parse_playlist_export_csv(user, body)
if playlist
return true
true
else
return false
false
end
else
return false
false
end
end
@ -232,9 +232,9 @@ struct Invidious::User
user.watched += watched
user.watched.uniq!
Invidious::Database::Users.update_watch_history(user)
return true
true
else
return false
false
end
end
@ -328,7 +328,7 @@ struct Invidious::User
end
# Success!
return true
true
end
end # module
end

View File

@ -64,20 +64,18 @@ struct Preferences
end
def self.from_json(value : JSON::PullParser) : String
begin
result = value.read_string
result = value.read_string
if result.empty?
CONFIG.default_user_preferences.dark_mode
else
result
end
rescue ex
if value.read_bool
"dark"
else
"light"
end
if result.empty?
CONFIG.default_user_preferences.dark_mode
else
result
end
rescue ex
if value.read_bool
"dark"
else
"light"
end
end
@ -262,12 +260,12 @@ struct Preferences
module TimeSpanConverter
def self.to_yaml(value : Time::Span, yaml : YAML::Nodes::Builder)
return yaml.scalar value.total_minutes.to_i32
yaml.scalar value.total_minutes.to_i32
end
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Time::Span
if node.is_a?(YAML::Nodes::Scalar)
return decode_interval(node.value)
decode_interval(node.value)
else
node.raise "Expected scalar, not #{node.class}"
end

View File

@ -17,11 +17,9 @@ struct Invidious::User
module PreferencesConverter
def self.from_rs(rs)
begin
Preferences.from_json(rs.read(String))
rescue ex
Preferences.from_json("{}")
end
Preferences.from_json(rs.read(String))
rescue ex
Preferences.from_json("{}")
end
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
@ -56,15 +56,15 @@ struct Video
def video_type : VideoType
video_type = info["videoType"]?.try &.as_s || "video"
return VideoType.parse?(video_type) || VideoType::Video
VideoType.parse?(video_type) || VideoType::Video
end
def schema_version : Int
return info["version"]?.try &.as_i || 1
info["version"]?.try &.as_i || 1
end
def published : Time
return info["published"]?
info["published"]?
.try { |t| Time.parse(t.as_s, "%Y-%m-%d", Time::Location::UTC) } || Time.utc
end
@ -73,11 +73,11 @@ struct Video
end
def live_now
return (self.video_type == VideoType::Livestream)
(video_type == VideoType::Livestream)
end
def post_live_dvr
return info["isPostLiveDvr"].as_bool
info["isPostLiveDvr"].as_bool
end
def premiere_timestamp : Time?
@ -94,21 +94,21 @@ struct Video
def fmt_stream : Array(Hash(String, JSON::Any))
if formats = info.dig?("streamingData", "formats")
return formats
formats
.as_a.map(&.as_h)
.sort_by! { |f| f["width"]?.try &.as_i || 0 }
else
return [] of Hash(String, JSON::Any)
[] of Hash(String, JSON::Any)
end
end
def adaptive_fmts : Array(Hash(String, JSON::Any))
if formats = info.dig?("streamingData", "adaptiveFormats")
return formats
formats
.as_a.map(&.as_h)
.sort_by! { |f| f["width"]?.try &.as_i || f["audioTrack"]?.try { |a| a["audioIsDefault"]?.try { |v| v.as_bool ? -1 : 0 } } || 0 }
else
return [] of Hash(String, JSON::Any)
[] of Hash(String, JSON::Any)
end
end
@ -124,11 +124,11 @@ struct Video
def storyboards
container = info.dig?("storyboards") || JSON::Any.new("{}")
return IV::Videos::Storyboard.from_yt_json(container, self.length_seconds)
IV::Videos::Storyboard.from_yt_json(container, length_seconds)
end
def paid
return (self.reason || "").includes? "requires payment"
(reason || "").includes? "requires payment"
end
def premium
@ -140,7 +140,7 @@ struct Video
@captions = Invidious::Videos::Captions::Metadata.from_yt_json(info["captions"])
end
return @captions
@captions
end
def hls_manifest_url : String?
@ -149,7 +149,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
@ -162,7 +162,7 @@ struct Video
dash_url.query = "#{dash_query}&mpd_version=5"
end
return dash_url.to_s
dash_url.to_s
end
def genre_url : String?
@ -170,11 +170,11 @@ struct Video
end
def vr? : Bool?
return {"EQUIRECTANGULAR", "MESH"}.includes? self.projection_type
{"EQUIRECTANGULAR", "MESH"}.includes? projection_type
end
def projection_type : String?
return info.dig?("streamingData", "adaptiveFormats", 0, "projectionType").try &.as_s
info.dig?("streamingData", "adaptiveFormats", 0, "projectionType").try &.as_s
end
def reason : String?
@ -182,50 +182,50 @@ 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
# 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
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)
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 }}
info[\{{name.stringify}}]?.try &.as_i64.to_{{ op }} || 0_{{ op }}
end
def \{{name.id.underscore}}=(value : Int)
@ -237,32 +237,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
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
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
@ -316,11 +316,11 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
Invidious::Database::Videos.insert(video) if !region
end
return video
video
rescue DB::Error
# Avoid common `DB::PoolRetryAttemptsExceeded` error and friends
# Note: All DB errors inherit from `DB::Error`
return fetch_video(id, region)
fetch_video(id, region)
end
def fetch_video(id, region)
@ -350,7 +350,7 @@ def fetch_video(id, region)
updated: Time.utc,
})
return video
video
end
def process_continuation(query, plid, id)

View File

@ -33,7 +33,7 @@ module Invidious::Videos
captions_list << Captions::Metadata.new(name, language_code, base_url, auto_generated)
end
return captions_list
captions_list
end
def timedtext_to_vtt(timedtext : String, tlang = nil) : String
@ -82,7 +82,7 @@ module Invidious::Videos
end
end
return result
result
end
end

View File

@ -26,7 +26,7 @@ private def copy_string(str : String::Builder, iter : Iterator, count : Int) : I
copied += 1
end
return copied
copied
end
def parse_description(desc, video_id : String) : String?
@ -52,7 +52,7 @@ def parse_description(desc, video_id : String) : String?
index = 0
return String.build do |str|
String.build do |str|
commands.each do |command|
cmd_start = command["startIndex"].as_i
cmd_length = command["length"].as_i

View File

@ -1,6 +1,6 @@
module Invidious::Videos::Formats
def self.itag_to_metadata?(itag : JSON::Any)
return FORMATS[itag.to_s]?
FORMATS[itag.to_s]?
end
# See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L380-#L476

View File

@ -7,7 +7,7 @@ require "json"
# 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")
@ -40,7 +40,7 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
# TODO: when refactoring video types, make a struct for related videos
# or reuse an existing type, if that fits.
return {
{
"id" => related["videoId"],
"title" => related["title"]["simpleText"],
"author" => author || JSON::Any.new(""),
@ -57,7 +57,7 @@ def extract_video_info(video_id : String)
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
@ -128,7 +128,7 @@ def extract_video_info(video_id : String)
# Data structure version, for cache control
params["version"] = JSON::Any.new(Video::SCHEMA_VERSION.to_i64)
return params
params
end
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
@ -145,9 +145,9 @@ def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConf
"The video returned by YouTube isn't the requested one. (#{client_config.client_type} client)"
)
elsif playability_status == "OK"
return response
response
else
return nil
return
end
end
@ -440,7 +440,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
"subCountText" => JSON::Any.new(subs_text || "-"),
}
return params
params
end
private def convert_url(fmt)
@ -457,9 +457,9 @@ private def convert_url(fmt)
url.query_params = params
LOGGER.trace("convert_url: new url is '#{url}'")
return url.to_s
url.to_s
rescue ex
LOGGER.debug("convert_url: Error when parsing video URL")
LOGGER.trace(ex.inspect_with_backtrace)
return ""
""
end

View File

@ -62,7 +62,7 @@ module Invidious::Videos
# The base URL is the first chunk
base_url = URI.parse(storyboards.shift)
return storyboards.map_with_index do |sb, i|
storyboards.map_with_index do |sb, i|
# Separate the different storyboard parameters:
# width/height: respective dimensions, in pixels, of a single thumbnail
# count: how many thumbnails are displayed across the full video

View File

@ -45,7 +45,7 @@ module Invidious::Videos
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
return params
params
end
# Constructs a Transcripts struct from the initial YouTube response
@ -92,7 +92,7 @@ module Invidious::Videos
lines << line_type.new(start_ms, end_ms, text)
end
return Transcript.new(
Transcript.new(
lines: lines,
language_code: language_code,
auto_generated: auto_generated,
@ -120,7 +120,7 @@ module Invidious::Videos
end
end
return vtt
vtt
end
def to_json(json : JSON::Builder)

View File

@ -158,5 +158,5 @@ def process_video_params(query, preferences)
save_player_pos: save_player_pos,
})
return params
params
end

View File

@ -16,7 +16,7 @@
best_m4a_stream_bitrate = 0
audio_streams.each_with_index do |fmt, i|
bandwidth = fmt["bitrate"].as_i
if (fmt["mimeType"].as_s.starts_with?("audio/mp4") && bandwidth > best_m4a_stream_bitrate)
if fmt["mimeType"].as_s.starts_with?("audio/mp4") && bandwidth > best_m4a_stream_bitrate
best_m4a_stream_bitrate = bandwidth
best_m4a_stream_index = i
end
@ -26,7 +26,7 @@
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
src_url += "&local=true" if params.local
src_url = invidious_companion.public_url.to_s + src_url +
"&check=#{invidious_companion_check_id}" if (invidious_companion)
"&check=#{invidious_companion_check_id}" if invidious_companion
bitrate = fmt["bitrate"]
mimetype = HTML.escape(fmt["mimeType"].as_s)
@ -42,7 +42,7 @@
<% if params.quality == "dash"
src_url = "/api/manifest/dash/id/" + video.id + "?local=true&unique_res=1"
src_url = invidious_companion.public_url.to_s + src_url +
"&check=#{invidious_companion_check_id}" if (invidious_companion)
"&check=#{invidious_companion_check_id}" if invidious_companion
%>
<source src="<%= src_url %>" type='application/dash+xml' label="dash">
<% end %>
@ -54,7 +54,7 @@
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
src_url += "&local=true" if params.local
src_url = invidious_companion.public_url.to_s + src_url +
"&check=#{invidious_companion_check_id}" if (invidious_companion)
"&check=#{invidious_companion_check_id}" if invidious_companion
quality = fmt["quality"]
mimetype = HTML.escape(fmt["mimeType"].as_s)
@ -70,7 +70,7 @@
<% preferred_captions.each do |caption|
api_captions_url = "/api/v1/captions/"
api_captions_url = invidious_companion.public_url.to_s + api_captions_url if (invidious_companion)
api_captions_url = invidious_companion.public_url.to_s + api_captions_url if invidious_companion
api_captions_check_id = "&check=#{invidious_companion_check_id}"
%>
<track kind="captions" src="<%= api_captions_url %><%= video.id %>?label=<%= caption.name %><%= api_captions_check_id %>" label="<%= caption.name %>">
@ -78,7 +78,7 @@
<% captions.each do |caption|
api_captions_url = "/api/v1/captions/"
api_captions_url = invidious_companion.public_url.to_s + api_captions_url if (invidious_companion)
api_captions_url = invidious_companion.public_url.to_s + api_captions_url if invidious_companion
api_captions_check_id = "&check=#{invidious_companion_check_id}"
%>
<track kind="captions" src="<%= api_captions_url %><%= video.id %>?label=<%= caption.name %><%= api_captions_check_id %>" label="<%= caption.name %>">

Some files were not shown because too many files have changed in this diff Show More