mirror of
https://github.com/iv-org/invidious.git
synced 2026-06-27 10:54:06 +05:30
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47ce8a726b |
@@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
# https://github.com/marketplace/actions/docker-manifest-create-action
|
||||
- name: Create and push manifest
|
||||
uses: int128/docker-manifest-create-action@v2.22.0
|
||||
uses: int128/docker-manifest-create-action@v2.21.0
|
||||
with:
|
||||
push: true
|
||||
tags: quay.io/invidious/invidious:master
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
# https://github.com/marketplace/actions/docker-manifest-create-action
|
||||
- name: Create and push manifest
|
||||
uses: int128/docker-manifest-create-action@v2.22.0
|
||||
uses: int128/docker-manifest-create-action@v2.21.0
|
||||
with:
|
||||
push: true
|
||||
tags: quay.io/invidious/invidious:latest
|
||||
|
||||
@@ -2,93 +2,6 @@
|
||||
|
||||
## 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 unused "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
|
||||
|
||||
### Wrap-up
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
CREATE INDEX channel_videos_ucid_published_idx
|
||||
ON public.channel_videos
|
||||
USING btree
|
||||
(ucid COLLATE pg_catalog."default", published);
|
||||
|
||||
DROP INDEX channel_videos_ucid_idx;
|
||||
@@ -19,12 +19,12 @@ CREATE TABLE IF NOT EXISTS public.channel_videos
|
||||
|
||||
GRANT ALL ON TABLE public.channel_videos TO current_user;
|
||||
|
||||
-- Index: public.channel_videos_ucid_idx
|
||||
-- Index: public.channel_videos_ucid_published_idx
|
||||
|
||||
-- DROP INDEX public.channel_videos_ucid_idx;
|
||||
-- DROP INDEX public.channel_videos_ucid_published_idx;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx
|
||||
CREATE INDEX IF NOT EXISTS channel_videos_ucid_published_idx
|
||||
ON public.channel_videos
|
||||
USING btree
|
||||
(ucid COLLATE pg_catalog."default");
|
||||
(ucid COLLATE pg_catalog."default", published);
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ;
|
||||
--link-flags "-lxml2 -llzma"; \
|
||||
fi
|
||||
|
||||
FROM alpine:3.24
|
||||
FROM alpine:3.23
|
||||
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
|
||||
WORKDIR /invidious
|
||||
RUN addgroup -g 1000 -S invidious && \
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
name: invidious
|
||||
|
||||
image:
|
||||
repository: quay.io/invidious/invidious
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 16
|
||||
targetCPUUtilizationPercentage: 50
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 3000
|
||||
#loadBalancerIP:
|
||||
|
||||
resources: {}
|
||||
#requests:
|
||||
# cpu: 100m
|
||||
# memory: 64Mi
|
||||
#limits:
|
||||
# cpu: 800m
|
||||
# memory: 512Mi
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
|
||||
# See https://github.com/bitnami/charts/tree/master/bitnami/postgresql
|
||||
postgresql:
|
||||
image:
|
||||
tag: 13
|
||||
auth:
|
||||
username: kemal
|
||||
password: kemal
|
||||
database: invidious
|
||||
primary:
|
||||
initdb:
|
||||
username: kemal
|
||||
password: kemal
|
||||
scriptsConfigMap: invidious-postgresql-init
|
||||
|
||||
# Adapted from ../config/config.yml
|
||||
config:
|
||||
channel_threads: 1
|
||||
db:
|
||||
user: kemal
|
||||
password: kemal
|
||||
host: invidious-postgresql
|
||||
port: 5432
|
||||
dbname: invidious
|
||||
full_refresh: false
|
||||
https_only: false
|
||||
domain:
|
||||
@@ -1,5 +1,5 @@
|
||||
name: invidious
|
||||
version: 2.20260626.0
|
||||
version: 2.20260207.0-dev
|
||||
|
||||
authors:
|
||||
- Invidious team <contact@invidious.io>
|
||||
|
||||
@@ -107,14 +107,6 @@ Kemal.config.extra_options do |parser|
|
||||
exit
|
||||
end
|
||||
end
|
||||
parser.on("-f THREADS", "--feed-threads=THREADS", "Number of threads for refreshing feeds (default: #{CONFIG.feed_threads})") do |number|
|
||||
begin
|
||||
CONFIG.feed_threads = number.to_i
|
||||
rescue ex
|
||||
puts "THREADS must be integer"
|
||||
exit
|
||||
end
|
||||
end
|
||||
parser.on("-o OUTPUT", "--output=OUTPUT", "Redirect output (default: #{CONFIG.output})") do |output|
|
||||
CONFIG.output = output
|
||||
end
|
||||
@@ -166,10 +158,6 @@ if CONFIG.channel_threads > 0
|
||||
Invidious::Jobs.register Invidious::Jobs::RefreshChannelsJob.new(PG_DB)
|
||||
end
|
||||
|
||||
if CONFIG.feed_threads > 0
|
||||
Invidious::Jobs.register Invidious::Jobs::RefreshFeedsJob.new(PG_DB)
|
||||
end
|
||||
|
||||
if CONFIG.statistics_enabled
|
||||
Invidious::Jobs.register Invidious::Jobs::StatisticsRefreshJob.new(PG_DB, SOFTWARE)
|
||||
end
|
||||
|
||||
@@ -95,8 +95,6 @@ class Config
|
||||
# Time interval between two executions of the job that crawls channel videos (subscriptions update).
|
||||
@[YAML::Field(converter: Preferences::TimeSpanConverter)]
|
||||
property channel_refresh_interval : Time::Span = 30.minutes
|
||||
# Number of threads to use for updating feeds
|
||||
property feed_threads : Int32 = 1
|
||||
# Log file path or STDOUT
|
||||
property output : String = "STDOUT"
|
||||
# Default log level, valid YAML values are ints and strings, see src/invidious/helpers/logger.cr
|
||||
|
||||
@@ -201,7 +201,7 @@ def error_redirect_helper(env : HTTP::Server::Context)
|
||||
<a href="/redirect?referer=#{env.get("current_page")}">#{switch_instance}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a rel="noreferrer noopener" href="https://www.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>
|
||||
</ul>
|
||||
END_HTML
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
class Invidious::Jobs::RefreshFeedsJob < Invidious::Jobs::BaseJob
|
||||
private getter db : DB::Database
|
||||
|
||||
def initialize(@db)
|
||||
end
|
||||
|
||||
def begin
|
||||
max_fibers = CONFIG.feed_threads
|
||||
active_fibers = 0
|
||||
active_channel = ::Channel(Bool).new
|
||||
|
||||
loop do
|
||||
db.query("SELECT email FROM users WHERE feed_needs_update = true OR feed_needs_update IS NULL") do |rs|
|
||||
rs.each do
|
||||
email = rs.read(String)
|
||||
view_name = "subscriptions_#{sha256(email)}"
|
||||
|
||||
if active_fibers >= max_fibers
|
||||
if active_channel.receive
|
||||
active_fibers -= 1
|
||||
end
|
||||
end
|
||||
|
||||
active_fibers += 1
|
||||
spawn do
|
||||
begin
|
||||
# Drop outdated views
|
||||
column_array = Invidious::Database.get_column_array(db, view_name)
|
||||
ChannelVideo.type_array.each_with_index do |name, i|
|
||||
if name != column_array[i]?
|
||||
LOGGER.info("RefreshFeedsJob: DROP MATERIALIZED VIEW #{view_name}")
|
||||
db.exec("DROP MATERIALIZED VIEW #{view_name}")
|
||||
raise "view does not exist"
|
||||
end
|
||||
end
|
||||
|
||||
if !db.query_one("SELECT pg_get_viewdef('#{view_name}')", as: String).includes? "WHERE ((cv.ucid = ANY (u.subscriptions))"
|
||||
LOGGER.info("RefreshFeedsJob: Materialized view #{view_name} is out-of-date, recreating...")
|
||||
db.exec("DROP MATERIALIZED VIEW #{view_name}")
|
||||
end
|
||||
|
||||
db.exec("REFRESH MATERIALIZED VIEW #{view_name}")
|
||||
db.exec("UPDATE users SET feed_needs_update = false WHERE email = $1", email)
|
||||
rescue ex
|
||||
# Rename old views
|
||||
begin
|
||||
legacy_view_name = "subscriptions_#{sha256(email)[0..7]}"
|
||||
|
||||
db.exec("SELECT * FROM #{legacy_view_name} LIMIT 0")
|
||||
LOGGER.info("RefreshFeedsJob: RENAME MATERIALIZED VIEW #{legacy_view_name}")
|
||||
db.exec("ALTER MATERIALIZED VIEW #{legacy_view_name} RENAME TO #{view_name}")
|
||||
rescue ex
|
||||
begin
|
||||
# While iterating through, we may have an email stored from a deleted account
|
||||
if db.query_one?("SELECT true FROM users WHERE email = $1", email, as: Bool)
|
||||
LOGGER.info("RefreshFeedsJob: CREATE #{view_name}")
|
||||
db.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(email)}")
|
||||
db.exec("UPDATE users SET feed_needs_update = false WHERE email = $1", email)
|
||||
end
|
||||
rescue ex
|
||||
LOGGER.error("RefreshFeedJobs: REFRESH #{email} : #{ex.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
active_channel.send(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sleep 5.seconds
|
||||
Fiber.yield
|
||||
end
|
||||
end
|
||||
end
|
||||
+15
-42
@@ -454,10 +454,6 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
|
||||
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))
|
||||
videos = [] of PlaylistVideo | ProblematicTimelineItem
|
||||
|
||||
@@ -471,7 +467,8 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
|
||||
tabs_contents = tabs_renderer.["contents"]? || tabs_renderer.["content"]
|
||||
|
||||
list_renderer = tabs_contents.["sectionListRenderer"]["contents"][0]
|
||||
contents = list_renderer.["itemSectionRenderer"]["contents"].as_a
|
||||
item_renderer = list_renderer.["itemSectionRenderer"]["contents"][0]
|
||||
contents = item_renderer.["playlistVideoListRenderer"]["contents"].as_a
|
||||
else
|
||||
# Continuation data
|
||||
contents = initial_data["onResponseReceivedActions"][0]?
|
||||
@@ -482,39 +479,15 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
|
||||
end
|
||||
|
||||
contents.try &.each do |item|
|
||||
if i = item["lockupViewModel"]?
|
||||
thumbnail_view_model = i.dig?(
|
||||
"contentImage", "thumbnailViewModel"
|
||||
)
|
||||
|
||||
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
|
||||
if i = item["playlistVideoRenderer"]?
|
||||
video_id = i.dig?("navigationEndpoint", "watchEndpoint", "videoId").try &.as_s || i.dig("videoId").as_s
|
||||
plid = i.dig?("navigationEndpoint", "watchEndpoint", "playlistId").try &.as_s || playlist_id
|
||||
index = i.dig?("navigationEndpoint", "watchEndpoint", "index").try &.as_i64 || i.dig("index", "simpleText").as_s.to_i64
|
||||
|
||||
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
|
||||
|
||||
if !length_seconds
|
||||
@@ -523,15 +496,15 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
|
||||
end
|
||||
|
||||
videos << PlaylistVideo.new({
|
||||
title: title || "",
|
||||
id: video_id || "",
|
||||
author: author || "",
|
||||
ucid: ucid || "",
|
||||
title: title,
|
||||
id: video_id,
|
||||
author: author,
|
||||
ucid: ucid,
|
||||
length_seconds: length_seconds,
|
||||
published: Time.utc,
|
||||
plid: plid,
|
||||
live_now: live,
|
||||
index: index || -1_i64,
|
||||
index: index,
|
||||
})
|
||||
end
|
||||
rescue ex
|
||||
|
||||
@@ -123,10 +123,8 @@ module Invidious::Routes::Account
|
||||
return error_template(400, ex)
|
||||
end
|
||||
|
||||
view_name = "subscriptions_#{sha256(user.email)}"
|
||||
Invidious::Database::Users.delete(user)
|
||||
Invidious::Database::SessionIDs.delete(email: user.email)
|
||||
PG_DB.exec("DROP MATERIALIZED VIEW #{view_name}")
|
||||
|
||||
env.request.cookies.each do |cookie|
|
||||
cookie.expires = Time.utc(1990, 1, 1)
|
||||
|
||||
@@ -351,7 +351,7 @@ module Invidious::Routes::Channels
|
||||
invidious_url_params.delete_all("user")
|
||||
|
||||
begin
|
||||
resolved_url = YoutubeAPI.resolve_url("https://www.youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}")
|
||||
resolved_url = YoutubeAPI.resolve_url("https://youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}")
|
||||
ucid = resolved_url["endpoint"]["browseEndpoint"]["browseId"]
|
||||
rescue ex : InfoException | KeyError
|
||||
return error_template(404, I18n.translate(locale, "This channel does not exist."))
|
||||
|
||||
@@ -8,7 +8,7 @@ module Invidious::Routes::ErrorRoutes
|
||||
if md = env.request.path.match(/^\/(?<id>([a-zA-Z0-9_-]{11})|(\w+))$/)
|
||||
item = md["id"]
|
||||
|
||||
# Check if item is branding URL e.g. https://www.youtube.com/gaming
|
||||
# Check if item is branding URL e.g. https://youtube.com/gaming
|
||||
response = YT_POOL.client &.get("/#{item}")
|
||||
|
||||
if response.status_code == 301
|
||||
|
||||
@@ -283,11 +283,6 @@ module Invidious::Routes::Feeds
|
||||
if playlist = Invidious::Database::Playlists.select(id: plid)
|
||||
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|
|
||||
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",
|
||||
|
||||
@@ -125,9 +125,6 @@ module Invidious::Routes::Login
|
||||
Invidious::Database::Users.insert(user)
|
||||
Invidious::Database::SessionIDs.insert(sid, email)
|
||||
|
||||
view_name = "subscriptions_#{sha256(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
|
||||
|
||||
@@ -37,18 +37,18 @@ module Invidious::Search
|
||||
|
||||
# Search inside of user subscriptions
|
||||
def subscriptions(query : Query, user : Invidious::User) : Array(ChannelVideo)
|
||||
view_name = "subscriptions_#{sha256(user.email)}"
|
||||
|
||||
return PG_DB.query_all("
|
||||
SELECT id,title,published,updated,ucid,author,length_seconds
|
||||
FROM (
|
||||
SELECT *,
|
||||
to_tsvector(#{view_name}.title) ||
|
||||
to_tsvector(#{view_name}.author)
|
||||
as document
|
||||
FROM #{view_name}
|
||||
) v_search WHERE v_search.document @@ plainto_tsquery($1) LIMIT 20 OFFSET $2;",
|
||||
query.text, (query.page - 1) * 20,
|
||||
SELECT cv.*,
|
||||
to_tsvector(cv.title) ||
|
||||
to_tsvector(cv.author) AS document
|
||||
FROM channel_videos cv
|
||||
JOIN users ON cv.ucid = any(users.subscriptions)
|
||||
WHERE users.email = $1 AND published > now() - interval '1 month'
|
||||
ORDER BY published
|
||||
) v_search WHERE v_search.document @@ plainto_tsquery($2) LIMIT 20 OFFSET $3;",
|
||||
user.email, query.text, (query.page - 1) * 20,
|
||||
as: ChannelVideo
|
||||
)
|
||||
end
|
||||
|
||||
+22
-17
@@ -27,7 +27,6 @@ def get_subscription_feed(user, max_results = 40, page = 1)
|
||||
offset = (page - 1) * limit
|
||||
|
||||
notifications = Invidious::Database::Users.select_notifications(user)
|
||||
view_name = "subscriptions_#{sha256(user.email)}"
|
||||
|
||||
if user.preferences.notifications_only && !notifications.empty?
|
||||
# Only show notifications
|
||||
@@ -53,33 +52,39 @@ def get_subscription_feed(user, max_results = 40, page = 1)
|
||||
# Show latest video from a channel that a user hasn't watched
|
||||
# "unseen_only" isn't really correct here, more accurate would be "unwatched_only"
|
||||
|
||||
if user.watched.empty?
|
||||
values = "'{}'"
|
||||
else
|
||||
values = "VALUES #{user.watched.map { |id| %(('#{id}')) }.join(",")}"
|
||||
end
|
||||
videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} WHERE NOT id = ANY (#{values}) ORDER BY ucid, published DESC", as: ChannelVideo)
|
||||
# "SELECT cv.* FROM channel_videos cv JOIN users ON cv.ucid = any(users.subscriptions) WHERE users.email = $1 AND published > now() - interval '1 month' ORDER BY published DESC"
|
||||
# "SELECT DISTINCT ON (cv.ucid) cv.* FROM channel_videos cv JOIN users ON cv.ucid = any(users.subscriptions) WHERE users.email = ? AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' ORDER BY ucid, published DESC"
|
||||
videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||
"FROM channel_videos cv " \
|
||||
"JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||
"WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \
|
||||
"ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||
else
|
||||
# Show latest video from each channel
|
||||
|
||||
videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} ORDER BY ucid, published DESC", as: ChannelVideo)
|
||||
videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \
|
||||
"FROM channel_videos cv " \
|
||||
"JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||
"WHERE users.email = $1 AND published > now() - interval '1 month' " \
|
||||
"ORDER BY ucid, published DESC", user.email, as: ChannelVideo)
|
||||
end
|
||||
|
||||
videos.sort_by!(&.published).reverse!
|
||||
else
|
||||
if user.preferences.unseen_only
|
||||
# Only show unwatched
|
||||
|
||||
if user.watched.empty?
|
||||
values = "'{}'"
|
||||
else
|
||||
values = "VALUES #{user.watched.map { |id| %(('#{id}')) }.join(",")}"
|
||||
end
|
||||
videos = PG_DB.query_all("SELECT * FROM #{view_name} WHERE NOT id = ANY (#{values}) ORDER BY published DESC LIMIT $1 OFFSET $2", limit, offset, as: ChannelVideo)
|
||||
videos = PG_DB.query_all("SELECT cv.* " \
|
||||
"FROM channel_videos cv " \
|
||||
"JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||
"WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \
|
||||
"ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||
else
|
||||
# Sort subscriptions as normal
|
||||
|
||||
videos = PG_DB.query_all("SELECT * FROM #{view_name} ORDER BY published DESC LIMIT $1 OFFSET $2", limit, offset, as: ChannelVideo)
|
||||
videos = PG_DB.query_all("SELECT cv.* " \
|
||||
"FROM channel_videos cv " \
|
||||
"JOIN users ON cv.ucid = any(users.subscriptions) " \
|
||||
"WHERE users.email = $1 AND published > now() - interval '1 month' " \
|
||||
"ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -480,7 +480,7 @@ module YoutubeAPI
|
||||
#
|
||||
# ```
|
||||
# # Valid channel "brand URL" gives the related UCID and browse ID
|
||||
# channel_a = YoutubeAPI.resolve_url("https://www.youtube.com/c/google")
|
||||
# channel_a = YoutubeAPI.resolve_url("https://youtube.com/c/google")
|
||||
# channel_a # => {
|
||||
# "endpoint": {
|
||||
# "browseEndpoint": {
|
||||
@@ -492,7 +492,7 @@ module YoutubeAPI
|
||||
# }
|
||||
#
|
||||
# # Invalid URL returns throws an InfoException
|
||||
# channel_b = YoutubeAPI.resolve_url("https://www.youtube.com/c/invalid")
|
||||
# channel_b = YoutubeAPI.resolve_url("https://youtube.com/c/invalid")
|
||||
# ```
|
||||
#
|
||||
def resolve_url(url : String, client_config : ClientConfig | Nil = nil)
|
||||
|
||||
Reference in New Issue
Block a user