// ==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
// ==/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;
}