Greasy Fork

Greasy Fork is available in English.

YouTube RatingBars (Like/Dislike Rating)

It shows RatingBars which represents Like/Dislike Rating ratio.

当前为 2019-02-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        YouTube RatingBars (Like/Dislike Rating)
// @namespace   knoa.jp
// @description It shows RatingBars which represents Like/Dislike Rating ratio.
// @description 動画へのリンクに「高く評価」された比率を示すバーを表示します。
// @include     https://www.youtube.com/*
// @version     2.1.0
// @grant       none
// @noframes
// en:
// API limits 1M queries/day. (approximately 100 views by 10,000 users.)
// You can use your own APIKEY to support this script.
// (But now, far from usage limit.)
// 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 = 'YouTubeRatingBars';
  const DEBUG = false;/**/
  console.time(SCRIPTNAME);
  const HEIGHT = '2px';/*border height*/
  const DISLIKECOLOR = 'rgba(136, 136, 136, 0.4)';
  const LIKECOLOR = 'rgb(39, 147, 230)';
  const MAXRESULTS = 48;/* 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 NEW = (!document.querySelector('body#body'));
  const VIEWS = (NEW) ? {/* querySelectors on each views */
    example: ['items', 'anchor[href]', 'insertParent', 'insertAfter'],
    home: ['ytd-grid-video-renderer', 'a', '#metadata', '#metadata-line'],
    results: ['ytd-video-renderer', 'a', 'ytd-video-meta-block', '#metadata'],
    watch: ['ytd-compact-video-renderer', 'a', '#metadata', '#metadata-line'],
  } : {
    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 BARS = (NEW)/* Bar in video pages */
  ? `<div id="container" class="style-scope ytd-sentiment-bar-renderer" style="background-color: ${DISLIKECOLOR}"><div id="like-bar" class="style-scope ytd-sentiment-bar-renderer" style="width: {LIKES}%;height: ${HEIGHT};background-color: ${LIKECOLOR}"></div></div>`
  : `<div class="video-extras-sparkbars"><div class="video-extras-sparkbar-likes" style="width: {LIKES}%;height: ${HEIGHT};"></div><div class="video-extras-sparkbar-dislikes" style="width: {DISLIKES}%;height: ${HEIGHT};"></div></div>`;
  let view, items = [], previousContent = '';
  let core = {
    initialize: function(){
      let previousUrl = null;
      setInterval(function(){
        if(location.href === previousUrl) return;
        previousUrl = location.href;
        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;
        }
        items.length = 0;
        core.getItems();
      }, 1000);
    },
    getItems: function(){
      let previousLength = items.length;
      items = document.querySelectorAll(view[0]);
      if(items.length === 0) return setTimeout(core.getItems, 1000);
      if(items.length !== previousLength) return setTimeout(core.getItems, 1000);/*on loading*/
      if(items[0].textContent === previousContent) return setTimeout(core.getItems, 1000);/*not yet replaced content*/
      previousContent = items[0].textContent;
      setTimeout(core.getBars, 100);
    },
    getBars: function(){
      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] = BARS;
          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 bar = document.createElement('div');
          bar.innerHTML = bars[items[i].dataset.videoid];
          bar.id = SCRIPTNAME;
          let oldBar = items[i].querySelector('#' + SCRIPTNAME);
          if(oldBar){
            oldBar.parentNode.replaceChild(bar, oldBar);
          }else{
            items[i].querySelector(view[2]).insertBefore(bar, items[i].querySelector(view[3]).nextElementSibling);
          }
        }
      };
      xhr.send();
    },
  };
  let log = function(){
    let l = log.last = log.now || new Date(), n = log.now = new Date();
    console.log(
      '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.caller */ (log.caller.caller && log.caller.caller.name ? `${log.caller.caller.name}() => ` : '') +
      /* caller        */ `${log.caller.name}()`,
      ...arguments
    );
    if(arguments.length === 1) return arguments[0];
  };
  core.initialize();
  console.timeEnd(SCRIPTNAME);
})();