// ==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.04 // @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 // @downloadURL none // ==/UserScript== GM_addStyle(""+ ".ratingsBar, .dislikesBar, .likesBar, .powerBar, .hatesBar, .pausedBar, .shadingBar, .textBar { "+ " position: absolute; "+ " bottom: 0px; "+ " } "+ ".ratingsBar:hover > .likesBar, "+ ".ratingsBar:hover > .dislikesBar, "+ ".ratingsBar:hover > .pausedBar { "+ " transition: height .25s .25s; "+ " height: 20px !important; "+ " } "+ ".likesBar, "+ ".dislikesBar, "+ ".pausedBar { "+ " transition: height .5s .0s; "+ " height: 4px; "+ " } "+ ".ratingsBar:hover, "+ ".ratingsBar:hover > .powerBar, "+ ".ratingsBar:hover > .hatesBar { "+ " transition: height .5s .0s; "+ " height: 26px !important; "+ " } "+ ".ratingsBar, "+ ".powerBar, "+ ".hatesBar { "+ " transition: height .25s .25s; "+ " height: 4px; "+ " } "+ ".ratingsBar { "+ " width: 100%; "+ " margin-bottom: .4px; "+ " } "+ ".dislikesBar { "+ " width: 100%; "+ " right: 0px; "+ " background-color: #CC0000; "+ " } "+ ".likesBar { "+ " background-color: #00BB22; "+ " } "+ ".powerBar { "+ " background-image: linear-gradient(90deg, rgba(200,200,255,.6) 20%, #0029FF 20%); "+ " background-position: right; "+ " background-size: 10px 100%; "+ " } "+ ".hatesBar { "+ " background-image: linear-gradient(90deg, rgba(200,200,255,.6) 20%, #0029FF 20%); "+ " 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; "+ " } "+ ".textBar { "+ " transition: opacity .25s .25s; "+ " opacity: 0; "+ " display: flex; "+ " align-items: center; "+ " width: 90%; "+ " height: 16px; "+ " padding: 5px 7.5% 5px 5px; "+ " 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 .15s .0s; "+ " 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%; "+ " 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; "+ " } "+ ".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, 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); } barMsg = '
View Count Paused 
'; } else { powerMeterScore = powerMeter(views, likes); 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) { 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 = ''+ Math.round(powerMeterScore*10)/10 +'  '; container.appendChild(powerBar); } } // textBar is the div with the numbers on it. var textBar = document.createElement('div'); textBar.classList.add('textBar'); textBar.innerHTML = '
'+ barMsg +'(+'+ likes +' / -'+ dislikes +')
'; 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; }