forked from midou/invidious
Add community page
This commit is contained in:
parent
2cc25b1e6e
commit
bcd239ac2b
101
assets/js/community.js
Normal file
101
assets/js/community.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide_youtube_replies(event) {
|
||||||
|
var target = event.target;
|
||||||
|
|
||||||
|
sub_text = target.getAttribute('data-inner-text');
|
||||||
|
inner_text = target.getAttribute('data-sub-text');
|
||||||
|
|
||||||
|
body = target.parentNode.parentNode.children[1];
|
||||||
|
body.style.display = 'none';
|
||||||
|
|
||||||
|
target.innerHTML = sub_text;
|
||||||
|
target.onclick = show_youtube_replies;
|
||||||
|
target.setAttribute('data-inner-text', inner_text);
|
||||||
|
target.setAttribute('data-sub-text', sub_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_youtube_replies(event) {
|
||||||
|
var target = event.target;
|
||||||
|
|
||||||
|
sub_text = target.getAttribute('data-inner-text');
|
||||||
|
inner_text = target.getAttribute('data-sub-text');
|
||||||
|
|
||||||
|
body = target.parentNode.parentNode.children[1];
|
||||||
|
body.style.display = '';
|
||||||
|
|
||||||
|
target.innerHTML = sub_text;
|
||||||
|
target.onclick = hide_youtube_replies;
|
||||||
|
target.setAttribute('data-inner-text', inner_text);
|
||||||
|
target.setAttribute('data-sub-text', sub_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function number_with_separator(val) {
|
||||||
|
while (/(\d+)(\d{3})/.test(val.toString())) {
|
||||||
|
val = val.toString().replace(/(\d+)(\d{3})/, '$1' + ',' + '$2');
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_replies(target, load_more) {
|
||||||
|
var continuation = target.getAttribute('data-continuation');
|
||||||
|
|
||||||
|
var body = target.parentNode.parentNode;
|
||||||
|
var fallback = body.innerHTML;
|
||||||
|
body.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
|
var url = '/api/v1/channels/comments/' + community_data.ucid +
|
||||||
|
'?format=html' +
|
||||||
|
'&hl=' + community_data.preferences.locale +
|
||||||
|
'&thin_mode=' + community_data.preferences.thin_mode +
|
||||||
|
'&continuation=' + continuation;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
if (load_more) {
|
||||||
|
body = body.parentNode.parentNode;
|
||||||
|
body.removeChild(body.lastElementChild);
|
||||||
|
body.innerHTML += xhr.response.contentHtml;
|
||||||
|
} else {
|
||||||
|
body.removeChild(body.lastElementChild);
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
var a = document.createElement('a');
|
||||||
|
p.appendChild(a);
|
||||||
|
|
||||||
|
a.href = 'javascript:void(0)';
|
||||||
|
a.onclick = hide_youtube_replies;
|
||||||
|
a.setAttribute('data-sub-text', community_data.hide_replies_text);
|
||||||
|
a.setAttribute('data-inner-text', community_data.show_replies_text);
|
||||||
|
a.innerText = community_data.hide_replies_text;
|
||||||
|
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = xhr.response.contentHtml;
|
||||||
|
|
||||||
|
body.appendChild(p);
|
||||||
|
body.appendChild(div);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Pulling comments failed.');
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "وضع الفيديو",
|
"Video mode": "وضع الفيديو",
|
||||||
"Videos": "الفيديوهات",
|
"Videos": "الفيديوهات",
|
||||||
"Playlists": "قوائم التشغيل",
|
"Playlists": "قوائم التشغيل",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "الإصدار الحالى"
|
"Current version: ": "الإصدار الحالى"
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Videomodus",
|
"Video mode": "Videomodus",
|
||||||
"Videos": "Videos",
|
"Videos": "Videos",
|
||||||
"Playlists": "Wiedergabelisten",
|
"Playlists": "Wiedergabelisten",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Aktuelle Version: "
|
"Current version: ": "Aktuelle Version: "
|
||||||
}
|
}
|
@ -361,5 +361,6 @@
|
|||||||
"Video mode": "Λειτουργία βίντεο",
|
"Video mode": "Λειτουργία βίντεο",
|
||||||
"Videos": "Βίντεο",
|
"Videos": "Βίντεο",
|
||||||
"Playlists": "Λίστες Αναπαραγωγής",
|
"Playlists": "Λίστες Αναπαραγωγής",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Τρέχουσα έκδοση: "
|
"Current version: ": "Τρέχουσα έκδοση: "
|
||||||
}
|
}
|
@ -361,5 +361,6 @@
|
|||||||
"Video mode": "Video mode",
|
"Video mode": "Video mode",
|
||||||
"Videos": "Videos",
|
"Videos": "Videos",
|
||||||
"Playlists": "Playlists",
|
"Playlists": "Playlists",
|
||||||
|
"Community": "Community",
|
||||||
"Current version: ": "Current version: "
|
"Current version: ": "Current version: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Videa reĝimo",
|
"Video mode": "Videa reĝimo",
|
||||||
"Videos": "Videoj",
|
"Videos": "Videoj",
|
||||||
"Playlists": "Ludlistoj",
|
"Playlists": "Ludlistoj",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Nuna versio: "
|
"Current version: ": "Nuna versio: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Modo de vídeo",
|
"Video mode": "Modo de vídeo",
|
||||||
"Videos": "Vídeos",
|
"Videos": "Vídeos",
|
||||||
"Playlists": "Listas de reproducción",
|
"Playlists": "Listas de reproducción",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Versión actual: "
|
"Current version: ": "Versión actual: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Mode Vidéo",
|
"Video mode": "Mode Vidéo",
|
||||||
"Videos": "Vidéos",
|
"Videos": "Vidéos",
|
||||||
"Playlists": "Liste de lecture",
|
"Playlists": "Liste de lecture",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Version actuelle : "
|
"Current version: ": "Version actuelle : "
|
||||||
}
|
}
|
@ -315,5 +315,6 @@
|
|||||||
"Video mode": "Modalità video",
|
"Video mode": "Modalità video",
|
||||||
"Videos": "",
|
"Videos": "",
|
||||||
"Playlists": "",
|
"Playlists": "",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": ""
|
"Current version: ": ""
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Video-modus",
|
"Video mode": "Video-modus",
|
||||||
"Videos": "Videoer",
|
"Videos": "Videoer",
|
||||||
"Playlists": "Spillelister",
|
"Playlists": "Spillelister",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Nåværende versjon: "
|
"Current version: ": "Nåværende versjon: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Videomodus",
|
"Video mode": "Videomodus",
|
||||||
"Videos": "Video's",
|
"Videos": "Video's",
|
||||||
"Playlists": "Afspeellijsten",
|
"Playlists": "Afspeellijsten",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Huidige versie: "
|
"Current version: ": "Huidige versie: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Tryb wideo",
|
"Video mode": "Tryb wideo",
|
||||||
"Videos": "Filmy",
|
"Videos": "Filmy",
|
||||||
"Playlists": "Playlisty",
|
"Playlists": "Playlisty",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Aktualna wersja: "
|
"Current version: ": "Aktualna wersja: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Видео режим",
|
"Video mode": "Видео режим",
|
||||||
"Videos": "Видео",
|
"Videos": "Видео",
|
||||||
"Playlists": "Плейлисты",
|
"Playlists": "Плейлисты",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Текущая версия: "
|
"Current version: ": "Текущая версия: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "Відеорежим",
|
"Video mode": "Відеорежим",
|
||||||
"Videos": "Відео",
|
"Videos": "Відео",
|
||||||
"Playlists": "Плейлисти",
|
"Playlists": "Плейлисти",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "Поточна версія: "
|
"Current version: ": "Поточна версія: "
|
||||||
}
|
}
|
@ -316,5 +316,6 @@
|
|||||||
"Video mode": "视频模式",
|
"Video mode": "视频模式",
|
||||||
"Videos": "视频",
|
"Videos": "视频",
|
||||||
"Playlists": "播放列表",
|
"Playlists": "播放列表",
|
||||||
|
"Community": "",
|
||||||
"Current version: ": "当前版本:"
|
"Current version: ": "当前版本:"
|
||||||
}
|
}
|
@ -2861,6 +2861,16 @@ get "/user/:user/videos" do |env|
|
|||||||
env.redirect "/channel/#{user}/videos"
|
env.redirect "/channel/#{user}/videos"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get "/user/:user/about" do |env|
|
||||||
|
user = env.params.url["user"]
|
||||||
|
env.redirect "/channel/#{user}"
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/channel:ucid/about" do |env|
|
||||||
|
ucid = env.params.url["ucid"]
|
||||||
|
env.redirect "/channel/#{ucid}"
|
||||||
|
end
|
||||||
|
|
||||||
get "/channel/:ucid" do |env|
|
get "/channel/:ucid" do |env|
|
||||||
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
|
||||||
|
|
||||||
@ -2968,6 +2978,46 @@ get "/channel/:ucid/playlists" do |env|
|
|||||||
templated "playlists"
|
templated "playlists"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get "/channel/:ucid/community" do |env|
|
||||||
|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
|
||||||
|
|
||||||
|
user = env.get? "user"
|
||||||
|
if user
|
||||||
|
user = user.as(User)
|
||||||
|
subscriptions = user.subscriptions
|
||||||
|
end
|
||||||
|
subscriptions ||= [] of String
|
||||||
|
|
||||||
|
ucid = env.params.url["ucid"]
|
||||||
|
|
||||||
|
thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode
|
||||||
|
thin_mode = thin_mode == "true"
|
||||||
|
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
# sort_by = env.params.query["sort_by"]?.try &.downcase
|
||||||
|
|
||||||
|
begin
|
||||||
|
channel = get_about_info(ucid, locale)
|
||||||
|
rescue ex
|
||||||
|
error_message = ex.message
|
||||||
|
env.response.status_code = 500
|
||||||
|
next templated "error"
|
||||||
|
end
|
||||||
|
|
||||||
|
if !channel.tabs.includes? "community"
|
||||||
|
next env.redirect "/channel/#{channel.ucid}"
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
items = JSON.parse(fetch_channel_community(ucid, continuation, locale, config, Kemal.config, "json", thin_mode))
|
||||||
|
rescue ex
|
||||||
|
env.response.status_code = 500
|
||||||
|
error_message = ex.message
|
||||||
|
end
|
||||||
|
|
||||||
|
templated "community"
|
||||||
|
end
|
||||||
|
|
||||||
# API Endpoints
|
# API Endpoints
|
||||||
|
|
||||||
get "/api/v1/stats" do |env|
|
get "/api/v1/stats" do |env|
|
||||||
@ -3757,12 +3807,17 @@ end
|
|||||||
|
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
|
|
||||||
continuation = env.params.query["continuation"]?
|
thin_mode = env.params.query["thin_mode"]?
|
||||||
|
thin_mode = thin_mode == "true"
|
||||||
|
|
||||||
|
format = env.params.query["format"]?
|
||||||
|
format ||= "json"
|
||||||
|
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
# sort_by = env.params.query["sort_by"]?.try &.downcase
|
# sort_by = env.params.query["sort_by"]?.try &.downcase
|
||||||
|
|
||||||
begin
|
begin
|
||||||
fetch_channel_community(ucid, continuation, locale, config, Kemal.config)
|
fetch_channel_community(ucid, continuation, locale, config, Kemal.config, format, thin_mode)
|
||||||
rescue ex
|
rescue ex
|
||||||
env.response.status_code = 400
|
env.response.status_code = 400
|
||||||
error_message = {"error" => ex.message}.to_json
|
error_message = {"error" => ex.message}.to_json
|
||||||
|
@ -123,6 +123,7 @@ struct AboutChannel
|
|||||||
is_family_friendly: Bool,
|
is_family_friendly: Bool,
|
||||||
allowed_regions: Array(String),
|
allowed_regions: Array(String),
|
||||||
related_channels: Array(AboutRelatedChannel),
|
related_channels: Array(AboutRelatedChannel),
|
||||||
|
tabs: Array(String),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -617,7 +618,7 @@ def extract_channel_playlists_cursor(url, auto_generated)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Add "sort_by"
|
# TODO: Add "sort_by"
|
||||||
def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
def fetch_channel_community(ucid, continuation, locale, config, kemal_config, format, thin_mode)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
headers = HTTP::Headers.new
|
headers = HTTP::Headers.new
|
||||||
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
|
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
|
||||||
@ -632,11 +633,10 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||||||
raise error_message
|
raise error_message
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ucid = response.body.match(/https:\/\/www.youtube.com\/channel\/(?<ucid>UC[a-zA-Z0-9_-]{22})/).not_nil!["ucid"]
|
||||||
|
|
||||||
if !continuation || continuation.empty?
|
if !continuation || continuation.empty?
|
||||||
response = JSON.parse(response.body.match(/window\["ytInitialData"\] = (?<info>.*?);\n/).try &.["info"] || "{}")
|
response = JSON.parse(response.body.match(/window\["ytInitialData"\] = (?<info>.*?);\n/).try &.["info"] || "{}")
|
||||||
ucid = response["responseContext"]["serviceTrackingParams"]
|
|
||||||
.as_a.select { |service| service["service"] == "GFEEDBACK" }[0]?.try &.["params"]
|
|
||||||
.as_a.select { |param| param["key"] == "browse_id" }[0]?.try &.["value"].as_s
|
|
||||||
body = response["contents"]?.try &.["twoColumnBrowseResultsRenderer"]["tabs"].as_a.select { |tab| tab["tabRenderer"]?.try &.["selected"].as_bool.== true }[0]?
|
body = response["contents"]?.try &.["twoColumnBrowseResultsRenderer"]["tabs"].as_a.select { |tab| tab["tabRenderer"]?.try &.["selected"].as_bool.== true }[0]?
|
||||||
|
|
||||||
if !body
|
if !body
|
||||||
@ -645,6 +645,8 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||||||
|
|
||||||
body = body["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]
|
body = body["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]
|
||||||
else
|
else
|
||||||
|
continuation = produce_channel_community_continuation(ucid, continuation)
|
||||||
|
|
||||||
headers["cookie"] = response.cookies.add_request_headers(headers)["cookie"]
|
headers["cookie"] = response.cookies.add_request_headers(headers)["cookie"]
|
||||||
headers["content-type"] = "application/x-www-form-urlencoded"
|
headers["content-type"] = "application/x-www-form-urlencoded"
|
||||||
|
|
||||||
@ -663,10 +665,6 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||||||
response = client.post("/comment_service_ajax?action_get_comments=1&ctoken=#{continuation}&continuation=#{continuation}&hl=en&gl=US", headers, form: post_req)
|
response = client.post("/comment_service_ajax?action_get_comments=1&ctoken=#{continuation}&continuation=#{continuation}&hl=en&gl=US", headers, form: post_req)
|
||||||
body = JSON.parse(response.body)
|
body = JSON.parse(response.body)
|
||||||
|
|
||||||
ucid = body["response"]["responseContext"]["serviceTrackingParams"]
|
|
||||||
.as_a.select { |service| service["service"] == "GFEEDBACK" }[0]?.try &.["params"]
|
|
||||||
.as_a.select { |param| param["key"] == "browse_id" }[0]?.try &.["value"].as_s
|
|
||||||
|
|
||||||
body = body["response"]["continuationContents"]["itemSectionContinuation"]? ||
|
body = body["response"]["continuationContents"]["itemSectionContinuation"]? ||
|
||||||
body["response"]["continuationContents"]["backstageCommentsContinuation"]?
|
body["response"]["continuationContents"]["backstageCommentsContinuation"]?
|
||||||
|
|
||||||
@ -685,7 +683,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||||||
raise error_message
|
raise error_message
|
||||||
end
|
end
|
||||||
|
|
||||||
JSON.build do |json|
|
response = JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "authorId", ucid
|
json.field "authorId", ucid
|
||||||
json.field "comments" do
|
json.field "comments" do
|
||||||
@ -755,6 +753,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||||||
|
|
||||||
json.field "likeCount", like_count
|
json.field "likeCount", like_count
|
||||||
json.field "commentId", post["postId"]? || post["commentId"]? || ""
|
json.field "commentId", post["postId"]? || post["commentId"]? || ""
|
||||||
|
json.field "authorIsChannelOwner", post["authorEndpoint"]["browseEndpoint"]["browseId"] == ucid
|
||||||
|
|
||||||
if attachment = post["backstageAttachment"]?
|
if attachment = post["backstageAttachment"]?
|
||||||
json.field "attachment" do
|
json.field "attachment" do
|
||||||
@ -837,7 +836,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||||||
json.field "replies" do
|
json.field "replies" do
|
||||||
json.object do
|
json.object do
|
||||||
json.field "replyCount", reply_count
|
json.field "replyCount", reply_count
|
||||||
json.field "continuation", continuation
|
json.field "continuation", extract_channel_community_cursor(continuation)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -847,11 +846,71 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if body["continuations"]?
|
if body["continuations"]?
|
||||||
continuation = body["continuations"][0]["nextContinuationData"]["continuation"]
|
continuation = body["continuations"][0]["nextContinuationData"]["continuation"].as_s
|
||||||
json.field "continuation", continuation
|
json.field "continuation", extract_channel_community_cursor(continuation)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if format == "html"
|
||||||
|
response = JSON.parse(response)
|
||||||
|
content_html = template_youtube_comments(response, locale, thin_mode)
|
||||||
|
|
||||||
|
response = JSON.build do |json|
|
||||||
|
json.object do
|
||||||
|
json.field "contentHtml", content_html
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
|
def produce_channel_community_continuation(ucid, cursor)
|
||||||
|
cursor = URI.escape(cursor)
|
||||||
|
continuation = IO::Memory.new
|
||||||
|
|
||||||
|
continuation.write(Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02])
|
||||||
|
continuation.write(write_var_int(3 + ucid.size + write_var_int(cursor.size).size + cursor.size))
|
||||||
|
|
||||||
|
continuation.write(Bytes[0x12, ucid.size])
|
||||||
|
continuation.print(ucid)
|
||||||
|
|
||||||
|
continuation.write(Bytes[0x1a])
|
||||||
|
continuation.write(write_var_int(cursor.size))
|
||||||
|
continuation.print(cursor)
|
||||||
|
continuation.rewind
|
||||||
|
|
||||||
|
continuation = Base64.urlsafe_encode(continuation.to_slice)
|
||||||
|
continuation = URI.escape(continuation)
|
||||||
|
|
||||||
|
return continuation
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_channel_community_cursor(continuation)
|
||||||
|
continuation = URI.unescape(continuation)
|
||||||
|
continuation = Base64.decode(continuation)
|
||||||
|
|
||||||
|
# 0xe2 0xa9 0x85 0xb2 0x02
|
||||||
|
continuation += 5
|
||||||
|
|
||||||
|
total_size = read_var_int(continuation[0, 4])
|
||||||
|
continuation += write_var_int(total_size).size
|
||||||
|
|
||||||
|
# 0x12
|
||||||
|
continuation += 1
|
||||||
|
ucid_size = continuation[0]
|
||||||
|
continuation += 1
|
||||||
|
ucid = continuation[0, ucid_size]
|
||||||
|
continuation += ucid_size
|
||||||
|
|
||||||
|
# 0x1a
|
||||||
|
continuation += 1
|
||||||
|
until continuation[0] == 'E'.ord
|
||||||
|
continuation += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return String.new(continuation)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_about_info(ucid, locale)
|
def get_about_info(ucid, locale)
|
||||||
@ -947,6 +1006,8 @@ def get_about_info(ucid, locale)
|
|||||||
auto_generated = true
|
auto_generated = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
tabs = about.xpath_nodes(%q(//ul[@id="channel-navigation-menu"]/li/a/span)).map { |node| node.content.downcase }
|
||||||
|
|
||||||
return AboutChannel.new(
|
return AboutChannel.new(
|
||||||
ucid: ucid,
|
ucid: ucid,
|
||||||
author: author,
|
author: author,
|
||||||
@ -961,7 +1022,8 @@ def get_about_info(ucid, locale)
|
|||||||
joined: joined,
|
joined: joined,
|
||||||
is_family_friendly: is_family_friendly,
|
is_family_friendly: is_family_friendly,
|
||||||
allowed_regions: allowed_regions,
|
allowed_regions: allowed_regions,
|
||||||
related_channels: related_channels
|
related_channels: related_channels,
|
||||||
|
tabs: tabs
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ def fetch_youtube_comments(id, db, continuation, format, locale, thin_mode, regi
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
comments = JSON.build do |json|
|
response = JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
if body["header"]?
|
if body["header"]?
|
||||||
count_text = body["header"]["commentsHeaderRenderer"]["countText"]
|
count_text = body["header"]["commentsHeaderRenderer"]["countText"]
|
||||||
@ -223,15 +223,15 @@ def fetch_youtube_comments(id, db, continuation, format, locale, thin_mode, regi
|
|||||||
end
|
end
|
||||||
|
|
||||||
if format == "html"
|
if format == "html"
|
||||||
comments = JSON.parse(comments)
|
response = JSON.parse(response)
|
||||||
content_html = template_youtube_comments(comments, locale, thin_mode)
|
content_html = template_youtube_comments(response, locale, thin_mode)
|
||||||
|
|
||||||
comments = JSON.build do |json|
|
response = JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "contentHtml", content_html
|
json.field "contentHtml", content_html
|
||||||
|
|
||||||
if comments["commentCount"]?
|
if response["commentCount"]?
|
||||||
json.field "commentCount", comments["commentCount"]
|
json.field "commentCount", response["commentCount"]
|
||||||
else
|
else
|
||||||
json.field "commentCount", 0
|
json.field "commentCount", 0
|
||||||
end
|
end
|
||||||
@ -239,7 +239,7 @@ def fetch_youtube_comments(id, db, continuation, format, locale, thin_mode, regi
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return comments
|
return response
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_reddit_comments(id, sort_by = "confidence")
|
def fetch_reddit_comments(id, sort_by = "confidence")
|
||||||
@ -286,7 +286,7 @@ def template_youtube_comments(comments, locale, thin_mode)
|
|||||||
<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="get_youtube_replies(this)">#{translate(locale, "View `x` replies", child["replies"]["replyCount"].to_s)}</a>
|
onclick="get_youtube_replies(this)">#{translate(locale, "View `x` replies", number_with_separator(child["replies"]["replyCount"]))}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -300,9 +300,9 @@ def template_youtube_comments(comments, locale, thin_mode)
|
|||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-END_HTML
|
||||||
<div class="pure-g">
|
<div class="pure-g" style="width:100%">
|
||||||
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
|
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
|
||||||
<img style="padding-right:1em;padding-top:1em" src="#{author_thumbnail}">
|
<img style="padding-right:1em;padding-top:1em;width:90%" src="#{author_thumbnail}">
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-20-24 pure-u-md-22-24">
|
<div class="pure-u-20-24 pure-u-md-22-24">
|
||||||
<p>
|
<p>
|
||||||
@ -310,10 +310,65 @@ def template_youtube_comments(comments, locale, thin_mode)
|
|||||||
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{child["author"]}</a>
|
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{child["author"]}</a>
|
||||||
</b>
|
</b>
|
||||||
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
||||||
|
END_HTML
|
||||||
|
|
||||||
|
if child["attachment"]?
|
||||||
|
attachment = child["attachment"]
|
||||||
|
|
||||||
|
case attachment["type"]
|
||||||
|
when "image"
|
||||||
|
attachment = attachment["imageThumbnails"][1]
|
||||||
|
|
||||||
|
html << <<-END_HTML
|
||||||
|
<div class="pure-g">
|
||||||
|
<div class="pure-u-1 pure-u-md-1-2">
|
||||||
|
<img style="width:100%" src="/ggpht#{URI.parse(attachment["url"].as_s).full_path}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
END_HTML
|
||||||
|
when "video"
|
||||||
|
html << <<-END_HTML
|
||||||
|
<div class="pure-g">
|
||||||
|
<div class="pure-u-1 pure-u-md-1-2">
|
||||||
|
<div style="position:relative;width:100%;height:0;padding-bottom:56.25%;margin-bottom:5px">
|
||||||
|
END_HTML
|
||||||
|
|
||||||
|
if attachment["error"]?
|
||||||
|
html << <<-END_HTML
|
||||||
|
<p>#{attachment["error"]}</p>
|
||||||
|
END_HTML
|
||||||
|
else
|
||||||
|
html << <<-END_HTML
|
||||||
|
<iframe id='ivplayer' type='text/html' style='position:absolute;width:100%;height:100%;left:0;top:0' src='/embed/#{attachment["videoId"]?}' frameborder='0'></iframe>
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
html << <<-END_HTML
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
html << <<-END_HTML
|
||||||
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
||||||
|
|
|
|
||||||
|
END_HTML
|
||||||
|
|
||||||
|
if comments["videoId"]?
|
||||||
|
html << <<-END_HTML
|
||||||
<a href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
<a href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||||
|
|
|
|
||||||
|
END_HTML
|
||||||
|
elsif comments["authorId"]?
|
||||||
|
html << <<-END_HTML
|
||||||
|
<a href="https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||||
|
|
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
html << <<-END_HTML
|
||||||
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
||||||
END_HTML
|
END_HTML
|
||||||
|
|
||||||
|
@ -49,6 +49,11 @@
|
|||||||
<a href="/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
<a href="/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<% if channel.tabs.includes? "community" %>
|
||||||
|
<a href="/channel/<%= channel.ucid %>/community"><%= translate(locale, "Community") %></a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3"></div>
|
<div class="pure-u-1-3"></div>
|
||||||
<div class="pure-u-1-3">
|
<div class="pure-u-1-3">
|
||||||
|
80
src/invidious/views/community.ecr
Normal file
80
src/invidious/views/community.ecr
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<% content_for "header" do %>
|
||||||
|
<title><%= channel.author %> - Invidious</title>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if channel.banner %>
|
||||||
|
<div class="h-box">
|
||||||
|
<img style="width:100%" src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).full_path %>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-box">
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="pure-g h-box">
|
||||||
|
<div class="pure-u-2-3">
|
||||||
|
<div class="channel-profile">
|
||||||
|
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).full_path %>">
|
||||||
|
<span><%= channel.author %></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pure-u-1-3" style="text-align:right">
|
||||||
|
<h3>
|
||||||
|
<a href="/feed/channel/<%= channel.ucid %>"><i class="icon ion-logo-rss"></i></a>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-box">
|
||||||
|
<% ucid = channel.ucid %>
|
||||||
|
<% author = channel.author %>
|
||||||
|
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
||||||
|
<%= rendered "components/subscribe_widget" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-g h-box">
|
||||||
|
<div class="pure-u-1-3">
|
||||||
|
<a href="https://www.youtube.com/channel/<%= channel.ucid %>/community"><%= translate(locale, "View channel on YouTube") %></a>
|
||||||
|
<% if !channel.auto_generated %>
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<a href="/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
||||||
|
</div>
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<% if channel.tabs.includes? "community" %>
|
||||||
|
<b><%= translate(locale, "Community") %></b>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pure-u-2-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-box">
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if error_message %>
|
||||||
|
<div class="h-box">
|
||||||
|
<p><%= error_message %></p>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="h-box pure-g" id="comments">
|
||||||
|
<%= template_youtube_comments(items.not_nil!, locale, thin_mode) %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var community_data = {
|
||||||
|
ucid: '<%= channel.ucid %>',
|
||||||
|
youtube_comments_text: '<%= HTML.escape(translate(locale, "View YouTube comments")) %>',
|
||||||
|
comments_text: '<%= HTML.escape(translate(locale, "View `x` comments", "{commentCount}")) %>',
|
||||||
|
hide_replies_text: '<%= HTML.escape(translate(locale, "Hide replies")) %>',
|
||||||
|
show_replies_text: '<%= HTML.escape(translate(locale, "Show replies")) %>',
|
||||||
|
preferences: <%= env.get("preferences").as(Preferences).to_json %>,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/js/community.js?v=<%= ASSET_COMMIT %>"></script>
|
@ -9,6 +9,20 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1><%= translate(locale, "JavaScript license information") %></h1>
|
<h1><%= translate(locale, "JavaScript license information") %></h1>
|
||||||
<table id="jslicense-labels1">
|
<table id="jslicense-labels1">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/js/community.js?v=<%= ASSET_COMMIT %>">community.js</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="/js/community.js?v=<%= ASSET_COMMIT %>"><%= translate(locale, "source") %></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="/js/embed.js?v=<%= ASSET_COMMIT %>">embed.js</a>
|
<a href="/js/embed.js?v=<%= ASSET_COMMIT %>">embed.js</a>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-g pure-u-1-3">
|
<div class="pure-g pure-u-1-3">
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
<a href="https://www.youtube.com/channel/<%= channel.ucid %>"><%= translate(locale, "View channel on YouTube") %></a>
|
<a href="https://www.youtube.com/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "View channel on YouTube") %></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
|
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
|
||||||
@ -46,6 +46,11 @@
|
|||||||
<b><%= translate(locale, "Playlists") %></b>
|
<b><%= translate(locale, "Playlists") %></b>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pure-u-1 pure-md-1-3">
|
||||||
|
<% if channel.tabs.includes? "community" %>
|
||||||
|
<a href="/channel/<%= channel.ucid %>/community"><%= translate(locale, "Community") %></a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-3"></div>
|
<div class="pure-u-1-3"></div>
|
||||||
<div class="pure-u-1-3">
|
<div class="pure-u-1-3">
|
||||||
|
Loading…
Reference in New Issue
Block a user