Youtube Video Ratings Bar with Power Meter

Highlights the most worthwhile videos on YouTube. In addition to a ratings bar, there's also a blue "Power Meter" which measures people's enthusiasm for videos.

נכון ליום 03-07-2014. ראה הגרסה האחרונה.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Youtube Video Ratings Bar with Power Meter
// @description Highlights the most worthwhile videos on YouTube. In addition to a ratings bar, there's also a blue "Power Meter" which measures people's enthusiasm for videos.
// @version     2014.07.02.a
// @author      lednerg
// @license     (CC) Attribution Non-Commercial Share Alike; http://creativecommons.org/licenses/by-nc-sa/3.0/
// @icon        http://i.imgur.com/lslCELP.png
// @include     http://*.youtube.com/*
// @include     http://youtube.com/*
// @include     https://*.youtube.com/*
// @include     https://youtube.com/*
// @grant       GM_addStyle
// @grant       GM_xmlhttpRequest
// @namespace   https://greatest.deepsurf.us/users/253
// ==/UserScript==

GM_addStyle(""+
".ratingsBar { "+
"  transition: height 1s .15s; "+
"   height: 4px; "+
"  width: 100%; "+
"  position: absolute; "+
"  bottom: 0px; "+
"  } "+
".ratingsBar:hover { "+
"  transition: height .5s .15s; "+
"   height: 26px; "+
"  } "+
".dislikesBar { "+
"  height: 100%; "+
"  width: 100%; "+
"  position: absolute; "+
"  right: 0px; "+
"  background-color: #CC0000; "+
"  } "+
".likesBar { "+
"  height: 100%; "+
"  position: absolute; "+
"  background-color: #00BB22; "+
"  } "+
".powerBar { "+
"  height: 100%; "+
"  position: absolute; "+
"  background-color: #0029FF; "+
"  background-image: linear-gradient(90deg, transparent 70%, #35B5b9 70%); "+
"  background-size: 10px 100%; "+
"  } "+
".hatesBar { "+
"  height: 100%; "+
"  position: absolute; "+
"  background-color: #591DD1; "+
"  background-image: linear-gradient(90deg, transparent 70%, #C96695 70%); "+
"  background-size: 10px 100%; "+
"  } "+
".pausedBar { "+
"  height: 100%; "+
"  position: absolute; "+
"  background-color: #00bb22; "+
"  background-image: linear-gradient(-45deg, #99e449 25%, transparent 25%, transparent 50%, #99e449 50%, #99e449 75%, transparent 75%, transparent); "+
"  background-size: 20px 20px; "+
"  } "+
".textBar { "+
"  transition: opacity .25s .15s; "+
"   opacity: 0; "+
"  display: flex; "+
"  align-items: center; "+
"  width: 90%; "+
"  height: 16px; "+
"  padding: 5px 7.5% 5px 5px; "+
"  position: absolute; "+
"  bottom: 0px; "+
"  margin-bottom: 0px; "+
"  color: #f0f0c0; "+
"  font-family: arial,​sans-serif; "+
"  font-size: 11px; "+
"  line-height: 11px; "+
"  font-weight: 700; "+
"  text-align: left; "+
"  text-shadow: black 0px 0px 7px, black 1px 1px 5px, black 1px 1px 4px, black 1px 1px 3px, black 1px 1px 0px; "+
"  } "+
".scanned:hover > .ratingsBar > .textBar, "+
".video-list-item:hover .ratingsBar > .textBar, "+
".feed-item-main-content:hover .ratingsBar > .textBar { "+
"  transition: opacity .25s .15s; "+
"   opacity: 1; "+
"  } "+
".textBar span { "+
"  flex: 0 0 auto; "+
"  } "+
".textBar div { "+
"  display: flex; "+
"  flex-flow: row wrap; "+
"  flex: 1 1 auto; "+
"  } "+
".shadingBar { "+
"  transition: opacity 1s .15s; "+
"   opacity: 0; "+
"  height: 100%; "+
"  width: 100%; "+
"  position: absolute; "+
"  bottom: 0px; "+
"  background: linear-gradient( to bottom, rgba(0,0,0,0) 75%, rgba(0,0,0,.2) 90%, rgba(0,0,0,.6) 100% ) ; "+
"  } "+
".ratingsBar:hover > .shadingBar { "+
"  transition: opacity .5s .35s; "+
"   opacity: .85; "+
"  } "+
".middleBar, .powerBar, .dislikesBar, .pausedBar { "+
"  transition: opacity 1s .0s, outline 1s .0s, outline-offset 1s .0s,  border-right 1s .0s; height .5s .5s; "+
"   opacity: 1; "+
"   outline: 1px solid rgba(0,0,0,.0); "+
"   outline-offset: 10px; "+
"  } "+
".ratingsBar:hover > .middleBar, .ratingsBar:hover > .powerBar, "+
".ratingsBar:hover > .dislikesBar, .ratingsBar:hover > .pausedBar { "+
"  transition: opacity .55s .35s, outline .35s .55s, outline-offset 0s .55s, border-right .35s .55s, height .35s .0s; "+
"   opacity: 1; "+
"   outline: 1px solid rgba(255,255,255,.75); "+
"   outline-offset: 0px; "+
"  } "+
".ratingsBar:hover > .dislikesBar, "+
".ratingsBar:hover > .likesBar, "+
".ratingsBar:hover > .pausedBar { "+
"/*transition: height .25s .0s;*/ "+
"   height: 90%; "+
"  bottom: 0px; "+
"  } "+
".video-actions, .video-time { "+
"  margin-bottom: 4px; "+
"  } "+
".video-actions { "+
"  top: 2px; "+
"  } "+
".watched .video-thumb { "+
"  opacity: 1 !important; "+
"  } "+
".watched .video-thumb img { "+
"  transition: opacity 1s .25s; "+
"   opacity: .5 !important; "+
"  -webkit-transform: translate3d( 0px, 0px, 0px ); "+
"  transform: translate3d( 0px, 0px, 0px ); "+
"  } "+
".watched:hover .video-thumb img, "+
".feed-item-main-content:hover .video-thumb img { "+
"  transition: opacity .15s 0s; "+
"   opacity: 1 !important; "+
"  } "+
".scanned .yt-thumb-clip { "+
"  bottom: -96px; "+
"  } "+
".scanned .yt-thumb-default { "+
"  margin-bottom: 4px; "+
"  } "+
".yt-thumb-72.scanned > .ratingsBar > * { "+
"  zoom: .75 !important; "+
"  } "+
".playlist-video > .scanned > .ratingsBar > * { "+
"  zoom: .8; "+
"  } ");

