2021-10-03 05:04:02 +09:00
|
|
|
{% skip_file if flag?(:api_only) %}
|
|
|
|
|
2021-08-11 19:36:25 +09:00
|
|
|
module Invidious::Routes::Watch
|
|
|
|
def self.handle(env)
|
2021-11-09 07:52:55 +09:00
|
|
|
locale = env.get("preferences").as(Preferences).locale
|
2020-11-03 09:06:06 +09:00
|
|
|
region = env.params.query["region"]?
|
|
|
|
|
|
|
|
if env.params.query.to_s.includes?("%20") || env.params.query.to_s.includes?("+")
|
|
|
|
url = "/watch?" + env.params.query.to_s.gsub("%20", "").delete("+")
|
|
|
|
return env.redirect url
|
|
|
|
end
|
|
|
|
|
|
|
|
if env.params.query["v"]?
|
|
|
|
id = env.params.query["v"]
|
|
|
|
|
|
|
|
if env.params.query["v"].empty?
|
2020-11-30 18:59:21 +09:00
|
|
|
return error_template(400, "Invalid parameters.")
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|
|
|
|
|
|
|
|
if id.size > 11
|
|
|
|
url = "/watch?v=#{id[0, 11]}"
|
|
|
|
env.params.query.delete_all("v")
|
|
|
|
if env.params.query.size > 0
|
|
|
|
url += "&#{env.params.query}"
|
|
|
|
end
|
|
|
|
|
|
|
|
return env.redirect url
|
|
|
|
end
|
|
|
|
else
|
|
|
|
return env.redirect "/"
|
|
|
|
end
|
|
|
|
|
2020-12-08 10:28:44 +09:00
|
|
|
embed_link = "/embed/#{id}"
|
|
|
|
if env.params.query.size > 1
|
2020-12-29 09:22:56 +09:00
|
|
|
embed_params = HTTP::Params.parse(env.params.query.to_s)
|
2020-12-08 10:28:44 +09:00
|
|
|
embed_params.delete_all("v")
|
|
|
|
embed_link += "?"
|
|
|
|
embed_link += embed_params.to_s
|
|
|
|
end
|
|
|
|
|
2020-11-03 09:06:06 +09:00
|
|
|
plid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "")
|
2021-12-07 10:55:43 +09:00
|
|
|
continuation = process_continuation(env.params.query, plid, id)
|
2020-11-03 09:06:06 +09:00
|
|
|
|
|
|
|
nojs = env.params.query["nojs"]?
|
|
|
|
|
|
|
|
nojs ||= "0"
|
|
|
|
nojs = nojs == "1"
|
|
|
|
|
|
|
|
preferences = env.get("preferences").as(Preferences)
|
|
|
|
|
|
|
|
user = env.get?("user").try &.as(User)
|
|
|
|
if user
|
|
|
|
subscriptions = user.subscriptions
|
|
|
|
watched = user.watched
|
|
|
|
notifications = user.notifications
|
|
|
|
end
|
|
|
|
subscriptions ||= [] of String
|
|
|
|
|
|
|
|
params = process_video_params(env.params.query, preferences)
|
|
|
|
env.params.query.delete_all("listen")
|
|
|
|
|
|
|
|
begin
|
2021-12-07 10:55:43 +09:00
|
|
|
video = get_video(id, region: params.region)
|
2020-11-03 09:06:06 +09:00
|
|
|
rescue ex : VideoRedirect
|
|
|
|
return env.redirect env.request.resource.gsub(id, ex.video_id)
|
|
|
|
rescue ex
|
2021-01-05 00:51:06 +09:00
|
|
|
LOGGER.error("get_video: #{id} : #{ex.message}")
|
2020-11-30 18:59:21 +09:00
|
|
|
return error_template(500, ex)
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|
|
|
|
|
|
|
|
if preferences.annotations_subscribed &&
|
|
|
|
subscriptions.includes?(video.ucid) &&
|
|
|
|
(env.params.query["iv_load_policy"]? || "1") == "1"
|
|
|
|
params.annotations = true
|
|
|
|
end
|
|
|
|
env.params.query.delete_all("iv_load_policy")
|
|
|
|
|
|
|
|
if watched && !watched.includes? id
|
2021-12-03 10:27:51 +09:00
|
|
|
Invidious::Database::Users.mark_watched(user.as(User), id)
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|
|
|
|
|
|
|
|
if notifications && notifications.includes? id
|
2021-12-03 11:29:52 +09:00
|
|
|
Invidious::Database::Users.remove_notification(user.as(User), id)
|
2020-11-03 09:06:06 +09:00
|
|
|
env.get("user").as(User).notifications.delete(id)
|
|
|
|
notifications.delete(id)
|
|
|
|
end
|
|
|
|
|
|
|
|
if nojs
|
|
|
|
if preferences
|
|
|
|
source = preferences.comments[0]
|
|
|
|
if source.empty?
|
|
|
|
source = preferences.comments[1]
|
|
|
|
end
|
|
|
|
|
|
|
|
if source == "youtube"
|
|
|
|
begin
|
2021-08-13 02:14:30 +09:00
|
|
|
comment_html = JSON.parse(fetch_youtube_comments(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"]
|
2020-11-03 09:06:06 +09:00
|
|
|
rescue ex
|
|
|
|
if preferences.comments[1] == "reddit"
|
|
|
|
comments, reddit_thread = fetch_reddit_comments(id)
|
|
|
|
comment_html = template_reddit_comments(comments, locale)
|
|
|
|
|
|
|
|
comment_html = fill_links(comment_html, "https", "www.reddit.com")
|
|
|
|
comment_html = replace_links(comment_html)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elsif source == "reddit"
|
|
|
|
begin
|
|
|
|
comments, reddit_thread = fetch_reddit_comments(id)
|
|
|
|
comment_html = template_reddit_comments(comments, locale)
|
|
|
|
|
|
|
|
comment_html = fill_links(comment_html, "https", "www.reddit.com")
|
|
|
|
comment_html = replace_links(comment_html)
|
|
|
|
rescue ex
|
|
|
|
if preferences.comments[1] == "youtube"
|
2021-08-13 02:14:30 +09:00
|
|
|
comment_html = JSON.parse(fetch_youtube_comments(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"]
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
2021-08-13 02:14:30 +09:00
|
|
|
comment_html = JSON.parse(fetch_youtube_comments(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"]
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|
|
|
|
|
|
|
|
comment_html ||= ""
|
|
|
|
end
|
|
|
|
|
|
|
|
fmt_stream = video.fmt_stream
|
|
|
|
adaptive_fmts = video.adaptive_fmts
|
|
|
|
|
|
|
|
if params.local
|
2021-02-01 03:52:32 +09:00
|
|
|
fmt_stream.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
|
|
|
|
adaptive_fmts.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|
|
|
|
|
|
|
|
video_streams = video.video_streams
|
|
|
|
audio_streams = video.audio_streams
|
|
|
|
|
|
|
|
# Older videos may not have audio sources available.
|
|
|
|
# We redirect here so they're not unplayable
|
|
|
|
if audio_streams.empty? && !video.live_now
|
|
|
|
if params.quality == "dash"
|
|
|
|
env.params.query.delete_all("quality")
|
|
|
|
env.params.query["quality"] = "medium"
|
|
|
|
return env.redirect "/watch?#{env.params.query}"
|
|
|
|
elsif params.listen
|
|
|
|
env.params.query.delete_all("listen")
|
|
|
|
env.params.query["listen"] = "0"
|
|
|
|
return env.redirect "/watch?#{env.params.query}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
captions = video.captions
|
|
|
|
|
|
|
|
preferred_captions = captions.select { |caption|
|
2021-06-27 23:18:16 +09:00
|
|
|
params.preferred_captions.includes?(caption.name) ||
|
2021-09-25 11:15:23 +09:00
|
|
|
params.preferred_captions.includes?(caption.language_code.split("-")[0])
|
2020-11-03 09:06:06 +09:00
|
|
|
}
|
|
|
|
preferred_captions.sort_by! { |caption|
|
2021-06-27 23:18:16 +09:00
|
|
|
(params.preferred_captions.index(caption.name) ||
|
2021-09-25 11:15:23 +09:00
|
|
|
params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil!
|
2020-11-03 09:06:06 +09:00
|
|
|
}
|
|
|
|
captions = captions - preferred_captions
|
|
|
|
|
|
|
|
aspect_ratio = "16:9"
|
|
|
|
|
|
|
|
thumbnail = "/vi/#{video.id}/maxres.jpg"
|
|
|
|
|
|
|
|
if params.raw
|
|
|
|
if params.listen
|
|
|
|
url = audio_streams[0]["url"].as_s
|
|
|
|
|
2021-07-29 08:28:09 +09:00
|
|
|
if params.quality.ends_with? "k"
|
|
|
|
audio_streams.each do |fmt|
|
|
|
|
if fmt["bitrate"].as_i == params.quality.rchop("k").to_i
|
|
|
|
url = fmt["url"].as_s
|
|
|
|
end
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
url = fmt_stream[0]["url"].as_s
|
|
|
|
|
|
|
|
fmt_stream.each do |fmt|
|
|
|
|
if fmt["quality"].as_s == params.quality
|
|
|
|
url = fmt["url"].as_s
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return env.redirect url
|
|
|
|
end
|
|
|
|
|
2022-02-23 01:42:41 +09:00
|
|
|
# Structure used for the download widget
|
|
|
|
video_assets = Invidious::Frontend::WatchPage::VideoAssets.new(
|
|
|
|
full_videos: fmt_stream,
|
|
|
|
video_streams: video_streams,
|
|
|
|
audio_streams: audio_streams,
|
|
|
|
captions: video.captions
|
|
|
|
)
|
|
|
|
|
2020-11-03 09:06:06 +09:00
|
|
|
templated "watch"
|
|
|
|
end
|
2021-04-07 12:07:19 +09:00
|
|
|
|
2021-08-11 19:36:25 +09:00
|
|
|
def self.redirect(env)
|
2021-04-12 14:09:46 +09:00
|
|
|
url = "/watch?v=#{env.params.url["id"]}"
|
|
|
|
if env.params.query.size > 0
|
|
|
|
url += "&#{env.params.query}"
|
|
|
|
end
|
|
|
|
|
|
|
|
return env.redirect url
|
2021-04-07 12:07:19 +09:00
|
|
|
end
|
2021-11-27 16:16:09 +09:00
|
|
|
|
|
|
|
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
|
2022-02-02 05:40:06 +09:00
|
|
|
|
|
|
|
def self.clip(env)
|
|
|
|
clip_id = env.params.url["clip"]?
|
|
|
|
|
|
|
|
return error_template(400, "A clip ID is required") if !clip_id
|
|
|
|
|
|
|
|
response = YoutubeAPI.resolve_url("https://www.youtube.com/clip/#{clip_id}")
|
|
|
|
return error_template(400, "Invalid clip ID") if response["error"]?
|
|
|
|
|
|
|
|
if video_id = response.dig?("endpoint", "watchEndpoint", "videoId")
|
|
|
|
return env.redirect "/watch?v=#{video_id}&#{env.params.query}"
|
|
|
|
else
|
|
|
|
return error_template(404, "The requested clip doesn't exist")
|
|
|
|
end
|
|
|
|
end
|
2020-11-03 09:06:06 +09:00
|
|
|
end
|