// ==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. // 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追加要素への対応は保留。 // @downloadURL none // ==/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 */ ? `
` : `
`; 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); })();