var lastScanTime = new Date().getTime();

scanVideos();
document.onload = function() {
    scanVideos();
};

// On some pages, YouTube adds thumbnails as you scroll down the page,
// so this waits for scroll events and starts the scan for new video thumbnails.
// (it's a bit lazy, and something I want to change later)
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 ".scanned" class yet. Once they are scanned, they will be added to it.
    var videoList = document.querySelectorAll('a.ux-thumb-wrap[href^="/watch"] > span.video-thumb:not(.scanned), a.related-video[href^="/watch"] > span:first-child:not(.scanned), a.playlist-video[href^="/watch"] > span.yt-thumb-64:first-child:not(.scanned), a.yt-uix-sessionlink[href^="/watch"] > div.video-thumb:not(.scanned)');
    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 flux242's old script because I don't understand GM_xmlhttpRequest as well as I should.
// I modified it to get the view count and date. It all seems to work and doesn't throw errors, so... yeah.
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 "scanned" to avoid checking it over and over again
                    node.classList.add('scanned');
                }
            }
        }
    });
}

// the ratings bar is made up of differently colored divs stocked on top of each other
function makeBar(node, daysAgo, views, likes, dislikes) {
    // .ratingsBar is for the position (top/bottom) and size of the bar 
    var container = document.createElement('div');
    container.classList.add('ratingsBar');
    // barMsg is for the "Power: X%" or "View Count Incorrect" messages for the tooltip
    var barMsg = "";
    var totalVotes = likes + dislikes;
    if (dislikes > 0) {
        var dislikesBar = document.createElement('div');
        dislikesBar.classList.add('dislikesBar');
        container.appendChild(dislikesBar);
    }
    // Checks to see if the view count has been paused by YouTube. (301-309 views and less than half a day old, or more votes than views)
    // We do this because we need an accurate view count to calculate the Power Meter.
    // This lets the user know that we can't make one yet, but at least the likesBar/red ratings bar is still available
    if (((views > 300) && (views < 310) && (daysAgo <= 0.45)) || (totalVotes > views)) {
        if (likes > 0) {
            var pausedBar = document.createElement('div');
            pausedBar.classList.add('pausedBar');
            pausedBar.setAttribute("style","width:"+ (100 * likes / totalVotes) +"%;");
            container.appendChild(pausedBar);
        }
        barMsg = '<div>View Count Paused&nbsp;</div>';
    }
    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('likesBar');
                middleBar.setAttribute("style","width:"+(100 * likes / totalVotes)+"%;");
            }
            else {
                middleBar.classList.add('hatesBar');
                middleBar.setAttribute("style","width:"+powerMeterScore+"%;");
            } 
            container.appendChild(middleBar);
        }
        if (powerMeterScore > 0) {
            var powerBar = document.createElement('div');
            powerBar.classList.add('powerBar');
            if ((100 * likes / totalVotes) > powerMeterScore) {
                powerBar.style.width = powerMeterScore+"%";
            }
            else {
                powerBar.style.width = ((100 * likes / totalVotes))+"%";
            }
            barMsg = '<span><span style="color:#99ddff">'+ Math.round(powerMeterScore*10)/10 +'</span></span>&nbsp;&nbsp;';
            container.appendChild(powerBar);
        }
    }
    // shadingBar gives the bar a 3D look when hovered
    var shadingBar = document.createElement('div');
    shadingBar.classList.add('shadingBar');
    container.appendChild(shadingBar);
    // textBar is the div with the numbers on it.
    var textBar = document.createElement('div');
    textBar.classList.add('textBar');
    textBar.innerHTML = '<div>'+ barMsg +'<div">(<span style="color:#77ff77">+'+ likes +'&nbsp;</span>/<span style="color:#ff9977">&nbsp;-'+ dislikes +'</span>)</div></div>';
    container.appendChild(textBar);
    node.insertBefore(container,node.childNodes[2]);
    node.classList.add('scanned');
}

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