HistogramHeatGraph_html5.user.js

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

Fra 29.09.2018. Se den seneste versjonen.

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