您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Generate Tabletop Simulator deck object json
当前为
// ==UserScript== // @name Scryfall TTS deck save // @namespace http://scryfall.com/ // @version 0.2 // @description Generate Tabletop Simulator deck object json // @author hyper // @match https://scryfall.com/*/decks/* // @grant none // ==/UserScript== (function() { 'use strict'; const controls = document.querySelectorAll('.deck-controls-group')[1]; controls.append(createSaveButton()); })(); function createExportButton(){ const btn = document.createElement('button'); btn.className = 'button-n tiny bp-mid-only'; btn.type = 'button'; btn.innerHTML = '<b>TTS Main</b>'; btn.onclick = () => showsheet(); return btn; } function createTokensButton(){ const btn = document.createElement('button'); btn.className = 'button-n tiny bp-mid-only'; btn.type = 'button'; btn.innerHTML = '<b>TTS Side & Tokens</b>'; btn.onclick = () => showsheet(false, true); return btn; } function createSaveButton(){ const btn = document.createElement('button'); btn.className = 'button-n tiny bp-mid-only'; btn.type = 'button'; btn.innerHTML = '<b>TTS Save</b>'; btn.onclick = () => downloadSave(); return btn; } async function showsheet(main=true, tokens=false){ const imgs = await collectImages(main, tokens); const img = createCanvasImage(imgs.flat()); img.style.maxWidth='100%'; img.style.maxHeight='100%'; const container = document.createElement('div'); container.style.position = 'fixed'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100vw'; container.style.height = '100vh'; container.style.zIndex = '1000'; container.style.padding = '10vw'; container.style.background = 'rgba(0,0,0,.7)'; container.append(img); container.onclick = () => container.remove(); document.body.append(container); } function loadImg(url){ const img = document.createElement('img'); img.src = url; return new Promise(resolve =>{ img.onload = () => resolve(img); }); } async function loadCard(url){ const res = await fetch(url); const json = await res.json(); const imguris = json.image_uris || json.card_faces[0].image_uris; const related = (json.all_parts || []).slice(1); const name = json.card_faces ? json.card_faces.map(cf => cf.printed_name || cf.name).join(' // ') : json.printed_name || json.name; const cost = json.mana_cost; const typeline = json.card_faces ? json.card_faces.map(cf => cf.printed_type_line || cf.type_line).join(' // ') : json.printed_type_line || json.type_line; const stats = json.power ? `${json.power}/${json.toughness}` : undefined; const oracle = json.card_faces ? json.card_faces.map(cf => cf.printed_text || cf.oracle_text).join(' // ') : json.printed_text || json.oracle_text; const head = `${name} - ${cost}\n${stats || ""} ${typeline}`; return [imguris.large, related, head, oracle]; } async function collectCards(main=true, tokens=false){ const cards = [...document.querySelectorAll('.deck-list-entry')].map( async item => { const side = item.parentElement.parentElement.firstElementChild.innerText.startsWith('SIDEBOARD'); const num = parseInt(item.querySelector('.deck-list-entry-count').innerText); const cardUrl = item.querySelector('a').href; const url = cardUrl.slice(0, cardUrl.lastIndexOf('/')).replace('scryfall', 'api.scryfall').replace('/card/', '/cards/'); const card = await loadCard(url); const cards = []; if( (main && !side) || (tokens && side)){ for(let i = 0; i < num; i ++) cards.push(card); } if(tokens){ const tokens = card[1]; const loaded = tokens.map(c => loadCard(c.uri)); cards.push(...await Promise.all(loaded)); } return cards; } ); return (await Promise.all(cards)).flat(); } async function collectImages(main=true, tokens=false){ return await Promise.all(collectCards(main, tokens).map(card => loadImg(card[0]))); } function createCanvasImage(imgs){ const w = 672; const h = 936; const canvas = document.createElement('canvas'); canvas.width = w * 10; canvas.height = h * 7; const ctx = canvas.getContext('2d'); let x = 0; let y = 0; function inc(){ x+=1; if(x>=10){ x-=10; y+=1; } } for(const img of imgs){ ctx.drawImage(img, x * w, y * h); inc(); } return canvas; } async function downloadSave(){ const cards = await collectCards(); const cardItems = cards.map((c,i) => makeTTSCard({name: c[2], oracle: c[3], face: c[0], id: i+1})); const deck = makeTTSDeck(cardItems); deck.Nickname = "Main"; const sides = await collectCards(false, true); const sideItems = sides.map((c,i) => makeTTSCard({name: c[2], oracle: c[3], face: c[0], id: i+1})); const sideDeck = makeTTSDeck(sideItems); sideDeck.Transform.posX = 3; sideDeck.Nickname = "Sideboard"; const name = document.querySelector(".deck-details-title").innerText.trim(); const save = makeTTSSave(deck, sideDeck); download(name + ".json", JSON.stringify(save, null, 4)); } function download(filename, text) { var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } function makeTTSCard({name, oracle, face, id}){ const card = {...TTSCard}; card.Nickname = name; card.Description = oracle; card.CardID = id * 100; card.CustomDeck = {}; card.CustomDeck[id] = { FaceURL: face, BackURL: "http://ww1.sinaimg.cn/large/8239391egy1gg9acxj0ryj207d0aldj7.jpg", "NumWidth": 1, "NumHeight": 1, "BackIsHidden": true, "UniqueBack": false, "Type": 0 }; card.GUID = Math.random().toString(16).slice(-6); return card; } function makeTTSDeck(cards){ const deck = {...TTSDeck}; deck.DeckIDs = cards.map(c => c.CardID); deck.CustomDeck = cards.reduce((a,b) => ({...a, ...b.CustomDeck}), {}); deck.ContainedObjects = cards; deck.GUID = Math.random().toString(16).slice(-6); return deck; } function makeTTSSave(...ObjectStates){ return {...TTS, ObjectStates}; } const TTS = { "SaveName": "", "GameMode": "", "Date": "", "Gravity": 0.5, "PlayArea": 0.5, "GameType": "", "GameComplexity": "", "Tags": [], "Table": "", "Sky": "", "Note": "", "Rules": "", "TabStates": {}, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "VersionNumber": "", } const TTSDeck = { "Name": "Deck", "Transform": { "posX": 0, "posY": 0, "posZ": 0, "rotX": 0, "rotY": 180, "rotZ": 180.0, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "ColorDiffuse": { "r": 0.713235259, "g": 0.713235259, "b": 0.713235259 }, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": true, "Hands": false, "SidewaysCard": false, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", } const TTSCard = { "Name": "Card", "Transform": { "posX": 0, "posY": 0, "posZ": 0, "rotX": 0, "rotY": 180, "rotZ": 180.0, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "ColorDiffuse": { "r": 0.713235259, "g": 0.713235259, "b": 0.713235259 }, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": true, "Hands": true, "CardID": 0, "SidewaysCard": false, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" };