Compare commits

..

11 Commits

Author SHA1 Message Date
Émilien (perso)
ad0bd9289f Release v2.20260626.0 (#5787)
* Release v2.20260626.0

* chore: improve changelog x1

Co-authored-by: Fijxu <fijxu@nadeko.net>

* chore: apply second suggestion

Co-authored-by: Fijxu <fijxu@nadeko.net>

---------

Co-authored-by: Fijxu <fijxu@nadeko.net>
2026-06-27 21:34:03 +02:00
Fijxu
08f862292a fix: fix playlists not showing any videos due to outdated playlist parsing (#5774)
* TODO, CHANGE COMMIT MESSAGE WHEN DONE

* save

* fix: fix playlists videos parsing

* revert text

* do not shadow outer item variable
2026-06-24 23:54:34 -04:00
dependabot[bot]
73a1bacea8 chore(deps): bump alpine from 3.23 to 3.24 in /docker (#5778)
Bumps alpine from 3.23 to 3.24.

---
updated-dependencies:
- dependency-name: alpine
  dependency-version: '3.24'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 22:59:52 +02:00
Fijxu
c435dc1204 fix: fix private invidious playlists on rss feeds from being fetched without authentication (#5776) 2026-06-14 22:31:22 -04:00
Jan Moesen
6dec63a3e5 Use “www.youtube.com” consistently (#5768)
Saves an unnecessary redirect by using `www.youtube.com` on some parts of Invidious where `youtube.com` was used instead. youtube.com will redirect to www.youtube.com anyways.

Co-authored-by: janmoesen <@>
2026-06-09 16:25:27 -04:00
dependabot[bot]
067260a4ab chore(deps): bump int128/docker-manifest-create-action (#5766)
Bumps [int128/docker-manifest-create-action](https://github.com/int128/docker-manifest-create-action) from 2.21.0 to 2.22.0.
- [Release notes](https://github.com/int128/docker-manifest-create-action/releases)
- [Commits](https://github.com/int128/docker-manifest-create-action/compare/v2.21.0...v2.22.0)

---
updated-dependencies:
- dependency-name: int128/docker-manifest-create-action
  dependency-version: 2.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-09 15:29:59 -04:00
Fijxu
6b21daab56 Revert "Fix: Restore whitespace formatting while keeping disable_abusable_api configuration"
This reverts commit 8f279745db.
2026-06-04 20:22:00 -04:00
Fijxu
8f279745db Fix: Restore whitespace formatting while keeping disable_abusable_api configuration 2026-06-04 20:20:14 -04:00
Fijxu
98f4f118b2 Add support for alternative domains for Invidious cookies (#5647)
* Add support for alternative domains for Invidious cookies

* check if @@secure is already true before changing it's value

* Add alternative_domains config option example
2026-06-04 19:59:32 -04:00
Fijxu
85534a988d Only include '&' if params are present (#5646) 2026-06-03 18:18:57 -04:00
Fijxu
0e0ee40cb6 Dockerfile: Switch to 84codes crystal compiler container image (#5473)
Closes https://github.com/iv-org/invidious/issues/5456

The 84codes Crystal container image builds libgc (bdwgc) with `--enable-large-config`: b321bb4358/alpine/Dockerfile (L13-L22)
2026-05-30 17:58:49 -04:00
17 changed files with 208 additions and 32 deletions

View File

@@ -86,7 +86,7 @@ jobs:
# https://github.com/marketplace/actions/docker-manifest-create-action # https://github.com/marketplace/actions/docker-manifest-create-action
- name: Create and push manifest - name: Create and push manifest
uses: int128/docker-manifest-create-action@v2.21.0 uses: int128/docker-manifest-create-action@v2.22.0
with: with:
push: true push: true
tags: quay.io/invidious/invidious:master tags: quay.io/invidious/invidious:master

View File

@@ -78,7 +78,7 @@ jobs:
# https://github.com/marketplace/actions/docker-manifest-create-action # https://github.com/marketplace/actions/docker-manifest-create-action
- name: Create and push manifest - name: Create and push manifest
uses: int128/docker-manifest-create-action@v2.21.0 uses: int128/docker-manifest-create-action@v2.22.0
with: with:
push: true push: true
tags: quay.io/invidious/invidious:latest tags: quay.io/invidious/invidious:latest

View File

@@ -2,6 +2,93 @@
## vX.Y.0 (future) ## vX.Y.0 (future)
## v2.20260626.0
### Wrap-up
This release hardens playlists, channels, and search, adds a privacy option for searches, and modernizes the packaging and CI pipeline.
Searches can now be submitted via `POST` so queries do not leak into server logs or browser history, Invidious cookies work across alternative domains, and "Watch on YouTube" / embed redirects use the correct timestamp and host. Playlist and channel parsing issues got fixed: outdated playlist parsing that hid all videos, paid course videos breaking imports, RSS feeds exposing private playlists without auth, broken author verification badges, and channel videos/playlists not loading from search. Thumbnail paths `/pl_c` / `/tvfilm_banner` are now supported, YouTube comments that were written in Japanese, Chinese, Korean and probably other languages do not longer swallows the last character when an emoji is present in the comment, and the search filters dropped the deprecated "sort by rating/date" options.
Packaging moves Docker builds to the 84codes Crystal compiler image, updates OpenSSL to 3.6.2 and Crystal to 1.20.x in OCI, bumps Alpine to 3.24, and unifies the ARM64 and AMD64 Dockerfiles. Developers benefit from continued encapsulation of constants/helpers/translation/video-parser logic into dedicated modules, an `api/v1/channels.cr` lint pass, trailing-whitespace cleanup, and a sweep of dependency and GitHub Actions bumps.
### New features & important changes
#### For Users
- Searches can be submitted through `POST` requests so queries stay out of URLs, server logs and browser history (#5551)
- Invidious cookies are honoured across alternative configured domains (#5647)
- Embed and "Watch on YouTube" redirects use the correct `t`/`start` parameter and the `www.youtube.com` host consistently (#5660, #5768)
- The `referrerpolicy`/`noreferrer` handling was corrected now that YouTube requires referrers on embeds (#5642)
- The listen button on the title updates its elapsed time, and the deprecated "sort by rating/date" search filter options were removed (#5625, #5629)
#### For instance owners
- Docker builds switched to the 84codes Crystal compiler container image, and OCI images were updated to Crystal 1.20.x with OpenSSL 3.6.2 (#5473, #5692)
- Alpine was bumped to 3.24 in the Docker image (#5778)
- ARM64 and AMD64 Dockerfiles were unified into a single workflow (#5700)
#### For developers
- Constants and functions were encapsulated into dedicated `I18n`, `Helpers`, `Invidious::Videos::Parser` and `Invidious::Videos::Clip` modules (#5637, #5639, #5745)
- `api/v1/channels.cr` received a lint pass and trailing whitespaces were removed from the codebase (#5693, #5634)
- CI bumped the Crystal version matrix and displayed compile progress/stats, and the `crystal-lang/install-crystal` action was updated (#5691, #5696, #5703, #5686)
### Bugs fixed
#### User-side
- Playlists showed no videos because of outdated playlist parsing; this is fixed along with paid course videos breaking the importer (#5774, #5207)
- Private Invidious playlists were reachable through RSS feeds without authentication (#5776)
- Channel videos and playlists failed to load from search, and channel author verification was broken (#5736, #5751)
- A missing `collectionThumbnailViewModel` hash key crashed channel browsing (#5725)
- The `quality=medium` query parameter was appended to videos about to premiere (#5755)
- YouTube/Invidious links did not rewind their timestamp when playback position was rewound (#5601)
- The last character of a comment was lost when the comment contained emoji (#5587)
- Playlist RSS `watch` URLs only joined params with `&` when params were present, and thumbnail paths `/pl_c` and `/tvfilm_banner` are now supported (#5646, #5742)
#### For instance owners
- Docker/OCI builds keep current with Crystal 1.20.1, OpenSSL 3.6.2, Alpine 3.24 and the unified multi-arch Dockerfile (#5703, #5701, #5778, #5700)
#### For developers
- Dependency and GitHub Actions bumps kept CI current: `docker/login-action`, `build-push-action`, `metadata-action`, `setup-buildx-action`, `int128/docker-manifest-create-action` and `crystal-lang/install-crystal` (#5705, #5766, #5721, #5686, #5661, #5662, #5663, #5664)
### Full list of pull requests merged since the last release (newest first)
* fix: fix playlists not showing any videos due to outdated playlist parsing (https://github.com/iv-org/invidious/pull/5774, by @Fijxu)
* chore(deps): bump alpine from 3.23 to 3.24 in /docker (https://github.com/iv-org/invidious/pull/5778, by @dependabot[bot])
* fix: fix private invidious playlists on rss feeds from being fetched without authentication (https://github.com/iv-org/invidious/pull/5776, by @Fijxu)
* Use "www.youtube.com" consistently (https://github.com/iv-org/invidious/pull/5768, by @janmoesen)
* chore(deps): bump int128/docker-manifest-create-action from 2.21.0 to 2.22.0 (https://github.com/iv-org/invidious/pull/5766, by @dependabot[bot])
* Add support for alternative domains for Invidious cookies (https://github.com/iv-org/invidious/pull/5647, by @Fijxu)
* Only include '&' if params are present in `watch` urls for playlist RSS (https://github.com/iv-org/invidious/pull/5646, by @Fijxu)
* Dockerfile: Switch to 84codes crystal compiler container image (https://github.com/iv-org/invidious/pull/5473, by @Fijxu)
* fix: Do not append query params `quality=medium` to videos that are about to premiere (https://github.com/iv-org/invidious/pull/5755, by @Fijxu)
* Fix Youtube and Invidious links not rewinding their time when video playback position is rewound (https://github.com/iv-org/invidious/pull/5601, by @Fijxu)
* feat: Add support for POST requests on searches for privacy (https://github.com/iv-org/invidious/pull/5551, by @Fijxu)
* Fix last character disappearance if emoji are in comment (https://github.com/iv-org/invidious/pull/5587, by @shiny-comic)
* Encapsulate videos parser and clip functions inside it's own `Invidious::Videos::Parser` and `Invidious::Videos::Clip` module (https://github.com/iv-org/invidious/pull/5745, by @Fijxu)
* fix: fix author verification in channels (https://github.com/iv-org/invidious/pull/5751, by @Fijxu)
* Add support for `/pl_c` and `/tvfilm_banner` paths (thumbnails used in some playlists) (https://github.com/iv-org/invidious/pull/5742, by @Fijxu)
* fix: fix channel videos and playlists on searches (https://github.com/iv-org/invidious/pull/5736, by @Fijxu)
* fix: fix `Missing hash key: "collectionThumbnailViewModel"` (https://github.com/iv-org/invidious/pull/5725, by @Fijxu)
* chore(deps): bump int128/docker-manifest-create-action from 2.20.0 to 2.21.0 (https://github.com/iv-org/invidious/pull/5721, by @dependabot[bot])
* chore: update openssl to 3.6.2 in OCI (https://github.com/iv-org/invidious/pull/5701, by @Fijxu)
* Bump int128/docker-manifest-create-action from 2.19.0 to 2.20.0 (https://github.com/iv-org/invidious/pull/5705, by @dependabot[bot])
* CI: Unify ARM64 and AMD64 Dockerfiles (https://github.com/iv-org/invidious/pull/5700, by @Fijxu)
* CI: update Crystal 1.20.0 to 1.20.1 in ci.yml matrix (https://github.com/iv-org/invidious/pull/5703, by @Fijxu)
* CI: display progress and stats when compiling Invidious in ci.yml matrix (https://github.com/iv-org/invidious/pull/5696, by @Fijxu)
* CI: Bump Crystal version matrix (https://github.com/iv-org/invidious/pull/5691, by @Fijxu)
* chore: update Crystal to 1.20.0 in OCI (https://github.com/iv-org/invidious/pull/5692, by @Fijxu)
* player: Use correct time parameter for YouTube embed redirects (https://github.com/iv-org/invidious/pull/5660, by @radmorecameron)
* chore: lint api/v1/channels.cr (https://github.com/iv-org/invidious/pull/5693, by @Fijxu)
* Encapsulate helpers constants and functions inside it's own `Helpers` module (https://github.com/iv-org/invidious/pull/5639, by @Fijxu)
* Encapsulate translation constants and functions inside it's own `I18n` module (https://github.com/iv-org/invidious/pull/5637, by @Fijxu)
* Bump crystal-lang/install-crystal from 1.9.1 to 1.9.2 (https://github.com/iv-org/invidious/pull/5686, by @dependabot[bot])
* Playlists: fix parsing error when some videos are paid for in a course (https://github.com/iv-org/invidious/pull/5207, by @ChunkyProgrammer)
* Bump docker/login-action from 3 to 4 (https://github.com/iv-org/invidious/pull/5661, by @dependabot[bot])
* Bump docker/build-push-action from 6 to 7 (https://github.com/iv-org/invidious/pull/5662, by @dependabot[bot])
* Bump docker/metadata-action from 5 to 6 (https://github.com/iv-org/invidious/pull/5663, by @dependabot[bot])
* Bump docker/setup-buildx-action from 3 to 4 (https://github.com/iv-org/invidious/pull/5664, by @dependabot[bot])
* Remove noreferrer since youtube now requires referrers on embeds (https://github.com/iv-org/invidious/pull/5642, by @ashleyirispuppy143)
* Remove trailing whitespaces from codebase (https://github.com/iv-org/invidious/pull/5634, by @Fijxu)
* Add title listen button time updates (https://github.com/iv-org/invidious/pull/5625, by @JeroenBoersma)
* Remove sort by rating and date in video search filters (https://github.com/iv-org/invidious/pull/5629, by @Fijxu)
## v2.20260207.0 ## v2.20260207.0
### Wrap-up ### Wrap-up

View File

@@ -151,6 +151,26 @@ db:
## ##
domain: domain:
##
## List of alternative domains where the invidious instance is being served.
## This needs to be set in order to be able to login and update user preferences
## when using a domain that is not the same as the `domain` configuration,
## like a .`onion` address, `.i2p` address, `.b32.i2p` address, etc.
##
## It will detect the alternative domain trough the `X-Forwarded-Host` header.
## https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host
##
## Accepted values: a list of fully qualified domain names (FQDN)
## Default: <none>
##
## Example:
## alternative_domains:
## - invidious.example.com
## - inv.example.com
## - videos.example.com
##
alternative_domains:
## ##
## Tell Invidious that it is behind a proxy that provides only ## Tell Invidious that it is behind a proxy that provides only
## HTTPS, so all links must use the https:// scheme. This ## HTTPS, so all links must use the https:// scheme. This

View File

@@ -61,7 +61,7 @@ RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ;
--link-flags "-lxml2 -llzma"; \ --link-flags "-lxml2 -llzma"; \
fi fi
FROM alpine:3.23 FROM alpine:3.24
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
WORKDIR /invidious WORKDIR /invidious
RUN addgroup -g 1000 -S invidious && \ RUN addgroup -g 1000 -S invidious && \

View File

@@ -1,5 +1,5 @@
name: invidious name: invidious
version: 2.20260207.0-dev version: 2.20260626.0
authors: authors:
- Invidious team <contact@invidious.io> - Invidious team <contact@invidious.io>

View File

@@ -121,6 +121,8 @@ class Config
property hmac_key : String = "" property hmac_key : String = ""
# Domain to be used for links to resources on the site where an absolute URL is required # Domain to be used for links to resources on the site where an absolute URL is required
property domain : String? property domain : String?
# Additional domain list that is going to be used for cookie domain validation
property alternative_domains : Array(String) = [] of String
# Subscribe to channels using PubSubHubbub (requires domain, hmac_key) # Subscribe to channels using PubSubHubbub (requires domain, hmac_key)
property use_pubsub_feeds : Bool | Int32 = false property use_pubsub_feeds : Bool | Int32 = false
property popular_enabled : Bool = true property popular_enabled : Bool = true

View File

@@ -201,7 +201,7 @@ def error_redirect_helper(env : HTTP::Server::Context)
<a href="/redirect?referer=#{env.get("current_page")}">#{switch_instance}</a> <a href="/redirect?referer=#{env.get("current_page")}">#{switch_instance}</a>
</li> </li>
<li> <li>
<a rel="noreferrer noopener" href="https://youtube.com#{env.request.resource}">#{go_to_youtube}</a> <a rel="noreferrer noopener" href="https://www.youtube.com#{env.request.resource}">#{go_to_youtube}</a>
</li> </li>
</ul> </ul>
END_HTML END_HTML

View File

@@ -454,6 +454,10 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
end end
end end
# TODO (2026-06-24): Migrate this function to use parsers instead, as it uses,
# the same LockupViewModel used in Channel videos and Youtube playlists that
# appears on searches (Invidious /search endpoint).
# Related to https://github.com/iv-org/invidious/pull/5736
def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JSON::Any)) def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JSON::Any))
videos = [] of PlaylistVideo | ProblematicTimelineItem videos = [] of PlaylistVideo | ProblematicTimelineItem
@@ -467,8 +471,7 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
tabs_contents = tabs_renderer.["contents"]? || tabs_renderer.["content"] tabs_contents = tabs_renderer.["contents"]? || tabs_renderer.["content"]
list_renderer = tabs_contents.["sectionListRenderer"]["contents"][0] list_renderer = tabs_contents.["sectionListRenderer"]["contents"][0]
item_renderer = list_renderer.["itemSectionRenderer"]["contents"][0] contents = list_renderer.["itemSectionRenderer"]["contents"].as_a
contents = item_renderer.["playlistVideoListRenderer"]["contents"].as_a
else else
# Continuation data # Continuation data
contents = initial_data["onResponseReceivedActions"][0]? contents = initial_data["onResponseReceivedActions"][0]?
@@ -479,15 +482,39 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
end end
contents.try &.each do |item| contents.try &.each do |item|
if i = item["playlistVideoRenderer"]? if i = item["lockupViewModel"]?
video_id = i.dig?("navigationEndpoint", "watchEndpoint", "videoId").try &.as_s || i.dig("videoId").as_s thumbnail_view_model = i.dig?(
plid = i.dig?("navigationEndpoint", "watchEndpoint", "playlistId").try &.as_s || playlist_id "contentImage", "thumbnailViewModel"
index = i.dig?("navigationEndpoint", "watchEndpoint", "index").try &.as_i64 || i.dig("index", "simpleText").as_s.to_i64 )
watch_endpoint = i.dig?("rendererContext", "commandContext", "onTap", "innertubeCommand", "watchEndpoint")
video_id = watch_endpoint.try &.["videoId"]?.try &.as_s
plid = watch_endpoint.try &.["playlistId"]?.try &.as_s || playlist_id
index = watch_endpoint.try &.["index"]?.try &.as_i64
metadata = i["metadata"]?
lockup_metadata_view_model = metadata.try &.dig?("lockupMetadataViewModel")
title = lockup_metadata_view_model.try &.dig?("title", "content").try &.as_s
lockup_metadata = lockup_metadata_view_model.try &.dig?("metadata")
metadata_rows = lockup_metadata.try &.dig?("contentMetadataViewModel", "metadataRows").try &.as_a
# Find the metadataParts with commandRuns inside, which contains author
# information.
metadata_parts = metadata_rows.try &.find { |row|
parts = row["metadataParts"]?.try &.as_a
parts && parts.any? { |item2| item2.dig?("text", "commandRuns").try &.as_a }
}.try &.["metadataParts"].as_a
if author_info = metadata_parts.try &.find(&.dig?("text", "commandRuns"))
.try &.["text"]
author = author_info["content"].as_s
ucid = author_info.dig?("commandRuns", 0, "onTap", "innertubeCommand", "browseEndpoint", "browseId")
.try &.as_s
end
length = thumbnail_view_model.try &.dig?("overlays", 0, "thumbnailBottomOverlayViewModel", "badges", 0, "thumbnailBadgeViewModel", "text").try &.as_s
length_seconds = decode_length_seconds(length) if length
title = i["title"].try { |t| t["simpleText"]? || t["runs"]?.try &.[0]["text"]? }.try &.as_s || ""
author = i["shortBylineText"]?.try &.["runs"][0]["text"].as_s || ""
ucid = i["shortBylineText"]?.try &.["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s || ""
length_seconds = i["lengthSeconds"]?.try &.as_s.to_i
live = false live = false
if !length_seconds if !length_seconds
@@ -496,15 +523,15 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
end end
videos << PlaylistVideo.new({ videos << PlaylistVideo.new({
title: title, title: title || "",
id: video_id, id: video_id || "",
author: author, author: author || "",
ucid: ucid, ucid: ucid || "",
length_seconds: length_seconds, length_seconds: length_seconds,
published: Time.utc, published: Time.utc,
plid: plid, plid: plid,
live_now: live, live_now: live,
index: index, index: index || -1_i64,
}) })
end end
rescue ex rescue ex

View File

@@ -32,6 +32,8 @@ module Invidious::Routes::BeforeAll
env.response.headers["X-XSS-Protection"] = "1; mode=block" env.response.headers["X-XSS-Protection"] = "1; mode=block"
env.response.headers["X-Content-Type-Options"] = "nosniff" env.response.headers["X-Content-Type-Options"] = "nosniff"
env.set "header_x-forwarded-host", env.request.headers["X-Forwarded-Host"]?
# Only allow the pages at /embed/* to be embedded # Only allow the pages at /embed/* to be embedded
if env.request.resource.starts_with?("/embed") if env.request.resource.starts_with?("/embed")
frame_ancestors = "'self' file: http: https:" frame_ancestors = "'self' file: http: https:"

View File

@@ -351,7 +351,7 @@ module Invidious::Routes::Channels
invidious_url_params.delete_all("user") invidious_url_params.delete_all("user")
begin begin
resolved_url = YoutubeAPI.resolve_url("https://youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}") resolved_url = YoutubeAPI.resolve_url("https://www.youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}")
ucid = resolved_url["endpoint"]["browseEndpoint"]["browseId"] ucid = resolved_url["endpoint"]["browseEndpoint"]["browseId"]
rescue ex : InfoException | KeyError rescue ex : InfoException | KeyError
return error_template(404, I18n.translate(locale, "This channel does not exist.")) return error_template(404, I18n.translate(locale, "This channel does not exist."))

View File

@@ -8,7 +8,7 @@ module Invidious::Routes::ErrorRoutes
if md = env.request.path.match(/^\/(?<id>([a-zA-Z0-9_-]{11})|(\w+))$/) if md = env.request.path.match(/^\/(?<id>([a-zA-Z0-9_-]{11})|(\w+))$/)
item = md["id"] item = md["id"]
# Check if item is branding URL e.g. https://youtube.com/gaming # Check if item is branding URL e.g. https://www.youtube.com/gaming
response = YT_POOL.client &.get("/#{item}") response = YT_POOL.client &.get("/#{item}")
if response.status_code == 301 if response.status_code == 301

View File

@@ -283,6 +283,11 @@ module Invidious::Routes::Feeds
if playlist = Invidious::Database::Playlists.select(id: plid) if playlist = Invidious::Database::Playlists.select(id: plid)
videos = get_playlist_videos(playlist, offset: 0) videos = get_playlist_videos(playlist, offset: 0)
user = env.get?("user").try &.as(User)
if !playlist || playlist.privacy.private? && playlist.author != user.try &.email
return error_atom(404, "Playlist does not exist.")
end
return XML.build(indent: " ", encoding: "UTF-8") do |xml| return XML.build(indent: " ", encoding: "UTF-8") do |xml|
xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015", xml.element("feed", "xmlns:yt": "http://www.youtube.com/xml/schemas/2015",
"xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom", "xmlns:media": "http://search.yahoo.com/mrss/", xmlns: "http://www.w3.org/2005/Atom",
@@ -320,7 +325,7 @@ module Invidious::Routes::Feeds
case attribute.name case attribute.name
when "url", "href" when "url", "href"
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}" if !params.empty?) : ""
node[attribute.name] = "#{HOST_URL}#{request_target}#{query_string_opt}" node[attribute.name] = "#{HOST_URL}#{request_target}#{query_string_opt}"
else nil # Skip else nil # Skip
end end

View File

@@ -26,6 +26,7 @@ module Invidious::Routes::Login
def self.login(env) def self.login(env)
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
host = env.get("header_x-forwarded-host")
referer = get_referer(env, "/feed/subscriptions") referer = get_referer(env, "/feed/subscriptions")
@@ -57,7 +58,11 @@ module Invidious::Routes::Login
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
Invidious::Database::SessionIDs.insert(sid, email) Invidious::Database::SessionIDs.insert(sid, email)
if alt = CONFIG.alternative_domains.index(host)
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.alternative_domains[alt], sid)
else
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
end
else else
return error_template(401, "Wrong username or password") return error_template(401, "Wrong username or password")
end end
@@ -123,7 +128,11 @@ module Invidious::Routes::Login
view_name = "subscriptions_#{sha256(user.email)}" view_name = "subscriptions_#{sha256(user.email)}"
PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}") PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
if alt = CONFIG.alternative_domains.index(host)
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.alternative_domains[alt], sid)
else
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
end
if env.request.cookies["PREFS"]? if env.request.cookies["PREFS"]?
user.preferences = env.get("preferences").as(Preferences) user.preferences = env.get("preferences").as(Preferences)

View File

@@ -230,9 +230,14 @@ module Invidious::Routes::PreferencesRoute
File.write("config/config.yml", CONFIG.to_yaml) File.write("config/config.yml", CONFIG.to_yaml)
end end
else
host = env.get("header_x-forwarded-host")
if alt = CONFIG.alternative_domains.index(host)
env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.alternative_domains[alt], preferences)
else else
env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences)
end end
end
env.redirect referer env.redirect referer
end end
@@ -266,8 +271,13 @@ module Invidious::Routes::PreferencesRoute
preferences.dark_mode = "dark" preferences.dark_mode = "dark"
end end
host = env.get("header_x-forwarded-host")
if alt = CONFIG.alternative_domains.index(host)
env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.alternative_domains[alt], preferences)
else
env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences)
end end
end
if redirect if redirect
env.redirect referer env.redirect referer

View File

@@ -6,17 +6,24 @@ struct Invidious::User
# Note: we use ternary operator because the two variables # Note: we use ternary operator because the two variables
# used in here are not booleans. # used in here are not booleans.
SECURE = (Kemal.config.ssl || CONFIG.https_only) ? true : false @@secure = (Kemal.config.ssl || CONFIG.https_only) ? true : false
# 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
# Not secure if it's being accessed from I2P
# Browsers expect the domain to include https. On I2P there is no HTTPS
# Tor browser works fine with secure being true
if (domain.try &.split(".").last == "i2p") && @@secure
@@secure = false
end
return HTTP::Cookie.new( return HTTP::Cookie.new(
name: "SID", name: "SID",
domain: domain, domain: domain,
value: sid, value: sid,
expires: Time.utc + 2.years, expires: Time.utc + 2.years,
secure: SECURE, secure: @@secure,
http_only: true, http_only: true,
samesite: HTTP::Cookie::SameSite::Lax samesite: HTTP::Cookie::SameSite::Lax
) )
@@ -25,12 +32,19 @@ 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
# Not secure if it's being accessed from I2P
# Browsers expect the domain to include https. On I2P there is no HTTPS
# Tor browser works fine with secure being true
if (domain.try &.split(".").last == "i2p") && @@secure
@@secure = false
end
return HTTP::Cookie.new( return 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),
expires: Time.utc + 2.years, expires: Time.utc + 2.years,
secure: SECURE, secure: @@secure,
http_only: false, http_only: false,
samesite: HTTP::Cookie::SameSite::Lax samesite: HTTP::Cookie::SameSite::Lax
) )

View File

@@ -480,7 +480,7 @@ module YoutubeAPI
# #
# ``` # ```
# # Valid channel "brand URL" gives the related UCID and browse ID # # Valid channel "brand URL" gives the related UCID and browse ID
# channel_a = YoutubeAPI.resolve_url("https://youtube.com/c/google") # channel_a = YoutubeAPI.resolve_url("https://www.youtube.com/c/google")
# channel_a # => { # channel_a # => {
# "endpoint": { # "endpoint": {
# "browseEndpoint": { # "browseEndpoint": {
@@ -492,7 +492,7 @@ module YoutubeAPI
# } # }
# #
# # Invalid URL returns throws an InfoException # # Invalid URL returns throws an InfoException
# channel_b = YoutubeAPI.resolve_url("https://youtube.com/c/invalid") # channel_b = YoutubeAPI.resolve_url("https://www.youtube.com/c/invalid")
# ``` # ```
# #
def resolve_url(url : String, client_config : ClientConfig | Nil = nil) def resolve_url(url : String, client_config : ClientConfig | Nil = nil)