TMDB load unloadable images

Force TMDB to load unloaded images

// ==UserScript==
// @name        TMDB load unloadable images
// @namespace   https://github.com/Tetrax-10
// @description Force TMDB to load unloaded images
// @icon        https://www.google.com/s2/favicons?sz=64&domain=themoviedb.org
// @license     MIT
// @version     1.4
// @match       *://*.themoviedb.org/*
// @run-at      document-idle
// ==/UserScript==

;(() => {
    function changeSrc(img) {
        if (img.src.includes("media.themoviedb.org") && [".jpg", ".png", ".webp"].some((e) => img.src.endsWith(e)) && !img.naturalHeight) {
            // Get the resolution and id of the image
            const resolution = img.src.match(/\/t\/p\/([^\/]+)\/[^\/]+$/)?.[1] ?? ""
            const id = img.src.split("/").pop()

            img.src = `https://image.tmdb.org/t/p/${resolution}/${id}`
            img.removeAttribute("srcset")
        }
    }

    // Function to handle intersection events
    function handleIntersection(entries, observer) {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                // Check if the element is visible in the viewport
                const imgElement = entry.target
                changeSrc(imgElement) // Change the image source if it's unloaded
                observer.unobserve(imgElement) // Stop observing the current image after it's logged
            }
        })
    }

    async function waitForElement(selector, timeout = null, nthElement = 1) {
        // wait till document body loads
        while (!document.body) {
            await new Promise((resolve) => setTimeout(resolve, 10))
        }

        nthElement -= 1

        return new Promise((resolve) => {
            if (document.querySelectorAll(selector)?.[nthElement]) {
                return resolve(document.querySelectorAll(selector)?.[nthElement])
            }

            const observer = new MutationObserver(async () => {
                if (document.querySelectorAll(selector)?.[nthElement]) {
                    resolve(document.querySelectorAll(selector)?.[nthElement])
                    observer.disconnect()
                } else {
                    if (timeout) {
                        async function timeOver() {
                            return new Promise((resolve) => {
                                setTimeout(() => {
                                    observer.disconnect()
                                    resolve(undefined)
                                }, timeout)
                            })
                        }
                        resolve(await timeOver())
                    }
                }
            })

            observer.observe(document.body, {
                childList: true,
                subtree: true,
            })
        })
    }

    function changeDivSrc(div) {
        // Get the background image URL from the div
        const bgImageUrl = getComputedStyle(div).backgroundImage

        // Extract the URL from the `url('...')` syntax
        const matches = bgImageUrl.match(/url\(['"]?(.*?)['"]?\)/)
        if (matches && matches[1]) {
            const url = matches[1]

            // Create a new Image object to check if it loads successfully
            const img = new Image()
            img.onerror = () => {
                if (url.includes("media.themoviedb.org") && [".jpg", ".png", ".webp"].some((e) => url.endsWith(e))) {
                    // Get the resolution and id of the image
                    const resolution = url.match(/\/t\/p\/([^\/]+)\/[^\/]+$/)?.[1] ?? ""
                    const id = url.split("/").pop()

                    div.style.backgroundImage = bgImageUrl.replace(
                        /https?:\/\/[^\s]+?\.(jpg|png|webp)/,
                        `https://image.tmdb.org/t/p/${resolution}/${id}`
                    )
                }
            }

            // Set the src to the extracted URL
            img.src = url
        } else {
            console.log("No valid background image URL found.")
        }
    }

    // Create a new IntersectionObserver instance
    const observer = new IntersectionObserver(handleIntersection, {
        root: null, // Observe the viewport
        rootMargin: "0px", // No margin around the viewport
        threshold: 0.1, // Trigger when at least 10% of the image is visible
    })

    // Select all img elements and observe them
    document.querySelectorAll("img").forEach((img) => {
        observer.observe(img)
    })

    // Create a MutationObserver to listen for new img elements
    const mutationObserver = new MutationObserver((mutationsList) => {
        mutationsList.forEach((mutation) => {
            // Check if nodes are added
            if (mutation.type === "childList") {
                mutation.addedNodes.forEach((node) => {
                    if (node.tagName === "IMG") {
                        console.log("New img element added:", node)
                        observer.observe(node) // Observe the new img
                    }
                    // Check for img elements inside added elements
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        node.querySelectorAll("img").forEach((img) => {
                            console.log("New img element added:", node)
                            observer.observe(img)
                        })
                    }
                })
            }
        })
    })

    // Start observing the body for changes
    mutationObserver.observe(document.body, {
        childList: true, // Listen for direct children changes
        subtree: true, // Include all descendants
    })

    // Change non img elements
    const nonImgEle = [".header"]

    for (const ele of nonImgEle) {
        waitForElement(ele, 10000).then((ele) => {
            changeDivSrc(ele)
        })
    }
})()