diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/invidious.cr | 536 | ||||
| -rw-r--r-- | src/invidious/channels/channels.cr | 24 | ||||
| -rw-r--r-- | src/invidious/database/base.cr | 64 | ||||
| -rw-r--r-- | src/invidious/database/channels.cr | 31 | ||||
| -rw-r--r-- | src/invidious/database/playlists.cr | 26 | ||||
| -rw-r--r-- | src/invidious/database/sessions.cr | 4 | ||||
| -rw-r--r-- | src/invidious/database/users.cr | 6 | ||||
| -rw-r--r-- | src/invidious/jobs/refresh_channels_job.cr | 2 | ||||
| -rw-r--r-- | src/invidious/routes/api/v1/authenticated.cr | 2 | ||||
| -rw-r--r-- | src/invidious/routes/feeds.cr | 2 | ||||
| -rw-r--r-- | src/invidious/routes/notifications.cr | 78 | ||||
| -rw-r--r-- | src/invidious/routes/playlists.cr | 16 | ||||
| -rw-r--r-- | src/invidious/routes/preferences.cr | 187 | ||||
| -rw-r--r-- | src/invidious/routes/subscriptions.cr | 168 | ||||
| -rw-r--r-- | src/invidious/routes/watch.cr | 66 | ||||
| -rw-r--r-- | src/invidious/users.cr | 2 | ||||
| -rw-r--r-- | src/invidious/views/components/player_sources.ecr | 30 | ||||
| -rw-r--r-- | src/invidious/views/embed.ecr | 4 | ||||
| -rw-r--r-- | src/invidious/views/licenses.ecr | 22 |
19 files changed, 648 insertions, 622 deletions
diff --git a/src/invidious.cr b/src/invidious.cr index 7a324bd1..b09f31c2 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -112,22 +112,19 @@ OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mo LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level) # Check table integrity -if CONFIG.check_tables - Invidious::Database.check_enum(PG_DB, "privacy", PlaylistPrivacy) - - Invidious::Database.check_table(PG_DB, "channels", InvidiousChannel) - Invidious::Database.check_table(PG_DB, "channel_videos", ChannelVideo) - Invidious::Database.check_table(PG_DB, "playlists", InvidiousPlaylist) - Invidious::Database.check_table(PG_DB, "playlist_videos", PlaylistVideo) - Invidious::Database.check_table(PG_DB, "nonces", Nonce) - Invidious::Database.check_table(PG_DB, "session_ids", SessionId) - Invidious::Database.check_table(PG_DB, "users", User) - Invidious::Database.check_table(PG_DB, "videos", Video) - - if CONFIG.cache_annotations - Invidious::Database.check_table(PG_DB, "annotations", Annotation) - end -end +Invidious::Database.check_integrity(CONFIG) + +# Resolve player dependencies. This is done at compile time. +# +# Running the script by itself would show some colorful feedback while this doesn't. +# Perhaps we should just move the script to runtime in order to get that feedback? + +{% puts "\nChecking player dependencies...\n" %} +{% if flag?(:minified_player_dependencies) %} + {% puts run("../scripts/fetch-player-dependencies.cr", "--minified").stringify %} +{% else %} + {% puts run("../scripts/fetch-player-dependencies.cr").stringify %} +{% end %} # Start jobs @@ -339,6 +336,7 @@ end end Invidious::Routing.get "/watch", Invidious::Routes::Watch, :handle + Invidious::Routing.post "/watch_ajax", Invidious::Routes::Watch, :mark_watched Invidious::Routing.get "/watch/:id", Invidious::Routes::Watch, :redirect Invidious::Routing.get "/shorts/:id", Invidious::Routes::Watch, :redirect Invidious::Routing.get "/w/:id", Invidious::Routes::Watch, :redirect @@ -372,6 +370,8 @@ end Invidious::Routing.get "/preferences", Invidious::Routes::PreferencesRoute, :show Invidious::Routing.post "/preferences", Invidious::Routes::PreferencesRoute, :update Invidious::Routing.get "/toggle_theme", Invidious::Routes::PreferencesRoute, :toggle_theme + Invidious::Routing.get "/data_control", Invidious::Routes::PreferencesRoute, :data_control + Invidious::Routing.post "/data_control", Invidious::Routes::PreferencesRoute, :update_data_control # Feeds Invidious::Routing.get "/view_all_playlists", Invidious::Routes::Feeds, :view_all_playlists_redirect @@ -390,6 +390,11 @@ end # Support push notifications via PubSubHubbub Invidious::Routing.get "/feed/webhook/:token", Invidious::Routes::Feeds, :push_notifications_get Invidious::Routing.post "/feed/webhook/:token", Invidious::Routes::Feeds, :push_notifications_post + + Invidious::Routing.get "/modify_notifications", Invidious::Routes::Notifications, :modify + + Invidious::Routing.post "/subscription_ajax", Invidious::Routes::Subscriptions, :toggle_subscription + Invidious::Routing.get "/subscription_manager", Invidious::Routes::Subscriptions, :subscription_manager {% end %} Invidious::Routing.get "/ggpht/*", Invidious::Routes::Images, :ggpht @@ -406,505 +411,6 @@ define_v1_api_routes() define_api_manifest_routes() define_video_playback_routes() -# Users - -post "/watch_ajax" do |env| - locale = env.get("preferences").as(Preferences).locale - - user = env.get? "user" - sid = env.get? "sid" - referer = get_referer(env, "/feed/subscriptions") - - redirect = env.params.query["redirect"]? - redirect ||= "true" - redirect = redirect == "true" - - if !user - if redirect - next env.redirect referer - else - next error_json(403, "No such user") - end - end - - user = user.as(User) - sid = sid.as(String) - token = env.params.body["csrf_token"]? - - id = env.params.query["id"]? - if !id - env.response.status_code = 400 - next - end - - begin - validate_request(token, sid, env.request, HMAC_KEY, locale) - rescue ex - if redirect - next error_template(400, ex) - else - next error_json(400, ex) - end - end - - if env.params.query["action_mark_watched"]? - action = "action_mark_watched" - elsif env.params.query["action_mark_unwatched"]? - action = "action_mark_unwatched" - else - next env.redirect referer - end - - case action - when "action_mark_watched" - if !user.watched.includes? id - Invidious::Database::Users.mark_watched(user, id) - end - when "action_mark_unwatched" - Invidious::Database::Users.mark_unwatched(user, id) - else - next error_json(400, "Unsupported action #{action}") - end - - if redirect - env.redirect referer - else - env.response.content_type = "application/json" - "{}" - end -end - -# /modify_notifications -# will "ding" all subscriptions. -# /modify_notifications?receive_all_updates=false&receive_no_updates=false -# will "unding" all subscriptions. -get "/modify_notifications" do |env| - locale = env.get("preferences").as(Preferences).locale - - user = env.get? "user" - sid = env.get? "sid" - referer = get_referer(env, "/") - - redirect = env.params.query["redirect"]? - redirect ||= "false" - redirect = redirect == "true" - - if !user - if redirect - next env.redirect referer - else - next error_json(403, "No such user") - end - end - - user = user.as(User) - - if !user.password - channel_req = {} of String => String - - channel_req["receive_all_updates"] = env.params.query["receive_all_updates"]? || "true" - channel_req["receive_no_updates"] = env.params.query["receive_no_updates"]? || "" - channel_req["receive_post_updates"] = env.params.query["receive_post_updates"]? || "true" - - channel_req.reject! { |k, v| v != "true" && v != "false" } - - headers = HTTP::Headers.new - headers["Cookie"] = env.request.headers["Cookie"] - - html = YT_POOL.client &.get("/subscription_manager?disable_polymer=1", headers) - - cookies = HTTP::Cookies.from_client_headers(headers) - html.cookies.each do |cookie| - if {"VISITOR_INFO1_LIVE", "YSC", "SIDCC"}.includes? cookie.name - if cookies[cookie.name]? - cookies[cookie.name] = cookie - else - cookies << cookie - end - end - end - headers = cookies.add_request_headers(headers) - - if match = html.body.match(/'XSRF_TOKEN': "(?<session_token>[^"]+)"/) - session_token = match["session_token"] - else - next env.redirect referer - end - - headers["content-type"] = "application/x-www-form-urlencoded" - channel_req["session_token"] = session_token - - subs = XML.parse_html(html.body) - subs.xpath_nodes(%q(//a[@class="subscription-title yt-uix-sessionlink"]/@href)).each do |channel| - channel_id = channel.content.lstrip("/channel/").not_nil! - channel_req["channel_id"] = channel_id - - YT_POOL.client &.post("/subscription_ajax?action_update_subscription_preferences=1", headers, form: channel_req) - end - end - - if redirect - env.redirect referer - else - env.response.content_type = "application/json" - "{}" - end -end - -post "/subscription_ajax" do |env| - locale = env.get("preferences").as(Preferences).locale - - user = env.get? "user" - sid = env.get? "sid" - referer = get_referer(env, "/") - - redirect = env.params.query["redirect"]? - redirect ||= "true" - redirect = redirect == "true" - - if !user - if redirect - next env.redirect referer - else - next error_json(403, "No such user") - end - end - - user = user.as(User) - sid = sid.as(String) - token = env.params.body["csrf_token"]? - - begin - validate_request(token, sid, env.request, HMAC_KEY, locale) - rescue ex - if redirect - next error_template(400, ex) - else - next error_json(400, ex) - end - end - - if env.params.query["action_create_subscription_to_channel"]?.try &.to_i?.try &.== 1 - action = "action_create_subscription_to_channel" - elsif env.params.query["action_remove_subscriptions"]?.try &.to_i?.try &.== 1 - action = "action_remove_subscriptions" - else - next env.redirect referer - end - - channel_id = env.params.query["c"]? - channel_id ||= "" - - if !user.password - # Sync subscriptions with YouTube - subscribe_ajax(channel_id, action, env.request.headers) - end - - case action - when "action_create_subscription_to_channel" - if !user.subscriptions.includes? channel_id - get_channel(channel_id, false, false) - Invidious::Database::Users.subscribe_channel(user, channel_id) - end - when "action_remove_subscriptions" - Invidious::Database::Users.unsubscribe_channel(user, channel_id) - else - next error_json(400, "Unsupported action #{action}") - end - - if redirect - env.redirect referer - else - env.response.content_type = "application/json" - "{}" - end -end - -get "/subscription_manager" do |env| - locale = env.get("preferences").as(Preferences).locale - - user = env.get? "user" - sid = env.get? "sid" - referer = get_referer(env) - - if !user - next env.redirect referer - end - - user = user.as(User) - sid = sid.as(String) - - if !user.password - # Refresh account - headers = HTTP::Headers.new - headers["Cookie"] = env.request.headers["Cookie"] - - user, sid = get_user(sid, headers) - end - - action_takeout = env.params.query["action_takeout"]?.try &.to_i? - action_takeout ||= 0 - action_takeout = action_takeout == 1 - - format = env.params.query["format"]? - format ||= "rss" - - subscriptions = Invidious::Database::Channels.select(user.subscriptions) - subscriptions.sort_by!(&.author.downcase) - - if action_takeout - if format == "json" - env.response.content_type = "application/json" - env.response.headers["content-disposition"] = "attachment" - playlists = Invidious::Database::Playlists.select_like_iv(user.email) - - next JSON.build do |json| - json.object do - json.field "subscriptions", user.subscriptions - json.field "watch_history", user.watched - json.field "preferences", user.preferences - json.field "playlists" do - json.array do - playlists.each do |playlist| - json.object do - json.field "title", playlist.title - json.field "description", html_to_content(playlist.description_html) - json.field "privacy", playlist.privacy.to_s - json.field "videos" do - json.array do - Invidious::Database::PlaylistVideos.select_ids(playlist.id, playlist.index, limit: 500).each do |video_id| - json.string video_id - end - end - end - end - end - end - end - end - end - else - env.response.content_type = "application/xml" - env.response.headers["content-disposition"] = "attachment" - export = XML.build do |xml| - xml.element("opml", version: "1.1") do - xml.element("body") do - if format == "newpipe" - title = "YouTube Subscriptions" - else - title = "Invidious Subscriptions" - end - - xml.element("outline", text: title, title: title) do - subscriptions.each do |channel| - if format == "newpipe" - xml_url = "https://www.youtube.com/feeds/videos.xml?channel_id=#{channel.id}" - else - xml_url = "#{HOST_URL}/feed/channel/#{channel.id}" - end - - xml.element("outline", text: channel.author, title: channel.author, - "type": "rss", xmlUrl: xml_url) - end - end - end - end - end - - next export.gsub(%(<?xml version="1.0"?>\n), "") - end - end - - templated "subscription_manager" -end - -get "/data_control" do |env| - locale = env.get("preferences").as(Preferences).locale - - user = env.get? "user" - referer = get_referer(env) - - if !user - next env.redirect referer - end - - user = user.as(User) - - templated "data_control" -end - -post "/data_control" do |env| - locale = env.get("preferences").as(Preferences).locale - - user = env.get? "user" - referer = get_referer(env) - - if user - user = user.as(User) - - # TODO: Find a way to prevent browser timeout - - HTTP::FormData.parse(env.request) do |part| - body = part.body.gets_to_end - type = part.headers["Content-Type"] - - next if body.empty? - - # TODO: Unify into single import based on content-type - case part.name - when "import_invidious" - body = JSON.parse(body) - - if body["subscriptions"]? - user.subscriptions += body["subscriptions"].as_a.map(&.as_s) - user.subscriptions.uniq! - - user.subscriptions = get_batch_channels(user.subscriptions, false, false) - - Invidious::Database::Users.update_subscriptions(user) - end - - if body["watch_history"]? - user.watched += body["watch_history"].as_a.map(&.as_s) - user.watched.uniq! - Invidious::Database::Users.update_watch_history(user) - end - - if body["preferences"]? - user.preferences = Preferences.from_json(body["preferences"].to_json) - Invidious::Database::Users.update_preferences(user) - end - - if playlists = body["playlists"]?.try &.as_a? - playlists.each do |item| - title = item["title"]?.try &.as_s?.try &.delete("<>") - description = item["description"]?.try &.as_s?.try &.delete("\r") - privacy = item["privacy"]?.try &.as_s?.try { |privacy| PlaylistPrivacy.parse? privacy } - - next if !title - next if !description - next if !privacy - - playlist = create_playlist(title, privacy, user) - Invidious::Database::Playlists.update_description(playlist.id, description) - - videos = item["videos"]?.try &.as_a?.try &.each_with_index do |video_id, idx| - raise InfoException.new("Playlist cannot have more than 500 videos") if idx > 500 - - video_id = video_id.try &.as_s? - next if !video_id - - begin - video = get_video(video_id) - rescue ex - next - end - - playlist_video = PlaylistVideo.new({ - title: video.title, - id: video.id, - author: video.author, - ucid: video.ucid, - length_seconds: video.length_seconds, - published: video.published, - plid: playlist.id, - live_now: video.live_now, - index: Random::Secure.rand(0_i64..Int64::MAX), - }) - - Invidious::Database::PlaylistVideos.insert(playlist_video) - Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index) - end - end - end - when "import_youtube" - filename = part.filename || "" - extension = filename.split(".").last - - if extension == "xml" || type == "application/xml" || type == "text/xml" - subscriptions = XML.parse(body) - user.subscriptions += subscriptions.xpath_nodes(%q(//outline[@type="rss"])).map do |channel| - channel["xmlUrl"].match(/UC[a-zA-Z0-9_-]{22}/).not_nil![0] - end - elsif extension == "json" || type == "application/json" - subscriptions = JSON.parse(body) - user.subscriptions += subscriptions.as_a.compact_map do |entry| - entry["snippet"]["resourceId"]["channelId"].as_s - end - elsif extension == "csv" || type == "text/csv" - subscriptions = parse_subscription_export_csv(body) - user.subscriptions += subscriptions - else - halt(env, status_code: 415, - response: error_template(415, "Invalid subscription file uploaded") - ) - end - - user.subscriptions.uniq! - user.subscriptions = get_batch_channels(user.subscriptions, false, false) - - Invidious::Database::Users.update_subscriptions(user) - when "import_freetube" - user.subscriptions += body.scan(/"channelId":"(?<channel_id>[a-zA-Z0-9_-]{24})"/).map do |md| - md["channel_id"] - end - user.subscriptions.uniq! - - user.subscriptions = get_batch_channels(user.subscriptions, false, false) - - Invidious::Database::Users.update_subscriptions(user) - when "import_newpipe_subscriptions" - body = JSON.parse(body) - user.subscriptions += body["subscriptions"].as_a.compact_map do |channel| - if match = channel["url"].as_s.match(/\/channel\/(?<channel>UC[a-zA-Z0-9_-]{22})/) - next match["channel"] - elsif match = channel["url"].as_s.match(/\/user\/(?<user>.+)/) - response = YT_POOL.client &.get("/user/#{match["user"]}?disable_polymer=1&hl=en&gl=US") - html = XML.parse_html(response.body) - ucid = html.xpath_node(%q(//link[@rel="canonical"])).try &.["href"].split("/")[-1] - next ucid if ucid - end - - nil - end - user.subscriptions.uniq! - - user.subscriptions = get_batch_channels(user.subscriptions, false, false) - - Invidious::Database::Users.update_subscriptions(user) - when "import_newpipe" - Compress::Zip::Reader.open(IO::Memory.new(body)) do |file| - file.each_entry do |entry| - if entry.filename == "newpipe.db" - tempfile = File.tempfile(".db") - File.write(tempfile.path, entry.io.gets_to_end) - db = DB.open("sqlite3://" + tempfile.path) - - user.watched += db.query_all("SELECT url FROM streams", as: String).map(&.lchop("https://www.youtube.com/watch?v=")) - user.watched.uniq! - - Invidious::Database::Users.update_watch_history(user) - - user.subscriptions += db.query_all("SELECT url FROM subscriptions", as: String).map(&.lchop("https://www.youtube.com/channel/")) - user.subscriptions.uniq! - - user.subscriptions = get_batch_channels(user.subscriptions, false, false) - - Invidious::Database::Users.update_subscriptions(user) - - db.close - tempfile.delete - end - end - end - else nil # Ignore - end - end - end - - env.redirect referer -end - get "/change_password" do |env| locale = env.get("preferences").as(Preferences).locale diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr index 46e34dd6..e0459cc3 100644 --- a/src/invidious/channels/channels.cr +++ b/src/invidious/channels/channels.cr @@ -106,8 +106,9 @@ class ChannelRedirect < Exception end end -def get_batch_channels(channels, refresh = false, pull_all_videos = true, max_threads = 10) +def get_batch_channels(channels) finished_channel = Channel(String | Nil).new + max_threads = 10 spawn do active_threads = 0 @@ -122,7 +123,7 @@ def get_batch_channels(channels, refresh = false, pull_all_videos = true, max_th active_threads += 1 spawn do begin - get_channel(ucid, refresh, pull_all_videos) + get_channel(ucid) finished_channel.send(ucid) rescue ex finished_channel.send(nil) @@ -143,23 +144,20 @@ def get_batch_channels(channels, refresh = false, pull_all_videos = true, max_th return final end -def get_channel(id, refresh = true, pull_all_videos = true) - if channel = Invidious::Database::Channels.select(id) - if refresh && Time.utc - channel.updated > 10.minutes - channel = fetch_channel(id, pull_all_videos: pull_all_videos) - Invidious::Database::Channels.insert(channel, update_on_conflict: true) - end - else - channel = fetch_channel(id, pull_all_videos: pull_all_videos) - Invidious::Database::Channels.insert(channel) +def get_channel(id) : InvidiousChannel + channel = Invidious::Database::Channels.select(id) + + if channel.nil? || (Time.utc - channel.updated) > 2.days + channel = fetch_channel(id, pull_all_videos: false) + Invidious::Database::Channels.insert(channel, update_on_conflict: true) end return channel end -def fetch_channel(ucid, pull_all_videos = true, locale = nil) +def fetch_channel(ucid, pull_all_videos : Bool) LOGGER.debug("fetch_channel: #{ucid}") - LOGGER.trace("fetch_channel: #{ucid} : pull_all_videos = #{pull_all_videos}, locale = #{locale}") + LOGGER.trace("fetch_channel: #{ucid} : pull_all_videos = #{pull_all_videos}") LOGGER.trace("fetch_channel: #{ucid} : Downloading RSS feed") rss = YT_POOL.client &.get("/feeds/videos.xml?channel_id=#{ucid}").body diff --git a/src/invidious/database/base.cr b/src/invidious/database/base.cr index 6e49ea1a..0fb1b6af 100644 --- a/src/invidious/database/base.cr +++ b/src/invidious/database/base.cr @@ -3,26 +3,52 @@ require "pg" module Invidious::Database extend self - def check_enum(db, enum_name, struct_type = nil) + # Checks table integrity + # + # Note: config is passed as a parameter to avoid complex + # dependencies between different parts of the software. + def check_integrity(cfg) + return if !cfg.check_tables + Invidious::Database.check_enum("privacy", PlaylistPrivacy) + + Invidious::Database.check_table("channels", InvidiousChannel) + Invidious::Database.check_table("channel_videos", ChannelVideo) + Invidious::Database.check_table("playlists", InvidiousPlaylist) + Invidious::Database.check_table("playlist_videos", PlaylistVideo) + Invidious::Database.check_table("nonces", Nonce) + Invidious::Database.check_table("session_ids", SessionId) + Invidious::Database.check_table("users", User) + Invidious::Database.check_table("videos", Video) + + if cfg.cache_annotations + Invidious::Database.check_table("annotations", Annotation) + end + end + + # + # Table/enum integrity checks + # + + def check_enum(enum_name, struct_type = nil) return # TODO - if !db.query_one?("SELECT true FROM pg_type WHERE typname = $1", enum_name, as: Bool) + if !PG_DB.query_one?("SELECT true FROM pg_type WHERE typname = $1", enum_name, as: Bool) LOGGER.info("check_enum: CREATE TYPE #{enum_name}") - db.using_connection do |conn| + PG_DB.using_connection do |conn| conn.as(PG::Connection).exec_all(File.read("config/sql/#{enum_name}.sql")) end end end - def check_table(db, table_name, struct_type = nil) + def check_table(table_name, struct_type = nil) # Create table if it doesn't exist begin - db.exec("SELECT * FROM #{table_name} LIMIT 0") + PG_DB.exec("SELECT * FROM #{table_name} LIMIT 0") rescue ex LOGGER.info("check_table: check_table: CREATE TABLE #{table_name}") - db.using_connection do |conn| + PG_DB.using_connection do |conn| conn.as(PG::Connection).exec_all(File.read("config/sql/#{table_name}.sql")) end end @@ -30,7 +56,7 @@ module Invidious::Database return if !struct_type struct_array = struct_type.type_array - column_array = get_column_array(db, table_name) + column_array = get_column_array(PG_DB, table_name) column_types = File.read("config/sql/#{table_name}.sql").match(/CREATE TABLE public\.#{table_name}\n\((?<types>[\d\D]*?)\);/) .try &.["types"].split(",").map(&.strip).reject &.starts_with?("CONSTRAINT") @@ -41,14 +67,14 @@ module Invidious::Database if !column_array[i]? new_column = column_types.select(&.starts_with?(name))[0] LOGGER.info("check_table: ALTER TABLE #{table_name} ADD COLUMN #{new_column}") - db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") + PG_DB.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") next end # Column doesn't exist if !column_array.includes? name new_column = column_types.select(&.starts_with?(name))[0] - db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") + PG_DB.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") end # Column exists but in the wrong position, rotate @@ -59,29 +85,29 @@ module Invidious::Database # There's a column we didn't expect if !new_column LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]}") - db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") + PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") - column_array = get_column_array(db, table_name) + column_array = get_column_array(PG_DB, table_name) next end LOGGER.info("check_table: ALTER TABLE #{table_name} ADD COLUMN #{new_column}") - db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") + PG_DB.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") LOGGER.info("check_table: UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}") - db.exec("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}") + PG_DB.exec("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}") LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") - db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") + PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") LOGGER.info("check_table: ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}") - db.exec("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}") + PG_DB.exec("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}") - column_array = get_column_array(db, table_name) + column_array = get_column_array(PG_DB, table_name) end else LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") - db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") + PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") end end end @@ -91,14 +117,14 @@ module Invidious::Database column_array.each do |column| if !struct_array.includes? column LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE") - db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE") + PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE") end end end def get_column_array(db, table_name) column_array = [] of String - db.query("SELECT * FROM #{table_name} LIMIT 0") do |rs| + PG_DB.query("SELECT * FROM #{table_name} LIMIT 0") do |rs| rs.column_count.times do |i| column = rs.as(PG::ResultSet).field(i) column_array << column.name diff --git a/src/invidious/database/channels.cr b/src/invidious/database/channels.cr index 134cf59d..df44e485 100644 --- a/src/invidious/database/channels.cr +++ b/src/invidious/database/channels.cr @@ -35,21 +35,31 @@ module Invidious::Database::Channels def update_author(id : String, author : String) request = <<-SQL UPDATE channels - SET updated = $1, author = $2, deleted = false - WHERE id = $3 + SET updated = now(), author = $1, deleted = false + WHERE id = $2 + SQL + + PG_DB.exec(request, author, id) + end + + def update_subscription_time(id : String) + request = <<-SQL + UPDATE channels + SET subscribed = now() + WHERE id = $1 SQL - PG_DB.exec(request, Time.utc, author, id) + PG_DB.exec(request, id) end def update_mark_deleted(id : String) request = <<-SQL UPDATE channels - SET updated = $1, deleted = true - WHERE id = $2 + SET updated = now(), deleted = true + WHERE id = $1 SQL - PG_DB.exec(request, Time.utc, id) + PG_DB.exec(request, id) end # ------------------- @@ -67,14 +77,13 @@ module Invidious::Database::Channels def select(ids : Array(String)) : Array(InvidiousChannel)? return [] of InvidiousChannel if ids.empty? - values = ids.map { |id| %(('#{id}')) }.join(",") request = <<-SQL SELECT * FROM channels - WHERE id = ANY(VALUES #{values}) + WHERE id = ANY($1) SQL - return PG_DB.query_all(request, as: InvidiousChannel) + return PG_DB.query_all(request, ids, as: InvidiousChannel) end end @@ -117,11 +126,11 @@ module Invidious::Database::ChannelVideos request = <<-SQL SELECT * FROM channel_videos - WHERE id IN (#{arg_array(ids)}) + WHERE id = ANY($1) ORDER BY published DESC SQL - return PG_DB.query_all(request, args: ids, as: ChannelVideo) + return PG_DB.query_all(request, ids, as: ChannelVideo) end def select_notfications(ucid : String, since : Time) : Array(ChannelVideo) diff --git a/src/invidious/database/playlists.cr b/src/invidious/database/playlists.cr index 7a5f61dc..c6754a1e 100644 --- a/src/invidious/database/playlists.cr +++ b/src/invidious/database/playlists.cr @@ -59,11 +59,11 @@ module Invidious::Database::Playlists def update_subscription_time(id : String) request = <<-SQL UPDATE playlists - SET subscribed = $1 - WHERE id = $2 + SET subscribed = now() + WHERE id = $1 SQL - PG_DB.exec(request, Time.utc, id) + PG_DB.exec(request, id) end def update_video_added(id : String, index : String | Int64) @@ -71,11 +71,11 @@ module Invidious::Database::Playlists UPDATE playlists SET index = array_append(index, $1), video_count = cardinality(index) + 1, - updated = $2 - WHERE id = $3 + updated = now() + WHERE id = $2 SQL - PG_DB.exec(request, index, Time.utc, id) + PG_DB.exec(request, index, id) end def update_video_removed(id : String, index : String | Int64) @@ -83,28 +83,24 @@ module Invidious::Database::Playlists UPDATE playlists SET index = array_remove(index, $1), video_count = cardinality(index) - 1, - updated = $2 - WHERE id = $3 + updated = now() + WHERE id = $2 SQL - PG_DB.exec(request, index, Time.utc, id) + PG_DB.exec(request, index, id) end # ------------------- # Salect # ------------------- - def select(*, id : String, raise_on_fail : Bool = false) : InvidiousPlaylist? + def select(*, id : String) : InvidiousPlaylist? request = <<-SQL SELECT * FROM playlists WHERE id = $1 SQL - if raise_on_fail - return PG_DB.query_one(request, id, as: InvidiousPlaylist) - else - return PG_DB.query_one?(request, id, as: InvidiousPlaylist) - end + return PG_DB.query_one?(request, id, as: InvidiousPlaylist) end def select_all(*, author : String) : Array(InvidiousPlaylist) diff --git a/src/invidious/database/sessions.cr b/src/invidious/database/sessions.cr index d5f85dd6..96587082 100644 --- a/src/invidious/database/sessions.cr +++ b/src/invidious/database/sessions.cr @@ -10,12 +10,12 @@ module Invidious::Database::SessionIDs def insert(sid : String, email : String, handle_conflicts : Bool = false) request = <<-SQL INSERT INTO session_ids - VALUES ($1, $2, $3) + VALUES ($1, $2, now()) SQL request += " ON CONFLICT (id) DO NOTHING" if handle_conflicts - PG_DB.exec(request, sid, email, Time.utc) + PG_DB.exec(request, sid, email) end # ------------------- diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr index 53724dbf..26be4270 100644 --- a/src/invidious/database/users.cr +++ b/src/invidious/database/users.cr @@ -143,11 +143,11 @@ module Invidious::Database::Users def clear_notifications(user : User) request = <<-SQL UPDATE users - SET notifications = '{}', updated = $1 - WHERE email = $2 + SET notifications = '{}', updated = now() + WHERE email = $1 SQL - PG_DB.exec(request, Time.utc, user.email) + PG_DB.exec(request, user.email) end # ------------------- diff --git a/src/invidious/jobs/refresh_channels_job.cr b/src/invidious/jobs/refresh_channels_job.cr index 941089c1..55fb8154 100644 --- a/src/invidious/jobs/refresh_channels_job.cr +++ b/src/invidious/jobs/refresh_channels_job.cr @@ -30,7 +30,7 @@ class Invidious::Jobs::RefreshChannelsJob < Invidious::Jobs::BaseJob spawn do begin LOGGER.trace("RefreshChannelsJob: #{id} fiber : Fetching channel") - channel = fetch_channel(id, CONFIG.full_refresh) + channel = fetch_channel(id, pull_all_videos: CONFIG.full_refresh) lim_fibers = max_fibers diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr index 5da6143b..4e9fc801 100644 --- a/src/invidious/routes/api/v1/authenticated.cr +++ b/src/invidious/routes/api/v1/authenticated.cr @@ -92,7 +92,7 @@ module Invidious::Routes::API::V1::Authenticated ucid = env.params.url["ucid"] if !user.subscriptions.includes? ucid - get_channel(ucid, false, false) + get_channel(ucid) Invidious::Database::Users.subscribe_channel(user, ucid) end diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr index c9271766..f7f7b426 100644 --- a/src/invidious/routes/feeds.cr +++ b/src/invidious/routes/feeds.cr @@ -362,7 +362,7 @@ module Invidious::Routes::Feeds end if ucid = HTTP::Params.parse(URI.parse(topic).query.not_nil!)["channel_id"]? - PG_DB.exec("UPDATE channels SET subscribed = $1 WHERE id = $2", Time.utc, ucid) + Invidious::Database::Channels.update_subscription_time(ucid) elsif plid = HTTP::Params.parse(URI.parse(topic).query.not_nil!)["playlist_id"]? Invidious::Database::Playlists.update_subscription_time(plid) else diff --git a/src/invidious/routes/notifications.cr b/src/invidious/routes/notifications.cr new file mode 100644 index 00000000..272a3dc7 --- /dev/null +++ b/src/invidious/routes/notifications.cr @@ -0,0 +1,78 @@ +module Invidious::Routes::Notifications + # /modify_notifications + # will "ding" all subscriptions. + # /modify_notifications?receive_all_updates=false&receive_no_updates=false + # will "unding" all subscriptions. + def self.modify(env) + locale = env.get("preferences").as(Preferences).locale + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env, "/") + + redirect = env.params.query["redirect"]? + redirect ||= "false" + redirect = redirect == "true" + + if !user + if redirect + return env.redirect referer + else + return error_json(403, "No such user") + end + end + + user = user.as(User) + + if !user.password + channel_req = {} of String => String + + channel_req["receive_all_updates"] = env.params.query["receive_all_updates"]? || "true" + channel_req["receive_no_updates"] = env.params.query["receive_no_updates"]? || "" + channel_req["receive_post_updates"] = env.params.query["receive_post_updates"]? || "true" + + channel_req.reject! { |k, v| v != "true" && v != "false" } + + headers = HTTP::Headers.new + headers["Cookie"] = env.request.headers["Cookie"] + + html = YT_POOL.client &.get("/subscription_manager?disable_polymer=1", headers) + + cookies = HTTP::Cookies.from_client_headers(headers) + html.cookies.each do |cookie| + if {"VISITOR_INFO1_LIVE", "YSC", "SIDCC"}.includes? cookie.name + if cookies[cookie.name]? + cookies[cookie.name] = cookie + else + cookies << cookie + end + end + end + headers = cookies.add_request_headers(headers) + + if match = html.body.match(/'XSRF_TOKEN': "(?<session_token>[^"]+)"/) + session_token = match["session_token"] + else + return env.redirect referer + end + + headers["content-type"] = "application/x-www-form-urlencoded" + channel_req["session_token"] = session_token + + subs = XML.parse_html(html.body) + subs.xpath_nodes(%q(//a[@class="subscription-title yt-uix-sessionlink"]/@href)).each do |channel| + channel_id = channel.content.lstrip("/channel/").not_nil! + channel_req["channel_id"] = channel_id + + YT_POOL.client &.post("/subscription_ajax?action_update_subscription_preferences=1", headers, form: channel_req) + end + end + + if redirect + env.redirect referer + else + env.response.content_type = "application/json" + "{}" + end + end +end diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr index 9c73874e..1ed29e79 100644 --- a/src/invidious/routes/playlists.cr +++ b/src/invidious/routes/playlists.cr @@ -151,12 +151,8 @@ module Invidious::Routes::Playlists page = env.params.query["page"]?.try &.to_i? page ||= 1 - begin - playlist = Invidious::Database::Playlists.select(id: plid, raise_on_fail: true) - if !playlist || playlist.author != user.email - return env.redirect referer - end - rescue ex + playlist = Invidious::Database::Playlists.select(id: plid) + if !playlist || playlist.author != user.email return env.redirect referer end @@ -235,12 +231,8 @@ module Invidious::Routes::Playlists page = env.params.query["page"]?.try &.to_i? page ||= 1 - begin - playlist = Invidious::Database::Playlists.select(id: plid, raise_on_fail: true) - if !playlist || playlist.author != user.email - return env.redirect referer - end - rescue ex + playlist = Invidious::Database::Playlists.select(id: plid) + if !playlist || playlist.author != user.email return env.redirect referer end diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index a832076c..9c740cf2 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -285,4 +285,191 @@ module Invidious::Routes::PreferencesRoute "{}" end end + + def self.data_control(env) + locale = env.get("preferences").as(Preferences).locale + + user = env.get? "user" + referer = get_referer(env) + + if !user + return env.redirect referer + end + + user = user.as(User) + + templated "data_control" + end + + def self.update_data_control(env) + locale = env.get("preferences").as(Preferences).locale + + user = env.get? "user" + referer = get_referer(env) + + if user + user = user.as(User) + + # TODO: Find a way to prevent browser timeout + + HTTP::FormData.parse(env.request) do |part| + body = part.body.gets_to_end + type = part.headers["Content-Type"] + + next if body.empty? + + # TODO: Unify into single import based on content-type + case part.name + when "import_invidious" + body = JSON.parse(body) + + if body["subscriptions"]? + user.subscriptions += body["subscriptions"].as_a.map(&.as_s) + user.subscriptions.uniq! + + user.subscriptions = get_batch_channels(user.subscriptions) + + Invidious::Database::Users.update_subscriptions(user) + end + + if body["watch_history"]? + user.watched += body["watch_history"].as_a.map(&.as_s) + user.watched.uniq! + Invidious::Database::Users.update_watch_history(user) + end + + if body["preferences"]? + user.preferences = Preferences.from_json(body["preferences"].to_json) + Invidious::Database::Users.update_preferences(user) + end + + if playlists = body["playlists"]?.try &.as_a? + playlists.each do |item| + title = item["title"]?.try &.as_s?.try &.delete("<>") + description = item["description"]?.try &.as_s?.try &.delete("\r") + privacy = item["privacy"]?.try &.as_s?.try { |privacy| PlaylistPrivacy.parse? privacy } + + next if !title + next if !description + next if !privacy + + playlist = create_playlist(title, privacy, user) + Invidious::Database::Playlists.update_description(playlist.id, description) + + videos = item["videos"]?.try &.as_a?.try &.each_with_index do |video_id, idx| + raise InfoException.new("Playlist cannot have more than 500 videos") if idx > 500 + + video_id = video_id.try &.as_s? + next if !video_id + + begin + video = get_video(video_id) + rescue ex + next + end + + playlist_video = PlaylistVideo.new({ + title: video.title, + id: video.id, + author: video.author, + ucid: video.ucid, + length_seconds: video.length_seconds, + published: video.published, + plid: playlist.id, + live_now: video.live_now, + index: Random::Secure.rand(0_i64..Int64::MAX), + }) + + Invidious::Database::PlaylistVideos.insert(playlist_video) + Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index) + end + end + end + when "import_youtube" + filename = part.filename || "" + extension = filename.split(".").last + + if extension == "xml" || type == "application/xml" || type == "text/xml" + subscriptions = XML.parse(body) + user.subscriptions += subscriptions.xpath_nodes(%q(//outline[@type="rss"])).map do |channel| + channel["xmlUrl"].match(/UC[a-zA-Z0-9_-]{22}/).not_nil![0] + end + elsif extension == "json" || type == "application/json" + subscriptions = JSON.parse(body) + user.subscriptions += subscriptions.as_a.compact_map do |entry| + entry["snippet"]["resourceId"]["channelId"].as_s + end + elsif extension == "csv" || type == "text/csv" + subscriptions = parse_subscription_export_csv(body) + user.subscriptions += subscriptions + else + haltf(env, status_code: 415, + response: error_template(415, "Invalid subscription file uploaded") + ) + end + + user.subscriptions.uniq! + user.subscriptions = get_batch_channels(user.subscriptions) + + Invidious::Database::Users.update_subscriptions(user) + when "import_freetube" + user.subscriptions += body.scan(/"channelId":"(?<channel_id>[a-zA-Z0-9_-]{24})"/).map do |md| + md["channel_id"] + end + user.subscriptions.uniq! + + user.subscriptions = get_batch_channels(user.subscriptions) + + Invidious::Database::Users.update_subscriptions(user) + when "import_newpipe_subscriptions" + body = JSON.parse(body) + user.subscriptions += body["subscriptions"].as_a.compact_map do |channel| + if match = channel["url"].as_s.match(/\/channel\/(?<channel>UC[a-zA-Z0-9_-]{22})/) + next match["channel"] + elsif match = channel["url"].as_s.match(/\/user\/(?<user>.+)/) + response = YT_POOL.client &.get("/user/#{match["user"]}?disable_polymer=1&hl=en&gl=US") + html = XML.parse_html(response.body) + ucid = html.xpath_node(%q(//link[@rel="canonical"])).try &.["href"].split("/")[-1] + next ucid if ucid + end + + nil + end + user.subscriptions.uniq! + + user.subscriptions = get_batch_channels(user.subscriptions) + + Invidious::Database::Users.update_subscriptions(user) + when "import_newpipe" + Compress::Zip::Reader.open(IO::Memory.new(body)) do |file| + file.each_entry do |entry| + if entry.filename == "newpipe.db" + tempfile = File.tempfile(".db") + File.write(tempfile.path, entry.io.gets_to_end) + db = DB.open("sqlite3://" + tempfile.path) + + user.watched += db.query_all("SELECT url FROM streams", as: String).map(&.lchop("https://www.youtube.com/watch?v=")) + user.watched.uniq! + + Invidious::Database::Users.update_watch_history(user) + + user.subscriptions += db.query_all("SELECT url FROM subscriptions", as: String).map(&.lchop("https://www.youtube.com/channel/")) + user.subscriptions.uniq! + + user.subscriptions = get_batch_channels(user.subscriptions) + + Invidious::Database::Users.update_subscriptions(user) + + db.close + tempfile.delete + end + end + end + else nil # Ignore + end + end + end + + env.redirect referer + end end diff --git a/src/invidious/routes/subscriptions.cr b/src/invidious/routes/subscriptions.cr new file mode 100644 index 00000000..ec8fe67b --- /dev/null +++ b/src/invidious/routes/subscriptions.cr @@ -0,0 +1,168 @@ +module Invidious::Routes::Subscriptions + def self.toggle_subscription(env) + locale = env.get("preferences").as(Preferences).locale + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env, "/") + + redirect = env.params.query["redirect"]? + redirect ||= "true" + redirect = redirect == "true" + + if !user + if redirect + return env.redirect referer + else + return error_json(403, "No such user") + end + end + + user = user.as(User) + sid = sid.as(String) + token = env.params.body["csrf_token"]? + + begin + validate_request(token, sid, env.request, HMAC_KEY, locale) + rescue ex + if redirect + return error_template(400, ex) + else + return error_json(400, ex) + end + end + + if env.params.query["action_create_subscription_to_channel"]?.try &.to_i?.try &.== 1 + action = "action_create_subscription_to_channel" + elsif env.params.query["action_remove_subscriptions"]?.try &.to_i?.try &.== 1 + action = "action_remove_subscriptions" + else + return env.redirect referer + end + + channel_id = env.params.query["c"]? + channel_id ||= "" + + if !user.password + # Sync subscriptions with YouTube + subscribe_ajax(channel_id, action, env.request.headers) + end + + case action + when "action_create_subscription_to_channel" + if !user.subscriptions.includes? channel_id + get_channel(channel_id) + Invidious::Database::Users.subscribe_channel(user, channel_id) + end + when "action_remove_subscriptions" + Invidious::Database::Users.unsubscribe_channel(user, channel_id) + else + return error_json(400, "Unsupported action #{action}") + end + + if redirect + env.redirect referer + else + env.response.content_type = "application/json" + "{}" + end + end + + def self.subscription_manager(env) + locale = env.get("preferences").as(Preferences).locale + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env) + + if !user + return env.redirect referer + end + + user = user.as(User) + sid = sid.as(String) + + if !user.password + # Refresh account + headers = HTTP::Headers.new + headers["Cookie"] = env.request.headers["Cookie"] + + user, sid = get_user(sid, headers) + end + + action_takeout = env.params.query["action_takeout"]?.try &.to_i? + action_takeout ||= 0 + action_takeout = action_takeout == 1 + + format = env.params.query["format"]? + format ||= "rss" + + subscriptions = Invidious::Database::Channels.select(user.subscriptions) + subscriptions.sort_by!(&.author.downcase) + + if action_takeout + if format == "json" + env.response.content_type = "application/json" + env.response.headers["content-disposition"] = "attachment" + playlists = Invidious::Database::Playlists.select_like_iv(user.email) + + return JSON.build do |json| + json.object do + json.field "subscriptions", user.subscriptions + json.field "watch_history", user.watched + json.field "preferences", user.preferences + json.field "playlists" do + json.array do + playlists.each do |playlist| + json.object do + json.field "title", playlist.title + json.field "description", html_to_content(playlist.description_html) + json.field "privacy", playlist.privacy.to_s + json.field "videos" do + json.array do + Invidious::Database::PlaylistVideos.select_ids(playlist.id, playlist.index, limit: 500).each do |video_id| + json.string video_id + end + end + end + end + end + end + end + end + end + else + env.response.content_type = "application/xml" + env.response.headers["content-disposition"] = "attachment" + export = XML.build do |xml| + xml.element("opml", version: "1.1") do + xml.element("body") do + if format == "newpipe" + title = "YouTube Subscriptions" + else + title = "Invidious Subscriptions" + end + + xml.element("outline", text: title, title: title) do + subscriptions.each do |channel| + if format == "newpipe" + xml_url = "https://www.youtube.com/feeds/videos.xml?channel_id=#{channel.id}" + else + xml_url = "#{HOST_URL}/feed/channel/#{channel.id}" + end + + xml.element("outline", text: channel.author, title: channel.author, + "type": "rss", xmlUrl: xml_url) + end + end + end + end + end + + return export.gsub(%(<?xml version="1.0"?>\n), "") + end + end + + templated "subscription_manager" + end +end diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 1198f48f..7d048ce8 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -200,4 +200,70 @@ module Invidious::Routes::Watch return env.redirect url end + + def self.mark_watched(env) + locale = env.get("preferences").as(Preferences).locale + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env, "/feed/subscriptions") + + redirect = env.params.query["redirect"]? + redirect ||= "true" + redirect = redirect == "true" + + if !user + if redirect + return env.redirect referer + else + return error_json(403, "No such user") + end + end + + user = user.as(User) + sid = sid.as(String) + token = env.params.body["csrf_token"]? + + id = env.params.query["id"]? + if !id + env.response.status_code = 400 + return + end + + begin + validate_request(token, sid, env.request, HMAC_KEY, locale) + rescue ex + if redirect + return error_template(400, ex) + else + return error_json(400, ex) + end + end + + if env.params.query["action_mark_watched"]? + action = "action_mark_watched" + elsif env.params.query["action_mark_unwatched"]? + action = "action_mark_unwatched" + else + return env.redirect referer + end + + case action + when "action_mark_watched" + if !user.watched.includes? id + Invidious::Database::Users.mark_watched(user, id) + end + when "action_mark_unwatched" + Invidious::Database::Users.mark_unwatched(user, id) + else + return error_json(400, "Unsupported action #{action}") + end + + if redirect + env.redirect referer + else + env.response.content_type = "application/json" + "{}" + end + end end diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 213f5622..9810f8a2 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -73,7 +73,7 @@ def fetch_user(sid, headers) end end - channels = get_batch_channels(channels, false, false) + channels = get_batch_channels(channels) email = feed.xpath_node(%q(//a[@class="yt-masthead-picker-header yt-masthead-picker-active-account"])) if email diff --git a/src/invidious/views/components/player_sources.ecr b/src/invidious/views/components/player_sources.ecr index 0d97d35a..9af3899c 100644 --- a/src/invidious/views/components/player_sources.ecr +++ b/src/invidious/views/components/player_sources.ecr @@ -1,18 +1,18 @@ -<link rel="stylesheet" href="/css/video-js.min.css?v=<%= ASSET_COMMIT %>"> -<link rel="stylesheet" href="/css/videojs-http-source-selector.css?v=<%= ASSET_COMMIT %>"> -<link rel="stylesheet" href="/css/videojs.markers.min.css?v=<%= ASSET_COMMIT %>"> -<link rel="stylesheet" href="/css/videojs-share.css?v=<%= ASSET_COMMIT %>"> -<link rel="stylesheet" href="/css/videojs-vtt-thumbnails.css?v=<%= ASSET_COMMIT %>"> -<link rel="stylesheet" href="/css/videojs-mobile-ui.css?v=<%= ASSET_COMMIT %>"> +<link rel="stylesheet" href="/videojs/video.js/video-js.css?v=<%= ASSET_COMMIT %>"> +<link rel="stylesheet" href="/videojs/videojs-http-source-selector/videojs-http-source-selector.css?v=<%= ASSET_COMMIT %>"> +<link rel="stylesheet" href="/videojs/videojs-markers/videojs.markers.css?v=<%= ASSET_COMMIT %>"> +<link rel="stylesheet" href="/videojs/videojs-share/videojs-share.css?v=<%= ASSET_COMMIT %>"> +<link rel="stylesheet" href="/videojs/videojs-vtt-thumbnails/videojs-vtt-thumbnails.css?v=<%= ASSET_COMMIT %>"> +<link rel="stylesheet" href="/videojs/videojs-mobile-ui/videojs-mobile-ui.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/player.css?v=<%= ASSET_COMMIT %>"> -<script src="/js/video.min.js?v=<%= ASSET_COMMIT %>"></script> -<script src="/js/videojs-mobile-ui.min.js?v=<%= ASSET_COMMIT %>"></script> -<script src="/js/videojs-contrib-quality-levels.min.js?v=<%= ASSET_COMMIT %>"></script> -<script src="/js/videojs-http-source-selector.min.js?v=<%= ASSET_COMMIT %>"></script> -<script src="/js/videojs-markers.min.js?v=<%= ASSET_COMMIT %>"></script> -<script src="/js/videojs-share.min.js?v=<%= ASSET_COMMIT %>"></script> -<script src="/js/videojs-vtt-thumbnails.min.js?v=<%= ASSET_COMMIT %>"></script> +<script src="/videojs/video.js/video.js?v=<%= ASSET_COMMIT %>"></script> +<script src="/videojs/videojs-mobile-ui/videojs-mobile-ui.js?v=<%= ASSET_COMMIT %>"></script> +<script src="/videojs/videojs-contrib-quality-levels/videojs-contrib-quality-levels.js?v=<%= ASSET_COMMIT %>"></script> +<script src="/videojs/videojs-http-source-selector/videojs-http-source-selector.js?v=<%= ASSET_COMMIT %>"></script> +<script src="/videojs/videojs-markers/videojs-markers.js?v=<%= ASSET_COMMIT %>"></script> +<script src="/videojs/videojs-share/videojs-share.js?v=<%= ASSET_COMMIT %>"></script> +<script src="/videojs/videojs-vtt-thumbnails/videojs-vtt-thumbnails.js?v=<%= ASSET_COMMIT %>"></script> <% if params.annotations %> @@ -26,6 +26,6 @@ <% end %> <% if !params.listen && params.vr_mode %> - <link rel="stylesheet" href="/css/videojs-vr.css?v=<%= ASSET_COMMIT %>"> - <script src="/js/videojs-vr.js?v=<%= ASSET_COMMIT %>"></script> + <link rel="stylesheet" href="/videojs/videojs-vr/videojs-vr.css?v=<%= ASSET_COMMIT %>"> + <script src="/videojs/videojs-vr/videojs-vr.js?v=<%= ASSET_COMMIT %>"></script> <% end %> diff --git a/src/invidious/views/embed.ecr b/src/invidious/views/embed.ecr index dbb86009..cd0fd0d5 100644 --- a/src/invidious/views/embed.ecr +++ b/src/invidious/views/embed.ecr @@ -6,8 +6,8 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="thumbnail" content="<%= thumbnail %>"> <%= rendered "components/player_sources" %> - <link rel="stylesheet" href="/css/videojs-overlay.css?v=<%= ASSET_COMMIT %>"> - <script src="/js/videojs-overlay.min.js?v=<%= ASSET_COMMIT %>"></script> + <link rel="stylesheet" href="/videojs/videojs-overlay/videojs-overlay.css?v=<%= ASSET_COMMIT %>"> + <script src="videojs/videojs-overlay/videojs-overlay.js?v=<%= ASSET_COMMIT %>"></script> <link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/embed.css?v=<%= ASSET_COMMIT %>"> <title><%= HTML.escape(video.title) %> - Invidious</title> diff --git a/src/invidious/views/licenses.ecr b/src/invidious/views/licenses.ecr index 9f5bcbdd..861913d0 100644 --- a/src/invidious/views/licenses.ecr +++ b/src/invidious/views/licenses.ecr @@ -75,7 +75,7 @@ </td> <td> - <a href="https://github.com/omarroth/videojs-quality-selector"><%= translate(locale, "source") %></a> + <a href="https://github.com/iv-org/videojs-quality-selector"><%= translate(locale, "source") %></a> </td> </tr> @@ -123,7 +123,7 @@ <tr> <td> - <a href="/js/videojs-contrib-quality-levels.min.js?v=<%= ASSET_COMMIT %>">videojs-contrib-quality-levels.min.js</a> + <a href="/videojs/videojs-contrib-quality-levels/videojs-contrib-quality-levels.js?v=<%= ASSET_COMMIT %>">videojs-contrib-quality-levels.js</a> </td> <td> @@ -137,7 +137,7 @@ <tr> <td> - <a href="/js/videojs-http-source-selector.min.js?v=<%= ASSET_COMMIT %>">videojs-http-source-selector.min.js</a> + <a href="/videojs/videojs-http-source-selector/videojs-http-source-selector.js?v=<%= ASSET_COMMIT %>">videojs-http-source-selector.js</a> </td> <td> @@ -151,7 +151,7 @@ <tr> <td> - <a href="/js/videojs-mobile-ui.min.js?v=<%= ASSET_COMMIT %>">videojs-mobile-ui.min.js</a> + <a href="/videojs/videojs-mobile-ui/videojs-mobile-ui.js?v=<%= ASSET_COMMIT %>">videojs-mobile-ui.js</a> </td> <td> @@ -165,7 +165,7 @@ <tr> <td> - <a href="/js/videojs-markers.min.js?v=<%= ASSET_COMMIT %>">videojs-markers.min.js</a> + <a href="/videojs/videojs-markers/videojs-markers.js?v=<%= ASSET_COMMIT %>">videojs-markers.js</a> </td> <td> @@ -179,7 +179,7 @@ <tr> <td> - <a href="/js/videojs-overlay.min.js?v=<%= ASSET_COMMIT %>">videojs-overlay.min.js</a> + <a href="/videojs/videojs-overlay/videojs-overlay.js?v=<%= ASSET_COMMIT %>">videojs-overlay.js</a> </td> <td> @@ -193,7 +193,7 @@ <tr> <td> - <a href="/js/videojs-share.min.js?v=<%= ASSET_COMMIT %>">videojs-share.min.js</a> + <a href="/videojs/videojs-share/videojs-share.js?v=<%= ASSET_COMMIT %>">videojs-share.js</a> </td> <td> @@ -207,7 +207,7 @@ <tr> <td> - <a href="/js/videojs-vtt-thumbnails.min.js?v=<%= ASSET_COMMIT %>">videojs-vtt-thumbnails.min.js</a> + <a href="/videojs/videojs-vtt-thumbnails/videojs-vtt-thumbnails.js?v=<%= ASSET_COMMIT %>">videojs-vtt-thumbnails.js</a> </td> <td> @@ -215,7 +215,7 @@ </td> <td> - <a href="https://github.com/omarroth/videojs-vtt-thumbnails"><%= translate(locale, "source") %></a> + <a href="https://github.com/chrisboustead/videojs-vtt-thumbnails"><%= translate(locale, "source") %></a> </td> </tr> @@ -235,7 +235,7 @@ <tr> <td> - <a href="/js/videojs-vr.js?v=<%= ASSET_COMMIT %>">videojs-vr.js</a> + <a href="/videojs/videojs-vr/videojs-vr.js?v=<%= ASSET_COMMIT %>">videojs-vr.js</a> </td> <td> @@ -249,7 +249,7 @@ <tr> <td> - <a href="/js/video.min.js?v=<%= ASSET_COMMIT %>">video.min.js</a> + <a href="/videojs/video.js/video.js?v=<%= ASSET_COMMIT %>">video.js</a> </td> <td> |
