Letterboxd Formated Runtime

Replaces the original runtime with a formated one

// ==UserScript==
// @name        Letterboxd Formated Runtime
// @namespace   https://github.com/Tetrax-10
// @description Replaces the original runtime with a formated one
// @icon        https://tetrax-10.github.io/letterboxd-custom-images/assets/icon.png
// @license     MIT
// @version     1.1
// @match       *://*.letterboxd.com/film/*
// @run-at      document-start
// ==/UserScript==

;(async () => {
    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(false)
                                }, timeout)
                            })
                        }
                        resolve(await timeOver())
                    }
                }
            })

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

    function formatTimeString(timeStr) {
        // Extract hours and minutes using regex
        const match = timeStr.match(/(\d+)h\s(\d+)m/)

        if (!match) return "" // If the input string does not match the pattern

        const hours = parseInt(match[1], 10)
        const minutes = parseInt(match[2], 10)

        // If hours is 0, return an empty string
        if (hours === 0) {
            return ""
        }

        // Build the result string
        let result = ""

        // Format hours
        result += hours === 1 ? "1 hr" : `${hours} hrs`

        // Format minutes if more than 0
        if (minutes > 0) {
            result += ` ${minutes} min${minutes > 1 ? "s" : ""}`
        }

        return result
    }

    const runtimeSelector = ".text-footer-extra.duration-extra[data-original-title]"
    const runtime = await waitForElement(runtimeSelector)
    const formatedRuntime = runtime.dataset.originalTitle || ""

    const reFormatedRuntime = formatTimeString(formatedRuntime)

    if (reFormatedRuntime) {
        runtime.dataset.originalTitle = runtime.innerText
        runtime.innerText = reFormatedRuntime
    }
})()