您需要先安装一个扩展,例如 篡改猴、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.2 // @match *://scryfall.com/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_openInTab // ==/UserScript== (function() { 'use strict'; GM_addStyle(` .print-langs-item.translate-toggle { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } `); const API_BASE_URL = 'https://api.sbwsz.com/card'; const TYPE_NAME_TRANSLATIONS_URL = 'https://sbwsz.com/static/typeName.json'; let typeNameTranslations = null; const cardLanguageStates = new Map(); GM_registerMenuCommand('默认显示中文: ' + (GM_getValue('defaultToChinese', false) ? '开' : '关'), toggleDefaultLanguage); function getCardInfoFromDOM(cardProfile) { const printsCurrentSet = cardProfile.querySelector('.prints-current-set'); if (!printsCurrentSet) return null; const setMatch = printsCurrentSet.href.match(/\/sets\/(\w+)/); if (!setMatch) return null; const setCode = setMatch[1]; const detailsText = cardProfile.querySelector('.prints-current-set-details')?.textContent || ''; const numberMatch = detailsText.match(/#([^\s]+)/); if (!numberMatch) return null; const collectorNumber = numberMatch[1]; return { setCode, collectorNumber }; } document.addEventListener('contextmenu', function(e) { if (e.target.classList.contains('translate-toggle')) { e.preventDefault(); e.stopPropagation(); const parent = e.target.closest('[data-card-id]') || document; const cardInfo = getCardInfoFromDOM(parent); if (cardInfo) { setTimeout(() => { const sbwszUrl = `https://sbwsz.com/card/${cardInfo.setCode}/${cardInfo.collectorNumber}`; GM_openInTab(sbwszUrl, false); }, 0); } return false; } }, {capture: true, passive: false}); 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 cardProfiles = document.querySelectorAll('.card-profile'); const containers = cardProfiles.length > 0 ? cardProfiles : [document]; for (const container of containers) { const cardInfo = getCardInfoFromDOM(container); if (!cardInfo) continue; const cardId = `${cardInfo.setCode}_${cardInfo.collectorNumber}`; container.dataset.cardId = cardId; try { saveOriginalContent(container); addToggleButton(true, container); const chineseData = await getChineseCardData(cardInfo.setCode, cardInfo.collectorNumber); const scryfallFaceCount = container.querySelectorAll('.card-text-title').length || 1; if (scryfallFaceCount === 1 || !chineseData.front) { await saveSingleFacedCard(chineseData, container); } else { await saveDoubleFacedCard(chineseData, container); } updateToggleButton(false, container); const defaultToChinese = GM_getValue('defaultToChinese', false); cardLanguageStates.set(cardId, false); if (defaultToChinese) { await toggleLanguage({ preventDefault: () => {}, target: container.querySelector('.print-langs-item') }, cardId); } } catch (error) { console.error('处理卡牌时出错:', error); updateToggleButton(true, container); } } } function addToggleButton(loading = false, parent = document) { const printLangs = parent.querySelector('.print-langs'); if (!printLangs) return; const cardId = parent.dataset.cardId || document.location.pathname; if (!parent.dataset.cardId) { parent.dataset.cardId = cardId; } const toggleLink = document.createElement('a'); toggleLink.className = 'print-langs-item translate-toggle'; toggleLink.href = 'javascript:void(0);'; toggleLink.textContent = loading ? '加载中...' : (cardLanguageStates.get(cardId) ? '原文' : '汉化'); toggleLink.style.cursor = loading ? 'wait' : 'pointer'; if (!loading) { toggleLink.addEventListener('click', (e) => toggleLanguage(e, cardId)); } printLangs.insertBefore(toggleLink, printLangs.firstChild); } function updateToggleButton(error = false, parent = document) { const toggleLink = parent.querySelector('.print-langs-item'); if (toggleLink) { const cardId = parent.dataset.cardId || document.location.pathname; toggleLink.textContent = error ? '加载失败' : (cardLanguageStates.get(cardId) ? '原文' : '汉化'); toggleLink.style.cursor = error ? 'not-allowed' : 'pointer'; if (error) { toggleLink.removeEventListener('click', (e) => toggleLanguage(e, cardId)); } else { toggleLink.addEventListener('click', (e) => toggleLanguage(e, cardId)); } } } async function toggleLanguage(event, cardId) { event.preventDefault(); const cardContainer = cardId === document.location.pathname ? document : document.querySelector(`[data-card-id="${cardId}"]`); if (!cardContainer) { console.error('找不到卡牌容器'); return; } const elements = cardContainer.querySelectorAll( '.card-text-card-name, .card-text-type-line, .card-text-oracle, .card-text-flavor, ' + '.card-legality-item dt, .card-legality-item dd' ); const toggleLink = event.target; if (elements.length === 0 || !elements[0].dataset.chineseContent) { console.error('中文数据尚未加载完成'); return; } const currentState = cardLanguageStates.get(cardId) || false; cardLanguageStates.set(cardId, !currentState); toggleLink.textContent = cardLanguageStates.get(cardId) ? '原文' : '汉化'; elements.forEach(el => { if (el.dataset.chineseContent) { [el.innerHTML, el.dataset.chineseContent] = [el.dataset.chineseContent, el.innerHTML]; } }); } function saveOriginalContent(parent = document) { parent.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, parent = document) { await saveCardFace(parent, parent, chineseData, 0); console.log('中文数据已保存'); } async function saveDoubleFacedCard(chineseData, parent = document) { const cardTextDiv = parent.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(), saveLegality(cardTextDiv) ]); } 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 }); }); } const FORMAT_TRANSLATIONS = { 'Standard': '标准', 'Alchemy': '炼金', 'Pioneer': '先驱', 'Explorer': '探险', 'Modern': '摩登', 'Historic': '史迹', 'Legacy': '薪传', 'Brawl': '争锋', 'Vintage': '特选', 'Timeless': '永恒', 'Commander': '指挥官', 'Pauper': '纯铁', 'Oathbreaker': '破誓', 'Penny': '便士' }; const LEGALITY_TRANSLATIONS = { 'Legal': '合法', 'Not Legal': '不合法', 'Banned': '禁用', 'Restrict.': '限制' }; async function saveLegality(parent = document) { const legalityItems = parent.querySelectorAll('.card-legality-item'); legalityItems.forEach(item => { const format = item.querySelector('dt'); const legality = item.querySelector('dd'); if (format && legality) { const formatText = format.textContent.trim(); format.dataset.chineseContent = FORMAT_TRANSLATIONS[formatText] || formatText; const legalityText = legality.textContent.trim(); legality.dataset.chineseContent = LEGALITY_TRANSLATIONS[legalityText] || legalityText; } }); } main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址