HistogramHeatGraph_html5.user.js

ニコニコ動画でコメントの盛り上がりをグラフで表示(html5版)

As of 2017-11-11. See the latest version.

  1. // ==UserScript==
  2. // @name HistogramHeatGraph_html5.user.js
  3. // @namespace sotoba
  4. // @version 0.20171111.1
  5. // @description ニコニコ動画でコメントの盛り上がりをグラフで表示(html5版)
  6. // @match http://www.nicovideo.jp/watch/*
  7. // @include http://www.nicovideo.jp/watch/*
  8. // @require http://code.jquery.com/jquery-latest.min.js
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13.  
  14. const MINIMUMBARNUM=50;
  15. const DEFAULTINTERBAL=10;
  16. const MAXCOMMENTNUM=30;
  17. const GRAPHHEIGHT = 30;
  18. const styleString = `
  19. #comment-graph {
  20. background: repeating-linear-gradient(to top, #000, #111 5px);
  21. border: 1px solid #000;
  22. border-top: 0;
  23. float: left;
  24. font-size: 0;
  25. white-space: nowrap;
  26. }
  27. #comment-graph :hover{
  28. -webkit-filter: hue-rotate(180deg);
  29. filter: hue-rotate(180deg);
  30. }
  31. #comment-list {
  32. background: #000;
  33. color: #fff;
  34. font-size: 12px;
  35. line-height: 1.25;
  36. padding: 4px 4px 0;
  37. pointer-events: none;
  38. position: absolute;
  39. z-index: 9999;
  40. }
  41. #comment-list:empty {
  42. display: none;
  43. }
  44. `;
  45. const style = document.createElement('style');
  46. style.appendChild(document.createTextNode(styleString));
  47. document.body.appendChild(style);
  48.  
  49. $('.PlayerContainer').eq(0).append('<div id=comment-graph></div>');
  50. $('.MainContainer').eq(0).append('<div id=comment-list></div>');
  51.  
  52. const $commentgraph = $('#comment-graph');
  53. const $commentlist = $('#comment-list');
  54.  
  55. var ApiJsonData=JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data'));
  56.  
  57. var thread_id;
  58. var video_id;
  59. var user_id;
  60. if(ApiJsonData.video.dmcInfo !== null){
  61. thread_id=ApiJsonData.video.dmcInfo.thread.thread_id;
  62. video_id=ApiJsonData.video.dmcInfo.video.video_id;
  63. user_id=ApiJsonData.video.dmcInfo.user.user_id;
  64. }else{
  65. thread_id=ApiJsonData.thread.ids.default;
  66. video_id=ApiJsonData.video.id;
  67. user_id=ApiJsonData.viewer.id;
  68. }
  69.  
  70. if(video_id.startsWith('sm')||video_id.startsWith('nm')){
  71. $.ajax({
  72. url:'http://nmsg.nicovideo.jp/api/thread?thread='+thread_id+'&version=20061206&res_from=-1000&scores=1',
  73. type:'GET',
  74. dataType:'xml',
  75. timeout:3000,
  76. error:function() {
  77. console.log("Ajax:failed");
  78. },
  79. success:function(xml){
  80. drowgraph(xml,ApiJsonData);
  81. }
  82. });
  83. }else{
  84. $.ajax({
  85. url:'http://flapi.nicovideo.jp/api/getthreadkey?thread='+thread_id,
  86. type:'GET',
  87. timeout:3000,
  88. error:function() {
  89. console.log("Ajax:failed");
  90. },
  91. success:function(response){
  92. $.ajax({
  93. url:'http://nmsg.nicovideo.jp/api/thread?thread='+thread_id+'&version=20061206&res_from=-1000&scores=1&user='+user_id+'&'+response,
  94. type:'GET',
  95. dataType:'xml',
  96. timeout:3000,
  97. error:function() {
  98. console.log("Ajax:failed...");
  99. },
  100. success:function(xml){
  101. drowgraph(xml,ApiJsonData);
  102. }
  103. });
  104. }
  105. });
  106. }
  107.  
  108. function drowgraph(commentData,ApiJsonData){
  109. var $canvas=$("#CommentRenderer").children('canvas').eq(0);
  110. var playerWidth =parseFloat($canvas.css("width"));
  111. var videoTotalTime = ApiJsonData.video.dmcInfo !== null ? ApiJsonData.video.dmcInfo.video.length_seconds : ApiJsonData.video.duration;
  112. var barTimeInterval;
  113. var barIndexNum;
  114. if(videoTotalTime > MINIMUMBARNUM*DEFAULTINTERBAL){
  115. barTimeInterval=DEFAULTINTERBAL;
  116. barIndexNum=Math.ceil(videoTotalTime / barTimeInterval);
  117. }else if(videoTotalTime>MINIMUMBARNUM){
  118. barIndexNum=MINIMUMBARNUM;
  119. barTimeInterval=Math.round(videoTotalTime/MINIMUMBARNUM);
  120. }else{
  121. barIndexNum=Math.floor(videoTotalTime);
  122. barTimeInterval=1;
  123. }
  124. $('#comment-graph').width( playerWidth );
  125. const barColors = [
  126. '003165', '00458f', '0058b5','005fc4', '006adb',
  127. '0072ec', '007cff', '55a7ff','3d9bff'
  128. ];
  129. var listCounts = (new Array(barIndexNum)).fill(0);
  130. var listMessages = (new Array(barIndexNum)).fill("");
  131. var listTimes = (new Array(barIndexNum)).fill("");
  132. var lastBarTimeIntervalGap = Math.floor(videoTotalTime- (barIndexNum * barTimeInterval));
  133. var barWidth = playerWidth / barIndexNum;
  134. var barTimePoint = 0;
  135.  
  136. $(commentData).find('chat').each(function(index){
  137. var vpos = $(this).attr('vpos')/100;
  138. var section=Math.floor(vpos/barTimeInterval);
  139. listCounts[section]++;
  140. if(listCounts[section]<=MAXCOMMENTNUM){
  141. var comment=$(this).text().replace(/"|<|&lt;/g, ' ').replace(/\n/g, '<br>');
  142. listMessages[section]+=comment+'<br>';
  143. }
  144. });
  145. var startMin=0;
  146. var startSec=0;
  147. var min=0;
  148. var sec=0;
  149. for (var i = 0; i < barIndexNum-1; i++) {
  150. startMin=min;
  151. startSec=sec;
  152. sec+=barTimeInterval;
  153. if(59 < sec){
  154. min+=1;
  155. sec-=60;
  156. }
  157. listTimes[i] += `${("0"+startMin).slice(-2)}:${("0"+startSec).slice(-2)}-${("0"+min).slice(-2)}:${("0"+sec).slice(-2)}`;
  158. }
  159. startMin=min;
  160. startSec=sec;
  161. sec+=(barTimeInterval+lastBarTimeIntervalGap);
  162. if(59 < sec){
  163. min+=1;
  164. sec-=60;
  165. }
  166. listTimes[barIndexNum-1] += `${("0"+startMin).slice(-2)}:${("0"+startSec).slice(-2)}-${("0"+min).slice(-2)}:${("0"+sec).slice(-2)}`;
  167.  
  168. // TODO なぜかbarIndexNum以上の配列ができる
  169. listCounts=listCounts.slice(0, barIndexNum);
  170. var listCountMax = Math.max.apply(null,listCounts);
  171. const barColorRatio = (barColors.length - 1) / listCountMax;
  172.  
  173.  
  174. $commentgraph.empty();
  175. $commentgraph.height(GRAPHHEIGHT);
  176. var barColor;
  177. var barBackground;
  178. for (i = 0; i <= barIndexNum; i++) {
  179. barColor = barColors[Math.floor(listCounts[i] * barColorRatio)];
  180. barBackground = `linear-gradient(to top, #${barColor}, #${barColor} ` +
  181. `${listCounts[i]}px, transparent ${listCounts[i]}px, transparent)`;
  182. var barText = listCounts[i] ?
  183. `${listMessages[i]}<br><br>${listTimes[i]} コメ ${listCounts[i]}` : '';
  184. $('<div>')
  185. .css('background-image', barBackground)
  186. .css('float','left')
  187. .data('text', barText)
  188. .height(GRAPHHEIGHT)
  189. .width(barWidth)
  190. .addClass("commentbar")
  191. .appendTo($commentgraph);
  192. }
  193. function mouseOverFunc() {
  194. $commentlist.css({
  195. 'left': $(this).offset().left,
  196. 'top': $commentgraph.offset().top - $commentlist.height() - 10
  197. })
  198. .html($(this).data('text'));
  199. }
  200. function mouseOutFunc() {
  201. $commentlist.empty();
  202. }
  203.  
  204. $commentgraph.children().on({
  205. 'mouseenter': function(val) {
  206. $commentlist.css({
  207. 'left': $(this).offset().left,
  208. 'top': $commentgraph.offset().top - $commentlist.height() - 10
  209. })
  210. .html($(this).data('text'));
  211. },
  212. 'mousemove': function(val) {
  213. $commentlist.offset({
  214. 'left': $(this).offset().left,
  215. 'top': $commentgraph.offset().top - $commentlist.height() - 10
  216. });
  217. },
  218. 'mouseleave': function() {
  219. $commentlist.empty();
  220. }
  221. });
  222.  
  223. /* 1 Dom Style Watcher本体 監視する側*/
  224. var domStyleWatcher = {
  225. Start: function(tgt, styleobj){
  226. function eventHappen(data1, data2){
  227. var throwval = tgt.css(styleobj);
  228. tgt.trigger('domStyleChange', [throwval]);
  229. }
  230. var tge = tgt[0];
  231. var filter = ['style'];
  232. var options = {
  233. attributes: true,
  234. attributeFilter: filter
  235. };
  236. var mutOb = new MutationObserver(eventHappen);
  237. mutOb.observe(tge, options);
  238. return mutOb;
  239. },
  240. Stop: function(mo){
  241. mo.disconnect();
  242. }
  243. };
  244. function catchEvent(event, value){
  245. playerWidth=parseFloat(value);
  246. $('#comment-graph').width(playerWidth);
  247. $('.commentbar').width(playerWidth / barIndexNum);
  248. console.log(event);
  249. console.log(value);
  250. }
  251. var target = $canvas;
  252. var styleobj = 'width';
  253. target.on('domStyleChange', catchEvent);//イベントを登録
  254. var dsw = domStyleWatcher.Start(target, styleobj);//監視開始
  255. //domStyleWatcher.Stop(dsw);//監視終了
  256. }
  257. })();