HistogramHeatGraph_html5.user.js

ニコニコ動画のコメントをグラフで表示(html5版)※コメントをリロードすることでグラフを再描画します

נכון ליום 19-11-2017. ראה הגרסה האחרונה.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name           HistogramHeatGraph_html5.user.js
// @namespace      sotoba
// @version        1.1.0.20171119
// @description    ニコニコ動画のコメントをグラフで表示(html5版)※コメントをリロードすることでグラフを再描画します
// @match          http://www.nicovideo.jp/watch/*
// @include        http://www.nicovideo.jp/watch/*
// @require        https://code.jquery.com/jquery-3.2.1.min.js
// @grant          none
// ==/UserScript==

(function () {
    'use strict';
    // default settings
    var NicoHeatGraph = function(){
        this.MINIMUMBARNUM=50;
        this.DEFAULTINTERBAL=10;
        this.MAXCOMMENTNUM=30;
        this.GRAPHHEIGHT = 30;
        this.GRAPHDEFWIDTH=856;
        this.BUTTONSIZE=15;
        this.barIndexNum=0;
        this.$canvas=null;

        this.$commentgraph=$('<div>').attr('id', 'comment-graph');
        this.$commentlist=$('<div>').attr('id', 'comment-list');

    };
    //draw background of graph
    NicoHeatGraph.prototype.drawCoordinate = function(){
        const $commentgraph = this.$commentgraph;
        const $commentlist = this.$commentlist;
        this.$canvas=$("#CommentRenderer").children('canvas').eq(0);
        $('.PlayerContainer').eq(0).append($commentgraph);
        $('.MainContainer').eq(0).append($commentlist);
        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);
        var 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,
        });
    };
    function getCommentData() {
        var ApiJsonData=JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data'));
        var thread_id;
        var video_id;
        var user_id;
        if(ApiJsonData.video.dmcInfo !== null){
            thread_id=ApiJsonData.video.dmcInfo.thread.thread_id;
            video_id=ApiJsonData.video.dmcInfo.video.video_id;
            user_id=ApiJsonData.video.dmcInfo.user.user_id;
        }else{
            thread_id=ApiJsonData.thread.ids.default;
            video_id=ApiJsonData.video.id;
            user_id=ApiJsonData.viewer.id;
        }

        if(video_id.startsWith('sm')||video_id.startsWith('nm')){
            return  $.ajax({
                url:'http://nmsg.nicovideo.jp/api/thread?thread='+thread_id+'&version=20061206&res_from=-1000&scores=1',
                type:'GET',
                dataType:'xml'
            });
        }else{
            return $.ajax({
                url:'http://flapi.nicovideo.jp/api/getthreadkey?thread='+thread_id,
                type:'GET',
            }).then(function(response){
                return  $.ajax({
                    url:'http://nmsg.nicovideo.jp/api/thread?thread='+thread_id+'&version=20061206&res_from=-1000&scores=1&user='+user_id+'&'+response,
                    type:'GET',
                    dataType:'xml'
                });
            });
        }
    }
    //draw bars  make comment list
    NicoHeatGraph.prototype.drowgraph = function(commentData,$canvas){
        const $commentgraph = this.$commentgraph;
        const $commentlist = this.$commentlist;
        var ApiJsonData=JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data'));
        var playerWidth =parseFloat($canvas.css("width"));
        var videoTotalTime = ApiJsonData.video.dmcInfo !== null ?  ApiJsonData.video.dmcInfo.video.length_seconds : ApiJsonData.video.duration;
        var 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'
        ];
        var listCounts = (new Array(this.barIndexNum+1)).fill(0);
        var listMessages = (new Array(this.barIndexNum+1)).fill("");
        var listTimes = (new Array(this.barIndexNum+1)).fill("");
        var lastBarTimeIntervalGap = Math.floor(videoTotalTime- (this.barIndexNum * barTimeInterval));
        var barWidth = playerWidth / this.barIndexNum;
        var barTimePoint = 0;

        const MAXCOMMENTNUM=this.MAXCOMMENTNUM;

        $(commentData).find('chat').each(function(index){
            let vpos = $(this).attr('vpos')/100;
            //動画長を超えた時間のpostがある
            if (videoTotalTime<=vpos){
                vpos=videoTotalTime;
            }
            let section=Math.floor(vpos/barTimeInterval);
            listCounts[section]++;
            if(listCounts[section]<= MAXCOMMENTNUM){
                let comment=$(this).text().replace(/"|<|&lt;/g, ' ').replace(/\n/g, '<br>');
                listMessages[section]+=comment+'<br>';
            }
        });

        let starttime=0;
        let nexttime=0;
        for (var i = 0; i < this.barIndexNum; i++) {
            starttime=nexttime;
            nexttime+=barTimeInterval;
            if(i==this.barIndexNum-1){
                nexttime+=lastBarTimeIntervalGap;
            }
            let startmin=Math.floor(starttime/60);
            let 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);
        var listCountMax = Math.max.apply(null,listCounts);
        const barColorRatio = (barColors.length - 1) / listCountMax;

        $commentgraph.empty();
        $commentgraph.height(this.GRAPHHEIGHT);
        var barColor;
        var barBackground;
        for (i = 0; i < this.barIndexNum; i++) {
            barColor = barColors[Math.floor(listCounts[i] * barColorRatio)];
            barBackground = `linear-gradient(to top, #${barColor}, #${barColor} ` +
                `${listCounts[i]}px, transparent ${listCounts[i]}px, transparent)`;
            var 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);
        }
    };
    // set mouse functions
    NicoHeatGraph.prototype.addMousefunc = function($canvas){
        let $commentgraph = this.$commentgraph;
        let $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本体 監視する側*/
        var domStyleWatcher = {
            Start: function(tgt, styleobj){
                function eventHappen(data1, data2){
                    var throwval = tgt.css(styleobj);
                    tgt.trigger('domStyleChange', [throwval]);
                }
                var tge = tgt[0];
                var filter = ['style'];
                var options = {
                    attributes: true,
                    attributeFilter: filter
                };
                var mutOb = new MutationObserver(eventHappen);
                mutOb.observe(tge, options);
                return mutOb;
            },
            Stop: function(mo){
                mo.disconnect();
            }
        };
        function catchEvent(event, value){
            var playerWidth=parseFloat(value);
            var barIndexNum=$('.commentbar').length;
            $commentgraph.width(playerWidth);
            $('.commentbar').width(playerWidth /barIndexNum);
        }
        var target = $canvas;
        var styleobj = 'width';
        target.on('domStyleChange', catchEvent);//イベントを登録
        var dsw = domStyleWatcher.Start(target, styleobj);//監視開始
        //domStyleWatcher.Stop(dsw);//監視終了
    };

    NicoHeatGraph.prototype.load = function(){
        let self=this;
        getCommentData().done(function(data, textStatus, jqXHR){
            this.canvas=$("#CommentRenderer").children('canvas').eq(0);
            self.drowgraph(data,this.canvas);
            self.addMousefunc(this.canvas);
        }).fail(function(jqXHR, textStatus, errorThrown){
            //TODO
            console.log("failed");
        });
    };
    NicoHeatGraph.prototype.reload = function(){
        this.load();
    };

    // Main
    var heatgraph = new NicoHeatGraph();
    heatgraph.drawCoordinate();
    heatgraph.load();
    //リロード
    let $reloadbutton=$('.ActionButton.ReloadButton').eq(0);
    $reloadbutton.click(function(e) {
        heatgraph.reload();
    });

})();