Raw Source
heyxsh / Unfuck IMDb

// ==UserScript==
// @name         Unfuck IMDb
// @namespace    https://www.imdb.com
// @version      0.4.2
// @description  Enhance the existing IMDB design. Works well with reference view.
// @author       heyxsh
// @match        *://www.imdb.com/search/title/*
// @match        *://www.imdb.com/title/tt*
// @icon         https://www.google.com/s2/favicons?domain=imdb.com
// @require      https://code.jquery.com/jquery-3.6.0.slim.min.js
// @require      https://cdn.jsdelivr.net/npm/spotlight.js@0.7.8/dist/spotlight.bundle.js
// @grant        GM_xmlhttpRequest
// @connect      yandex.com
// @connect      self
// @updateURL    https://raw.githubusercontent.com/heyxsh/userscripts/master/src/unfuckimdb.user.js
// @downloadURL  https://raw.githubusercontent.com/heyxsh/userscripts/master/src/unfuckimdb.user.js
// @license      MIT
// ==/UserScript==

function getTitleName() {
  let title = document.querySelector('title').innerText.trim()
  title = title.substr(0, title.lastIndexOf(')') + 1)
  title = title.replace('(', '')
  title = title.replace(')', '')
  // return encodeURIComponent(title)
  return title
}

/*
 * High Quality Poster Thumbs
 */
async function applyToSearchPage() {
  const images = document.querySelectorAll('.lister-item-image a img')
  images.forEach((img) => {
    img.setAttribute('width', 72)
    img.setAttribute('height', 105)
    let imgUrl = img.getAttribute('loadlate') || img.getAttribute('src')
    if (imgUrl) {
      let lastDot = imgUrl.lastIndexOf('.')
      let secLastDot = imgUrl.substr(0, lastDot).lastIndexOf('.')
      let params = imgUrl.substr(secLastDot + 1, lastDot - secLastDot - 1)
      if (params.search('_V[0-9]_') > -1) {
        let newParams = params.replace(/67/g, "240")
        newParams = newParams.replace(/98/g, "323")
        let newUrl = imgUrl.replace(params, newParams)
        img.setAttribute('loadlate', newUrl)
        img.setAttribute('src', newUrl)
      }
    }
  })
}

/*
 * High Quality Poster
 */
async function hdPoster() {
  const poster = document.querySelector('img.titlereference-primary-image')
  if (!poster) return
  const imgUrl = poster.getAttribute('src') || poster.getAttribute('loadlate')
  if (imgUrl.search('_V[0-9]_') > -1) {
    let newUrl = imgUrl.slice(0, 116 + 1) + imgUrl.substr(imgUrl.lastIndexOf('.') + 1)
    poster.setAttribute('src', newUrl)
  }
}

/*
 * High Quality Actor Images
 */
async function hdActorImages() {
  const actorPhotos = document.querySelectorAll('.primary_photo a img')
  actorPhotos.forEach(img => {
    img.style.borderRadius = "4px"
    const imgUrl = img.getAttribute('loadlate') || img.getAttribute('src')
    if (imgUrl) {
      img.setAttribute('width', '90')
      img.setAttribute('height', '121')
      if (imgUrl.search(/_V[0-9]_/) > -1) {
        let lastDot = imgUrl.lastIndexOf('.')
        let secLastDot = imgUrl.substr(0, lastDot).lastIndexOf('.')
        let params = imgUrl.substr(secLastDot + 1, lastDot - secLastDot - 1)
        if (params.search('_V[0-9]_') > -1) {
          let newParams = params.replace(/26/g, "160")
          newParams = newParams.replace(/35/g, "215")
          let newUrl = imgUrl.replace(params, newParams)
          img.setAttribute('loadlate', newUrl)
          img.setAttribute('src', newUrl)
        }
      }
    }
  })
}

/*
 * Fetch Parental Guide for Title Page
 */
