Youtube Live Replay Comment Collector

利用直播comment寻找直播回放中的热点片段方便剪辑man干活

当前为 2019-09-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Live Replay Comment Collector
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.2
  5. // @description 利用直播comment寻找直播回放中的热点片段方便剪辑man干活
  6. // @author yuyuyzl
  7. // @match https://www.youtube.com/watch?v=*
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_setClipboard
  12. // @grant GM_info
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. setTimeout(function(){
  18. var chatID=document.body.innerHTML.match(/"reloadContinuationData":{"continuation":".*?"/g)[1].split('\"')[5];
  19. //var chatID="op2w0wRjGlBDamdhRFFvTFoySnVTbkJrWTNSQlVITXFKd29ZVlVNeGIzQklWWEozT0hKMmJuTmhaRlF0YVVkd04wTm5FZ3RuWW01S2NHUmpkRUZRY3lBQkABWgUQoI2DAWAEcgIIBHgB"
  20. console.log(chatID);
  21. var timeStr=document.getElementsByClassName("ytp-bound-time-right")[0].innerText.split(":");
  22. console.log(timeStr);
  23. var videoLengthMs=0;
  24. for(var s of timeStr){
  25. videoLengthMs=videoLengthMs*60+(+s);
  26. }
  27. videoLengthMs*=1000;
  28. console.log(videoLengthMs);
  29. var comments={};
  30. var requestCount=0,requestDone=0;
  31. var animateBar=0;
  32. var node=document.createElement("DIV");
  33. node.style["pointer-events"]="none";
  34. document.getElementsByClassName("ytp-progress-bar-container")[0].appendChild(node);
  35. var btnStart=document.createElement("BUTTON");
  36. function getChatReplay(chatID,offsetMs,callback){
  37. requestCount++;
  38. btnStart.innerText="获取评论数据中...("+requestDone+"/"+requestCount+")";
  39. GM_xmlhttpRequest({
  40. method: "GET",
  41. cache: false,
  42. url: "https://www.youtube.com/live_chat_replay/get_live_chat_replay?continuation="+chatID+"&playerOffsetMs="+offsetMs+"&hidden=false&pbj=1",
  43. onload:function(data){
  44. var responseObj=JSON.parse(data.responseText);
  45. //console.log(responseObj.response.continuationContents.liveChatContinuation.actions);
  46. var itemsList={};
  47. let minTime=2148473647;
  48. let maxTime=0;
  49. for(var item of responseObj.response.continuationContents.liveChatContinuation.actions){
  50. let itemInner={};
  51. let itemArranged={
  52. time: +item.replayChatItemAction.videoOffsetTimeMsec,
  53. //id: item.replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer.id,
  54. //text: item.replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer.message.runs[0].text
  55. };
  56. itemArranged.timehms=Math.floor(itemArranged.time/1000/60/60)+":"+Math.floor(itemArranged.time/1000/60)%60+":"+Math.floor(itemArranged.time/1000)%60;
  57. //console.log(item);
  58. try{
  59. if(item.replayChatItemAction.actions[0].addChatItemAction.item.hasOwnProperty("liveChatPaidMessageRenderer")){
  60. //continue;
  61. itemInner=item.replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidMessageRenderer;
  62. itemArranged.isPaid=true;
  63. }else if(item.replayChatItemAction.actions[0].addChatItemAction.item.hasOwnProperty("liveChatTextMessageRenderer")){
  64. itemInner=item.replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer;
  65. itemArranged.isPaid=false;
  66. }else continue;
  67. }catch(e){continue;}
  68. itemArranged.id=itemInner.id;
  69. itemArranged.authorName=itemInner.authorName.simpleText;
  70. var itemText="";
  71. try{
  72. for(var run of itemInner.message.runs){
  73. if(run.hasOwnProperty("text"))itemText+=run.text;
  74. if(run.hasOwnProperty("emoji"))itemText+=run.emoji.shortcuts[0];
  75. }
  76. }catch(e){continue;}
  77. itemArranged.text=itemText;
  78. if(itemArranged.isPaid===false){
  79. minTime=minTime>itemArranged.time?itemArranged.time:minTime;
  80. maxTime=maxTime<itemArranged.time?itemArranged.time:maxTime;
  81. }
  82. itemsList[itemArranged.id]=itemArranged;
  83. }
  84. requestDone++;
  85. btnStart.innerText="获取评论数据中...("+requestDone+"/"+requestCount+")";
  86. callback(itemsList,minTime,maxTime);
  87. }
  88. });
  89. }
  90. var lock=0;
  91. var timestart=new Date().getTime();
  92. function sliceBlankArea(left,right){
  93. if(requestCount>2000)return;
  94. var reqTime=Math.round((left+right)/2);
  95. console.log(left+"-sliceBlankArea-"+right);
  96. if(right-left>300000){
  97. sliceBlankArea(left,reqTime);
  98. sliceBlankArea(reqTime,right);
  99. return;
  100. }
  101. getChatReplay(chatID,reqTime,(data,min,max)=>{
  102. if(min>reqTime)min=reqTime;
  103. if(max<reqTime)max=reqTime;
  104. for(var key in data)comments[key]=data[key];
  105. console.log("UPDATED COMMENTS, SIZE:"+Object.keys(comments).length);
  106. console.log("REQCOUNT:"+requestCount)
  107. if(left<min)sliceBlankArea(left,min);
  108. if(max<right)sliceBlankArea(max,right);
  109. if(requestCount===requestDone){
  110. clearInterval(animateBar);
  111. console.log("sliceBlank DONE, time:"+(new Date().getTime()-timestart)+" REQCOUNT:"+requestCount);
  112. console.log(comments);
  113. btnStart.disabled=false;
  114. btnStart.innerText="导出CSV/TSV文件";
  115. btnStart.onclick=function(){
  116. function getUrlParam(k) {
  117. var regExp = new RegExp('([?]|&)' + k + '=([^&]*)(&|$)');
  118. var result = window.location.href.match(regExp);
  119. if (result) {
  120. return decodeURIComponent(result[2]);
  121. } else {
  122. return null;
  123. }
  124. }
  125. var sep=confirm("导出为TSV?(是:TSV,否:CSV)")?"\t":",";
  126.  
  127. var csvComments="\ufeffid"+sep+"isPaid"+sep+"author"+sep+"time"+sep+"timehms"+sep+"text";
  128. for(let commentID in comments){
  129. const comment=comments[commentID];
  130. csvComments+="\n"+comment.id+sep+comment.isPaid+sep+comment.authorName+sep+comment.time+sep+comment.timehms+sep+comment.text;
  131. }
  132. console.log(csvComments);
  133. var blobContent = new Blob([csvComments], {type: "text/plain;charset=utf-8"});
  134. const blobUrl = window.URL.createObjectURL(blobContent)
  135.  
  136. downloadFileByBlob(blobUrl, getUrlParam("v")+'_Comments.'+(sep==="\t"?"tsv":"csv"));
  137.  
  138. function downloadFileByBlob(blobUrl, filename) {
  139. const eleLink = document.createElement('a')
  140. eleLink.download = filename
  141. eleLink.style.display = 'none'
  142. eleLink.href = blobUrl
  143. document.body.appendChild(eleLink)
  144. eleLink.click()
  145. document.body.removeChild(eleLink)
  146. }
  147. }
  148. const gradientCount=1000;
  149. var commentCount=new Array(gradientCount+1);
  150. for(let i=0;i<=gradientCount;i++)commentCount[i]=0;
  151. for(let commentID in comments){
  152. const comment=comments[commentID];
  153. if(comment.isPaid===false)commentCount[Math.round(comment.time*gradientCount/videoLengthMs)]++;
  154. }
  155. //console.log(commentCount);
  156. var countMax=0;
  157. var countMin=999999999;
  158. for(let i=0;i<=gradientCount;i++){
  159. countMax=countMax<commentCount[i]?commentCount[i]:countMax;
  160. countMin=countMin>commentCount[i]?commentCount[i]:countMin;
  161. }
  162. //console.log(countMax+","+countMin);
  163.  
  164. for(let i=0;i<=gradientCount;i++){
  165. console.log(commentCount[i])
  166. commentCount[i]="rgba(0,255,0,"+((commentCount[i]-countMin)/(countMax-countMin)).toFixed(2)+") "+(i*100/(gradientCount)).toFixed(2)+"%";
  167. }
  168. //console.log(commentCount);
  169. var stylebg="linear-gradient(to right,"+commentCount.slice(0,gradientCount+1).join(",")+")";
  170. node.style.background=stylebg;
  171. console.log(node.style.background);
  172. console.log(stylebg);
  173. }
  174. });
  175.  
  176. }
  177. Node.prototype.prependChild = function (newNode){
  178. this.insertBefore(newNode,this.firstChild);
  179. }
  180. btnStart.innerText="运行评论热点分析";
  181. btnStart.style.background= "none";
  182. btnStart.style.border= "1px solid rgb(5,95,212)";
  183. btnStart.style.width= "100%";
  184. btnStart.style.height= "36px";
  185. btnStart.style.color= "rgb(5,95,212)";
  186. btnStart.onclick=function(){
  187. btnStart.disabled=true;
  188. timestart=new Date().getTime();
  189. node.style.height="100%";
  190. node.style.width="100%";
  191. node.style.position="absolute";
  192. node.style.bottom="0";
  193. node.style.left="0";
  194. node.style["z-index"]="32";
  195. var animateCount=0;
  196. animateBar=setInterval(function(){
  197. animateCount=(animateCount+1)%20;
  198. if(requestCount!==requestDone)node.style.background="rgba(0,255,0,"+(1-animateCount/19)+")";
  199. },50);
  200. sliceBlankArea(0,videoLengthMs);
  201. };
  202. document.getElementById("secondary-inner").prependChild(btnStart);
  203.  
  204.  
  205.  
  206. },5000);
  207.  
  208. })();