GGn VNDB uploady new

input game title or vndb id (anything with v(digits)) and click vndb to fill

当前为 2025-09-04 提交的版本,查看 最新版本

// ==UserScript==
// @name         GGn VNDB uploady new
// @namespace    none
// @version      6
// @description  input game title or vndb id (anything with v(digits)) and click vndb to fill
// @author       ingts
// @match        https://gazellegames.net/upload.php*
// @match        https://gazellegames.net/torrents.php?action=editgroup*
// @connect      api.vndb.org
// @grant        GM.xmlHttpRequest
// @require      https://update.greasyfork.org/scripts/548332/1654460/GGn%20Uploady.js
// ==/UserScript==
if (typeof GM_getValue('auto_search_trailer') === 'undefined')
    GM_setValue('auto_search_trailer', false)

const tagsDictionary = {
    'romance': ["Love", "Polyamory", "Polygamy", "Swinging", "Romance"],
    'horror': ["Horror", "Graphic Violence"],
    'science.fiction': ["Science Fiction", "AI"],
    'drama': ["Drama", "Suicide", "Suicidal", "Desperation"],
    'crime': ["Crime", "Slave"],
    'mystery': ["Mystery", "Amnesia", "Disappearance", "Secret Identity"],
    'comedy': ["Comedy", "Slapstick", "Comedic"],
    'fantasy': ["Fantasy", "Magic", "Mahou", "Superpowers"]
}

function removeLastBracket(str) {
    if (!str) return ''
    if (!str.endsWith(']')) return str

    let i = str.length - 1
    let bracketCounter = 0
    for (; i >= 0; i--) {
        if (str[i] === ']') {
            bracketCounter++
        } else if (str[i] === '[') {
            bracketCounter--
            if (bracketCounter === 0) {
                break
            }
        }
    }
    return str.substring(0, i).trim()
}

/** @type {HTMLInputElement} */
const gameTitleInput = document.getElementById('title')

/**
 * @typedef Result
 * @property {VisualNovel} vn
 * @property {Release[]} releases
 */

/**
 * @param {Result} result
 */
function fillUpload(result) {
    const pcPlatforms = ['win', 'lin', 'mac']
    const vn = result.vn
    const consoleOnly = vn.platforms.every(platform => !pcPlatforms.includes(platform))

    if (!consoleOnly)
        document.getElementById('platform').value = 'Windows'
    const englishTitle = vn.titles.find(a => a.lang === 'en')
    gameTitleInput.value = getTitle(result, englishTitle)

    if (GM_getValue('auto_search_trailer'))
        window.open(`https://www.youtube.com/results?search_query=${gameTitleInput.value} trailer`, '_blank').focus()

    const aliasInput = document.getElementById('aliases')
    aliasInput.value = getAliases(result, englishTitle)

    const foundTags = new Set()

    const noRomance = vn.tags.some(tag => tag.name === "No Romance Plot")
    for (const [ggnTag, vndbTagsArr] of Object.entries(tagsDictionary)) {
        if (ggnTag === 'romance' && noRomance) continue
        for (const resultTag of vn.tags) {
            if (vndbTagsArr.some(word => resultTag.name.includes(word)))
                foundTags.add(ggnTag)
        }
    }

    const tagsInput = document.getElementById('tags')
    tagsInput.value = foundTags.size === 0 ? 'visual.novel' : `visual.novel, ${Array.from(foundTags).join(', ')}`

    document.getElementById('year').value = getYear(result)

    document.getElementById('image').value = vn.image.url

    const systemRequirements = `
[quote][align=center][b][u]System Requirements[/u][/b][/align]
[*][b]OS[/b]: 
[*][b]Processor[/b]: 
[*][b]Memory[/b]: 
[*][b]Graphics[/b]: 
[*][b]DirectX[/b]: 
[*][b]Storage[/b]: [/quote]`
    const descInput = document.getElementById('album_desc')
    descInput.value =
        `${getDescription(result)}
${consoleOnly ? '' : systemRequirements}`

    insertScreenshots(vn.screenshots.map(s => s.url), true)
}

const idRegExp = /v(\d+)/

if (location.href.includes('upload.php')) {
    gameTitleInput.insertAdjacentHTML("afterend", '<a href="javascript:" id="fill_vndb">vndb</a>')
    const fill_vndb = document.getElementById('fill_vndb')
    fill_vndb.onclick = () => {
        if (!gameTitleInput.value) {
            return
        }
        const idMatch = idRegExp.exec(gameTitleInput.value)?.[0]
        req(idMatch).then(fillUpload)
    }
} else {
    createFiller('vndburi', idRegExp, req, {
        getAliases: r => getAliases(r),
        getCover: r => r.vn.image.url,
        getAgeRating: getAgeRating,
        getDescription: getDescription,
        getScreenshots: result => result.vn.screenshots.map(s => s.url),
        getYear: getYear,
        getTitle: getTitle,
    })
}

