// ==UserScript== // @name YouTube SparkBars (Like/Dislike Rating) // @namespace knoa.jp // @description:en It shows SparkBars whitch represents Like/Dislike Rating ratio. // @description:ja 動画へのリンクに「高く評価」された比率を示すバーを表示します。 // @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追加要素への対応は保留。 // @description It shows SparkBars whitch represents Like/Dislike Rating ratio. // @downloadURL none // ==/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 = '
';/* 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); })();