Greasy Fork is available in English.

ZenzaNicoru

ZenzaWatchを疑似ニコるに対応させるやつ

26.10.2016 itibariyledir. En son verisyonu görün.

// ==UserScript==
// @name         ZenzaNicoru
// @namespace    https://gitlab.com/u/umany
// @version      0.5.1
// @description  ZenzaWatchを疑似ニコるに対応させるやつ
// @author       umany
// @match        http://www.nicovideo.jp/*
// @match        http://ext.nicovideo.jp/
// @match        http://ext.nicovideo.jp/#*
// @match        http://ext.nicovideo.jp/thumb/*
// @match        http://api.ce.nicovideo.jp/api/v1/system.unixtime*
// @match        http://blog.nicovideo.jp/*
// @match        http://ch.nicovideo.jp/*
// @match        http://com.nicovideo.jp/*
// @match        http://commons.nicovideo.jp/*
// @match        http://dic.nicovideo.jp/*
// @match        http://ex.nicovideo.jp/*
// @match        http://info.nicovideo.jp/*
// @match        http://search.nicovideo.jp/*
// @match        http://uad.nicovideo.jp/*
// @match        http://*.nicovideo.jp/smile*
// @exclude      http://ads*.nicovideo.jp/*
// @exclude      http://www.upload.nicovideo.jp/*
// @exclude      http://www.nicovideo.jp/watch/*?edit=*
// @exclude      http://ch.nicovideo.jp/tool/*
// @exclude      http://flapi.nicovideo.jp/*
// @exclude      http://dic.nicovideo.jp/p/*
// @grant        none
// ==/UserScript==