async function fakeReq(id) {
    return {
        "vn": {
            "aliases": [
                "Pascha3"
            ],
            "alttitle": "パステルチャイム3 バインドシーカー",
            "description": `One day, during a mission with a 0% danger probability, an A-class adventurer Kaitos comes across a mysterious witch and accidentally disrupts her ritual. As a result, many evil spirits called Binds are released into the world and Kaitos, being affected by the ritual too, doesn't have much choice but to start helping the witch in sealing the Binds that have been set free.

Enlisting the support from his friends both new and old, our hero sets out on a quest which ends up being much more complicated than a simple Bind hunt...

[quote][align=center][b][u]System Requirements[/u][/b][/align]
[b]Minimum[/b]
[*][b]OS[/b]: Windows 7
[*][b]Processor[/b]: 2.0 GHz
[*][b]Memory[/b]: 2 GB RAM
[*][b]Graphics[/b]: Integrated Graphics
[*][b]Storage[/b]: 200 MB available space[/quote] []`,
            "id": "v11292",
            "image": {
                "url": "https://t.vndb.org/cv/92/95792.jpg"
            },
            "platforms": [
                "win"
            ],
            "released": "2013-02-15",
            "screenshots": [
                {
                    "url": "https://t.vndb.org/sf/42/43242.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/43/43243.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/44/43244.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/45/43245.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/46/43246.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/47/43247.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/48/43248.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/49/43249.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/50/43250.jpg"
                },
                {
                    "url": "https://t.vndb.org/sf/17/44017.jpg"
                }
            ],
            "tags": [
                {
                    "id": "g2",
                    "name": "Fantasy"
                },
                {
                    "id": "g3171",
                    "name": "High School Student Heroine"
                }
            ],
            "title": "Pastel Chime 3 Bind Seeker",
            "titles": [
                {
                    "lang": "ja",
                    "title": "パステルチャイム3 バインドシーカー"
                },
                {
                    "lang": "zh-Hans",
                    "title": "粉彩铃音3"
                }
            ]
        },
        "releases": [
            {
                "extlinks": [
                    {
                        "id": "http://www.alicesoft.com/pastelchime3/"
                    },
                    {
                        "id": "14859"
                    },
                    {
                        "id": "699486"
                    }
                ],
                "has_ero": true,
                "id": "r23266",
                "minage": 18
            },
            {
                "extlinks": [
                    {
                        "id": "http://www.alicesoft.com/pastelchime3/download/index.html"
                    }
                ],
                "has_ero": true,
                "id": "r25263",
                "minage": 18
            },
            {
                "extlinks": [
                    {
                        "id": "http://www.alicesoft.com/pastelchime3/"
                    },
                    {
                        "id": "VJ007518"
                    },
                    {
                        "id": "102512"
                    },
                    {
                        "id": "80992"
                    },
                    {
                        "id": "1004276"
                    },
                    {
                        "id": "dlsoft.dmm.co.jp/detail/alice_0021/"
                    }
                ],
                "has_ero": true,
                "id": "r28316",
                "minage": 18
            },
            {
                "extlinks": [
                    {
                        "id": "https://mangagamer.org/pastelchime3/"
                    }
                ],
                "has_ero": true,
                "id": "r138108",
                "minage": 18
            }
        ]
    }
}

/** @returns {Promise<Result>} */
async function req(id) {
    /** @type {Result} */
    const result = {}
    await GM.xmlHttpRequest({
        url: 'https://api.vndb.org/kana/vn',
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        data: JSON.stringify({
            "filters": id ? ["id", "=", id] : ["search", "=", `${gameTitleInput.value}`],
            "fields": "alttitle, titles.title, title, aliases, description, image.url, screenshots.url, released, titles.lang, tags.name, platforms",
            "results": 1
        }),
        responseType: "json",
        onload: response => result.vn = response.response.results[0]
    })

    await GM.xmlHttpRequest({
        url: 'https://api.vndb.org/kana/release',
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        data: JSON.stringify({
            "filters": ["and", ["vn", "=", ["id", "=", result.vn.id]], ["official", "=", 1]],
            "fields": "minage, has_ero, extlinks.id",
            "results": 100
        }),
        responseType: "json",
        onload: response => result.releases = response.response.results
    })

    return result
}

/** @param {Result} result
 * @param {VndbTitle?} englishTitle
 */
function getTitle(result, englishTitle) {
    englishTitle ??= result.vn.titles.find(a => a.lang === 'en')
    return englishTitle ? englishTitle.title : result.vn.title
}

/** @param {Result} result */
function getYear(result) {
    return result.vn.released === 'TBA' ? new Date().getFullYear() + 1 : result.vn.released.split('-')[0]
}

/** @param {Result} result */
function getDescription(result) {
    return `[align=center][b][u]About the game[/u][/b][/align]
${removeLastBracket(result.vn.description)}`
}


/** @param {Result} result */
function getAgeRating(result) {
    let rating
    const highestMinAge = Math.max(...result.releases.map(result => result.minage))
    if (highestMinAge === 12 || highestMinAge === 13) rating = 5
    else if (highestMinAge === 16 || highestMinAge === 17) rating = 7
    else if (highestMinAge >= 18) rating = 9
    else rating = 13
    return rating
}

/**
 * @param {Result} result
 * @param {VndbTitle?} englishTitle
 * @returns {string}
 */
function getAliases(result, englishTitle) {
    const vn = result.vn
    englishTitle ??= vn.titles.find(a => a.lang === 'en')
    const aliases = [vn.alttitle, vn.aliases.join(", "), englishTitle ? vn.title : null].filter(Boolean)

    for (const externalLink of result.releases.flatMap(release => release.extlinks)) {
        if (/[A-Z]{2}\d{4,}/.test(externalLink.id))
            aliases.push(externalLink.id)
    }

    return [...new Set(aliases)].join(', ')
}