您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为Scryfall没有中文的卡牌添加汉化,所有汉化数据均来自中文卡查sbwsz.com
当前为
// ==UserScript== // @name Scryfall卡牌汉化 // @description 为Scryfall没有中文的卡牌添加汉化,所有汉化数据均来自中文卡查sbwsz.com // @author lieyanqzu // @license GPL // @namespace http://github.com/lieyanqzu // @icon https://scryfall.com/favicon.ico // @version 1.0 // @match *://scryfall.com/card/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; const API_BASE_URL = 'https://api.sbwsz.com/card'; const TYPE_NAME_TRANSLATIONS_URL = 'https://sbwsz.com/static/typeName.json'; let typeNameTranslations = null; let isChineseDisplayed = GM_getValue('defaultToChinese', false); GM_registerMenuCommand('默认显示中文: ' + (isChineseDisplayed ? '开' : '关'), toggleDefaultLanguage); function toggleDefaultLanguage() { const newDefault = !GM_getValue('defaultToChinese', false); GM_setValue('defaultToChinese', newDefault); location.reload(); } async function getChineseCardData(setCode, collectorNumber) { const apiUrl = `${API_BASE_URL}/${setCode}/${collectorNumber}`; try { const response = await makeRequest('GET', apiUrl); const data = JSON.parse(response.responseText); const scryfallFaceCount = document.querySelectorAll('.card-text-title').length || 1; if (scryfallFaceCount === 1) { return processSingleFacedCard(data.data[0]); } else if (data.type === 'double' && data.data.length === 2) { return processDoubleFacedCard(data.data); } else if (data.type === 'normal' && data.data.length > 0) { return processSingleFacedCard(data.data[0]); } throw new Error('无法获取中文卡牌数据'); } catch (error) { console.error('获取中文卡牌数据失败:', error); throw error; } } function processCardFace(cardData) { const name = cardData.zhs_faceName || cardData.translatedName || cardData.zhs_name || cardData.officialName || cardData.name; return { name, text: processText(cardData.translatedText || cardData.zhs_text || cardData.officialText || cardData.text, name), flavorText: processText(cardData.zhs_flavorText || cardData.translatedFlavorText || cardData.flavorText) }; } const processDoubleFacedCard = data => ({ front: processCardFace(data[0]), back: processCardFace(data[1]) }); const processSingleFacedCard = cardData => processCardFace(cardData); function processText(text, cardName) { if (!text) return text; text = text.replace(/\\n/g, '\n'); return cardName ? text.replace(/CARDNAME/g, cardName) : text; } async function getTypeNameTranslations() { if (typeNameTranslations) return typeNameTranslations; try { const response = await makeRequest('GET', TYPE_NAME_TRANSLATIONS_URL); typeNameTranslations = JSON.parse(response.responseText); return typeNameTranslations; } catch (error) { console.error('获取类别翻译数据失败:', error); throw error; } } async function translateType(englishType) { const translations = await getTypeNameTranslations(); return englishType.trim().split('—').map((part, index) => { const words = part.trim().split(/\s+/); const translatedWords = words.map(word => translations[word] || word); return index === 0 ? translatedWords.join('') : translatedWords.join('/'); }).join(' ~ '); } async function main() { const match = window.location.pathname.match(/\/card\/(\w+)\/([^/]+)\//); if (!match) { console.error('无法从 URL 获取 setCode 和 collectorNumber'); return; } const [, setCode, collectorNumber] = match; try { saveOriginalContent(); addToggleButton(true); const chineseData = await getChineseCardData(setCode, collectorNumber); const scryfallFaceCount = document.querySelectorAll('.card-text-title').length || 1; if (scryfallFaceCount === 1 || !chineseData.front) { await saveSingleFacedCard(chineseData); } else { await saveDoubleFacedCard(chineseData); } updateToggleButton(); if (isChineseDisplayed) { isChineseDisplayed = false; await toggleLanguage({ preventDefault: () => {}, target: document.querySelector('.print-langs-item') }, false); } } catch (error) { console.error('获取或保存中文数据时出错:', error); updateToggleButton(true); } } function addToggleButton(loading = false) { const printLangs = document.querySelector('.print-langs'); if (!printLangs) return; const toggleLink = document.createElement('a'); toggleLink.className = 'print-langs-item'; toggleLink.href = 'javascript:void(0);'; toggleLink.textContent = loading ? '加载中...' : (isChineseDisplayed ? '原文' : '汉化'); toggleLink.style.cursor = loading ? 'wait' : 'pointer'; if (!loading) { toggleLink.addEventListener('click', toggleLanguage); } printLangs.insertBefore(toggleLink, printLangs.firstChild); } function updateToggleButton(error = false) { const toggleLink = document.querySelector('.print-langs-item'); if (toggleLink) { toggleLink.textContent = error ? '加载失败' : (isChineseDisplayed ? '原文' : '汉化'); toggleLink.style.cursor = error ? 'not-allowed' : 'pointer'; toggleLink[error ? 'removeEventListener' : 'addEventListener']('click', toggleLanguage); } } async function toggleLanguage(event, updateButton = true) { event.preventDefault(); const elements = document.querySelectorAll('.card-text-card-name, .card-text-type-line, .card-text-oracle, .card-text-flavor'); const toggleLink = event.target; if (elements.length === 0 || !elements[0].dataset.chineseContent) { console.error('中文数据尚未加载完成'); return; } isChineseDisplayed = !isChineseDisplayed; elements.forEach(el => { if (el.dataset.chineseContent) { [el.innerHTML, el.dataset.chineseContent] = [el.dataset.chineseContent, el.innerHTML]; } }); if (updateButton) { toggleLink.textContent = isChineseDisplayed ? '原文' : '汉化'; } } function saveOriginalContent() { document.querySelectorAll('.card-text-card-name, .card-text-type-line, .card-text-oracle, .card-text-flavor').forEach(el => { el.dataset.originalContent = el.innerHTML; }); } async function saveSingleFacedCard(chineseData) { await saveCardFace(document, document, chineseData, 0); console.log('中文数据已保存'); } async function saveDoubleFacedCard(chineseData) { const cardTextDiv = document.querySelector('.card-text'); if (!cardTextDiv) { console.error('无法找到卡牌文本元素'); return; } const cardFaces = cardTextDiv.querySelectorAll('.card-text-title'); if (cardFaces.length !== 2) { console.error('无法找到双面卡牌的元素'); return; } await Promise.all([ saveCardFace(cardTextDiv, cardFaces[0], chineseData.front, 0), saveCardFace(cardTextDiv, cardFaces[1], chineseData.back, 1) ]); } async function saveCardFace(cardTextDiv, cardFace, faceData, faceIndex) { await Promise.all([ saveElementText('.card-text-card-name', faceData.name, cardFace), saveType(cardTextDiv.querySelectorAll('.card-text-type-line')[faceIndex], faceData.name), saveCardText('.card-text-oracle', faceData.text, cardTextDiv, faceIndex), faceData.flavorText ? saveCardText('.card-text-flavor', faceData.flavorText, cardTextDiv, faceIndex) : Promise.resolve() ]); } async function saveElementText(selector, text, parent = document) { const element = parent.querySelector(selector); if (element) { element.dataset.chineseContent = text; } } async function saveType(typeLineElement, cardName) { if (!typeLineElement) return; const colorIndicator = typeLineElement.querySelector('.color-indicator'); const typeText = typeLineElement.textContent.replace(colorIndicator ? colorIndicator.textContent.trim() : '', '').trim(); try { const translatedType = await translateType(typeText); typeLineElement.dataset.chineseContent = colorIndicator ? `${colorIndicator.outerHTML} ${translatedType}` : translatedType; } catch (error) { console.error('翻译类型时出错:', error); } } async function saveCardText(selector, text, parent = document, index = 0) { const elements = parent.querySelectorAll(selector); if (elements[index]) { const preservedHtml = await preserveManaSymbols(elements[index].innerHTML, text); elements[index].dataset.chineseContent = `<p>${preservedHtml.replace(/\n/g, '</p><p>')}</p>`; } } async function preserveManaSymbols(originalHtml, chineseText) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = originalHtml; const manaSymbols = tempDiv.querySelectorAll('abbr.card-symbol'); const symbolMap = new Map(); manaSymbols.forEach(symbol => { const symbolText = symbol.title.match(/\{(.+?)\}/)?.[0] || symbol.textContent; symbolMap.set(symbolText, (symbolMap.get(symbolText) || []).concat(symbol.outerHTML)); }); return Array.from(symbolMap).reduce((result, [symbolText, htmls]) => { let index = 0; return result.replace(new RegExp(escapeRegExp(symbolText), 'g'), () => { const html = htmls[index]; index = (index + 1) % htmls.length; return html; }); }, chineseText); } const escapeRegExp = string => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); function makeRequest(method, url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method, url, onload: resolve, onerror: reject }); }); } main(); })();