(function () {

  const load = function () {
    const ZenzaWatch = window.ZenzaWatch;
    const $ = ZenzaWatch.lib.$;

    const gnApiUrl = 'http://j9oi.xyz/api/v1/';

    // ニコるくんの画像
    const nicorukun = 'data:image/png;base64,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';

    let nicoru = {};
    let nicotta = {};
    let watchingId = '';

    // 動画情報読み込み監視
    const loadVideoInfoHandler = function (data, watchApi, watchId) {
      // console.log('動画情報読み込み (%s : %o)', watchId, data);

      /*
       * so接頭辞の動画ページアクセス時、'watch/soXXXXX'→'watch/[thread_id]'にリダイレクトされる為、
       * watchIdが'soXXXXX'であれば、疑似ニコるAPIに送信する動画IDをthread_idとする
       * (全てのso動画がリダイレクト対象なのか不明瞭なので、また実装変わるかも)
       */
      watchingId = watchId;
      if(watchingId.startsWith('so')){
        watchingId = data.msgInfo.threadId;
      }
      getNicoru(watchingId);
    };
    ZenzaWatch.emitter.on('loadVideoInfo', loadVideoInfoHandler);

    // コメントリスト生成監視
    const commentListWatchHandler = function (prop, oldval, newval) {
      ZenzaWatch.debug.unwatch('$commentList');

      // Chromeでは$commentListがundefinedに変わってしまうので手動で元に戻す
      ZenzaWatch.debug.$commentList = newval;

      observeCommentList(newval.context);
      return newval;
    };
    ZenzaWatch.debug.watch('$commentList', commentListWatchHandler);

    // 疑似ニコる受信
    const getNicoru = function (watchId) {
      nicoru = {};
      nicotta = {};
      $.getJSON(gnApiUrl + 'getGN.php', {
          a: watchId
        },
        function (data, textStatus) {
          console.log('疑似ニコる受信: %s%O', textStatus, data);
          nicoru = data;
        }
      );
    };

    // 疑似ニコる送信
    const postNicoru = function (watchId, commentNo) {
      $.post(gnApiUrl + 'postGN.php', {
          m: watchId,
          c: commentNo
        },
        function (data, textStatus) {
          console.log('疑似ニコる送信: %s (動画ID: %s, コメント番号: %s)', textStatus, watchId, commentNo);
        }
      );
    };

    // コメントリスト監視
    const observeCommentList = function (commentList) {
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.addedNodes.length > 0) {
            setNicoruToCommentList(mutation.addedNodes);
          }
        });
      });
      const config = {
        childList: true
      };
      observer.observe(commentList, config);
    };

    // コメ欄にニコるを設定する
    const setNicoruToCommentList = function (nodeList) {
      $.each(nodeList, function () {
        const commentNo = $(this).data('no');

        /*
         * 通常コメントとコミュニティコメントはクラス'fork0'を持つ
         * 投稿者コメントは'fork1'を持つ
         * 通常コメントとコミュニティコメントも判別したいが厳しいところ
         * 自分のコメントはリロードするまでdata-noが0になるので無視
         */
        if ($(this).hasClass('fork0') && commentNo > 0) {
          setCommentListItemStyle($(this));

          $(this).find('.text').dblclick({
            commentNo: commentNo
          }, executeNicoruHandler);
        }
      });
    };

    //コメ欄のスタイルを設定
    const setCommentListItemStyle = function (commentListItem) {
      const commentNo = commentListItem.data('no');
      const nicorare = nicoru[commentNo] || 0;

      const text = commentListItem.find('.text');
      const textStyle = {};
      if (nicorare > 0) {
        textStyle.fontWeight = 'bold';
        if (nicorare >= 3 && nicorare < 10) {
          textStyle.color = 'orange';
        } else if (nicorare >= 10) {
          textStyle.color = 'red';
        }
        text.css(textStyle);
      }

      const info = commentListItem.find('.info');
      const nicoruInfoStyle = {
        display: 'inline-block',
        width: '50px'
      };

      const nicoruImageStyle = {
        width: '16px',
        hight: '16px',
        float: 'left',
        margin: '2px 5px 2px 0px',
      };

      if (nicotta[commentNo]) {
        nicoruImageStyle.transform = 'rotate(-90deg)';
        nicoruImageStyle.filter = 'grayscale(1)';
      }

      if (info.find('.nicoru').length) {
        nicoruImageStyle.transition = '.5s';
        info.find('.nicoruImage').css(nicoruImageStyle);
        info.find('.nicoruValue').text(nicorare);
      } else {
        const nicoruInfo = document.createElement('span');
        const nicoruImage = document.createElement('img');
        const nicoruValue = document.createElement('div');
        nicoruImage.src = nicorukun;
        nicoruValue.innerText = nicorare;
        $(nicoruImage).addClass('nicoruImage').css(nicoruImageStyle);
        $(nicoruValue).addClass('nicoruValue');
        $(nicoruInfo).addClass('nicoru').css(nicoruInfoStyle).append(nicoruImage, nicoruValue);

        info.prepend(nicoruInfo);
      }
    };

    // ニコる実行ハンドラ
    const executeNicoruHandler = function (e) {
      const commentNo = e.data.commentNo;

      if (!nicotta[commentNo]) {
        postNicoru(watchingId, commentNo);
        nicoru[commentNo] ? nicoru[commentNo]++ : nicoru[commentNo] = 1;
        nicotta[commentNo] = true;

        setCommentListItemStyle($(e.target).parent());
        e.preventDefault();
        e.stopPropagation();
      }
    };
  };


  // ZenzaWatch待機
  const waitForZenzaWatch = function () {
    if (window.ZenzaWatch && window.ZenzaWatch.ready) {
      window.console.log('ZenzaWatch is Ready');
      load();
    } else {
      window.jQuery('body').on('ZenzaWatchReady', function () {
        window.console.log('onZenzaWatchReady');
        load();
      });
    }
  };


  /*
   * object.watch polyfill
   *
   * 2012-04-03
   *
   * By Eli Grey, http://eligrey.com
   * Public Domain.
   * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
   */

  // object.watch
  if (!Object.prototype.watch) {
    Object.defineProperty(Object.prototype, 'watch', {
      enumerable: false,
      configurable: true,
      writable: false,
      value: function (prop, handler) {
        var
          oldval = this[prop],
          newval = oldval,
          getter = function () {
            return newval;
          },
          setter = function (val) {
            oldval = newval;
            return newval = handler.call(this, prop, oldval, val);
          };

        if (delete this[prop]) { // can't watch constants
          Object.defineProperty(this, prop, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
          });
        }
      }
    });
  }

  // object.unwatch
  if (!Object.prototype.unwatch) {
    Object.defineProperty(Object.prototype, 'unwatch', {
      enumerable: false,
      configurable: true,
      writable: false,
      value: function (prop) {
        var val = this[prop];
        delete this[prop]; // remove accessors
        this[prop] = val;
      }
    });
  }


  waitForZenzaWatch();
})();