YouTube SparkBars (Like/Dislike Rating)

It shows SparkBars whitch represents Like/Dislike Rating ratio.

目前為 2017-07-13 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
})();