您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Uploady library
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/548332/1657218/GGn%20Uploady.js
// ==UserScript== // @name GGn Uploady // @version 4 // @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 = // language=CSS ` [id*=filler] label { display: flex; align-items: center; 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(40 43 76); border-radius: 2px; border: 2px solid #997979; z-index: 9; max-width: 71%; height: 840px; ` 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; height: 810px; ` const bottom = document.createElement('div') mainContainer.appendChild(bottom) bottom.style.margin = '10px 0 10px 0' bottom.style.display = 'flex' bottom.style.gap = '3px' bottom.insertAdjacentHTML('beforeend', `<button type="button">Close</button> <button type="button">Check all</button> <button type="button" style="margin-left: 10px;">Fill</button> <label style="margin-left: 15px;"> <input type="checkbox" checked> Auto PTPimg all </label>`) 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: 100%">${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" class="ss-remove-existing" checked> 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') { 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') 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 autoPtpimg = bottom.querySelector('input').checked const namedCheckboxes = innerContainer.querySelectorAll('input[type=checkbox][data-name]') for (const namedCheckbox of namedCheckboxes) { if (!namedCheckbox.checked) continue const name = namedCheckbox.dataset.name const value = inputMap[name].value if (!value) continue const element = document.querySelector(`[name=${inputMap[name].selector}]`) element.value = value if (name === 'cover' && autoPtpimg) { imageUpload(value, element) } } 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-remove-existing").checked, autoPtpimg) } mainContainer.style.display = 'none' } function createLabelWithCheckbox(after, checkboxAttrs, appendTo) { const label = document.createElement('label'); (appendTo || innerContainer).appendChild(label) label.innerHTML = `<input type="checkbox" ${checkboxAttrs ? checkboxAttrs : ''}> ${after} ` return label } } } /** * @param {string[]} urls * @param {boolean=true} removeExisting * @param {boolean=false} autoPtpimg */ function insertScreenshots(urls, removeExisting = true, autoPtpimg = false) { 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 (autoPtpimg) imageUpload(screenInputs[i].value, screenInputs[i]) } // remove extra inputs 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() } } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址