async function fetchParentalGuide() {
  const url = window.location.href.replace("/reference", "/parentalguide")
  fetch(url).then(res => {
    return res.text()
  })
    .then(html => {
      const div = document.createElement("div")
      div.innerHTML = html.trim()
      const main = div.querySelector("#main > section")
      const subPage = main.querySelector(".subpage_title_block")
      const itemCount = main.querySelector(".ipl-itemcount-header")
      const jumpto = main.querySelector(".ipl-jumpto-container")
      subPage.parentNode.removeChild(subPage)
      itemCount.parentNode.removeChild(itemCount)
      jumpto.parentNode.removeChild(jumpto)
      const section = document.querySelector("div#main > section.content-advisories-index")
      if (section) section.appendChild(main)
    })
    .catch(err => {
      console.log(err)
    })
}

/*
 * Declutters and Makes the Title Page Compact
 */
async function declutterTitlePage() {
  const cast_list = document.querySelector(".cast_list")
  const sidebar = document.querySelector("#sidebar")
  if (!sidebar) return
  sidebar.innerHTML = ""
  sidebar.appendChild(cast_list)
  cast_list.style.marginBottom = "20px"
  const credits = document.querySelector(".titlereference-section-credits")
  const production = document.querySelector(".titlereference-section-credits + section")
  const contrib = document.querySelector("#contribute-main-section")
  const rvi = document.querySelector("#rvi-div")
  credits.parentNode.removeChild(credits)
  production.parentNode.removeChild(production)
  contrib.parentNode.removeChild(contrib)
  rvi.parentNode.removeChild(rvi)
  $("td.ellipsis").remove()
  const remove_rows = ["Plot Summary", "Parents Guide", "Certification", "Sound Mix", "Aspect Ratio", "Opening Weekend", "Taglines", "Gross United States", "Trivia", "Goofs"]
  const tr = document.querySelectorAll('.ipl-zebra-list__item')
  tr.forEach(t => {
    for (var i = 0; i < remove_rows.length; i++) {
      if (t.innerText.indexOf(remove_rows[i]) > -1) {
        t.parentNode.removeChild(t)
      }
    }
  })
  const summary = $(".titlereference-section-overview > div:first")
  const summaryLink = $(summary).find('a')[0]
  if (summaryLink.href && $(summary).text().includes("more")) {
    GM_xmlhttpRequest({
      method: "GET",
      url: summaryLink.href,
      onload: r => {
        const sum = $($.parseHTML(r.responseText)).find('#main section #plot-summaries-content p:first').text();
        $(summary).text(sum)
      }
    })
  }
}

/*
 * Opens Spotlight Image Viewer
 */
function openSpotlight(srcs) {
  Spotlight.show(srcs, {
    media: "image",
    infinite: true,
    preload: true,
    download: true
  })
  document.querySelector('.spl-title').setAttribute("style", "font-size: 0.65em; margin-bottom: 0; font-weight: bold;")
  document.querySelector('.spl-description').setAttribute("style", "font-size: 0.55em; margin-bottom: 4px;")
  document.querySelector('.spl-footer').setAttribute("style", "line-height: 15px; background: rgba(0, 0, 0, 0.25); padding: 4px 4px 0 4px;")
}

/*
 * Handle the Image/GIFS Sources
 */
async function handleImageSrcs(html) {
  var d = document.createElement('div')
  var srcs = []
  d.innerHTML = html
  d.querySelectorAll(".serp-item.serp-item_type_search").forEach(item => {
    const data = (JSON.parse(item.getAttribute('data-bem')))["serp-item"]
    var title = false
    var description = false
    if (data.snippet && data.snippet.title) {
      const e = document.createElement('div')
      e.innerHTML = data.snippet.title
      title = e.innerText.trim()
    }
    if (data.snippet && data.snippet.text) {
      const e = document.createElement('div')
      e.innerHTML = data.snippet.text
      description = e.innerText.trim()
    }
    if (data.img_href) {
      srcs.push({
        src: data.img_href,
        description,
        title
      })
    }
  })
  openSpotlight(srcs)
}

/*
 * Binds Events for Fetching Images
 */
