GGn Uploady

Uploady library

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/548332/1656391/GGn%20Uploady.js

// ==UserScript==
// @name         GGn Uploady
// @version      3
// @description  Uploady library
// @author       ingts
// @match        https://gazellegames.net/
// ==/UserScript==
/**
 * @template T
 * @typedef {{
 * getAliases?(result: T): string,
 * getCover?(result: T): Promise<string> | string,
 * getDescription?(result: T): string,
 * getScreenshots?(result: T): string[],
 * getAgeRating?(result: T): number
 * getYear?(result: T): string
 * getTrailer?(result: T): string
 * getTitle?(result: T): string
 * }} ValueGetters
 */

/**
 * @template T
 * @param {string} linkInputId
 * @param {RegExp} paramRegexp
 * @param {(param: string) => Promise<T>} fetchFn
 * @param {ValueGetters<T>} valueGetters
 */
function createFiller(linkInputId, paramRegexp, fetchFn, valueGetters) {
    const linkInput = document.getElementById(linkInputId)
    if (!linkInput) return
    const param = paramRegexp.exec(linkInput?.value)?.[0]
    if (!param) return


    const showButton = document.createElement('button')
    linkInput.insertAdjacentElement('afterend', showButton)
    showButton.textContent = 'Fill'
    showButton.type = 'button'
    showButton.onclick = async () => {
        const mainId = `${linkInputId}_filler`
        let mainContainer = document.getElementById(mainId)
        if (mainContainer) {
            mainContainer.style.display = 'block'
            return
        }

        const style = document.createElement('style')
        style.textContent = `
        [id*=filler] label {
        input[type=checkbox], input[type=radio] {
    margin: 0 3px 0 0;
}
        }
        `
        document.body.after(style)

        /** @type {{[name: string]: {selector?: string, value?: string|string[]}}} */
        let inputMap = JSON.parse(sessionStorage.getItem(param))

        if (!inputMap) {
            inputMap = {
                aliases: {
                    selector: 'aliases',
                },
                cover: {
                    selector: 'image',
                },
                description: {
                    selector: 'body',
                },
                screenshots: {},
                ageRating: {
                    selector: 'rating',
                },
                year: {
                    selector: 'year'
                },
                trailer: {
                    selector: 'trailer',
                },
                title: {
                    selector: 'name',
                },
            }

            showButton.textContent = 'Loading'
            showButton.disabled = true

            const result = await fetchFn(param)
            showButton.textContent = 'Fill'
            showButton.disabled = false

            for (const [name, fn] of Object.entries(valueGetters)) {
                const value = await fn(result)
                const inputName = name
                    .replace('get', '')
                    .replace(/\w/, s => s.toLowerCase())
                if (inputName === 'ageRating') {
                    const ratingMap = new Map([
                        [1, '3+'],
                        [3, '7+'],
                        [5, '12+'],
                        [7, '16+'],
                        [9, '18+'],
                    ])
                    inputMap[inputName].value = ratingMap.get(value) ?? 'N/A'
                } else inputMap[inputName].value = value
            }

            sessionStorage.setItem(param, JSON.stringify(inputMap))
        }

        mainContainer = document.createElement('div')
        document.body.append(mainContainer)
        mainContainer.style.cssText = `
        position: absolute;
        padding: 10px;
        background-color: rgb(51 55 96);
        border-radius: 2px;
        border: 2px solid #997979;
        z-index: 9;
        max-width: 71%;
        max-height: 830px;
         `
        mainContainer.id = mainId

        const tdRect = document.getElementById('non_wiki_editing').nextElementSibling.querySelector('td').getBoundingClientRect()
        mainContainer.style.top = tdRect.top + window.scrollY + 'px'
        mainContainer.style.left = tdRect.right + window.scrollX - tdRect.width + 'px'

        const innerContainer = document.createElement('div')
        mainContainer.appendChild(innerContainer)
        innerContainer.style.cssText = `
            display: flex;
            flex-direction: column;
            height: 100%;
            overflow: auto;
            max-height: 800px;
        `

        const bottom = document.createElement('div')
        mainContainer.appendChild(bottom)

        bottom.style.marginTop = '10px'
        bottom.insertAdjacentHTML('beforeend',
            `<button type="button">Close</button>
<button type="button">Check all</button>
    <button type="button" style="margin-left: 10px;">Fill</button>`)
        mainContainer.appendChild(bottom)


        const closeButton = bottom.children[0]
        closeButton.onclick = () => mainContainer.style.display = 'none'

        const checkallButton = bottom.children[1]
        checkallButton.onclick = () => {
            for (const checkbox of mainContainer.querySelectorAll('input[type=checkbox]')) {
                checkbox.checked = true
            }
        }

        let newSr = ''
        const srRegex = /\[quote]\[align=center]\[b]\[u]System Requirements\[\/u]\[\/b]\[\/align].*\[\/quote]/si
        const imgEls = []

        for (const [name, obj] of Object.entries(inputMap)) {
            const value = obj.value
            if (!value) continue
            const formattedName = name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/\w/, s => s.toUpperCase())

            if (name !== 'screenshots') {
                if (name === 'cover') {
                    const label = createLabelWithCheckbox(`<strong>${formattedName}</strong>`, `data-name=${name}`)
                    const img = document.createElement('img')
                    label.after(img)
                    img.src = value
                    img.style.maxHeight = '180px'
                    img.style.maxWidth = '120px'
                    img.style.marginBottom = '10px'
                    img.onload = () =>
                        label.querySelector('strong').insertAdjacentText('afterend', `: ${img.naturalWidth}x${img.naturalHeight}`)
                    continue
                } else if (name === 'description') {
                    const appendTo = document.createElement('div')
                    appendTo.style.display = 'flex'
                    appendTo.style.gap = '10px'
                    innerContainer.appendChild(appendTo)

                    createLabelWithCheckbox('Description', `data-name=${name}`, appendTo)

                    newSr = srRegex.exec(value)?.[0]
                    if (newSr) createLabelWithCheckbox('Only system requirements', `class=newsr`, appendTo)

                    innerContainer.insertAdjacentHTML('beforeend',
                        `<textarea readonly cols="150" style="margin: 5px 0 10px 0;height: 12em;">${value}</textarea>`)
                    continue
                }

                createLabelWithCheckbox(`<strong>${formattedName}</strong><span>: ${value}</span>`, `data-name=${name}`,)
                continue
            }

            innerContainer.insertAdjacentHTML('beforeend',
                // language=HTML
                `
                    <strong>Screenshots</strong>
                    <div style="border:1px solid #c0a5a5;padding: 3px;margin-bottom: 20px;">
                        <div style="display:flex;gap: 15px;margin-bottom: 10px;align-items:center;">
                            <label>
                                <input type="checkbox" checked class="ss-replace-existing">
                                Remove existing
                            </label>
                            <div style="display:none;flex-direction:column;">
                                <div>
                                    <button type="button">Check</button>
                                    <span>resolutions</span>
                                </div>
                                <div style="display:flex;gap: 8px;">
                                    <label>
                                        <input type="radio" name="resCheck" value="highest" checked>
                                        Highest
                                    </label>
                                    <label>
                                        <input type="radio" name="resCheck" value="majority">
                                        Majority
                                    </label>
                                </div>
                            </div>
                            <button type="button"">Check all</button>
                            <span style="color: #8eddc0;">0/${value.length} selected</span>
                        </div>
                        <div style="display:flex;gap: 5px;flex-wrap:wrap;max-height: 280px;overflow-y: auto"></div>
                    </div>
                `
            )

            const imgDiv = innerContainer.lastElementChild.lastElementChild

            for (const url of value) {
                const div = document.createElement('div')
                div.style.display = 'flex'
                div.style.flexDirection = 'column'
                div.style.gap = '5px'
                imgDiv.append(div)
                const label = createLabelWithCheckbox('', undefined, div)
                label.style.display = 'none'

                const img = document.createElement('img')
                div.append(img)
                img.src = url
                img.style.maxWidth = '200px'
                const checkbox = label.querySelector('input')
                img.addEventListener('load', () => {
                    checkbox.insertAdjacentText('afterend', ` ${img.naturalWidth}x${img.naturalHeight}`)
                    label.style.display = 'flex'
                    label.style.fontSize = '0.9em'
                })
                imgEls.push(img)
            }

            const ssTopDiv = innerContainer.lastElementChild.children[0]
            const countSpan = ssTopDiv.querySelectorAll('span')[1]
            imgDiv.addEventListener('change', e => {
                if (e.target?.type === 'checkbox') {
                    debugger
                    const checkedCount = Array.from(imgDiv.querySelectorAll('input[type=checkbox]:checked')).length
                    countSpan.textContent = countSpan.textContent.replace(/\d+/, checkedCount.toString())
                }
            })

            Promise.all(imgEls.map(img => new Promise(resolve => img.addEventListener('load', resolve))))
                .then(() => {
                    const resolutions = Array.from(imgEls).map(img => `${img.naturalWidth}x${img.naturalHeight}`)
                    const [resCheckBtn,
                        checkAllBtn] = ssTopDiv.querySelectorAll('button')
                    debugger

                    if (!resolutions.every(r => r === resolutions[0])) {
                        const resOptions = ssTopDiv.querySelector('div')
                        resOptions.style.display = 'flex'

                        resCheckBtn.onclick = () => {
                            if (resOptions.querySelector('[name=resCheck]:checked').value === 'highest') {
                                const highestRes = resolutions.reduce((highest, current) => {
                                    const [width, height] = current.split('x').map(Number)
                                    const [highestWidth, highestHeight] = highest.split('x').map(Number)
                                    const highestPixels = highestWidth * highestHeight

                                    return width * height > highestPixels ? current : highest
                                })
                                resCheck(highestRes)
                                return
                            }

                            const freqMap = resolutions.reduce((acc, val) => {
                                acc[val] = (acc[val] || 0) + 1
                                return acc
                            }, {})
                            const majorityRes = Object.keys(freqMap)
                                .reduce((max, cur) => freqMap[max] > freqMap[cur] ? max : cur)

                            resCheck(majorityRes)
                        }
                    }

                    checkAllBtn.style.display = 'block'
                    checkAllBtn.onclick = () => {
                        for (const imgEl of imgEls) {
                            checkImg(imgEl)
                        }
                    }

                    function checkImg(imgEl) {
                        const checkbox = imgEl.parentElement.querySelector('input')
                        checkbox.checked = true
                        checkbox.dispatchEvent(new Event('change', {bubbles: true}))
                    }

                    function resCheck(target) {
                        for (let index = 0; index < imgEls.length; index++) {
                            const imgEl = imgEls[index]
                            if (resolutions[index] !== target) continue
                            checkImg(imgEl)
                        }
                    }
                })
        }

        const fillButton = bottom.children[2]
        fillButton.onclick = () => {
            const namedCheckboxes = innerContainer.querySelectorAll('input[type=checkbox][data-name]')
            for (const namedCheckbox of namedCheckboxes) {
                if (!namedCheckbox.checked) continue
                const name = namedCheckbox.dataset.name
                if (inputMap[name].value) {
                    const selector = inputMap[name].selector
                    document.querySelector(`[name=${selector}]`).value = inputMap[name].value
                }
            }

            if (innerContainer.querySelector('.newsr')?.checked) {
                const descInput = document.querySelector('textarea[name=body]')
                const existingSr = srRegex.exec(descInput.value)?.[0]
                descInput.value = existingSr
                    ? descInput.value.replace(existingSr, newSr)
                    : descInput.value + '\n\n' + newSr
            }

            if (imgEls.length > 0) {
                const checkedUrls = imgEls.filter(el => el.parentElement.querySelector('input:checked')).map(el => el.src)
                insertScreenshots(checkedUrls, document.querySelector(".ss-replace-existing").checked)
            }

            mainContainer.style.display = 'none'
        }

        function createLabelWithCheckbox(after, checkboxAttrs, appendTo) {
            const label = document.createElement('label')
            label.style.display = 'flex'
            label.style.alignItems = 'center';
            (appendTo || innerContainer).appendChild(label)
            label.innerHTML =
                `<input type="checkbox" ${checkboxAttrs ? checkboxAttrs : ''}>
${after}
                    `
            return label
        }
    }
}

/**
 * @param {string[]} urls
 * @param {boolean=true} removeExisting
 */
function insertScreenshots(urls, removeExisting = true) {
    const screenInputs = document.getElementsByName("screens[]")
    if (!removeExisting) urls = [...urls, ...Array.from(screenInputs).map(i => i.value)]

    for (let i = 0; i < urls.length; i++) {
        if (screenInputs.length === 20) break

        if (removeExisting) {
            if (!screenInputs[i]) AddScreenField(true)
            screenInputs[i].value = urls[i]
        } else {
            if (screenInputs[screenInputs.length - 1].value) AddScreenField(true)
            screenInputs[i].value = urls[i]
        }
    }

    if (removeExisting) {
        for (let i = 0; i < screenInputs.length - urls.length; i++) {
            RemoveScreenField()
        }
    } else {
        for (let i = screenInputs.length - 1; i >= 0; i--) {
            if (screenInputs[i].value) break
            RemoveScreenField()
        }
    }
}