forked from midou/invidious
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
45fa148380 | |||
2ba0063dc0 | |||
b57176d7ef | |||
0dbef6ab9f | |||
8fc4dcfdea | |||
6c98513153 | |||
c3d8ca68b3 | |||
a37692cce4 | |||
a1ad561b98 | |||
7fd0f93d02 | |||
23aaf7f1b7 | |||
41a04e7c67 | |||
77b12b6249 | |||
78fcf579a7 | |||
9ae3bf216e | |||
0e7c56687b | |||
01a80995d3 |
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Invidious is what YouTube should be
|
## Invidious is what YouTube should be
|
||||||
|
|
||||||
|
Liberapay: https://liberapay.com/omarroth
|
||||||
Patreon: https://patreon.com/omarroth
|
Patreon: https://patreon.com/omarroth
|
||||||
BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY
|
BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY
|
||||||
BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk
|
BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk
|
||||||
|
183
src/invidious.cr
183
src/invidious.cr
@ -215,8 +215,9 @@ get "/watch" do |env|
|
|||||||
end
|
end
|
||||||
subscriptions ||= [] of String
|
subscriptions ||= [] of String
|
||||||
|
|
||||||
autoplay, video_loop, video_start, video_end, listen, raw, quality, controls = process_video_params(env.params.query, preferences)
|
params = process_video_params(env.params.query, preferences)
|
||||||
if listen
|
|
||||||
|
if params[:listen]
|
||||||
env.params.query.delete_all("listen")
|
env.params.query.delete_all("listen")
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -234,13 +235,17 @@ get "/watch" do |env|
|
|||||||
audio_streams = video.audio_streams(adaptive_fmts)
|
audio_streams = video.audio_streams(adaptive_fmts)
|
||||||
|
|
||||||
captions = video.captions
|
captions = video.captions
|
||||||
if preferences
|
|
||||||
preferred_captions = captions.select { |caption| preferences.captions.includes? caption.name.simpleText }
|
|
||||||
preferred_captions.sort_by! { |caption| preferences.captions.index(caption.name.simpleText).not_nil! }
|
|
||||||
|
|
||||||
captions = captions - preferred_captions
|
preferred_captions = captions.select { |caption|
|
||||||
end
|
params[:preferred_captions].includes?(caption.name.simpleText) ||
|
||||||
preferred_captions ||= [] of Caption
|
params[:preferred_captions].includes?(caption.languageCode.split("-")[0])
|
||||||
|
}
|
||||||
|
preferred_captions.sort_by! { |caption|
|
||||||
|
(params[:preferred_captions].index(caption.name.simpleText) ||
|
||||||
|
params[:preferred_captions].index(caption.languageCode.split("-")[0])).not_nil!
|
||||||
|
}
|
||||||
|
captions = captions - preferred_captions
|
||||||
|
|
||||||
aspect_ratio = "16:9"
|
aspect_ratio = "16:9"
|
||||||
|
|
||||||
video.description = fill_links(video.description, "https", "www.youtube.com")
|
video.description = fill_links(video.description, "https", "www.youtube.com")
|
||||||
@ -259,11 +264,11 @@ get "/watch" do |env|
|
|||||||
# TODO: Find highest resolution thumbnail automatically
|
# TODO: Find highest resolution thumbnail automatically
|
||||||
thumbnail = "https://i.ytimg.com/vi/#{video.id}/mqdefault.jpg"
|
thumbnail = "https://i.ytimg.com/vi/#{video.id}/mqdefault.jpg"
|
||||||
|
|
||||||
if raw
|
if params[:raw]
|
||||||
url = fmt_stream[0]["url"]
|
url = fmt_stream[0]["url"]
|
||||||
|
|
||||||
fmt_stream.each do |fmt|
|
fmt_stream.each do |fmt|
|
||||||
if fmt["label"].split(" - ")[0] == quality
|
if fmt["label"].split(" - ")[0] == params[:quality]
|
||||||
url = fmt["url"]
|
url = fmt["url"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -276,7 +281,9 @@ get "/watch" do |env|
|
|||||||
rvs << HTTP::Params.parse(rv).to_h
|
rvs << HTTP::Params.parse(rv).to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# rating = (video.likes.to_f/(video.likes.to_f + video.dislikes.to_f) * 4 + 1)
|
||||||
rating = video.info["avg_rating"].to_f64
|
rating = video.info["avg_rating"].to_f64
|
||||||
|
|
||||||
engagement = ((video.dislikes.to_f + video.likes.to_f)/video.views * 100)
|
engagement = ((video.dislikes.to_f + video.likes.to_f)/video.views * 100)
|
||||||
|
|
||||||
playability_status = video.player_response["playabilityStatus"]?
|
playability_status = video.player_response["playabilityStatus"]?
|
||||||
@ -313,21 +320,7 @@ get "/embed/:id" do |env|
|
|||||||
next env.redirect url
|
next env.redirect url
|
||||||
end
|
end
|
||||||
|
|
||||||
autoplay, video_loop, video_start, video_end, listen, raw, quality, controls = process_video_params(env.params.query, nil)
|
params = process_video_params(env.params.query, nil)
|
||||||
preferred_captions = [] of Caption
|
|
||||||
preferences = Preferences.from_json({
|
|
||||||
"video_loop" => video_loop,
|
|
||||||
"autoplay" => autoplay,
|
|
||||||
"speed" => 1.0,
|
|
||||||
"quality" => quality,
|
|
||||||
"volume" => 100,
|
|
||||||
"max_results" => 0,
|
|
||||||
"sort" => "",
|
|
||||||
"latest_only" => false,
|
|
||||||
"unseen_only" => false,
|
|
||||||
"dark_mode" => false,
|
|
||||||
}.to_json)
|
|
||||||
aspect_ratio = nil
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
video = get_video(id, PG_DB)
|
video = get_video(id, PG_DB)
|
||||||
@ -343,6 +336,18 @@ get "/embed/:id" do |env|
|
|||||||
|
|
||||||
captions = video.captions
|
captions = video.captions
|
||||||
|
|
||||||
|
preferred_captions = captions.select { |caption|
|
||||||
|
params[:preferred_captions].includes?(caption.name.simpleText) ||
|
||||||
|
params[:preferred_captions].includes?(caption.languageCode.split("-")[0])
|
||||||
|
}
|
||||||
|
preferred_captions.sort_by! { |caption|
|
||||||
|
(params[:preferred_captions].index(caption.name.simpleText) ||
|
||||||
|
params[:preferred_captions].index(caption.languageCode.split("-")[0])).not_nil!
|
||||||
|
}
|
||||||
|
captions = captions - preferred_captions
|
||||||
|
|
||||||
|
aspect_ratio = nil
|
||||||
|
|
||||||
video.description = fill_links(video.description, "https", "www.youtube.com")
|
video.description = fill_links(video.description, "https", "www.youtube.com")
|
||||||
video.description = add_alt_links(video.description)
|
video.description = add_alt_links(video.description)
|
||||||
description = video.short_description
|
description = video.short_description
|
||||||
@ -359,11 +364,11 @@ get "/embed/:id" do |env|
|
|||||||
# TODO: Find highest resolution thumbnail automatically
|
# TODO: Find highest resolution thumbnail automatically
|
||||||
thumbnail = "https://i.ytimg.com/vi/#{video.id}/mqdefault.jpg"
|
thumbnail = "https://i.ytimg.com/vi/#{video.id}/mqdefault.jpg"
|
||||||
|
|
||||||
if raw
|
if params[:raw]
|
||||||
url = fmt_stream[0]["url"]
|
url = fmt_stream[0]["url"]
|
||||||
|
|
||||||
fmt_stream.each do |fmt|
|
fmt_stream.each do |fmt|
|
||||||
if fmt["label"].split(" - ")[0] == quality
|
if fmt["label"].split(" - ")[0] == params[:quality]
|
||||||
url = fmt["url"]
|
url = fmt["url"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -424,8 +429,32 @@ get "/search" do |env|
|
|||||||
page = env.params.query["page"]?.try &.to_i?
|
page = env.params.query["page"]?.try &.to_i?
|
||||||
page ||= 1
|
page ||= 1
|
||||||
|
|
||||||
search_params = build_search_params(sort_by: "relevance", content_type: "video")
|
sort = "relevance"
|
||||||
videos = search(query, page, search_params)
|
date = ""
|
||||||
|
duration = ""
|
||||||
|
features = [] of String
|
||||||
|
|
||||||
|
operators = query.split(" ").select { |a| a.match(/\w+:[\w,]+/) }
|
||||||
|
operators.each do |operator|
|
||||||
|
key, value = operator.split(":")
|
||||||
|
|
||||||
|
case key
|
||||||
|
when "sort"
|
||||||
|
sort = value
|
||||||
|
when "date"
|
||||||
|
date = value
|
||||||
|
when "duration"
|
||||||
|
duration = value
|
||||||
|
when "features"
|
||||||
|
features = value.split(",")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
query = (query.split(" ") - operators).join(" ")
|
||||||
|
|
||||||
|
search_params = build_search_params(sort: sort, date: date, content_type: "video",
|
||||||
|
duration: duration, features: features)
|
||||||
|
count, videos = search(query, page, search_params).as(Tuple)
|
||||||
|
|
||||||
templated "search"
|
templated "search"
|
||||||
end
|
end
|
||||||
@ -761,8 +790,9 @@ post "/preferences" do |env|
|
|||||||
volume = env.params.body["volume"]?.try &.as(String).to_i?
|
volume = env.params.body["volume"]?.try &.as(String).to_i?
|
||||||
volume ||= 100
|
volume ||= 100
|
||||||
|
|
||||||
comments = env.params.body["comments"]?
|
comments_0 = env.params.body["comments_0"]?.try &.as(String) || "youtube"
|
||||||
comments ||= "youtube"
|
comments_1 = env.params.body["comments_1"]?.try &.as(String) || ""
|
||||||
|
comments = [comments_0, comments_1]
|
||||||
|
|
||||||
captions_0 = env.params.body["captions_0"]?.try &.as(String) || ""
|
captions_0 = env.params.body["captions_0"]?.try &.as(String) || ""
|
||||||
captions_1 = env.params.body["captions_1"]?.try &.as(String) || ""
|
captions_1 = env.params.body["captions_1"]?.try &.as(String) || ""
|
||||||
@ -1027,18 +1057,18 @@ post "/data_control" do |env|
|
|||||||
body["watch_history"].as_a.each do |id|
|
body["watch_history"].as_a.each do |id|
|
||||||
id = id.as_s
|
id = id.as_s
|
||||||
if !user.watched.includes? id
|
if !user.watched.includes? id
|
||||||
PG_DB.exec("UPDATE users SET watched = array_append(watched,$1) WHERE id = $2", id, user.id)
|
PG_DB.exec("UPDATE users SET watched = array_append(watched,$1) WHERE email = $2", id, user.email)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PG_DB.exec("UPDATE users SET preferences = $1 WHERE id = $2", body["preferences"].to_json, user.id)
|
PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", body["preferences"].to_json, user.email)
|
||||||
when "import_youtube"
|
when "import_youtube"
|
||||||
subscriptions = XML.parse(body)
|
subscriptions = XML.parse(body)
|
||||||
subscriptions.xpath_nodes(%q(//outline[@type="rss"])).each do |channel|
|
subscriptions.xpath_nodes(%q(//outline[@type="rss"])).each do |channel|
|
||||||
ucid = channel["xmlUrl"].match(/UC[a-zA-Z0-9_-]{22}/).not_nil![0]
|
ucid = channel["xmlUrl"].match(/UC[a-zA-Z0-9_-]{22}/).not_nil![0]
|
||||||
|
|
||||||
if !user.subscriptions.includes? ucid
|
if !user.subscriptions.includes? ucid
|
||||||
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE id = $2", ucid, user.id)
|
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", ucid, user.email)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
@ -1053,7 +1083,7 @@ post "/data_control" do |env|
|
|||||||
ucid = md["channel_id"]
|
ucid = md["channel_id"]
|
||||||
|
|
||||||
if !user.subscriptions.includes? ucid
|
if !user.subscriptions.includes? ucid
|
||||||
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE id = $2", ucid, user.id)
|
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", ucid, user.email)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
@ -1069,7 +1099,7 @@ post "/data_control" do |env|
|
|||||||
ucid = channel["url"].as_s.match(/UC[a-zA-Z0-9_-]{22}/).not_nil![0]
|
ucid = channel["url"].as_s.match(/UC[a-zA-Z0-9_-]{22}/).not_nil![0]
|
||||||
|
|
||||||
if !user.subscriptions.includes? ucid
|
if !user.subscriptions.includes? ucid
|
||||||
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE id = $2", ucid, user.id)
|
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", ucid, user.email)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
@ -1090,14 +1120,14 @@ post "/data_control" do |env|
|
|||||||
db = entry.io.gets_to_end
|
db = entry.io.gets_to_end
|
||||||
db.scan(/youtube\.com\/watch\?v\=(?<id>[a-zA-Z0-9_-]{11})/) do |md|
|
db.scan(/youtube\.com\/watch\?v\=(?<id>[a-zA-Z0-9_-]{11})/) do |md|
|
||||||
if !user.watched.includes? md["id"]
|
if !user.watched.includes? md["id"]
|
||||||
PG_DB.exec("UPDATE users SET watched = array_append(watched,$1) WHERE id = $2", md["id"], user.id)
|
PG_DB.exec("UPDATE users SET watched = array_append(watched,$1) WHERE email = $2", md["id"], user.email)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
db.scan(/youtube\.com\/channel\/(?<ucid>[a-zA-Z0-9_-]{22})/) do |md|
|
db.scan(/youtube\.com\/channel\/(?<ucid>[a-zA-Z0-9_-]{22})/) do |md|
|
||||||
ucid = md["ucid"]
|
ucid = md["ucid"]
|
||||||
if !user.subscriptions.includes? ucid
|
if !user.subscriptions.includes? ucid
|
||||||
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE id = $2", ucid, user.id)
|
PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", ucid, user.email)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
@ -1197,7 +1227,7 @@ get "/clear_watch_history" do |env|
|
|||||||
if user
|
if user
|
||||||
user = user.as(User)
|
user = user.as(User)
|
||||||
|
|
||||||
PG_DB.exec("UPDATE users SET watched = '{}' WHERE id = $1", user.id)
|
PG_DB.exec("UPDATE users SET watched = '{}' WHERE email = $1", user.email)
|
||||||
end
|
end
|
||||||
|
|
||||||
env.redirect referer
|
env.redirect referer
|
||||||
@ -1242,21 +1272,21 @@ get "/feed/subscriptions" do |env|
|
|||||||
if preferences.notifications_only && !notifications.empty?
|
if preferences.notifications_only && !notifications.empty?
|
||||||
args = arg_array(notifications)
|
args = arg_array(notifications)
|
||||||
|
|
||||||
videos = PG_DB.query_all("SELECT * FROM channel_videos WHERE id IN (#{args})
|
notifications = PG_DB.query_all("SELECT * FROM channel_videos WHERE id IN (#{args})
|
||||||
ORDER BY published DESC", notifications, as: ChannelVideo)
|
ORDER BY published DESC", notifications, as: ChannelVideo)
|
||||||
notifications = [] of ChannelVideo
|
videos = [] of ChannelVideo
|
||||||
|
|
||||||
videos.sort_by! { |video| video.published }.reverse!
|
notifications.sort_by! { |video| video.published }.reverse!
|
||||||
|
|
||||||
case preferences.sort
|
case preferences.sort
|
||||||
when "alphabetically"
|
when "alphabetically"
|
||||||
videos.sort_by! { |video| video.title }
|
notifications.sort_by! { |video| video.title }
|
||||||
when "alphabetically - reverse"
|
when "alphabetically - reverse"
|
||||||
videos.sort_by! { |video| video.title }.reverse!
|
notifications.sort_by! { |video| video.title }.reverse!
|
||||||
when "channel name"
|
when "channel name"
|
||||||
videos.sort_by! { |video| video.author }
|
notifications.sort_by! { |video| video.author }
|
||||||
when "channel name - reverse"
|
when "channel name - reverse"
|
||||||
videos.sort_by! { |video| video.author }.reverse!
|
notifications.sort_by! { |video| video.author }.reverse!
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if preferences.latest_only
|
if preferences.latest_only
|
||||||
@ -1567,6 +1597,7 @@ get "/channel/:ucid" do |env|
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
videos = extract_playlist(ucid, page)
|
videos = extract_playlist(ucid, page)
|
||||||
|
videos.each { |a| a.playlists.clear }
|
||||||
rescue ex
|
rescue ex
|
||||||
error_message = ex.message
|
error_message = ex.message
|
||||||
next templated "error"
|
next templated "error"
|
||||||
@ -2170,10 +2201,16 @@ get "/api/v1/channels/:ucid" do |env|
|
|||||||
is_family_friendly = channel_html.xpath_node(%q(//meta[@itemprop="isFamilyFriendly"])).not_nil!["content"] == "True"
|
is_family_friendly = channel_html.xpath_node(%q(//meta[@itemprop="isFamilyFriendly"])).not_nil!["content"] == "True"
|
||||||
allowed_regions = channel_html.xpath_node(%q(//meta[@itemprop="regionsAllowed"])).not_nil!["content"].split(",")
|
allowed_regions = channel_html.xpath_node(%q(//meta[@itemprop="regionsAllowed"])).not_nil!["content"].split(",")
|
||||||
|
|
||||||
sub_count, total_views, joined = channel_html.xpath_nodes(%q(//span[@class="about-stat"]))
|
anchor = channel_html.xpath_nodes(%q(//span[@class="about-stat"]))
|
||||||
sub_count = sub_count.content.rchop(" subscribers").delete(",").to_i64
|
if anchor[0].content.includes? "views"
|
||||||
total_views = total_views.content.rchop(" views").lchop(" • ").delete(",").to_i64
|
sub_count = 0
|
||||||
joined = Time.parse(joined.content.lchop("Joined "), "%b %-d, %Y", Time::Location.local)
|
total_views = anchor[0].content.delete("views •,").to_i64
|
||||||
|
joined = Time.parse(anchor[1].content.lchop("Joined "), "%b %-d, %Y", Time::Location.local)
|
||||||
|
else
|
||||||
|
sub_count = anchor[0].content.delete("subscribers").delete(",").to_i64
|
||||||
|
total_views = anchor[1].content.delete("views •,").to_i64
|
||||||
|
joined = Time.parse(anchor[2].content.lchop("Joined "), "%b %-d, %Y", Time::Location.local)
|
||||||
|
end
|
||||||
|
|
||||||
latest_videos = PG_DB.query_all("SELECT * FROM channel_videos WHERE ucid = $1 ORDER BY published DESC LIMIT 15",
|
latest_videos = PG_DB.query_all("SELECT * FROM channel_videos WHERE ucid = $1 ORDER BY published DESC LIMIT 15",
|
||||||
channel.id, as: ChannelVideo)
|
channel.id, as: ChannelVideo)
|
||||||
@ -2359,7 +2396,7 @@ get "/api/v1/search" do |env|
|
|||||||
|
|
||||||
response = JSON.build do |json|
|
response = JSON.build do |json|
|
||||||
json.array do
|
json.array do
|
||||||
search_results = search(query, page, search_params)
|
count, search_results = search(query, page, search_params).as(Tuple)
|
||||||
search_results.each do |video|
|
search_results.each do |video|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "title", video.title
|
json.field "title", video.title
|
||||||
@ -2653,6 +2690,12 @@ get "/videoplayback" do |env|
|
|||||||
client = make_client(URI.parse(host))
|
client = make_client(URI.parse(host))
|
||||||
response = client.head(url)
|
response = client.head(url)
|
||||||
|
|
||||||
|
if response.headers["Location"]?
|
||||||
|
url = URI.parse(response.headers["Location"])
|
||||||
|
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
next env.redirect url.full_path
|
||||||
|
end
|
||||||
|
|
||||||
headers = env.request.headers
|
headers = env.request.headers
|
||||||
headers.delete("Host")
|
headers.delete("Host")
|
||||||
headers.delete("Cookie")
|
headers.delete("Cookie")
|
||||||
@ -2660,30 +2703,24 @@ get "/videoplayback" do |env|
|
|||||||
headers.delete("Referer")
|
headers.delete("Referer")
|
||||||
|
|
||||||
client.get(url, headers) do |response|
|
client.get(url, headers) do |response|
|
||||||
if response.headers["Location"]?
|
env.response.status_code = response.status_code
|
||||||
url = URI.parse(response.headers["Location"])
|
|
||||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
|
||||||
env.redirect url.full_path
|
|
||||||
else
|
|
||||||
env.response.status_code = response.status_code
|
|
||||||
|
|
||||||
response.headers.each do |key, value|
|
response.headers.each do |key, value|
|
||||||
env.response.headers[key] = value
|
env.response.headers[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
chunk_size = 4096
|
chunk_size = 4096
|
||||||
size = 1
|
size = 1
|
||||||
while size > 0
|
while size > 0
|
||||||
size = IO.copy(response.body_io, env.response.output, chunk_size)
|
size = IO.copy(response.body_io, env.response.output, chunk_size)
|
||||||
env.response.flush
|
env.response.flush
|
||||||
Fiber.yield
|
Fiber.yield
|
||||||
end
|
|
||||||
rescue ex
|
|
||||||
break
|
|
||||||
end
|
end
|
||||||
|
rescue ex
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -93,7 +93,7 @@ def template_youtube_comments(comments)
|
|||||||
<div class="pure-u-23-24">
|
<div class="pure-u-23-24">
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
||||||
onclick="load_comments(this)">View #{child["replies"]["replyCount"]} replies</a>
|
onclick="get_youtube_replies(this)">View #{child["replies"]["replyCount"]} replies</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -127,7 +127,7 @@ def template_youtube_comments(comments)
|
|||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
|
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
|
||||||
onclick="load_comments(this)">Load more</a>
|
onclick="get_youtube_replies(this)">Load more</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,13 +2,13 @@ def crawl_videos(db)
|
|||||||
ids = Deque(String).new
|
ids = Deque(String).new
|
||||||
random = Random.new
|
random = Random.new
|
||||||
|
|
||||||
search(random.base64(3)).each do |video|
|
search(random.base64(3)).as(Tuple)[1].each do |video|
|
||||||
ids << video.id
|
ids << video.id
|
||||||
end
|
end
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
if ids.empty?
|
if ids.empty?
|
||||||
search(random.base64(3)).each do |video|
|
search(random.base64(3)).as(Tuple)[1].each do |video|
|
||||||
ids << video.id
|
ids << video.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -14,21 +14,26 @@ end
|
|||||||
|
|
||||||
def search(query, page = 1, search_params = build_search_params(content_type: "video"))
|
def search(query, page = 1, search_params = build_search_params(content_type: "video"))
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
|
if query.empty?
|
||||||
|
return {0, [] of SearchVideo}
|
||||||
|
end
|
||||||
|
|
||||||
html = client.get("/results?q=#{URI.escape(query)}&page=#{page}&sp=#{search_params}&disable_polymer=1").body
|
html = client.get("/results?q=#{URI.escape(query)}&page=#{page}&sp=#{search_params}&disable_polymer=1").body
|
||||||
if html.empty?
|
if html.empty?
|
||||||
return [] of SearchVideo
|
return {0, [] of SearchVideo}
|
||||||
end
|
end
|
||||||
|
|
||||||
html = XML.parse_html(html)
|
html = XML.parse_html(html)
|
||||||
nodeset = html.xpath_nodes(%q(//ol[@class="item-section"]/li))
|
nodeset = html.xpath_nodes(%q(//ol[@class="item-section"]/li))
|
||||||
videos = extract_videos(nodeset)
|
videos = extract_videos(nodeset)
|
||||||
|
|
||||||
return videos
|
return {nodeset.size, videos}
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_search_params(sort_by = "relevance", date : String = "", content_type : String = "", duration : String = "", features : Array(String) = [] of String)
|
def build_search_params(sort : String = "relevance", date : String = "", content_type : String = "",
|
||||||
|
duration : String = "", features : Array(String) = [] of String)
|
||||||
head = "\x08"
|
head = "\x08"
|
||||||
head += case sort_by
|
head += case sort
|
||||||
when "relevance"
|
when "relevance"
|
||||||
"\x00"
|
"\x00"
|
||||||
when "rating"
|
when "rating"
|
||||||
@ -38,7 +43,7 @@ def build_search_params(sort_by = "relevance", date : String = "", content_type
|
|||||||
when "view_count"
|
when "view_count"
|
||||||
"\x03"
|
"\x03"
|
||||||
else
|
else
|
||||||
raise "No sort #{sort_by}"
|
raise "No sort #{sort}"
|
||||||
end
|
end
|
||||||
|
|
||||||
body = ""
|
body = ""
|
||||||
|
@ -3,7 +3,7 @@ def fetch_decrypt_function(client, id = "CvFH_6DNRCY")
|
|||||||
url = document.match(/src="(?<url>\/yts\/jsbin\/player-.{9}\/en_US\/base.js)"/).not_nil!["url"]
|
url = document.match(/src="(?<url>\/yts\/jsbin\/player-.{9}\/en_US\/base.js)"/).not_nil!["url"]
|
||||||
player = client.get(url).body
|
player = client.get(url).body
|
||||||
|
|
||||||
function_name = player.match(/\(b\|\|\(b="signature"\),d.set\(b,(?<name>[a-zA-Z0-9]{2})\(c\)\)\)/).not_nil!["name"]
|
function_name = player.match(/"signature",(?<name>[a-zA-Z0-9]{2})\(/).not_nil!["name"]
|
||||||
function_body = player.match(/#{function_name}=function\(a\){(?<body>[^}]+)}/).not_nil!["body"]
|
function_body = player.match(/#{function_name}=function\(a\){(?<body>[^}]+)}/).not_nil!["body"]
|
||||||
function_body = function_body.split(";")[1..-2]
|
function_body = function_body.split(";")[1..-2]
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ DEFAULT_USER_PREFERENCES = Preferences.from_json({
|
|||||||
"speed" => 1.0,
|
"speed" => 1.0,
|
||||||
"quality" => "hd720",
|
"quality" => "hd720",
|
||||||
"volume" => 100,
|
"volume" => 100,
|
||||||
"comments" => "youtube",
|
"comments" => ["youtube", ""],
|
||||||
"captions" => ["", "", ""],
|
"captions" => ["", "", ""],
|
||||||
"dark_mode" => false,
|
"dark_mode" => false,
|
||||||
"thin_mode " => false,
|
"thin_mode " => false,
|
||||||
@ -43,6 +43,29 @@ DEFAULT_USER_PREFERENCES = Preferences.from_json({
|
|||||||
}.to_json)
|
}.to_json)
|
||||||
|
|
||||||
class Preferences
|
class Preferences
|
||||||
|
module StringToArray
|
||||||
|
def self.to_json(value : Array(String), json : JSON::Builder)
|
||||||
|
json.array do
|
||||||
|
value.each do |element|
|
||||||
|
json.string element
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_json(value : JSON::PullParser) : Array(String)
|
||||||
|
begin
|
||||||
|
result = [] of String
|
||||||
|
value.read_array do
|
||||||
|
result << value.read_string
|
||||||
|
end
|
||||||
|
rescue ex
|
||||||
|
result = [value.read_string, ""]
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
JSON.mapping({
|
JSON.mapping({
|
||||||
video_loop: Bool,
|
video_loop: Bool,
|
||||||
autoplay: Bool,
|
autoplay: Bool,
|
||||||
@ -50,8 +73,9 @@ class Preferences
|
|||||||
quality: String,
|
quality: String,
|
||||||
volume: Int32,
|
volume: Int32,
|
||||||
comments: {
|
comments: {
|
||||||
type: String,
|
type: Array(String),
|
||||||
default: "youtube",
|
default: ["youtube", ""],
|
||||||
|
converter: StringToArray,
|
||||||
},
|
},
|
||||||
captions: {
|
captions: {
|
||||||
type: Array(String),
|
type: Array(String),
|
||||||
|
@ -504,16 +504,29 @@ end
|
|||||||
|
|
||||||
def process_video_params(query, preferences)
|
def process_video_params(query, preferences)
|
||||||
autoplay = query["autoplay"]?.try &.to_i?
|
autoplay = query["autoplay"]?.try &.to_i?
|
||||||
|
preferred_captions = query["subtitles"]?.try &.split(",").map { |a| a.downcase }
|
||||||
|
quality = query["quality"]?
|
||||||
|
speed = query["speed"]?.try &.to_f?
|
||||||
video_loop = query["loop"]?.try &.to_i?
|
video_loop = query["loop"]?.try &.to_i?
|
||||||
|
volume = query["volume"]?.try &.to_i?
|
||||||
|
|
||||||
if preferences
|
if preferences
|
||||||
autoplay ||= preferences.autoplay.to_unsafe
|
autoplay ||= preferences.autoplay.to_unsafe
|
||||||
|
preferred_captions ||= preferences.captions
|
||||||
|
quality ||= preferences.quality
|
||||||
|
speed ||= preferences.speed
|
||||||
video_loop ||= preferences.video_loop.to_unsafe
|
video_loop ||= preferences.video_loop.to_unsafe
|
||||||
|
volume ||= preferences.volume
|
||||||
end
|
end
|
||||||
autoplay ||= 0
|
|
||||||
autoplay = autoplay == 1
|
|
||||||
|
|
||||||
|
autoplay ||= 0
|
||||||
|
preferred_captions ||= [] of String
|
||||||
|
quality ||= "hd720"
|
||||||
|
speed ||= 1
|
||||||
video_loop ||= 0
|
video_loop ||= 0
|
||||||
|
volume ||= 100
|
||||||
|
|
||||||
|
autoplay = autoplay == 1
|
||||||
video_loop = video_loop == 1
|
video_loop = video_loop == 1
|
||||||
|
|
||||||
if query["t"]?
|
if query["t"]?
|
||||||
@ -542,14 +555,25 @@ def process_video_params(query, preferences)
|
|||||||
raw ||= 0
|
raw ||= 0
|
||||||
raw = raw == 1
|
raw = raw == 1
|
||||||
|
|
||||||
quality = query["quality"]?
|
|
||||||
quality ||= "hd720"
|
|
||||||
|
|
||||||
controls = query["controls"]?.try &.to_i?
|
controls = query["controls"]?.try &.to_i?
|
||||||
controls ||= 1
|
controls ||= 1
|
||||||
controls = controls == 1
|
controls = controls == 1
|
||||||
|
|
||||||
return autoplay, video_loop, video_start, video_end, listen, raw, quality, controls
|
params = {
|
||||||
|
autoplay: autoplay,
|
||||||
|
controls: controls,
|
||||||
|
listen: listen,
|
||||||
|
preferred_captions: preferred_captions,
|
||||||
|
quality: quality,
|
||||||
|
raw: raw,
|
||||||
|
speed: speed,
|
||||||
|
video_end: video_end,
|
||||||
|
video_loop: video_loop,
|
||||||
|
video_start: video_start,
|
||||||
|
volume: volume,
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_thumbnails(json, id)
|
def generate_thumbnails(json, id)
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<video style="width:100%" playsinline poster="<%= thumbnail %>" title="<%= HTML.escape(video.title) %>"
|
<video style="width:100%" playsinline poster="<%= thumbnail %>" title="<%= HTML.escape(video.title) %>"
|
||||||
id="player" class="video-js"
|
id="player" class="video-js"
|
||||||
<% if autoplay %>autoplay<% end %>
|
<% if params[:autoplay] %>autoplay<% end %>
|
||||||
<% if video_loop %>loop<% end %>
|
<% if params[:video_loop] %>loop<% end %>
|
||||||
<% if controls %>controls<% end %>>
|
<% if params[:controls] %>controls<% end %>>
|
||||||
<% if hlsvp %>
|
<% if hlsvp %>
|
||||||
<source src="<%= hlsvp %>" type="application/x-mpegURL">
|
<source src="<%= hlsvp %>" type="application/x-mpegURL">
|
||||||
<% else %>
|
<% else %>
|
||||||
<% if listen %>
|
<% if params[:listen] %>
|
||||||
<% audio_streams.each_with_index do |fmt, i| %>
|
<% audio_streams.each_with_index do |fmt, i| %>
|
||||||
<source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["bitrate"] %>k" selected="<%= i == 0 ? true : false %>">
|
<source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["bitrate"] %>k" selected="<%= i == 0 ? true : false %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% fmt_stream.each_with_index do |fmt, i| %>
|
<% fmt_stream.each_with_index do |fmt, i| %>
|
||||||
<% if preferences %>
|
<% if params[:quality] %>
|
||||||
<source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["label"] %>" selected="<%= preferences.quality == fmt["label"].split(" - ")[0] %>">
|
<source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["label"] %>" selected="<%= params[:quality] == fmt["label"].split(" - ")[0] %>">
|
||||||
<% else %>
|
<% else %>
|
||||||
<source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["label"] %>" selected="<%= i == 0 ? true : false %>">
|
<source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["label"] %>" selected="<%= i == 0 ? true : false %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -110,7 +110,7 @@ var player = videojs("player", options, function() {
|
|||||||
|
|
||||||
player.share(shareOptions);
|
player.share(shareOptions);
|
||||||
|
|
||||||
<% if video_start > 0 || video_end > 0 %>
|
<% if params[:video_start] > 0 || params[:video_end] > 0 %>
|
||||||
player.markers({
|
player.markers({
|
||||||
onMarkerReached: function(marker) {
|
onMarkerReached: function(marker) {
|
||||||
if (marker.text === "End") {
|
if (marker.text === "End") {
|
||||||
@ -122,19 +122,19 @@ player.markers({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
markers: [
|
markers: [
|
||||||
{ time: <%= video_start %>, text: "Start" },
|
{ time: <%= params[:video_start] %>, text: "Start" },
|
||||||
<% if video_end < 0 %>
|
<% if params[:video_end] < 0 %>
|
||||||
{ time: <%= video.info["length_seconds"].to_f - 0.5 %>, text: "End" }
|
{ time: <%= video.info["length_seconds"].to_f - 0.5 %>, text: "End" }
|
||||||
<% else %>
|
<% else %>
|
||||||
{ time: <%= video_end %>, text: "End" }
|
{ time: <%= params[:video_end] %>, text: "End" }
|
||||||
<% end %>
|
<% end %>
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
player.currentTime(<%= video_start %>);
|
player.currentTime(<%= params[:video_start] %>);
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if !listen %>
|
<% if !params[:listen] %>
|
||||||
var currentSources = player.currentSources();
|
var currentSources = player.currentSources();
|
||||||
for (var i = 0; i < currentSources.length; i++) {
|
for (var i = 0; i < currentSources.length; i++) {
|
||||||
if (player.canPlayType(currentSources[i]["type"].split(";")[0]) === "") {
|
if (player.canPlayType(currentSources[i]["type"].split(";")[0]) === "") {
|
||||||
@ -146,8 +146,6 @@ for (var i = 0; i < currentSources.length; i++) {
|
|||||||
player.src(currentSources);
|
player.src(currentSources);
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if preferences %>
|
player.volume(<%= params[:volume].to_f / 100 %>);
|
||||||
player.volume(<%= preferences.volume.to_f / 100 %>);
|
player.playbackRate(<%= params[:speed] %>);
|
||||||
player.playbackRate(<%= preferences.speed %>);
|
|
||||||
<% end %>
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="pure-u-1 pure-u-md-1-4">
|
<div class="pure-u-1 pure-u-md-1-4">
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<% if video.responds_to?(:playlists) %>
|
<% if video.responds_to?(:playlists) && !video.playlists.empty? %>
|
||||||
<% params = "&list=#{video.playlists[0]}" %>
|
<% params = "&list=#{video.playlists[0]}" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% params = nil %>
|
<% params = nil %>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<form class="pure-form pure-form-aligned" enctype="multipart/form-data" action="/data_control" method="post">
|
<form class="pure-form pure-form-aligned" enctype="multipart/form-data" action="/data_control?referer=<%= referer %>" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Import</legend>
|
<legend>Import</legend>
|
||||||
|
|
||||||
|
@ -48,10 +48,19 @@ function update_value(element) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<label for="comments">Pull comments from: </label>
|
<label for="comments_0">Default comments: </label>
|
||||||
<select name="comments" id="comments">
|
<select name="comments_0" id="comments_0">
|
||||||
<% {"youtube", "reddit"}.each do |option| %>
|
<% {"", "youtube", "reddit"}.each do |option| %>
|
||||||
<option <% if user.preferences.comments == option %> selected <% end %>><%= option %></option>
|
<option <% if user.preferences.comments[0] == option %> selected <% end %>><%= option %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-control-group">
|
||||||
|
<label for="comments_1">Fallback comments: </label>
|
||||||
|
<select name="comments_1" id="comments_1">
|
||||||
|
<% {"", "youtube", "reddit"}.each do |option| %>
|
||||||
|
<option <% if user.preferences.comments[1] == option %> selected <% end %>><%= option %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -128,11 +137,11 @@ function update_value(element) {
|
|||||||
|
|
||||||
<legend>Data preferences</legend>
|
<legend>Data preferences</legend>
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/clear_watch_history">Clear watch history</a>
|
<a href="/clear_watch_history?referer=<%= referer %>">Clear watch history</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
<a href="/data_control">Import/Export data</a>
|
<a href="/data_control?referer=<%= referer %>">Import/Export data</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-3-5"></div>
|
<div class="pure-u-1 pure-u-md-3-5"></div>
|
||||||
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
<div style="text-align:right;" class="pure-u-1 pure-u-md-1-5">
|
||||||
|
<% if count == 20 %>
|
||||||
<a href="/search?q=<%= query %>&page=<%= page + 1 %>">Next page</a>
|
<a href="/search?q=<%= query %>&page=<%= page + 1 %>">Next page</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3" style="text-align:right;">
|
<div class="pure-u-1-3" style="text-align:right;">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="/data_control">Import/Export</a>
|
<a href="/data_control?referer=<%= referer %>">Import/Export</a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,6 +72,11 @@
|
|||||||
Roth</a>.
|
Roth</a>.
|
||||||
Source available <a
|
Source available <a
|
||||||
href="https://github.com/omarroth/invidious">here</a>.
|
href="https://github.com/omarroth/invidious">here</a>.
|
||||||
|
<p>Liberapay:
|
||||||
|
<a href="https://liberapay.com/omarroth">
|
||||||
|
https://liberapay.com/omarroth
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
<p>Patreon:
|
<p>Patreon:
|
||||||
<a href="https://patreon.com/omarroth">
|
<a href="https://patreon.com/omarroth">
|
||||||
https://patreon.com/omarroth
|
https://patreon.com/omarroth
|
||||||
|
@ -30,163 +30,10 @@
|
|||||||
<%= rendered "components/player" %>
|
<%= rendered "components/player" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function toggle(target) {
|
|
||||||
body = target.parentNode.parentNode.children[1];
|
|
||||||
if (body.style.display === null || body.style.display === "") {
|
|
||||||
target.innerHTML = "[ + ]";
|
|
||||||
body.style.display = "none";
|
|
||||||
} else {
|
|
||||||
target.innerHTML = "[ - ]";
|
|
||||||
body.style.display = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_comments(target) {
|
|
||||||
body = target.parentNode.parentNode.parentNode.children[1];
|
|
||||||
if (body.style.display === null || body.style.display === "") {
|
|
||||||
target.innerHTML = "[ + ]";
|
|
||||||
body.style.display = "none";
|
|
||||||
} else {
|
|
||||||
target.innerHTML = "[ - ]";
|
|
||||||
body.style.display = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function load_comments(target) {
|
|
||||||
var continuation = target.getAttribute("data-continuation");
|
|
||||||
|
|
||||||
var body = target.parentNode.parentNode;
|
|
||||||
var fallback = body.innerHTML;
|
|
||||||
body.innerHTML =
|
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
|
||||||
|
|
||||||
var url =
|
|
||||||
"/api/v1/comments/<%= video.id %>?format=html&continuation=" + continuation;
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.responseType = "json";
|
|
||||||
xhr.timeout = 20000;
|
|
||||||
xhr.open("GET", url, true);
|
|
||||||
xhr.send();
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
|
||||||
if (xhr.readyState == 4) {
|
|
||||||
if (xhr.status == 200) {
|
|
||||||
body.innerHTML = xhr.response.contentHtml;
|
|
||||||
} else {
|
|
||||||
body.innerHTML = fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
|
||||||
body.innerHTML = fallback;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_reddit_comments() {
|
|
||||||
var url = "/api/v1/comments/<%= video.id %>?source=reddit";
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.responseType = "json";
|
|
||||||
xhr.timeout = 20000;
|
|
||||||
xhr.open("GET", url, true);
|
|
||||||
xhr.send();
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
|
||||||
if (xhr.readyState == 4)
|
|
||||||
if (xhr.status == 200) {
|
|
||||||
comments = document.getElementById("comments");
|
|
||||||
comments.innerHTML = `
|
|
||||||
<div>
|
|
||||||
<h3>
|
|
||||||
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a>
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<b>
|
|
||||||
<a target="_blank" href="https://reddit.com{permalink}">View more comments on Reddit</a>
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
<div>{contentHtml}</div>
|
|
||||||
|
|
||||||
<hr>`.supplant({
|
|
||||||
title: xhr.response.title,
|
|
||||||
permalink: xhr.response.permalink,
|
|
||||||
contentHtml: xhr.response.contentHtml
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
get_youtube_comments();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
|
||||||
get_reddit_comments();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_youtube_comments() {
|
|
||||||
var url = "/api/v1/comments/<%= video.id %>?format=html";
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.responseType = "json";
|
|
||||||
xhr.timeout = 20000;
|
|
||||||
xhr.open("GET", url, true);
|
|
||||||
xhr.send();
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
|
||||||
if (xhr.readyState == 4)
|
|
||||||
if (xhr.status == 200) {
|
|
||||||
comments = document.getElementById("comments");
|
|
||||||
comments.innerHTML = `
|
|
||||||
<div>
|
|
||||||
<h3>
|
|
||||||
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a>
|
|
||||||
View {commentCount} comments
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div>{contentHtml}</div>
|
|
||||||
<hr>`.supplant({
|
|
||||||
contentHtml: xhr.response.contentHtml,
|
|
||||||
commentCount: commaSeparateNumber(xhr.response.commentCount)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
comments = document.getElementById("comments");
|
|
||||||
comments.innerHTML = "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
|
||||||
comments = document.getElementById("comments");
|
|
||||||
comments.innerHTML =
|
|
||||||
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
|
||||||
get_youtube_comments();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function commaSeparateNumber(val){
|
|
||||||
while (/(\d+)(\d{3})/.test(val.toString())){
|
|
||||||
val = val.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
String.prototype.supplant = function(o) {
|
|
||||||
return this.replace(/{([^{}]*)}/g, function(a, b) {
|
|
||||||
var r = o[b];
|
|
||||||
return typeof r === "string" || typeof r === "number" ? r : a;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
<% if preferences && preferences.comments == "reddit" %>
|
|
||||||
get_reddit_comments();
|
|
||||||
<% else %>
|
|
||||||
get_youtube_comments();
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<h1>
|
<h1>
|
||||||
<%= HTML.escape(video.title) %>
|
<%= HTML.escape(video.title) %>
|
||||||
<% if listen %>
|
<% if params[:listen] %>
|
||||||
<a href="/watch?<%= env.params.query %>">
|
<a href="/watch?<%= env.params.query %>">
|
||||||
<i class="icon ion-ios-videocam"></i>
|
<i class="icon ion-ios-videocam"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -284,3 +131,184 @@ get_youtube_comments();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggle(target) {
|
||||||
|
body = target.parentNode.parentNode.children[1];
|
||||||
|
if (body.style.display === null || body.style.display === "") {
|
||||||
|
target.innerHTML = "[ + ]";
|
||||||
|
body.style.display = "none";
|
||||||
|
} else {
|
||||||
|
target.innerHTML = "[ - ]";
|
||||||
|
body.style.display = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_comments(target) {
|
||||||
|
body = target.parentNode.parentNode.parentNode.children[1];
|
||||||
|
if (body.style.display === null || body.style.display === "") {
|
||||||
|
target.innerHTML = "[ + ]";
|
||||||
|
body.style.display = "none";
|
||||||
|
} else {
|
||||||
|
target.innerHTML = "[ - ]";
|
||||||
|
body.style.display = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_replies(target) {
|
||||||
|
var continuation = target.getAttribute("data-continuation");
|
||||||
|
|
||||||
|
var body = target.parentNode.parentNode;
|
||||||
|
var fallback = body.innerHTML;
|
||||||
|
body.innerHTML =
|
||||||
|
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
||||||
|
|
||||||
|
var url =
|
||||||
|
"/api/v1/comments/<%= video.id %>?format=html&continuation=" + continuation;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = "json";
|
||||||
|
xhr.timeout = 20000;
|
||||||
|
xhr.open("GET", url, true);
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
body.innerHTML = xhr.response.contentHtml;
|
||||||
|
} else {
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
console.log("Pulling comments timed out.");
|
||||||
|
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_reddit_comments() {
|
||||||
|
var url = "/api/v1/comments/<%= video.id %>?source=reddit";
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = "json";
|
||||||
|
xhr.timeout = 20000;
|
||||||
|
xhr.open("GET", url, true);
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState == 4)
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
comments = document.getElementById("comments");
|
||||||
|
comments.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<h3>
|
||||||
|
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a>
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<b>
|
||||||
|
<a target="_blank" href="https://reddit.com{permalink}">View more comments on Reddit</a>
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
<div>{contentHtml}</div>
|
||||||
|
|
||||||
|
<hr>`.supplant({
|
||||||
|
title: xhr.response.title,
|
||||||
|
permalink: xhr.response.permalink,
|
||||||
|
contentHtml: xhr.response.contentHtml
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
<% if preferences && preferences.comments[1] == "youtube" %>
|
||||||
|
get_youtube_comments();
|
||||||
|
<% else %>
|
||||||
|
comments = document.getElementById("comments");
|
||||||
|
comments.innerHTML = "";
|
||||||
|
<% end %>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
console.log("Pulling comments timed out.");
|
||||||
|
|
||||||
|
get_reddit_comments();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_comments() {
|
||||||
|
var url = "/api/v1/comments/<%= video.id %>?format=html";
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = "json";
|
||||||
|
xhr.timeout = 20000;
|
||||||
|
xhr.open("GET", url, true);
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState == 4)
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
comments = document.getElementById("comments");
|
||||||
|
comments.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<h3>
|
||||||
|
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a>
|
||||||
|
View {commentCount} comments
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div>{contentHtml}</div>
|
||||||
|
<hr>`.supplant({
|
||||||
|
contentHtml: xhr.response.contentHtml,
|
||||||
|
commentCount: commaSeparateNumber(xhr.response.commentCount)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
<% if preferences && preferences.comments[1] == "youtube" %>
|
||||||
|
get_youtube_comments();
|
||||||
|
<% else %>
|
||||||
|
comments = document.getElementById("comments");
|
||||||
|
comments.innerHTML = "";
|
||||||
|
<% end %>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
console.log("Pulling comments timed out.");
|
||||||
|
|
||||||
|
comments = document.getElementById("comments");
|
||||||
|
comments.innerHTML =
|
||||||
|
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>';
|
||||||
|
get_youtube_comments();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function commaSeparateNumber(val){
|
||||||
|
while (/(\d+)(\d{3})/.test(val.toString())){
|
||||||
|
val = val.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.supplant = function(o) {
|
||||||
|
return this.replace(/{([^{}]*)}/g, function(a, b) {
|
||||||
|
var r = o[b];
|
||||||
|
return typeof r === "string" || typeof r === "number" ? r : a;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
<% if preferences %>
|
||||||
|
<% if preferences.comments[0] == "youtube" %>
|
||||||
|
get_youtube_comments();
|
||||||
|
<% elsif preferences.comments[0] == "reddit" %>
|
||||||
|
get_reddit_comments();
|
||||||
|
<% else %>
|
||||||
|
<% if preferences.comments[1] == "youtube" %>
|
||||||
|
get_youtube_comments();
|
||||||
|
<% elsif preferences.comments[1] == "reddit" %>
|
||||||
|
get_reddit_comments();
|
||||||
|
<% else %>
|
||||||
|
comments = document.getElementById("comments");
|
||||||
|
comments.innerHTML = "";
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
get_youtube_comments();
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
</script>
|
Reference in New Issue
Block a user