nascent / MrSkin Ratings in IMDb

// ==UserScript==
// @name        MrSkin Ratings in IMDb
// @namespace   mrskin.com
// @description Display an actress' MrSkin Rating and a link to her MrSkin page
// @include     http://*.imdb.com/name/nm*
// @include     http://*.imdb.com/title/tt*
// @version     1.24
//
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @connect imdb.com
// @connect mrskin.com
//
// @history 1.24 Fixed Title pages.  Found new data on MrSkin pages to extract info from as backup and to provide additional info in tooltip where available. Quick workaround to have better looking stars on new design title pages (reuse imdb's own stars)
// @history 1.23 Fixed support for New IMDB Title Design.  Edited line break for rating on new design so only occurs on name and not title pages.  Stars look ugly on New design Title Page, will modify later.
// @history 1.22 Fixed some missing semicolons.  Added line break for rating on new design so it doesn't truncate.
// @history 1.21 Fixed regex for 'new design' IMDB, apparently they updated the code at some point.  Thanks derjanb.
// @history 1.20 Removed native Chrome support (as Chrome doesn't allow 3rd party scripts anymore anyway). Bringing back compatibility for Firefox.  To use with Chrome, this scripts must be used with TamperMonkey now.
// @history 1.19 Changes to try to comply to TM 4.0 update
// @history 1.18 As MrSkin no longer stipulates Best Matches, it's not 100% if the MrSkin results are the correct actress/title. Now prompts if search results doesn't 100% match, might show for slightly different spellings or grammar.
// @history 1.17 MrSkin redesign rewrite. Remove autoupdater (all clients should do this automaticlly with greasemonkey and tampermonkey now).
// @history 1.16 Fix for current iterations of IMDB and MrSkin.
// @history 1.15 MrSkin removed search landing page.  Also Fix of formatting of code with last version.
// @history 1.14 Fix clickable rating link for new mrskin exact-match search skips.
// @history 1.13 Title check to ensure MrSkin results match.
// @history 1.12 MrSkin integration with IMDB Title pages. (Option)
// @history 1.11 Fix for MrSkin site change: skip search results on popular match.
// @history 1.10 Fix for missing MrSkin star resources
// @history 1.09 Google Chrome support. Fix for nudity roles in tooltip.
// @history 1.08 New IMDB design support. Fix for checking underage actresses.
// @history 1.07 Bug fix (search results url restored). 
// @history 1.06 User requested option for opening links in new tab
// @history 1.05 Fixed bug with script not working with unique names that have no similarities to other actresses.
// @history 1.04 Removed unecessary regex line
// @history 1.03 Made actress comparison case insensitive e.g: Clea DuVall. Fixed search url for when actress isn't found.
// @history	1.02 Added option to add google image search link
// @history	1.01 Fix for update of site and fixed mistaken use of constant rating image
// @history	1.00 Initial release
// @license GPL-3.0-or-later
// @updateURL https://openuserjs.org/meta/nascent/MrSkin_Ratings_in_IMDb.meta.js
// @downloadURL https://openuserjs.org/install/nascent/MrSkin_Ratings_in_IMDb.user.js
// ==/UserScript==

//Based on http://userscripts.org/scripts/2273

//Settings
var googleLink; //Show link to google image search
var openInNewTab; //Open created links in new tab
var mrSkinInTitlePages; //Integrate MrSkin ratings into IMDB title pages.

var mrskinDiscrepancy = ""; //Boolean variable to flag if MrSKin doesn't find an exact match for IMDB Acterss/Title (this will allow for UI notice on IMDB)

var isTitle = false; //Temporary global variable to check if current page is actress or title
var imdbNewDesign = false; //Check if current page is loaded in reference/classic design or new design

