HistogramHeatGraph_html5.user.js

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

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

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