Last active
          October 25, 2025 19:23 
        
      - 
      
- 
        Save stpettersens/ebcbc2efac3c82a3b6caf79d526c3989 to your computer and use it in GitHub Desktop. 
    Rotten Tomatoes Links for Jellyfin user script (my instance).
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | // ==UserScript== | |
| // @name Rotten Tomatoes Links for Jellyfin | |
| // @namespace https://github.com/stpettersens | |
| // @version 2025-10-25 | |
| // @description This script adds a Rotten Tomatoes link for TV and movies on Jellyfin. | |
| // @author Sam Saint-Pettersen | |
| // @match https://jellyfin.mmedia.stpettersen.xyz/* | |
| // @icon https://www.rottentomatoes.com/assets/pizza-pie/images/favicon.ico | |
| // @grant none | |
| // @run-at document-start | |
| // ==/UserScript== | |
| 'use strict'; | |
| // Replace @match (+ /*) and BaseURL with your Jellyfin server URL. | |
| const BaseURL = "https://jellyfin.mmedia.stpettersen.xyz"; | |
| (function() { | |
| function waitForElement(selector, targetNode = document.body) { | |
| return new Promise((resolve) => { | |
| const element = document.querySelector(selector); | |
| if (element) { | |
| return resolve(element); | |
| } | |
| const observer = new MutationObserver(() => { | |
| const foundElement = document.querySelector(selector); | |
| if (foundElement) { | |
| observer.disconnect(); | |
| resolve(foundElement); | |
| } | |
| }); | |
| observer.observe(targetNode, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| }); | |
| } | |
| function stripBdiTag(html) { | |
| return html.replace(/<bdi[^>]*>|<\/bdi>/gi, ""); | |
| } | |
| function stripBrackets(html) { | |
| return html.replace('(', '').replace(')', ''); | |
| } | |
| function injectRTLink() { | |
| let targetEl = null; | |
| let links = document.getElementsByClassName('button-link') | |
| for (let i = 0; i < links.length; i++) { | |
| if (links[i].href.startsWith("https://www.themoviedb.org")) { | |
| targetEl = links[i]; | |
| break; | |
| } | |
| if (targetEl != null) { | |
| break; | |
| } | |
| } | |
| let genre = "m"; | |
| let year = ""; | |
| let title = stripBdiTag(stripBrackets(document.getElementsByTagName("h1")[0].innerHTML)).toLowerCase() | |
| .replace("/", "").replace(/ /g,"_").replace(/'/g,"").replace(/_&/g, "_and") | |
| .replace(/:/g, "").replace(/,/g, "").replace(/-/g, "_").replace(/\./g, ""); | |
| let otm = document.getElementsByClassName("originalTitle")[0]; | |
| if (otm) { | |
| let ot = document.getElementsByClassName("originalTitle")[0].innerHTML; | |
| if (ot.length > 0) year = "_" + ot; | |
| if (ot.indexOf("-") != -1) { | |
| year = ""; | |
| title = ot + title.replace("the", ""); | |
| } | |
| if (ot.indexOf("!") != -1) { | |
| year = ""; | |
| title = title.replace(ot.substring(1), "").replace("_", ""); | |
| } | |
| } | |
| let ott = document.getElementsByClassName("originalTitle")[0]; | |
| if (ott) { | |
| let ot = document.getElementsByClassName("originalTitle")[0].innerHTML; | |
| if (ot.indexOf("!RT") != -1) return; // If tagged !RT, there is no Rotten Tomatoes entry for the title. | |
| if (ot == "TV") genre = "tv"; | |
| } | |
| let template = `, <a id="rt-link" target="_blank" class="button-link emby-button" href="https://www.rottentomatoes.com/${genre}/${title}${year}">Rotten Tomatoes</a>`; | |
| if (title.indexOf("nothing_here") != -1) { | |
| window.location.reload(); | |
| } | |
| console.log(template); | |
| targetEl.insertAdjacentHTML("afterEnd", template.replace('_TV', '')); | |
| } | |
| let done = false; | |
| waitForElement('.button-link').then(element => { | |
| if (!done) { | |
| injectRTLink(); | |
| done = true; | |
| } | |
| }); | |
| waitForElement('.headerHomeButton').then(element => { | |
| let homeBtn = document.getElementsByClassName("headerHomeButton")[0] | |
| homeBtn.addEventListener('click', (event) => { | |
| event.preventDefault(); | |
| window.location = BaseURL; | |
| }); | |
| }); | |
| waitForElement('.headerButtonLeft').then(element => { | |
| let backBtn = document.getElementsByClassName("headerButtonLeft")[0]; | |
| backBtn.addEventListener('click', (event) => { | |
| event.preventDefault(); | |
| window.location = BaseURL; | |
| }); | |
| }); | |
| window.navigation.addEventListener("navigate", (event) => { | |
| waitForElement('.button-link').then(element => { | |
| if (!done) { | |
| injectRTLink(); | |
| done = true; | |
| } | |
| }); | |
| waitForElement('.headerHomeButton').then(element => { | |
| let homeBtn = document.getElementsByClassName("headerHomeButton")[0] | |
| homeBtn.addEventListener('click', (event) => { | |
| event.preventDefault(); | |
| window.location = BaseURL; | |
| }); | |
| }); | |
| waitForElement('.headerButtonLeft').then(element => { | |
| let backBtn = document.getElementsByClassName("headerButtonLeft")[0]; | |
| backBtn.addEventListener('click', (event) => { | |
| event.preventDefault(); | |
| window.location = BaseURL; | |
| }); | |
| }); | |
| }); | |
| })(); | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment