From 344bc2d8e950748ab0c5f68f4c18d12e45b9c281 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Fri, 16 Jan 2026 19:39:44 -0300 Subject: [PATCH 01/23] Strip unwanted headers from response headers in images and videoplayback (#5595) Image responses contained the following unwanted headers that should not be passed to the clients: ``` "Cross-Origin-Resource-Policy" ["cross-origin"] "Cross-Origin-Opener-Policy-Report-Only" ["same-origin; report-to=\"youtube\""] "Report-To" ["{\"group\":\"youtube\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube\"}]}"] "Timing-Allow-Origin" ["*"] ``` --- src/invidious.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious.cr b/src/invidious.cr index a61f91a9..ec518453 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -78,7 +78,7 @@ TEST_IDS = {"AgbeGFYluEA", "BaW_jenozKc", "a9LDPn-MO4I", "ddFvjfvPnqk" MAX_ITEMS_PER_PAGE = 1500 REQUEST_HEADERS_WHITELIST = {"accept", "accept-encoding", "cache-control", "content-length", "if-none-match", "range"} -RESPONSE_HEADERS_BLACKLIST = {"access-control-allow-origin", "alt-svc", "server"} +RESPONSE_HEADERS_BLACKLIST = {"access-control-allow-origin", "alt-svc", "server", "cross-origin-opener-policy-report-only", "report-to", "cross-origin", "timing-allow-origin", "cross-origin-resource-policy"} HTTP_CHUNK_SIZE = 10485760 # ~10MB CURRENT_BRANCH = {{ "#{`git branch | sed -n '/* /s///p'`.strip}" }} From 66c67f4c7a2646c5d1b555fd833826917f1cb58f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20=28perso=29?= <4016501+unixfox@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:15:32 +0100 Subject: [PATCH 02/23] doc: Update HTTP proxy configuration comments (#5586) * doc: Update HTTP proxy configuration comments Added information about proxy configuration for YouTube streams. * Document supported proxy types in config.example.yml Added note about supported proxy types in configuration. --- config/config.example.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/config.example.yml b/config/config.example.yml index eedd9539..7cc480c6 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -223,9 +223,13 @@ https_only: false ## ## Configuration for using a HTTP proxy -## ## If unset, then no HTTP proxy will be used. +## Proxy type supported: HTTP, HTTPS ## +## This is not used for loading the video streams from YouTube servers (circumvent YouTube restrictions) +## Please instead configure the proxy in Invidious companion: +## https://github.com/iv-org/invidious-companion/blob/master/config/config.example.toml +## #http_proxy: # user: # password: From d25cc9570c9738f16e15437bcc69a12ab2095738 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:59:44 +0100 Subject: [PATCH 03/23] Bump crystallang/crystal from 1.16.3-alpine to 1.19.0-alpine in /docker (#5603) Bumps crystallang/crystal from 1.16.3-alpine to 1.19.0-alpine. --- updated-dependencies: - dependency-name: crystallang/crystal dependency-version: 1.19.0-alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e2d30364..97c43ef1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,7 +2,7 @@ ARG OPENSSL_VERSION='3.5.2' ARG OPENSSL_SHA256='c53a47e5e441c930c3928cf7bf6fb00e5d129b630e0aa873b08258656e7345ec' -FROM crystallang/crystal:1.16.3-alpine AS dependabot-crystal +FROM crystallang/crystal:1.19.0-alpine AS dependabot-crystal # We compile openssl ourselves due to a memory leak in how crystal interacts # with openssl From 7e36cfb6678770db8a55e575caddd981dce2d032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20=28perso=29?= <4016501+unixfox@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:39:01 +0100 Subject: [PATCH 04/23] =?UTF-8?q?Revert=20"Bump=20crystallang/crystal=20fr?= =?UTF-8?q?om=201.16.3-alpine=20to=201.19.0-alpine=20in=20/dock=E2=80=A6"?= =?UTF-8?q?=20(#5604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d25cc9570c9738f16e15437bcc69a12ab2095738. --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 97c43ef1..e2d30364 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,7 +2,7 @@ ARG OPENSSL_VERSION='3.5.2' ARG OPENSSL_SHA256='c53a47e5e441c930c3928cf7bf6fb00e5d129b630e0aa873b08258656e7345ec' -FROM crystallang/crystal:1.19.0-alpine AS dependabot-crystal +FROM crystallang/crystal:1.16.3-alpine AS dependabot-crystal # We compile openssl ourselves due to a memory leak in how crystal interacts # with openssl From d51a7a44ad91d2fa7d1330970a15a0d8f365f250 Mon Sep 17 00:00:00 2001 From: Kiril Isakov Date: Fri, 23 Jan 2026 13:18:41 +0100 Subject: [PATCH 05/23] Fix commit command in README instructions, as per #5606 (#5607) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97d2109b..5b789a50 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ You can read more here: https://docs.invidious.io/applications/ 1. Fork it ( https://github.com/iv-org/invidious/fork ). 1. Create your feature branch (`git checkout -b my-new-feature`). 1. Stage your files (`git add .`). -1. Commit your changes (`git commit -am 'Add some feature'`). +1. Commit your changes (`git commit -m 'Add some feature'`). 1. Push to the branch (`git push origin my-new-feature`). 1. Create a new pull request ( https://github.com/iv-org/invidious/compare ). From abb0aa436ce9dd31d96601c14a352a98de0e3469 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Fri, 30 Jan 2026 18:01:04 -0300 Subject: [PATCH 06/23] Fix thin_mode preference for channel community page (#5567) thin_mode only took in account the query param because env.get("preferences").as(Preferences).thin_mode returned a boolean and not a string to be able to compare it with the string `"true"` --- src/invidious/routes/channels.cr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index f785de18..968d38dc 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -231,8 +231,10 @@ module Invidious::Routes::Channels env.redirect "/post/#{URI.encode_www_form(lb)}?ucid=#{URI.encode_www_form(ucid)}" end - thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode - thin_mode = thin_mode == "true" + preferences = env.get("preferences").as(Preferences) + + thin_mode = env.params.query["thin_mode"]? + thin_mode = (thin_mode == "true") || preferences.thin_mode continuation = env.params.query["continuation"]? From b521e3be6c0d925a96a97ce6a233aa8a55a7edc3 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Fri, 30 Jan 2026 18:01:16 -0300 Subject: [PATCH 07/23] chore: Do not convert thin_mode preference to string to compare it (#5568) --- src/invidious/routes/before_all.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr index 63b935ec..06746a12 100644 --- a/src/invidious/routes/before_all.cr +++ b/src/invidious/routes/before_all.cr @@ -94,8 +94,8 @@ module Invidious::Routes::BeforeAll end dark_mode = convert_theme(env.params.query["dark_mode"]?) || preferences.dark_mode.to_s - thin_mode = env.params.query["thin_mode"]? || preferences.thin_mode.to_s - thin_mode = thin_mode == "true" + thin_mode = env.params.query["thin_mode"]? + thin_mode = (thin_mode == "true") || preferences.thin_mode locale = env.params.query["hl"]? || preferences.locale preferences.dark_mode = dark_mode From 48be830544313ac6ccd2fe257526b5607f3c5fe4 Mon Sep 17 00:00:00 2001 From: Harm133 Date: Fri, 30 Jan 2026 23:39:07 +0100 Subject: [PATCH 08/23] Update shard.yml to include target (#5608) [shard.yml] - Include a target for LSPs to use as an entrypoint: (https://github.com/elbywan/crystalline?tab=readme-ov-file#entry-point) --- shard.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shard.yml b/shard.yml index bc6c4bf4..dde1851e 100644 --- a/shard.yml +++ b/shard.yml @@ -5,6 +5,10 @@ authors: - Invidious team - Contributors! +targets: + invidious: + main: src/invidious.cr + description: | Invidious is an alternative front-end to YouTube From a9f812799c2aa2541e13fc291522fb2fb03d47b2 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Tue, 3 Feb 2026 16:18:15 -0300 Subject: [PATCH 09/23] fix: add missing embedded protobuf message in continuation token for channel videos (#5614) * fix: add missing embedded protobuf message in continuation token for channel videos * fix: add missing embedded protobuf message in continuation token for channel shorts * fix: add missing embedded protobuf message in continuation token for channel livestreams --- src/invidious/channels/videos.cr | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/invidious/channels/videos.cr b/src/invidious/channels/videos.cr index 96400f47..e2cc8305 100644 --- a/src/invidious/channels/videos.cr +++ b/src/invidious/channels/videos.cr @@ -114,7 +114,11 @@ module Invidious::Channel::Tabs "2:embedded" => { "1:string" => "00000000-0000-0000-0000-000000000000", }, - "4:varint" => sort_options_videos_short(sort_by), + "4:varint" => sort_options_videos_short(sort_by), + "8:embedded" => { + "1:string" => "00000000-0000-0000-0000-000000000000", + "3:varint" => sort_options_videos_short(sort_by), + }, }, } @@ -130,7 +134,11 @@ module Invidious::Channel::Tabs "2:embedded" => { "1:string" => "00000000-0000-0000-0000-000000000000", }, - "4:varint" => sort_options_videos_short(sort_by), + "4:varint" => sort_options_videos_short(sort_by), + "7:embedded" => { + "1:string" => "00000000-0000-0000-0000-000000000000", + "3:varint" => sort_options_videos_short(sort_by), + }, }, } @@ -154,7 +162,11 @@ module Invidious::Channel::Tabs "2:embedded" => { "1:string" => "00000000-0000-0000-0000-000000000000", }, - "5:varint" => sort_by_numerical, + "5:varint" => sort_by_numerical, + "8:embedded" => { + "1:string" => "00000000-0000-0000-0000-000000000000", + "3:varint" => sort_by_numerical, + }, }, } From ecbc21b0678eac4a0c8f745de5cc78eef4841221 Mon Sep 17 00:00:00 2001 From: Cameron Radmore Date: Wed, 4 Feb 2026 10:57:16 -0500 Subject: [PATCH 10/23] playlist: parse playlist thumbnails for topic autogenerated playlists (#5616) --- src/invidious/playlists.cr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 7c584d15..ec64bee4 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -359,6 +359,9 @@ def fetch_playlist(plid : String) thumbnail = playlist_info.dig?( "thumbnailRenderer", "playlistVideoThumbnailRenderer", "thumbnail", "thumbnails", 0, "url" + ).try &.as_s || playlist_info.dig?( + "thumbnailRenderer", "playlistCustomThumbnailRenderer", + "thumbnail", "thumbnails", 0, "url" ).try &.as_s views = 0_i64 From 864893f4c75d79b725aba2ddfbf0d55a2b71111e Mon Sep 17 00:00:00 2001 From: Cameron Radmore Date: Thu, 5 Feb 2026 09:58:52 -0500 Subject: [PATCH 11/23] Channels: parse pronouns and display them on channel page (#5617) --- assets/css/default.css | 17 ++++++++++++++++- src/invidious/channels/about.cr | 17 +++++++++++++---- src/invidious/routes/api/v1/channels.cr | 1 + src/invidious/views/components/channel_info.ecr | 5 ++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/assets/css/default.css b/assets/css/default.css index 78ef7a60..ff07bdb4 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -75,6 +75,16 @@ body { height: auto; } +.channel-profile > .channel-name-pronouns { + display: inline-block; +} + +.channel-profile > .channel-name-pronouns > .channel-pronouns { + font-style: italic; + font-size: .8em; + font-weight: lighter; +} + body a.channel-owner { background-color: #008bec; color: #fff; @@ -406,7 +416,12 @@ input[type="search"]::-webkit-search-cancel-button { p.channel-name { margin: 0; overflow-wrap: anywhere;} p.video-data { margin: 0; font-weight: bold; font-size: 80%; } -.channel-profile > .channel-name { overflow-wrap: anywhere;} + +.channel-profile > .channel-name, +.channel-profile > .channel-name-pronouns > .channel-name +{ + overflow-wrap: anywhere; +} /* diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr index 13909527..537aa034 100644 --- a/src/invidious/channels/about.cr +++ b/src/invidious/channels/about.cr @@ -12,6 +12,7 @@ record AboutChannel, sub_count : Int32, joined : Time, is_family_friendly : Bool, + pronouns : String?, allowed_regions : Array(String), tabs : Array(String), tags : Array(String), @@ -160,14 +161,21 @@ def get_about_info(ucid, locale) : AboutChannel end sub_count = 0 + pronouns = nil if (metadata_rows = initdata.dig?("header", "pageHeaderRenderer", "content", "pageHeaderViewModel", "metadata", "contentMetadataViewModel", "metadataRows").try &.as_a) metadata_rows.each do |row| - metadata_part = row.dig?("metadataParts").try &.as_a.find { |i| i.dig?("text", "content").try &.as_s.includes?("subscribers") } - if !metadata_part.nil? - sub_count = short_text_to_number(metadata_part.dig("text", "content").as_s.split(" ")[0]).to_i32 + subscribe_metadata_part = row.dig?("metadataParts").try &.as_a.find { |i| i.dig?("text", "content").try &.as_s.includes?("subscribers") } + if !subscribe_metadata_part.nil? + sub_count = short_text_to_number(subscribe_metadata_part.dig("text", "content").as_s.split(" ")[0]).to_i32 end - break if sub_count != 0 + + pronoun_metadata_part = row.dig?("metadataParts").try &.as_a.find { |i| i.dig?("tooltip").try &.as_s.includes?("Pronouns") } + if !pronoun_metadata_part.nil? + pronouns = pronoun_metadata_part.dig("text", "content").as_s + end + + break if sub_count != 0 && !pronouns.nil? end end @@ -184,6 +192,7 @@ def get_about_info(ucid, locale) : AboutChannel sub_count: sub_count, joined: joined, is_family_friendly: is_family_friendly, + pronouns: pronouns, allowed_regions: allowed_regions, tabs: tab_names, tags: tags, diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr index 503b8c05..f8060342 100644 --- a/src/invidious/routes/api/v1/channels.cr +++ b/src/invidious/routes/api/v1/channels.cr @@ -104,6 +104,7 @@ module Invidious::Routes::API::V1::Channels json.field "tabs", channel.tabs json.field "tags", channel.tags json.field "authorVerified", channel.verified + json.field "pronouns", channel.pronouns json.field "latestVideos" do json.array do diff --git a/src/invidious/views/components/channel_info.ecr b/src/invidious/views/components/channel_info.ecr index 2c177b59..97a2d7da 100644 --- a/src/invidious/views/components/channel_info.ecr +++ b/src/invidious/views/components/channel_info.ecr @@ -12,7 +12,10 @@
- <%= author %><% if !channel.verified.nil? && channel.verified %> <% end %> +
+ <%= author %><% if !channel.verified.nil? && channel.verified %> <% end %> + <% if !channel.pronouns.nil? %>
<%= channel.pronouns %><% end %> +
From 84a699f7b7b3c1bc60598077f0da111219b621bc Mon Sep 17 00:00:00 2001 From: Cameron Radmore Date: Thu, 5 Feb 2026 09:59:27 -0500 Subject: [PATCH 12/23] Playlist API: return empty author url if ucid is empty (#5618) --- src/invidious/playlists.cr | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index ec64bee4..eb084331 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -107,7 +107,11 @@ struct Playlist json.field "author", self.author json.field "authorId", self.ucid - json.field "authorUrl", "/channel/#{self.ucid}" + if !self.ucid.empty? + json.field "authorUrl", "/channel/#{self.ucid}" + else + json.field "authorUrl", "" + end json.field "subtitle", self.subtitle json.field "authorThumbnails" do From 7be6fbd75c9d680e1595bdeae32e62e5ddc5e745 Mon Sep 17 00:00:00 2001 From: ThatMatrix Date: Thu, 11 Jul 2024 01:53:58 +0200 Subject: [PATCH 13/23] Fix(user/importers): Fixed youtube csv playlist importer --- src/invidious/user/imports.cr | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 007eb666..0dbc9b03 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -30,22 +30,18 @@ struct Invidious::User return subscriptions end - def parse_playlist_export_csv(user : User, raw_input : String) + # Parse a CSV Google Takeout - Youtube Playlist file + def parse_playlist_export_csv(user : User, playlist_name : String, raw_input : String) # Split the input into head and body content raw_head, raw_body = raw_input.strip('\n').split("\n\n", limit: 2, remove_empty: true) # Create the playlist from the head content csv_head = CSV.new(raw_head.strip('\n'), headers: true) csv_head.next - title = csv_head[4] - description = csv_head[5] - visibility = csv_head[6] + title = playlist_name - if visibility.compare("Public", case_insensitive: true) == 0 - privacy = PlaylistPrivacy::Public - else - privacy = PlaylistPrivacy::Private - end + description = "This is the default description of an imported playlist. Feel Free to change it as you see fit." + privacy = PlaylistPrivacy::Private playlist = create_playlist(title, privacy, user) Invidious::Database::Playlists.update_description(playlist.id, description) @@ -204,10 +200,12 @@ struct Invidious::User end def from_youtube_pl(user : User, body : String, filename : String, type : String) : Bool - extension = filename.split(".").last + filename_array = filename.split(".") + playlist_name = filename_array.first + extension = filename_array.last if extension == "csv" || type == "text/csv" - playlist = parse_playlist_export_csv(user, body) + playlist = parse_playlist_export_csv(user, playlist_name,playlist_name, body) if playlist return true else @@ -219,6 +217,7 @@ struct Invidious::User end def from_youtube_wh(user : User, body : String, filename : String, type : String) : Bool + filename = filename.split(".") extension = filename.split(".").last if extension == "json" || type == "application/json" From 471857ce8bbb8397e75c9d16a781f5b859fe74b0 Mon Sep 17 00:00:00 2001 From: ThatMatrix Date: Thu, 11 Jul 2024 02:41:08 +0200 Subject: [PATCH 14/23] Fix(user/importers): Fixed typos --- docker-compose.yml | 2 +- src/invidious/user/imports.cr | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cb53bdd6..899f2118 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: # domain: # https_only: false # statistics_enabled: false - hmac_key: "CHANGE_ME!!" + hmac_key: "ahyeef5xahyohliefi3A" healthcheck: test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1 interval: 30s diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 0dbc9b03..df93422f 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -33,7 +33,7 @@ struct Invidious::User # Parse a CSV Google Takeout - Youtube Playlist file def parse_playlist_export_csv(user : User, playlist_name : String, raw_input : String) # Split the input into head and body content - raw_head, raw_body = raw_input.strip('\n').split("\n\n", limit: 2, remove_empty: true) + raw_head, raw_body = raw_input.split("\n\n", limit: 2, remove_empty: true) # Create the playlist from the head content csv_head = CSV.new(raw_head.strip('\n'), headers: true) @@ -205,7 +205,7 @@ struct Invidious::User extension = filename_array.last if extension == "csv" || type == "text/csv" - playlist = parse_playlist_export_csv(user, playlist_name,playlist_name, body) + playlist = parse_playlist_export_csv(user, playlist_name, body) if playlist return true else @@ -217,7 +217,6 @@ struct Invidious::User end def from_youtube_wh(user : User, body : String, filename : String, type : String) : Bool - filename = filename.split(".") extension = filename.split(".").last if extension == "json" || type == "application/json" From 050032b18880a90118bc27f98ebdf7e5fe9bd67d Mon Sep 17 00:00:00 2001 From: ThatMatrix Date: Thu, 11 Jul 2024 02:52:39 +0200 Subject: [PATCH 15/23] fix(docker-compose.yml): removed hmac_key (randomly generated) used for testing --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 899f2118..cb53bdd6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: # domain: # https_only: false # statistics_enabled: false - hmac_key: "ahyeef5xahyohliefi3A" + hmac_key: "CHANGE_ME!!" healthcheck: test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1 interval: 30s From e4beb00413e3a008d8d87442b6c4eab776406827 Mon Sep 17 00:00:00 2001 From: ThatMatrix Date: Thu, 11 Jul 2024 03:32:06 +0200 Subject: [PATCH 16/23] fix(user/imports.cr): splitting error fixed --- src/invidious/user/imports.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index df93422f..bc149454 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -33,7 +33,7 @@ struct Invidious::User # Parse a CSV Google Takeout - Youtube Playlist file def parse_playlist_export_csv(user : User, playlist_name : String, raw_input : String) # Split the input into head and body content - raw_head, raw_body = raw_input.split("\n\n", limit: 2, remove_empty: true) + raw_head, raw_body = raw_input.split("\n", limit: 2, remove_empty: true) # Create the playlist from the head content csv_head = CSV.new(raw_head.strip('\n'), headers: true) From ce9494133df596ada104acee43dcc1b32e34bebc Mon Sep 17 00:00:00 2001 From: ThatMatrix Date: Thu, 11 Jul 2024 03:44:56 +0200 Subject: [PATCH 17/23] fix(user/imports.cr): double header removal caused first video to be skipped --- src/invidious/user/imports.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index bc149454..7c4101cc 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -47,7 +47,7 @@ struct Invidious::User Invidious::Database::Playlists.update_description(playlist.id, description) # Add each video to the playlist from the body content - csv_body = CSV.new(raw_body.strip('\n'), headers: true) + csv_body = CSV.new(raw_body.strip('\n'), headers: false) csv_body.each do |row| video_id = row[0] if playlist From a3a97ccf073808d25900d661b79216625b4221f1 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Sun, 28 Sep 2025 00:38:23 -0300 Subject: [PATCH 18/23] Only generate companion CSP one time to reuse it --- src/invidious/routes/before_all.cr | 17 +++++++++++++++-- src/invidious/routes/embed.cr | 11 ----------- src/invidious/routes/watch.cr | 11 ----------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr index 06746a12..347a6021 100644 --- a/src/invidious/routes/before_all.cr +++ b/src/invidious/routes/before_all.cr @@ -1,4 +1,17 @@ module Invidious::Routes::BeforeAll + struct CompanionCSP + property companion_urls : String = "" + + def initialize + self.companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion| + uri = + "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" + end.join(" ") + end + end + + private COMPANION_CSP = CompanionCSP.new + def self.handle(env) preferences = Preferences.from_json("{}") @@ -35,9 +48,9 @@ module Invidious::Routes::BeforeAll "style-src 'self' 'unsafe-inline'", "img-src 'self' data:", "font-src 'self' data:", - "connect-src 'self'", + "connect-src 'self' " + COMPANION_CSP.companion_urls, "manifest-src 'self'", - "media-src 'self' blob:", + "media-src 'self' blob: " + COMPANION_CSP.companion_urls, "child-src 'self' blob:", "frame-src 'self'", "frame-ancestors " + frame_ancestors, diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr index d0a3b5c1..ec5a5804 100644 --- a/src/invidious/routes/embed.cr +++ b/src/invidious/routes/embed.cr @@ -208,17 +208,6 @@ module Invidious::Routes::Embed if CONFIG.invidious_companion.present? invidious_companion = CONFIG.invidious_companion.sample - invidious_companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion| - uri = - "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" - end.join(" ") - - if !invidious_companion_urls.empty? - env.response.headers["Content-Security-Policy"] = - env.response.headers["Content-Security-Policy"] - .gsub("media-src", "media-src #{invidious_companion_urls}") - .gsub("connect-src", "connect-src #{invidious_companion_urls}") - end end rendered "embed" diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 4c181503..b829b0f5 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -193,17 +193,6 @@ module Invidious::Routes::Watch if CONFIG.invidious_companion.present? invidious_companion = CONFIG.invidious_companion.sample - invidious_companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion| - uri = - "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" - end.join(" ") - - if !invidious_companion_urls.empty? - env.response.headers["Content-Security-Policy"] = - env.response.headers["Content-Security-Policy"] - .gsub("media-src", "media-src #{invidious_companion_urls}") - .gsub("connect-src", "connect-src #{invidious_companion_urls}") - end end templated "watch" From 0ee92e329857e416a24815ba13a9f4d951b28946 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Thu, 4 Dec 2025 11:59:06 -0300 Subject: [PATCH 19/23] Update src/invidious/routes/before_all.cr Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/invidious/routes/before_all.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr index 347a6021..6d374fff 100644 --- a/src/invidious/routes/before_all.cr +++ b/src/invidious/routes/before_all.cr @@ -4,8 +4,7 @@ module Invidious::Routes::BeforeAll def initialize self.companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion| - uri = - "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" + "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" end.join(" ") end end From cc7cb94095a27e2e544e21b11b85f027cd41de8f Mon Sep 17 00:00:00 2001 From: Fijxu Date: Thu, 12 Jun 2025 18:57:35 -0400 Subject: [PATCH 20/23] Document use of unix sockets for `db` --- config/config.example.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/config.example.yml b/config/config.example.yml index 7cc480c6..f3f43bba 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -8,6 +8,13 @@ ## Database configuration with separate parameters. ## This setting is MANDATORY, unless 'database_url' is used. ## +## Note: The 'db' setting allows the use of UNIX +## sockets. To do so, set 'host' to "" +## E.g: +## password: kemal +## host: "" +## port: 5432 +## db: user: kemal password: kemal From ffd9f4b11226c18cd06443917a438f667a323d6f Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Thu, 26 Jun 2025 19:15:12 +0000 Subject: [PATCH 21/23] pages/watch: HTML escape 'action' in download widget Caught in the review of PR 5224, but forgot to click on "send review" in time. I realized that too late, after the PR was already merged. --- src/invidious/frontend/watch_page.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/frontend/watch_page.cr b/src/invidious/frontend/watch_page.cr index 14e169e8..642ab4cc 100644 --- a/src/invidious/frontend/watch_page.cr +++ b/src/invidious/frontend/watch_page.cr @@ -36,7 +36,7 @@ module Invidious::Frontend::WatchPage return String.build(4000) do |str| str << "" From 067a426235b920bcb6d3c0fb36783f44c60dc7ba Mon Sep 17 00:00:00 2001 From: Fijxu Date: Fri, 16 Jan 2026 16:01:57 -0300 Subject: [PATCH 22/23] refactor: Move top level constants to it's own modules --- src/invidious.cr | 15 ++------------- src/invidious/comments/reddit.cr | 1 + src/invidious/helpers/helpers.cr | 2 ++ src/invidious/helpers/utils.cr | 2 ++ src/invidious/routes/api/v1/videos.cr | 5 ++++- src/invidious/routes/routes.cr | 21 +++++++++++++++++++++ src/invidious/routes/video_playback.cr | 2 ++ 7 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 src/invidious/routes/routes.cr diff --git a/src/invidious.cr b/src/invidious.cr index ec518453..d7c5b80b 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -67,20 +67,9 @@ rescue ex puts "Check your 'config.yml' database settings or PostgreSQL settings." exit(1) end -ARCHIVE_URL = URI.parse("https://archive.org") -PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com") -REDDIT_URL = URI.parse("https://www.reddit.com") -YT_URL = URI.parse("https://www.youtube.com") -HOST_URL = make_host_url(Kemal.config) - -CHARS_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" -TEST_IDS = {"AgbeGFYluEA", "BaW_jenozKc", "a9LDPn-MO4I", "ddFvjfvPnqk", "iqKdEhx-dD4"} +HOST_URL = make_host_url(Kemal.config) MAX_ITEMS_PER_PAGE = 1500 -REQUEST_HEADERS_WHITELIST = {"accept", "accept-encoding", "cache-control", "content-length", "if-none-match", "range"} -RESPONSE_HEADERS_BLACKLIST = {"access-control-allow-origin", "alt-svc", "server", "cross-origin-opener-policy-report-only", "report-to", "cross-origin", "timing-allow-origin", "cross-origin-resource-policy"} -HTTP_CHUNK_SIZE = 10485760 # ~10MB - CURRENT_BRANCH = {{ "#{`git branch | sed -n '/* /s///p'`.strip}" }} CURRENT_COMMIT = {{ "#{`git rev-list HEAD --max-count=1 --abbrev-commit`.strip}" }} CURRENT_VERSION = {{ "#{`git log -1 --format=%ci | awk '{print $1}' | sed s/-/./g`.strip}" }} @@ -97,7 +86,7 @@ SOFTWARE = { "branch" => "#{CURRENT_BRANCH}", } -YT_POOL = YoutubeConnectionPool.new(YT_URL, capacity: CONFIG.pool_size) +YT_POOL = YoutubeConnectionPool.new(URI.parse("https://www.youtube.com"), capacity: CONFIG.pool_size) # Image request pool diff --git a/src/invidious/comments/reddit.cr b/src/invidious/comments/reddit.cr index ba9c19f1..e128350c 100644 --- a/src/invidious/comments/reddit.cr +++ b/src/invidious/comments/reddit.cr @@ -1,5 +1,6 @@ module Invidious::Comments extend self + private REDDIT_URL = URI.parse("https://www.reddit.com") def fetch_reddit(id, sort_by = "confidence") client = make_client(REDDIT_URL) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 6add0237..ab694b1f 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -1,5 +1,7 @@ require "./macros" +TEST_IDS = {"AgbeGFYluEA", "BaW_jenozKc", "a9LDPn-MO4I", "ddFvjfvPnqk", "iqKdEhx-dD4"} + struct Nonce include DB::Serializable diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 5637e533..24b20ed9 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -1,3 +1,5 @@ +PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com") + # See http://www.evanmiller.org/how-not-to-sort-by-average-rating.html def ci_lower_bound(pos, n) if n == 0 diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index 6a3eb8ae..fc3de695 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -1,6 +1,9 @@ require "html" module Invidious::Routes::API::V1::Videos + private INTERNET_ARCHIVE_URL = URI.parse("https://archive.org") + private CHARS_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + def self.videos(env) locale = env.get("preferences").as(Preferences).locale @@ -279,7 +282,7 @@ module Invidious::Routes::API::V1::Videos file = URI.encode_www_form("#{id[0, 3]}/#{id}.xml") - location = make_client(ARCHIVE_URL, &.get("/download/youtubeannotations_#{index}/#{id[0, 2]}.tar/#{file}")) + location = make_client(INTERNET_ARCHIVE_URL, &.get("/download/youtubeannotations_#{index}/#{id[0, 2]}.tar/#{file}")) if !location.headers["Location"]? env.response.status_code = location.status_code diff --git a/src/invidious/routes/routes.cr b/src/invidious/routes/routes.cr new file mode 100644 index 00000000..57f10d35 --- /dev/null +++ b/src/invidious/routes/routes.cr @@ -0,0 +1,21 @@ +module Invidious::Routes + private REQUEST_HEADERS_WHITELIST = { + "accept", + "accept-encoding", + "cache-control", + "content-length", + "if-none-match", + "range", + } + private RESPONSE_HEADERS_BLACKLIST = { + "access-control-allow-origin", + "alt-svc", + "server", + "cross-origin-opener-policy-report-only", + "report-to", + "cross-origin", + "timing-allow-origin", + "cross-origin-resource-policy + ", + } +end diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr index 083087a9..7c01aa36 100644 --- a/src/invidious/routes/video_playback.cr +++ b/src/invidious/routes/video_playback.cr @@ -1,4 +1,6 @@ module Invidious::Routes::VideoPlayback + private HTTP_CHUNK_SIZE = 10485760 # ~10MB + # /videoplayback def self.get_video_playback(env) locale = env.get("preferences").as(Preferences).locale From 29c29f7c8d95da33898ebfed27752c4e0a8910dc Mon Sep 17 00:00:00 2001 From: Fijxu Date: Tue, 3 Feb 2026 17:32:22 -0300 Subject: [PATCH 23/23] Update src/invidious/routes/routes.cr Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/invidious/routes/routes.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/invidious/routes/routes.cr b/src/invidious/routes/routes.cr index 57f10d35..68b1ff82 100644 --- a/src/invidious/routes/routes.cr +++ b/src/invidious/routes/routes.cr @@ -15,7 +15,6 @@ module Invidious::Routes "report-to", "cross-origin", "timing-allow-origin", - "cross-origin-resource-policy - ", + "cross-origin-resource-policy", } end