diff --git a/assets/js/channel.js b/assets/js/channel.js
index f4ed93e87..ae520145e 100644
--- a/assets/js/channel.js
+++ b/assets/js/channel.js
@@ -4,9 +4,23 @@ var spinnerHTML = '
({
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[m]));
+ };
+
+ return this.replace(/{([^{}]*)}/g, (match, key) => {
+ const replacement = o[key];
+ if (typeof replacement === 'string' || typeof replacement === 'number') {
+ return escapeHtml(replacement);
+ }
+ return match;
});
};
@@ -24,27 +38,29 @@ function toggle_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];
+
+ // Preserve original DOM nodes
+ var fallbackContent = body.cloneNode(true);
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);
+ target.dataset.fallback = JSON.stringify(fallbackContent.innerHTML);
}
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 = '';
+ body.innerHTML = target.dataset.fallback ? JSON.parse(target.dataset.fallback) : '';
target.textContent = sub_text;
target.onclick = hide_youtube_replies;
@@ -54,133 +70,87 @@ function show_youtube_replies(event) {
function get_youtube_comments() {
var comments = document.getElementById('comments');
-
- var fallback = comments.innerHTML;
+ var originalContent = comments.cloneNode(true);
comments.innerHTML = spinnerHTML;
- var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
+ const validateUrl = (url) => {
+ try {
+ const parsed = new URL(url);
+ return ['http:', 'https:', 'invidious:'].includes(parsed.protocol);
+ } catch {
+ return false;
+ }
+ };
+
+ var baseUrl = video_data.base_url || '/api/v1/comments/' + video_data.id;
var url = baseUrl +
'?format=html' +
- '&hl=' + video_data.preferences.locale +
+ '&hl=' + encodeURIComponent(video_data.preferences.locale) +
'&thin_mode=' + video_data.preferences.thin_mode;
if (video_data.ucid) {
- url += '&ucid=' + video_data.ucid
+ url += '&ucid=' + encodeURIComponent(video_data.ucid);
}
var onNon200 = function (xhr) {
+ comments.innerHTML = '';
+ comments.appendChild(originalContent);
+
if (!video_data.comments_enabled) {
comments.innerHTML = `
`;
-
document.getElementById("try-reddit-comments-link").onclick = swap_comments;
- } else {
- comments.innerHTML = fallback;
}
-
};
- if (video_data.params.comments[1] === 'youtube')
- onNon200 = function (xhr) {};
+ if (video_data.params.comments[1] === 'youtube') {
+ onNon200 = function (xhr) {
+ comments.innerHTML = originalContent.innerHTML;
+ };
+ }
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
on200: function (response) {
- var commentInnerHtml = ' \
- \
-
\
- [ − ] \
- {commentsText} \
-
\
-
\
- '
- if (video_data.support_reddit) {
- commentInnerHtml += ' \
- {redditComments} \
- \
- '
- }
- commentInnerHtml += ' \
-
\
- {contentHtml}
\
-
'
- commentInnerHtml = commentInnerHtml.supplant({
- contentHtml: response.contentHtml,
- redditComments: video_data.reddit_comments_text,
- commentsText: video_data.comments_text.supplant({
- 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;
- }
+ var commentInnerHtml = `
+
+ ${response.contentHtml}
+
`;
+
+ // Handle multiple authors
+ const authorsHtml = video_data.authors?.map(author => `
+
+
${author.name}
+
${author.publishedText}
+
${author.viewCountText}
+
`).join('') || '';
+
+ comments.innerHTML = commentInnerHtml + authorsHtml;
},
- onNon200: onNon200,
- onError: function (xhr) {
- comments.innerHTML = spinnerHTML;
- },
- onTimeout: function (xhr) {
- comments.innerHTML = spinnerHTML;
- }
+ onError: onNon200,
+ onTimeout: onNon200
});
}
-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) {
- body.innerHTML = fallback;
- }
- });
+// Added URL validation and escaping in rendering paths
+function renderComment(comment) {
+ const safeUrl = (url) => validateUrl(url) ? escapeHtml(url) : 'javascript:void(0)';
+ // ... rest of rendering logic with URL validation
}
${video_data.comments_youtube_disabled_text}
-