GGn Title and Screenshots Formatter

Formats title, sets alias if applicable and has buttons to undo. Removes screenshots until they are a multiple of 4

目前為 2024-04-01 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         GGn Title and Screenshots Formatter
// @namespace    none
// @version      18
// @description  Formats title, sets alias if applicable and has buttons to undo. Removes screenshots until they are a multiple of 4
// @author       ingts
// @match        https://gazellegames.net/upload.php
// @exclude      https://gazellegames.net/upload.php?*
// @grant        unsafeWindow
// ==/UserScript==
let titleInput = document.getElementById('title')

// changing the category changes the form using a server request and the title input is replaced
document.getElementById('categories').addEventListener('change', () => {
    new MutationObserver((mutations, observer) => {
        titleInput = document.getElementById('title')
        observer.disconnect()
    }).observe(document.getElementById('upload_table'), {childList: true, subtree: true})
})

const globals = unsafeWindow.TitleAndScreenshotsFormatter = {}
globals.toTitleCase = function (str, alias) {
    const japaneseLowercase = new Map([
        ["ga", ["が", "ガ"]],
        ["no", ["の", "ノ"]],
        ["wa", ["わ", "ワ"]],
        ["mo", ["も", "モ"]],
        ["kara", ["から", "カラ"]],
        ["made", ["まで", "マデ"]],
        ["to", ["と", "ト"]],
        ["ya", ["や", "ヤ"]],
        ["de", ["で", "デ"]],
        ["ni", ["に", "ニ"]],
        ["so", ["そ", "ソ"]],
        ["na", ["な", "ナ"]],
        ["i", ["い", "イ"]],
        ["u", ["う", "ウ"]],
        ["e", ["え", "エ"]],
        ["o", ["お", "オ"]],
    ]);

    const smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|v.?|vs.?|via)$/i
    const alphanumericPattern = /([A-Za-z0-9\u00C0-\u00FF])/
    const wordSeparators = /([ :–—-]|[^a-zA-Z'])/
    const allUppercase = new Set(['rpg', 'fps', 'tps', 'rts', 'tbs', 'mmo', 'mmorpg', 'arpg', 'jrpg', 'pvp', 'pve', 'ntr', 'td', 'vr'])

    return str
        .replace(/\s/g, ' ')
        .replace(/ ~ /, ': ').replace(/ ~/, ': ').replace(/~$/, '').replace(/ ~$/, '').replace(/ - /, ': ').replace(/ -/, ': ').replace(/-$/, '')
        .replace('™', '').replace('®', '')
        .toLowerCase().trim()
        .split(wordSeparators)
        .map(function (current, index, array) {
            if (allUppercase.has(current.trim()) || /\b([IVX])(X{0,3}I{0,3}|X{0,2}VI{0,3}|X{0,2}I?[VX])(?![A-Za-z'])\b/i.test(current))
                return current.toUpperCase()
            if (alias) {
                const jpWords = japaneseLowercase.get(current)
                if (jpWords && jpWords.some(w => alias.includes(w))) return current
            }
            if (
                /* Check for small words */
                current.search(smallWords) > -1 &&
                /* Skip first and last word */
                index !== 0 &&
                index !== array.length - 1 &&
                /* Ignore title end and subtitle start */
                array[index - 3] !== ':' &&
                array[index + 1] !== ':' &&
                /* Ignore small words that start a hyphenated phrase */
                (array[index + 1] !== '-' ||
                    (array[index - 1] === '-' && array[index + 1] === '-'))
            ) {
                return current
            }
            /* Capitalize the first letter */
            return current.replace(alphanumericPattern, function (match) {
                return match.toUpperCase()
            })
        })
        .join('')
}

function formatText() {
    let origTitle = titleInput.value
    const aliases = document.getElementById('aliases')
    let origAlias = aliases.value

    let titleAfterTitleCase = globals.toTitleCase(titleInput.value, aliases.value)
    titleInput.value = titleAfterTitleCase

    if (document.getElementById('categories').value === 'Games') {
        const excludePattern = /[^a-zA-Z0-9 .~?!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/]+/g
        let excluded = titleInput.value.match(excludePattern)

        if (excluded) {
            if (excluded.length === 1) {
                aliases.value ? aliases.value += ', ' + excluded.join('') : aliases.value = excluded.join('')
                titleInput.value = titleInput.value.replace(excludePattern, "").trim()
            } else {
                aliases.value ? aliases.value += ', ' + titleInput.value : titleInput.value
                titleInput.value = ''
                startTextFormat(false)
                return
            }
        }
    }

    if (titleAfterTitleCase !== origTitle || aliases.value !== origAlias) {
        document.querySelector("#title_tr > td.label").insertAdjacentHTML('beforeend', `<span style="color: #ebaf51;display: block;">Undo Title Formatter</span>
    <div id="tf-undo-buttons"></div>`)

        const buttonDiv = document.getElementById('tf-undo-buttons')

        if (titleAfterTitleCase !== origTitle) {
            const button1 = document.createElement('button')
            button1.textContent = 'Formatting'
            button1.type = 'button'
            button1.onclick = () => {
                titleInput.value = origTitle
            }
            buttonDiv.append(button1)
        }
        if (aliases.value !== origAlias) {
            const button2 = document.createElement('button')
            button2.textContent = 'Alias'
            button2.type = 'button'
            button2.onclick = () => {
                titleInput.value = titleAfterTitleCase
                aliases.value = origAlias
            }
            buttonDiv.append(button2)
        }
    }
}

function startTextFormat(wait) {
    const tInterval = setInterval(() => {
        if (document.activeElement === titleInput || !titleInput.value)
            return
        clearInterval(tInterval)
        if (wait) {
            // to allow upload scripts that use the title input's value to set the title before formatting
            setTimeout(formatText, 2000)
        } else formatText()
    }, 500)
}

startTextFormat(true)


const screenshotInputs = document.getElementsByName('screens[]')
const removeButton = document.querySelector("#image_block > a:nth-of-type(2)")
const sInterval = setInterval(() => {
    for (let i = 0; i < screenshotInputs.length; i++) {
        if (!screenshotInputs[i].value) {
            return
        }
    }
    const number = screenshotInputs.length < 4 ? screenshotInputs.length : Math.min(Math.floor(screenshotInputs.length / 4) * 4, 20)
    while (screenshotInputs.length > number) {
        removeButton.click()
    }
    clearInterval(sInterval)
}, 1000)
document.querySelector("#image_block > a:nth-of-type(1)").addEventListener('click', () => {
    clearInterval(sInterval)
})