YouTube SparkBars (Like/Dislike Rating)

It shows SparkBars whitch represents Like/Dislike Rating ratio.

当前为 2017-07-13 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        YouTube SparkBars (Like/Dislike Rating)
// @namespace   knoa.jp
// @description It shows SparkBars whitch represents Like/Dislike Rating ratio.
// @description 動画へのリンクに「高く評価」された比率を示すバーを表示します。
// @include     https://www.youtube.com/*
// @version     2.0.0
// @grant       none
// en:
// API limits 1M queries/day. (approximately 100 views by 10,000 users.)
// You can use your own APIKEY to support this script.
// https://console.developers.google.com/apis/
// It doesn't support Ajax additional videos yet.
// ja:
// APIの制限は1日あたり100万クエリ(1万ユーザーなら1人あたり100ビュー)です。
// 各自でAPIKEYを書き換えてくれるとスクリプトの寿命が延びます。
// https://console.developers.google.com/apis/
// Ajax追加要素への対応は保留。

// ==/UserScript==

(function () {
  const SCRIPTNAME = 'YouTubeSparkBars';
  const DEBUG = false;
  console.time(SCRIPTNAME);
  const MAXRESULTS = 24;/* API limits 50 videos per request */
  const APIKEY = 'AIzaSyAyOgssM7s_vvOUDV0ZTRvk6LrTwr_1f5k';
  const API = 'https://www.googleapis.com/youtube/v3/videos?id={VIDEOIDS}&part=statistics&fields=items(id,statistics)&maxResults=' + MAXRESULTS + '&key=' + APIKEY;
  const VIEWS = {/* querySelectors on each views */
    example: ['items', 'anchor[href]', 'insertParent', 'insertAfter'],
    home: ['#feed ul > li.yt-shelf-grid-item', 'a', 'div.yt-lockup-content', 'div.yt-lockup-meta'],
    results: ['ol.item-section > li', 'div.yt-lockup-video a.yt-uix-tile-link[href]', 'div.yt-lockup-meta', 'ul.yt-lockup-meta-info'],
    watch: ['li.video-list-item', 'a.content-link[href]', 'a.content-link', 'span.view-count'],
  };
  const SPARKBARS = '<div class="video-extras-sparkbars"><div class="video-extras-sparkbar-likes" style="width: {LIKES}%"></div><div class="video-extras-sparkbar-dislikes" style="width: {DISLIKES}%"></div></div>';/* SparkBar in video pages */
  let view;
  let core = {
    initialize: function(){
      window.addEventListener('load', core.getSparkBars);
      window.addEventListener('spfdone', core.getSparkBars);
    },
    getSparkBars: function(){
      switch(true){
        case(location.href === 'https://www.youtube.com/'):
          view = VIEWS.home;
          break;
        case(location.href.startsWith('https://www.youtube.com/results?')):
          view = VIEWS.results;
          break;
        case(location.href.startsWith('https://www.youtube.com/watch?')):
          view = VIEWS.watch;
          break;
        default:
          return;
      }
      let items = document.querySelectorAll(view[0]);
      if(items === null || items.length === 0) return;
      let videoids = [];
      for(let i = 0; items[i]; i++){
        try{
          let id = items[i].querySelector(view[1]).href.match(/\?v=([^&]+)/)[1];
          videoids.push(id);
          items[i].dataset.videoid = id;
        }catch(e){
          continue;
        }
      }
      videoids.length = Math.min(videoids.length, MAXRESULTS);
      let xhr = new XMLHttpRequest();
      xhr.responseType = 'json';
      xhr.open('GET', API.replace('{VIDEOIDS}', videoids.join()));
      xhr.onreadystatechange = function () {
        if(xhr.readyState !== 4 || xhr.status !== 200) return;
        if(!xhr.response.items) return;
        let bars = {};
        for(let i = 0; xhr.response.items[i]; i++){
          let v = xhr.response.items[i], s = v.statistics;
          if(!s.likeCount && !s.dislikeCount) continue;
          bars[v.id] = SPARKBARS;
          bars[v.id] = bars[v.id].replace('{LIKES}', (100 * parseInt(s.likeCount)) / (parseInt(s.likeCount) + parseInt(s.dislikeCount)));
          bars[v.id] = bars[v.id].replace('{DISLIKES}', (100 * parseInt(s.dislikeCount)) / (parseInt(s.likeCount) + parseInt(s.dislikeCount)));
        }
        for(let i = 0; items[i]; i++){
          if(!bars[items[i].dataset.videoid]) continue;
          let div = document.createElement('div');
          div.innerHTML = bars[items[i].dataset.videoid];
          items[i].querySelector(view[2]).insertBefore(div, items[i].querySelector(view[3]).nextElementSibling);
        }
      };
      xhr.send();
    },
  };
  let log = (DEBUG) ? function(){
    let l = log.last = log.now || new Date(), n = log.now = new Date();
    console.log.bind(null,
      SCRIPTNAME + ':',
      /* 00:00:00.000 */ n.toLocaleTimeString() + '.' + n.getTime().toString().slice(-3),
      /* +0.000s      */ '+' + ((n-l)/1000).toFixed(3) + 's',
      /* :00          */ ':' + new Error().stack.match(/:[0-9]+:[0-9]+/g)[1].split(':')[1],/*LINE*/
      /* caller       */ log.caller ? log.caller.name : '',
    ).apply(null, arguments);
  } : function(){};
  core.initialize();
  console.timeEnd(SCRIPTNAME);
})();