您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Formatters
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/540511/1620007/GGn%20Formatters.js
// ==UserScript== // @name GGn Formatters // @version 7 // @description Formatters // @author ingts (some by ZeDoCaixao and letsclay) // @match https://gazellegames.net/ // ==/UserScript== function formatTitle(str, alias) { const japaneseLowercase = new Map([ ["ga", ["が", "ガ"]], ["no", ["の", "ノ"]], ["wa", ["わ", "ワ"]], ["mo", ["も", "モ"]], ["kara", ["から", "カラ"]], ["made", ["まで", "マデ"]], ["to", ["と", "ト"]], ["ya", ["や", "ヤ"]], ["de", ["で", "デ"]], ["ni", ["に", "ニ"]], ["so", ["そ", "ソ"]], ["na", ["な", "ナ"]], ["i", ["い", "イ"]], ["u", ["う", "ウ"]], ["e", ["え", "エ"]], ["o", ["お", "オ"]], ["san", ["さん"]], ["sama", ["さま"]], ["kun", ["くん"]], ["chan", ["ちゃん"]] ]) 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-Z0-9'’])/ const allUppercase = new Set(['rpg', 'fps', 'tps', 'rts', 'tbs', 'mmo', 'mmorpg', 'arpg', 'jrpg', 'pvp', 'pve', 'ntr', 'td', 'vr', 'npc', 'ost']) return str .replace(/\s/g, ' ').replace('—', ' - ') .replace(/~$/, '').replace(/ ~$/, '').replace(/-$/, '').replace(/^-/, '').replace(/ ~ /, ': ').replace(/ ~/, ': ').replace(/ - /, ': ').replace(/ -/, ': ') .replace('™', '').replace('®', '').replace(' : ', ': ') .toLowerCase().trim() .split(wordSeparators) .map((current, index, array) => { const isFirstOrLastWord = index !== 0 && index !== array.length - 1 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 && !isFirstOrLastWord) { const jpWords = japaneseLowercase.get(current) if (jpWords?.some(w => alias.includes(w))) return current } if ( /* Check for small words */ current.search(smallWords) > -1 && /* Skip first and last word */ isFirstOrLastWord && /* 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 formatAbout(str, gameTitle) { if (!str) return "" str = str.replace(/defence/g, 'defense') str = str.replace(/ *\/ */g, '/') // bold game title. replace [u] or [i] with bold if (gameTitle) { // noinspection RegExpSuspiciousBackref const boldTitleRegex = new RegExp(`(?:\\[([ui])\])?${gameTitle}(?:\\[\\/\\1])?`, 'i') // maybe use RegExp.escape later str = str.replace(boldTitleRegex, `[b]${gameTitle}[/b]`) } // If a line starts with [u], [i], or [b], there is no other text on that line, and it contains 'features', replace tags with [align=center][b][u] str = str.replace(/^(\[b]|\[u]|\[i])*(.*?)(\[\/b]|\[\/u]|\[\/i])*$/gm, (match, p1, p2, p3) => (p1 && p3 && /features/i.test(p2)) ? `[align=center][b][u]${p2}[/u][/b][/align]` : match) // Title case text inside [align=center][b][u] str = str.replace(/\[align=center]\[([bu])]\[([bu])]([\s\S]*?)\[\/\2]\[\/\1]\[\/align]/g, (match, p1, p2, p3) => `[align=center][b][u]${formatTitle(p3)}[/u][/b][/align]`) // Add a newline before lines with [align=center] if there isn't already a double newline before it str = str.replace(/(?<!\n\n)(\[align=center])/g, '\n\$1') // Remove colons in text inside [align=center][b][u] str = str.replace(/\[align=center]\[b]\[u](.*?)\[\/u]\[\/b]\[\/align]/g, (match, _) => match.replace(/:/g, '')) // Replace different list symbols at the start with [*] str = str.replace(/^[-•◦■・★]\s*/gm, '[*]') // If a line starts with [u], [i], or [b] and it is not the only text on that line, add [*] at the start and replace tags with [b] str = str.replace(/^(\[b]|\[u]|\[i])*(.*?)(\[\/b]|\[\/u]|\[\/i])+(.*$)/gm, (match, p1, p2, p3, p4) => { if (p4.trim() === '') { return match } return p1 && p3 ? `[*][b]${p2}[/b]${p4}` : match }) // If a line starts with [*] followed by a [u] or [i], replace them with [b] str = str.replace(/^\[\*]\[[ui]](.*?)\[\/[ui]]/gm, '[b]$1[/b]') // If a line starts with [u], [i], or [b] and has nothing else after the closing tag, sentence case the text inside tags str = str.replace(/(^|\n)(\[([uib])](.*?)\[\/([uib])]\s*$)/gm, (match, p1, p2, p3, p4) => `${p1}[${p3}]${toSentenceCase(p4)}[/${p3}]`) // If a line starts with [*], have only one new line until the next [*] and after the last one, have a double newline str = fixMultiLinesInLists(str) // Remove double newlines between [*] lines // str = str.replace(/(\[\*][^\n]*)(\n{2,})(?=\[\*])/g, '$1\n') // Add a newline when next line doesn't start with [*] // str = str.replace(/(\[\*][^\n]*\n)([^\[*\]\n])/g, '$1\n$2') // Move : and . outside of closing tags str = str.replace(/(\[([bui])])(.*?)([:.])\[\/([bui])]/g, '$1\$3[/b]\$4') // If the line starts with [*] and the whole line until terminal punctuation is wrapped in [u], [i], or [b], remove the wrapping tags str = str.replace(/^\[\*]\[([bui])](.*?)\[\/([bui])]([.?!。?!])$/gm, "[*]$2$4") // If a line ends with [/align] replace double newlines with one newline str = str.replace(/(\[\/align])\n\n/g, '$1\n') return str function fixMultiLinesInLists() { const lines = str.split('\n'); const result = []; let i = 0; while (i < lines.length) { const line = lines[i]; // If line starts with [*], we're in a list section if (line.startsWith('[*]')) { const listItems = []; // Collect all consecutive [*] items (skipping empty lines between them) while (i < lines.length && (lines[i].startsWith('[*]') || lines[i].trim() === '')) { if (lines[i].startsWith('[*]')) { listItems.push(lines[i]); } i++; } // Add list items with single newlines between them result.push(...listItems); // Add single empty line after the list section if there's more content if (i < lines.length) { result.push(''); // This creates one empty line, which with the next line creates a double newline } } else { // Regular line, add it result.push(line); i++; } } return result.join('\n'); } function toSentenceCase(str) { let lowerStr = str.toLowerCase(); // Capitalize the first letter let newStr = lowerStr.charAt(0).toUpperCase() + lowerStr.slice(1); // Capitalise subsequent sentences newStr = newStr.replace(/([.?!]\s+)([a-z])/g, (match, p1, p2) => p1 + p2.toUpperCase()); return newStr; } } function formatSysReqs(str) { if (!str) return "" str = str.replace(/:\[\/b] /g, "[/b]: ") str = str.replace(/:\n/g, "\n") str = str.replace(/:\[\/b]\n/g, "[/b]\n") // str = str.replace(/\n\n\[b]/g, "\n[b]") str = str.replace(/\[\*]Requires a 64-bit processor and operating system\n(.*)/g, (match, osLine) => osLine.includes('64') ? osLine : `${osLine} (64-bit)`) str = str.replace(/OS \*/g, 'OS') str = str.replace(/(\d+)\s?(\w)b/gi, (match, p1, p2) => `${p1} ${p2.toUpperCase()}B`) str = str.replace(/([a-zA-Z]{2,})(\d)/g, '$1 $2') str = str.replace(/ *\(?or\)? *(?:better|greater|higher|over|similar|more|later|equivalent)\)?/gi, '') // convert to next unit if divisible by 1024 str = str.replace(/(\d+)\s*([KM]B)/gi, (match, num, unit) => { const intNum = parseInt(num) if (intNum % 1024 === 0) { return unit === 'KB' ? `${intNum / 1024} MB` : unit === 'MB' ? `${intNum / 1024} GB` : match } return match }) formatSection('OS', match => { match = match.replace(/(?:Microsoft\s?)?Win(?:dows)?/gi, 'Windows') match = match.replace(/^\(?64-bit\)?\s?(.*)/g, "$1 (64-bit)") match = match.replace(/macos/gi, "macOS") match = match.replace(/, /g, '/') // Remove repeated OS names let firstSkipped = false match = match.replace(/[a-zA-Z]+ /g, match => { if (!firstSkipped) { firstSkipped = true return match } return '' }) return match }) formatSection('Processor', match => { match = match.replace(/i(\d+)/gi, 'i$1') // small i match = match.replace(/ or /gi, ' /') match = match.replace(/ryzen/gi, 'Ryzen') match = match.replace(/core/gi, 'Core') match = match.replace(/core with (.*)Hz/gi, 'Core $1Hz') match = match.replace(/(.*?)-core/gi, '$1 Core') match = match.replace(/(\d\.?\d?)\s?(\w)hz/gi, (match, p1, p2) => `${p1} ${p2.toUpperCase()}Hz`) match = match.replace(/(\d{3,}) (.*Hz)/g, '$1, $2') match = match.replace(/(i\d) - (\d)/g, '$1-$2') return match }) formatSection('Graphics', match => { match = match.replace(/(?:nvidia )?(?:geforce )?([rg]tx) ?(\d+)/gi, (_, tx, num) => `Nvidia GeForce ${tx.toUpperCase()} ${num}`) match = match.replace(/(?:of )?(?:dedicated )?(?:graphics |video )?/gi, '') match = match.replace(/(?<!V)RAM|memory/, 'VRAM') match = match.replace(/graphics card with (.*?)\s?(?:of)? V?RAM/gi, '$1 VRAM') match = match.replace(/nvidia/gi, 'Nvidia') match = match.replace(/gpu/gi, '') match = match.replace(/(\d)Ti/gi, '$1 Ti') match = match.replace(/(?:series|video)?\s?card?/gi, '') match = match.replace(/(\w)\/(\w)/g, '$1 / $2') return match }) formatSection('DirectX', match => { match = match.replace(/v(?:ersion)?\s?/i, '') match = match.replace('.0', '') return match }) str = str.replace(/(\d)\/([a-zA-Z])/g, '$1 / $2') str = str.replace(/(\S)\(/g, '$1 (') str = str.replace(/(\S)\+/g, '$1') str = str.replace(/intel/gi, 'Intel') str = str.replace(/amd/gi, 'AMD') str = str.replace(/\(?64.?bit\)?/g, "(64-bit)") return str /** * @param {string} sectionName * @param {(match: string) => string} func */ function formatSection(sectionName, func) { const regExp = new RegExp(`^\\[\\*]\\[b]${sectionName}\\[\\/b]: (.*)`, 'gm') for (const match of str.matchAll(regExp)) { if (match?.[1]) { str = str.replace(match[1], func(match[1])) } } } } function formatDescCommon(str) { if (!str) return "" str = str.replace(/[™®©]/g, '') str = str.replace(/([?!#$%;,])(\w)/g, '$1 $2') str = str.replace(/([a-zA-Z]):(\w)/g, '$1: $2') // [a-zA-Z] to avoid aspect ratio str = str.replace(/(\w)\.(\w)/g, (match, p1, p2) => { if (/\d/.test(p1) && /\d/.test(p2)) return match // dont add space for decimals return '$1. $2' }) return str }