Add support for post page
This commit is contained in:
		| @@ -392,7 +392,7 @@ 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; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										174
									
								
								assets/js/comments.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								assets/js/comments.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								assets/js/post.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								assets/js/post.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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,35 @@ 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, postId, locale, format, thin_mode, params : String | Nil = nil) | ||||||
|  |   if params.nil? | ||||||
|  |     object = { | ||||||
|  |       "2:string"    => "community", | ||||||
|  |       "25:embedded" => { | ||||||
|  |         "22:string" => postId.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) } | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   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 +67,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 +271,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, postId) | ||||||
|  |     object = { | ||||||
|  |       "2:string"    => "community", | ||||||
|  |       "25:embedded" => { | ||||||
|  |         "22:string" => postId, | ||||||
|  |       }, | ||||||
|  |       "45:embedded" => { | ||||||
|  |         "2:varint" => 1_i64, | ||||||
|  |         "3:varint" => 1_i64, | ||||||
|  |       }, | ||||||
|  |       "53:embedded" => { | ||||||
|  |         "4:embedded" => { | ||||||
|  |           "6:varint"  => 0_i64, | ||||||
|  |           "27:varint" => 1_i64, | ||||||
|  |           "29:string" => postId, | ||||||
|  |           "30:string" => ucid, | ||||||
|  |         }, | ||||||
|  |         "8:string" => "comments-section", | ||||||
|  |       }, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     objectParsed = 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" => objectParsed, | ||||||
|  |       }, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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,53 @@ 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" | ||||||
|  |  | ||||||
|  |     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 | ||||||
|  |     region = env.params.query["region"]? | ||||||
|  |  | ||||||
|  |     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,23 @@ 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 sub_endpoint = endpoint.dig?("watchEndpoint") | ||||||
|       elsif resolved_ucid = endpoint.dig?("browseEndpoint", "browseId") |         resolved_ucid = sub_endpoint.dig?("videoId") | ||||||
|  |       elsif sub_endpoint = endpoint.dig?("browseEndpoint") | ||||||
|  |         resolved_ucid = sub_endpoint.dig?("browseId") | ||||||
|       elsif pageType == "WEB_PAGE_TYPE_UNKNOWN" |       elsif pageType == "WEB_PAGE_TYPE_UNKNOWN" | ||||||
|         return error_json(400, "Unknown url") |         return error_json(400, "Unknown url") | ||||||
|       end |       end | ||||||
|  |       if !sub_endpoint.nil? | ||||||
|  |         params = sub_endpoint.dig?("params") | ||||||
|  |       end | ||||||
|     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", resolved_ucid.try &.as_s || "" | ||||||
|  |         json.field "params", params.try &.as_s | ||||||
|         json.field "pageType", pageType |         json.field "pageType", pageType | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   | |||||||
| @@ -159,6 +159,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/#{lb}?ucid=#{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" | ||||||
|  |  | ||||||
| @@ -187,6 +192,38 @@ 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 | ||||||
|  |     region = env.params.query["region"]? || prefs.region | ||||||
|  |  | ||||||
|  |     thin_mode = env.params.query["thin_mode"]? || prefs.thin_mode | ||||||
|  |     thin_mode = thin_mode == "true" | ||||||
|  |  | ||||||
|  |     client_config = YoutubeAPI::ClientConfig.new(region: region) | ||||||
|  |  | ||||||
|  |     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 | ||||||
|  |       params = response.dig("endpoint", "browseEndpoint", "params").as_s | ||||||
|  |       post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode, params: params) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     post_response = JSON.parse(post_response) | ||||||
|  |     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) | ||||||
|   | |||||||
| @@ -127,6 +127,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 | ||||||
|  |  | ||||||
|     {"", "/videos", "/shorts", "/streams", "/playlists", "/community", "/about"}.each do |path| |     {"", "/videos", "/shorts", "/streams", "/playlists", "/community", "/about"}.each do |path| | ||||||
|       # /c/LinusTechTips |       # /c/LinusTechTips | ||||||
| @@ -240,6 +241,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 | ||||||
| @@ -249,6 +254,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 %> | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								src/invidious/views/post.ecr
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/invidious/views/post.ecr
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | <% content_for "header" do %> | ||||||
|  | <title>Invidious</title> | ||||||
|  | <% end %> | ||||||
|  |  | ||||||
|  | <div id="post" class="comments"> | ||||||
|  |     <%= IV::Frontend::Comments.template_youtube(post_response.not_nil!, locale, thin_mode) %> | ||||||
|  | </div> | ||||||
|  | <div id="comments" class="comments"> | ||||||
|  | </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/" + 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> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 ChunkyProgrammer
					ChunkyProgrammer