Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- Script to get IMDb rating next to the release year under poster on cards in Jellyfin
- Inspired by this post: https://www.reddit.com/r/jellyfin/comments/1qn68yy/jellyfin_ratings_setup_complete/
- Disclaimer: Everything apart from code copied from the above script was written by my AI slaves.
- 1. Install Jellyfin JavaScript Injector
- https://github.com/n00bcodr/Jellyfin-JavaScript-Injector
- 2. Restart Jellyfin after installing the plugin.
- 3. Create an account on https://api.mdblist.com/ and generate an API key
- You will get 1K free requests per day. A $1.80 Patreon sub gives you 10K.
- If you show a lot of cards, 1K won't be enough, but you can set cache below.
- cURL command to check your rate limit info:
- $ curl "https://api.mdblist.com/user?apikey=YOUR_MDBLIST_API_KEY_HERE"
- 4. Go to: Dashboard → Plugins → JavaScript Injector
- 5. Paste the full JavaScript code into the large Custom JavaScript box and save.
- */
- (function () {
- 'use strict';
- const API_KEY = 'YOUR_MDBLIST_API_KEY_HERE';
- const LOGO = 'https://cdn.jsdelivr.net/gh/Druidblack/jellyfin_ratings@main/logo/IMDb.png';
- const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
- const REQUEST_DELAY_MS = 300;
- const ratingCache = new Map();
- const queue = [];
- const queued = new WeakSet();
- let queueRunning = false;
- const visibleObserver = new IntersectionObserver(entries => {
- for (const entry of entries) {
- if (!entry.isIntersecting) continue;
- visibleObserver.unobserve(entry.target);
- enqueue(entry.target);
- }
- }, {
- root: null,
- rootMargin: '600px 0px',
- threshold: 0.01
- });
- function scan(root = document) {
- root.querySelectorAll('.card[data-id]:not([data-imdb-done])').forEach(observeCard);
- root.querySelectorAll('.listItem[data-id]:not([data-imdb-done])').forEach(observeListItem);
- }
- function observeCard(card) {
- if (!['Movie', 'Series'].includes(card.dataset.type)) return;
- const target =
- card.querySelector('.cardText-secondary') ||
- card.querySelector('.cardText-first');
- if (!target) return;
- visibleObserver.observe(card);
- }
- function observeListItem(item) {
- if (!['Movie', 'Series'].includes(item.dataset.type)) return;
- const target = item.querySelector('.listItemBodyText');
- if (!target) return;
- visibleObserver.observe(item);
- }
- function enqueue(el) {
- if (el.dataset.imdbDone) return;
- if (queued.has(el)) return;
- queued.add(el);
- queue.push(el);
- runQueue();
- }
- function runQueue() {
- if (queueRunning) return;
- queueRunning = true;
- processNext();
- }
- function processNext() {
- const el = queue.shift();
- if (!el) {
- queueRunning = false;
- return;
- }
- processElement(el)
- .finally(() => {
- setTimeout(processNext, REQUEST_DELAY_MS);
- });
- }
- function processElement(el) {
- if (el.dataset.imdbDone) return Promise.resolve();
- const isList = el.classList.contains('listItem');
- let target;
- if (isList) {
- target = el.querySelector('.listItemBodyText');
- } else {
- target =
- el.querySelector('.cardText-secondary') ||
- el.querySelector('.cardText-first');
- }
- if (!target) return Promise.resolve();
- el.dataset.imdbDone = '1';
- return injectRating(el, target);
- }
- function injectRating(el, target) {
- const row = document.createElement('span');
- row.className = 'imdb-home-rating';
- row.style.cssText =
- 'display:inline-flex;align-items:center;gap:0px;font-size:inherit;margin-left:8px;vertical-align:middle;color:inherit';
- target.appendChild(row);
- return ApiClient
- .getItem(ApiClient.getCurrentUserId(), el.dataset.id)
- .then(item => {
- if (!item || !item.ProviderIds) return null;
- const type =
- item.Type === 'Series'
- ? 'show'
- : 'movie';
- return getRating(type, item.ProviderIds);
- })
- .then(rating => {
- if (!rating) {
- row.remove();
- return;
- }
- row.innerHTML = '';
- const logo = document.createElement('span');
- logo.title = 'IMDb: ' + rating;
- logo.style.cssText =
- 'display:inline-block;' +
- 'height:1.15em;' +
- 'width:2.6em;' +
- 'background-color:currentColor;' +
- 'mask:url("' + LOGO + '") center / contain no-repeat;' +
- '-webkit-mask:url("' + LOGO + '") center / contain no-repeat;' +
- 'vertical-align:middle;' +
- 'opacity:.55';
- const value = document.createElement('span');
- value.textContent = rating;
- value.style.color = 'inherit';
- row.append(
- logo,
- value
- );
- })
- .catch(() => {
- row.remove();
- });
- }
- function getRating(type, providerIds) {
- const tmdb = providerIds.Tmdb;
- const imdb = providerIds.Imdb;
- const tvdb = providerIds.Tvdb;
- const key =
- type +
- ':' +
- (tmdb || imdb || tvdb);
- if (ratingCache.has(key)) {
- return Promise.resolve(
- ratingCache.get(key)
- );
- }
- const cached =
- getCachedRating(key);
- if (cached !== undefined) {
- ratingCache.set(
- key,
- cached
- );
- return Promise.resolve(
- cached
- );
- }
- const urls = [];
- if (tmdb) {
- urls.push(
- `https://api.mdblist.com/tmdb/${type}/${tmdb}?apikey=${API_KEY}`
- );
- }
- if (imdb) {
- urls.push(
- `https://api.mdblist.com/imdb/${type}/${imdb}?apikey=${API_KEY}`
- );
- }
- if (tvdb) {
- urls.push(
- `https://api.mdblist.com/tvdb/${type}/${tvdb}?apikey=${API_KEY}`
- );
- }
- return tryUrls(
- urls,
- key
- );
- }
- function tryUrls(urls, key) {
- if (!urls.length) {
- ratingCache.set(
- key,
- null
- );
- setCachedRating(
- key,
- null
- );
- return Promise.resolve(
- null
- );
- }
- const url =
- urls.shift();
- return fetch(url)
- .then(r =>
- r.ok
- ? r.json()
- : null
- )
- .then(j => {
- const imdb =
- j &&
- Array.isArray(j.ratings)
- ? j.ratings.find(
- x =>
- String(
- x.source || ''
- ).toLowerCase()
- === 'imdb'
- )
- : null;
- const value =
- imdb &&
- imdb.value != null
- ? Number(
- imdb.value
- ).toFixed(1)
- : null;
- if (value) {
- ratingCache.set(
- key,
- value
- );
- setCachedRating(
- key,
- value
- );
- return value;
- }
- return tryUrls(
- urls,
- key
- );
- })
- .catch(() =>
- tryUrls(
- urls,
- key
- )
- );
- }
- function getCachedRating(key) {
- const raw =
- localStorage.getItem(
- 'imdb-rating:' + key
- );
- if (!raw)
- return undefined;
- try {
- const cached =
- JSON.parse(raw);
- if (
- !cached ||
- typeof cached !== 'object'
- )
- return undefined;
- if (
- Date.now()
- - cached.time
- > CACHE_TTL_MS
- )
- return undefined;
- return cached.value;
- } catch {
- return undefined;
- }
- }
- function setCachedRating(
- key,
- value
- ) {
- try {
- localStorage.setItem(
- 'imdb-rating:' + key,
- JSON.stringify({
- time:
- Date.now(),
- value
- })
- );
- } catch {}
- }
- new MutationObserver(
- mutations => {
- for (
- const mutation
- of mutations
- ) {
- for (
- const node
- of mutation.addedNodes
- ) {
- if (
- node.nodeType !== 1
- )
- continue;
- scan(node);
- }
- }
- }
- )
- .observe(
- document.body,
- {
- childList: true,
- subtree: true
- }
- );
- setTimeout(
- scan,
- 1000
- );
- setTimeout(
- scan,
- 3000
- );
- setTimeout(
- scan,
- 6000
- );
- window.addEventListener(
- 'hashchange',
- () =>
- setTimeout(
- scan,
- 1000
- )
- );
- window.addEventListener(
- 'popstate',
- () =>
- setTimeout(
- scan,
- 1000
- )
- );
- document.addEventListener(
- 'viewshow',
- () =>
- setTimeout(
- scan,
- 1000
- )
- );
- setInterval(
- scan,
- 3000
- );
- })();
Advertisement