// ==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.14.b
// @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://greasyfork.org/users/253
// @require http://code.jquery.com/jquery-1.11.1.min.js
// ==/UserScript==
GM_addStyle(""+
".ratingsBar:hover > .likesBar, "+
".ratingsBar:hover > .dislikesBar, "+
".ratingsBar:hover > .pausedBar, "+
".ratingsBar:hover > .powerBar, "+
".ratingsBar:hover > .hatesBar { "+
" transition: height .25s .0s; "+
" height: 18px; "+
" } "+
" .likesBar, "+
" .dislikesBar, "+
" .pausedBar,"+
" .ratingsBar, "+
" .powerBar, "+
" .hatesBar { "+
" transition: height .25s .0s; "+
" height: 4px; "+
" position: absolute; "+
" bottom: 0px; "+
" } "+
".ratingsBar:hover { "+
" transition: height .25s .0s; "+
" height: 26px; "+
" }"+
" .ratingsBar { "+
" width: 100%; "+
" } "+
".powerBar,"+
".hatesBar { "+
" position: absolute; "+
" top: 0px; "+
" } "+
".textContainer { "+
" display: table; "+
" position: absolute; "+
" bottom: 0px; "+
" height: 26px; "+
" width: 100%; "+
"} "+
".textContainer:hover.short { "+
" transition: height .15s .0s !important; "+
" height: 18px; "+
" padding-top: 8px; "+
"} "+
" .textContainer.short { "+
" transition: height .5s .15s; "+
" height: 26px; "+
"} "+
".dislikesBar { "+
" width: 100%; "+
" right: 0px; "+
" background-color: #CC0000; "+
" } "+
".likesBar { "+
" background-color: #00BB22; "+
" } "+
".powerBar { "+
" background-color: #0029FF; "+
" background-position: right; "+
" background-size: 10px 100%; "+
" } "+
".hatesBar { "+
" background-image: linear-gradient(90deg, rgba(200,200,255,.65) 40%, #0029FF 40%); "+
" background-position: left; "+
" background-size: 10px 100%; "+
" } "+
" .pausedBar { "+
" 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; "+
" } "+
".yt-uix-simple-thumb-wrap:hover .textBar, "+
" .video-thumb:hover .textBar { "+
" transition: opacity .15s .0s; "+
" opacity: 1; "+
" } "+
" .textBar { "+
" transition: opacity .25s .25s; "+
" opacity: 0; "+
" display: table-cell; "+
" position: relative; "+
" vertical-align: middle; "+
" width: 100%; "+
" color: #f0f0c0; "+
" font-family: arial,sans-serif; "+
" font-size: 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; "+
" } "+
".textBar:hover > *:hover { "+
" transition: opacity .25s .15s; "+
" opacity: .5; "+
" } "+
".powerScore { "+
" display: inline-block; "+
" padding-left: 2px; "+
" } "+
".ratingsScore { "+
" display: inline-block; "+
" padding-left: 2px; "+
" } "+
".likesScore { "+
" color: #77ff77; "+
" } "+
".dislikesScore { "+
" color: #ff9977; "+
" padding-right: 2px; "+
" } "+
".ratingsBar:hover > .shadingBar { "+
" transition: opacity .25s .15s; "+
" opacity: .85; "+
" } "+
" .shadingBar { "+
" transition: opacity .25s .15s; "+
" opacity: 0; "+
" height: 100%; "+
" width: 100%; "+
" background: linear-gradient( to bottom, rgba(0,0,0,0) 75%, rgba(0,0,0,.2) 90%, rgba(0,0,0,.6) 100% ) ; "+
" } "+
".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; "+
" } "+
".load-more-button,"+
" .video-list-item { "+
" animation-duration: 3s; "+
" -webkit-animation-duration: 3s; "+
" animation-name: addedThumbnails; "+
" -webkit-animation-name: addedThumbnails; "+
" -webkit-animation-iteration-count: 1; "+
"} "+
"@keyframes addedThumbnails { "+
" from { "+
" outline-color: #0ff; "+
" } "+
" to { "+
" outline-color: #f00; "+
" } "+
"} "+
"@-webkit-keyframes addedThumbnails { "+
" from { "+
" outline-color: #0ff; "+
" } "+
" to { "+
" outline-color: #f00; "+
" } "+
"} ");
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();
}
};
// for detecting the Load More button (this needs some work)
var thingy = $(".feed-container, #body-container");
if (thingy) { buttonListen(); }
function buttonListen(thingy) {
$("#body-container, .feed-container, #watch-related").bind("animationstart webkitAnimationStart oAnimationStart MSAnimationStart", function(){ scanVideos(); console.log("Should be Scanning");});
$("#body-container, .feed-container, #watch-related").bind("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", function(){ scanVideos(); console.log("Should be Scanning");});
}
function scanVideos() {
lastScanTime = new Date().getTime();
// 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):not(.gettingData), a.related-video[href^="/watch"] > span:first-child:not(.scanned):not(.gettingData), a.playlist-video[href^="/watch"] > span.yt-thumb-64:first-child:not(.scanned):not(.gettingData), a.yt-uix-sessionlink[href^="/watch"] > div.video-thumb:not(.scanned):not(.gettingData)') ;
if (videoList.length > 0) {
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);
}
}
}
// 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) {
if ( !node.classList.contains("gettingData") ) {
node.classList.add('gettingData');
setTimeout(function(){node.classList.toggle("gettingData")},1000);
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);
}
}
}
});
}
}
// the ratings bar is made up of differently colored divs stacked on top of each other
function makeBar(node, daysAgo, views, likes, dislikes) {
var container = document.createElement('div');
container.classList.add('ratingsBar');
var barMsg = "";
var pausedMsg = "";
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, and/or more votes than views)
// We do this because we need an accurate view count to calculate the Power Meter.
// The green/yellow 'pausedBar' lets the user know that we can't make one yet, but at least the likesBar/red ratings bar is still available
if (((views >= 301) && (views <= 309) && (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);
}
pausedMsg = '<span class="powerScore"><i> Views Paused </i></span>';
}
else {
powerMeterScore = powerMeter(views, likes, dislikes);
if (likes > 0) {
var likesBar = document.createElement('div');
likesBar.classList.add('likesBar');
likesBar.setAttribute("style","width:"+(100 * likes / totalVotes)+"%;");
container.appendChild(likesBar);
}
// shadingBar gives the ratings bar a 3D look when hovered
var shadingBar = document.createElement('div');
shadingBar.classList.add('shadingBar');
container.appendChild(shadingBar);
if ((100 * likes / totalVotes) < powerMeterScore) {
var hatesBar = document.createElement('div');
hatesBar.classList.add('hatesBar');
hatesBar.setAttribute("style","width:"+(powerMeterScore - (100 * likes / totalVotes))+"%; margin-left: "+(100 * likes / totalVotes)+"%;");
container.appendChild(hatesBar);
}
if (powerMeterScore >= 0.0455) {
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 class="powerScore"> <span style="color:#99ddff">'+ Math.round(powerMeterScore*10)/10 +'</span> </span>';
container.appendChild(powerBar);
}
}
var textContainer = document.createElement('span');
textContainer.classList.add('textContainer');
if (powerMeterScore < 0.0455 || pausedBar) {textContainer.classList.add('short');}
var textBar = document.createElement('span');
textBar.classList.add('textBar');
textBar.innerHTML = barMsg+pausedMsg +'<span class="ratingsScore"> (<span class="likesScore">+'+ likes +' </span>/<span class="dislikesScore"> -'+ dislikes +'</span>) </span>';
textContainer.appendChild(textBar);
container.appendChild(textContainer);
if ( !node.classList.contains("scanned") ) {
node.insertBefore(container,node.childNodes[2]);
node.classList.add('scanned');
}
}
// trade secrets
function powerMeter(view1, likes, dislikes) {
var viewLikeRatio;
var views = view1 - dislikes
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;
}