- // ==UserScript==
- // @name Add movie ratings to IMDB links [By Pharaoh2k]
- // @description Adds movie ratings and number of voters to links on IMDB. Modified version of http://userscripts.org/scripts/show/96884
- // @author StackOverflow community (especially Brock Adams)
- // @version 2023-01-28-01-Pharaoh2k
- // @license MIT
- // @match *://www.imdb.com/*
- // @grant GM_xmlhttpRequest
- // TODO: Remove this
- // @grant unsafeWindow
- // @grant GM_addStyle
- // @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
- // @namespace https://greasyfork.org/users/2427
- // @derived-from https://greasyfork.org/en/scripts/2033-add-imdb-rating-votes-next-to-all-imdb-movie-series-links-improved
- // ==/UserScript==
- // Special Thanks to Brock Adams for this script: http://stackoverflow.com/questions/23974801/gm-xmlhttprequest-data-is-being-placed-in-the-wrong-places/23992742
-
- var maxLinksAtATime = 100; //-- Pages can have 100's of links to fetch. Don't spam server or browser.
- var skipEpisodes = true; //-- I only want to see ratings for movies or TV shows, not TV episodes.
- var showAsStar = false; //-- Use IMDB star instead of colored div, less info but more consistent with the rest of the site.
- var addRatingToTitle = true; //-- Adds the rating to the browser's title bar (so rating will appear in browser bookmarks).
- var showMetaScore = true; //-- When the metascore is available, show it
- var useLightBackground = false; //-- If you prefer the site to have a light grey background
-
- if (useLightBackground) {
- GM_addStyle('.ipc-page-background { background: #e3e2dd !important; color: black !important; }');
- // You could also try #262626 for a dark grey but not black background
- }
-
- // Nov 2022 design has `display: flex` to make all/some info flow downwards, which causes our rating to appear below the link, instead of after it
- // TODO: A better solution might be to replace the <a> with a <div> containing the <a> and our rating
- // TODO: Or we could try putting the rating inside the link
- // TODO: Or we could make the rating float after the link, using position: absolute
- GM_addStyle(`
- /* For the "Known For" section */
- .ipc-primary-image-list-card__content-top {
- flex-direction: row;
- }
-
- /* For the "Credits" section */
- .ipc-metadata-list-summary-item__tc {
- display: initial;
- }
- `);
-
- // The old iMDB site exposed jQuery, but the new one does not
- //var $ = unsafeWindow.$;
- // This was exposed by the @require
- var $ = jQuery;
-
- var fetchedLinkCnt = 0;
-
- //const ratingSelectorNew = '.ipc-button > div > div > div > div > span:first-child';
- //const ratingSelectorNew = '.ipc-button > div > div > div > div > span:first-child';
- const ratingSelectorNew = "*[data-testid='hero-rating-bar__aggregate-rating__score'] > span:nth-child(1)";
- const voteCountSelectorNew = "*[data-testid='hero-rating-bar__aggregate-rating__score'] + div + div";
-
- function processIMDB_Links() {
- //--- Get only links that could be to IMBD movie/TV pages.
- var linksToIMBD_Shows = document.querySelectorAll("a[href*='/title/']");
-
- var lastLinkProcessed;
-
- for (var J = 0, L = linksToIMBD_Shows.length; J < L; J++) {
- const currentLink = linksToIMBD_Shows[J];
-
- /*--- Strict tests for the correct IMDB link to keep from spamming the page
- with erroneous results.
- */
- if (!/^(?:www\.)?IMDB\.com$/i.test(currentLink.hostname) ||
- !/^\/title\/tt\d+\/?$/i.test(currentLink.pathname)
- )
- continue;
-
- // I am beginning to think a whitelist might be better than this blacklist!
-
- // Skip if in Bio
- if ($(currentLink).hasClass("ipc-md-link")) {
- continue;
- }
-
- // Skip thumbnails on the search results page
- if ($(currentLink).closest('.primary_photo').length) {
- continue;
- }
-
- // Skip thumbnails in the six recommendations area of a title page
- if ($(currentLink).closest('.rec_item, .rec_poster').length) {
- continue;
- }
-
- // Skip top-rated episodes on the right-hand sidebar of TV series pages; they already display a rating anyway!
- if ($(currentLink).closest('#top-rated-episodes-rhs').length) {
- continue;
- }
-
- // Skip thumbnail of title at top of Season page
- if ($(currentLink).find(':only-child').prop('tagName') === 'IMG') {
- continue;
- }
-
- // Skip the thumbnail of each episode on a season page (episode names still processed)
- if ($(currentLink).closest('.image').length) {
- continue;
- }
-
- // Skip thumbnails in "Known For" section of actor pages
- if ($(currentLink).closest('.known-for, .knownfor-title').length && $(currentLink).find('img').length) {
- continue;
- }
-
- // Skip links to character pages
- // || currentLink.href.includes('/characters/')
- if ($(currentLink).closest('td.character').length) {
- continue;
- }
-
- // Skip episodes on actor pages
- if (skipEpisodes && $(currentLink).closest('.filmo-episodes').length) {
- continue;
- }
-
- // On an episode page, skip the next/previous buttons
- if ($(currentLink).closest('.bp_item').length) {
- continue;
- }
-
- // New layout 2021
-
- // The thumbnails on the "More like this" video cards
- if ($(currentLink).closest('.ipc-lockup-overlay').length) {
- continue;
- }
-
- if (typeof lastLinkProcessed !== 'undefined') {
- lastLinkProcessed = /[^\?]*/.exec(lastLinkProcessed)[0];
- // console.log("lastLinkProcessed="+lastLinkProcessed);
-
- }
- // console.log("currentLink.href="+currentLink.href.split('?')[0]);
-
- continueBttn.style.display = 'inline';
- continueBttn.style.top = '0px';
- continueBttn.style.left = '50%';
- continueBttn.style.position = 'fixed';
- continueBttn.style.height = '30px';
- continueBttn.style.width = '170px';
- continueBttn.style.color = 'black';
- continueBttn.style.zIndex = '1000';
- continueBttn.style.backgroundColor = 'rgba(245, 245, 149, 0.7)';
- continueBttn.style.boxShadow = '0 6px 6px rgb(0 0 0 / 60%)';
- currentLink.parentNode.insertBefore(continueBttn, currentLink);
-
-
- // Nov 2022: In the list of titles for an actor, there are now two <a>s in each row.
- // if (lastLinkProcessed && currentLink.href === lastLinkProcessed.href) {
- if (lastLinkProcessed === currentLink.href.split('?')[0]) {
- currentLink.setAttribute("data-gm-fetched", "true");
- continue;
- }
-
- if (!currentLink.getAttribute("data-gm-fetched")) {
- if (fetchedLinkCnt >= maxLinksAtATime) {
- //--- Position the "continue" button.
-
- break;
- }
-
-
- //fetchTargetLink (currentLink); //-- AJAX-in the ratings for a given link.
-
- // Stagger the fetches, so we don't overwhelm IMDB's servers (or trigger any throttles they might have)
- // Needs currentLink to be a const, or a closure around it
- setTimeout(() => fetchTargetLink(currentLink), 300 * fetchedLinkCnt);
-
- //---Mark the link with a data attribute, so we know it's been fetched.
- currentLink.setAttribute("data-gm-fetched", "true");
- lastLinkProcessed = currentLink;
- fetchedLinkCnt++;
- }
- }
- }
-
- function fetchTargetLink(linkNode) {
- //--- This function provides a closure so that the callbacks can work correctly.
-
- //console.log("Fetching " + linkNode.href + ' for ', linkNode);
-
- /*--- Must either call AJAX in a closure or pass a context.
- But Tampermonkey does not implement context correctly!
- (Tries to JSON serialize a DOM node.)
- */
- GM_xmlhttpRequest({
- method: 'get',
- url: linkNode.href,
- //context: linkNode,
- onload: function(response) {
- prependIMDB_Rating(response, linkNode);
- },
- onload: function(response) {
- prependIMDB_Rating(response, linkNode);
- },
- onabort: function(response) {
- prependIMDB_Rating(response, linkNode);
- }
- });
- }
-
- function prependIMDB_Rating(resp, targetLink) {
- var isError = true;
- var ratingTxt = "** Unknown Error!";
- var colnumber = 0;
- var justrate = 'RATING_NOT_FOUND';
-
- if (resp.status != 200 && resp.status != 304) {
- ratingTxt = '** ' + resp.status + ' Error!';
- } else {
- // Example value: ["Users rated this 8.5/10 (", "8.5/10"]
- //var ratingM = resp.responseText.match (/Users rated this (.*) \(/);
- // Example value: ["(1,914 votes) -", "1,914"]
- //var votesM = resp.responseText.match (/\((.*) votes\) -/);
-
- var doc = document.createElement('div');
- doc.innerHTML = resp.responseText;
- var elem = doc.querySelector('.title-overview .vital .ratingValue strong');
-
- var ratingT, votesT;
- if (elem) {
- // Old site
- var title = elem && elem.title || '';
-
- ratingT = title.replace(/ based on .*$/, '');
- votesT = title.replace(/.* based on /, '').replace(/ user ratings/, '');
- } else {
- // New site
- var ratingElem = doc.querySelector(ratingSelectorNew);
- ratingT = ratingElem && ratingElem.textContent || '';
-
- var votesElem = doc.querySelector(voteCountSelectorNew);
- votesT = votesElem && votesElem.textContent || '';
-
- //console.log('ratingElem', ratingElem);
- //console.log('votesElem', votesElem);
-
- if (votesT.slice(-1) == 'K') {
- votesT = String(1000 * votesT.slice(0, -1));
- } else if (votesT.slice(-1) == 'M') {
- votesT = String(1000000 * votesT.slice(0, -1));
- }
- // Add in commas (to match old format)
- votesT = votesT.replace(/(\d)(\d\d\d)(\d\d\d)$/, '$1,$2,$3').replace(/(\d)(\d\d\d$)/, '$1,$2');
- //console.log('votesT:', votesT);
- }
- // The code below expects arrays (originally returned by string match)
- var ratingM = [ratingT, ratingT + "/10"];
- var votesM = [votesT, votesT];
-
- //console.log('ratingM', ratingM);
- //console.log('votesM', votesM);
-
- // This doesn't work on the new version of the site
- //if (/\(awaiting \d+ votes\)|\(voting begins after release\)|in development,/i.test (resp.responseText) ) {
- // hopefully this will work better
- if (ratingT == '' || votesT == '') {
- ratingTxt = "NR";
- isError = false;
- colnumber = 0;
- } else {
- if (ratingM && ratingM.length > 1 && votesM && votesM.length > 1) {
- isError = false;
-
- justrate = ratingM[1].substr(0, ratingM[1].indexOf("/"));
-
- // Let's try the metascore instead
- // Not all movied have a metascore
- var metaScoreElem = showMetaScore && doc.querySelector('.score-meta');
- //var metaScore = metaScoreElem && (Number(metaScoreElem.textContent) / 10).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 });
- var metaScore = metaScoreElem && metaScoreElem.textContent;
- var metaScoreColor = metaScoreElem && metaScoreElem.style.backgroundColor;
-
- var votes = votesM[1];
- var votesNum = Number(votes.replace(',', '', 'g'));
- var commas_found = (votes.match(/,/g) || []).length;
- if (commas_found === 1) {
- votes = votes.replace(/,\d\d\d$/, 'k');
- } else if (commas_found === 2) {
- votes = votes.replace(/,\d\d\d,\d\d\d$/, 'M');
- }
-
- // ratingTxt = ratingM[1] + " - " + votesM[1];
- // We use the element style to override IMDB's reset
- ratingTxt = "<strong style=\"font-weight: bolder\">" + justrate + "</strong>" + " / " + votes;
- //ratingTxt = "<strong>" + (metaScoreElem ? metaScore : justrate) + "</strong>" + " / " + votes;
- //ratingTxt = "<strong>" + (metaScoreElem ? metaScore : justrate) + "</strong>" + " / " + votes + (metaScoreElem ? " (" + justrate + "i)" : "" );
- //ratingTxt = "<strong>" + justrate + "</strong>" + " / " + votes + (metaScoreElem ? " (<strong>" + metaScore + "</strong> meta)" : "" );
- colnumber = Math.round(justrate);
- // If metaScore was found, use that for the colour instead of the IMDB rating. But since metascores are lower than imdb scores, add 1.5.
- //colnumber = Math.round(metaScoreElem ? metaScore / 10 + 1.5 : justrate);
-
- //if (metaScoreElem) {
- // justRate = metaScore / 10;
- //}
- }
- }
- }
-
- //console.log('ratingTxt', ratingTxt);
- //console.log('justrate', justrate);
-
- // NOTE: I switched from <b> to <strong> simply because on Season pages, the rating injected after episode titles was getting uglified by an IMDB CSS rule: .list_item .info b { font-size: 15px; }
- //targetLink.setAttribute("title", "Rated " + ratingTxt.replace(/<\/*strong>/g,'').replace(/\//,'by') + " users." );
- targetLink.setAttribute("title", `Rated ${justrate} by ${votes} users.`);
-
- if (!(justrate > 0)) {
- return;
- }
-
-
- // Slowly approach full opacity as votesNum increases. 10,000 votes results in opacity 0.5 (actually 0.6 when adjusted).
- var opacity = 1 - 1 / (1 + 0.0001 * votesNum);
- // Actually let's not start from 0; we may still want to see the numbers!
- opacity = 0.2 + 0.8 * opacity;
- // Don't use too many decimal points; it's ugly!
- //opacity = Math.round(opacity * 10000) / 10000;
- opacity = opacity.toFixed(3);
-
- var colors = ["#Faa", "#Faa", "#Faa", "#Faa", "#Faa", "#F88", "#Faa", "#ff7", "#7e7", "#5e5", "#0e0", "#ddd"];
- var bgCol = colors[colnumber];
- //var hue = justrate <= 6 ? 0 : justrate <= 8 ? 120 * (justrate - 6) / 2 : 120;
- //var bgCol = `hsla(${hue}, 100%, 60%, ${opacity})`;
- var resltSpan = document.createElement("span");
- // resltSpan.innerHTML = '<b><font style="border-radius: 5px;padding: 1px;border: #575757 solid 1px; background-color:' + color[colnumber] + ';">' + ' [' + ratingTxt + '] </font></b> ';
- // resltSpan.innerHTML = '<b><font style="background-color:' + justrate + '">' + ' [' + ratingTxt + '] </font></b> ';
- // I wanted vertical padding 1px but then the element does not fit in the "also liked" area, causing the top border to disappear! Although reducing the font size to 70% is an alternative.
- resltSpan.innerHTML = ' <font style="font-weight: normal;font-size: 80%;opacity: ' + opacity + ';border-radius: 3px;padding: 0.1em 0.6em;border: rgba(0,0,0,0.1) solid 1px; background-color:' + bgCol + ';color: black;">' + '' + ratingTxt + '</font>';
-
- if (showAsStar) {
- resltSpan.innerHTML = `
- <div class="ipl-rating-star" style="font-weight: normal">
- <span class="ipl-rating-star__star">
- <svg class="ipl-icon ipl-star-icon " xmlns="http://www.w3.org/2000/svg" fill="#000000" height="24" viewBox="0 0 24 24" width="24">
- <path d="M0 0h24v24H0z" fill="none"></path>
- <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path>
- <path d="M0 0h24v24H0z" fill="none"></path>
- </svg>
- </span>
- <span class="ipl-rating-star__rating">${justrate}</span>
- </div>
- `;
- }
-
- if (isError)
- resltSpan.style.color = 'red';
-
- //var targetLink = resp.context;
- //console.log ("targetLink: ", targetLink);
-
- // The "More like this" cards have a vertical flowing grid, so if we want rating and metascore to appear next to each other, they will need a container
- var container = document.createElement('div');
- container.style.display = 'inline-block';
- container.appendChild(resltSpan);
-
- //targetLink.parentNode.insertBefore (container, targetLink);
- targetLink.parentNode.insertBefore(container, targetLink.nextSibling);
-
- if (metaScoreElem) {
- // I am reluctant to move an element from another document into this one, multiple times.
- // Therefore we create a new element, like the original.
- var newMetaScoreElem = document.createElement(metaScoreElem.tagName);
- //newMetaScoreElem.outerHTML = metaScoreElem.outerHTML;
- newMetaScoreElem.className = metaScoreElem.className;
- newMetaScoreElem.textContent = metaScoreElem.textContent;
- newMetaScoreElem.style.backgroundColor = metaScoreElem.style.backgroundColor;
- // Missing despite the class. It seems some pages don't include the .score-meta CSS
- newMetaScoreElem.style.color = 'white';
- newMetaScoreElem.style.padding = '2px';
- //resltSpan.parentNode.insertBefore (newMetaScoreElem, resltSpan.nextSibling);
- //resltSpan.parentNode.insertBefore (document.createTextNode(' '), resltSpan.nextSibling);
- container.appendChild(document.createTextNode(' '));
- container.appendChild(newMetaScoreElem);
- }
- }
-
- //--- Create the continue button
- var continueBttn = document.createElement("button");
- continueBttn.innerHTML = "Get more ratings";
-
- continueBttn.addEventListener("click", function() {
- fetchedLinkCnt = 0;
- continueBttn.style.display = 'none';
- processIMDB_Links();
- },
- false
- );
-
- processIMDB_Links();
-
- if (addRatingToTitle) {
- setTimeout(function() {
- // Selectors for old site and new site
- var foundRating = document.querySelectorAll('.ratingValue [itemprop=ratingValue], ' + ratingSelectorNew);
- if (foundRating.length >= 1) {
- var rating = foundRating[0].textContent;
- if (rating.match(/^[0-9]\.[0-9]$/)) {
- document.title = `(${rating}) ` + document.title;
- }
- }
- }, 2000);
- }