您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ニコニコ動画のコメントをグラフで表示(html5版)※コメントをリロードすることでグラフを再描画します
当前为
// ==UserScript== // @name HistogramHeatGraph_html5.user.js // @namespace sotoba // @version 1.1.9.20210816 // @description ニコニコ動画のコメントをグラフで表示(html5版)※コメントをリロードすることでグラフを再描画します // @homepageURL https://github.com/SotobatoNihu/HistogramHeatGraph_html5 // @match https://www.nicovideo.jp/* // @match https://www.nicovideo.jp/watch/* // @require https://code.jquery.com/jquery-3.2.1.min.js // @grant none // ==/UserScript== (function () { 'use strict'; // default settings class NicoHeatGraph { constructor() { this.MINIMUMBARNUM = 50; this.DEFAULTINTERBAL = 10; this.MAXCOMMENTNUM = 30; this.GRAPHHEIGHT = 30; this.GRAPHDEFWIDTH = 856; this.barIndexNum = 0; this.$canvas = null; this.$commentgraph = $('<div>').attr('id', 'comment-graph'); this.$commentlist = $('<div>').attr('id', 'comment-list'); } drawCoordinate() { const $commentgraph = this.$commentgraph; const $commentlist = this.$commentlist; if (!($('#comment-graph').length)) { $('.PlayerContainer').eq(0).append($commentgraph); $('.MainContainer').eq(0).append($commentlist); } this.$canvas = $(".CommentRenderer").eq(0); const styleString = ` #comment-graph :hover{ -webkit-filter: hue-rotate(180deg); filter: hue-rotate(180deg); } #comment-list:empty { display: none; } `; const style = document.createElement('style'); style.appendChild(document.createTextNode(styleString)); document.body.appendChild(style); const playerWidth = parseFloat(this.$canvas.css('width')) | this.GRAPHDEFWIDTH; $commentgraph.height(this.GRAPHHEIGHT); $commentgraph.width(playerWidth); $commentgraph.css({ background: 'repeating-linear-gradient(to top, #000, #111 5px)', border: '1px solid #000', borderTo: 0, float: 'left', fontSize: 0, whiteSpace: 'nowrap', }); $commentlist.css({ background: '#000', color: '#fff', fontSize: '12px', lineHeight: 1.25, padding: '4px 4px 0', pointerEvents: 'none', position: 'absolute', zIndex: 9999, }); } drowgraph(commentData, $canvas) { const $commentgraph = this.$commentgraph; const $commentlist = this.$commentlist; const ApiJsonData = JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data')) const playerWidth = $commentgraph.width(); const videoTotalTime = ApiJsonData.video.duration; let barTimeInterval; //TODO 非常に長い(2,3時間以上)動画の処理 //長い動画 if (videoTotalTime > this.MINIMUMBARNUM * this.DEFAULTINTERBAL) { barTimeInterval = this.DEFAULTINTERBAL; this.barIndexNum = Math.ceil(videoTotalTime / barTimeInterval); //普通の動画 } else if (videoTotalTime > this.MINIMUMBARNUM) { this.barIndexNum = this.MINIMUMBARNUM; barTimeInterval = videoTotalTime / this.MINIMUMBARNUM; } else { //MINIMUMBARNUM秒以下の短い動画 this.barIndexNum = Math.floor(videoTotalTime); barTimeInterval = 1; } $commentgraph.width(playerWidth); const barColors = [ '003165', '00458f', '0058b5', '005fc4', '006adb', '0072ec', '007cff', '55a7ff', '3d9bff' ]; let listCounts = (new Array(this.barIndexNum + 1)).fill(0); const listMessages = (new Array(this.barIndexNum + 1)).fill(""); const listTimes = (new Array(this.barIndexNum + 1)).fill(""); const lastBarTimeIntervalGap = Math.floor(videoTotalTime - (this.barIndexNum * barTimeInterval)); const barWidth = playerWidth / this.barIndexNum; const MAXCOMMENTNUM = this.MAXCOMMENTNUM; for (let item of commentData) { if (item.chat === undefined || item.chat.content === undefined) { continue; } let vpos = item.chat.vpos / 100; //動画長を超えた時間のpostがあるため対処 if (videoTotalTime <= vpos) { vpos = videoTotalTime; } const section = Math.floor(vpos / barTimeInterval); listCounts[section]++; if (listCounts[section] <= MAXCOMMENTNUM) { const comment = item.chat.content.replace(/"|<|</g, ' ').replace(/\n/g, '<br>'); listMessages[section] += comment + '<br>'; } } let starttime = 0; let nexttime = 0; for (let i = 0; i < this.barIndexNum; i++) { starttime = nexttime; nexttime += barTimeInterval; if (i == this.barIndexNum - 1) { nexttime += lastBarTimeIntervalGap; } const startmin = Math.floor(starttime / 60); const startsec = Math.floor(starttime - startmin * 60); let endmin = Math.floor(nexttime / 60); let endsec = Math.ceil(nexttime - endmin * 60); if (59 < endsec) { endmin += 1; endsec -= 60; } listTimes[i] += `${("0" + startmin).slice(-2)}:${("0" + startsec).slice(-2)}-${("0" + endmin).slice(-2)}:${("0" + endsec).slice(-2)}`; } // TODO なぜかthis.barIndexNum以上の配列ができる listCounts = listCounts.slice(0, this.barIndexNum); const listCountMax = Math.max.apply(null, listCounts); const barColorRatio = (barColors.length - 1) / listCountMax; $commentgraph.empty(); $commentgraph.height(this.GRAPHHEIGHT); for (let i = 0; i < this.barIndexNum; i++) { const barColor = barColors[Math.floor(listCounts[i] * barColorRatio)]; const barBackground = `linear-gradient(to top, #${barColor}, #${barColor} ` + `${listCounts[i]}px, transparent ${listCounts[i]}px, transparent)`; const barText = listCounts[i] ? `${listMessages[i]}<br><br>${listTimes[i]} コメ ${listCounts[i]}` : ''; $('<div>') .css('background-image', barBackground) .css('float', 'left') .data('text', barText) .height(this.GRAPHHEIGHT) .width(barWidth) .addClass("commentbar") .appendTo($commentgraph); } } addMousefunc($canvas) { const $commentgraph = this.$commentgraph; const $commentlist = this.$commentlist; function mouseOverFunc() { $commentlist.css({ 'left': $(this).offset().left, 'top': $commentgraph.offset().top - $commentlist.height() - 10 }).html($(this).data('text')); } function mouseOutFunc() { $commentlist.empty(); } $commentgraph.children().on({ 'mouseenter': function (val) { $commentlist.css({ 'left': $(this).offset().left, 'top': $commentgraph.offset().top - $commentlist.height() - 10 }).html($(this).data('text')); }, 'mousemove': function (val) { $commentlist.offset({ 'left': $(this).offset().left, 'top': $commentgraph.offset().top - $commentlist.height() - 10 }); }, 'mouseleave': function () { $commentlist.empty(); } }); /* 1 Dom Style Watcher本体 監視する側*/ const domStyleWatcher = { Start: function (tgt, styleobj) { function eventHappen(data1, data2) { const throwval = tgt.css(styleobj); tgt.trigger('domStyleChange', [throwval]); } const filter = ['style']; const options = { attributes: true, attributeFilter: filter }; const mutOb = new MutationObserver(eventHappen); mutOb.observe(tgt, options); return mutOb; }, Stop: function (mo) { mo.disconnect(); } }; function catchEvent(event, value) { const playerWidth = parseFloat(value); const barIndexNum = $('.commentbar').length; $commentgraph.width(playerWidth); $('.commentbar').width(playerWidth / barIndexNum); } const target = document.getElementsByClassName('CommentRenderer')[0]; if (target) { target.addEventListener('domStyleChange', catchEvent);//イベントを登録 domStyleWatcher.Start(target, 'width');//監視開始 } //domStyleWatcher.Stop(dsw);//監視終了 } async getCommentData() { const ApiJsonData = await JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data')); if (!ApiJsonData) { return; } const threads = ApiJsonData.comment.threads; const normalCommentId = threads.findIndex(c => c.label === 'default'); const server = threads[normalCommentId]["server"]; const threadId = threads[normalCommentId]["id"]; const url = `${server}/api.json/thread?thread=${threadId}&version=20090904&res_from=-1000&scores=1` const params = { mode: 'cors', }; const data = await fetch(url, params) .then(response => response.text()) return JSON.parse(data); } load() { const self = this; this.getCommentData().then(data => { this.canvas = $('.CommentRenderer').eq(0); self.drowgraph(data, this.canvas) self.addMousefunc(this.canvas) } )//.catch(console.log("load failed")) } reload() { this.load() } } // Main const heatgraph = new NicoHeatGraph(); heatgraph.drawCoordinate(); window.onload = () => { heatgraph.load(); //reload when start button pushed const startButtons = document.getElementsByClassName('VideoStartButtonContainer') for (let startbutton of startButtons) { startbutton.addEventListener('click', () => { console.log("comment reload.") heatgraph.reload() }, false) } // reload when reload button pushed const reloadButtons = document.getElementsByClassName('ReloadButton') for (let reloadButton of reloadButtons) { reloadButton.addEventListener('click', () => { console.log("comment reload.") heatgraph.reload() }, false) } const links = document.getElementsByTagName('a'); for (const link of links) { link.addEventListener('click', () => { heatgraph.reload() }); } } })();