// ==UserScript== // @name Youtube Video Ratings Bar with Power Meter // @description Puts a ratings bar below YouTube thumbnails displaying likes / dislikes, and also shows a "Power Meter" (in blue) to tell you how enthusiastic people are about each video. This helps you find the best of the best videos and avoid the bad ones. // @version 2014.06.11 // @author lednerg // @include http://*.youtube.com/* // @include http://youtube.com/* // @include https://*.youtube.com/* // @include https://youtube.com/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @namespace https://greasyfork.org/users/253 // @downloadURL none // ==/UserScript== // moves the thumbnail clocks up to avoid overlapping GM_addStyle(".video-actions, .video-time {margin-bottom:4px !important;})"); // This section could probably be handled in a better way // On some pages, YouTube adds thumbnails as you scroll down the page. So this does scans whenever you scroll scanVideos(); document.onload = function() { scanVideos(); }; // this will run scanVideos when you scroll, but only if a second has passed since the last time is scanned var lastScanTime = new Date().getTime(); window.onscroll = function() { var timeNow = new Date().getTime(); var timeDiff = timeNow - lastScanTime; if (timeDiff >= 1000) { scanVideos(); } }; function scanVideos() { // makes a List of video links which are not in the ".processed" class yet. Once they are processed, they will be added to it. var videoList = document.querySelectorAll('a.ux-thumb-wrap[href^="/watch"] > span.video-thumb:not(.processed), a.related-video[href^="/watch"] > span:first-child:not(.processed), a.playlist-video[href^="/watch"] > span.yt-thumb-64:first-child:not(.processed)'); for ( var i = 0; i < videoList.length; i++ ) { // searches for the video id number which we'll use to poll YouTube for ratings information var videoId = videoList[i].parentNode.getAttribute("href").replace(/.*[v|s]=([^&%]*).*/, "$1"); getGdata(videoList[i],videoId); } lastScanTime = new Date().getTime(); } // Parts of this were copied from elsewhere because I don't understand GM_xmlhttpRequest as well as I should. // I did modify it to get the view count and date, and that seems to work function getGdata(node,videoId) { GM_xmlhttpRequest({ method: 'GET', url: "http://gdata.youtube.com/feeds/api/videos/" + videoId + "?v=2&alt=json&fields=yt:rating,yt:statistics,published", onload: function(response) { if (response.status === 200) { var rsp = eval( '(' + response.responseText + ')' ); if (rsp && rsp.entry && rsp.entry.published && rsp.entry.yt$statistics && rsp.entry.yt$rating) { var daysAgo = (lastScanTime - new Date(rsp.entry.published.$t).getTime())/1000/60/60/24; var views = parseInt(rsp.entry.yt$statistics.viewCount, 10); var likes = parseInt(rsp.entry.yt$rating.numLikes, 10); var dislikes = parseInt(rsp.entry.yt$rating.numDislikes, 10); makeBar(node, daysAgo, views, likes, dislikes); } else { // if there is no data, mark the thumbnail as "processed" to avoid checking it over and over again node.classList.add('processed'); } } } }); } // the ratings bar is made up of differently colored divs stocked on top of each other function makeBar(node, daysAgo, views, likes, dislikes) { // .ratingsBarContainer is for the position (top/bottom) and size of the bar var container = document.createElement('div'); container.classList.add('ratingsBarContainer'); container.setAttribute("style","position:absolute; bottom:0px; width:100%; height: 4px;"); // barMsg is for the "Power: X%" or "View Count Incorrect" messages for the tooltip var barMsg = ""; var totalVotes = likes + dislikes; if (dislikes > 0) { var redBar = document.createElement('div'); redBar.classList.add('redBar'); redBar.setAttribute("style","position:absolute; right:0px; width:100%; height:100%; background-color:#c00;"); container.appendChild(redBar); } // Checks to see if the view count has been paused by YouTube. (301-319 views and less than half a day old, or more votes than views) // We do this because we need an accurate view count to make a Power Meter. // This lets the user know that we can't make one yet, but at least the green/red ratings bar is still available if (((views > 300) && (views < 320) && (daysAgo <= 0.5)) || (totalVotes > views)) { if (likes > 0) { var pauseBar = document.createElement('div'); pauseBar.classList.add('pauseBar'); pauseBar.setAttribute("style","position:absolute; height:0px; width:"+ (100 * likes / totalVotes) +"%;"); pauseBar.style.backgroundColor = "rgb(180, 240, 50)"; pauseBar.style.borderTop = "4px dotted rgb(31, 177, 90)"; container.appendChild(pauseBar); } barMsg = " View Count Incorrect"; } else { powerMeterScore = powerMeter(views, likes); if (likes > 0) { // var middleBar = document.createElement('div'); middleBar.classList.add('middleBar'); if ((100 * likes / totalVotes) >= powerMeterScore) { middleBar.classList.add('green'); middleBar.setAttribute("style","position:absolute; height:100%; width:"+(100 * likes / totalVotes)+"%;"); middleBar.style.backgroundColor = "rgb(0, 187, 34)"; } else { middleBar.classList.add('purple'); middleBar.setAttribute("style","position:absolute; height:0px; width:"+powerMeterScore+"%;"); middleBar.style.backgroundColor = "rgb(185, 102, 165)"; middleBar.style.borderTop = "4px dotted rgb(5, 5, 209)"; } container.appendChild(middleBar); } if (powerMeterScore > 0) { var blueBar = document.createElement('div'); blueBar.classList.add('blueBar'); blueBar.setAttribute("style","position:absolute; background-color:rgb(53, 165, 201); border-top: 4px dotted rgb(0, 41, 255); height:0px;"); if ((100 * likes / totalVotes) > powerMeterScore) { blueBar.style.width = powerMeterScore+"%"; } else { blueBar.style.width = ((100 * likes / totalVotes))+"%"; } barMsg = " Power: "+ Math.round(powerMeterScore*100)/100 +"%"; container.appendChild(blueBar); } } node.appendChild(container); node.setAttribute("title","Likes: "+ likes +" Dislikes: "+ dislikes + barMsg); node.classList.add('processed'); } // trade secrets function powerMeter(views, likes) { var viewLikeRatio; if (views < 2000) { var viewLikeRatio2k = Math.round( (views + views * ((3000-views)/2000)) / (likes) ); if (views < 255) { viewLikeRatio = Math.round( viewLikeRatio2k / (views/255) ); } else { viewLikeRatio = viewLikeRatio2k; } } else { viewLikeRatio = Math.round( (views+7000) / 3 / (likes) ); } if ((viewLikeRatio < 1) || (viewLikeRatio > 255)) { return 0; } var powerMeterScore = Math.round(Math.pow(((255-viewLikeRatio)/2.55), 3)) / 10000; return powerMeterScore; }