mirror of https://github.com/iv-org/invidious.git
Add support for community post page/comments (#4010)
This commit is contained in:
commit
60fae015d8
|
@ -392,11 +392,19 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
|
||||||
* Comments & community posts
|
* Comments & community posts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#comments {
|
.comments {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't want the top and bottom margin on the post page.
|
||||||
|
*/
|
||||||
|
.comments.post-comments {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.video-iframe-wrapper {
|
.video-iframe-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
var video_data = JSON.parse(document.getElementById('video_data').textContent);
|
||||||
|
|
||||||
|
var spinnerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
var spinnerHTMLwithHR = spinnerHTML + '<hr>';
|
||||||
|
|
||||||
|
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 toggle_comments(event) {
|
||||||
|
var target = event.target;
|
||||||
|
var body = target.parentNode.parentNode.parentNode.children[1];
|
||||||
|
if (body.style.display === 'none') {
|
||||||
|
target.textContent = '[ − ]';
|
||||||
|
body.style.display = '';
|
||||||
|
} else {
|
||||||
|
target.textContent = '[ + ]';
|
||||||
|
body.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide_youtube_replies(event) {
|
||||||
|
var target = event.target;
|
||||||
|
|
||||||
|
var sub_text = target.getAttribute('data-inner-text');
|
||||||
|
var inner_text = target.getAttribute('data-sub-text');
|
||||||
|
|
||||||
|
var body = target.parentNode.parentNode.children[1];
|
||||||
|
body.style.display = 'none';
|
||||||
|
|
||||||
|
target.textContent = 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;
|
||||||
|
|
||||||
|
var sub_text = target.getAttribute('data-inner-text');
|
||||||
|
var inner_text = target.getAttribute('data-sub-text');
|
||||||
|
|
||||||
|
var body = target.parentNode.parentNode.children[1];
|
||||||
|
body.style.display = '';
|
||||||
|
|
||||||
|
target.textContent = sub_text;
|
||||||
|
target.onclick = hide_youtube_replies;
|
||||||
|
target.setAttribute('data-inner-text', inner_text);
|
||||||
|
target.setAttribute('data-sub-text', sub_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_comments() {
|
||||||
|
var comments = document.getElementById('comments');
|
||||||
|
|
||||||
|
var fallback = comments.innerHTML;
|
||||||
|
comments.innerHTML = spinnerHTML;
|
||||||
|
|
||||||
|
var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
|
||||||
|
var url = baseUrl +
|
||||||
|
'?format=html' +
|
||||||
|
'&hl=' + video_data.preferences.locale +
|
||||||
|
'&thin_mode=' + video_data.preferences.thin_mode;
|
||||||
|
|
||||||
|
if (video_data.ucid) {
|
||||||
|
url += '&ucid=' + video_data.ucid
|
||||||
|
}
|
||||||
|
|
||||||
|
var onNon200 = function (xhr) { comments.innerHTML = fallback; };
|
||||||
|
if (video_data.params.comments[1] === 'youtube')
|
||||||
|
onNon200 = function (xhr) {};
|
||||||
|
|
||||||
|
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
|
||||||
|
on200: function (response) {
|
||||||
|
var commentInnerHtml = ' \
|
||||||
|
<div> \
|
||||||
|
<h3> \
|
||||||
|
<a href="javascript:void(0)">[ − ]</a> \
|
||||||
|
{commentsText} \
|
||||||
|
</h3> \
|
||||||
|
<b> \
|
||||||
|
'
|
||||||
|
if (video_data.support_reddit) {
|
||||||
|
commentInnerHtml += ' <a href="javascript:void(0)" data-comments="reddit"> \
|
||||||
|
{redditComments} \
|
||||||
|
</a> \
|
||||||
|
'
|
||||||
|
}
|
||||||
|
commentInnerHtml += ' </b> \
|
||||||
|
</div> \
|
||||||
|
<div>{contentHtml}</div> \
|
||||||
|
<hr>'
|
||||||
|
commentInnerHtml = commentInnerHtml.supplant({
|
||||||
|
contentHtml: response.contentHtml,
|
||||||
|
redditComments: video_data.reddit_comments_text,
|
||||||
|
commentsText: video_data.comments_text.supplant({
|
||||||
|
// toLocaleString correctly splits number with local thousands separator. e.g.:
|
||||||
|
// '1,234,567.89' for user with English locale
|
||||||
|
// '1 234 567,89' for user with Russian locale
|
||||||
|
// '1.234.567,89' for user with Portuguese locale
|
||||||
|
commentCount: response.commentCount.toLocaleString()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
comments.innerHTML = commentInnerHtml;
|
||||||
|
comments.children[0].children[0].children[0].onclick = toggle_comments;
|
||||||
|
if (video_data.support_reddit) {
|
||||||
|
comments.children[0].children[1].children[0].onclick = swap_comments;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNon200: onNon200, // declared above
|
||||||
|
onError: function (xhr) {
|
||||||
|
comments.innerHTML = spinnerHTML;
|
||||||
|
},
|
||||||
|
onTimeout: function (xhr) {
|
||||||
|
comments.innerHTML = spinnerHTML;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_replies(target, load_more, load_replies) {
|
||||||
|
var continuation = target.getAttribute('data-continuation');
|
||||||
|
|
||||||
|
var body = target.parentNode.parentNode;
|
||||||
|
var fallback = body.innerHTML;
|
||||||
|
body.innerHTML = spinnerHTML;
|
||||||
|
var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
|
||||||
|
var url = baseUrl +
|
||||||
|
'?format=html' +
|
||||||
|
'&hl=' + video_data.preferences.locale +
|
||||||
|
'&thin_mode=' + video_data.preferences.thin_mode +
|
||||||
|
'&continuation=' + continuation;
|
||||||
|
|
||||||
|
if (video_data.ucid) {
|
||||||
|
url += '&ucid=' + video_data.ucid
|
||||||
|
}
|
||||||
|
if (load_replies) url += '&action=action_get_comment_replies';
|
||||||
|
|
||||||
|
helpers.xhr('GET', url, {}, {
|
||||||
|
on200: function (response) {
|
||||||
|
if (load_more) {
|
||||||
|
body = body.parentNode.parentNode;
|
||||||
|
body.removeChild(body.lastElementChild);
|
||||||
|
body.insertAdjacentHTML('beforeend', 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', video_data.hide_replies_text);
|
||||||
|
a.setAttribute('data-inner-text', video_data.show_replies_text);
|
||||||
|
a.textContent = video_data.hide_replies_text;
|
||||||
|
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = response.contentHtml;
|
||||||
|
|
||||||
|
body.appendChild(p);
|
||||||
|
body.appendChild(div);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNon200: function (xhr) {
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
},
|
||||||
|
onTimeout: function (xhr) {
|
||||||
|
console.warn('Pulling comments failed');
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
addEventListener('load', function (e) {
|
||||||
|
get_youtube_comments();
|
||||||
|
});
|
|
@ -1,14 +1,4 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
var video_data = JSON.parse(document.getElementById('video_data').textContent);
|
|
||||||
var spinnerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
|
||||||
var spinnerHTMLwithHR = spinnerHTML + '<hr>';
|
|
||||||
|
|
||||||
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 toggle_parent(target) {
|
function toggle_parent(target) {
|
||||||
var body = target.parentNode.parentNode.children[1];
|
var body = target.parentNode.parentNode.children[1];
|
||||||
|
@ -21,18 +11,6 @@ function toggle_parent(target) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle_comments(event) {
|
|
||||||
var target = event.target;
|
|
||||||
var body = target.parentNode.parentNode.parentNode.children[1];
|
|
||||||
if (body.style.display === 'none') {
|
|
||||||
target.textContent = '[ − ]';
|
|
||||||
body.style.display = '';
|
|
||||||
} else {
|
|
||||||
target.textContent = '[ + ]';
|
|
||||||
body.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function swap_comments(event) {
|
function swap_comments(event) {
|
||||||
var source = event.target.getAttribute('data-comments');
|
var source = event.target.getAttribute('data-comments');
|
||||||
|
|
||||||
|
@ -43,36 +21,6 @@ function swap_comments(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide_youtube_replies(event) {
|
|
||||||
var target = event.target;
|
|
||||||
|
|
||||||
var sub_text = target.getAttribute('data-inner-text');
|
|
||||||
var inner_text = target.getAttribute('data-sub-text');
|
|
||||||
|
|
||||||
var body = target.parentNode.parentNode.children[1];
|
|
||||||
body.style.display = 'none';
|
|
||||||
|
|
||||||
target.textContent = 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;
|
|
||||||
|
|
||||||
var sub_text = target.getAttribute('data-inner-text');
|
|
||||||
var inner_text = target.getAttribute('data-sub-text');
|
|
||||||
|
|
||||||
var body = target.parentNode.parentNode.children[1];
|
|
||||||
body.style.display = '';
|
|
||||||
|
|
||||||
target.textContent = sub_text;
|
|
||||||
target.onclick = hide_youtube_replies;
|
|
||||||
target.setAttribute('data-inner-text', inner_text);
|
|
||||||
target.setAttribute('data-sub-text', sub_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
var continue_button = document.getElementById('continue');
|
var continue_button = document.getElementById('continue');
|
||||||
if (continue_button) {
|
if (continue_button) {
|
||||||
continue_button.onclick = continue_autoplay;
|
continue_button.onclick = continue_autoplay;
|
||||||
|
@ -208,111 +156,6 @@ function get_reddit_comments() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_youtube_comments() {
|
|
||||||
var comments = document.getElementById('comments');
|
|
||||||
|
|
||||||
var fallback = comments.innerHTML;
|
|
||||||
comments.innerHTML = spinnerHTML;
|
|
||||||
|
|
||||||
var url = '/api/v1/comments/' + video_data.id +
|
|
||||||
'?format=html' +
|
|
||||||
'&hl=' + video_data.preferences.locale +
|
|
||||||
'&thin_mode=' + video_data.preferences.thin_mode;
|
|
||||||
|
|
||||||
var onNon200 = function (xhr) { comments.innerHTML = fallback; };
|
|
||||||
if (video_data.params.comments[1] === 'youtube')
|
|
||||||
onNon200 = function (xhr) {};
|
|
||||||
|
|
||||||
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
|
|
||||||
on200: function (response) {
|
|
||||||
comments.innerHTML = ' \
|
|
||||||
<div> \
|
|
||||||
<h3> \
|
|
||||||
<a href="javascript:void(0)">[ − ]</a> \
|
|
||||||
{commentsText} \
|
|
||||||
</h3> \
|
|
||||||
<b> \
|
|
||||||
<a href="javascript:void(0)" data-comments="reddit"> \
|
|
||||||
{redditComments} \
|
|
||||||
</a> \
|
|
||||||
</b> \
|
|
||||||
</div> \
|
|
||||||
<div>{contentHtml}</div> \
|
|
||||||
<hr>'.supplant({
|
|
||||||
contentHtml: response.contentHtml,
|
|
||||||
redditComments: video_data.reddit_comments_text,
|
|
||||||
commentsText: video_data.comments_text.supplant({
|
|
||||||
// toLocaleString correctly splits number with local thousands separator. e.g.:
|
|
||||||
// '1,234,567.89' for user with English locale
|
|
||||||
// '1 234 567,89' for user with Russian locale
|
|
||||||
// '1.234.567,89' for user with Portuguese locale
|
|
||||||
commentCount: response.commentCount.toLocaleString()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
comments.children[0].children[0].children[0].onclick = toggle_comments;
|
|
||||||
comments.children[0].children[1].children[0].onclick = swap_comments;
|
|
||||||
},
|
|
||||||
onNon200: onNon200, // declared above
|
|
||||||
onError: function (xhr) {
|
|
||||||
comments.innerHTML = spinnerHTML;
|
|
||||||
},
|
|
||||||
onTimeout: function (xhr) {
|
|
||||||
comments.innerHTML = spinnerHTML;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_youtube_replies(target, load_more, load_replies) {
|
|
||||||
var continuation = target.getAttribute('data-continuation');
|
|
||||||
|
|
||||||
var body = target.parentNode.parentNode;
|
|
||||||
var fallback = body.innerHTML;
|
|
||||||
body.innerHTML = spinnerHTML;
|
|
||||||
|
|
||||||
var url = '/api/v1/comments/' + video_data.id +
|
|
||||||
'?format=html' +
|
|
||||||
'&hl=' + video_data.preferences.locale +
|
|
||||||
'&thin_mode=' + video_data.preferences.thin_mode +
|
|
||||||
'&continuation=' + continuation;
|
|
||||||
if (load_replies) url += '&action=action_get_comment_replies';
|
|
||||||
|
|
||||||
helpers.xhr('GET', url, {}, {
|
|
||||||
on200: function (response) {
|
|
||||||
if (load_more) {
|
|
||||||
body = body.parentNode.parentNode;
|
|
||||||
body.removeChild(body.lastElementChild);
|
|
||||||
body.insertAdjacentHTML('beforeend', 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', video_data.hide_replies_text);
|
|
||||||
a.setAttribute('data-inner-text', video_data.show_replies_text);
|
|
||||||
a.textContent = video_data.hide_replies_text;
|
|
||||||
|
|
||||||
var div = document.createElement('div');
|
|
||||||
div.innerHTML = response.contentHtml;
|
|
||||||
|
|
||||||
body.appendChild(p);
|
|
||||||
body.appendChild(div);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onNon200: function (xhr) {
|
|
||||||
body.innerHTML = fallback;
|
|
||||||
},
|
|
||||||
onTimeout: function (xhr) {
|
|
||||||
console.warn('Pulling comments failed');
|
|
||||||
body.innerHTML = fallback;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (video_data.play_next) {
|
if (video_data.play_next) {
|
||||||
player.on('ended', function () {
|
player.on('ended', function () {
|
||||||
var url = new URL('https://example.com/watch?v=' + video_data.next_video);
|
var url = new URL('https://example.com/watch?v=' + video_data.next_video);
|
||||||
|
|
|
@ -24,7 +24,33 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
|
||||||
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
|
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_channel_community(items, *, ucid, locale, format, thin_mode)
|
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
|
||||||
|
object = {
|
||||||
|
"2:string" => "community",
|
||||||
|
"25:embedded" => {
|
||||||
|
"22:string" => post_id.to_s,
|
||||||
|
},
|
||||||
|
"45:embedded" => {
|
||||||
|
"2:varint" => 1_i64,
|
||||||
|
"3:varint" => 1_i64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
params = object.try { |i| Protodec::Any.cast_json(i) }
|
||||||
|
.try { |i| Protodec::Any.from_json(i) }
|
||||||
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
|
initial_data = YoutubeAPI.browse(ucid, params: params)
|
||||||
|
|
||||||
|
items = [] of JSON::Any
|
||||||
|
extract_items(initial_data) do |item|
|
||||||
|
items << item
|
||||||
|
end
|
||||||
|
|
||||||
|
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode, is_single_post: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_single_post : Bool = false)
|
||||||
if message = items[0]["messageRenderer"]?
|
if message = items[0]["messageRenderer"]?
|
||||||
error_message = (message["text"]["simpleText"]? ||
|
error_message = (message["text"]["simpleText"]? ||
|
||||||
message["text"]["runs"]?.try &.[0]?.try &.["text"]?)
|
message["text"]["runs"]?.try &.[0]?.try &.["text"]?)
|
||||||
|
@ -39,6 +65,9 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode)
|
||||||
response = JSON.build do |json|
|
response = JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "authorId", ucid
|
json.field "authorId", ucid
|
||||||
|
if is_single_post
|
||||||
|
json.field "singlePost", true
|
||||||
|
end
|
||||||
json.field "comments" do
|
json.field "comments" do
|
||||||
json.array do
|
json.array do
|
||||||
items.each do |post|
|
items.each do |post|
|
||||||
|
@ -240,11 +269,13 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if !is_single_post
|
||||||
if cont = items.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token")
|
if cont = items.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token")
|
||||||
json.field "continuation", extract_channel_community_cursor(cont.as_s)
|
json.field "continuation", extract_channel_community_cursor(cont.as_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if format == "html"
|
if format == "html"
|
||||||
response = JSON.parse(response)
|
response = JSON.parse(response)
|
||||||
|
|
|
@ -13,6 +13,51 @@ module Invidious::Comments
|
||||||
|
|
||||||
client_config = YoutubeAPI::ClientConfig.new(region: region)
|
client_config = YoutubeAPI::ClientConfig.new(region: region)
|
||||||
response = YoutubeAPI.next(continuation: ctoken, client_config: client_config)
|
response = YoutubeAPI.next(continuation: ctoken, client_config: client_config)
|
||||||
|
return parse_youtube(id, response, format, locale, thin_mode, sort_by)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_community_post_comments(ucid, post_id)
|
||||||
|
object = {
|
||||||
|
"2:string" => "community",
|
||||||
|
"25:embedded" => {
|
||||||
|
"22:string" => post_id,
|
||||||
|
},
|
||||||
|
"45:embedded" => {
|
||||||
|
"2:varint" => 1_i64,
|
||||||
|
"3:varint" => 1_i64,
|
||||||
|
},
|
||||||
|
"53:embedded" => {
|
||||||
|
"4:embedded" => {
|
||||||
|
"6:varint" => 0_i64,
|
||||||
|
"27:varint" => 1_i64,
|
||||||
|
"29:string" => post_id,
|
||||||
|
"30:string" => ucid,
|
||||||
|
},
|
||||||
|
"8:string" => "comments-section",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
object_parsed = object.try { |i| Protodec::Any.cast_json(i) }
|
||||||
|
.try { |i| Protodec::Any.from_json(i) }
|
||||||
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
|
|
||||||
|
object2 = {
|
||||||
|
"80226972:embedded" => {
|
||||||
|
"2:string" => ucid,
|
||||||
|
"3:string" => object_parsed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
continuation = object2.try { |i| Protodec::Any.cast_json(i) }
|
||||||
|
.try { |i| Protodec::Any.from_json(i) }
|
||||||
|
.try { |i| Base64.urlsafe_encode(i) }
|
||||||
|
.try { |i| URI.encode_www_form(i) }
|
||||||
|
|
||||||
|
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
return initial_data
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_youtube(id, response, format, locale, thin_mode, sort_by = "top", isPost = false)
|
||||||
contents = nil
|
contents = nil
|
||||||
|
|
||||||
if on_response_received_endpoints = response["onResponseReceivedEndpoints"]?
|
if on_response_received_endpoints = response["onResponseReceivedEndpoints"]?
|
||||||
|
@ -68,7 +113,11 @@ module Invidious::Comments
|
||||||
json.field "commentCount", comment_count
|
json.field "commentCount", comment_count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if isPost
|
||||||
|
json.field "postId", id
|
||||||
|
else
|
||||||
json.field "videoId", id
|
json.field "videoId", id
|
||||||
|
end
|
||||||
|
|
||||||
json.field "comments" do
|
json.field "comments" do
|
||||||
json.array do
|
json.array do
|
||||||
|
|
|
@ -23,6 +23,24 @@ module Invidious::Frontend::Comments
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
END_HTML
|
||||||
|
elsif comments["authorId"]? && !comments["singlePost"]?
|
||||||
|
# for posts we should display a link to the post
|
||||||
|
replies_count_text = translate_count(locale,
|
||||||
|
"comments_view_x_replies",
|
||||||
|
child["replyCount"].as_i64 || 0,
|
||||||
|
NumberFormatting::Separator
|
||||||
|
)
|
||||||
|
|
||||||
|
replies_html = <<-END_HTML
|
||||||
|
<div class="pure-g">
|
||||||
|
<div class="pure-u-1-24"></div>
|
||||||
|
<div class="pure-u-23-24">
|
||||||
|
<p>
|
||||||
|
<a href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
END_HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
if !thin_mode
|
if !thin_mode
|
||||||
|
|
|
@ -343,6 +343,59 @@ module Invidious::Routes::API::V1::Channels
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.post(env)
|
||||||
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
id = env.params.url["id"].to_s
|
||||||
|
ucid = env.params.query["ucid"]?
|
||||||
|
|
||||||
|
thin_mode = env.params.query["thin_mode"]?
|
||||||
|
thin_mode = thin_mode == "true"
|
||||||
|
|
||||||
|
format = env.params.query["format"]?
|
||||||
|
format ||= "json"
|
||||||
|
|
||||||
|
if ucid.nil?
|
||||||
|
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
|
||||||
|
return error_json(400, "Invalid post ID") if response["error"]?
|
||||||
|
ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
|
||||||
|
else
|
||||||
|
ucid = ucid.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
fetch_channel_community_post(ucid, id, locale, format, thin_mode)
|
||||||
|
rescue ex
|
||||||
|
return error_json(500, ex)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.post_comments(env)
|
||||||
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
|
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
|
||||||
|
id = env.params.url["id"]
|
||||||
|
|
||||||
|
thin_mode = env.params.query["thin_mode"]?
|
||||||
|
thin_mode = thin_mode == "true"
|
||||||
|
|
||||||
|
format = env.params.query["format"]?
|
||||||
|
format ||= "json"
|
||||||
|
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
|
case continuation
|
||||||
|
when nil, ""
|
||||||
|
ucid = env.params.query["ucid"]
|
||||||
|
comments = Comments.fetch_community_post_comments(ucid, id)
|
||||||
|
else
|
||||||
|
comments = YoutubeAPI.browse(continuation: continuation)
|
||||||
|
end
|
||||||
|
return Comments.parse_youtube(id, comments, format, locale, thin_mode, isPost: true)
|
||||||
|
end
|
||||||
|
|
||||||
def self.channels(env)
|
def self.channels(env)
|
||||||
locale = env.get("preferences").as(Preferences).locale
|
locale = env.get("preferences").as(Preferences).locale
|
||||||
ucid = env.params.url["ucid"]
|
ucid = env.params.url["ucid"]
|
||||||
|
|
|
@ -162,17 +162,20 @@ module Invidious::Routes::API::V1::Misc
|
||||||
resolved_url = YoutubeAPI.resolve_url(url.as(String))
|
resolved_url = YoutubeAPI.resolve_url(url.as(String))
|
||||||
endpoint = resolved_url["endpoint"]
|
endpoint = resolved_url["endpoint"]
|
||||||
pageType = endpoint.dig?("commandMetadata", "webCommandMetadata", "webPageType").try &.as_s || ""
|
pageType = endpoint.dig?("commandMetadata", "webCommandMetadata", "webPageType").try &.as_s || ""
|
||||||
if resolved_ucid = endpoint.dig?("watchEndpoint", "videoId")
|
if pageType == "WEB_PAGE_TYPE_UNKNOWN"
|
||||||
elsif resolved_ucid = endpoint.dig?("browseEndpoint", "browseId")
|
|
||||||
elsif pageType == "WEB_PAGE_TYPE_UNKNOWN"
|
|
||||||
return error_json(400, "Unknown url")
|
return error_json(400, "Unknown url")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sub_endpoint = endpoint["watchEndpoint"]? || endpoint["browseEndpoint"]? || endpoint
|
||||||
|
params = sub_endpoint.try &.dig?("params")
|
||||||
rescue ex
|
rescue ex
|
||||||
return error_json(500, ex)
|
return error_json(500, ex)
|
||||||
end
|
end
|
||||||
JSON.build do |json|
|
JSON.build do |json|
|
||||||
json.object do
|
json.object do
|
||||||
json.field "ucid", resolved_ucid.try &.as_s || ""
|
json.field "ucid", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]?
|
||||||
|
json.field "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]?
|
||||||
|
json.field "params", params.try &.as_s
|
||||||
json.field "pageType", pageType
|
json.field "pageType", pageType
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -165,6 +165,11 @@ module Invidious::Routes::Channels
|
||||||
end
|
end
|
||||||
locale, user, subscriptions, continuation, ucid, channel = data
|
locale, user, subscriptions, continuation, ucid, channel = data
|
||||||
|
|
||||||
|
# redirect to post page
|
||||||
|
if lb = env.params.query["lb"]?
|
||||||
|
env.redirect "/post/#{URI.encode_www_form(lb)}?ucid=#{URI.encode_www_form(ucid)}"
|
||||||
|
end
|
||||||
|
|
||||||
thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode
|
thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode
|
||||||
thin_mode = thin_mode == "true"
|
thin_mode = thin_mode == "true"
|
||||||
|
|
||||||
|
@ -193,6 +198,44 @@ module Invidious::Routes::Channels
|
||||||
templated "community"
|
templated "community"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.post(env)
|
||||||
|
# /post/{postId}
|
||||||
|
id = env.params.url["id"]
|
||||||
|
ucid = env.params.query["ucid"]?
|
||||||
|
|
||||||
|
prefs = env.get("preferences").as(Preferences)
|
||||||
|
|
||||||
|
locale = prefs.locale
|
||||||
|
|
||||||
|
thin_mode = env.params.query["thin_mode"]? || prefs.thin_mode
|
||||||
|
thin_mode = thin_mode == "true"
|
||||||
|
|
||||||
|
nojs = env.params.query["nojs"]?
|
||||||
|
|
||||||
|
nojs ||= "0"
|
||||||
|
nojs = nojs == "1"
|
||||||
|
|
||||||
|
if !ucid.nil?
|
||||||
|
ucid = ucid.to_s
|
||||||
|
post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode)
|
||||||
|
else
|
||||||
|
# resolve the url to get the author's UCID
|
||||||
|
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
|
||||||
|
return error_template(400, "Invalid post ID") if response["error"]?
|
||||||
|
|
||||||
|
ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
|
||||||
|
post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
post_response = JSON.parse(post_response)
|
||||||
|
|
||||||
|
if nojs
|
||||||
|
comments = Comments.fetch_community_post_comments(ucid, id)
|
||||||
|
comment_html = JSON.parse(Comments.parse_youtube(id, comments, "html", locale, thin_mode, isPost: true))["contentHtml"]
|
||||||
|
end
|
||||||
|
templated "post"
|
||||||
|
end
|
||||||
|
|
||||||
def self.channels(env)
|
def self.channels(env)
|
||||||
data = self.fetch_basic_information(env)
|
data = self.fetch_basic_information(env)
|
||||||
return data if !data.is_a?(Tuple)
|
return data if !data.is_a?(Tuple)
|
||||||
|
|
|
@ -128,6 +128,7 @@ module Invidious::Routing
|
||||||
get "/channel/:ucid/live", Routes::Channels, :live
|
get "/channel/:ucid/live", Routes::Channels, :live
|
||||||
get "/user/:user/live", Routes::Channels, :live
|
get "/user/:user/live", Routes::Channels, :live
|
||||||
get "/c/:user/live", Routes::Channels, :live
|
get "/c/:user/live", Routes::Channels, :live
|
||||||
|
get "/post/:id", Routes::Channels, :post
|
||||||
|
|
||||||
# Channel catch-all, to redirect future routes to the channel's home
|
# Channel catch-all, to redirect future routes to the channel's home
|
||||||
# NOTE: defined last in order to be processed after the other routes
|
# NOTE: defined last in order to be processed after the other routes
|
||||||
|
@ -253,6 +254,10 @@ module Invidious::Routing
|
||||||
get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}}
|
get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
|
# Posts
|
||||||
|
get "/api/v1/post/:id", {{namespace}}::Channels, :post
|
||||||
|
get "/api/v1/post/:id/comments", {{namespace}}::Channels, :post_comments
|
||||||
|
|
||||||
# 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community
|
# 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community
|
||||||
get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect
|
get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect
|
||||||
get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect
|
get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect
|
||||||
|
@ -262,6 +267,7 @@ module Invidious::Routing
|
||||||
get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions
|
get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions
|
||||||
get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag
|
get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag
|
||||||
|
|
||||||
|
|
||||||
# Authenticated
|
# Authenticated
|
||||||
|
|
||||||
# The notification APIs cannot be extracted yet! They require the *local* notifications constant defined in invidious.cr
|
# The notification APIs cannot be extracted yet! They require the *local* notifications constant defined in invidious.cr
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<p><%= error_message %></p>
|
<p><%= error_message %></p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="h-box pure-g" id="comments">
|
<div class="h-box pure-g comments" id="comments">
|
||||||
<%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %>
|
<%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<% content_for "header" do %>
|
||||||
|
<title>Invidious</title>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div id="post" class="comments post-comments">
|
||||||
|
<%= IV::Frontend::Comments.template_youtube(post_response.not_nil!, locale, thin_mode) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if nojs %>
|
||||||
|
<hr>
|
||||||
|
<% end %>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div id="comments" class="comments post-comments">
|
||||||
|
<% if nojs %>
|
||||||
|
<%= comment_html %>
|
||||||
|
<% else %>
|
||||||
|
<noscript>
|
||||||
|
<a href="/post/<%= id %>?ucid=<%= ucid %>&nojs=1">
|
||||||
|
<%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
|
||||||
|
</a>
|
||||||
|
</noscript>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id="video_data" type="application/json">
|
||||||
|
<%=
|
||||||
|
{
|
||||||
|
"id" => id,
|
||||||
|
"youtube_comments_text" => HTML.escape(translate(locale, "View YouTube comments")),
|
||||||
|
"reddit_comments_text" => "",
|
||||||
|
"reddit_permalink_text" => "",
|
||||||
|
"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")),
|
||||||
|
"params" => {
|
||||||
|
"comments": ["youtube"]
|
||||||
|
},
|
||||||
|
"preferences" => prefs,
|
||||||
|
"base_url" => "/api/v1/post/#{URI.encode_www_form(id)}/comments",
|
||||||
|
"ucid" => ucid
|
||||||
|
}.to_pretty_json
|
||||||
|
%>
|
||||||
|
</script>
|
||||||
|
<script src="/js/comments.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
|
<script src="/js/post.js?v=<%= ASSET_COMMIT %>"></script>
|
|
@ -64,7 +64,8 @@ we're going to need to do it here in order to allow for translations.
|
||||||
"premiere_timestamp" => video.premiere_timestamp.try &.to_unix,
|
"premiere_timestamp" => video.premiere_timestamp.try &.to_unix,
|
||||||
"vr" => video.is_vr,
|
"vr" => video.is_vr,
|
||||||
"projection_type" => video.projection_type,
|
"projection_type" => video.projection_type,
|
||||||
"local_disabled" => CONFIG.disabled?("local")
|
"local_disabled" => CONFIG.disabled?("local"),
|
||||||
|
"support_reddit" => true
|
||||||
}.to_pretty_json
|
}.to_pretty_json
|
||||||
%>
|
%>
|
||||||
</script>
|
</script>
|
||||||
|
@ -270,7 +271,7 @@ we're going to need to do it here in order to allow for translations.
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<div id="comments">
|
<div id="comments" class="comments">
|
||||||
<% if nojs %>
|
<% if nojs %>
|
||||||
<%= comment_html %>
|
<%= comment_html %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
@ -352,4 +353,5 @@ we're going to need to do it here in order to allow for translations.
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="/js/comments.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
<script src="/js/watch.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/watch.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
|
|
Loading…
Reference in New Issue