(function() {
	
	//Settings (As no settings interface is displayed, just edit the settings directly from about:config)
	googleLink = GM_getValue("googleLink", true);
	openInNewTab = GM_getValue("openInNewTab", false);
	mrSkinInTitlePages = GM_getValue("mrSkinInTitlePages", false);
	

	/* Check for actress */

    /* Below code checks age of acctress to save lookup as MrSkin only has 18+ actresses in database */
	isTitle = document.URL.lastIndexOf("\/title\/tt") >= 0; //IMDB Title page or Name page?
	if (!isTitle && document.URL.lastIndexOf("\/name\/nm") >= 0) { // IMDB Name page (not Title page)
        if (
			//Check if actress in IMDB classic design
			document.evaluate(
				'//h5/a[text()=\'Actress:\']', document, null,
				XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
			).snapshotLength == 0
		) {
			/* Returned not an actress. */
			if (
				//Check if actress in IMDB new 2012 design
				document.evaluate(
					//'//a[.=\'Actress\']', document, null,
					'//div[@class=\'infobar\']/a[@href=\'#actress\']', document, null,
					XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
				).snapshotLength == 0
			) {
				/* This isn't an actress, ignore. */
				return;
			}
		}

		// Check for legal age

		// Get year
		var now = new Date();
		var years = document.evaluate(
			"//a[contains(@href,'birth_year')]", document, null,
			XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
		);

		if (years.snapshotLength > 0) {
			var href = new String(years.snapshotItem(0));
			var bornYear = href.substring(href.length - 4);
			if ((now.getFullYear() - bornYear) < 18) {
				// Too young
				return;
			}
			if ((now.getFullYear() - bornYear) == 18) {
				// Might still be too young
				var dates = document.evaluate(
					"//a[contains(@href,'OnThisDay')]", document, null,
					XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
				);
				if (dates.snapshotLength > 0) {
					href = new String(dates.snapshotItem(0));

					var dateindex   = href.indexOf('day=');
					var monthindex = href.indexOf('month=');

					var day = href.substring(dateindex + 4, monthindex - 1);
					var month = href.substring(monthindex + 6);

					// convert month into a number
					switch (month) {
						case 'January':         month = 0;      break;
						case 'February':        month = 1;      break;
						case 'March':           month = 2;      break;
						case 'April':           month = 3;      break;
						case 'May':             month = 4;      break;
						case 'June':            month = 5;      break;
						case 'July':            month = 6;      break;
						case 'August':          month = 7;      break;
						case 'September':       month = 8;      break;
						case 'October':         month = 9;      break;
						case 'November':        month = 10;     break;
						case 'December':        month = 11;     break;
					}

					if (month > now.getMonth()) {
						// Too young
						return;
					}
					if (month == now.getMonth()) {
						if (day > now.getDate()) {
							// Too young
							return;
						}
					}
				}
			}
		}
	}
    
    /* end of check of actress age on name pages */


    /* IMDB parsing and MrSkin Lookup */
    //Lookup Actress/Title on Classic/Reference Design
	var s = document.evaluate(
		'id(\'tn15title\')/h1', document, null,
		XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
	).snapshotItem(0);
	
	//IMDB new (2012) design support
    //This could be replaced by extracting info from the Title tag, but I like this as it ensures the pages are as expected, may change in future
	if (s == null) { //classic design not working (check s is empty)
		//Check for Actress
        s = document.evaluate(
			//'//td[@id=\'overview-top\']/h1', document, null,
              '//td [contains(@id,"overview-top")] //span [contains(@itemprop,"name")]', document, null,
			XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
		).snapshotItem(0);
        imdbNewDesign = true;
	}
    if (s === null) { //classic design and new design actress not working (check s is empty)
		//Check for Title
        s = document.evaluate(
              '//div [contains(@class,"title_wrapper")] //h1 [contains(@itemprop,"name")]', document, null,
			XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
		).snapshotItem(0);
        imdbNewDesign = true;
	}
	if (s !== null) { //Found elements, continuing lookup on MrSkin  (check s is not empty)
		var span = document.createElement("span");
		span.setAttribute("style", "font-size:small");
		s.parentNode.insertBefore(span, s.nextSibling);

		span.appendChild(document.createTextNode(" "));
		var actressname = trim(s.childNodes.item(0).data.replace(/ \(I+\)$/, "").replace(/ /g, " "));
        if (actressname.length === 0) console.log("Warning, " + "MrSkin Ratings in IMDb script unable to parse Actress from IMDB - has there been a redesign?");

        //alert("http://www.mrskin.com/search/search?term=" +escape(trim(s.childNodes.item(0).data.replace(/ \(I+\)$/, "").replace(/ /g, "+"))));
		//findActress(span,s,actressname,"http://www.mrskin.com/search/search?term=" +escape(trim(s.childNodes.item(0).data.replace(/ \(I+\)$/, "").a(/ /g, "+"))));
        findActress(span,s,actressname,"http://www.mrskin.com/search/api.json?term=" +escape(trim(s.childNodes.item(0).data.replace(/ \(I+\)$/, ""))));
        //alert(escape(trim(s.childNodes.item(0).data.replace(/ \(I+\)$/, ""))));
	}
    else
    { 
        //Cant find on New (2012) Design either
        console.log("Warning, " + "MrSkin Ratings in IMDb script unable to parse IMDB - has there been a redesign?");
    }

	//Save settings
	GM_setValue('googleLink', googleLink);
	GM_setValue('openInNewTab', openInNewTab);
	GM_setValue('mrSkinInTitlePages', mrSkinInTitlePages);

})();

