mirror of
https://github.com/iv-org/invidious.git
synced 2025-08-21 10:49:02 -05:00
Merge 0b2ec108d17e7a5a1ca47cb297b62b793a19bbed into 1ae0f45b0e5dca696986925a06ef4f4b4f43894b
This commit is contained in:
commit
04dc797790
@ -3,6 +3,7 @@ FROM crystallang/crystal:1.16.3-alpine AS builder
|
||||
RUN apk add --no-cache sqlite-static yaml-static
|
||||
|
||||
ARG release
|
||||
ARG api_only
|
||||
|
||||
WORKDIR /invidious
|
||||
COPY ./shard.yml ./shard.yml
|
||||
@ -21,16 +22,13 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||
|
||||
RUN crystal spec --warnings all \
|
||||
--link-flags "-lxml2 -llzma"
|
||||
RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/crystal \
|
||||
crystal build ./src/invidious.cr \
|
||||
--release \
|
||||
${release:+--release} \
|
||||
--static --warnings all \
|
||||
--link-flags "-lxml2 -llzma"; \
|
||||
else \
|
||||
crystal build ./src/invidious.cr \
|
||||
--static --warnings all \
|
||||
--link-flags "-lxml2 -llzma"; \
|
||||
fi
|
||||
--link-flags "-lxml2 -llzma" \
|
||||
${api_only:+-Dapi_only -Dskip_videojs_download}
|
||||
|
||||
FROM alpine:3.21
|
||||
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
|
||||
|
@ -3,6 +3,7 @@ RUN apk add --no-cache 'crystal=1.14.0-r0' shards sqlite-static yaml-static yaml
|
||||
zlib-static openssl-libs-static openssl-dev musl-dev xz-static
|
||||
|
||||
ARG release
|
||||
ARG api_only
|
||||
|
||||
WORKDIR /invidious
|
||||
COPY ./shard.yml ./shard.yml
|
||||
@ -22,16 +23,12 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||
RUN crystal spec --warnings all \
|
||||
--link-flags "-lxml2 -llzma"
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
|
||||
RUN --mount=type=cache,target=/root/.cache/crystal \
|
||||
crystal build ./src/invidious.cr \
|
||||
--release \
|
||||
${release:+--release} \
|
||||
--static --warnings all \
|
||||
--link-flags "-lxml2 -llzma"; \
|
||||
else \
|
||||
crystal build ./src/invidious.cr \
|
||||
--static --warnings all \
|
||||
--link-flags "-lxml2 -llzma"; \
|
||||
fi
|
||||
--link-flags "-lxml2 -llzma" \
|
||||
${api_only:+-Dapi_only -Dskip_videojs_download}
|
||||
|
||||
FROM alpine:3.21
|
||||
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
|
||||
|
@ -31,24 +31,33 @@ require "yaml"
|
||||
require "compress/zip"
|
||||
require "protodec/utils"
|
||||
|
||||
# Database requires
|
||||
{% unless flag?(:api_only) %}
|
||||
require "./invidious/database/*"
|
||||
require "./invidious/database/migrations/*"
|
||||
{% else %}
|
||||
require "./invidious/database/api_only_stubs"
|
||||
{% end %}
|
||||
|
||||
# Core requires
|
||||
require "./invidious/http_server/*"
|
||||
require "./invidious/helpers/*"
|
||||
require "./invidious/yt_backend/*"
|
||||
require "./invidious/frontend/*"
|
||||
require "./invidious/videos/*"
|
||||
|
||||
require "./invidious/jsonify/**"
|
||||
|
||||
require "./invidious/*"
|
||||
require "./invidious/requires"
|
||||
require "./invidious/comments/*"
|
||||
require "./invidious/channels/*"
|
||||
require "./invidious/user/*"
|
||||
require "./invidious/search/*"
|
||||
require "./invidious/routes/**"
|
||||
|
||||
# Jobs (not needed in API-only mode)
|
||||
{% unless flag?(:api_only) %}
|
||||
require "./invidious/jobs/base_job"
|
||||
require "./invidious/jobs/*"
|
||||
{% end %}
|
||||
|
||||
# Declare the base namespace for invidious
|
||||
module Invidious
|
||||
@ -60,7 +69,13 @@ alias IV = Invidious
|
||||
CONFIG = Config.load
|
||||
HMAC_KEY = CONFIG.hmac_key
|
||||
|
||||
# Database connection
|
||||
{% unless flag?(:api_only) %}
|
||||
PG_DB = DB.open CONFIG.database_url
|
||||
{% else %}
|
||||
require "./invidious/api_only_types"
|
||||
PG_DB = DummyDB.new
|
||||
{% end %}
|
||||
ARCHIVE_URL = URI.parse("https://archive.org")
|
||||
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
|
||||
REDDIT_URL = URI.parse("https://www.reddit.com")
|
||||
@ -133,7 +148,11 @@ Kemal.config.extra_options do |parser|
|
||||
exit
|
||||
end
|
||||
parser.on("--migrate", "Run any migrations (beta, use at your own risk!!") do
|
||||
{% unless flag?(:api_only) %}
|
||||
Invidious::Database::Migrator.new(PG_DB).migrate
|
||||
{% else %}
|
||||
puts "Database migrations are not available in API-only mode"
|
||||
{% end %}
|
||||
exit
|
||||
end
|
||||
end
|
||||
@ -147,9 +166,11 @@ OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mo
|
||||
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level, CONFIG.colorize_logs)
|
||||
|
||||
# Check table integrity
|
||||
{% unless flag?(:api_only) %}
|
||||
Invidious::Database.check_integrity(CONFIG)
|
||||
{% end %}
|
||||
|
||||
{% if !flag?(:skip_videojs_download) %}
|
||||
{% if !flag?(:skip_videojs_download) && !flag?(:api_only) %}
|
||||
# Resolve player dependencies. This is done at compile time.
|
||||
#
|
||||
# Running the script by itself would show some colorful feedback while this doesn't.
|
||||
@ -175,6 +196,7 @@ DECRYPT_FUNCTION =
|
||||
|
||||
# Start jobs
|
||||
|
||||
{% unless flag?(:api_only) %}
|
||||
if CONFIG.channel_threads > 0
|
||||
Invidious::Jobs.register Invidious::Jobs::RefreshChannelsJob.new(PG_DB)
|
||||
end
|
||||
@ -204,9 +226,18 @@ Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
|
||||
Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new
|
||||
|
||||
Invidious::Jobs.start_all
|
||||
{% else %}
|
||||
# Define channels for API-only mode (even though they won't be used)
|
||||
NOTIFICATION_CHANNEL = ::Channel(VideoNotification).new(1)
|
||||
CONNECTION_CHANNEL = ::Channel({Bool, ::Channel(PQ::Notification)}).new(1)
|
||||
{% end %}
|
||||
|
||||
def popular_videos
|
||||
{% if flag?(:api_only) %}
|
||||
[] of ChannelVideo
|
||||
{% else %}
|
||||
Invidious::Jobs::PullPopularVideosJob::POPULAR_VIDEOS.get
|
||||
{% end %}
|
||||
end
|
||||
|
||||
# Routing
|
||||
|
50
src/invidious/api_only_types.cr
Normal file
50
src/invidious/api_only_types.cr
Normal file
@ -0,0 +1,50 @@
|
||||
# API-only mode type definitions
|
||||
# This file provides dummy type definitions when running in API-only mode
|
||||
|
||||
# Dummy DB class for API-only mode
|
||||
class DummyDB
|
||||
def query_all(*args, as : T.class) forall T
|
||||
[] of T
|
||||
end
|
||||
|
||||
def query_one(*args, as : T.class) forall T
|
||||
raise "Database not available in API-only mode"
|
||||
end
|
||||
|
||||
def query_one?(*args, as : T.class) forall T
|
||||
nil
|
||||
end
|
||||
|
||||
def scalar(*args)
|
||||
0
|
||||
end
|
||||
|
||||
def exec(*args)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# VideoNotification struct for API-only mode
|
||||
struct VideoNotification
|
||||
property video_id : String
|
||||
property channel_id : String
|
||||
property published : Time
|
||||
|
||||
def initialize(@video_id = "", @channel_id = "", @published = Time.utc)
|
||||
end
|
||||
|
||||
def self.from_video(video : ChannelVideo) : VideoNotification
|
||||
VideoNotification.new(video.id, video.ucid, video.published)
|
||||
end
|
||||
end
|
||||
|
||||
# PQ module with Notification for API-only mode
|
||||
module PQ
|
||||
struct Notification
|
||||
property channel : String = ""
|
||||
property payload : String = ""
|
||||
|
||||
def initialize(@channel = "", @payload = "")
|
||||
end
|
||||
end
|
||||
end
|
@ -107,7 +107,9 @@ class Config
|
||||
property full_refresh : Bool = false
|
||||
|
||||
# Jobs config structure. See jobs.cr and jobs/base_job.cr
|
||||
{% unless flag?(:api_only) %}
|
||||
property jobs = Invidious::Jobs::JobsConfig.new
|
||||
{% end %}
|
||||
|
||||
# Used to tell Invidious it is behind a proxy, so links to resources should be https://
|
||||
property https_only : Bool?
|
||||
@ -285,6 +287,7 @@ class Config
|
||||
end
|
||||
|
||||
# Build database_url from db.* if it's not set directly
|
||||
{% unless flag?(:api_only) %}
|
||||
if config.database_url.to_s.empty?
|
||||
if db = config.db
|
||||
config.database_url = URI.new(
|
||||
@ -300,6 +303,12 @@ class Config
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
{% else %}
|
||||
# In API-only mode, database is optional
|
||||
if config.database_url.to_s.empty?
|
||||
config.database_url = URI.parse("postgres://dummy:dummy@localhost/dummy")
|
||||
end
|
||||
{% end %}
|
||||
|
||||
# Check if the socket configuration is valid
|
||||
if sb = config.socket_binding
|
||||
|
240
src/invidious/database/api_only_stubs.cr
Normal file
240
src/invidious/database/api_only_stubs.cr
Normal file
@ -0,0 +1,240 @@
|
||||
# API-only mode database stubs
|
||||
# This file provides dummy implementations for database modules when running in API-only mode
|
||||
|
||||
module Invidious::Database
|
||||
module SessionIDs
|
||||
def self.select_email(sid : String) : String?
|
||||
nil
|
||||
end
|
||||
|
||||
def self.select_all(email : String)
|
||||
[] of {session: String, issued: Time}
|
||||
end
|
||||
|
||||
def self.delete(sid : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.insert(session : String, email : String)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module Users
|
||||
def self.select!(email : String)
|
||||
raise "Database not available in API-only mode"
|
||||
end
|
||||
|
||||
def self.update_preferences(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.mark_watched(user, id)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.mark_unwatched(user, id)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.clear_watch_history(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.subscribe_channel(user, ucid)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.unsubscribe_channel(user, ucid)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update_subscriptions(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update_watch_history(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update_password(user, password : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.insert(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.delete(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.select_notifications(user)
|
||||
[] of String
|
||||
end
|
||||
|
||||
def self.mark_notifications_as_read(user)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.feed_needs_update(user)
|
||||
false
|
||||
end
|
||||
|
||||
def self.update_feed_watched(user)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module Channels
|
||||
def self.select(subscriptions)
|
||||
[] of InvidiousChannel
|
||||
end
|
||||
|
||||
def self.select(id : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.insert(channel, update_on_conflict : Bool = false)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module ChannelVideos
|
||||
def self.select_notfications(*args)
|
||||
[] of ChannelVideo
|
||||
end
|
||||
|
||||
def self.select_latest_videos(*args)
|
||||
[] of ChannelVideo
|
||||
end
|
||||
|
||||
def self.insert(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.select(notifications)
|
||||
[] of ChannelVideo
|
||||
end
|
||||
end
|
||||
|
||||
module Playlists
|
||||
def self.select_all(author : String)
|
||||
[] of InvidiousPlaylist
|
||||
end
|
||||
|
||||
def self.count_owned_by(email : String)
|
||||
0
|
||||
end
|
||||
|
||||
def self.select(id : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.delete(id : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update_video_added(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update_video_removed(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.select_like_iv(email : String)
|
||||
[] of InvidiousPlaylist
|
||||
end
|
||||
|
||||
def self.insert(playlist)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update_description(id : String, description : String)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module PlaylistVideos
|
||||
def self.insert(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.delete(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.select_ids(*args, limit : Int32? = nil)
|
||||
[] of String
|
||||
end
|
||||
|
||||
def self.select(*args, limit : Int32? = nil)
|
||||
[] of PlaylistVideo
|
||||
end
|
||||
end
|
||||
|
||||
module Annotations
|
||||
def self.select(id : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.insert(id : String, annotations : String)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module Videos
|
||||
def self.select(id : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.insert(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.update(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.delete(id : String)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
module Nonces
|
||||
def self.select(nonce : String)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.insert(nonce : String, expire : Time? = nil)
|
||||
nil
|
||||
end
|
||||
|
||||
def self.delete(nonce : String)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Migrator stub
|
||||
class Migrator
|
||||
def initialize(db)
|
||||
end
|
||||
|
||||
def migrate
|
||||
puts "Database migrations are not available in API-only mode"
|
||||
end
|
||||
end
|
||||
|
||||
def self.check_integrity(config)
|
||||
# Skip integrity check in API-only mode
|
||||
end
|
||||
end
|
@ -186,6 +186,7 @@ end
|
||||
#
|
||||
# Creates a new tracker when unavailable.
|
||||
def get_playback_statistic
|
||||
{% unless flag?(:api_only) %}
|
||||
if (tracker = Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"]) && tracker.as(Hash).empty?
|
||||
tracker = {
|
||||
"totalRequests" => 0_i64,
|
||||
@ -197,4 +198,12 @@ def get_playback_statistic
|
||||
end
|
||||
|
||||
return tracker.as(Hash(String, Int64 | Float64))
|
||||
{% else %}
|
||||
# Return empty statistics in API-only mode
|
||||
return {
|
||||
"totalRequests" => 0_i64,
|
||||
"successfulRequests" => 0_i64,
|
||||
"ratio" => 0_f64,
|
||||
}
|
||||
{% end %}
|
||||
end
|
||||
|
@ -1,3 +1,4 @@
|
||||
{% unless flag?(:api_only) %}
|
||||
module Invidious::Jobs
|
||||
JOBS = [] of BaseJob
|
||||
|
||||
@ -38,3 +39,4 @@ module Invidious::Jobs
|
||||
end
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
|
18
src/invidious/requires.cr
Normal file
18
src/invidious/requires.cr
Normal file
@ -0,0 +1,18 @@
|
||||
# Common requires for Invidious
|
||||
# This file contains the require statements organized by category
|
||||
|
||||
# Core requires
|
||||
require "./config"
|
||||
require "./exceptions"
|
||||
require "./hashtag"
|
||||
require "./mixes"
|
||||
require "./playlists"
|
||||
require "./routing"
|
||||
require "./trending"
|
||||
require "./users"
|
||||
require "./videos"
|
||||
|
||||
# Jobs (only if not in API-only mode)
|
||||
{% unless flag?(:api_only) %}
|
||||
require "./jobs"
|
||||
{% end %}
|
@ -6,6 +6,7 @@ module Invidious::Routes::API::V1::Misc
|
||||
if !CONFIG.statistics_enabled
|
||||
return {"software" => SOFTWARE}.to_json
|
||||
else
|
||||
{% unless flag?(:api_only) %}
|
||||
# Calculate playback success rate
|
||||
if (tracker = Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"]?)
|
||||
tracker = tracker.as(Hash(String, Int64 | Float64))
|
||||
@ -23,6 +24,10 @@ module Invidious::Routes::API::V1::Misc
|
||||
end
|
||||
|
||||
return Invidious::Jobs::StatisticsRefreshJob::STATISTICS.to_json
|
||||
{% else %}
|
||||
# Return empty statistics in API-only mode
|
||||
return {"software" => SOFTWARE}.to_json
|
||||
{% end %}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -72,6 +72,7 @@ module Invidious::Routes::BeforeAll
|
||||
raise "Cannot use token as SID"
|
||||
end
|
||||
|
||||
{% unless flag?(:api_only) %}
|
||||
if email = Database::SessionIDs.select_email(sid)
|
||||
user = Database::Users.select!(email: email)
|
||||
csrf_token = generate_response(sid, {
|
||||
@ -90,6 +91,7 @@ module Invidious::Routes::BeforeAll
|
||||
env.set "csrf_token", csrf_token
|
||||
env.set "user", user
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
|
||||
dark_mode = convert_theme(env.params.query["dark_mode"]?) || preferences.dark_mode.to_s
|
||||
|
Loading…
x
Reference in New Issue
Block a user