mirror of
https://github.com/iv-org/invidious.git
synced 2026-01-28 07:48:31 -06:00
Merge 540b65a332b869dc42b2427ee285554589e3ffd5 into d51a7a44ad91d2fa7d1330970a15a0d8f365f250
This commit is contained in:
commit
5a4ba13228
44
.ameba.yml
44
.ameba.yml
@ -2,23 +2,15 @@
|
|||||||
# Lint
|
# Lint
|
||||||
#
|
#
|
||||||
|
|
||||||
|
Lint/UnusedArgument:
|
||||||
|
Excluded:
|
||||||
|
- "**/*.ecr"
|
||||||
|
|
||||||
# Exclude assigns for ECR files
|
# Exclude assigns for ECR files
|
||||||
Lint/UselessAssign:
|
Lint/UselessAssign:
|
||||||
Excluded:
|
Excluded:
|
||||||
- src/invidious.cr
|
- "**/*.ecr"
|
||||||
- src/invidious/helpers/errors.cr
|
- src/invidious/routes/**/*.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
|
|
||||||
|
|
||||||
Lint/NotNil:
|
Lint/NotNil:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
@ -27,41 +19,17 @@ Lint/SpecFilename:
|
|||||||
Excluded:
|
Excluded:
|
||||||
- spec/parsers_helper.cr
|
- spec/parsers_helper.cr
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Style
|
# 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.
|
# This requires a rewrite of most data structs (and their usage) in Invidious.
|
||||||
Naming/QueryBoolMethods:
|
Naming/QueryBoolMethods:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Naming/AccessorMethodName:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
Naming/BlockParameterName:
|
Naming/BlockParameterName:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Hides TODO comment warnings.
|
|
||||||
#
|
|
||||||
# Call `bin/ameba --only Documentation/DocumentationAdmonition` to
|
|
||||||
# list them
|
|
||||||
Documentation/DocumentationAdmonition:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Metrics
|
# Metrics
|
||||||
#
|
#
|
||||||
|
|||||||
19
.github/workflows/ameba.yml
vendored
Normal file
19
.github/workflows/ameba.yml
vendored
Normal 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
|
||||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -26,7 +26,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
name: "build - crystal: ${{ matrix.crystal }}, stable: ${{ matrix.stable }}"
|
name: "build - crystal: ${{ matrix.crystal }}, stable: ${{ matrix.stable }}"
|
||||||
@ -122,7 +121,6 @@ jobs:
|
|||||||
run: docker compose logs
|
run: docker compose logs
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@ -159,6 +157,3 @@ jobs:
|
|||||||
git diff
|
git diff
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Run Ameba linter
|
|
||||||
run: bin/ameba
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ version: 2.0
|
|||||||
shards:
|
shards:
|
||||||
ameba:
|
ameba:
|
||||||
git: https://github.com/crystal-ameba/ameba.git
|
git: https://github.com/crystal-ameba/ameba.git
|
||||||
version: 1.6.1
|
version: 1.7.0-dev+git.commit.9dbeb92f89d7a668940029bd7b935bef370f26c1
|
||||||
|
|
||||||
athena-negotiation:
|
athena-negotiation:
|
||||||
git: https://github.com/athena-framework/negotiation.git
|
git: https://github.com/athena-framework/negotiation.git
|
||||||
|
|||||||
@ -6,7 +6,7 @@ authors:
|
|||||||
- Contributors!
|
- Contributors!
|
||||||
|
|
||||||
description: |
|
description: |
|
||||||
Invidious is an alternative front-end to YouTube
|
Invidious is an alternative front-end to YouTube
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pg:
|
pg:
|
||||||
@ -34,7 +34,7 @@ development_dependencies:
|
|||||||
version: ~> 0.10.4
|
version: ~> 0.10.4
|
||||||
ameba:
|
ameba:
|
||||||
github: crystal-ameba/ameba
|
github: crystal-ameba/ameba
|
||||||
version: ~> 1.6.1
|
branch: master
|
||||||
|
|
||||||
crystal: ">= 1.10.0, < 2.0.0"
|
crystal: ">= 1.10.0, < 2.0.0"
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ require "spectator"
|
|||||||
require "../../../src/invidious/http_server/static_assets_handler.cr"
|
require "../../../src/invidious/http_server/static_assets_handler.cr"
|
||||||
|
|
||||||
private def get_static_assets_handler
|
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
|
end
|
||||||
|
|
||||||
# Slightly modified version of `handle` function from
|
# 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
|
# Get relative file path to a file within the static_assets_handler folder
|
||||||
macro get_file_path(basename)
|
macro get_file_path(basename)
|
||||||
"spec/http_server/handlers/static_assets_handler/#{ {{basename}} }"
|
"spec/http_server/handlers/static_assets_handler/#{ {{ basename }} }"
|
||||||
end
|
end
|
||||||
|
|
||||||
Spectator.describe StaticAssetsHandler do
|
Spectator.describe StaticAssetsHandler do
|
||||||
@ -125,7 +125,7 @@ Spectator.describe StaticAssetsHandler do
|
|||||||
gzip.gets_to_end
|
gzip.gets_to_end
|
||||||
end
|
end
|
||||||
|
|
||||||
return expect(decompressed)
|
expect(decompressed)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "For full file requests" do
|
it "For full file requests" do
|
||||||
|
|||||||
@ -7,22 +7,22 @@ Spectator.configure do |config|
|
|||||||
end
|
end
|
||||||
|
|
||||||
def csv_sample
|
def csv_sample
|
||||||
return <<-CSV
|
<<-CSV
|
||||||
Kanal-ID,Kanal-URL,Kanaltitel
|
Kanal-ID,Kanal-URL,Kanaltitel
|
||||||
UC0hHW5Y08ggq-9kbrGgWj0A,http://www.youtube.com/channel/UC0hHW5Y08ggq-9kbrGgWj0A,Matias Marolla
|
UC0hHW5Y08ggq-9kbrGgWj0A,http://www.youtube.com/channel/UC0hHW5Y08ggq-9kbrGgWj0A,Matias Marolla
|
||||||
UC0vBXGSyV14uvJ4hECDOl0Q,http://www.youtube.com/channel/UC0vBXGSyV14uvJ4hECDOl0Q,Techquickie
|
UC0vBXGSyV14uvJ4hECDOl0Q,http://www.youtube.com/channel/UC0vBXGSyV14uvJ4hECDOl0Q,Techquickie
|
||||||
UC1sELGmy5jp5fQUugmuYlXQ,http://www.youtube.com/channel/UC1sELGmy5jp5fQUugmuYlXQ,Minecraft
|
UC1sELGmy5jp5fQUugmuYlXQ,http://www.youtube.com/channel/UC1sELGmy5jp5fQUugmuYlXQ,Minecraft
|
||||||
UC9kFnwdCRrX7oTjqKd6-tiQ,http://www.youtube.com/channel/UC9kFnwdCRrX7oTjqKd6-tiQ,LUMOX - Topic
|
UC9kFnwdCRrX7oTjqKd6-tiQ,http://www.youtube.com/channel/UC9kFnwdCRrX7oTjqKd6-tiQ,LUMOX - Topic
|
||||||
UCBa659QWEk1AI4Tg--mrJ2A,http://www.youtube.com/channel/UCBa659QWEk1AI4Tg--mrJ2A,Tom Scott
|
UCBa659QWEk1AI4Tg--mrJ2A,http://www.youtube.com/channel/UCBa659QWEk1AI4Tg--mrJ2A,Tom Scott
|
||||||
UCGu6_XQ64rXPR6nuitMQE_A,http://www.youtube.com/channel/UCGu6_XQ64rXPR6nuitMQE_A,Callcenter Fun
|
UCGu6_XQ64rXPR6nuitMQE_A,http://www.youtube.com/channel/UCGu6_XQ64rXPR6nuitMQE_A,Callcenter Fun
|
||||||
UCGwu0nbY2wSkW8N-cghnLpA,http://www.youtube.com/channel/UCGwu0nbY2wSkW8N-cghnLpA,Jaiden Animations
|
UCGwu0nbY2wSkW8N-cghnLpA,http://www.youtube.com/channel/UCGwu0nbY2wSkW8N-cghnLpA,Jaiden Animations
|
||||||
UCQ0OvZ54pCFZwsKxbltg_tg,http://www.youtube.com/channel/UCQ0OvZ54pCFZwsKxbltg_tg,Methos
|
UCQ0OvZ54pCFZwsKxbltg_tg,http://www.youtube.com/channel/UCQ0OvZ54pCFZwsKxbltg_tg,Methos
|
||||||
UCRE6itj4Jte4manQEu3Y7OA,http://www.youtube.com/channel/UCRE6itj4Jte4manQEu3Y7OA,Chipflake
|
UCRE6itj4Jte4manQEu3Y7OA,http://www.youtube.com/channel/UCRE6itj4Jte4manQEu3Y7OA,Chipflake
|
||||||
UCRLc6zsv_d0OEBO8OOkz-DA,http://www.youtube.com/channel/UCRLc6zsv_d0OEBO8OOkz-DA,Kegy
|
UCRLc6zsv_d0OEBO8OOkz-DA,http://www.youtube.com/channel/UCRLc6zsv_d0OEBO8OOkz-DA,Kegy
|
||||||
UCSl5Uxu2LyaoAoMMGp6oTJA,http://www.youtube.com/channel/UCSl5Uxu2LyaoAoMMGp6oTJA,Atomic Shrimp
|
UCSl5Uxu2LyaoAoMMGp6oTJA,http://www.youtube.com/channel/UCSl5Uxu2LyaoAoMMGp6oTJA,Atomic Shrimp
|
||||||
UCXuqSBlHAE6Xw-yeJA0Tunw,http://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw,Linus Tech Tips
|
UCXuqSBlHAE6Xw-yeJA0Tunw,http://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw,Linus Tech Tips
|
||||||
UCZ5XnGb-3t7jCkXdawN2tkA,http://www.youtube.com/channel/UCZ5XnGb-3t7jCkXdawN2tkA,Discord
|
UCZ5XnGb-3t7jCkXdawN2tkA,http://www.youtube.com/channel/UCZ5XnGb-3t7jCkXdawN2tkA,Discord
|
||||||
CSV
|
CSV
|
||||||
end
|
end
|
||||||
|
|
||||||
Spectator.describe Invidious::User::Import do
|
Spectator.describe Invidious::User::Import do
|
||||||
|
|||||||
@ -26,7 +26,7 @@ def load_mock(file) : Hash(String, JSON::Any)
|
|||||||
file = File.join(__DIR__, "..", "mocks", file + ".json")
|
file = File.join(__DIR__, "..", "mocks", file + ".json")
|
||||||
content = File.read(file)
|
content = File.read(file)
|
||||||
|
|
||||||
return JSON.parse(content).as_h
|
JSON.parse(content).as_h
|
||||||
end
|
end
|
||||||
|
|
||||||
Spectator.configure do |config|
|
Spectator.configure do |config|
|
||||||
|
|||||||
@ -161,7 +161,7 @@ def get_about_info(ucid, locale) : AboutChannel
|
|||||||
|
|
||||||
sub_count = 0
|
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_rows.each do |row|
|
||||||
metadata_part = row.dig?("metadataParts").try &.as_a.find { |i| i.dig?("text", "content").try &.as_s.includes?("subscribers") }
|
metadata_part = row.dig?("metadataParts").try &.as_a.find { |i| i.dig?("text", "content").try &.as_s.includes?("subscribers") }
|
||||||
if !metadata_part.nil?
|
if !metadata_part.nil?
|
||||||
|
|||||||
@ -26,21 +26,21 @@ struct ChannelVideo
|
|||||||
json.object do
|
json.object do
|
||||||
json.field "type", "shortVideo"
|
json.field "type", "shortVideo"
|
||||||
|
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "videoId", self.id
|
json.field "videoId", id
|
||||||
json.field "videoThumbnails" do
|
json.field "videoThumbnails" do
|
||||||
Invidious::JSONify::APIv1.thumbnails(json, self.id)
|
Invidious::JSONify::APIv1.thumbnails(json, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "lengthSeconds", self.length_seconds
|
json.field "lengthSeconds", length_seconds
|
||||||
|
|
||||||
json.field "author", self.author
|
json.field "author", author
|
||||||
json.field "authorId", self.ucid
|
json.field "authorId", ucid
|
||||||
json.field "authorUrl", "/channel/#{self.ucid}"
|
json.field "authorUrl", "/channel/#{ucid}"
|
||||||
json.field "published", self.published.to_unix
|
json.field "published", published.to_unix
|
||||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
|
||||||
|
|
||||||
json.field "viewCount", self.views
|
json.field "viewCount", views
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -51,34 +51,34 @@ struct ChannelVideo
|
|||||||
end
|
end
|
||||||
|
|
||||||
def to_xml(locale, query_params, xml : XML::Builder)
|
def to_xml(locale, query_params, xml : XML::Builder)
|
||||||
query_params["v"] = self.id
|
query_params["v"] = id
|
||||||
|
|
||||||
xml.element("entry") do
|
xml.element("entry") do
|
||||||
xml.element("id") { xml.text "yt:video:#{self.id}" }
|
xml.element("id") { xml.text "yt:video:#{id}" }
|
||||||
xml.element("yt:videoId") { xml.text self.id }
|
xml.element("yt:videoId") { xml.text id }
|
||||||
xml.element("yt:channelId") { xml.text self.ucid }
|
xml.element("yt:channelId") { xml.text ucid }
|
||||||
xml.element("title") { xml.text self.title }
|
xml.element("title") { xml.text title }
|
||||||
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}")
|
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}")
|
||||||
|
|
||||||
xml.element("author") do
|
xml.element("author") do
|
||||||
xml.element("name") { xml.text self.author }
|
xml.element("name") { xml.text author }
|
||||||
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" }
|
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.element("content", type: "xhtml") do
|
xml.element("content", type: "xhtml") do
|
||||||
xml.element("div", xmlns: "http://www.w3.org/1999/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("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
|
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("updated") { xml.text self.updated.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:group") do
|
||||||
xml.element("media:title") { xml.text self.title }
|
xml.element("media:title") { xml.text title }
|
||||||
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg",
|
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{id}/mqdefault.jpg",
|
||||||
width: "320", height: "180")
|
width: "320", height: "180")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -93,7 +93,7 @@ struct ChannelVideo
|
|||||||
def to_tuple
|
def to_tuple
|
||||||
{% begin %}
|
{% begin %}
|
||||||
{
|
{
|
||||||
{{@type.instance_vars.map(&.name).splat}}
|
{{ @type.instance_vars.map(&.name).splat }}
|
||||||
}
|
}
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
@ -107,7 +107,7 @@ class ChannelRedirect < Exception
|
|||||||
end
|
end
|
||||||
|
|
||||||
def get_batch_channels(channels)
|
def get_batch_channels(channels)
|
||||||
finished_channel = Channel(String | Nil).new
|
finished_channel = Channel(String?).new
|
||||||
max_threads = 10
|
max_threads = 10
|
||||||
|
|
||||||
spawn do
|
spawn do
|
||||||
@ -141,7 +141,7 @@ def get_batch_channels(channels)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return final
|
final
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_channel(id) : InvidiousChannel
|
def get_channel(id) : InvidiousChannel
|
||||||
@ -152,7 +152,7 @@ def get_channel(id) : InvidiousChannel
|
|||||||
Invidious::Database::Channels.insert(channel, update_on_conflict: true)
|
Invidious::Database::Channels.insert(channel, update_on_conflict: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
return channel
|
channel
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_channel(ucid, pull_all_videos : Bool)
|
def fetch_channel(ucid, pull_all_videos : Bool)
|
||||||
@ -292,5 +292,5 @@ def fetch_channel(ucid, pull_all_videos : Bool)
|
|||||||
end
|
end
|
||||||
|
|
||||||
channel.updated = Time.utc
|
channel.updated = Time.utc
|
||||||
return channel
|
channel
|
||||||
end
|
end
|
||||||
|
|||||||
@ -21,7 +21,7 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
|
|||||||
items = container.as_a
|
items = container.as_a
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def decode_ucid_from_post_protobuf(params)
|
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| IO::Memory.new(i) }
|
||||||
.try { |i| Protodec::Any.parse(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
|
end
|
||||||
|
|
||||||
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
|
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
|
items << item
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_single_post : Bool = false)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
return response
|
response
|
||||||
end
|
end
|
||||||
|
|
||||||
def produce_channel_community_continuation(ucid, cursor)
|
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| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
return continuation
|
continuation
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_channel_community_cursor(continuation)
|
def extract_channel_community_cursor(continuation)
|
||||||
|
|||||||
@ -24,7 +24,7 @@ def fetch_channel_playlists(ucid, author, continuation, sort_by)
|
|||||||
initial_data = YoutubeAPI.browse(ucid, params: params || "")
|
initial_data = YoutubeAPI.browse(ucid, params: params || "")
|
||||||
end
|
end
|
||||||
|
|
||||||
return extract_items(initial_data, author, ucid)
|
extract_items(initial_data, author, ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_channel_podcasts(ucid, author, continuation)
|
def fetch_channel_podcasts(ucid, author, continuation)
|
||||||
@ -33,7 +33,7 @@ def fetch_channel_podcasts(ucid, author, continuation)
|
|||||||
else
|
else
|
||||||
initial_data = YoutubeAPI.browse(ucid, params: "Eghwb2RjYXN0c_IGBQoDugEA")
|
initial_data = YoutubeAPI.browse(ucid, params: "Eghwb2RjYXN0c_IGBQoDugEA")
|
||||||
end
|
end
|
||||||
return extract_items(initial_data, author, ucid)
|
extract_items(initial_data, author, ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_channel_releases(ucid, author, continuation)
|
def fetch_channel_releases(ucid, author, continuation)
|
||||||
@ -42,7 +42,7 @@ def fetch_channel_releases(ucid, author, continuation)
|
|||||||
else
|
else
|
||||||
initial_data = YoutubeAPI.browse(ucid, params: "EghyZWxlYXNlc_IGBQoDsgEA")
|
initial_data = YoutubeAPI.browse(ucid, params: "EghyZWxlYXNlc_IGBQoDsgEA")
|
||||||
end
|
end
|
||||||
return extract_items(initial_data, author, ucid)
|
extract_items(initial_data, author, ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_channel_courses(ucid, author, continuation)
|
def fetch_channel_courses(ucid, author, continuation)
|
||||||
@ -51,5 +51,5 @@ def fetch_channel_courses(ucid, author, continuation)
|
|||||||
else
|
else
|
||||||
initial_data = YoutubeAPI.browse(ucid, params: "Egdjb3Vyc2Vz8gYFCgPCAQA%3D")
|
initial_data = YoutubeAPI.browse(ucid, params: "Egdjb3Vyc2Vz8gYFCgPCAQA%3D")
|
||||||
end
|
end
|
||||||
return extract_items(initial_data, author, ucid)
|
extract_items(initial_data, author, ucid)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -9,7 +9,7 @@ module Invidious::Channel::Tabs
|
|||||||
# an author name and ucid directly (e.g in RSS feeds).
|
# an author name and ucid directly (e.g in RSS feeds).
|
||||||
# TODO: figure out how to get rid of that
|
# TODO: figure out how to get rid of that
|
||||||
def get_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
def get_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||||
return get_videos(
|
get_videos(
|
||||||
channel.author, channel.ucid,
|
channel.author, channel.ucid,
|
||||||
continuation: continuation, sort_by: sort_by
|
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).
|
# an author name and ucid directly (e.g in RSS feeds).
|
||||||
# TODO: figure out how to get rid of that
|
# TODO: figure out how to get rid of that
|
||||||
def get_videos(channel : InvidiousChannel, *, continuation : String? = nil, sort_by = "newest")
|
def get_videos(channel : InvidiousChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||||
return get_videos(
|
get_videos(
|
||||||
channel.author, channel.id,
|
channel.author, channel.id,
|
||||||
continuation: continuation, sort_by: sort_by
|
continuation: continuation, sort_by: sort_by
|
||||||
)
|
)
|
||||||
@ -29,7 +29,7 @@ module Invidious::Channel::Tabs
|
|||||||
continuation ||= make_initial_videos_ctoken(ucid, sort_by)
|
continuation ||= make_initial_videos_ctoken(ucid, sort_by)
|
||||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
|
||||||
return extract_items(initial_data, author, ucid)
|
extract_items(initial_data, author, ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_60_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
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)
|
continuation ||= make_initial_shorts_ctoken(channel.ucid, sort_by)
|
||||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
|
||||||
return extract_items(initial_data, channel.author, channel.ucid)
|
extract_items(initial_data, channel.author, channel.ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -70,7 +70,7 @@ module Invidious::Channel::Tabs
|
|||||||
continuation ||= make_initial_livestreams_ctoken(channel.ucid, sort_by)
|
continuation ||= make_initial_livestreams_ctoken(channel.ucid, sort_by)
|
||||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
|
||||||
return extract_items(initial_data, channel.author, channel.ucid)
|
extract_items(initial_data, channel.author, channel.ucid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_60_livestreams(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
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)
|
private def sort_options_videos_short(sort_by : String)
|
||||||
case sort_by
|
case sort_by
|
||||||
when "newest" then return 4_i64
|
when "newest" then 4_i64
|
||||||
when "popular" then return 2_i64
|
when "popular" then 2_i64
|
||||||
when "oldest" then return 5_i64
|
when "oldest" then 5_i64
|
||||||
else return 4_i64 # Fallback to "newest"
|
else 4_i64 # Fallback to "newest"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ module Invidious::Channel::Tabs
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return channel_ctoken_wrap(ucid, object)
|
channel_ctoken_wrap(ucid, object)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate the initial "continuation token" to get the first page of the
|
# 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
|
end
|
||||||
|
|
||||||
# Generate the initial "continuation token" to get the first page of the
|
# 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
|
end
|
||||||
|
|
||||||
# The protobuf structure common between videos/shorts/livestreams
|
# The protobuf structure common between videos/shorts/livestreams
|
||||||
@ -187,6 +187,6 @@ module Invidious::Channel::Tabs
|
|||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
return continuation
|
continuation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -37,7 +37,7 @@ def text_to_parsed_content(text : String) : JSON::Any
|
|||||||
nodes << (node)
|
nodes << (node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return JSON.parse({"runs" => nodes}.to_json)
|
JSON.parse({"runs" => nodes}.to_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_content(content : JSON::Any, video_id : String? = "") : String
|
def parse_content(content : JSON::Any, video_id : String? = "") : String
|
||||||
@ -85,5 +85,5 @@ def content_to_comment_html(content, video_id : String? = "")
|
|||||||
text
|
text
|
||||||
end
|
end
|
||||||
|
|
||||||
return html_array.join("").delete('\ufeff')
|
html_array.join("").delete('\ufeff')
|
||||||
end
|
end
|
||||||
|
|||||||
@ -45,7 +45,7 @@ module Invidious::Comments
|
|||||||
html = node
|
html = node
|
||||||
end
|
end
|
||||||
|
|
||||||
return html.to_xml(options: XML::SaveOptions::NO_DECL)
|
html.to_xml(options: XML::SaveOptions::NO_DECL)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fill_links(html, scheme, host)
|
def fill_links(html, scheme, host)
|
||||||
@ -71,6 +71,6 @@ module Invidious::Comments
|
|||||||
html = html.xpath_node(%q(//body/p)).not_nil!
|
html = html.xpath_node(%q(//body/p)).not_nil!
|
||||||
end
|
end
|
||||||
|
|
||||||
return html.to_xml(options: XML::SaveOptions::NO_DECL)
|
html.to_xml(options: XML::SaveOptions::NO_DECL)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -13,7 +13,7 @@ module Invidious::Comments
|
|||||||
|
|
||||||
client_config = YoutubeAPI::ClientConfig.new(region: region)
|
client_config = YoutubeAPI::ClientConfig.new(region: region)
|
||||||
response = YoutubeAPI.next(continuation: ctoken, client_config: client_config)
|
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
|
end
|
||||||
|
|
||||||
def fetch_community_post_comments(ucid, post_id, sort_by = "top")
|
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) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
return initial_data
|
initial_data
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", is_post = false)
|
def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", is_post = false)
|
||||||
@ -320,7 +320,7 @@ module Invidious::Comments
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return response
|
response
|
||||||
end
|
end
|
||||||
|
|
||||||
def produce_continuation(video_id, cursor = "", sort_by = "top")
|
def produce_continuation(video_id, cursor = "", sort_by = "top")
|
||||||
@ -364,6 +364,6 @@ module Invidious::Comments
|
|||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
return continuation
|
continuation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -58,7 +58,7 @@ struct ConfigPreferences
|
|||||||
def to_tuple
|
def to_tuple
|
||||||
{% begin %}
|
{% 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 %}
|
||||||
end
|
end
|
||||||
@ -183,15 +183,15 @@ class Config
|
|||||||
def disabled?(option)
|
def disabled?(option)
|
||||||
case disabled = CONFIG.disable_proxy
|
case disabled = CONFIG.disable_proxy
|
||||||
when Bool
|
when Bool
|
||||||
return disabled
|
disabled
|
||||||
when Array
|
when Array
|
||||||
if disabled.includes? option
|
if disabled.includes? option
|
||||||
return true
|
true
|
||||||
else
|
else
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -212,14 +212,14 @@ class Config
|
|||||||
{% for ivar in Config.instance_vars %}
|
{% for ivar in Config.instance_vars %}
|
||||||
{% env_id = "INVIDIOUS_#{ivar.id.upcase}" %}
|
{% env_id = "INVIDIOUS_#{ivar.id.upcase}" %}
|
||||||
|
|
||||||
if ENV.has_key?({{env_id}})
|
if ENV.has_key?({{ env_id }})
|
||||||
env_value = ENV.fetch({{env_id}})
|
env_value = ENV.fetch({{ env_id }})
|
||||||
success = false
|
success = false
|
||||||
|
|
||||||
# Use YAML converter if specified
|
# Use YAML converter if specified
|
||||||
{% ann = ivar.annotation(::YAML::Field) %}
|
{% ann = ivar.annotation(::YAML::Field) %}
|
||||||
{% if ann && ann[:converter] %}
|
{% 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
|
success = true
|
||||||
|
|
||||||
# Use regular YAML parser otherwise
|
# Use regular YAML parser otherwise
|
||||||
@ -227,10 +227,10 @@ class Config
|
|||||||
{% ivar_types = ivar.type.union? ? ivar.type.union_types : [ivar.type] %}
|
{% ivar_types = ivar.type.union? ? ivar.type.union_types : [ivar.type] %}
|
||||||
# Sort types to avoid parsing nulls and numbers as strings
|
# 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 = 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
|
if !success
|
||||||
begin
|
begin
|
||||||
config.{{ivar.id}} = ivar_type.from_yaml(env_value)
|
config.{{ ivar.id }} = ivar_type.from_yaml(env_value)
|
||||||
success = true
|
success = true
|
||||||
rescue
|
rescue
|
||||||
# nop
|
# nop
|
||||||
@ -241,14 +241,14 @@ class Config
|
|||||||
|
|
||||||
# Exit on fail
|
# Exit on fail
|
||||||
if !success
|
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)
|
exit(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Warn when any config attribute is set to "CHANGE_ME!!"
|
# Warn when any config attribute is set to "CHANGE_ME!!"
|
||||||
if config.{{ivar.id}} == "CHANGE_ME!!"
|
if config.{{ ivar.id }} == "CHANGE_ME!!"
|
||||||
puts "Config: The value of '#{ {{ivar.stringify}} }' needs to be changed!!"
|
puts "Config: The value of '#{ {{ ivar.stringify }} }' needs to be changed!!"
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -318,6 +318,6 @@ class Config
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return config
|
config
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -8,7 +8,7 @@ module Invidious::Database::Annotations
|
|||||||
INSERT INTO annotations
|
INSERT INTO annotations
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT DO NOTHING
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, id, annotations)
|
PG_DB.exec(request, id, annotations)
|
||||||
end
|
end
|
||||||
@ -17,8 +17,8 @@ module Invidious::Database::Annotations
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM annotations
|
SELECT * FROM annotations
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, id, as: Annotation)
|
PG_DB.query_one?(request, id, as: Annotation)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -32,6 +32,7 @@ module Invidious::Database
|
|||||||
def check_enum(enum_name, struct_type = nil)
|
def check_enum(enum_name, struct_type = nil)
|
||||||
return # TODO
|
return # TODO
|
||||||
|
|
||||||
|
# ameba:disable Lint/UnreachableCode
|
||||||
if !PG_DB.query_one?("SELECT true FROM pg_type WHERE typname = $1", enum_name, as: Bool)
|
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}")
|
LOGGER.info("check_enum: CREATE TYPE #{enum_name}")
|
||||||
|
|
||||||
@ -131,6 +132,6 @@ module Invidious::Database
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return column_array
|
column_array
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -16,13 +16,13 @@ module Invidious::Database::Channels
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
INSERT INTO channels
|
INSERT INTO channels
|
||||||
VALUES (#{arg_array(channel_array)})
|
VALUES (#{arg_array(channel_array)})
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
if update_on_conflict
|
if update_on_conflict
|
||||||
request += <<-SQL
|
request += <<-SQL
|
||||||
ON CONFLICT (id) DO UPDATE
|
ON CONFLICT (id) DO UPDATE
|
||||||
SET author = $2, updated = $3
|
SET author = $2, updated = $3
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
PG_DB.exec(request, args: channel_array)
|
PG_DB.exec(request, args: channel_array)
|
||||||
@ -37,7 +37,7 @@ module Invidious::Database::Channels
|
|||||||
UPDATE channels
|
UPDATE channels
|
||||||
SET updated = now(), author = $1, deleted = false
|
SET updated = now(), author = $1, deleted = false
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, author, id)
|
PG_DB.exec(request, author, id)
|
||||||
end
|
end
|
||||||
@ -47,7 +47,7 @@ module Invidious::Database::Channels
|
|||||||
UPDATE channels
|
UPDATE channels
|
||||||
SET subscribed = now()
|
SET subscribed = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, id)
|
PG_DB.exec(request, id)
|
||||||
end
|
end
|
||||||
@ -57,7 +57,7 @@ module Invidious::Database::Channels
|
|||||||
UPDATE channels
|
UPDATE channels
|
||||||
SET updated = now(), deleted = true
|
SET updated = now(), deleted = true
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, id)
|
PG_DB.exec(request, id)
|
||||||
end
|
end
|
||||||
@ -70,9 +70,9 @@ module Invidious::Database::Channels
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM channels
|
SELECT * FROM channels
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, id, as: InvidiousChannel)
|
PG_DB.query_one?(request, id, as: InvidiousChannel)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select(ids : Array(String)) : Array(InvidiousChannel)?
|
def select(ids : Array(String)) : Array(InvidiousChannel)?
|
||||||
@ -81,9 +81,9 @@ module Invidious::Database::Channels
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM channels
|
SELECT * FROM channels
|
||||||
WHERE id = ANY($1)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -112,9 +112,9 @@ module Invidious::Database::ChannelVideos
|
|||||||
SET title = $2, published = $3, updated = $4, ucid = $5,
|
SET title = $2, published = $3, updated = $4, ucid = $5,
|
||||||
author = $6, length_seconds = $7, live_now = $8, #{last_items}
|
author = $6, length_seconds = $7, live_now = $8, #{last_items}
|
||||||
RETURNING (xmax=0) AS was_insert
|
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
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -128,9 +128,9 @@ module Invidious::Database::ChannelVideos
|
|||||||
SELECT * FROM channel_videos
|
SELECT * FROM channel_videos
|
||||||
WHERE id = ANY($1)
|
WHERE id = ANY($1)
|
||||||
ORDER BY published DESC
|
ORDER BY published DESC
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_all(request, ids, as: ChannelVideo)
|
PG_DB.query_all(request, ids, as: ChannelVideo)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_notfications(ucid : String, since : Time) : Array(ChannelVideo)
|
def select_notfications(ucid : String, since : Time) : Array(ChannelVideo)
|
||||||
@ -139,9 +139,9 @@ module Invidious::Database::ChannelVideos
|
|||||||
WHERE ucid = $1 AND published > $2
|
WHERE ucid = $1 AND published > $2
|
||||||
ORDER BY published DESC
|
ORDER BY published DESC
|
||||||
LIMIT 15
|
LIMIT 15
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_all(request, ucid, since, as: ChannelVideo)
|
PG_DB.query_all(request, ucid, since, as: ChannelVideo)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_popular_videos : Array(ChannelVideo)
|
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
|
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)
|
GROUP BY channel ORDER BY COUNT(channel) DESC LIMIT 40)
|
||||||
ORDER BY ucid, published DESC
|
ORDER BY ucid, published DESC
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_all(request, as: ChannelVideo)
|
PG_DB.query_all(request, as: ChannelVideo)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,27 +4,27 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.channels
|
CREATE TABLE IF NOT EXISTS public.channels
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
author text,
|
author text,
|
||||||
updated timestamp with time zone,
|
updated timestamp with time zone,
|
||||||
deleted boolean,
|
deleted boolean,
|
||||||
subscribed timestamp with time zone,
|
subscribed timestamp with time zone,
|
||||||
CONSTRAINT channels_id_key UNIQUE (id)
|
CONSTRAINT channels_id_key UNIQUE (id)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.channels TO current_user;
|
GRANT ALL ON TABLE public.channels TO current_user;
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE INDEX IF NOT EXISTS channels_id_idx
|
CREATE INDEX IF NOT EXISTS channels_id_idx
|
||||||
ON public.channels
|
ON public.channels
|
||||||
USING btree
|
USING btree
|
||||||
(id COLLATE pg_catalog."default");
|
(id COLLATE pg_catalog."default");
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,25 +4,25 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE UNLOGGED TABLE IF NOT EXISTS public.videos
|
CREATE UNLOGGED TABLE IF NOT EXISTS public.videos
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
info text,
|
info text,
|
||||||
updated timestamp with time zone,
|
updated timestamp with time zone,
|
||||||
CONSTRAINT videos_pkey PRIMARY KEY (id)
|
CONSTRAINT videos_pkey PRIMARY KEY (id)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.videos TO current_user;
|
GRANT ALL ON TABLE public.videos TO current_user;
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS id_idx
|
CREATE UNIQUE INDEX IF NOT EXISTS id_idx
|
||||||
ON public.videos
|
ON public.videos
|
||||||
USING btree
|
USING btree
|
||||||
(id COLLATE pg_catalog."default");
|
(id COLLATE pg_catalog."default");
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,32 +4,32 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.channel_videos
|
CREATE TABLE IF NOT EXISTS public.channel_videos
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
title text,
|
title text,
|
||||||
published timestamp with time zone,
|
published timestamp with time zone,
|
||||||
updated timestamp with time zone,
|
updated timestamp with time zone,
|
||||||
ucid text,
|
ucid text,
|
||||||
author text,
|
author text,
|
||||||
length_seconds integer,
|
length_seconds integer,
|
||||||
live_now boolean,
|
live_now boolean,
|
||||||
premiere_timestamp timestamp with time zone,
|
premiere_timestamp timestamp with time zone,
|
||||||
views bigint,
|
views bigint,
|
||||||
CONSTRAINT channel_videos_id_key UNIQUE (id)
|
CONSTRAINT channel_videos_id_key UNIQUE (id)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.channel_videos TO current_user;
|
GRANT ALL ON TABLE public.channel_videos TO current_user;
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx
|
CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx
|
||||||
ON public.channel_videos
|
ON public.channel_videos
|
||||||
USING btree
|
USING btree
|
||||||
(ucid COLLATE pg_catalog."default");
|
(ucid COLLATE pg_catalog."default");
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,31 +4,31 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.users
|
CREATE TABLE IF NOT EXISTS public.users
|
||||||
(
|
(
|
||||||
updated timestamp with time zone,
|
updated timestamp with time zone,
|
||||||
notifications text[],
|
notifications text[],
|
||||||
subscriptions text[],
|
subscriptions text[],
|
||||||
email text NOT NULL,
|
email text NOT NULL,
|
||||||
preferences text,
|
preferences text,
|
||||||
password text,
|
password text,
|
||||||
token text,
|
token text,
|
||||||
watched text[],
|
watched text[],
|
||||||
feed_needs_update boolean,
|
feed_needs_update boolean,
|
||||||
CONSTRAINT users_email_key UNIQUE (email)
|
CONSTRAINT users_email_key UNIQUE (email)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.users TO current_user;
|
GRANT ALL ON TABLE public.users TO current_user;
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx
|
CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx
|
||||||
ON public.users
|
ON public.users
|
||||||
USING btree
|
USING btree
|
||||||
(lower(email) COLLATE pg_catalog."default");
|
(lower(email) COLLATE pg_catalog."default");
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,25 +4,25 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.session_ids
|
CREATE TABLE IF NOT EXISTS public.session_ids
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
email text,
|
email text,
|
||||||
issued timestamp with time zone,
|
issued timestamp with time zone,
|
||||||
CONSTRAINT session_ids_pkey PRIMARY KEY (id)
|
CONSTRAINT session_ids_pkey PRIMARY KEY (id)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.session_ids TO current_user;
|
GRANT ALL ON TABLE public.session_ids TO current_user;
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE INDEX IF NOT EXISTS session_ids_id_idx
|
CREATE INDEX IF NOT EXISTS session_ids_id_idx
|
||||||
ON public.session_ids
|
ON public.session_ids
|
||||||
USING btree
|
USING btree
|
||||||
(id COLLATE pg_catalog."default");
|
(id COLLATE pg_catalog."default");
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,24 +4,24 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.nonces
|
CREATE TABLE IF NOT EXISTS public.nonces
|
||||||
(
|
(
|
||||||
nonce text,
|
nonce text,
|
||||||
expire timestamp with time zone,
|
expire timestamp with time zone,
|
||||||
CONSTRAINT nonces_id_key UNIQUE (nonce)
|
CONSTRAINT nonces_id_key UNIQUE (nonce)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.nonces TO current_user;
|
GRANT ALL ON TABLE public.nonces TO current_user;
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE INDEX IF NOT EXISTS nonces_nonce_idx
|
CREATE INDEX IF NOT EXISTS nonces_nonce_idx
|
||||||
ON public.nonces
|
ON public.nonces
|
||||||
USING btree
|
USING btree
|
||||||
(nonce COLLATE pg_catalog."default");
|
(nonce COLLATE pg_catalog."default");
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,17 +4,17 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.annotations
|
CREATE TABLE IF NOT EXISTS public.annotations
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
annotations xml,
|
annotations xml,
|
||||||
CONSTRAINT annotations_id_key UNIQUE (id)
|
CONSTRAINT annotations_id_key UNIQUE (id)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.annotations TO current_user;
|
GRANT ALL ON TABLE public.annotations TO current_user;
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -5,33 +5,33 @@ module Invidious::Database::Migrations
|
|||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
if !privacy_type_exists?(conn)
|
if !privacy_type_exists?(conn)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TYPE public.privacy AS ENUM
|
CREATE TYPE public.privacy AS ENUM
|
||||||
(
|
(
|
||||||
'Public',
|
'Public',
|
||||||
'Unlisted',
|
'Unlisted',
|
||||||
'Private'
|
'Private'
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.playlists
|
CREATE TABLE IF NOT EXISTS public.playlists
|
||||||
(
|
(
|
||||||
title text,
|
title text,
|
||||||
id text primary key,
|
id text primary key,
|
||||||
author text,
|
author text,
|
||||||
description text,
|
description text,
|
||||||
video_count integer,
|
video_count integer,
|
||||||
created timestamptz,
|
created timestamptz,
|
||||||
updated timestamptz,
|
updated timestamptz,
|
||||||
privacy privacy,
|
privacy privacy,
|
||||||
index int8[]
|
index int8[]
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON public.playlists TO current_user;
|
GRANT ALL ON public.playlists TO current_user;
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
private def privacy_type_exists?(conn : DB::Connection) : Bool
|
private def privacy_type_exists?(conn : DB::Connection) : Bool
|
||||||
@ -42,7 +42,7 @@ module Invidious::Database::Migrations
|
|||||||
WHERE pg_namespace.nspname = 'public'
|
WHERE pg_namespace.nspname = 'public'
|
||||||
AND pg_type.typname = 'privacy'
|
AND pg_type.typname = 'privacy'
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
!conn.query_one?(request, as: Int32).nil?
|
!conn.query_one?(request, as: Int32).nil?
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,24 +4,24 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
CREATE TABLE IF NOT EXISTS public.playlist_videos
|
CREATE TABLE IF NOT EXISTS public.playlist_videos
|
||||||
(
|
(
|
||||||
title text,
|
title text,
|
||||||
id text,
|
id text,
|
||||||
author text,
|
author text,
|
||||||
ucid text,
|
ucid text,
|
||||||
length_seconds integer,
|
length_seconds integer,
|
||||||
published timestamptz,
|
published timestamptz,
|
||||||
plid text references playlists(id),
|
plid text references playlists(id),
|
||||||
index int8,
|
index int8,
|
||||||
live_now boolean,
|
live_now boolean,
|
||||||
PRIMARY KEY (index,plid)
|
PRIMARY KEY (index,plid)
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
GRANT ALL ON TABLE public.playlist_videos TO current_user;
|
GRANT ALL ON TABLE public.playlist_videos TO current_user;
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,8 +4,8 @@ module Invidious::Database::Migrations
|
|||||||
|
|
||||||
def up(conn : DB::Connection)
|
def up(conn : DB::Connection)
|
||||||
conn.exec <<-SQL
|
conn.exec <<-SQL
|
||||||
ALTER TABLE public.videos SET UNLOGGED;
|
ALTER TABLE public.videos SET UNLOGGED;
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -44,6 +44,6 @@ class Invidious::Database::Migrator
|
|||||||
id bigserial PRIMARY KEY,
|
id bigserial PRIMARY KEY,
|
||||||
version bigint NOT NULL
|
version bigint NOT NULL
|
||||||
)
|
)
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -12,7 +12,7 @@ module Invidious::Database::Nonces
|
|||||||
INSERT INTO nonces
|
INSERT INTO nonces
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT DO NOTHING
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, nonce, expire)
|
PG_DB.exec(request, nonce, expire)
|
||||||
end
|
end
|
||||||
@ -21,7 +21,7 @@ module Invidious::Database::Nonces
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM nonces *
|
DELETE FROM nonces *
|
||||||
WHERE expire < now()
|
WHERE expire < now()
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request)
|
PG_DB.exec(request)
|
||||||
end
|
end
|
||||||
@ -35,7 +35,7 @@ module Invidious::Database::Nonces
|
|||||||
UPDATE nonces
|
UPDATE nonces
|
||||||
SET expire = $1
|
SET expire = $1
|
||||||
WHERE nonce = $2
|
WHERE nonce = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, Time.utc(1990, 1, 1), nonce)
|
PG_DB.exec(request, Time.utc(1990, 1, 1), nonce)
|
||||||
end
|
end
|
||||||
@ -48,8 +48,8 @@ module Invidious::Database::Nonces
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM nonces
|
SELECT * FROM nonces
|
||||||
WHERE nonce = $1
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -16,7 +16,7 @@ module Invidious::Database::Playlists
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
INSERT INTO playlists
|
INSERT INTO playlists
|
||||||
VALUES (#{arg_array(playlist_array)})
|
VALUES (#{arg_array(playlist_array)})
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, args: playlist_array)
|
PG_DB.exec(request, args: playlist_array)
|
||||||
end
|
end
|
||||||
@ -27,7 +27,7 @@ module Invidious::Database::Playlists
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM playlists *
|
DELETE FROM playlists *
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, id)
|
PG_DB.exec(request, id)
|
||||||
end
|
end
|
||||||
@ -41,7 +41,7 @@ module Invidious::Database::Playlists
|
|||||||
UPDATE playlists
|
UPDATE playlists
|
||||||
SET title = $1, privacy = $2, description = $3, updated = $4
|
SET title = $1, privacy = $2, description = $3, updated = $4
|
||||||
WHERE id = $5
|
WHERE id = $5
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, title, privacy, description, updated, id)
|
PG_DB.exec(request, title, privacy, description, updated, id)
|
||||||
end
|
end
|
||||||
@ -51,7 +51,7 @@ module Invidious::Database::Playlists
|
|||||||
UPDATE playlists
|
UPDATE playlists
|
||||||
SET description = $1
|
SET description = $1
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, description, id)
|
PG_DB.exec(request, description, id)
|
||||||
end
|
end
|
||||||
@ -61,7 +61,7 @@ module Invidious::Database::Playlists
|
|||||||
UPDATE playlists
|
UPDATE playlists
|
||||||
SET subscribed = now()
|
SET subscribed = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, id)
|
PG_DB.exec(request, id)
|
||||||
end
|
end
|
||||||
@ -73,7 +73,7 @@ module Invidious::Database::Playlists
|
|||||||
video_count = cardinality(index) + 1,
|
video_count = cardinality(index) + 1,
|
||||||
updated = now()
|
updated = now()
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, index, id)
|
PG_DB.exec(request, index, id)
|
||||||
end
|
end
|
||||||
@ -85,7 +85,7 @@ module Invidious::Database::Playlists
|
|||||||
video_count = cardinality(index) - 1,
|
video_count = cardinality(index) - 1,
|
||||||
updated = now()
|
updated = now()
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, index, id)
|
PG_DB.exec(request, index, id)
|
||||||
end
|
end
|
||||||
@ -98,18 +98,18 @@ module Invidious::Database::Playlists
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM playlists
|
SELECT * FROM playlists
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, id, as: InvidiousPlaylist)
|
PG_DB.query_one?(request, id, as: InvidiousPlaylist)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_all(*, author : String) : Array(InvidiousPlaylist)
|
def select_all(*, author : String) : Array(InvidiousPlaylist)
|
||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM playlists
|
SELECT * FROM playlists
|
||||||
WHERE author = $1
|
WHERE author = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_all(request, author, as: InvidiousPlaylist)
|
PG_DB.query_all(request, author, as: InvidiousPlaylist)
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -121,7 +121,7 @@ module Invidious::Database::Playlists
|
|||||||
SELECT * FROM playlists
|
SELECT * FROM playlists
|
||||||
WHERE author = $1 AND id LIKE 'IV%'
|
WHERE author = $1 AND id LIKE 'IV%'
|
||||||
ORDER BY created
|
ORDER BY created
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_all(request, email, as: InvidiousPlaylist)
|
PG_DB.query_all(request, email, as: InvidiousPlaylist)
|
||||||
end
|
end
|
||||||
@ -131,7 +131,7 @@ module Invidious::Database::Playlists
|
|||||||
SELECT * FROM playlists
|
SELECT * FROM playlists
|
||||||
WHERE author = $1 AND id NOT LIKE 'IV%'
|
WHERE author = $1 AND id NOT LIKE 'IV%'
|
||||||
ORDER BY created
|
ORDER BY created
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_all(request, email, as: InvidiousPlaylist)
|
PG_DB.query_all(request, email, as: InvidiousPlaylist)
|
||||||
end
|
end
|
||||||
@ -141,7 +141,7 @@ module Invidious::Database::Playlists
|
|||||||
SELECT id,title FROM playlists
|
SELECT id,title FROM playlists
|
||||||
WHERE author = $1 AND id LIKE 'IV%'
|
WHERE author = $1 AND id LIKE 'IV%'
|
||||||
ORDER BY title
|
ORDER BY title
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_all(request, email, as: {String, String})
|
PG_DB.query_all(request, email, as: {String, String})
|
||||||
end
|
end
|
||||||
@ -155,9 +155,9 @@ module Invidious::Database::Playlists
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT id FROM playlists
|
SELECT id FROM playlists
|
||||||
WHERE id = $1
|
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
|
end
|
||||||
|
|
||||||
# Count how many playlist a user has created.
|
# Count how many playlist a user has created.
|
||||||
@ -165,9 +165,9 @@ module Invidious::Database::Playlists
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT count(*) FROM playlists
|
SELECT count(*) FROM playlists
|
||||||
WHERE author = $1
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ module Invidious::Database::PlaylistVideos
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
INSERT INTO playlist_videos
|
INSERT INTO playlist_videos
|
||||||
VALUES (#{arg_array(video_array)})
|
VALUES (#{arg_array(video_array)})
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, args: video_array)
|
PG_DB.exec(request, args: video_array)
|
||||||
end
|
end
|
||||||
@ -198,7 +198,7 @@ module Invidious::Database::PlaylistVideos
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM playlist_videos *
|
DELETE FROM playlist_videos *
|
||||||
WHERE index = $1
|
WHERE index = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, index)
|
PG_DB.exec(request, index)
|
||||||
end
|
end
|
||||||
@ -207,7 +207,7 @@ module Invidious::Database::PlaylistVideos
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM playlist_videos *
|
DELETE FROM playlist_videos *
|
||||||
WHERE plid = $1
|
WHERE plid = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, plid)
|
PG_DB.exec(request, plid)
|
||||||
end
|
end
|
||||||
@ -223,9 +223,9 @@ module Invidious::Database::PlaylistVideos
|
|||||||
ORDER BY array_position($2, index)
|
ORDER BY array_position($2, index)
|
||||||
LIMIT $3
|
LIMIT $3
|
||||||
OFFSET $4
|
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
|
end
|
||||||
|
|
||||||
def select_index(plid : String, vid : String) : Int64?
|
def select_index(plid : String, vid : String) : Int64?
|
||||||
@ -233,9 +233,9 @@ module Invidious::Database::PlaylistVideos
|
|||||||
SELECT index FROM playlist_videos
|
SELECT index FROM playlist_videos
|
||||||
WHERE plid = $1 AND id = $2
|
WHERE plid = $1 AND id = $2
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, plid, vid, as: Int64)
|
PG_DB.query_one?(request, plid, vid, as: Int64)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_one_id(plid : String, index : VideoIndex) : String?
|
def select_one_id(plid : String, index : VideoIndex) : String?
|
||||||
@ -244,9 +244,9 @@ module Invidious::Database::PlaylistVideos
|
|||||||
WHERE plid = $1
|
WHERE plid = $1
|
||||||
ORDER BY array_position($2, index)
|
ORDER BY array_position($2, index)
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, plid, index, as: String)
|
PG_DB.query_one?(request, plid, index, as: String)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_ids(plid : String, index : VideoIndex, limit = 500) : Array(String)
|
def select_ids(plid : String, index : VideoIndex, limit = 500) : Array(String)
|
||||||
@ -255,8 +255,8 @@ module Invidious::Database::PlaylistVideos
|
|||||||
WHERE plid = $1
|
WHERE plid = $1
|
||||||
ORDER BY array_position($2, index)
|
ORDER BY array_position($2, index)
|
||||||
LIMIT $3
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -11,7 +11,7 @@ module Invidious::Database::SessionIDs
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
INSERT INTO session_ids
|
INSERT INTO session_ids
|
||||||
VALUES ($1, $2, now())
|
VALUES ($1, $2, now())
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
request += " ON CONFLICT (id) DO NOTHING" if handle_conflicts
|
request += " ON CONFLICT (id) DO NOTHING" if handle_conflicts
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ module Invidious::Database::SessionIDs
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM session_ids *
|
DELETE FROM session_ids *
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, sid)
|
PG_DB.exec(request, sid)
|
||||||
end
|
end
|
||||||
@ -35,7 +35,7 @@ module Invidious::Database::SessionIDs
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM session_ids *
|
DELETE FROM session_ids *
|
||||||
WHERE email = $1
|
WHERE email = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, email)
|
PG_DB.exec(request, email)
|
||||||
end
|
end
|
||||||
@ -44,7 +44,7 @@ module Invidious::Database::SessionIDs
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM session_ids *
|
DELETE FROM session_ids *
|
||||||
WHERE id = $1 AND email = $2
|
WHERE id = $1 AND email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, sid, email)
|
PG_DB.exec(request, sid, email)
|
||||||
end
|
end
|
||||||
@ -57,7 +57,7 @@ module Invidious::Database::SessionIDs
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT email FROM session_ids
|
SELECT email FROM session_ids
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_one?(request, sid, as: String)
|
PG_DB.query_one?(request, sid, as: String)
|
||||||
end
|
end
|
||||||
@ -67,7 +67,7 @@ module Invidious::Database::SessionIDs
|
|||||||
SELECT id, issued FROM session_ids
|
SELECT id, issued FROM session_ids
|
||||||
WHERE email = $1
|
WHERE email = $1
|
||||||
ORDER BY issued DESC
|
ORDER BY issued DESC
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_all(request, email, as: {session: String, issued: Time})
|
PG_DB.query_all(request, email, as: {session: String, issued: Time})
|
||||||
end
|
end
|
||||||
|
|||||||
@ -10,7 +10,7 @@ module Invidious::Database::Statistics
|
|||||||
def count_users_total : Int64
|
def count_users_total : Int64
|
||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT count(*) FROM users
|
SELECT count(*) FROM users
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_one(request, as: Int64)
|
PG_DB.query_one(request, as: Int64)
|
||||||
end
|
end
|
||||||
@ -19,7 +19,7 @@ module Invidious::Database::Statistics
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT count(*) FROM users
|
SELECT count(*) FROM users
|
||||||
WHERE CURRENT_TIMESTAMP - updated < '6 months'
|
WHERE CURRENT_TIMESTAMP - updated < '6 months'
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_one(request, as: Int64)
|
PG_DB.query_one(request, as: Int64)
|
||||||
end
|
end
|
||||||
@ -28,7 +28,7 @@ module Invidious::Database::Statistics
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT count(*) FROM users
|
SELECT count(*) FROM users
|
||||||
WHERE CURRENT_TIMESTAMP - updated < '1 month'
|
WHERE CURRENT_TIMESTAMP - updated < '1 month'
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_one(request, as: Int64)
|
PG_DB.query_one(request, as: Int64)
|
||||||
end
|
end
|
||||||
@ -42,7 +42,7 @@ module Invidious::Database::Statistics
|
|||||||
SELECT updated FROM channels
|
SELECT updated FROM channels
|
||||||
ORDER BY updated DESC
|
ORDER BY updated DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.query_one?(request, as: Time)
|
PG_DB.query_one?(request, as: Time)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -14,13 +14,13 @@ module Invidious::Database::Users
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
INSERT INTO users
|
INSERT INTO users
|
||||||
VALUES (#{arg_array(user_array)})
|
VALUES (#{arg_array(user_array)})
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
if update_on_conflict
|
if update_on_conflict
|
||||||
request += <<-SQL
|
request += <<-SQL
|
||||||
ON CONFLICT (email) DO UPDATE
|
ON CONFLICT (email) DO UPDATE
|
||||||
SET updated = $1, subscriptions = $3
|
SET updated = $1, subscriptions = $3
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
PG_DB.exec(request, args: user_array)
|
PG_DB.exec(request, args: user_array)
|
||||||
@ -30,7 +30,7 @@ module Invidious::Database::Users
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM users *
|
DELETE FROM users *
|
||||||
WHERE email = $1
|
WHERE email = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, user.email)
|
PG_DB.exec(request, user.email)
|
||||||
end
|
end
|
||||||
@ -44,7 +44,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET watched = $1
|
SET watched = $1
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, user.watched, user.email)
|
PG_DB.exec(request, user.watched, user.email)
|
||||||
end
|
end
|
||||||
@ -54,7 +54,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET watched = array_append(array_remove(watched, $1), $1)
|
SET watched = array_append(array_remove(watched, $1), $1)
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, vid, user.email)
|
PG_DB.exec(request, vid, user.email)
|
||||||
end
|
end
|
||||||
@ -64,7 +64,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET watched = array_remove(watched, $1)
|
SET watched = array_remove(watched, $1)
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, vid, user.email)
|
PG_DB.exec(request, vid, user.email)
|
||||||
end
|
end
|
||||||
@ -74,7 +74,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET watched = '{}'
|
SET watched = '{}'
|
||||||
WHERE email = $1
|
WHERE email = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, user.email)
|
PG_DB.exec(request, user.email)
|
||||||
end
|
end
|
||||||
@ -88,7 +88,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET feed_needs_update = true, subscriptions = $1
|
SET feed_needs_update = true, subscriptions = $1
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, user.subscriptions, user.email)
|
PG_DB.exec(request, user.subscriptions, user.email)
|
||||||
end
|
end
|
||||||
@ -99,7 +99,7 @@ module Invidious::Database::Users
|
|||||||
SET feed_needs_update = true,
|
SET feed_needs_update = true,
|
||||||
subscriptions = array_append(subscriptions,$1)
|
subscriptions = array_append(subscriptions,$1)
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, ucid, user.email)
|
PG_DB.exec(request, ucid, user.email)
|
||||||
end
|
end
|
||||||
@ -110,7 +110,7 @@ module Invidious::Database::Users
|
|||||||
SET feed_needs_update = true,
|
SET feed_needs_update = true,
|
||||||
subscriptions = array_remove(subscriptions, $1)
|
subscriptions = array_remove(subscriptions, $1)
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, ucid, user.email)
|
PG_DB.exec(request, ucid, user.email)
|
||||||
end
|
end
|
||||||
@ -125,7 +125,7 @@ module Invidious::Database::Users
|
|||||||
SET notifications = array_cat(notifications, $1),
|
SET notifications = array_cat(notifications, $1),
|
||||||
feed_needs_update = true
|
feed_needs_update = true
|
||||||
WHERE $2 = ANY(subscriptions)
|
WHERE $2 = ANY(subscriptions)
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, video_ids, channel_id)
|
PG_DB.exec(request, video_ids, channel_id)
|
||||||
end
|
end
|
||||||
@ -135,7 +135,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET notifications = array_remove(notifications, $1)
|
SET notifications = array_remove(notifications, $1)
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, vid, user.email)
|
PG_DB.exec(request, vid, user.email)
|
||||||
end
|
end
|
||||||
@ -145,7 +145,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET notifications = '{}', updated = now()
|
SET notifications = '{}', updated = now()
|
||||||
WHERE email = $1
|
WHERE email = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, user.email)
|
PG_DB.exec(request, user.email)
|
||||||
end
|
end
|
||||||
@ -159,7 +159,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET feed_needs_update = true
|
SET feed_needs_update = true
|
||||||
WHERE $1 = ANY(subscriptions)
|
WHERE $1 = ANY(subscriptions)
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, channel_id)
|
PG_DB.exec(request, channel_id)
|
||||||
end
|
end
|
||||||
@ -169,7 +169,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET preferences = $1
|
SET preferences = $1
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, user.preferences.to_json, user.email)
|
PG_DB.exec(request, user.preferences.to_json, user.email)
|
||||||
end
|
end
|
||||||
@ -179,7 +179,7 @@ module Invidious::Database::Users
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET password = $1
|
SET password = $1
|
||||||
WHERE email = $2
|
WHERE email = $2
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, pass, user.email)
|
PG_DB.exec(request, pass, user.email)
|
||||||
end
|
end
|
||||||
@ -192,9 +192,9 @@ module Invidious::Database::Users
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM users
|
SELECT * FROM users
|
||||||
WHERE email = $1
|
WHERE email = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, email, as: User)
|
PG_DB.query_one?(request, email, as: User)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Same as select, but can raise an exception
|
# Same as select, but can raise an exception
|
||||||
@ -202,18 +202,18 @@ module Invidious::Database::Users
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM users
|
SELECT * FROM users
|
||||||
WHERE email = $1
|
WHERE email = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one(request, email, as: User)
|
PG_DB.query_one(request, email, as: User)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select(*, token : String) : User?
|
def select(*, token : String) : User?
|
||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM users
|
SELECT * FROM users
|
||||||
WHERE token = $1
|
WHERE token = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, token, as: User)
|
PG_DB.query_one?(request, token, as: User)
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_notifications(user : User) : Array(String)
|
def select_notifications(user : User) : Array(String)
|
||||||
@ -221,8 +221,8 @@ module Invidious::Database::Users
|
|||||||
SELECT notifications
|
SELECT notifications
|
||||||
FROM users
|
FROM users
|
||||||
WHERE email = $1
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -8,7 +8,7 @@ module Invidious::Database::Videos
|
|||||||
INSERT INTO videos
|
INSERT INTO videos
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
ON CONFLICT (id) DO NOTHING
|
ON CONFLICT (id) DO NOTHING
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
|
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
|
||||||
end
|
end
|
||||||
@ -17,7 +17,7 @@ module Invidious::Database::Videos
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM videos *
|
DELETE FROM videos *
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, id)
|
PG_DB.exec(request, id)
|
||||||
end
|
end
|
||||||
@ -26,7 +26,7 @@ module Invidious::Database::Videos
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
DELETE FROM videos *
|
DELETE FROM videos *
|
||||||
WHERE updated < (now() - interval '6 hours')
|
WHERE updated < (now() - interval '6 hours')
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request)
|
PG_DB.exec(request)
|
||||||
end
|
end
|
||||||
@ -36,7 +36,7 @@ module Invidious::Database::Videos
|
|||||||
UPDATE videos
|
UPDATE videos
|
||||||
SET (id, info, updated) = ($1, $2, $3)
|
SET (id, info, updated) = ($1, $2, $3)
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
|
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
|
||||||
end
|
end
|
||||||
@ -45,8 +45,8 @@ module Invidious::Database::Videos
|
|||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM videos
|
SELECT * FROM videos
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
return PG_DB.query_one?(request, id, as: Video)
|
PG_DB.query_one?(request, id, as: Video)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class BrokenTubeException < Exception
|
|||||||
end
|
end
|
||||||
|
|
||||||
def message
|
def message
|
||||||
return "Missing JSON element \"#{@element}\""
|
"Missing JSON element \"#{@element}\""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -14,12 +14,12 @@ module Invidious::Frontend::ChannelPage
|
|||||||
end
|
end
|
||||||
|
|
||||||
def generate_tabs_links(locale : String, channel : AboutChannel, selected_tab : TabsAvailable)
|
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}"
|
base_url = "/channel/#{channel.ucid}"
|
||||||
|
|
||||||
TabsAvailable.each do |tab|
|
TabsAvailable.each do |tab|
|
||||||
# Ignore playlists, as it is not supported for auto-generated channels yet
|
# 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
|
tab_name = tab.to_s.downcase
|
||||||
|
|
||||||
|
|||||||
@ -11,38 +11,38 @@ module Invidious::Frontend::Comments
|
|||||||
replies_html = ""
|
replies_html = ""
|
||||||
if child.replies.is_a?(RedditThing)
|
if child.replies.is_a?(RedditThing)
|
||||||
replies = child.replies.as(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
|
end
|
||||||
|
|
||||||
if child.depth > 0
|
if child.depth > 0
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-24">
|
<div class="pure-u-1-24">
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-23-24">
|
<div class="pure-u-23-24">
|
||||||
END_HTML
|
HTML
|
||||||
else
|
else
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
END_HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" data-onclick="toggle_parent">[ − ]</a>
|
<a href="javascript:void(0)" data-onclick="toggle_parent">[ − ]</a>
|
||||||
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
|
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
|
||||||
#{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)}
|
#{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>
|
<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>
|
<a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
#{body_html}
|
#{body_html}
|
||||||
#{replies_html}
|
#{replies_html}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
HTML
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -12,17 +12,17 @@ module Invidious::Frontend::Comments
|
|||||||
NumberFormatting::Separator
|
NumberFormatting::Separator
|
||||||
)
|
)
|
||||||
|
|
||||||
replies_html = <<-END_HTML
|
replies_html = <<-HTML
|
||||||
<div id="replies" class="pure-g">
|
<div id="replies" class="pure-g">
|
||||||
<div class="pure-u-1-24"></div>
|
<div class="pure-u-1-24"></div>
|
||||||
<div class="pure-u-23-24">
|
<div class="pure-u-23-24">
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
||||||
data-onclick="get_youtube_replies" data-load-replies>#{replies_count_text}</a>
|
data-onclick="get_youtube_replies" data-load-replies>#{replies_count_text}</a>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
HTML
|
||||||
END_HTML
|
|
||||||
elsif comments["authorId"]? && !comments["singlePost"]?
|
elsif comments["authorId"]? && !comments["singlePost"]?
|
||||||
# for posts we should display a link to the post
|
# for posts we should display a link to the post
|
||||||
replies_count_text = translate_count(locale,
|
replies_count_text = translate_count(locale,
|
||||||
@ -31,16 +31,16 @@ module Invidious::Frontend::Comments
|
|||||||
NumberFormatting::Separator
|
NumberFormatting::Separator
|
||||||
)
|
)
|
||||||
|
|
||||||
replies_html = <<-END_HTML
|
replies_html = <<-HTML
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-24"></div>
|
<div class="pure-u-1-24"></div>
|
||||||
<div class="pure-u-23-24">
|
<div class="pure-u-23-24">
|
||||||
<p>
|
<p>
|
||||||
<a href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
|
<a href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
HTML
|
||||||
END_HTML
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if !thin_mode
|
if !thin_mode
|
||||||
@ -65,19 +65,19 @@ module Invidious::Frontend::Comments
|
|||||||
str << %(width="16" height="16" />)
|
str << %(width="16" height="16" />)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<div class="pure-g" style="width:100%">
|
<div class="pure-g" style="width:100%">
|
||||||
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
|
<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="" />
|
<img loading="lazy" style="margin-right:1em;margin-top:1em;width:90%" src="#{author_thumbnail}" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-20-24 pure-u-md-22-24">
|
<div class="pure-u-20-24 pure-u-md-22-24">
|
||||||
<p>
|
<p>
|
||||||
<b>
|
<b>
|
||||||
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
|
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
|
||||||
</b>
|
</b>
|
||||||
#{sponsor_icon}
|
#{sponsor_icon}
|
||||||
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
||||||
END_HTML
|
HTML
|
||||||
|
|
||||||
if child["attachment"]?
|
if child["attachment"]?
|
||||||
attachment = child["attachment"]
|
attachment = child["attachment"]
|
||||||
@ -86,82 +86,81 @@ module Invidious::Frontend::Comments
|
|||||||
when "image"
|
when "image"
|
||||||
attachment = attachment["imageThumbnails"][1]
|
attachment = attachment["imageThumbnails"][1]
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1 pure-u-md-1-2">
|
<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="" />
|
<img loading="lazy" style="width:100%" src="/ggpht#{URI.parse(attachment["url"].as_s).request_target}" alt="" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
HTML
|
||||||
END_HTML
|
|
||||||
when "video"
|
when "video"
|
||||||
if attachment["error"]?
|
if attachment["error"]?
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<div class="pure-g video-iframe-wrapper">
|
<div class="pure-g video-iframe-wrapper">
|
||||||
<p>#{attachment["error"]}</p>
|
<p>#{attachment["error"]}</p>
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
HTML
|
||||||
else
|
else
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<div class="pure-g video-iframe-wrapper">
|
<div class="pure-g video-iframe-wrapper">
|
||||||
<iframe class="video-iframe" src='/embed/#{attachment["videoId"]?}?autoplay=0'></iframe>
|
<iframe class="video-iframe" src='/embed/#{attachment["videoId"]?}?autoplay=0'></iframe>
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
HTML
|
||||||
end
|
end
|
||||||
when "multiImage"
|
when "multiImage"
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<section class="carousel">
|
<section class="carousel">
|
||||||
<a class="skip-link" href="#skip-#{child["commentId"]}">#{translate(locale, "carousel_skip")}</a>
|
<a class="skip-link" href="#skip-#{child["commentId"]}">#{translate(locale, "carousel_skip")}</a>
|
||||||
<div class="slides">
|
<div class="slides">
|
||||||
END_HTML
|
HTML
|
||||||
image_array = attachment["images"].as_a
|
image_array = attachment["images"].as_a
|
||||||
|
|
||||||
image_array.each_index do |i|
|
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">
|
<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="" />
|
<img loading="lazy" src="/ggpht#{URI.parse(image_array[i][1]["url"].as_s).request_target}" alt="" />
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
</div>
|
</div>
|
||||||
<div class="carousel__nav">
|
<div class="carousel__nav">
|
||||||
END_HTML
|
HTML
|
||||||
attachment["images"].as_a.each_index do |i|
|
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>
|
<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
|
end
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
</div>
|
</div>
|
||||||
<div id="skip-#{child["commentId"]}"></div>
|
<div id="skip-#{child["commentId"]}"></div>
|
||||||
</section>
|
</section>
|
||||||
END_HTML
|
HTML
|
||||||
else nil # Ignore
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<p>
|
<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>
|
<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
|
||||||
|
|
||||||
if comments["videoId"]?
|
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>
|
<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"]?
|
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>
|
<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
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
||||||
END_HTML
|
HTML
|
||||||
|
|
||||||
if child["creatorHeart"]?
|
if child["creatorHeart"]?
|
||||||
if !thin_mode
|
if !thin_mode
|
||||||
@ -170,7 +169,7 @@ module Invidious::Frontend::Comments
|
|||||||
creator_thumbnail = ""
|
creator_thumbnail = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
|
|
||||||
<span class="creator-heart-container" title="#{translate(locale, "`x` marked it with a ❤", child["creatorHeart"]["creatorName"].as_s)}">
|
<span class="creator-heart-container" title="#{translate(locale, "`x` marked it with a ❤", child["creatorHeart"]["creatorName"].as_s)}">
|
||||||
<span class="creator-heart">
|
<span class="creator-heart">
|
||||||
@ -180,28 +179,28 @@ module Invidious::Frontend::Comments
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
END_HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
</p>
|
</p>
|
||||||
#{replies_html}
|
#{replies_html}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
HTML
|
||||||
END_HTML
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if comments["continuation"]?
|
if comments["continuation"]?
|
||||||
html << <<-END_HTML
|
html << <<-HTML
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
|
<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>
|
data-onclick="get_youtube_replies" data-load-more #{"data-load-replies" if is_replies}>#{translate(locale, "Load more")}</a>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
HTML
|
||||||
END_HTML
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -6,9 +6,9 @@ module Invidious::Frontend::Misc
|
|||||||
|
|
||||||
if preferences.automatic_instance_redirect
|
if preferences.automatic_instance_redirect
|
||||||
current_page = env.get?("current_page").as(String)
|
current_page = env.get?("current_page").as(String)
|
||||||
return "/redirect?referer=#{current_page}"
|
"/redirect?referer=#{current_page}"
|
||||||
else
|
else
|
||||||
return "https://redirect.invidious.io#{env.request.resource}"
|
"https://redirect.invidious.io#{env.request.resource}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -60,7 +60,7 @@ module Invidious::Frontend::Pagination
|
|||||||
end
|
end
|
||||||
|
|
||||||
def nav_numeric(locale : String?, *, base_url : String | URI, current_page : Int, show_next : Bool = true)
|
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="h-box">\n)
|
||||||
str << %(<div class="page-nav-container flexible">\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}
|
params_prev = URI::Params{"page" => (current_page - 1).to_s}
|
||||||
url_prev = HttpServer::Utils.add_params_to_url(base_url, params_prev)
|
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
|
end
|
||||||
|
|
||||||
str << %(</div>\n)
|
str << %(</div>\n)
|
||||||
@ -80,7 +80,7 @@ module Invidious::Frontend::Pagination
|
|||||||
params_next = URI::Params{"page" => (current_page + 1).to_s}
|
params_next = URI::Params{"page" => (current_page + 1).to_s}
|
||||||
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
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
|
end
|
||||||
|
|
||||||
str << %(</div>\n)
|
str << %(</div>\n)
|
||||||
@ -91,7 +91,7 @@ module Invidious::Frontend::Pagination
|
|||||||
end
|
end
|
||||||
|
|
||||||
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?, first_page : Bool, params : URI::Params)
|
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="h-box">\n)
|
||||||
str << %(<div class="page-nav-container flexible">\n)
|
str << %(<div class="page-nav-container flexible">\n)
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ module Invidious::Frontend::Pagination
|
|||||||
params["continuation"] = ctoken
|
params["continuation"] = ctoken
|
||||||
url_next = HttpServer::Utils.add_params_to_url(base_url, params)
|
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
|
end
|
||||||
|
|
||||||
str << %(</div>\n)
|
str << %(</div>\n)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ module Invidious::Frontend::SearchFilters
|
|||||||
|
|
||||||
# Generate the search filters collapsable widget.
|
# Generate the search filters collapsable widget.
|
||||||
def generate(filters : Search::Filters, query : String, page : Int, locale : String) : String
|
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 << "<div id='filters'>\n"
|
||||||
str << "\t<details id='filters-collapse'>"
|
str << "\t<details id='filters-collapse'>"
|
||||||
str << "\t\t<summary>" << translate(locale, "search_filters_title") << "</summary>\n"
|
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<div class=\"filter-column\"><fieldset>\n"
|
||||||
|
|
||||||
str << "\t\t\t\t\t<legend><div class=\"filter-name underlined\">"
|
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 << "</div></legend>\n"
|
||||||
|
|
||||||
str << "\t\t\t\t\t<div class=\"filter-options\">\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\t</div>"
|
||||||
|
|
||||||
str << "\t\t\t\t</fieldset></div>\n"
|
str << "\t\t\t\t</fieldset></div>\n"
|
||||||
@ -57,12 +57,12 @@ module Invidious::Frontend::SearchFilters
|
|||||||
{% date = value.underscore %}
|
{% date = value.underscore %}
|
||||||
|
|
||||||
str << "\t\t\t\t\t\t<div>"
|
str << "\t\t\t\t\t\t<div>"
|
||||||
str << "<input type='radio' name='date' id='filter-date-{{date}}' value='{{date}}'"
|
str << "<input type='radio' name='date' id='filter-date-{{ date }}' value='{{ date }}'"
|
||||||
str << " checked" if value.{{date}}?
|
str << " checked" if value.{{ date }}?
|
||||||
str << '>'
|
str << '>'
|
||||||
|
|
||||||
str << "<label for='filter-date-{{date}}'>"
|
str << "<label for='filter-date-{{ date }}'>"
|
||||||
str << translate(locale, "search_filters_date_option_{{date}}")
|
str << translate(locale, "search_filters_date_option_{{ date }}")
|
||||||
str << "</label></div>\n"
|
str << "</label></div>\n"
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
@ -73,12 +73,12 @@ module Invidious::Frontend::SearchFilters
|
|||||||
{% type = value.underscore %}
|
{% type = value.underscore %}
|
||||||
|
|
||||||
str << "\t\t\t\t\t\t<div>"
|
str << "\t\t\t\t\t\t<div>"
|
||||||
str << "<input type='radio' name='type' id='filter-type-{{type}}' value='{{type}}'"
|
str << "<input type='radio' name='type' id='filter-type-{{ type }}' value='{{ type }}'"
|
||||||
str << " checked" if value.{{type}}?
|
str << " checked" if value.{{ type }}?
|
||||||
str << '>'
|
str << '>'
|
||||||
|
|
||||||
str << "<label for='filter-type-{{type}}'>"
|
str << "<label for='filter-type-{{ type }}'>"
|
||||||
str << translate(locale, "search_filters_type_option_{{type}}")
|
str << translate(locale, "search_filters_type_option_{{ type }}")
|
||||||
str << "</label></div>\n"
|
str << "</label></div>\n"
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
@ -89,12 +89,12 @@ module Invidious::Frontend::SearchFilters
|
|||||||
{% duration = value.underscore %}
|
{% duration = value.underscore %}
|
||||||
|
|
||||||
str << "\t\t\t\t\t\t<div>"
|
str << "\t\t\t\t\t\t<div>"
|
||||||
str << "<input type='radio' name='duration' id='filter-duration-{{duration}}' value='{{duration}}'"
|
str << "<input type='radio' name='duration' id='filter-duration-{{ duration }}' value='{{ duration }}'"
|
||||||
str << " checked" if value.{{duration}}?
|
str << " checked" if value.{{ duration }}?
|
||||||
str << '>'
|
str << '>'
|
||||||
|
|
||||||
str << "<label for='filter-duration-{{duration}}'>"
|
str << "<label for='filter-duration-{{ duration }}'>"
|
||||||
str << translate(locale, "search_filters_duration_option_{{duration}}")
|
str << translate(locale, "search_filters_duration_option_{{ duration }}")
|
||||||
str << "</label></div>\n"
|
str << "</label></div>\n"
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
@ -106,12 +106,12 @@ module Invidious::Frontend::SearchFilters
|
|||||||
{% feature = value.underscore %}
|
{% feature = value.underscore %}
|
||||||
|
|
||||||
str << "\t\t\t\t\t\t<div>"
|
str << "\t\t\t\t\t\t<div>"
|
||||||
str << "<input type='checkbox' name='features' id='filter-feature-{{feature}}' value='{{feature}}'"
|
str << "<input type='checkbox' name='features' id='filter-feature-{{ feature }}' value='{{ feature }}'"
|
||||||
str << " checked" if value.{{feature}}?
|
str << " checked" if value.{{ feature }}?
|
||||||
str << '>'
|
str << '>'
|
||||||
|
|
||||||
str << "<label for='filter-feature-{{feature}}'>"
|
str << "<label for='filter-feature-{{ feature }}'>"
|
||||||
str << translate(locale, "search_filters_features_option_{{feature}}")
|
str << translate(locale, "search_filters_features_option_{{ feature }}")
|
||||||
str << "</label></div>\n"
|
str << "</label></div>\n"
|
||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
@ -123,12 +123,12 @@ module Invidious::Frontend::SearchFilters
|
|||||||
{% sort = value.underscore %}
|
{% sort = value.underscore %}
|
||||||
|
|
||||||
str << "\t\t\t\t\t\t<div>"
|
str << "\t\t\t\t\t\t<div>"
|
||||||
str << "<input type='radio' name='sort' id='filter-sort-{{sort}}' value='{{sort}}'"
|
str << "<input type='radio' name='sort' id='filter-sort-{{ sort }}' value='{{ sort }}'"
|
||||||
str << " checked" if value.{{sort}}?
|
str << " checked" if value.{{ sort }}?
|
||||||
str << '>'
|
str << '>'
|
||||||
|
|
||||||
str << "<label for='filter-sort-{{sort}}'>"
|
str << "<label for='filter-sort-{{ sort }}'>"
|
||||||
str << translate(locale, "search_filters_sort_option_{{sort}}")
|
str << translate(locale, "search_filters_sort_option_{{ sort }}")
|
||||||
str << "</label></div>\n"
|
str << "</label></div>\n"
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -28,12 +28,12 @@ module Invidious::Frontend::WatchPage
|
|||||||
end
|
end
|
||||||
|
|
||||||
url = "/download"
|
url = "/download"
|
||||||
if (CONFIG.invidious_companion.present?)
|
if CONFIG.invidious_companion.present?
|
||||||
invidious_companion = CONFIG.invidious_companion.sample
|
invidious_companion = CONFIG.invidious_companion.sample
|
||||||
url = "#{invidious_companion.public_url}/download?check=#{invidious_companion_encrypt(video.id)}"
|
url = "#{invidious_companion.public_url}/download?check=#{invidious_companion_encrypt(video.id)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
return String.build(4000) do |str|
|
String.build(4000) do |str|
|
||||||
str << "<form"
|
str << "<form"
|
||||||
str << " class=\"pure-form pure-form-stacked\""
|
str << " class=\"pure-form pure-form-stacked\""
|
||||||
str << " action='#{url}'"
|
str << " action='#{url}'"
|
||||||
|
|||||||
@ -9,7 +9,7 @@ module Invidious::Hashtag
|
|||||||
response = YoutubeAPI.browse(continuation: ctoken, client_config: client_config)
|
response = YoutubeAPI.browse(continuation: ctoken, client_config: client_config)
|
||||||
|
|
||||||
items, _ = extract_items(response)
|
items, _ = extract_items(response)
|
||||||
return items
|
items
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_continuation(hashtag : String, cursor : Int)
|
def generate_continuation(hashtag : String, cursor : Int)
|
||||||
@ -37,6 +37,6 @@ module Invidious::Hashtag
|
|||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
return continuation
|
continuation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
macro error_template(*args)
|
macro error_template(*args)
|
||||||
error_template_helper(env, {{args.splat}})
|
error_template_helper(env, {{ args.splat }})
|
||||||
end
|
end
|
||||||
|
|
||||||
def github_details(summary : String, content : String)
|
def github_details(summary : String, content : String)
|
||||||
@ -15,19 +15,19 @@ def github_details(summary : String, content : String)
|
|||||||
details += %(\n```)
|
details += %(\n```)
|
||||||
details += %(\n</p>)
|
details += %(\n</p>)
|
||||||
details += %(\n</details>)
|
details += %(\n</details>)
|
||||||
return HTML.escape(details)
|
HTML.escape(details)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_issue_template(env : HTTP::Server::Context, exception : Exception) : Tuple(String, String)
|
def get_issue_template(env : HTTP::Server::Context, exception : Exception) : Tuple(String, String)
|
||||||
issue_title = "#{exception.message} (#{exception.class})"
|
issue_title = "#{exception.message} (#{exception.class})"
|
||||||
|
|
||||||
issue_template = <<-TEXT
|
issue_template = <<-TEXT
|
||||||
Title: `#{HTML.escape(issue_title)}`
|
Title: `#{HTML.escape(issue_title)}`
|
||||||
Date: `#{Time::Format::ISO_8601_DATE_TIME.format(Time.utc)}`
|
Date: `#{Time::Format::ISO_8601_DATE_TIME.format(Time.utc)}`
|
||||||
Route: `#{HTML.escape(env.request.resource)}`
|
Route: `#{HTML.escape(env.request.resource)}`
|
||||||
Version: `#{SOFTWARE["version"]} @ #{SOFTWARE["branch"]}`
|
Version: `#{SOFTWARE["version"]} @ #{SOFTWARE["branch"]}`
|
||||||
|
|
||||||
TEXT
|
TEXT
|
||||||
|
|
||||||
issue_template += github_details("Backtrace", exception.inspect_with_backtrace)
|
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 += "?labels=bug&template=bug_report.md&title="
|
||||||
url_new_issue += URI.encode_www_form("[Bug] " + issue_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">
|
<div class="error_message">
|
||||||
<h2>#{translate(locale, "crash_page_you_found_a_bug")}</h2>
|
<h2>#{translate(locale, "crash_page_you_found_a_bug")}</h2>
|
||||||
<br/><br/>
|
<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 -->
|
<!-- TODO: Add a "copy to clipboard" button -->
|
||||||
<pre class="error-issue-template">#{issue_template}</pre>
|
<pre class="error-issue-template">#{issue_template}</pre>
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
HTML
|
||||||
|
|
||||||
# Don't show the usual "next steps" widget. The same options are
|
# Don't show the usual "next steps" widget. The same options are
|
||||||
# proposed above the error message, just worded differently.
|
# proposed above the error message, just worded differently.
|
||||||
|
# ameba:disable Lint/UselessAssign
|
||||||
next_steps = ""
|
next_steps = ""
|
||||||
|
|
||||||
return templated "error"
|
templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_template_helper(env : HTTP::Server::Context, status_code : Int32, message : String)
|
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
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
|
||||||
|
# ameba:disable Lint/UselessAssign
|
||||||
error_message = translate(locale, message)
|
error_message = translate(locale, message)
|
||||||
|
# ameba:disable Lint/UselessAssign
|
||||||
next_steps = error_redirect_helper(env)
|
next_steps = error_redirect_helper(env)
|
||||||
|
|
||||||
return templated "error"
|
templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -106,7 +110,7 @@ end
|
|||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
macro error_atom(*args)
|
macro error_atom(*args)
|
||||||
error_atom_helper(env, {{args.splat}})
|
error_atom_helper(env, {{ args.splat }})
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, exception : Exception)
|
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.content_type = "application/atom+xml"
|
||||||
env.response.status_code = status_code
|
env.response.status_code = status_code
|
||||||
|
|
||||||
return "<error>#{exception.inspect_with_backtrace}</error>"
|
"<error>#{exception.inspect_with_backtrace}</error>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, message : String)
|
def error_atom_helper(env : HTTP::Server::Context, status_code : Int32, message : String)
|
||||||
env.response.content_type = "application/atom+xml"
|
env.response.content_type = "application/atom+xml"
|
||||||
env.response.status_code = status_code
|
env.response.status_code = status_code
|
||||||
|
|
||||||
return "<error>#{message}</error>"
|
"<error>#{message}</error>"
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -132,14 +136,14 @@ end
|
|||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
macro error_json(*args)
|
macro error_json(*args)
|
||||||
error_json_helper(env, {{args.splat}})
|
error_json_helper(env, {{ args.splat }})
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_json_helper(
|
def error_json_helper(
|
||||||
env : HTTP::Server::Context,
|
env : HTTP::Server::Context,
|
||||||
status_code : Int32,
|
status_code : Int32,
|
||||||
exception : Exception,
|
exception : Exception,
|
||||||
additional_fields : Hash(String, Object) | Nil = nil,
|
additional_fields : Hash(String, Object)? = nil,
|
||||||
)
|
)
|
||||||
if exception.is_a?(InfoException)
|
if exception.is_a?(InfoException)
|
||||||
return error_json_helper(env, status_code, exception.message || "", additional_fields)
|
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)
|
error_message = error_message.merge(additional_fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
return error_message.to_json
|
error_message.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_json_helper(
|
def error_json_helper(
|
||||||
env : HTTP::Server::Context,
|
env : HTTP::Server::Context,
|
||||||
status_code : Int32,
|
status_code : Int32,
|
||||||
message : String,
|
message : String,
|
||||||
additional_fields : Hash(String, Object) | Nil = nil,
|
additional_fields : Hash(String, Object)? = nil,
|
||||||
)
|
)
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
env.response.status_code = status_code
|
env.response.status_code = status_code
|
||||||
@ -172,7 +176,7 @@ def error_json_helper(
|
|||||||
error_message = error_message.merge(additional_fields)
|
error_message = error_message.merge(additional_fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
return error_message.to_json
|
error_message.to_json
|
||||||
end
|
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")
|
go_to_youtube = translate(locale, "next_steps_error_message_go_to_youtube")
|
||||||
switch_instance = translate(locale, "Switch Invidious Instance")
|
switch_instance = translate(locale, "Switch Invidious Instance")
|
||||||
|
|
||||||
return <<-END_HTML
|
<<-HTML
|
||||||
<p style="margin-bottom: 4px;">#{next_steps_text}</p>
|
<p style="margin-bottom: 4px;">#{next_steps_text}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<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>
|
<a rel="noreferrer noopener" href="https://youtube.com#{env.request.resource}">#{go_to_youtube}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
END_HTML
|
HTML
|
||||||
else
|
else
|
||||||
return ""
|
""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -2,10 +2,10 @@ module HTTP::Handler
|
|||||||
@@exclude_routes_tree = Radix::Tree(String).new
|
@@exclude_routes_tree = Radix::Tree(String).new
|
||||||
|
|
||||||
macro exclude(paths, method = "GET")
|
macro exclude(paths, method = "GET")
|
||||||
class_name = {{@type.name}}
|
class_name = {{ @type.name }}
|
||||||
method_downcase = {{method.downcase}}
|
method_downcase = {{ method.downcase }}
|
||||||
class_name_method = "#{class_name}/#{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
|
@@exclude_routes_tree.add class_name_method + path, '/' + method_downcase + path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -20,8 +20,8 @@ module HTTP::Handler
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Kemal::RouteHandler
|
class Kemal::RouteHandler
|
||||||
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
|
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
|
||||||
exclude ["/api/v1/*"], {{method}}
|
exclude ["/api/v1/*"], {{ method }}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
# Processes the route if it's a match. Otherwise renders 404.
|
# Processes the route if it's a match. Otherwise renders 404.
|
||||||
@ -44,8 +44,8 @@ class Kemal::RouteHandler
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Kemal::ExceptionHandler
|
class Kemal::ExceptionHandler
|
||||||
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
|
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
|
||||||
exclude ["/api/v1/*"], {{method}}
|
exclude ["/api/v1/*"], {{ method }}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
private def call_exception_with_status_code(context : HTTP::Server::Context, exception : Exception, status_code : Int32)
|
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
|
end
|
||||||
|
|
||||||
class AuthHandler < Kemal::Handler
|
class AuthHandler < Kemal::Handler
|
||||||
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
|
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
|
||||||
only ["/api/v1/auth/*"], {{method}}
|
only ["/api/v1/auth/*"], {{ method }}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
@ -121,8 +121,8 @@ class AuthHandler < Kemal::Handler
|
|||||||
end
|
end
|
||||||
|
|
||||||
class APIHandler < Kemal::Handler
|
class APIHandler < Kemal::Handler
|
||||||
{% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %}
|
{% for method in %w[GET POST PUT HEAD DELETE PATCH OPTIONS] %}
|
||||||
only ["/api/v1/*"], {{method}}
|
only ["/api/v1/*"], {{ method }}
|
||||||
{% end %}
|
{% end %}
|
||||||
exclude ["/api/v1/auth/notifications"], "GET"
|
exclude ["/api/v1/auth/notifications"], "GET"
|
||||||
exclude ["/api/v1/auth/notifications"], "POST"
|
exclude ["/api/v1/auth/notifications"], "POST"
|
||||||
|
|||||||
@ -32,7 +32,7 @@ def html_to_content(description_html : String)
|
|||||||
description = XML.parse_html(description).content.strip("\n ")
|
description = XML.parse_html(description).content.strip("\n ")
|
||||||
end
|
end
|
||||||
|
|
||||||
return description
|
description
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_annotation(id, annotations)
|
def cache_annotation(id, annotations)
|
||||||
@ -165,7 +165,7 @@ def create_notification_stream(env, topics, connection_channel)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def extract_initial_data(body) : Hash(String, JSON::Any)
|
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
|
end
|
||||||
|
|
||||||
def proxy_file(response, env)
|
def proxy_file(response, env)
|
||||||
@ -196,5 +196,5 @@ def get_playback_statistic
|
|||||||
Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"] = tracker
|
Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"] = tracker
|
||||||
end
|
end
|
||||||
|
|
||||||
return tracker.as(Hash(String, Int64 | Float64))
|
tracker.as(Hash(String, Int64 | Float64))
|
||||||
end
|
end
|
||||||
|
|||||||
@ -91,10 +91,10 @@ def load_all_locales
|
|||||||
locales[name] = JSON.parse(File.read("locales/#{name}.json")).as_h
|
locales[name] = JSON.parse(File.read("locales/#{name}.json")).as_h
|
||||||
end
|
end
|
||||||
|
|
||||||
return locales
|
locales
|
||||||
end
|
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
|
# 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.
|
# that key as the text, so this is more or less transparent to the user.
|
||||||
if !LOCALES["en-US"].has_key?(key)
|
if !LOCALES["en-US"].has_key?(key)
|
||||||
@ -141,7 +141,7 @@ def translate(locale : String?, key : String, text : String | Hash(String, Strin
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return translation
|
translation
|
||||||
end
|
end
|
||||||
|
|
||||||
def translate_count(locale : String, key : String, count : Int, format = NumberFormatting::None) : String
|
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
|
else count_txt = count.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
return translation.gsub("{{count}}", count_txt)
|
translation.gsub("{{count}}", count_txt)
|
||||||
end
|
end
|
||||||
|
|
||||||
def translate_bool(locale : String?, translation : Bool)
|
def translate_bool(locale : String?, translation : Bool)
|
||||||
case translation
|
case translation
|
||||||
when true
|
when true
|
||||||
return translate(locale, "Yes")
|
translate(locale, "Yes")
|
||||||
when false
|
when false
|
||||||
return translate(locale, "No")
|
translate(locale, "No")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -195,5 +195,5 @@ def locale_is_rtl?(locale : String?)
|
|||||||
|
|
||||||
# Arabic, Persian, Hebrew
|
# Arabic, Persian, Hebrew
|
||||||
# See https://en.wikipedia.org/wiki/Right-to-left_script#List_of_RTL_scripts
|
# 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
|
end
|
||||||
|
|||||||
@ -151,17 +151,17 @@ module I18next::Plurals
|
|||||||
@version = version.to_u8
|
@version = version.to_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
self.init_rules
|
init_rules
|
||||||
end
|
end
|
||||||
|
|
||||||
def init_rules
|
def init_rules
|
||||||
# Look into sets
|
# Look into sets
|
||||||
PLURAL_SETS.each do |form, langs|
|
PLURAL_SETS.each do |form, langs|
|
||||||
langs.each { |lang| self.forms[lang] = form }
|
langs.each { |lang| forms[lang] = form }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add plurals from the "singles" set
|
# Add plurals from the "singles" set
|
||||||
self.forms.merge!(PLURAL_SINGLES)
|
forms.merge!(PLURAL_SINGLES)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_plural_form(locale : String) : PluralForms
|
def get_plural_form(locale : String) : PluralForms
|
||||||
@ -170,12 +170,12 @@ module I18next::Plurals
|
|||||||
locale = locale.split('-')[0]
|
locale = locale.split('-')[0]
|
||||||
end
|
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
|
# If nothing was found, then use the most common form, i.e
|
||||||
# one singular and one plural, as in english. Not perfect,
|
# one singular and one plural, as in english. Not perfect,
|
||||||
# but better than yielding an exception at the user.
|
# but better than yielding an exception at the user.
|
||||||
return PluralForms::Single_not_one
|
PluralForms::Single_not_one
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_suffix(locale : String, count : Int) : String
|
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,
|
# determine if comparison should be done on a signed or unsigned integer,
|
||||||
# but this variable is never set, resulting in the comparison always
|
# but this variable is never set, resulting in the comparison always
|
||||||
# being done on absolute numbers.
|
# being done on absolute numbers.
|
||||||
return get_suffix_retrocompat(locale, count.abs)
|
get_suffix_retrocompat(locale, count.abs)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Emulate the `rule.numbers.size == 2 && rule.numbers[0] == 1` check
|
# Emulate the `rule.numbers.size == 2 && rule.numbers[0] == 1` check
|
||||||
# from original i18next code
|
# from original i18next code
|
||||||
private def simple_plural?(form : PluralForms) : Bool
|
private def simple_plural?(form : PluralForms) : Bool
|
||||||
case form
|
case form
|
||||||
when .single_gt_one? then return true
|
when .single_gt_one? then true
|
||||||
when .single_not_one? then return true
|
when .single_not_one? then true
|
||||||
when .special_icelandic? then return true
|
when .special_icelandic? then true
|
||||||
when .special_macedonian? then return true
|
when .special_macedonian? then true
|
||||||
else
|
else
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ module I18next::Plurals
|
|||||||
# when 2
|
# when 2
|
||||||
# return "_#{suffix}"
|
# return "_#{suffix}"
|
||||||
# else # v3
|
# else # v3
|
||||||
return "_#{idx}"
|
"_#{idx}"
|
||||||
# end
|
# end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -238,35 +238,35 @@ module I18next::Plurals
|
|||||||
module SuffixIndex
|
module SuffixIndex
|
||||||
def self.get_index(plural_form : PluralForms, count : Int) : UInt8
|
def self.get_index(plural_form : PluralForms, count : Int) : UInt8
|
||||||
case plural_form
|
case plural_form
|
||||||
when .single_gt_one? then return (count > 1) ? 1_u8 : 0_u8
|
when .single_gt_one? then (count > 1) ? 1_u8 : 0_u8
|
||||||
when .single_not_one? then return (count != 1) ? 1_u8 : 0_u8
|
when .single_not_one? then (count != 1) ? 1_u8 : 0_u8
|
||||||
when .none? then return 0_u8
|
when .none? then 0_u8
|
||||||
when .dual_slavic? then return dual_slavic(count)
|
when .dual_slavic? then dual_slavic(count)
|
||||||
when .special_arabic? then return special_arabic(count)
|
when .special_arabic? then special_arabic(count)
|
||||||
when .special_czech_slovak? then return special_czech_slovak(count)
|
when .special_czech_slovak? then special_czech_slovak(count)
|
||||||
when .special_polish_kashubian? then return special_polish_kashubian(count)
|
when .special_polish_kashubian? then special_polish_kashubian(count)
|
||||||
when .special_welsh? then return special_welsh(count)
|
when .special_welsh? then special_welsh(count)
|
||||||
when .special_irish? then return special_irish(count)
|
when .special_irish? then special_irish(count)
|
||||||
when .special_scottish_gaelic? then return special_scottish_gaelic(count)
|
when .special_scottish_gaelic? then special_scottish_gaelic(count)
|
||||||
when .special_icelandic? then return special_icelandic(count)
|
when .special_icelandic? then special_icelandic(count)
|
||||||
when .special_javanese? then return special_javanese(count)
|
when .special_javanese? then special_javanese(count)
|
||||||
when .special_cornish? then return special_cornish(count)
|
when .special_cornish? then special_cornish(count)
|
||||||
when .special_lithuanian? then return special_lithuanian(count)
|
when .special_lithuanian? then special_lithuanian(count)
|
||||||
when .special_latvian? then return special_latvian(count)
|
when .special_latvian? then special_latvian(count)
|
||||||
when .special_macedonian? then return special_macedonian(count)
|
when .special_macedonian? then special_macedonian(count)
|
||||||
when .special_mandinka? then return special_mandinka(count)
|
when .special_mandinka? then special_mandinka(count)
|
||||||
when .special_maltese? then return special_maltese(count)
|
when .special_maltese? then special_maltese(count)
|
||||||
when .special_romanian? then return special_romanian(count)
|
when .special_romanian? then special_romanian(count)
|
||||||
when .special_slovenian? then return special_slovenian(count)
|
when .special_slovenian? then special_slovenian(count)
|
||||||
when .special_hebrew? then return special_hebrew(count)
|
when .special_hebrew? then special_hebrew(count)
|
||||||
when .special_odia? then return special_odia(count)
|
when .special_odia? then special_odia(count)
|
||||||
# Mixed v3/v4 forms
|
# Mixed v3/v4 forms
|
||||||
when .special_spanish_italian? then return special_cldr_spanish_italian(count)
|
when .special_spanish_italian? then special_cldr_spanish_italian(count)
|
||||||
when .special_french_portuguese? then return special_cldr_french_portuguese(count)
|
when .special_french_portuguese? then special_cldr_french_portuguese(count)
|
||||||
when .special_hungarian_serbian? then return special_cldr_hungarian_serbian(count)
|
when .special_hungarian_serbian? then special_cldr_hungarian_serbian(count)
|
||||||
else
|
else
|
||||||
# default, if nothing matched above
|
# default, if nothing matched above
|
||||||
return 0_u8
|
0_u8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -280,11 +280,11 @@ module I18next::Plurals
|
|||||||
n_mod_100 = count % 100
|
n_mod_100 = count % 100
|
||||||
|
|
||||||
if n_mod_10 == 1 && n_mod_100 != 11
|
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)
|
elsif n_mod_10 >= 2 && n_mod_10 <= 4 && (n_mod_100 < 10 || n_mod_100 >= 20)
|
||||||
return 1_u8
|
1_u8
|
||||||
else
|
else
|
||||||
return 2_u8
|
2_u8
|
||||||
end
|
end
|
||||||
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)
|
# 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
|
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
|
n_mod_100 = count % 100
|
||||||
|
|
||||||
return 3_u8 if (n_mod_100 >= 3 && n_mod_100 <= 10)
|
return 3_u8 if n_mod_100 >= 3 && n_mod_100 <= 10
|
||||||
return 4_u8 if (n_mod_100 >= 11)
|
return 4_u8 if n_mod_100 >= 11
|
||||||
return 5_u8
|
5_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Czech and Slovak languages
|
# Plural form for Czech and Slovak languages
|
||||||
@ -309,9 +309,9 @@ module I18next::Plurals
|
|||||||
# Rule: ((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)
|
# Rule: ((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2)
|
||||||
#
|
#
|
||||||
def self.special_czech_slovak(count : Int) : UInt8
|
def self.special_czech_slovak(count : Int) : UInt8
|
||||||
return 0_u8 if (count == 1)
|
return 0_u8 if count == 1
|
||||||
return 1_u8 if (count >= 2 && count <= 4)
|
return 1_u8 if count >= 2 && count <= 4
|
||||||
return 2_u8
|
2_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Polish and Kashubian languages
|
# 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)
|
# 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
|
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_10 = count % 10
|
||||||
n_mod_100 = count % 100
|
n_mod_100 = count % 100
|
||||||
|
|
||||||
if n_mod_10 >= 2 && n_mod_10 <= 4 && (n_mod_100 < 10 || n_mod_100 >= 20)
|
if n_mod_10 >= 2 && n_mod_10 <= 4 && (n_mod_100 < 10 || n_mod_100 >= 20)
|
||||||
return 1_u8
|
1_u8
|
||||||
else
|
else
|
||||||
return 2_u8
|
2_u8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -338,10 +338,10 @@ module I18next::Plurals
|
|||||||
# Rule: ((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3)
|
# Rule: ((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3)
|
||||||
#
|
#
|
||||||
def self.special_welsh(count : Int) : UInt8
|
def self.special_welsh(count : Int) : UInt8
|
||||||
return 0_u8 if (count == 1)
|
return 0_u8 if count == 1
|
||||||
return 1_u8 if (count == 2)
|
return 1_u8 if count == 2
|
||||||
return 2_u8 if (count != 8 && count != 11)
|
return 2_u8 if count != 8 && count != 11
|
||||||
return 3_u8
|
3_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Irish language
|
# 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)
|
# Rule: (n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4)
|
||||||
#
|
#
|
||||||
def self.special_irish(count : Int) : UInt8
|
def self.special_irish(count : Int) : UInt8
|
||||||
return 0_u8 if (count == 1)
|
return 0_u8 if count == 1
|
||||||
return 1_u8 if (count == 2)
|
return 1_u8 if count == 2
|
||||||
return 2_u8 if (count < 7)
|
return 2_u8 if count < 7
|
||||||
return 3_u8 if (count < 11)
|
return 3_u8 if count < 11
|
||||||
return 4_u8
|
4_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Gaelic language
|
# 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)
|
# 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
|
def self.special_scottish_gaelic(count : Int) : UInt8
|
||||||
return 0_u8 if (count == 1 || count == 11)
|
return 0_u8 if count == 1 || count == 11
|
||||||
return 1_u8 if (count == 2 || count == 12)
|
return 1_u8 if count == 2 || count == 12
|
||||||
return 2_u8 if (count > 2 && count < 20)
|
return 2_u8 if count > 2 && count < 20
|
||||||
return 3_u8
|
3_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Icelandic language
|
# Plural form for Icelandic language
|
||||||
@ -376,9 +376,9 @@ module I18next::Plurals
|
|||||||
#
|
#
|
||||||
def self.special_icelandic(count : Int) : UInt8
|
def self.special_icelandic(count : Int) : UInt8
|
||||||
if (count % 10) != 1 || (count % 100) == 11
|
if (count % 10) != 1 || (count % 100) == 11
|
||||||
return 1_u8
|
1_u8
|
||||||
else
|
else
|
||||||
return 0_u8
|
0_u8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ module I18next::Plurals
|
|||||||
# Rule: (n !== 0)
|
# Rule: (n !== 0)
|
||||||
#
|
#
|
||||||
def self.special_javanese(count : Int) : UInt8
|
def self.special_javanese(count : Int) : UInt8
|
||||||
return (count != 0) ? 1_u8 : 0_u8
|
(count != 0) ? 1_u8 : 0_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Cornish language
|
# Plural form for Cornish language
|
||||||
@ -400,7 +400,7 @@ module I18next::Plurals
|
|||||||
return 0_u8 if count == 1
|
return 0_u8 if count == 1
|
||||||
return 1_u8 if count == 2
|
return 1_u8 if count == 2
|
||||||
return 2_u8 if count == 3
|
return 2_u8 if count == 3
|
||||||
return 3_u8
|
3_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Lithuanian language
|
# Plural form for Lithuanian language
|
||||||
@ -413,11 +413,11 @@ module I18next::Plurals
|
|||||||
n_mod_100 = count % 100
|
n_mod_100 = count % 100
|
||||||
|
|
||||||
if n_mod_10 == 1 && n_mod_100 != 11
|
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)
|
elsif n_mod_10 >= 2 && (n_mod_100 < 10 || n_mod_100 >= 20)
|
||||||
return 1_u8
|
1_u8
|
||||||
else
|
else
|
||||||
return 2_u8
|
2_u8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -428,11 +428,11 @@ module I18next::Plurals
|
|||||||
#
|
#
|
||||||
def self.special_latvian(count : Int) : UInt8
|
def self.special_latvian(count : Int) : UInt8
|
||||||
if (count % 10) == 1 && (count % 100) != 11
|
if (count % 10) == 1 && (count % 100) != 11
|
||||||
return 0_u8
|
0_u8
|
||||||
elsif count != 0
|
elsif count != 0
|
||||||
return 1_u8
|
1_u8
|
||||||
else
|
else
|
||||||
return 2_u8
|
2_u8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -443,9 +443,9 @@ module I18next::Plurals
|
|||||||
#
|
#
|
||||||
def self.special_macedonian(count : Int) : UInt8
|
def self.special_macedonian(count : Int) : UInt8
|
||||||
if count == 1 || ((count % 10) == 1 && (count % 100) != 11)
|
if count == 1 || ((count % 10) == 1 && (count % 100) != 11)
|
||||||
return 0_u8
|
0_u8
|
||||||
else
|
else
|
||||||
return 1_u8
|
1_u8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -455,7 +455,7 @@ module I18next::Plurals
|
|||||||
# Rule: (n==0 ? 0 : n==1 ? 1 : 2)
|
# Rule: (n==0 ? 0 : n==1 ? 1 : 2)
|
||||||
#
|
#
|
||||||
def self.special_mandinka(count : Int) : UInt8
|
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
|
end
|
||||||
|
|
||||||
# Plural form for Maltese language
|
# Plural form for Maltese language
|
||||||
@ -468,9 +468,9 @@ module I18next::Plurals
|
|||||||
return 1_u8 if count == 0
|
return 1_u8 if count == 0
|
||||||
|
|
||||||
n_mod_100 = count % 100
|
n_mod_100 = count % 100
|
||||||
return 1_u8 if (n_mod_100 > 1 && n_mod_100 < 11)
|
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 2_u8 if n_mod_100 > 10 && n_mod_100 < 20
|
||||||
return 3_u8
|
3_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Romanian language
|
# Plural form for Romanian language
|
||||||
@ -483,8 +483,8 @@ module I18next::Plurals
|
|||||||
return 1_u8 if count == 0
|
return 1_u8 if count == 0
|
||||||
|
|
||||||
n_mod_100 = count % 100
|
n_mod_100 = count % 100
|
||||||
return 1_u8 if (n_mod_100 > 0 && n_mod_100 < 20)
|
return 1_u8 if n_mod_100 > 0 && n_mod_100 < 20
|
||||||
return 2_u8
|
2_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Slovenian language
|
# Plural form for Slovenian language
|
||||||
@ -494,10 +494,10 @@ module I18next::Plurals
|
|||||||
#
|
#
|
||||||
def self.special_slovenian(count : Int) : UInt8
|
def self.special_slovenian(count : Int) : UInt8
|
||||||
n_mod_100 = count % 100
|
n_mod_100 = count % 100
|
||||||
return 1_u8 if (n_mod_100 == 1)
|
return 1_u8 if n_mod_100 == 1
|
||||||
return 2_u8 if (n_mod_100 == 2)
|
return 2_u8 if n_mod_100 == 2
|
||||||
return 3_u8 if (n_mod_100 == 3 || n_mod_100 == 4)
|
return 3_u8 if n_mod_100 == 3 || n_mod_100 == 4
|
||||||
return 0_u8
|
0_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Hebrew language
|
# 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)
|
# Rule: (n==1 ? 0 : n==2 ? 1 : (n<0 || n>10) && n%10==0 ? 2 : 3)
|
||||||
#
|
#
|
||||||
def self.special_hebrew(count : Int) : UInt8
|
def self.special_hebrew(count : Int) : UInt8
|
||||||
return 0_u8 if (count == 1)
|
return 0_u8 if count == 1
|
||||||
return 1_u8 if (count == 2)
|
return 1_u8 if count == 2
|
||||||
|
|
||||||
if (count < 0 || count > 10) && (count % 10) == 0
|
if (count < 0 || count > 10) && (count % 10) == 0
|
||||||
return 2_u8
|
2_u8
|
||||||
else
|
else
|
||||||
return 3_u8
|
3_u8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -523,7 +523,7 @@ module I18next::Plurals
|
|||||||
# special rule for it.
|
# special rule for it.
|
||||||
#
|
#
|
||||||
def self.special_odia(count : Int) : UInt8
|
def self.special_odia(count : Int) : UInt8
|
||||||
return (count == 1) ? 0_u8 : 1_u8
|
(count == 1) ? 0_u8 : 1_u8
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -535,9 +535,9 @@ module I18next::Plurals
|
|||||||
# This rule is mostly compliant to CLDR v42
|
# This rule is mostly compliant to CLDR v42
|
||||||
#
|
#
|
||||||
def self.special_cldr_spanish_italian(count : Int) : UInt8
|
def self.special_cldr_spanish_italian(count : Int) : UInt8
|
||||||
return 0_u8 if (count == 1) # one
|
return 0_u8 if count == 1 # one
|
||||||
return 1_u8 if (count != 0 && count % 1_000_000 == 0) # many
|
return 1_u8 if count != 0 && count % 1_000_000 == 0 # many
|
||||||
return 2_u8 # other
|
2_u8 # other
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for French and Portuguese
|
# Plural form for French and Portuguese
|
||||||
@ -545,9 +545,9 @@ module I18next::Plurals
|
|||||||
# This rule is mostly compliant to CLDR v42
|
# This rule is mostly compliant to CLDR v42
|
||||||
#
|
#
|
||||||
def self.special_cldr_french_portuguese(count : Int) : UInt8
|
def self.special_cldr_french_portuguese(count : Int) : UInt8
|
||||||
return 0_u8 if (count == 0 || count == 1) # one
|
return 0_u8 if count == 0 || count == 1 # one
|
||||||
return 1_u8 if (count % 1_000_000 == 0) # many
|
return 1_u8 if count % 1_000_000 == 0 # many
|
||||||
return 2_u8 # other
|
2_u8 # other
|
||||||
end
|
end
|
||||||
|
|
||||||
# Plural form for Hungarian and Serbian
|
# Plural form for Hungarian and Serbian
|
||||||
@ -558,9 +558,9 @@ module I18next::Plurals
|
|||||||
n_mod_10 = count % 10
|
n_mod_10 = count % 10
|
||||||
n_mod_100 = count % 100
|
n_mod_100 = count % 100
|
||||||
|
|
||||||
return 0_u8 if (n_mod_10 == 1 && n_mod_100 != 11) # one
|
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 1_u8 if 2 <= n_mod_10 <= 4 && (n_mod_100 < 12 || 14 < n_mod_100) # few
|
||||||
return 2_u8 # other
|
2_u8 # other
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -55,10 +55,10 @@ class Invidious::LogHandler < Kemal::BaseLogHandler
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
{% for level in %w(trace debug info warn error fatal) %}
|
{% for level in %w[trace debug info warn error fatal] %}
|
||||||
def {{level.id}}(message : String)
|
def {{ level.id }}(message : String)
|
||||||
if LogLevel::{{level.id.capitalize}} >= @level
|
if LogLevel::{{ level.id.capitalize }} >= @level
|
||||||
puts("#{Time.utc} [{{level.id}}] #{message}".colorize(color(LogLevel::{{level.id.capitalize}})))
|
puts("#{Time.utc} [{{ level.id }}] #{message}".colorize(color(LogLevel::{{ level.id.capitalize }})))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|||||||
@ -49,24 +49,24 @@ module JSON::Serializable
|
|||||||
end
|
end
|
||||||
|
|
||||||
macro templated(_filename, template = "template", navbar_search = true)
|
macro templated(_filename, template = "template", navbar_search = true)
|
||||||
navbar_search = {{navbar_search}}
|
navbar_search = {{ navbar_search }}
|
||||||
|
|
||||||
{{ filename = "src/invidious/views/" + _filename + ".ecr" }}
|
{{ filename = "src/invidious/views/" + _filename + ".ecr" }}
|
||||||
{{ layout = "src/invidious/views/" + template + ".ecr" }}
|
{{ layout = "src/invidious/views/" + template + ".ecr" }}
|
||||||
|
|
||||||
__content_filename__ = {{filename}}
|
__content_filename__ = {{ filename }}
|
||||||
render {{filename}}, {{layout}}
|
render {{ filename }}, {{ layout }}
|
||||||
end
|
end
|
||||||
|
|
||||||
macro rendered(filename)
|
macro rendered(filename)
|
||||||
render("src/invidious/views/#{{{filename}}}.ecr")
|
render("src/invidious/views/#{{{ filename }}}.ecr")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Similar to Kemals halt method but works in a
|
# Similar to Kemals halt method but works in a
|
||||||
# method.
|
# method.
|
||||||
macro haltf(env, status_code = 200, response = "")
|
macro haltf(env, status_code = 200, response = "")
|
||||||
{{env}}.response.status_code = {{status_code}}
|
{{ env }}.response.status_code = {{ status_code }}
|
||||||
{{env}}.response.print {{response}}
|
{{ env }}.response.print {{ response }}
|
||||||
{{env}}.response.close
|
{{ env }}.response.close
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@ -28,19 +28,19 @@ struct SearchVideo
|
|||||||
property badges : VideoBadges
|
property badges : VideoBadges
|
||||||
|
|
||||||
def to_xml(auto_generated, query_params, xml : XML::Builder)
|
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("entry") do
|
||||||
xml.element("id") { xml.text "yt:video:#{self.id}" }
|
xml.element("id") { xml.text "yt:video:#{id}" }
|
||||||
xml.element("yt:videoId") { xml.text self.id }
|
xml.element("yt:videoId") { xml.text id }
|
||||||
xml.element("yt:channelId") { xml.text self.ucid }
|
xml.element("yt:channelId") { xml.text ucid }
|
||||||
xml.element("title") { xml.text self.title }
|
xml.element("title") { xml.text title }
|
||||||
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}")
|
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?#{query_params}")
|
||||||
|
|
||||||
xml.element("author") do
|
xml.element("author") do
|
||||||
if auto_generated
|
if auto_generated
|
||||||
xml.element("name") { xml.text self.author }
|
xml.element("name") { xml.text author }
|
||||||
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" }
|
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
|
||||||
else
|
else
|
||||||
xml.element("name") { xml.text author }
|
xml.element("name") { xml.text author }
|
||||||
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
|
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
|
||||||
@ -50,24 +50,24 @@ struct SearchVideo
|
|||||||
xml.element("content", type: "xhtml") do
|
xml.element("content", type: "xhtml") do
|
||||||
xml.element("div", xmlns: "http://www.w3.org/1999/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("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
|
||||||
|
|
||||||
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
|
||||||
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:group") do
|
||||||
xml.element("media:title") { xml.text self.title }
|
xml.element("media:title") { xml.text title }
|
||||||
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg",
|
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{id}/mqdefault.jpg",
|
||||||
width: "320", height: "180")
|
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
|
end
|
||||||
|
|
||||||
xml.element("media:community") do
|
xml.element("media:community") do
|
||||||
xml.element("media:statistics", views: self.views)
|
xml.element("media:statistics", views: views)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -81,13 +81,13 @@ struct SearchVideo
|
|||||||
def to_json(locale : String?, json : JSON::Builder)
|
def to_json(locale : String?, json : JSON::Builder)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", "video"
|
json.field "type", "video"
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "videoId", self.id
|
json.field "videoId", id
|
||||||
|
|
||||||
json.field "author", self.author
|
json.field "author", author
|
||||||
json.field "authorId", self.ucid
|
json.field "authorId", ucid
|
||||||
json.field "authorUrl", "/channel/#{self.ucid}"
|
json.field "authorUrl", "/channel/#{ucid}"
|
||||||
json.field "authorVerified", self.author_verified
|
json.field "authorVerified", author_verified
|
||||||
|
|
||||||
author_thumbnail = self.author_thumbnail
|
author_thumbnail = self.author_thumbnail
|
||||||
|
|
||||||
@ -108,31 +108,31 @@ struct SearchVideo
|
|||||||
end
|
end
|
||||||
|
|
||||||
json.field "videoThumbnails" do
|
json.field "videoThumbnails" do
|
||||||
Invidious::JSONify::APIv1.thumbnails(json, self.id)
|
Invidious::JSONify::APIv1.thumbnails(json, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "description", html_to_content(self.description_html)
|
json.field "description", html_to_content(description_html)
|
||||||
json.field "descriptionHtml", self.description_html
|
json.field "descriptionHtml", description_html
|
||||||
|
|
||||||
json.field "viewCount", self.views
|
json.field "viewCount", views
|
||||||
json.field "viewCountText", translate_count(locale, "generic_views_count", self.views, NumberFormatting::Short)
|
json.field "viewCountText", translate_count(locale, "generic_views_count", views, NumberFormatting::Short)
|
||||||
json.field "published", self.published.to_unix
|
json.field "published", published.to_unix
|
||||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
|
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
|
||||||
json.field "lengthSeconds", self.length_seconds
|
json.field "lengthSeconds", length_seconds
|
||||||
json.field "liveNow", self.badges.live_now?
|
json.field "liveNow", badges.live_now?
|
||||||
json.field "premium", self.badges.premium?
|
json.field "premium", badges.premium?
|
||||||
json.field "isUpcoming", self.upcoming?
|
json.field "isUpcoming", upcoming?
|
||||||
|
|
||||||
if self.premiere_timestamp
|
if premiere_timestamp
|
||||||
json.field "premiereTimestamp", self.premiere_timestamp.try &.to_unix
|
json.field "premiereTimestamp", premiere_timestamp.try &.to_unix
|
||||||
end
|
end
|
||||||
json.field "isNew", self.badges.new?
|
json.field "isNew", badges.new?
|
||||||
json.field "is4k", self.badges.four_k?
|
json.field "is4k", badges.four_k?
|
||||||
json.field "is8k", self.badges.eight_k?
|
json.field "is8k", badges.eight_k?
|
||||||
json.field "isVr180", self.badges.vr180?
|
json.field "isVr180", badges.vr180?
|
||||||
json.field "isVr360", self.badges.vr360?
|
json.field "isVr360", badges.vr360?
|
||||||
json.field "is3d", self.badges.three_d?
|
json.field "is3d", badges.three_d?
|
||||||
json.field "hasCaptions", self.badges.closed_captions?
|
json.field "hasCaptions", badges.closed_captions?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -175,20 +175,20 @@ struct SearchPlaylist
|
|||||||
def to_json(locale : String?, json : JSON::Builder)
|
def to_json(locale : String?, json : JSON::Builder)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", "playlist"
|
json.field "type", "playlist"
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "playlistId", self.id
|
json.field "playlistId", id
|
||||||
json.field "playlistThumbnail", self.thumbnail
|
json.field "playlistThumbnail", thumbnail
|
||||||
|
|
||||||
json.field "author", self.author
|
json.field "author", author
|
||||||
json.field "authorId", self.ucid
|
json.field "authorId", ucid
|
||||||
json.field "authorUrl", "/channel/#{self.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.field "videos" do
|
||||||
json.array do
|
json.array do
|
||||||
self.videos.each do |video|
|
videos.each do |video|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "title", video.title
|
json.field "title", video.title
|
||||||
json.field "videoId", video.id
|
json.field "videoId", video.id
|
||||||
@ -232,17 +232,17 @@ struct SearchChannel
|
|||||||
def to_json(locale : String?, json : JSON::Builder)
|
def to_json(locale : String?, json : JSON::Builder)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", "channel"
|
json.field "type", "channel"
|
||||||
json.field "author", self.author
|
json.field "author", author
|
||||||
json.field "authorId", self.ucid
|
json.field "authorId", ucid
|
||||||
json.field "authorUrl", "/channel/#{self.ucid}"
|
json.field "authorUrl", "/channel/#{ucid}"
|
||||||
json.field "authorVerified", self.author_verified
|
json.field "authorVerified", author_verified
|
||||||
json.field "authorThumbnails" do
|
json.field "authorThumbnails" do
|
||||||
json.array do
|
json.array do
|
||||||
qualities = {32, 48, 76, 100, 176, 512}
|
qualities = {32, 48, 76, 100, 176, 512}
|
||||||
|
|
||||||
qualities.each do |quality|
|
qualities.each do |quality|
|
||||||
json.object do
|
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 "width", quality
|
||||||
json.field "height", quality
|
json.field "height", quality
|
||||||
end
|
end
|
||||||
@ -250,13 +250,13 @@ struct SearchChannel
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "autoGenerated", self.auto_generated
|
json.field "autoGenerated", auto_generated
|
||||||
json.field "subCount", self.subscriber_count
|
json.field "subCount", subscriber_count
|
||||||
json.field "videoCount", self.video_count
|
json.field "videoCount", video_count
|
||||||
json.field "channelHandle", self.channel_handle
|
json.field "channelHandle", channel_handle
|
||||||
|
|
||||||
json.field "description", html_to_content(self.description_html)
|
json.field "description", html_to_content(description_html)
|
||||||
json.field "descriptionHtml", self.description_html
|
json.field "descriptionHtml", description_html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -283,10 +283,10 @@ struct SearchHashtag
|
|||||||
def to_json(locale : String?, json : JSON::Builder)
|
def to_json(locale : String?, json : JSON::Builder)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", "hashtag"
|
json.field "type", "hashtag"
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "url", self.url
|
json.field "url", url
|
||||||
json.field "videoCount", self.video_count
|
json.field "videoCount", video_count
|
||||||
json.field "channelCount", self.channel_count
|
json.field "channelCount", channel_count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -315,7 +315,7 @@ struct ProblematicTimelineItem
|
|||||||
|
|
||||||
# Provides compatibility with PlaylistVideo
|
# Provides compatibility with PlaylistVideo
|
||||||
def to_json(json : JSON::Builder, *args, **kwargs)
|
def to_json(json : JSON::Builder, *args, **kwargs)
|
||||||
return to_json("", json)
|
to_json("", json)
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_xml(env, locale, xml : XML::Builder)
|
def to_xml(env, locale, xml : XML::Builder)
|
||||||
@ -352,10 +352,10 @@ class Category
|
|||||||
def to_json(locale : String?, json : JSON::Builder)
|
def to_json(locale : String?, json : JSON::Builder)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", "category"
|
json.field "type", "category"
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "contents" do
|
json.field "contents" do
|
||||||
json.array do
|
json.array do
|
||||||
self.contents.each do |item|
|
contents.each do |item|
|
||||||
item.to_json(locale, json)
|
item.to_json(locale, json)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -16,7 +16,7 @@ def generate_token(email, scopes, expire, key)
|
|||||||
|
|
||||||
token["signature"] = sign_token(key, token)
|
token["signature"] = sign_token(key, token)
|
||||||
|
|
||||||
return token.to_json
|
token.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_response(session, scopes, key, expire = 6.hours, use_nonce = false)
|
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)
|
token["signature"] = sign_token(key, token)
|
||||||
|
|
||||||
return token.to_json
|
token.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign_token(key, hash)
|
def sign_token(key, hash)
|
||||||
@ -45,6 +45,7 @@ def sign_token(key, hash)
|
|||||||
# TODO: figure out which "key" variable is used
|
# TODO: figure out which "key" variable is used
|
||||||
# Ameba reports a warning for "Lint/ShadowingOuterLocalVar" on this
|
# Ameba reports a warning for "Lint/ShadowingOuterLocalVar" on this
|
||||||
# variable, but it's preferable to not touch that (works fine atm).
|
# variable, but it's preferable to not touch that (works fine atm).
|
||||||
|
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||||
hash.each do |key, value|
|
hash.each do |key, value|
|
||||||
next if key == "signature"
|
next if key == "signature"
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ def sign_token(key, hash)
|
|||||||
end
|
end
|
||||||
|
|
||||||
string_to_sign = string_to_sign.sort.join("\n")
|
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
|
end
|
||||||
|
|
||||||
def validate_request(token, session, request, key, locale = nil)
|
def validate_request(token, session, request, key, locale = nil)
|
||||||
@ -116,7 +117,7 @@ def scope_includes_scope(scope, subset)
|
|||||||
subset_endpoint = subset_endpoint.downcase
|
subset_endpoint = subset_endpoint.downcase
|
||||||
|
|
||||||
if methods.empty?
|
if methods.empty?
|
||||||
methods = %w(GET POST PUT HEAD DELETE PATCH OPTIONS)
|
methods = %w[GET POST PUT HEAD DELETE PATCH OPTIONS]
|
||||||
end
|
end
|
||||||
|
|
||||||
if methods & subset_methods != subset_methods
|
if methods & subset_methods != subset_methods
|
||||||
@ -131,7 +132,7 @@ def scope_includes_scope(scope, subset)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def scopes_include_scope(scopes, subset)
|
def scopes_include_scope(scopes, subset)
|
||||||
@ -141,5 +142,5 @@ def scopes_include_scope(scopes, subset)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
|
|||||||
@ -8,7 +8,7 @@ def ci_lower_bound(pos, n)
|
|||||||
z = 1.96
|
z = 1.96
|
||||||
phat = 1.0*pos/n
|
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
|
end
|
||||||
|
|
||||||
def elapsed_text(elapsed)
|
def elapsed_text(elapsed)
|
||||||
@ -31,12 +31,12 @@ def decode_length_seconds(string)
|
|||||||
seconds: length_seconds[2]
|
seconds: length_seconds[2]
|
||||||
).total_seconds.to_i32
|
).total_seconds.to_i32
|
||||||
|
|
||||||
return length_seconds
|
length_seconds
|
||||||
end
|
end
|
||||||
|
|
||||||
def recode_length_seconds(time)
|
def recode_length_seconds(time)
|
||||||
if time <= 0
|
if time <= 0
|
||||||
return ""
|
""
|
||||||
else
|
else
|
||||||
time = time.seconds
|
time = time.seconds
|
||||||
text = "#{time.minutes.to_s.rjust(2, '0')}:#{time.seconds.to_s.rjust(2, '0')}"
|
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')
|
text = text.lchop('0')
|
||||||
|
|
||||||
return text
|
text
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ def decode_interval(string : String) : Time::Span
|
|||||||
time = Time::Span.new(minutes: raw_minutes)
|
time = Time::Span.new(minutes: raw_minutes)
|
||||||
end
|
end
|
||||||
|
|
||||||
return time
|
time
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_time(string)
|
def decode_time(string)
|
||||||
@ -88,7 +88,7 @@ def decode_time(string)
|
|||||||
time = hours * 3600 + minutes * 60 + seconds + millis // 1000
|
time = hours * 3600 + minutes * 60 + seconds + millis // 1000
|
||||||
end
|
end
|
||||||
|
|
||||||
return time
|
time
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_date(string : String)
|
def decode_date(string : String)
|
||||||
@ -108,7 +108,6 @@ def decode_date(string : String)
|
|||||||
return Time.utc
|
return Time.utc
|
||||||
when "yesterday"
|
when "yesterday"
|
||||||
return Time.utc - 1.day
|
return Time.utc - 1.day
|
||||||
else nil # Continue
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# String matches format "20 hours ago", "4 months ago", "20s ago", "15min ago"...
|
# 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}"
|
raise "Could not parse #{string}"
|
||||||
end
|
end
|
||||||
|
|
||||||
return Time.utc - delta
|
Time.utc - delta
|
||||||
end
|
end
|
||||||
|
|
||||||
def recode_date(time : Time, locale)
|
def recode_date(time : Time, locale)
|
||||||
span = Time.utc - time
|
span = Time.utc - time
|
||||||
|
|
||||||
if span.total_days > 365.0
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -174,9 +173,9 @@ def short_text_to_number(short_text : String) : Int64
|
|||||||
when "b" then number *= 1_000_000_000
|
when "b" then number *= 1_000_000_000
|
||||||
end
|
end
|
||||||
|
|
||||||
return number.to_i64
|
number.to_i64
|
||||||
rescue ex
|
rescue ex
|
||||||
return 0_i64
|
0_i64
|
||||||
end
|
end
|
||||||
|
|
||||||
def number_to_short_text(number)
|
def number_to_short_text(number)
|
||||||
@ -209,7 +208,7 @@ def arg_array(array, start = 1)
|
|||||||
args = args.join(",")
|
args = args.join(",")
|
||||||
end
|
end
|
||||||
|
|
||||||
return args
|
args
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_host_url(kemal_config)
|
def make_host_url(kemal_config)
|
||||||
@ -235,7 +234,7 @@ def make_host_url(kemal_config)
|
|||||||
|
|
||||||
host = CONFIG.domain.not_nil!.lchop(".")
|
host = CONFIG.domain.not_nil!.lchop(".")
|
||||||
|
|
||||||
return "#{scheme}#{host}#{port}"
|
"#{scheme}#{host}#{port}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_referer(env, fallback = "/", unroll = true)
|
def get_referer(env, fallback = "/", unroll = true)
|
||||||
@ -268,13 +267,13 @@ def get_referer(env, fallback = "/", unroll = true)
|
|||||||
referer = fallback
|
referer = fallback
|
||||||
end
|
end
|
||||||
|
|
||||||
return referer
|
referer
|
||||||
end
|
end
|
||||||
|
|
||||||
def sha256(text)
|
def sha256(text)
|
||||||
digest = OpenSSL::Digest.new("SHA256")
|
digest = OpenSSL::Digest.new("SHA256")
|
||||||
digest << text
|
digest << text
|
||||||
return digest.final.hexstring
|
digest.final.hexstring
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribe_pubsub(topic, key)
|
def subscribe_pubsub(topic, key)
|
||||||
@ -302,7 +301,7 @@ def subscribe_pubsub(topic, key)
|
|||||||
"hub.secret" => key.to_s,
|
"hub.secret" => key.to_s,
|
||||||
}
|
}
|
||||||
|
|
||||||
return make_client(PUBSUB_URL, &.post("/subscribe", form: body))
|
make_client(PUBSUB_URL, &.post("/subscribe", form: body))
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_range(range)
|
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
|
if str.size > max_length
|
||||||
str = "#{str[0, max_length]}#{suffix}"
|
str = "#{str[0, max_length]}#{suffix}"
|
||||||
end
|
end
|
||||||
return str
|
str
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the html link from a NavigationEndpoint or an innertubeCommand
|
# 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>)
|
text = %(<a href="#{url}">#{reduce_uri(text)}</a>)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return text
|
text
|
||||||
end
|
end
|
||||||
|
|
||||||
def encrypt_ecb_without_salt(data, key)
|
def encrypt_ecb_without_salt(data, key)
|
||||||
@ -394,11 +393,11 @@ def encrypt_ecb_without_salt(data, key)
|
|||||||
io.write(cipher.final)
|
io.write(cipher.final)
|
||||||
io.rewind
|
io.rewind
|
||||||
|
|
||||||
return io
|
io
|
||||||
end
|
end
|
||||||
|
|
||||||
def invidious_companion_encrypt(data)
|
def invidious_companion_encrypt(data)
|
||||||
timestamp = Time.utc.to_unix
|
timestamp = Time.utc.to_unix
|
||||||
encrypted_data = encrypt_ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key)
|
encrypted_data = encrypt_ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key)
|
||||||
return Base64.urlsafe_encode(encrypted_data)
|
Base64.urlsafe_encode(encrypted_data)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -20,7 +20,7 @@ module WebVTT
|
|||||||
# Writes an vtt cue with the specified time stamp and contents
|
# Writes an vtt cue with the specified time stamp and contents
|
||||||
def cue(start_time : Time::Span, end_time : Time::Span, text : String)
|
def cue(start_time : Time::Span, end_time : Time::Span, text : String)
|
||||||
timestamp(start_time, end_time)
|
timestamp(start_time, end_time)
|
||||||
@io << self.escape(text)
|
@io << escape(text)
|
||||||
@io << "\n\n"
|
@io << "\n\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ module WebVTT
|
|||||||
end
|
end
|
||||||
|
|
||||||
private def escape(text : String) : String
|
private def escape(text : String) : String
|
||||||
return text.gsub(ESCAPE_SUBSTITUTIONS)
|
text.gsub(ESCAPE_SUBSTITUTIONS)
|
||||||
end
|
end
|
||||||
|
|
||||||
def document(setting_fields : Hash(String, String)? = nil, &)
|
def document(setting_fields : Hash(String, String)? = nil, &)
|
||||||
|
|||||||
@ -68,7 +68,7 @@ module Invidious::HttpServer
|
|||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
# Writes file data to the cache
|
# 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.
|
# 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)
|
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
|
# 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
|
end
|
||||||
|
|
||||||
# Clear cached files.
|
# 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
|
# This is only used in the specs to clear the cache before each handler test
|
||||||
def self.clear_cache
|
def self.clear_cache
|
||||||
@@current_cache_size = 0
|
@@current_cache_size = 0
|
||||||
return @@cached_files.clear
|
@@cached_files.clear
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -14,9 +14,9 @@ module Invidious::HttpServer
|
|||||||
url.query_params = params
|
url.query_params = params
|
||||||
|
|
||||||
if absolute
|
if absolute
|
||||||
return "#{HOST_URL}#{url.request_target}"
|
"#{HOST_URL}#{url.request_target}"
|
||||||
else
|
else
|
||||||
return url.request_target
|
url.request_target
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ module Invidious::HttpServer
|
|||||||
str << params
|
str << params
|
||||||
end
|
end
|
||||||
|
|
||||||
return url
|
url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
|
|||||||
# - Is it an instance with a good uptime?
|
# - Is it an instance with a good uptime?
|
||||||
# - Is it an updated instance?
|
# - Is it an updated instance?
|
||||||
private def refresh_instances
|
private def refresh_instances
|
||||||
raw_instance_list = self.fetch_instances
|
raw_instance_list = fetch_instances
|
||||||
filtered_instance_list = [] of Tuple(String, String)
|
filtered_instance_list = [] of Tuple(String, String)
|
||||||
|
|
||||||
raw_instance_list.each do |instance_data|
|
raw_instance_list.each do |instance_data|
|
||||||
@ -73,7 +73,7 @@ class Invidious::Jobs::InstanceListRefreshJob < Invidious::Jobs::BaseJob
|
|||||||
raw_instance_list = [] of JSON::Any
|
raw_instance_list = [] of JSON::Any
|
||||||
end
|
end
|
||||||
|
|
||||||
return raw_instance_list
|
raw_instance_list
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if the given target instance is outdated
|
# 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)
|
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)
|
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
|
end
|
||||||
|
|
||||||
# Checks if the uptime of the target instance is greater than 90% over a 30 day period
|
# 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["down"].as_bool == false
|
||||||
return true if target_instance_health_monitor["uptime"].as_f < 90
|
return true if target_instance_health_monitor["uptime"].as_f < 90
|
||||||
|
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -13,10 +13,10 @@ module Invidious::JSONify::APIv1
|
|||||||
json.field "error", video.info["reason"] if video.info["reason"]?
|
json.field "error", video.info["reason"] if video.info["reason"]?
|
||||||
|
|
||||||
json.field "videoThumbnails" do
|
json.field "videoThumbnails" do
|
||||||
self.thumbnails(json, video.id)
|
thumbnails(json, video.id)
|
||||||
end
|
end
|
||||||
json.field "storyboards" do
|
json.field "storyboards" do
|
||||||
self.storyboards(json, video.id, video.storyboards)
|
storyboards(json, video.id, video.storyboards)
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "description", video.description
|
json.field "description", video.description
|
||||||
@ -138,7 +138,7 @@ module Invidious::JSONify::APIv1
|
|||||||
|
|
||||||
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
|
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
|
||||||
json.field "container", fmt_info["ext"]
|
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
|
||||||
|
|
||||||
# Livestream chunk infos
|
# Livestream chunk infos
|
||||||
@ -199,7 +199,7 @@ module Invidious::JSONify::APIv1
|
|||||||
|
|
||||||
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
|
if fmt_info = Invidious::Videos::Formats.itag_to_metadata?(fmt["itag"])
|
||||||
json.field "container", fmt_info["ext"]
|
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
|
end
|
||||||
end
|
end
|
||||||
@ -241,7 +241,7 @@ module Invidious::JSONify::APIv1
|
|||||||
json.field "videoId", rv["id"]
|
json.field "videoId", rv["id"]
|
||||||
json.field "title", rv["title"]
|
json.field "title", rv["title"]
|
||||||
json.field "videoThumbnails" do
|
json.field "videoThumbnails" do
|
||||||
self.thumbnails(json, rv["id"])
|
thumbnails(json, rv["id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "author", rv["author"]
|
json.field "author", rv["author"]
|
||||||
|
|||||||
@ -74,7 +74,7 @@ def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
|||||||
|
|
||||||
videos.uniq!(&.id)
|
videos.uniq!(&.id)
|
||||||
videos = videos.first(50)
|
videos = videos.first(50)
|
||||||
return Mix.new({
|
Mix.new({
|
||||||
title: mix_title,
|
title: mix_title,
|
||||||
id: rdid,
|
id: rdid,
|
||||||
videos: videos,
|
videos: videos,
|
||||||
@ -82,18 +82,18 @@ def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def template_mix(mix, listen)
|
def template_mix(mix, listen)
|
||||||
html = <<-END_HTML
|
html = <<-HTML
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/mix?list=#{mix["mixId"]}">
|
<a href="/mix?list=#{mix["mixId"]}">
|
||||||
#{mix["title"]}
|
#{mix["title"]}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="pure-menu pure-menu-scrollable playlist-restricted">
|
<div class="pure-menu pure-menu-scrollable playlist-restricted">
|
||||||
<ol class="pure-menu-list">
|
<ol class="pure-menu-list">
|
||||||
END_HTML
|
HTML
|
||||||
|
|
||||||
mix["videos"].as_a.each do |video|
|
mix["videos"].as_a.each do |video|
|
||||||
html += <<-END_HTML
|
html += <<-HTML
|
||||||
<li class="pure-menu-item">
|
<li class="pure-menu-item">
|
||||||
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}#{listen ? "&listen=1" : ""}">
|
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}#{listen ? "&listen=1" : ""}">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
@ -106,14 +106,14 @@ def template_mix(mix, listen)
|
|||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
END_HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
html += <<-END_HTML
|
html += <<-HTML
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
END_HTML
|
HTML
|
||||||
|
|
||||||
html
|
html
|
||||||
end
|
end
|
||||||
|
|||||||
@ -13,30 +13,30 @@ struct PlaylistVideo
|
|||||||
|
|
||||||
def to_xml(xml : XML::Builder)
|
def to_xml(xml : XML::Builder)
|
||||||
xml.element("entry") do
|
xml.element("entry") do
|
||||||
xml.element("id") { xml.text "yt:video:#{self.id}" }
|
xml.element("id") { xml.text "yt:video:#{id}" }
|
||||||
xml.element("yt:videoId") { xml.text self.id }
|
xml.element("yt:videoId") { xml.text id }
|
||||||
xml.element("yt:channelId") { xml.text self.ucid }
|
xml.element("yt:channelId") { xml.text ucid }
|
||||||
xml.element("title") { xml.text self.title }
|
xml.element("title") { xml.text title }
|
||||||
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?v=#{self.id}")
|
xml.element("link", rel: "alternate", href: "#{HOST_URL}/watch?v=#{id}")
|
||||||
|
|
||||||
xml.element("author") do
|
xml.element("author") do
|
||||||
xml.element("name") { xml.text self.author }
|
xml.element("name") { xml.text author }
|
||||||
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{self.ucid}" }
|
xml.element("uri") { xml.text "#{HOST_URL}/channel/#{ucid}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.element("content", type: "xhtml") do
|
xml.element("content", type: "xhtml") do
|
||||||
xml.element("div", xmlns: "http://www.w3.org/1999/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("a", href: "#{HOST_URL}/watch?v=#{id}") 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
|
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:group") do
|
||||||
xml.element("media:title") { xml.text self.title }
|
xml.element("media:title") { xml.text title }
|
||||||
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg",
|
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{id}/mqdefault.jpg",
|
||||||
width: "320", height: "180")
|
width: "320", height: "180")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -54,15 +54,15 @@ struct PlaylistVideo
|
|||||||
json.object do
|
json.object do
|
||||||
json.field "type", "video"
|
json.field "type", "video"
|
||||||
|
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "videoId", self.id
|
json.field "videoId", id
|
||||||
|
|
||||||
json.field "author", self.author
|
json.field "author", author
|
||||||
json.field "authorId", self.ucid
|
json.field "authorId", ucid
|
||||||
json.field "authorUrl", "/channel/#{self.ucid}"
|
json.field "authorUrl", "/channel/#{ucid}"
|
||||||
|
|
||||||
json.field "videoThumbnails" do
|
json.field "videoThumbnails" do
|
||||||
Invidious::JSONify::APIv1.thumbnails(json, self.id)
|
Invidious::JSONify::APIv1.thumbnails(json, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
if index
|
if index
|
||||||
@ -72,8 +72,8 @@ struct PlaylistVideo
|
|||||||
json.field "index", self.index
|
json.field "index", self.index
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "lengthSeconds", self.length_seconds
|
json.field "lengthSeconds", length_seconds
|
||||||
json.field "liveNow", self.live_now
|
json.field "liveNow", live_now
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -101,14 +101,14 @@ struct Playlist
|
|||||||
def to_json(offset, json : JSON::Builder, video_id : String? = nil)
|
def to_json(offset, json : JSON::Builder, video_id : String? = nil)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", "playlist"
|
json.field "type", "playlist"
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "playlistId", self.id
|
json.field "playlistId", id
|
||||||
json.field "playlistThumbnail", self.thumbnail
|
json.field "playlistThumbnail", thumbnail
|
||||||
|
|
||||||
json.field "author", self.author
|
json.field "author", author
|
||||||
json.field "authorId", self.ucid
|
json.field "authorId", ucid
|
||||||
json.field "authorUrl", "/channel/#{self.ucid}"
|
json.field "authorUrl", "/channel/#{ucid}"
|
||||||
json.field "subtitle", self.subtitle
|
json.field "subtitle", subtitle
|
||||||
|
|
||||||
json.field "authorThumbnails" do
|
json.field "authorThumbnails" do
|
||||||
json.array do
|
json.array do
|
||||||
@ -116,7 +116,7 @@ struct Playlist
|
|||||||
|
|
||||||
qualities.each do |quality|
|
qualities.each do |quality|
|
||||||
json.object do
|
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 "width", quality
|
||||||
json.field "height", quality
|
json.field "height", quality
|
||||||
end
|
end
|
||||||
@ -124,13 +124,13 @@ struct Playlist
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
json.field "description", self.description
|
json.field "description", description
|
||||||
json.field "descriptionHtml", self.description_html
|
json.field "descriptionHtml", description_html
|
||||||
json.field "videoCount", self.video_count
|
json.field "videoCount", video_count
|
||||||
|
|
||||||
json.field "viewCount", self.views
|
json.field "viewCount", views
|
||||||
json.field "updated", self.updated.to_unix
|
json.field "updated", updated.to_unix
|
||||||
json.field "isListed", self.privacy.public?
|
json.field "isListed", privacy.public?
|
||||||
|
|
||||||
json.field "videos" do
|
json.field "videos" do
|
||||||
json.array do
|
json.array do
|
||||||
@ -180,33 +180,33 @@ struct InvidiousPlaylist
|
|||||||
|
|
||||||
module PlaylistPrivacyConverter
|
module PlaylistPrivacyConverter
|
||||||
def self.from_rs(rs)
|
def self.from_rs(rs)
|
||||||
return PlaylistPrivacy.parse(String.new(rs.read(Slice(UInt8))))
|
PlaylistPrivacy.parse(String.new(rs.read(Slice(UInt8))))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_json(offset, json : JSON::Builder, video_id : String? = nil)
|
def to_json(offset, json : JSON::Builder, video_id : String? = nil)
|
||||||
json.object do
|
json.object do
|
||||||
json.field "type", "invidiousPlaylist"
|
json.field "type", "invidiousPlaylist"
|
||||||
json.field "title", self.title
|
json.field "title", title
|
||||||
json.field "playlistId", self.id
|
json.field "playlistId", id
|
||||||
|
|
||||||
json.field "author", self.author
|
json.field "author", author
|
||||||
json.field "authorId", self.ucid
|
json.field "authorId", ucid
|
||||||
json.field "authorUrl", nil
|
json.field "authorUrl", nil
|
||||||
json.field "authorThumbnails", [] of String
|
json.field "authorThumbnails", [] of String
|
||||||
|
|
||||||
json.field "description", html_to_content(self.description_html)
|
json.field "description", html_to_content(description_html)
|
||||||
json.field "descriptionHtml", self.description_html
|
json.field "descriptionHtml", description_html
|
||||||
json.field "videoCount", self.video_count
|
json.field "videoCount", video_count
|
||||||
|
|
||||||
json.field "viewCount", self.views
|
json.field "viewCount", views
|
||||||
json.field "updated", self.updated.to_unix
|
json.field "updated", updated.to_unix
|
||||||
json.field "isListed", self.privacy.public?
|
json.field "isListed", privacy.public?
|
||||||
|
|
||||||
json.field "videos" do
|
json.field "videos" do
|
||||||
json.array do
|
json.array do
|
||||||
if (!offset || offset == 0) && !video_id.nil?
|
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
|
offset = self.index.index(index) || 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -227,7 +227,7 @@ struct InvidiousPlaylist
|
|||||||
|
|
||||||
def thumbnail
|
def thumbnail
|
||||||
# TODO: Get playlist thumbnail from playlist data rather than first video
|
# 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"
|
"/vi/#{@thumbnail_id}/mqdefault.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ struct InvidiousPlaylist
|
|||||||
end
|
end
|
||||||
|
|
||||||
def description_html
|
def description_html
|
||||||
HTML.escape(self.description)
|
HTML.escape(description)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ def create_playlist(title, privacy, user)
|
|||||||
|
|
||||||
Invidious::Database::Playlists.insert(playlist)
|
Invidious::Database::Playlists.insert(playlist)
|
||||||
|
|
||||||
return playlist
|
playlist
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribe_playlist(user, playlist)
|
def subscribe_playlist(user, playlist)
|
||||||
@ -283,7 +283,7 @@ def subscribe_playlist(user, playlist)
|
|||||||
|
|
||||||
Invidious::Database::Playlists.insert(playlist)
|
Invidious::Database::Playlists.insert(playlist)
|
||||||
|
|
||||||
return playlist
|
playlist
|
||||||
end
|
end
|
||||||
|
|
||||||
def produce_playlist_continuation(id, index)
|
def produce_playlist_continuation(id, index)
|
||||||
@ -318,18 +318,18 @@ def produce_playlist_continuation(id, index)
|
|||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
return continuation
|
continuation
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_playlist(plid : String)
|
def get_playlist(plid : String)
|
||||||
if plid.starts_with? "IV"
|
if plid.starts_with? "IV"
|
||||||
if playlist = Invidious::Database::Playlists.select(id: plid)
|
if playlist = Invidious::Database::Playlists.select(id: plid)
|
||||||
return playlist
|
playlist
|
||||||
else
|
else
|
||||||
raise NotFoundException.new("Playlist does not exist.")
|
raise NotFoundException.new("Playlist does not exist.")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return fetch_playlist(plid)
|
fetch_playlist(plid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -398,7 +398,7 @@ def fetch_playlist(plid : String)
|
|||||||
ucid = author_info.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
|
ucid = author_info.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
|
||||||
end
|
end
|
||||||
|
|
||||||
return Playlist.new({
|
Playlist.new({
|
||||||
title: title,
|
title: title,
|
||||||
id: plid,
|
id: plid,
|
||||||
author: author,
|
author: author,
|
||||||
@ -443,7 +443,7 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
|
|||||||
offset += 100
|
offset += 100
|
||||||
end
|
end
|
||||||
|
|
||||||
return videos
|
videos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -504,22 +504,22 @@ def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
|||||||
videos << ProblematicTimelineItem.new(parse_exception: ex)
|
videos << ProblematicTimelineItem.new(parse_exception: ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
return videos
|
videos
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_playlist(playlist, listen)
|
def template_playlist(playlist, listen)
|
||||||
html = <<-END_HTML
|
html = <<-HTML
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/playlist?list=#{playlist["playlistId"]}">
|
<a href="/playlist?list=#{playlist["playlistId"]}">
|
||||||
#{playlist["title"]}
|
#{playlist["title"]}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="pure-menu pure-menu-scrollable playlist-restricted">
|
<div class="pure-menu pure-menu-scrollable playlist-restricted">
|
||||||
<ol class="pure-menu-list">
|
<ol class="pure-menu-list">
|
||||||
END_HTML
|
HTML
|
||||||
|
|
||||||
playlist["videos"].as_a.each do |video|
|
playlist["videos"].as_a.each do |video|
|
||||||
html += <<-END_HTML
|
html += <<-HTML
|
||||||
<li class="pure-menu-item" id="#{video["videoId"]}">
|
<li class="pure-menu-item" id="#{video["videoId"]}">
|
||||||
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}#{listen ? "&listen=1" : ""}">
|
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}#{listen ? "&listen=1" : ""}">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
@ -532,14 +532,14 @@ def template_playlist(playlist, listen)
|
|||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
END_HTML
|
HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
html += <<-END_HTML
|
html += <<-HTML
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
END_HTML
|
HTML
|
||||||
|
|
||||||
html
|
html
|
||||||
end
|
end
|
||||||
|
|||||||
@ -337,10 +337,10 @@ module Invidious::Routes::Account
|
|||||||
end
|
end
|
||||||
|
|
||||||
if redirect
|
if redirect
|
||||||
return env.redirect referer
|
env.redirect referer
|
||||||
else
|
else
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
return "{}"
|
"{}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -140,7 +140,7 @@ module Invidious::Routes::API::Manifest
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return manifest
|
manifest
|
||||||
end
|
end
|
||||||
|
|
||||||
# /api/manifest/dash/id/videoplayback
|
# /api/manifest/dash/id/videoplayback
|
||||||
|
|||||||
@ -17,6 +17,7 @@ module Invidious::Routes::API::V1::Authenticated
|
|||||||
user.preferences.to_json
|
user.preferences.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ameba:disable Naming/AccessorMethodName
|
||||||
def self.set_preferences(env)
|
def self.set_preferences(env)
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
user = env.get("user").as(User)
|
user = env.get("user").as(User)
|
||||||
@ -35,7 +36,7 @@ module Invidious::Routes::API::V1::Authenticated
|
|||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
user = env.get("user").as(User)
|
user = env.get("user").as(User)
|
||||||
|
|
||||||
return Invidious::User::Export.to_invidious(user)
|
Invidious::User::Export.to_invidious(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.import_invidious(env)
|
def self.import_invidious(env)
|
||||||
@ -71,7 +72,7 @@ module Invidious::Routes::API::V1::Authenticated
|
|||||||
end
|
end
|
||||||
watched ||= [] of String
|
watched ||= [] of String
|
||||||
|
|
||||||
return watched.to_json
|
watched.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.mark_watched(env)
|
def self.mark_watched(env)
|
||||||
@ -423,7 +424,7 @@ module Invidious::Routes::API::V1::Authenticated
|
|||||||
env.response.content_type = "text/html"
|
env.response.content_type = "text/html"
|
||||||
|
|
||||||
csrf_token = generate_response(sid, {":authorize_token"}, HMAC_KEY, use_nonce: true)
|
csrf_token = generate_response(sid, {":authorize_token"}, HMAC_KEY, use_nonce: true)
|
||||||
return templated "user/authorize_token"
|
templated "user/authorize_token"
|
||||||
else
|
else
|
||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
@ -482,7 +483,7 @@ module Invidious::Routes::API::V1::Authenticated
|
|||||||
env.response.content_type = "text/event-stream"
|
env.response.content_type = "text/event-stream"
|
||||||
|
|
||||||
raw_topics = env.params.body["topics"]? || env.params.query["topics"]?
|
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
|
topics ||= [] of String
|
||||||
|
|
||||||
create_notification_stream(env, topics, CONNECTION_CHANNEL)
|
create_notification_stream(env, topics, CONNECTION_CHANNEL)
|
||||||
|
|||||||
@ -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("sort_by") if env.params.query.has_key?("sort_by")
|
||||||
env.params.query.delete("continuation") if env.params.query.has_key?("continuation")
|
env.params.query.delete("continuation") if env.params.query.has_key?("continuation")
|
||||||
|
|
||||||
return self.videos(env)
|
videos(env)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.videos(env)
|
def self.videos(env)
|
||||||
@ -173,7 +173,7 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "videos" do
|
json.field "videos" do
|
||||||
json.array do
|
json.array do
|
||||||
@ -219,7 +219,7 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "videos" do
|
json.field "videos" do
|
||||||
json.array do
|
json.array do
|
||||||
@ -265,7 +265,7 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "videos" do
|
json.field "videos" do
|
||||||
json.array do
|
json.array do
|
||||||
@ -416,7 +416,7 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
begin
|
begin
|
||||||
fetch_channel_community(ucid, continuation, locale, format, thin_mode)
|
fetch_channel_community(ucid, continuation, locale, format, thin_mode)
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
error_json(500, ex)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -444,7 +444,7 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
begin
|
begin
|
||||||
fetch_channel_community_post(ucid, id, locale, format, thin_mode)
|
fetch_channel_community_post(ucid, id, locale, format, thin_mode)
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
error_json(500, ex)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -472,7 +472,7 @@ module Invidious::Routes::API::V1::Channels
|
|||||||
else
|
else
|
||||||
comments = YoutubeAPI.browse(continuation: continuation)
|
comments = YoutubeAPI.browse(continuation: continuation)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def self.channels(env)
|
def self.channels(env)
|
||||||
|
|||||||
@ -4,10 +4,10 @@ module Invidious::Routes::API::V1::Misc
|
|||||||
env.response.content_type = "application/json"
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
if !CONFIG.statistics_enabled
|
if !CONFIG.statistics_enabled
|
||||||
return {"software" => SOFTWARE}.to_json
|
{"software" => SOFTWARE}.to_json
|
||||||
else
|
else
|
||||||
# Calculate playback success rate
|
# 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))
|
tracker = tracker.as(Hash(String, Int64 | Float64))
|
||||||
|
|
||||||
if !tracker.empty?
|
if !tracker.empty?
|
||||||
@ -22,7 +22,7 @@ module Invidious::Routes::API::V1::Misc
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Invidious::Jobs::StatisticsRefreshJob::STATISTICS.to_json
|
Invidious::Jobs::StatisticsRefreshJob::STATISTICS.to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,7 @@ module Invidious::Routes::API::V1::Search
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
error_json(500, ex)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
return JSON.build do |json|
|
JSON.build do |json|
|
||||||
Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy)
|
Invidious::JSONify::APIv1.video(video, json, locale: locale, proxy: proxy)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -247,7 +247,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
# videojs-vtt-thumbnails is not compliant to the VTT specification, it
|
# videojs-vtt-thumbnails is not compliant to the VTT specification, it
|
||||||
# doesn't unescape the HTML entities, so we have to do it here:
|
# doesn't unescape the HTML entities, so we have to do it here:
|
||||||
# TODO: remove this when we migrate to VideoJS 8
|
# TODO: remove this when we migrate to VideoJS 8
|
||||||
return HTML.unescape(vtt_file)
|
HTML.unescape(vtt_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.annotations(env)
|
def self.annotations(env)
|
||||||
@ -352,7 +352,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
return comments
|
comments
|
||||||
elsif source == "reddit"
|
elsif source == "reddit"
|
||||||
sort_by ||= "confidence"
|
sort_by ||= "confidence"
|
||||||
|
|
||||||
@ -418,7 +418,7 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
return JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "startTime", start_time
|
json.field "startTime", start_time
|
||||||
json.field "endTime", end_time
|
json.field "endTime", end_time
|
||||||
@ -513,6 +513,6 @@ module Invidious::Routes::API::V1::Videos
|
|||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
return transcript.to_json
|
transcript.to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,15 +4,15 @@ module Invidious::Routes::Channels
|
|||||||
# Redirection for unsupported routes ("tabs")
|
# Redirection for unsupported routes ("tabs")
|
||||||
def self.redirect_home(env)
|
def self.redirect_home(env)
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
return env.redirect "/channel/#{URI.encode_www_form(ucid)}"
|
env.redirect "/channel/#{URI.encode_www_form(ucid)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.home(env)
|
def self.home(env)
|
||||||
self.videos(env)
|
videos(env)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.videos(env)
|
def self.videos(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -64,7 +64,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.shorts(env)
|
def self.shorts(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -99,7 +99,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.streams(env)
|
def self.streams(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -134,7 +134,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.playlists(env)
|
def self.playlists(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -158,7 +158,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.podcasts(env)
|
def self.podcasts(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -178,7 +178,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.releases(env)
|
def self.releases(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -198,7 +198,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.courses(env)
|
def self.courses(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -220,7 +220,7 @@ module Invidious::Routes::Channels
|
|||||||
def self.community(env)
|
def self.community(env)
|
||||||
return env.redirect env.request.path.sub("posts", "community") if env.request.path.split("/").last == "posts"
|
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)
|
if !data.is_a?(Tuple)
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
@ -298,7 +298,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.channels(env)
|
def self.channels(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
@ -318,7 +318,7 @@ module Invidious::Routes::Channels
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.about(env)
|
def self.about(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = fetch_basic_information(env)
|
||||||
if !data.is_a?(Tuple)
|
if !data.is_a?(Tuple)
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
@ -365,7 +365,7 @@ module Invidious::Routes::Channels
|
|||||||
|
|
||||||
url += "?#{invidious_url_params}" if !invidious_url_params.empty?
|
url += "?#{invidious_url_params}" if !invidious_url_params.empty?
|
||||||
|
|
||||||
return env.redirect url
|
env.redirect url
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handles redirects for the /profile endpoint
|
# Handles redirects for the /profile endpoint
|
||||||
@ -378,7 +378,7 @@ module Invidious::Routes::Channels
|
|||||||
|
|
||||||
user = env.params.query["user"]?
|
user = env.params.query["user"]?
|
||||||
if !user
|
if !user
|
||||||
return error_template(404, "This channel does not exist.")
|
error_template(404, "This channel does not exist.")
|
||||||
else
|
else
|
||||||
env.redirect "/user/#{user}#{uri_params}"
|
env.redirect "/user/#{user}#{uri_params}"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -9,7 +9,7 @@ module Invidious::Routes::Companion
|
|||||||
begin
|
begin
|
||||||
COMPANION_POOL.client do |wrapper|
|
COMPANION_POOL.client do |wrapper|
|
||||||
wrapper.client.get(url, env.request.headers) do |resp|
|
wrapper.client.get(url, env.request.headers) do |resp|
|
||||||
return self.proxy_companion(env, resp)
|
return proxy_companion(env, resp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
@ -26,7 +26,7 @@ module Invidious::Routes::Companion
|
|||||||
begin
|
begin
|
||||||
COMPANION_POOL.client do |wrapper|
|
COMPANION_POOL.client do |wrapper|
|
||||||
wrapper.client.post(url, env.request.headers, env.request.body) do |resp|
|
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
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
@ -42,7 +42,7 @@ module Invidious::Routes::Companion
|
|||||||
begin
|
begin
|
||||||
COMPANION_POOL.client do |wrapper|
|
COMPANION_POOL.client do |wrapper|
|
||||||
wrapper.client.options(url, env.request.headers) do |resp|
|
wrapper.client.options(url, env.request.headers) do |resp|
|
||||||
return self.proxy_companion(env, resp)
|
return proxy_companion(env, resp)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
@ -55,6 +55,6 @@ module Invidious::Routes::Companion
|
|||||||
env.response.headers[key] = value
|
env.response.headers[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
return IO.copy response.body_io, env.response
|
IO.copy response.body_io, env.response
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -119,7 +119,6 @@ module Invidious::Routes::Embed
|
|||||||
end
|
end
|
||||||
|
|
||||||
return env.redirect url
|
return env.redirect url
|
||||||
else nil # Continue
|
|
||||||
end
|
end
|
||||||
|
|
||||||
params = process_video_params(env.params.query, preferences)
|
params = process_video_params(env.params.query, preferences)
|
||||||
@ -182,14 +181,14 @@ module Invidious::Routes::Embed
|
|||||||
|
|
||||||
captions = video.captions
|
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.name) ||
|
||||||
params.preferred_captions.includes?(caption.language_code.split("-")[0])
|
params.preferred_captions.includes?(caption.language_code.split("-")[0])
|
||||||
}
|
end
|
||||||
preferred_captions.sort_by! { |caption|
|
preferred_captions.sort_by! do |caption|
|
||||||
(params.preferred_captions.index(caption.name) ||
|
(params.preferred_captions.index(caption.name) ||
|
||||||
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
|
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
|
||||||
}
|
end
|
||||||
captions = captions - preferred_captions
|
captions = captions - preferred_captions
|
||||||
|
|
||||||
aspect_ratio = nil
|
aspect_ratio = nil
|
||||||
|
|||||||
@ -322,7 +322,6 @@ module Invidious::Routes::Feeds
|
|||||||
request_target = URI.parse(node[attribute.name]).request_target
|
request_target = URI.parse(node[attribute.name]).request_target
|
||||||
query_string_opt = request_target.starts_with?("/watch?v=") ? "&#{params}" : ""
|
query_string_opt = request_target.starts_with?("/watch?v=") ? "&#{params}" : ""
|
||||||
node[attribute.name] = "#{HOST_URL}#{request_target}#{query_string_opt}"
|
node[attribute.name] = "#{HOST_URL}#{request_target}#{query_string_opt}"
|
||||||
else nil # Skip
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -13,7 +13,7 @@ module Invidious::Routes::Images
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
GGPHT_POOL.client &.get(url, headers) do |resp|
|
GGPHT_POOL.client &.get(url, headers) do |resp|
|
||||||
return self.proxy_image(env, resp)
|
return proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
end
|
end
|
||||||
@ -44,7 +44,7 @@ module Invidious::Routes::Images
|
|||||||
begin
|
begin
|
||||||
get_ytimg_pool(authority).client &.get(url, headers) do |resp|
|
get_ytimg_pool(authority).client &.get(url, headers) do |resp|
|
||||||
env.response.headers["Connection"] = "close"
|
env.response.headers["Connection"] = "close"
|
||||||
return self.proxy_image(env, resp)
|
return proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
end
|
end
|
||||||
@ -66,7 +66,7 @@ module Invidious::Routes::Images
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
get_ytimg_pool("i9").client &.get(url, headers) do |resp|
|
get_ytimg_pool("i9").client &.get(url, headers) do |resp|
|
||||||
return self.proxy_image(env, resp)
|
return proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
end
|
end
|
||||||
@ -128,7 +128,7 @@ module Invidious::Routes::Images
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
get_ytimg_pool("i").client &.get(url, headers) do |resp|
|
get_ytimg_pool("i").client &.get(url, headers) do |resp|
|
||||||
return self.proxy_image(env, resp)
|
return proxy_image(env, resp)
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
end
|
end
|
||||||
@ -148,6 +148,6 @@ module Invidious::Routes::Images
|
|||||||
return env.response.headers.delete("Transfer-Encoding")
|
return env.response.headers.delete("Transfer-Encoding")
|
||||||
end
|
end
|
||||||
|
|
||||||
return proxy_file(response, env)
|
proxy_file(response, env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -347,7 +347,6 @@ module Invidious::Routes::PreferencesRoute
|
|||||||
response: error_template(415, "Uploaded file is too large")
|
response: error_template(415, "Uploaded file is too large")
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
else nil # Ignore
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -241,7 +241,7 @@ module Invidious::Routes::VideoPlayback
|
|||||||
query_params = HTTP::Params.new(raw_params)
|
query_params = HTTP::Params.new(raw_params)
|
||||||
|
|
||||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
return env.redirect "/videoplayback?#{query_params}"
|
env.redirect "/videoplayback?#{query_params}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# /videoplayback/* && /videoplayback/*
|
# /videoplayback/* && /videoplayback/*
|
||||||
@ -307,6 +307,6 @@ module Invidious::Routes::VideoPlayback
|
|||||||
url += "&title=#{URI.encode_www_form(title, space_to_plus: false)}" if title
|
url += "&title=#{URI.encode_www_form(title, space_to_plus: false)}" if title
|
||||||
end
|
end
|
||||||
|
|
||||||
return env.redirect url
|
env.redirect url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -145,14 +145,14 @@ module Invidious::Routes::Watch
|
|||||||
|
|
||||||
captions = video.captions
|
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.name) ||
|
||||||
params.preferred_captions.includes?(caption.language_code.split("-")[0])
|
params.preferred_captions.includes?(caption.language_code.split("-")[0])
|
||||||
}
|
end
|
||||||
preferred_captions.sort_by! { |caption|
|
preferred_captions.sort_by! do |caption|
|
||||||
(params.preferred_captions.index(caption.name) ||
|
(params.preferred_captions.index(caption.name) ||
|
||||||
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
|
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
|
||||||
}
|
end
|
||||||
captions = captions - preferred_captions
|
captions = captions - preferred_captions
|
||||||
|
|
||||||
aspect_ratio = "16:9"
|
aspect_ratio = "16:9"
|
||||||
@ -215,7 +215,7 @@ module Invidious::Routes::Watch
|
|||||||
url += "&#{env.params.query}"
|
url += "&#{env.params.query}"
|
||||||
end
|
end
|
||||||
|
|
||||||
return env.redirect url
|
env.redirect url
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.mark_watched(env)
|
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
|
env.params.query["end"] = end_time.to_s if end_time != nil
|
||||||
end
|
end
|
||||||
|
|
||||||
return env.redirect "/watch?v=#{video_id}&#{env.params.query}"
|
env.redirect "/watch?v=#{video_id}&#{env.params.query}"
|
||||||
else
|
else
|
||||||
return error_template(404, "The requested clip doesn't exist")
|
error_template(404, "The requested clip doesn't exist")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -330,16 +330,16 @@ module Invidious::Routes::Watch
|
|||||||
env.params.query["title"] = filename
|
env.params.query["title"] = filename
|
||||||
env.params.query["label"] = URI.decode_www_form(label.as_s)
|
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
|
elsif itag = download_widget["itag"]?.try &.as_i.to_s
|
||||||
# URL params specific to /latest_version
|
# URL params specific to /latest_version
|
||||||
env.params.query["id"] = video_id
|
env.params.query["id"] = video_id
|
||||||
env.params.query["title"] = filename
|
env.params.query["title"] = filename
|
||||||
env.params.query["local"] = "true"
|
env.params.query["local"] = "true"
|
||||||
|
|
||||||
return Invidious::Routes::VideoPlayback.latest_version(env)
|
Invidious::Routes::VideoPlayback.latest_version(env)
|
||||||
else
|
else
|
||||||
return error_template(400, "Invalid label or itag")
|
error_template(400, "Invalid label or itag")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -3,12 +3,12 @@ module Invidious::Routing
|
|||||||
|
|
||||||
{% for http_method in {"get", "post", "delete", "options", "patch", "put"} %}
|
{% 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}})
|
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
|
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)
|
\{{ controller }}.\{{ method.id }}(env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -42,11 +42,11 @@ module Invidious::Routing
|
|||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
self.register_image_routes
|
register_image_routes
|
||||||
self.register_api_v1_routes
|
register_api_v1_routes
|
||||||
self.register_api_manifest_routes
|
register_api_manifest_routes
|
||||||
self.register_video_playback_routes
|
register_video_playback_routes
|
||||||
self.register_companion_routes
|
register_companion_routes
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -238,91 +238,91 @@ module Invidious::Routing
|
|||||||
|
|
||||||
def register_api_v1_routes
|
def register_api_v1_routes
|
||||||
{% begin %}
|
{% begin %}
|
||||||
{{namespace = Routes::API::V1}}
|
{{ namespace = Routes::API::V1 }}
|
||||||
|
|
||||||
# Videos
|
# Videos
|
||||||
get "/api/v1/videos/:id", {{namespace}}::Videos, :videos
|
get "/api/v1/videos/:id", {{ namespace }}::Videos, :videos
|
||||||
get "/api/v1/storyboards/:id", {{namespace}}::Videos, :storyboards
|
get "/api/v1/storyboards/:id", {{ namespace }}::Videos, :storyboards
|
||||||
get "/api/v1/captions/:id", {{namespace}}::Videos, :captions
|
get "/api/v1/captions/:id", {{ namespace }}::Videos, :captions
|
||||||
get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations
|
get "/api/v1/annotations/:id", {{ namespace }}::Videos, :annotations
|
||||||
get "/api/v1/comments/:id", {{namespace}}::Videos, :comments
|
get "/api/v1/comments/:id", {{ namespace }}::Videos, :comments
|
||||||
get "/api/v1/clips/:id", {{namespace}}::Videos, :clips
|
get "/api/v1/clips/:id", {{ namespace }}::Videos, :clips
|
||||||
get "/api/v1/transcripts/:id", {{namespace}}::Videos, :transcripts
|
get "/api/v1/transcripts/:id", {{ namespace }}::Videos, :transcripts
|
||||||
|
|
||||||
# Feeds
|
# Feeds
|
||||||
get "/api/v1/trending", {{namespace}}::Feeds, :trending
|
get "/api/v1/trending", {{ namespace }}::Feeds, :trending
|
||||||
get "/api/v1/popular", {{namespace}}::Feeds, :popular
|
get "/api/v1/popular", {{ namespace }}::Feeds, :popular
|
||||||
|
|
||||||
# Channels
|
# Channels
|
||||||
get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
|
get "/api/v1/channels/:ucid", {{ namespace }}::Channels, :home
|
||||||
get "/api/v1/channels/:ucid/latest", {{namespace}}::Channels, :latest
|
get "/api/v1/channels/:ucid/latest", {{ namespace }}::Channels, :latest
|
||||||
get "/api/v1/channels/:ucid/videos", {{namespace}}::Channels, :videos
|
get "/api/v1/channels/:ucid/videos", {{ namespace }}::Channels, :videos
|
||||||
get "/api/v1/channels/:ucid/shorts", {{namespace}}::Channels, :shorts
|
get "/api/v1/channels/:ucid/shorts", {{ namespace }}::Channels, :shorts
|
||||||
get "/api/v1/channels/:ucid/streams", {{namespace}}::Channels, :streams
|
get "/api/v1/channels/:ucid/streams", {{ namespace }}::Channels, :streams
|
||||||
get "/api/v1/channels/:ucid/podcasts", {{namespace}}::Channels, :podcasts
|
get "/api/v1/channels/:ucid/podcasts", {{ namespace }}::Channels, :podcasts
|
||||||
get "/api/v1/channels/:ucid/releases", {{namespace}}::Channels, :releases
|
get "/api/v1/channels/:ucid/releases", {{ namespace }}::Channels, :releases
|
||||||
get "/api/v1/channels/:ucid/courses", {{namespace}}::Channels, :courses
|
get "/api/v1/channels/:ucid/courses", {{ namespace }}::Channels, :courses
|
||||||
get "/api/v1/channels/:ucid/playlists", {{namespace}}::Channels, :playlists
|
get "/api/v1/channels/:ucid/playlists", {{ namespace }}::Channels, :playlists
|
||||||
get "/api/v1/channels/:ucid/community", {{namespace}}::Channels, :community
|
get "/api/v1/channels/:ucid/community", {{ namespace }}::Channels, :community
|
||||||
get "/api/v1/channels/:ucid/posts", {{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/channels", {{ namespace }}::Channels, :channels
|
||||||
get "/api/v1/channels/:ucid/search", {{namespace}}::Channels, :search
|
get "/api/v1/channels/:ucid/search", {{ namespace }}::Channels, :search
|
||||||
|
|
||||||
# Posts
|
# Posts
|
||||||
get "/api/v1/post/:id", {{namespace}}::Channels, :post
|
get "/api/v1/post/:id", {{ namespace }}::Channels, :post
|
||||||
get "/api/v1/post/:id/comments", {{namespace}}::Channels, :post_comments
|
get "/api/v1/post/:id/comments", {{ namespace }}::Channels, :post_comments
|
||||||
|
|
||||||
# 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community
|
# 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/comments/:ucid", {{ namespace }}::Channels, :channel_comments_redirect
|
||||||
get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect
|
get "/api/v1/channels/:ucid/comments", {{ namespace }}::Channels, :channel_comments_redirect
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
get "/api/v1/search", {{namespace}}::Search, :search
|
get "/api/v1/search", {{ namespace }}::Search, :search
|
||||||
get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions
|
get "/api/v1/search/suggestions", {{ namespace }}::Search, :search_suggestions
|
||||||
get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag
|
get "/api/v1/hashtag/:hashtag", {{ namespace }}::Search, :hashtag
|
||||||
|
|
||||||
|
|
||||||
# Authenticated
|
# Authenticated
|
||||||
|
|
||||||
get "/api/v1/auth/preferences", {{namespace}}::Authenticated, :get_preferences
|
get "/api/v1/auth/preferences", {{ namespace }}::Authenticated, :get_preferences
|
||||||
post "/api/v1/auth/preferences", {{namespace}}::Authenticated, :set_preferences
|
post "/api/v1/auth/preferences", {{ namespace }}::Authenticated, :set_preferences
|
||||||
|
|
||||||
get "/api/v1/auth/export/invidious", {{namespace}}::Authenticated, :export_invidious
|
get "/api/v1/auth/export/invidious", {{ namespace }}::Authenticated, :export_invidious
|
||||||
post "/api/v1/auth/import/invidious", {{namespace}}::Authenticated, :import_invidious
|
post "/api/v1/auth/import/invidious", {{ namespace }}::Authenticated, :import_invidious
|
||||||
|
|
||||||
get "/api/v1/auth/history", {{namespace}}::Authenticated, :get_history
|
get "/api/v1/auth/history", {{ namespace }}::Authenticated, :get_history
|
||||||
post "/api/v1/auth/history/:id", {{namespace}}::Authenticated, :mark_watched
|
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/:id", {{ namespace }}::Authenticated, :mark_unwatched
|
||||||
delete "/api/v1/auth/history", {{namespace}}::Authenticated, :clear_history
|
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
|
get "/api/v1/auth/subscriptions", {{ namespace }}::Authenticated, :get_subscriptions
|
||||||
post "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :subscribe_channel
|
post "/api/v1/auth/subscriptions/:ucid", {{ namespace }}::Authenticated, :subscribe_channel
|
||||||
delete "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :unsubscribe_channel
|
delete "/api/v1/auth/subscriptions/:ucid", {{ namespace }}::Authenticated, :unsubscribe_channel
|
||||||
|
|
||||||
get "/api/v1/auth/playlists", {{namespace}}::Authenticated, :list_playlists
|
get "/api/v1/auth/playlists", {{ namespace }}::Authenticated, :list_playlists
|
||||||
post "/api/v1/auth/playlists", {{namespace}}::Authenticated, :create_playlist
|
post "/api/v1/auth/playlists", {{ namespace }}::Authenticated, :create_playlist
|
||||||
patch "/api/v1/auth/playlists/:plid",{{namespace}}:: Authenticated, :update_playlist_attribute
|
patch "/api/v1/auth/playlists/:plid",{{ namespace }}:: Authenticated, :update_playlist_attribute
|
||||||
delete "/api/v1/auth/playlists/:plid", {{namespace}}::Authenticated, :delete_playlist
|
delete "/api/v1/auth/playlists/:plid", {{ namespace }}::Authenticated, :delete_playlist
|
||||||
post "/api/v1/auth/playlists/:plid/videos", {{namespace}}::Authenticated, :insert_video_into_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
|
delete "/api/v1/auth/playlists/:plid/videos/:index", {{ namespace }}::Authenticated, :delete_video_in_playlist
|
||||||
|
|
||||||
get "/api/v1/auth/tokens", {{namespace}}::Authenticated, :get_tokens
|
get "/api/v1/auth/tokens", {{ namespace }}::Authenticated, :get_tokens
|
||||||
post "/api/v1/auth/tokens/register", {{namespace}}::Authenticated, :register_token
|
post "/api/v1/auth/tokens/register", {{ namespace }}::Authenticated, :register_token
|
||||||
post "/api/v1/auth/tokens/unregister", {{namespace}}::Authenticated, :unregister_token
|
post "/api/v1/auth/tokens/unregister", {{ namespace }}::Authenticated, :unregister_token
|
||||||
|
|
||||||
if CONFIG.enable_user_notifications
|
if CONFIG.enable_user_notifications
|
||||||
get "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
|
get "/api/v1/auth/notifications", {{ namespace }}::Authenticated, :notifications
|
||||||
post "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
|
post "/api/v1/auth/notifications", {{ namespace }}::Authenticated, :notifications
|
||||||
end
|
end
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
get "/api/v1/stats", {{namespace}}::Misc, :stats
|
get "/api/v1/stats", {{ namespace }}::Misc, :stats
|
||||||
get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist
|
get "/api/v1/playlists/:plid", {{ namespace }}::Misc, :get_playlist
|
||||||
get "/api/v1/auth/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/mixes/:rdid", {{ namespace }}::Misc, :mixes
|
||||||
get "/api/v1/resolveurl", {{namespace}}::Misc, :resolve_url
|
get "/api/v1/resolveurl", {{ namespace }}::Misc, :resolve_url
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -28,5 +28,5 @@ def produce_channel_search_continuation(ucid, query, page)
|
|||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
return continuation
|
continuation
|
||||||
end
|
end
|
||||||
|
|||||||
@ -80,7 +80,7 @@ module Invidious::Search
|
|||||||
end
|
end
|
||||||
|
|
||||||
def default? : Bool
|
def default? : Bool
|
||||||
return @date.none? && @type.all? && @duration.none? && \
|
@date.none? && @type.all? && @duration.none? && \
|
||||||
@features.none? && @sort.relevance?
|
@features.none? && @sort.relevance?
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ module Invidious::Search
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return features
|
features
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.format_features(features : Features) : String
|
def self.format_features(features : Features) : String
|
||||||
@ -132,7 +132,7 @@ module Invidious::Search
|
|||||||
str << "location" if features.location?
|
str << "location" if features.location?
|
||||||
str << "purchased" if features.purchased?
|
str << "purchased" if features.purchased?
|
||||||
|
|
||||||
return str.join(',')
|
str.join(',')
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.from_legacy_filters(str : String) : {Filters, String, String, Bool}
|
def self.from_legacy_filters(str : String) : {Filters, String, String, Bool}
|
||||||
@ -230,7 +230,7 @@ module Invidious::Search
|
|||||||
params.delete("sort")
|
params.delete("sort")
|
||||||
end
|
end
|
||||||
|
|
||||||
return filters
|
filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_iv_params : HTTP::Params
|
def to_iv_params : HTTP::Params
|
||||||
@ -249,7 +249,7 @@ module Invidious::Search
|
|||||||
raw_params["features"] = [Filters.format_features(@features)]
|
raw_params["features"] = [Filters.format_features(@features)]
|
||||||
end
|
end
|
||||||
|
|
||||||
return HTTP::Params.new(raw_params)
|
HTTP::Params.new(raw_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -304,7 +304,7 @@ module Invidious::Search
|
|||||||
# See https://github.com/iv-org/invidious/issues/4398
|
# See https://github.com/iv-org/invidious/issues/4398
|
||||||
object["30:varint"] = 1.to_i64
|
object["30:varint"] = 1.to_i64
|
||||||
|
|
||||||
return object
|
object
|
||||||
.try { |i| Protodec::Any.cast_json(i) }
|
.try { |i| Protodec::Any.cast_json(i) }
|
||||||
.try { |i| Protodec::Any.from_json(i) }
|
.try { |i| Protodec::Any.from_json(i) }
|
||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
@ -370,7 +370,7 @@ module Invidious::Search
|
|||||||
|
|
||||||
# Remove URL parameter and return result
|
# Remove URL parameter and return result
|
||||||
params.delete("sp")
|
params.delete("sp")
|
||||||
return filters
|
filters
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -10,7 +10,7 @@ module Invidious::Search
|
|||||||
initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config)
|
initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config)
|
||||||
|
|
||||||
items, _ = extract_items(initial_data)
|
items, _ = extract_items(initial_data)
|
||||||
return items.reject!(Category)
|
items.reject!(Category)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Search a youtube channel
|
# Search a youtube channel
|
||||||
@ -32,14 +32,14 @@ module Invidious::Search
|
|||||||
response_json = YoutubeAPI.browse(continuation)
|
response_json = YoutubeAPI.browse(continuation)
|
||||||
|
|
||||||
items, _ = extract_items(response_json, "", ucid)
|
items, _ = extract_items(response_json, "", ucid)
|
||||||
return items.reject!(Category)
|
items.reject!(Category)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Search inside of user subscriptions
|
# Search inside of user subscriptions
|
||||||
def subscriptions(query : Query, user : Invidious::User) : Array(ChannelVideo)
|
def subscriptions(query : Query, user : Invidious::User) : Array(ChannelVideo)
|
||||||
view_name = "subscriptions_#{sha256(user.email)}"
|
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
|
SELECT id,title,published,updated,ucid,author,length_seconds
|
||||||
FROM (
|
FROM (
|
||||||
SELECT *,
|
SELECT *,
|
||||||
@ -47,10 +47,8 @@ module Invidious::Search
|
|||||||
to_tsvector(#{view_name}.author)
|
to_tsvector(#{view_name}.author)
|
||||||
as document
|
as document
|
||||||
FROM #{view_name}
|
FROM #{view_name}
|
||||||
) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;",
|
) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;
|
||||||
query.text, (query.page - 1) * 20,
|
SQL
|
||||||
as: ChannelVideo
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -25,19 +25,19 @@ module Invidious::Search
|
|||||||
|
|
||||||
# Return true if @raw_query is either `nil` or empty
|
# Return true if @raw_query is either `nil` or empty
|
||||||
private def empty_raw_query?
|
private def empty_raw_query?
|
||||||
return @raw_query.empty?
|
@raw_query.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Same as `empty_raw_query?`, but named for external use
|
# Same as `empty_raw_query?`, but named for external use
|
||||||
def empty?
|
def empty?
|
||||||
return self.empty_raw_query?
|
empty_raw_query?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Getter for the query string.
|
# Getter for the query string.
|
||||||
# It is named `text` to reduce confusion (`search_query.text` makes more
|
# It is named `text` to reduce confusion (`search_query.text` makes more
|
||||||
# sense than `search_query.query`)
|
# sense than `search_query.query`)
|
||||||
def text
|
def text
|
||||||
return @query
|
@query
|
||||||
end
|
end
|
||||||
|
|
||||||
# Initialize a new search query.
|
# Initialize a new search query.
|
||||||
@ -70,7 +70,7 @@ module Invidious::Search
|
|||||||
|
|
||||||
# Stop here if raw query is empty
|
# Stop here if raw query is empty
|
||||||
# NOTE: maybe raise in the future?
|
# NOTE: maybe raise in the future?
|
||||||
return if self.empty_raw_query?
|
return if empty_raw_query?
|
||||||
|
|
||||||
# Specific handling
|
# Specific handling
|
||||||
case @type
|
case @type
|
||||||
@ -120,7 +120,7 @@ module Invidious::Search
|
|||||||
items = [] of SearchItem
|
items = [] of SearchItem
|
||||||
|
|
||||||
# Don't bother going further if search query is empty
|
# 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
|
case @type
|
||||||
when .regular?, .playlist?
|
when .regular?, .playlist?
|
||||||
@ -135,7 +135,7 @@ module Invidious::Search
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return items
|
items
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the HTTP::Params corresponding to this Query (invidious format)
|
# Return the HTTP::Params corresponding to this Query (invidious format)
|
||||||
@ -145,7 +145,7 @@ module Invidious::Search
|
|||||||
params["q"] = @query
|
params["q"] = @query
|
||||||
params["channel"] = @channel if !@channel.empty?
|
params["channel"] = @channel if !@channel.empty?
|
||||||
|
|
||||||
return params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if the query is a standalone URL
|
# Checks if the query is a standalone URL
|
||||||
@ -160,7 +160,7 @@ module Invidious::Search
|
|||||||
return false if !@filters.default?
|
return false if !@filters.default?
|
||||||
|
|
||||||
# Simple heuristics: domain name
|
# Simple heuristics: domain name
|
||||||
return @raw_query.starts_with?(
|
@raw_query.starts_with?(
|
||||||
/(https?:\/\/)?(www\.)?(m\.)?youtu(\.be|be\.com)\//
|
/(https?:\/\/)?(www\.)?(m\.)?youtu(\.be|be\.com)\//
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -35,7 +35,7 @@ def fetch_trending(trending_type, region, locale)
|
|||||||
# Ignore the smaller categories, as they generally contain a sponsored
|
# Ignore the smaller categories, as they generally contain a sponsored
|
||||||
# channel, which brings a lot of noise on the trending page.
|
# channel, which brings a lot of noise on the trending page.
|
||||||
# See: https://github.com/iv-org/invidious/issues/2989
|
# 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)
|
extracted.concat itm.contents.select(SearchItem)
|
||||||
else
|
else
|
||||||
|
|||||||
@ -19,29 +19,29 @@ struct Invidious::User
|
|||||||
hour = 12
|
hour = 12
|
||||||
end
|
end
|
||||||
|
|
||||||
clock_svg = <<-END_SVG
|
clock_svg = <<-SVG
|
||||||
<svg viewBox="0 0 100 100" width="200px" height="200px">
|
<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>
|
<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="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="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="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="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="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="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="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="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="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="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="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="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>
|
<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="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="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>
|
<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>
|
||||||
END_SVG
|
SVG
|
||||||
|
|
||||||
image = "data:image/png;base64,"
|
image = "data:image/png;base64,"
|
||||||
image += Process.run(%(rsvg-convert -w 400 -h 400 -b none -f png), shell: true,
|
image += Process.run(%(rsvg-convert -w 400 -h 400 -b none -f png), shell: true,
|
||||||
@ -53,7 +53,7 @@ struct Invidious::User
|
|||||||
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
|
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
|
||||||
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
|
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
|
||||||
|
|
||||||
return {
|
{
|
||||||
question: image,
|
question: image,
|
||||||
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
|
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ struct Invidious::User
|
|||||||
# Session ID (SID) cookie
|
# Session ID (SID) cookie
|
||||||
# Parameter "domain" comes from the global config
|
# Parameter "domain" comes from the global config
|
||||||
def sid(domain : String?, sid) : HTTP::Cookie
|
def sid(domain : String?, sid) : HTTP::Cookie
|
||||||
return HTTP::Cookie.new(
|
HTTP::Cookie.new(
|
||||||
name: "SID",
|
name: "SID",
|
||||||
domain: domain,
|
domain: domain,
|
||||||
value: sid,
|
value: sid,
|
||||||
@ -25,7 +25,7 @@ struct Invidious::User
|
|||||||
# Preferences (PREFS) cookie
|
# Preferences (PREFS) cookie
|
||||||
# Parameter "domain" comes from the global config
|
# Parameter "domain" comes from the global config
|
||||||
def prefs(domain : String?, preferences : Preferences) : HTTP::Cookie
|
def prefs(domain : String?, preferences : Preferences) : HTTP::Cookie
|
||||||
return HTTP::Cookie.new(
|
HTTP::Cookie.new(
|
||||||
name: "PREFS",
|
name: "PREFS",
|
||||||
domain: domain,
|
domain: domain,
|
||||||
value: URI.encode_www_form(preferences.to_json),
|
value: URI.encode_www_form(preferences.to_json),
|
||||||
|
|||||||
@ -5,7 +5,7 @@ struct Invidious::User
|
|||||||
def to_invidious(user : User)
|
def to_invidious(user : User)
|
||||||
playlists = Invidious::Database::Playlists.select_like_iv(user.email)
|
playlists = Invidious::Database::Playlists.select_like_iv(user.email)
|
||||||
|
|
||||||
return JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "subscriptions", user.subscriptions
|
json.field "subscriptions", user.subscriptions
|
||||||
json.field "watch_history", user.watched
|
json.field "watch_history", user.watched
|
||||||
|
|||||||
@ -27,7 +27,7 @@ struct Invidious::User
|
|||||||
subscriptions << channel_id
|
subscriptions << channel_id
|
||||||
end
|
end
|
||||||
|
|
||||||
return subscriptions
|
subscriptions
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_playlist_export_csv(user : User, raw_input : String)
|
def parse_playlist_export_csv(user : User, raw_input : String)
|
||||||
@ -81,7 +81,7 @@ struct Invidious::User
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return playlist
|
playlist
|
||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -171,7 +171,7 @@ struct Invidious::User
|
|||||||
|
|
||||||
opml_extensions = ["xml", "opml"]
|
opml_extensions = ["xml", "opml"]
|
||||||
|
|
||||||
return opml_mimetypes.any?(&.== mimetype) || opml_extensions.any?(&.== extension)
|
opml_mimetypes.any?(&.== mimetype) || opml_extensions.any?(&.== extension)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Import subscribed channels from Youtube
|
# Import subscribed channels from Youtube
|
||||||
@ -200,7 +200,7 @@ struct Invidious::User
|
|||||||
user.subscriptions = get_batch_channels(user.subscriptions)
|
user.subscriptions = get_batch_channels(user.subscriptions)
|
||||||
|
|
||||||
Invidious::Database::Users.update_subscriptions(user)
|
Invidious::Database::Users.update_subscriptions(user)
|
||||||
return true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_youtube_pl(user : User, body : String, filename : String, type : String) : Bool
|
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"
|
if extension == "csv" || type == "text/csv"
|
||||||
playlist = parse_playlist_export_csv(user, body)
|
playlist = parse_playlist_export_csv(user, body)
|
||||||
if playlist
|
if playlist
|
||||||
return true
|
true
|
||||||
else
|
else
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -232,9 +232,9 @@ struct Invidious::User
|
|||||||
user.watched += watched
|
user.watched += watched
|
||||||
user.watched.uniq!
|
user.watched.uniq!
|
||||||
Invidious::Database::Users.update_watch_history(user)
|
Invidious::Database::Users.update_watch_history(user)
|
||||||
return true
|
true
|
||||||
else
|
else
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -328,7 +328,7 @@ struct Invidious::User
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Success!
|
# Success!
|
||||||
return true
|
true
|
||||||
end
|
end
|
||||||
end # module
|
end # module
|
||||||
end
|
end
|
||||||
|
|||||||
@ -64,20 +64,18 @@ struct Preferences
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.from_json(value : JSON::PullParser) : String
|
def self.from_json(value : JSON::PullParser) : String
|
||||||
begin
|
result = value.read_string
|
||||||
result = value.read_string
|
|
||||||
|
|
||||||
if result.empty?
|
if result.empty?
|
||||||
CONFIG.default_user_preferences.dark_mode
|
CONFIG.default_user_preferences.dark_mode
|
||||||
else
|
else
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
rescue ex
|
rescue ex
|
||||||
if value.read_bool
|
if value.read_bool
|
||||||
"dark"
|
"dark"
|
||||||
else
|
else
|
||||||
"light"
|
"light"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -262,12 +260,12 @@ struct Preferences
|
|||||||
|
|
||||||
module TimeSpanConverter
|
module TimeSpanConverter
|
||||||
def self.to_yaml(value : Time::Span, yaml : YAML::Nodes::Builder)
|
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
|
end
|
||||||
|
|
||||||
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Time::Span
|
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Time::Span
|
||||||
if node.is_a?(YAML::Nodes::Scalar)
|
if node.is_a?(YAML::Nodes::Scalar)
|
||||||
return decode_interval(node.value)
|
decode_interval(node.value)
|
||||||
else
|
else
|
||||||
node.raise "Expected scalar, not #{node.class}"
|
node.raise "Expected scalar, not #{node.class}"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -17,11 +17,9 @@ struct Invidious::User
|
|||||||
|
|
||||||
module PreferencesConverter
|
module PreferencesConverter
|
||||||
def self.from_rs(rs)
|
def self.from_rs(rs)
|
||||||
begin
|
Preferences.from_json(rs.read(String))
|
||||||
Preferences.from_json(rs.read(String))
|
rescue ex
|
||||||
rescue ex
|
Preferences.from_json("{}")
|
||||||
Preferences.from_json("{}")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -45,7 +45,6 @@ def get_subscription_feed(user, max_results = 40, page = 1)
|
|||||||
notifications.sort_by!(&.author)
|
notifications.sort_by!(&.author)
|
||||||
when "channel name - reverse"
|
when "channel name - reverse"
|
||||||
notifications.sort_by!(&.author).reverse!
|
notifications.sort_by!(&.author).reverse!
|
||||||
else nil # Ignore
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if user.preferences.latest_only
|
if user.preferences.latest_only
|
||||||
@ -94,7 +93,6 @@ def get_subscription_feed(user, max_results = 40, page = 1)
|
|||||||
videos.sort_by!(&.author)
|
videos.sort_by!(&.author)
|
||||||
when "channel name - reverse"
|
when "channel name - reverse"
|
||||||
videos.sort_by!(&.author).reverse!
|
videos.sort_by!(&.author).reverse!
|
||||||
else nil # Ignore
|
|
||||||
end
|
end
|
||||||
|
|
||||||
notifications = Invidious::Database::Users.select_notifications(user)
|
notifications = Invidious::Database::Users.select_notifications(user)
|
||||||
|
|||||||
@ -48,7 +48,7 @@ struct Video
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_json(json : JSON::Builder | Nil = nil)
|
def to_json(json : JSON::Builder? = nil)
|
||||||
to_json(nil, json)
|
to_json(nil, json)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -56,15 +56,15 @@ struct Video
|
|||||||
|
|
||||||
def video_type : VideoType
|
def video_type : VideoType
|
||||||
video_type = info["videoType"]?.try &.as_s || "video"
|
video_type = info["videoType"]?.try &.as_s || "video"
|
||||||
return VideoType.parse?(video_type) || VideoType::Video
|
VideoType.parse?(video_type) || VideoType::Video
|
||||||
end
|
end
|
||||||
|
|
||||||
def schema_version : Int
|
def schema_version : Int
|
||||||
return info["version"]?.try &.as_i || 1
|
info["version"]?.try &.as_i || 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def published : Time
|
def published : Time
|
||||||
return info["published"]?
|
info["published"]?
|
||||||
.try { |t| Time.parse(t.as_s, "%Y-%m-%d", Time::Location::UTC) } || Time.utc
|
.try { |t| Time.parse(t.as_s, "%Y-%m-%d", Time::Location::UTC) } || Time.utc
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -73,11 +73,11 @@ struct Video
|
|||||||
end
|
end
|
||||||
|
|
||||||
def live_now
|
def live_now
|
||||||
return (self.video_type == VideoType::Livestream)
|
(video_type == VideoType::Livestream)
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_live_dvr
|
def post_live_dvr
|
||||||
return info["isPostLiveDvr"].as_bool
|
info["isPostLiveDvr"].as_bool
|
||||||
end
|
end
|
||||||
|
|
||||||
def premiere_timestamp : Time?
|
def premiere_timestamp : Time?
|
||||||
@ -94,21 +94,21 @@ struct Video
|
|||||||
|
|
||||||
def fmt_stream : Array(Hash(String, JSON::Any))
|
def fmt_stream : Array(Hash(String, JSON::Any))
|
||||||
if formats = info.dig?("streamingData", "formats")
|
if formats = info.dig?("streamingData", "formats")
|
||||||
return formats
|
formats
|
||||||
.as_a.map(&.as_h)
|
.as_a.map(&.as_h)
|
||||||
.sort_by! { |f| f["width"]?.try &.as_i || 0 }
|
.sort_by! { |f| f["width"]?.try &.as_i || 0 }
|
||||||
else
|
else
|
||||||
return [] of Hash(String, JSON::Any)
|
[] of Hash(String, JSON::Any)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def adaptive_fmts : Array(Hash(String, JSON::Any))
|
def adaptive_fmts : Array(Hash(String, JSON::Any))
|
||||||
if formats = info.dig?("streamingData", "adaptiveFormats")
|
if formats = info.dig?("streamingData", "adaptiveFormats")
|
||||||
return formats
|
formats
|
||||||
.as_a.map(&.as_h)
|
.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 }
|
.sort_by! { |f| f["width"]?.try &.as_i || f["audioTrack"]?.try { |a| a["audioIsDefault"]?.try { |v| v.as_bool ? -1 : 0 } } || 0 }
|
||||||
else
|
else
|
||||||
return [] of Hash(String, JSON::Any)
|
[] of Hash(String, JSON::Any)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -124,11 +124,11 @@ struct Video
|
|||||||
|
|
||||||
def storyboards
|
def storyboards
|
||||||
container = info.dig?("storyboards") || JSON::Any.new("{}")
|
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
|
end
|
||||||
|
|
||||||
def paid
|
def paid
|
||||||
return (self.reason || "").includes? "requires payment"
|
(reason || "").includes? "requires payment"
|
||||||
end
|
end
|
||||||
|
|
||||||
def premium
|
def premium
|
||||||
@ -140,7 +140,7 @@ struct Video
|
|||||||
@captions = Invidious::Videos::Captions::Metadata.from_yt_json(info["captions"])
|
@captions = Invidious::Videos::Captions::Metadata.from_yt_json(info["captions"])
|
||||||
end
|
end
|
||||||
|
|
||||||
return @captions
|
@captions
|
||||||
end
|
end
|
||||||
|
|
||||||
def hls_manifest_url : String?
|
def hls_manifest_url : String?
|
||||||
@ -149,7 +149,7 @@ struct Video
|
|||||||
|
|
||||||
def dash_manifest_url : String?
|
def dash_manifest_url : String?
|
||||||
raw_dash_url = info.dig?("streamingData", "dashManifestUrl").try &.as_s
|
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
|
# Use manifest v5 parameter to reduce file size
|
||||||
# See https://github.com/iv-org/invidious/issues/4186
|
# See https://github.com/iv-org/invidious/issues/4186
|
||||||
@ -162,7 +162,7 @@ struct Video
|
|||||||
dash_url.query = "#{dash_query}&mpd_version=5"
|
dash_url.query = "#{dash_query}&mpd_version=5"
|
||||||
end
|
end
|
||||||
|
|
||||||
return dash_url.to_s
|
dash_url.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def genre_url : String?
|
def genre_url : String?
|
||||||
@ -170,11 +170,11 @@ struct Video
|
|||||||
end
|
end
|
||||||
|
|
||||||
def vr? : Bool?
|
def vr? : Bool?
|
||||||
return {"EQUIRECTANGULAR", "MESH"}.includes? self.projection_type
|
{"EQUIRECTANGULAR", "MESH"}.includes? projection_type
|
||||||
end
|
end
|
||||||
|
|
||||||
def projection_type : String?
|
def projection_type : String?
|
||||||
return info.dig?("streamingData", "adaptiveFormats", 0, "projectionType").try &.as_s
|
info.dig?("streamingData", "adaptiveFormats", 0, "projectionType").try &.as_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def reason : String?
|
def reason : String?
|
||||||
@ -182,50 +182,50 @@ struct Video
|
|||||||
end
|
end
|
||||||
|
|
||||||
def music : Array(VideoMusic)
|
def music : Array(VideoMusic)
|
||||||
info["music"].as_a.map { |music_json|
|
info["music"].as_a.map do |music_json|
|
||||||
VideoMusic.new(
|
VideoMusic.new(
|
||||||
music_json["song"].as_s,
|
music_json["song"].as_s,
|
||||||
music_json["album"].as_s,
|
music_json["album"].as_s,
|
||||||
music_json["artist"].as_s,
|
music_json["artist"].as_s,
|
||||||
music_json["license"].as_s
|
music_json["license"].as_s
|
||||||
)
|
)
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Macros defining getters/setters for various types of data
|
# Macros defining getters/setters for various types of data
|
||||||
|
|
||||||
private macro getset_string(name)
|
private macro getset_string(name)
|
||||||
# Return {{name.stringify}} from `info`
|
# Return {{ name.stringify }} from `info`
|
||||||
def {{name.id.underscore}} : String
|
def {{ name.id.underscore }} : String
|
||||||
return info[{{name.stringify}}]?.try &.as_s || ""
|
info[{{ name.stringify }}]?.try &.as_s || ""
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update {{name.stringify}} into `info`
|
# Update {{ name.stringify }} into `info`
|
||||||
def {{name.id.underscore}}=(value : String)
|
def {{ name.id.underscore }}=(value : String)
|
||||||
info[{{name.stringify}}] = JSON::Any.new(value)
|
info[{{ name.stringify }}] = JSON::Any.new(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
{% if flag?(:debug_macros) %} {{debug}} {% end %}
|
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
private macro getset_string_array(name)
|
private macro getset_string_array(name)
|
||||||
# Return {{name.stringify}} from `info`
|
# Return {{ name.stringify }} from `info`
|
||||||
def {{name.id.underscore}} : Array(String)
|
def {{ name.id.underscore }} : Array(String)
|
||||||
return info[{{name.stringify}}]?.try &.as_a.map &.as_s || [] of String
|
info[{{ name.stringify }}]?.try &.as_a.map &.as_s || [] of String
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update {{name.stringify}} into `info`
|
# Update {{ name.stringify }} into `info`
|
||||||
def {{name.id.underscore}}=(value : Array(String))
|
def {{ name.id.underscore }}=(value : Array(String))
|
||||||
info[{{name.stringify}}] = JSON::Any.new(value)
|
info[{{ name.stringify }}] = JSON::Any.new(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
{% if flag?(:debug_macros) %} {{debug}} {% end %}
|
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
{% for op, type in {i32: Int32, i64: Int64} %}
|
{% for op, type in {i32: Int32, i64: Int64} %}
|
||||||
private macro getset_{{op}}(name)
|
private macro getset_{{ op }}(name)
|
||||||
def \{{name.id.underscore}} : {{type}}
|
def \{{name.id.underscore}} : {{ type }}
|
||||||
return info[\{{name.stringify}}]?.try &.as_i64.to_{{op}} || 0_{{op}}
|
info[\{{name.stringify}}]?.try &.as_i64.to_{{ op }} || 0_{{ op }}
|
||||||
end
|
end
|
||||||
|
|
||||||
def \{{name.id.underscore}}=(value : Int)
|
def \{{name.id.underscore}}=(value : Int)
|
||||||
@ -237,32 +237,32 @@ struct Video
|
|||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
private macro getset_bool(name)
|
private macro getset_bool(name)
|
||||||
# Return {{name.stringify}} from `info`
|
# Return {{ name.stringify }} from `info`
|
||||||
def {{name.id.underscore}} : Bool
|
def {{ name.id.underscore }} : Bool
|
||||||
return info[{{name.stringify}}]?.try &.as_bool || false
|
info[{{ name.stringify }}]?.try &.as_bool || false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update {{name.stringify}} into `info`
|
# Update {{ name.stringify }} into `info`
|
||||||
def {{name.id.underscore}}=(value : Bool)
|
def {{ name.id.underscore }}=(value : Bool)
|
||||||
info[{{name.stringify}}] = JSON::Any.new(value)
|
info[{{ name.stringify }}] = JSON::Any.new(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
{% if flag?(:debug_macros) %} {{debug}} {% end %}
|
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Macro to generate ? and = accessor methods for attributes in `info`
|
# Macro to generate ? and = accessor methods for attributes in `info`
|
||||||
private macro predicate_bool(method_name, name)
|
private macro predicate_bool(method_name, name)
|
||||||
# Return {{name.stringify}} from `info`
|
# Return {{ name.stringify }} from `info`
|
||||||
def {{method_name.id.underscore}}? : Bool
|
def {{ method_name.id.underscore }}? : Bool
|
||||||
return info[{{name.stringify}}]?.try &.as_bool || false
|
info[{{ name.stringify }}]?.try &.as_bool || false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update {{name.stringify}} into `info`
|
# Update {{ name.stringify }} into `info`
|
||||||
def {{method_name.id.underscore}}=(value : Bool)
|
def {{ method_name.id.underscore }}=(value : Bool)
|
||||||
info[{{name.stringify}}] = JSON::Any.new(value)
|
info[{{ name.stringify }}] = JSON::Any.new(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
{% if flag?(:debug_macros) %} {{debug}} {% end %}
|
{% if flag?(:debug_macros) %} {{ debug }} {% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Method definitions, using the macros above
|
# 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
|
Invidious::Database::Videos.insert(video) if !region
|
||||||
end
|
end
|
||||||
|
|
||||||
return video
|
video
|
||||||
rescue DB::Error
|
rescue DB::Error
|
||||||
# Avoid common `DB::PoolRetryAttemptsExceeded` error and friends
|
# Avoid common `DB::PoolRetryAttemptsExceeded` error and friends
|
||||||
# Note: All DB errors inherit from `DB::Error`
|
# Note: All DB errors inherit from `DB::Error`
|
||||||
return fetch_video(id, region)
|
fetch_video(id, region)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_video(id, region)
|
def fetch_video(id, region)
|
||||||
@ -350,7 +350,7 @@ def fetch_video(id, region)
|
|||||||
updated: Time.utc,
|
updated: Time.utc,
|
||||||
})
|
})
|
||||||
|
|
||||||
return video
|
video
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_continuation(query, plid, id)
|
def process_continuation(query, plid, id)
|
||||||
|
|||||||
@ -33,7 +33,7 @@ module Invidious::Videos
|
|||||||
captions_list << Captions::Metadata.new(name, language_code, base_url, auto_generated)
|
captions_list << Captions::Metadata.new(name, language_code, base_url, auto_generated)
|
||||||
end
|
end
|
||||||
|
|
||||||
return captions_list
|
captions_list
|
||||||
end
|
end
|
||||||
|
|
||||||
def timedtext_to_vtt(timedtext : String, tlang = nil) : String
|
def timedtext_to_vtt(timedtext : String, tlang = nil) : String
|
||||||
@ -82,7 +82,7 @@ module Invidious::Videos
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return result
|
result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ private def copy_string(str : String::Builder, iter : Iterator, count : Int) : I
|
|||||||
copied += 1
|
copied += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
return copied
|
copied
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_description(desc, video_id : String) : String?
|
def parse_description(desc, video_id : String) : String?
|
||||||
@ -52,7 +52,7 @@ def parse_description(desc, video_id : String) : String?
|
|||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
return String.build do |str|
|
String.build do |str|
|
||||||
commands.each do |command|
|
commands.each do |command|
|
||||||
cmd_start = command["startIndex"].as_i
|
cmd_start = command["startIndex"].as_i
|
||||||
cmd_length = command["length"].as_i
|
cmd_length = command["length"].as_i
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
module Invidious::Videos::Formats
|
module Invidious::Videos::Formats
|
||||||
def self.itag_to_metadata?(itag : JSON::Any)
|
def self.itag_to_metadata?(itag : JSON::Any)
|
||||||
return FORMATS[itag.to_s]?
|
FORMATS[itag.to_s]?
|
||||||
end
|
end
|
||||||
|
|
||||||
# See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L380-#L476
|
# See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L380-#L476
|
||||||
|
|||||||
@ -7,7 +7,7 @@ require "json"
|
|||||||
# TODO: "compactRadioRenderer" (Mix) and
|
# TODO: "compactRadioRenderer" (Mix) and
|
||||||
# TODO: Use a proper struct/class instead of a hacky JSON object
|
# TODO: Use a proper struct/class instead of a hacky JSON object
|
||||||
def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
|
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
|
# The compact renderer has video length in seconds, where the end
|
||||||
# screen rendered has a full text version ("42:40")
|
# 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
|
# TODO: when refactoring video types, make a struct for related videos
|
||||||
# or reuse an existing type, if that fits.
|
# or reuse an existing type, if that fits.
|
||||||
return {
|
{
|
||||||
"id" => related["videoId"],
|
"id" => related["videoId"],
|
||||||
"title" => related["title"]["simpleText"],
|
"title" => related["title"]["simpleText"],
|
||||||
"author" => author || JSON::Any.new(""),
|
"author" => author || JSON::Any.new(""),
|
||||||
@ -57,7 +57,7 @@ def extract_video_info(video_id : String)
|
|||||||
player_response = YoutubeAPI.player(video_id: video_id)
|
player_response = YoutubeAPI.player(video_id: video_id)
|
||||||
|
|
||||||
if player_response.nil?
|
if player_response.nil?
|
||||||
return nil
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
playability_status = player_response.dig?("playabilityStatus", "status").try &.as_s
|
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
|
# Data structure version, for cache control
|
||||||
params["version"] = JSON::Any.new(Video::SCHEMA_VERSION.to_i64)
|
params["version"] = JSON::Any.new(Video::SCHEMA_VERSION.to_i64)
|
||||||
|
|
||||||
return params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
|
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)"
|
"The video returned by YouTube isn't the requested one. (#{client_config.client_type} client)"
|
||||||
)
|
)
|
||||||
elsif playability_status == "OK"
|
elsif playability_status == "OK"
|
||||||
return response
|
response
|
||||||
else
|
else
|
||||||
return nil
|
return
|
||||||
end
|
end
|
||||||
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 || "-"),
|
"subCountText" => JSON::Any.new(subs_text || "-"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
private def convert_url(fmt)
|
private def convert_url(fmt)
|
||||||
@ -457,9 +457,9 @@ private def convert_url(fmt)
|
|||||||
url.query_params = params
|
url.query_params = params
|
||||||
LOGGER.trace("convert_url: new url is '#{url}'")
|
LOGGER.trace("convert_url: new url is '#{url}'")
|
||||||
|
|
||||||
return url.to_s
|
url.to_s
|
||||||
rescue ex
|
rescue ex
|
||||||
LOGGER.debug("convert_url: Error when parsing video URL")
|
LOGGER.debug("convert_url: Error when parsing video URL")
|
||||||
LOGGER.trace(ex.inspect_with_backtrace)
|
LOGGER.trace(ex.inspect_with_backtrace)
|
||||||
return ""
|
""
|
||||||
end
|
end
|
||||||
|
|||||||
@ -62,7 +62,7 @@ module Invidious::Videos
|
|||||||
# The base URL is the first chunk
|
# The base URL is the first chunk
|
||||||
base_url = URI.parse(storyboards.shift)
|
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:
|
# Separate the different storyboard parameters:
|
||||||
# width/height: respective dimensions, in pixels, of a single thumbnail
|
# width/height: respective dimensions, in pixels, of a single thumbnail
|
||||||
# count: how many thumbnails are displayed across the full video
|
# count: how many thumbnails are displayed across the full video
|
||||||
|
|||||||
@ -45,7 +45,7 @@ module Invidious::Videos
|
|||||||
.try { |i| Base64.urlsafe_encode(i) }
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
.try { |i| URI.encode_www_form(i) }
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
return params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
# Constructs a Transcripts struct from the initial YouTube response
|
# 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)
|
lines << line_type.new(start_ms, end_ms, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Transcript.new(
|
Transcript.new(
|
||||||
lines: lines,
|
lines: lines,
|
||||||
language_code: language_code,
|
language_code: language_code,
|
||||||
auto_generated: auto_generated,
|
auto_generated: auto_generated,
|
||||||
@ -120,7 +120,7 @@ module Invidious::Videos
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return vtt
|
vtt
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_json(json : JSON::Builder)
|
def to_json(json : JSON::Builder)
|
||||||
|
|||||||
@ -158,5 +158,5 @@ def process_video_params(query, preferences)
|
|||||||
save_player_pos: save_player_pos,
|
save_player_pos: save_player_pos,
|
||||||
})
|
})
|
||||||
|
|
||||||
return params
|
params
|
||||||
end
|
end
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
best_m4a_stream_bitrate = 0
|
best_m4a_stream_bitrate = 0
|
||||||
audio_streams.each_with_index do |fmt, i|
|
audio_streams.each_with_index do |fmt, i|
|
||||||
bandwidth = fmt["bitrate"].as_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_bitrate = bandwidth
|
||||||
best_m4a_stream_index = i
|
best_m4a_stream_index = i
|
||||||
end
|
end
|
||||||
@ -25,8 +25,8 @@
|
|||||||
audio_streams.each_with_index do |fmt, i|
|
audio_streams.each_with_index do |fmt, i|
|
||||||
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
|
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
|
||||||
src_url += "&local=true" if params.local
|
src_url += "&local=true" if params.local
|
||||||
src_url = invidious_companion.public_url.to_s + src_url +
|
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"]
|
bitrate = fmt["bitrate"]
|
||||||
mimetype = HTML.escape(fmt["mimeType"].as_s)
|
mimetype = HTML.escape(fmt["mimeType"].as_s)
|
||||||
@ -42,7 +42,7 @@
|
|||||||
<% if params.quality == "dash"
|
<% if params.quality == "dash"
|
||||||
src_url = "/api/manifest/dash/id/" + video.id + "?local=true&unique_res=1"
|
src_url = "/api/manifest/dash/id/" + video.id + "?local=true&unique_res=1"
|
||||||
src_url = invidious_companion.public_url.to_s + src_url +
|
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">
|
<source src="<%= src_url %>" type='application/dash+xml' label="dash">
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -54,7 +54,7 @@
|
|||||||
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
|
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
|
||||||
src_url += "&local=true" if params.local
|
src_url += "&local=true" if params.local
|
||||||
src_url = invidious_companion.public_url.to_s + src_url +
|
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"]
|
quality = fmt["quality"]
|
||||||
mimetype = HTML.escape(fmt["mimeType"].as_s)
|
mimetype = HTML.escape(fmt["mimeType"].as_s)
|
||||||
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
<% preferred_captions.each do |caption|
|
<% preferred_captions.each do |caption|
|
||||||
api_captions_url = "/api/v1/captions/"
|
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}"
|
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 %>">
|
<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|
|
<% captions.each do |caption|
|
||||||
api_captions_url = "/api/v1/captions/"
|
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}"
|
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 %>">
|
<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
Loading…
x
Reference in New Issue
Block a user