function trim(stringToTrim) {
	return stringToTrim.replace(/^\s+|\s+$/g,"");
}

function findActress(span,s,actressname,link){
    var MrSkinJSON;
    GM_xmlhttpRequest({
        method: "GET",
        url: link,
        synchronous: true,
        headers: {
            //GET /search/celebs?term=jennifer+aniston+ HTTP/1.1\r\n
            'Host': 'www.mrskin.com',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Upgrade-Insecure-Requests': '1',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36',
            'DNT': '1',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': "en-US,en;q=0.8,en-GB;q=0.6"
        },
        onload:function(result) {
            
            if (!result.responseJSON) {
                MrSkinJSON = JSON.parse(result.responseText);
            }
	  
			var actress;  //(ALL,url,name)
			//var rating;	  //(ALL,ratingNumber,ratingSummary,"Node rules:",nudeRolesNumber)
						  //(Rating, Nudity summary, #Nude roles)
			
            //Search MrSkin
            if (MrSkinJSON.results.length > 0) {
                var movieTitle = actress = MrSkinJSON.results[0].name; //movieTitle is just assigned to actress to act as an alias.

                //Compare IMDB Actress/Title with MrSkin Actress/Title result
                var tmpTitle = ""; //Temporary variable for comparing title of MrSkin page against IMDB page
                if (MrSkinJSON.results[0].type == "title"){ //if MrSkin page is a movie title page rather than actress page
                    // Uses reverse regex to remove the last brackets in case a title has brackets in  \([^\(]*$
                    //Uses standard regex to remove first set of brackets found (could cause issues if title has brackets within it) \([0-9]+\)
                    tmpTitle = trim(movieTitle.replace(/\([^\(]*$/, "")); //extract the (YYYY) backets and year info from title
                }
                else //returned page is an actress page and not a movie title
                {
                    tmpTitle = actress; //store actress name in temp comparison variable instead to avoid comparison issues in next query
                }

                //alert("IMDB: " + actressname + ", MrSkin: " + MrSkinJSON.results[0].name);

                if(actressname.toLowerCase() == tmpTitle.toLowerCase()){
                    //Results match perfectly.
                }
                else
                {
                    //Results don't match, could be spelling discrepancy but could be correct result.
                    console.log("Info, " + "MrSkin actress/title result differs from IMDB actress/title\nThis could be the correct result with spelling discrepnancy, or could be that MrSkin search can't find actress/title.\n" + "IMDB: " + actressname + ", MrSkin: " + tmpTitle);
                    mrskinDiscrepancy = actress;
                    //Code to go here to cycle through array and compare all MrSkin results to see if better match exists.
                }

                //Get MrSkin Rating/Nudity info and display
                getRating(span,s,MrSkinJSON.results[0].name,"http://www.mrskin.com" + MrSkinJSON.results[0].url);
            }
            else
            {
                console.log("Warning, " + "No MrSkin Search Results for Actress/Title");
            }
		}
   });
}

function getRating(span,s,actressname,link){
    
    GM_xmlhttpRequest({
      method: "GET",
        url: link,
        synchronous: true,
        headers: {
            //GET /search/celebs?term=jennifer+aniston+ HTTP/1.1\r\n
            'Host': 'www.mrskin.com',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Upgrade-Insecure-Requests': '1',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36',
            'DNT': '1',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': "en-US,en;q=0.8,en-GB;q=0.6"
        },
	  onload: function(result) {
	  
          var actress;  //(ALL,url,name)
          // ratingArray;	  //(ALL,ratingNumber,ratingSummary,"Nude roles:",nudeRolesNumber)
						  //(Rating, Nudity summary, #Nude roles)


          //(Rating, Nudity summary, "Nude roles:", #Nude roles) - Rating, Type of nudity and how many nude roles
          

          var ratingArray = result.responseText.match(/<span class='detail-type'>Keywords:<\/span>[\s\S].+?rating=(.+?)">(.+?)<\/a>/); //This works for actress pages
          var rating;  var ratingSummary; var nudeRoles;
          var fakeBreasts; var breastSize; var bodyType; var hairColor; //nudeAppearancesCount = Nude Roles // starRating/titleNudityRating = rating 

          if (!(ratingArray)){ //No result, possibly Title page
              ratingArray = result.responseText.match(/<div class='object-rating'>[\s\S]+?star-rating[\s\S]+?rating-string'>(.+)<\/span>/);
              
              if (ratingArray){ //result found
              //Try counting the active star images
              rating = (ratingArray[0].match(/"fa fa-star active"/g) || []).length; //Counts stars
              ratingSummary = ratingArray[1]; //Summary of nudity such as "Brief Nudity" 
              
              //Try extracting the info from the "_track_page" script in header
              
                  //Clear no longer needed array
              ratingArray.length = 0;
                  
              //<div class='object-rating'>[\s\S]+?star-rating([\s\S])+?<\/div>
              ratingArray = result.responseText.match(/<script>[\s\S]+?titleNudityRating":([\s\S]+?),[\s\S]+?nudeAppearancesCount":([\s\S]+?)}[\s\S]+?<\/script>/);
              if (ratingArray){ //Not Empty
                  if (!(rating)){ //Empty
                      rating = ratingArray[1]; //titleNudityRating
                  }
                  nudeRoles = ratingArray[2]; //nudeAppearancesCount
                  //Clear no longer needed array
              ratingArray.length = 0;
              }
              //  var _track_page = {"titleType":"Movie","titleId":15414,"titleName":"The Break-Up","titleYear":"(2006)","titleNudityRating":2,"titleGenres":["Comedy","Romance"],"nudity":true,"mediaCount":11,"sceneCount":3,"picCount":8,"nudeSceneCount":1,"nudePicCount":5,"appearancesCount":1,"nudeAppearancesCount":1};
              }
              
          }
          else {
              rating = ratingArray[1]; //rating value from MrSkin Actress page
              ratingSummary = ratingArray[2]; //Summary of nudity such as "Brief Nudity"
              
              //(Rating, Nudity summary) - The rating and type of nudity              
              
              //Clear no longer needed array
              ratingArray.length = 0;
              
              
          }
          
          //Try extracting the info from the "_track_page" script in header
              ratingArray = result.responseText.match(/<script>[\s\S]+?starRating":([\s\S]+?),[\s\S]+?breastSize":"([\s\S]+?)",[\s\S]+?bodyType":"([\s\S]+?)",[\s\S]+?fakeBreasts":([\s\S]+?),[\s\S]+?hairColor":"([\s\S]+?)"[\s\S]+?nudeAppearancesCount":([\s\S]+?)}[\s\S]+?<\/script>/);
              if (ratingArray){//Not Empty
                  if (!(rating)){ //Empty
                      rating = ratingArray[1]; //titleNudityRating
                  }
                  nudeRoles = ratingArray[6]; //nudeAppearancesCount
                  fakeBreasts = ratingArray[4];
                  breastSize = ratingArray[2];
                  bodyType = ratingArray[3];
                  hairColor = ratingArray[5];
                  //Clear no longer needed array
                  ratingArray.length = 0;
              }
          
                      
          //window.prompt("responseText", result.responseText);
          if (!(nudeRoles)){ //If we don't already have nudeRoles from _track_page then get it from body
                var nudeRolesArray = result.responseText.match(/<div class='pedigree-wrapper'>[\s\S]+?<span class='detail-type'>Nude Roles:<\/span>[\s\S]+?detail-info'>(.+)<\/span>/);
        if (!(nudeRolesArray)){
            //nudeRoles information not available (probably a Title page, or a redesign)
            console.log("Info, " + "Nude Roles information not available (probably a Title page, possibly just lacking for this actress, or a redesign)");
        }
        else {

            nudeRoles = nudeRolesArray[1]; //Number of roles Actress appeared nude

            //Clear no longer needed array
            nudeRolesArray.length = 0;
        }
    }

          //alert("rating: "  + rating + "\nratingSummary: " + ratingSummary + "\nnudeRoles: " + nudeRoles);
          
          //Add linebreak in 'new design' IMDb so that rating is under actress name instead of alongside.
          if (imdbNewDesign) {
              if (!isTitle) {
                  var lineBR = document.createElement("br");
                  span.appendChild(lineBR);
              }
          }
          
          //Display rating
          var newLink = document.createElement("a");
          newLink.setAttribute("href", link);

          if (openInNewTab) {newLink.setAttribute("target", "_blank");}

          var sknImg = document.createElement("img");
          //http://img-p.mrskin.com/assets/images/rating_2.png
          //alert("summary: " + ratingSummary);
          if(rating.length > 0 && ratingSummary.length > 0 && typeof nudeRoles !== 'undefined' && nudeRoles.length > 0){
              sknImg.title = ratingSummary + " (Nude Roles: " + nudeRoles + ")";
          }
          else if(ratingSummary.length > 0) 
          {
              sknImg.title = ratingSummary;
          }
          
          if (typeof fakeBreasts !== 'undefined') { //if (fakeBreasts) //Variable contains data
              if (fakeBreasts == "false") {
                  sknImg.title = "\n" + sknImg.title + "  -  Real Breasts (" + breastSize + "), Body Type: " + bodyType + ", Hair Color: " + hairColor;
              }
              else
              {
                  sknImg.title = "\n" + sknImg.title + "  -  Fake Breasts (" + breastSize + "), Body Type: " + bodyType + ", Hair Color: " + hairColor;
              }
          }
          
          var ratingImage = "";
          if (rating == 1) {
              if (imdbNewDesign && isTitle) {
                      ratingImage = "http://i.imgur.com/djcu3e0.png";
              }
              else
              {
                  ratingImage = "http://i.imgur.com/bh7FS.png";
              }
          }
          else if (rating == 2){
              if (imdbNewDesign && isTitle) {
                      ratingImage = "https://i.imgur.com/MR5MHi4.png";
              }
              else
              {
                  ratingImage = "http://i.imgur.com/OnETx.png";
              }
          }
          else if (rating == 3){
              if (imdbNewDesign && isTitle) {
                      ratingImage = "https://i.imgur.com/LYVWRK3.png";
              }
              else
              {
                  ratingImage = "http://i.imgur.com/nUxuM.png";
              }
          }
          else if (rating == 4){
              if (imdbNewDesign && isTitle) {
                      ratingImage = "https://i.imgur.com/VkCui3R.png";
              }
              else
              {
                  ratingImage = "http://i.imgur.com/q0Ldw.png";
              }
          }

          //sknImg.src = "http://img-p.mrskin.com/assets/images/rating_" + rating[1] + ".png";
          sknImg.src = ratingImage;
          
          newLink.appendChild(sknImg);
          
          span.appendChild(newLink);
          
          //MrSkin Actress/Title doesn't exactly match IMDb actress/Title
          if (mrskinDiscrepancy.length > 0) {
              //Notify in tooltip
              sknImg.title = "Please note this rating might be for the incorrect Actress/Title as MrSkin didn't return an exact match.\n" + "Results returned were for " + mrskinDiscrepancy + '\n' + sknImg.title;

              //Add astrix to rating
              span.style.fontSize = "18px"; //Increase the fontsize so that it shows a bit more clearly
              var t = document.createTextNode("*");
              span.appendChild(t);
          }
          
          //Google
			if (googleLink){
				var gglLink = document.createElement("a");
				gglLink.setAttribute("href", "http://google.com/images?q=" + actressname);
				if (openInNewTab) {gglLink.setAttribute("target", "_blank");}
				var gglImg = document.createElement("img");
				gglImg.src = "http://www.google.com/favicon.ico";
                gglImg.height = height="21";
                gglImg.width = width="21";
				//Add padding
				span.appendChild(document.createTextNode('\xa0' + '\xa0' + '\xa0' + '\xa0' + '\xa0' + '\xa0' + '\xa0' + '\xa0' + '\xa0' + '\xa0' + '\xa0'));
				gglLink.appendChild(gglImg);
				span.appendChild(gglLink);
			}
          
          //reset Variable
          mrskinDiscrepancy = "";

		}
   });
}