async function bindImagesFetch() {
  const actors = document.querySelectorAll(".primary_photo a img")
  const seeMore = document.querySelector(".combined-see-more.see-more")
  actors.forEach(actor => {
    const tr = actor.parentNode.parentNode.parentNode
    const charName = $(tr).children("td.character").text().trim()
    $(tr).children("td.itemprop").append("<hr><em>" + charName + "</em>")
    $(tr).find("td.character").remove()
    $(tr).css({ fontSize: "0.85em" })
    tr.innerHTML += `
          <td class="character" style="width: 85px;">
            <div class="actor_photos" style="float: right; padding-bottom: 2px;">Photos</div>
            <div class="actor_gifs" style="float: right; clear: both; padding-bottom: 2px;">GIFS</div>
            <div class="title_photos" style="float: right; clear: both;paddin-bottom: 2px;">Title Photos</div>
            <div class="title_gifs" style="float: right; clear: both;">Title GIFS</div>
          </td>`
    $(tr).children('.character').children().css({ color: "#136CB2", cursor: "pointer" })
    const toFetch = {
      actor_photos: {
        el: tr.querySelector('.actor_photos'),
        url: encodeURI("https://yandex.com/images/search?text=" + actor.getAttribute('title') + "&ncrnd=0.874686120501252")
      },
      actor_gifs: {
        el: tr.querySelector('.actor_gifs'),
        url: encodeURI("https://yandex.com/images/search?itype=gifan&text=" + actor.getAttribute('title') + "&ncrnd=0.874686120501252")
      },
      title_photos: {
        el: tr.querySelector('.title_photos'),
        url: encodeURI("https://yandex.com/images/search?text=" + actor.getAttribute('title') + " " + getTitleName() + "&ncrnd=0.874686120501252")
      },
      title_gifs: {
        el: tr.querySelector('.title_gifs'),
        url: encodeURI("https://yandex.com/images/search?text=" + actor.getAttribute('title') + " " + getTitleName() + "&itype=gifan&ncrnd=0.874686120501252")
      }
    }

    Object.keys(toFetch).forEach(k => {
      toFetch[k].el.addEventListener("mousedown", function(evt) {
        evt.stopPropagation()
        evt.preventDefault()
        GM_xmlhttpRequest({
          method: "GET",
          url: toFetch[k].url,
          onload: function(res) {
            handleImageSrcs(res.responseText)
          }
        })
      })
    })
  })

  if (seeMore) {
    seeMore.innerHTML += ` <span class="ghost">|</span> <span class="external_images">More Images</span>`
    seeMore.innerHTML += ` <span class="ghost">|</span> <span class="gifs">GIFS</span>`
    $(seeMore).children(".external_images, .gifs").css({ color: "#136CB2", cursor: "pointer" })
    const toFetch = {
      ext_images: {
        el: seeMore.querySelector(".external_images"),
        url: encodeURI("https://yandex.com/images/search?text=" + getTitleName() + "&ncrnd=0.874686120501252")
      },
      gifs: {
        el: seeMore.querySelector(".gifs"),
        url: encodeURI("https://yandex.com/images/search?itype=gifan&text=" + getTitleName() + "&ncrnd=0.874686120501252")
      }
    }
    Object.keys(toFetch).forEach(k => {
      toFetch[k].el.addEventListener("mousedown", function(e) {
        e.preventDefault()
        e.stopPropagation()
        GM_xmlhttpRequest({
          method: "GET",
          url: toFetch[k].url,
          onload: function(res) {
            handleImageSrcs(res.responseText)
          }
        })
      })
    })
  }

}

async function applyToTitlePage() {
  declutterTitlePage()
  hdPoster()
  hdActorImages()
  fetchParentalGuide()
  bindImagesFetch()
}

(function() {
  'use strict';
  const currentUrl = window.location.href
  if (currentUrl.search(/title\/tt/) > -1) {
    applyToTitlePage()
  } else {
    applyToSearchPage()
  }
})();