Greasy Fork is available in English.
Highlight entries based on status
// ==UserScript==
// @name IMDb Highlighter
// @namespace http://www.imdb.com
// @version 1.5
// @description Highlight entries based on status
// @author frtazz
// @match https://www.imdb.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const GRAPHQL_URL = "https://api.graphql.imdb.com/";
const DROPPED_LIST_ID = null;
let watchlistIds = null;
let droppedIds = null;
let dataReady = false;
let timeout = null;
// ---------------------------
// Inject CSS
// ---------------------------
function injectStyles() {
const style = document.createElement("style");
style.textContent = `
.imdb-watched {
background-color: #7bd88f !important;
color: #1a1a1a !important;
}
.imdb-watchlist {
background-color: #7dbbff !important;
color: #1a1a1a !important;
}
.imdb-dropped {
background-color: #ff6b6b !important;
color: #1a1a1a !important;
}
`;
document.head.appendChild(style);
}
// ---------------------------
// GraphQL
// ---------------------------
async function gql(body) {
const res = await fetch(GRAPHQL_URL, {
method: "POST",
credentials: "include",
headers: { "content-type": "application/json" },
body: JSON.stringify(body)
});
return res.json();
}
async function fetchViaGraphql(ids, listId) {
const query = listId
? `query List($id: ID!, $after: String) {
list(id: $id) {
titleListItemSearch(first: 250, after: $after) {
edges { title { id } }
pageInfo { hasNextPage endCursor }
}
}
}`
: `query WL($after: String) {
predefinedList(classType: WATCH_LIST) {
titleListItemSearch(first: 250, after: $after) {
edges { title { id } }
pageInfo { hasNextPage endCursor }
}
}
}`;
let after = null;
for (let i = 0; i < 50; i++) {
const vars = listId ? { id: listId, after } : { after };
const data = await gql({ query, variables: vars });
const conn = listId
? data?.data?.list?.titleListItemSearch
: data?.data?.predefinedList?.titleListItemSearch;
if (!conn) return false;
for (const edge of conn.edges || []) {
const id = edge?.title?.id;
if (id) ids.add(id);
}
if (!conn.pageInfo?.hasNextPage) break;
after = conn.pageInfo.endCursor;
}
return ids.size > 0;
}
async function fetchWatchlistIds() {
const ids = new Set();
await fetchViaGraphql(ids, null);
return ids;
}
async function fetchDroppedIds() {
if (!DROPPED_LIST_ID) return new Set();
const ids = new Set();
await fetchViaGraphql(ids, DROPPED_LIST_ID);
return ids;
}
// ---------------------------
// Helpers
// ---------------------------
function getTitleId(root) {
const link = root.querySelector('a[href*="/title/tt"]');
const m = link && link.getAttribute("href").match(/tt\d+/);
return m ? m[0] : null;
}
function isWatchedItem(root) {
return !!(
root.querySelector(".ipc-rating-star--currentUser") ||
root.querySelector('button[aria-pressed="true"]')
);
}
function applyClasses(el, watched, dropped, watchlist) {
el.classList.remove("imdb-watched", "imdb-watchlist", "imdb-dropped");
if (watched) el.classList.add("imdb-watched");
else if (dropped) el.classList.add("imdb-dropped");
else if (watchlist) el.classList.add("imdb-watchlist");
}
// ---------------------------
// Highlight
// ---------------------------
function highlight() {
if (!dataReady) return;
const items = document.querySelectorAll('.ipc-metadata-list-summary-item__c');
for (const item of items) {
const box = item.closest("li.ipc-metadata-list-summary-item") || item;
const id = getTitleId(item);
const watched = isWatchedItem(item);
const dropped = !watched && id && droppedIds?.has(id);
const watchlist = !watched && !dropped && id && watchlistIds?.has(id);
applyClasses(box, watched, dropped, watchlist);
}
const cards = document.querySelectorAll('.ipc-primary-image-list-card');
for (const card of cards) {
const id = getTitleId(card);
const watched = isWatchedItem(card);
const dropped = !watched && id && droppedIds?.has(id);
const watchlist = !watched && !dropped && id && watchlistIds?.has(id);
applyClasses(card, watched, dropped, watchlist);
}
}
// ---------------------------
// Overlay cleanup
// ---------------------------
function removeOverlayButtons() {
if (!location.pathname.includes("/name/")) return;
const rows = document.querySelectorAll('.ipc-metadata-list-summary-item');
for (const row of rows) {
const buttons = row.querySelectorAll('button');
for (const btn of buttons) {
const text = btn.textContent.trim();
if (/manage/i.test(text)) continue;
const hasTitleLink = row.querySelector('a[href*="/title/tt"]');
if (!hasTitleLink) continue;
btn.remove();
}
}
}
// ---------------------------
// Debounced observer
// ---------------------------
function runFast() {
if (!dataReady) return;
if (timeout) return;
timeout = setTimeout(() => {
removeOverlayButtons();
highlight();
timeout = null;
}, 80);
}
// ---------------------------
// Init
// ---------------------------
injectStyles();
highlight();
removeOverlayButtons();
(async () => {
watchlistIds = await fetchWatchlistIds();
droppedIds = await fetchDroppedIds();
dataReady = true;
highlight();
})();
const observer = new MutationObserver(runFast);
observer.observe(document.body, { childList: true, subtree: true });
})();