HistogramHeatGraph_html5.user.js

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

Від 18.11.2017. Дивіться остання версія.

// ==UserScript==
// @name           HistogramHeatGraph_html5.user.js
// @namespace      sotoba
// @version        1.0.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=1368;
        this.barIndexNum=0;
        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;
        let $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($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;
        }
        //console.log(":"+);
        console.log("thread_id:"+thread_id);
        console.log("video_id:"+video_id);
        console.log("user_id:"+user_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{
            console.log("thread_id:"+thread_id);
            return $.ajax({
                url:'http://flapi.nicovideo.jp/api/getthreadkey?thread='+thread_id,
                type:'GET',
            }).then(function(response){
                console.log("thread_id:"+thread_id);
                console.log("response:"+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 = $('#comment-graph');
        const $commentlist = $('#comment-list');
        var ApiJsonData=JSON.parse(document.getElementById('js-initial-watch-data').getAttribute('data-api-data'));
        //var $canvas=$("#CommentRenderer").children('canvas').eq(0);
        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;

        console.log("barTimeInterval:"+barTimeInterval);
        console.log("this.barIndexNum:"+this.barIndexNum);
        console.log("lastBarTimeIntervalGap:"+lastBarTimeIntervalGap);
        console.log("barWidth:"+barWidth);
        console.log("this.MAXCOMMENTNUM"+this.MAXCOMMENTNUM);
        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);

            console.log("evetnt"+event);
            console.log("value"+value);
            console.log("barIndexNum"+barIndexNum);
        }
        var target = $canvas;
        var styleobj = 'width';
        target.on('domStyleChange', catchEvent);//イベントを登録
        var dsw = domStyleWatcher.Start(target, styleobj);//監視開始
        //domStyleWatcher.Stop(dsw);//監視終了
    };

    // Main
    var heatgraph = new NicoHeatGraph();
    heatgraph.drawCoordinate();
    getCommentData().done(function(data, textStatus, jqXHR){
        let canvas=$("#CommentRenderer").children('canvas').eq(0);
        heatgraph.drowgraph(data,canvas);
        heatgraph.addMousefunc(canvas);
    }).fail(function(jqXHR, textStatus, errorThrown){
        //TODO
        console.log("failed");
    });
})();