Compare commits

..

No commits in common. "master" and "v2.9.2" have entirely different histories.

10 changed files with 3154 additions and 12495 deletions

View File

@ -1,13 +0,0 @@
# Configuration options: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
# Directory must be set to "/" to check for workflow files in .github/workflows
directory: "/"
# Check for updates to GitHub Actions once a week
schedule:
interval: "weekly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2

View File

@ -1,104 +0,0 @@
name: Picture Health
on:
push:
branches: [ master ]
paths:
- '.github/workflows/check_links.yml'
- '**.xml'
pull_request:
branches: [ master ]
paths:
- '.github/workflows/check_links.yml'
- '**.xml'
pull_request_review:
types: [ submitted ]
workflow_dispatch:
schedule:
# Runs at the start of each month (UTC)
- cron: '0 0 1 * *'
jobs:
check_urls:
# Do not run the scheduled workflow on forks
if: ( github.event_name != 'schedule' || github.repository_owner == 'Cockatrice' )
name: Check image links
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
# Restore cache
- name: Restore lychee cache
id: restore-cache
uses: actions/cache/restore@v5
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-
# Extract picture URLs from tokens.xml
- name: Extract URLs
id: tokens_pic_urls
uses: lycheeverse/lychee-action@v2
with:
args: --dump -- tokens.xml
output: url_list.md
jobSummary: false
# Check dumped URLs
- name: Check token art URLs
if: steps.tokens_pic_urls.outcome == 'success'
uses: lycheeverse/lychee-action@v2
with:
args: --no-progress --require-https --cache --max-cache-age 8h -- url_list.md
jobSummary: true
# List helpful stats on missing pictures
- name: List missing image link counts
shell: bash
run: |
{
echo "❌ **Missing Image Links (<kbd>master</kbd> Branch)**"
echo "| Count | Description |"
echo "|------:|:------------|"
echo "| [![](https://img.shields.io/badge/dynamic/xml?label=&colorB=white&query=count%28%2F%2Fcard%5Bnot%28set%29%5D%29&url=https%3A%2F%2Fraw.githubusercontent.com%2FCockatrice%2FMagic-Token%2Fmaster%2Ftokens.xml)](https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml) | Tokens with missing \`set\` element |"
echo "| [![](https://img.shields.io/badge/dynamic/xml?label=&colorB=white&query=count%28%2F%2Fset%29-count%28%2F%2Fset%5B%40picURL%5D%29&url=https%3A%2F%2Fraw.githubusercontent.com%2FCockatrice%2FMagic-Token%2Fmaster%2Ftokens.xml)](https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml) | Set elements with missing picURL attribute (\`<set>\`) |"
echo "| [![](https://img.shields.io/badge/dynamic/xml?label=&colorB=white&query=count%28%2F%2Fset%5B%40picURL%3D%22%22%5D%29&url=https%3A%2F%2Fraw.githubusercontent.com%2FCockatrice%2FMagic-Token%2Fmaster%2Ftokens.xml)](https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml) | Set elements with empty picURL value (\`<set picURL=\"\">\`) |"
} >> $GITHUB_STEP_SUMMARY
# Analyse extracted links (1/2)
- name: List duplicated image links
if: steps.tokens_pic_urls.outcome == 'success'
shell: bash
run: |
{
echo "🪞 **Duplicated Image Links**"
echo "| Count | Image URL |"
echo "|------:|:----------|"
# Remove empty lines | trim trailing integers (Scryfall) | sort | count and list only duplicates with numbers | sort descending | print md table
grep . url_list.md | sed 's/\.jpg?.*/.jpg/' | sort | uniq -cd | sort -nr | awk '{printf "| %5s | %s |\n", $1, $2}'
} >> $GITHUB_STEP_SUMMARY
# Analyse extracted links (2/2)
- name: List image hosting sources
if: steps.tokens_pic_urls.outcome == 'success'
shell: bash
run: |
{
echo "📶 **Image Hosting Statistics**"
echo "| Count | Domain |"
echo "|------:|:-------|"
# Extract 3rd field (domain name) | remove empty lines | sort | count and list with numbers | sort descending | print md table
awk -F/ '{print $3}' url_list.md | grep . | sort | uniq -c | sort -nr | awk '{printf "| %5s | %s |\n", $1, $2}'
} >> $GITHUB_STEP_SUMMARY
# Always save cache
- name: Save lychee cache
uses: actions/cache/save@v5
if: always()
with:
path: .lycheecache
key: ${{ steps.restore-cache.outputs.cache-primary-key }}

View File

@ -1,66 +0,0 @@
name: Image URLs
on:
workflow_dispatch:
schedule:
# runs in the middle of each month
- cron: '0 0 15 * *'
pull_request:
branches: [ master ]
paths:
- '.github/workflows/update_links.yml'
- 'scripts/update_image_links.py'
jobs:
update_links:
# Do not run the scheduled workflow on forks
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Update links
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: Query Scryfall API and update picURLs
shell: bash
working-directory: ./scripts
run: python3 update_image_links.py ../tokens.xml --inplace
- name: Create pull request
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v8
with:
add-paths: |
tokens.xml
commit-message: Update image links
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_image_links
delete-branch: true
title: 'Update image links'
body: |
Update all picURLs in the `tokens.xml` file via Scryfall API.
---
*This PR is automatically generated and updated by the workflow at `.github/workflows/update_links.yml`. Review [action runs][1].*<br>
[1]: https://github.com/Cockatrice/Magic-Token/actions/workflows/update_links.yml?query=branch%3Amaster
labels: |
CI
draft: false
- name: PR Status
if: github.event_name != 'pull_request'
shell: bash
env:
STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
run: |
if [[ "$STATUS" == "none" ]]; then
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
else
echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
fi
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY

View File

@ -1,84 +0,0 @@
name: Update version on tokens.xml changes
on:
push:
branches: [ master ]
paths:
- 'tokens.xml'
- 'version.txt'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !contains(github.ref, '/master') }}
jobs:
update_version:
if: "!startsWith(github.event.head_commit.message, 'Update version to ')"
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v6
- name: Get previous version
id: get_versions
shell: bash
run: |
old_version="$(<version.txt)"
current_date="$(date --utc +%Y%m%d)"
echo "old_version=$old_version" >> $GITHUB_OUTPUT
echo "current_date=$current_date" >> $GITHUB_OUTPUT
- name: Compute new version
id: compute_version
shell: python
run: |
import os, re
old_version = "${{steps.get_versions.outputs.old_version}}"
current_date = "${{steps.get_versions.outputs.current_date}}"
new_version = current_date
if old_version.startswith(current_date):
match = re.search(r"[a-z]+", old_version)
if match is None:
suffix = "a" # the first a is actually hidden
else:
suffix = match[0]
charlist = []
rev = reversed(suffix)
for char in rev:
if char == "z":
charlist.append("a")
else:
nextchar = chr(ord(char) + 1)
charlist.append(nextchar)
charlist.extend(rev)
break
else:
charlist.append("a")
new_version += "".join(reversed(charlist))
with open(os.environ["GITHUB_OUTPUT"], "a") as envFile:
print(f"new_version={new_version}", file=envFile)
- name: Update version in files
shell: bash
env:
VERSION_OLD: ${{steps.get_versions.outputs.old_version}}
VERSION_NEW: ${{steps.compute_version.outputs.new_version}}
run: |
echo "Updating version from $VERSION_OLD to $VERSION_NEW"
tag="sourceVersion"
sed -i "s?<$tag>.*</$tag>?<$tag>$VERSION_NEW</$tag>?" tokens.xml
echo "$VERSION_NEW" >version.txt
- name: Commit and push changes
shell: bash
env:
VERSION_NEW: ${{steps.compute_version.outputs.new_version}}
run: |
echo "::notice::Triggering commit: https://github.com/${GITHUB_REPOSITORY}/commit/$GITHUB_SHA"
git config user.name github-actions
git config user.email github-actions@github.com
git add tokens.xml version.txt
git commit -m "Update version to $VERSION_NEW" -m "Bump triggered by commit $GITHUB_SHA"
git push
commit_hash="$(git rev-parse HEAD)"
echo "::notice::Pushed commit: https://github.com/${GITHUB_REPOSITORY}/commit/$commit_hash"

View File

@ -1,9 +1,10 @@
[![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
[![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) [![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Magic-Spoiler)](https://gitter.im/Cockatrice/Magic-Spoiler)
Magic-Token [![](https://img.shields.io/badge/dynamic/xml.svg?label=version&url=https%3A%2F%2Fraw.githubusercontent.com%2FCockatrice%2FMagic-Token%2Fmaster%2Ftokens.xml&query=%2F%2FsourceVersion)](https://github.com/Cockatrice/Magic-Token/blob/master/tokens.xml) [![Picture Health](https://github.com/Cockatrice/Magic-Token/actions/workflows/check_links.yml/badge.svg?branch=master)](https://github.com/Cockatrice/Magic-Token/actions/workflows/check_links.yml?query=branch%3Amaster)
Magic-Token [![](https://img.shields.io/badge/dynamic/xml.svg?label=version&url=https%3A%2F%2Fraw.githubusercontent.com%2FCockatrice%2FMagic-Token%2Fmaster%2Ftokens.xml&query=%2F%2FsourceVersion)](https://github.com/Cockatrice/Magic-Token/blob/master/tokens.xml)
=================
This repo contains token information in [Cockatrice](https://github.com/cockatrice/cockatrice)'s [XML card database format](https://github.com/Cockatrice/Cockatrice/wiki/Custom-Cards-&-Sets#to-add-your-own-custom-cards-follow-these-steps) for Magic: The Gathering.<br />
It describes [![](https://img.shields.io/badge/dynamic/xml.svg?label=&colorB=white&url=https%3A%2F%2Fraw.githubusercontent.com%2FCockatrice%2FMagic-Token%2Fmaster%2Ftokens.xml&query=count(%2F%2Fcard))](https://github.com/Cockatrice/Magic-Token/blob/master/tokens.xml) individual tokens with linked art and the relation to their creators.
This repo contains tokens information in [Cockatrice](https://github.com/cockatrice/cockatrice)'s XML card database format for Magic: The Gathering
Cockatrice pulls its copy of the token file from here: [**tokens.xml**](https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml)
For normal tokens, save the file found here: [**tokens.xml**](https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml)
For tokens from the Theros block challege decks, save the file found here: [**challenge_tokens.xml**](https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/challenge_tokens.xml)

707
challenge_tokens.xml Normal file
View File

@ -0,0 +1,707 @@
<?xml version="1.0" encoding="UTF-8"?>
<cockatrice_carddatabase version="3">
<sets>
<set>
<name>CTH</name>
<longname>Challenge the Horde</longname>
<settype>Challenge</settype>
<releasedate>2011-09-05</releasedate>
</set>
</sets>
<cards>
<card>
<name>Strike the Weak Spot</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66612-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Destroy target Head. If that Head was elite, the Hydra take an extra turn after this one.</text>
</card>
<card>
<name>Mogis's Chosen</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66703-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Minotaur</type>
<pt>5/4</pt>
<tablerow>2</tablerow>
<text>Mogis's Chosen enters the battlefield tapped. Mogis's Chosen attacks each turn if able.</text>
</card>
<card>
<name>Rip to Pieces</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66809-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>At the beginning of combat this turn, each Reveler deals 1 damage to each player and each creature those players control.</text>
</card>
<card>
<name>Descend on the Prey</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66707-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Whenever a Minotaur attacks this turn, it gains first strike until end of turn and must be blocked this turn if able.</text>
</card>
<card>
<name>The Vanquisher</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66786-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>Your starting hand size is increased by one. Your maximum hand size is increased by one.</text>
</card>
<card>
<name>The General</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66724-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>Exile The General: Creature you control get +1/+1 until end of turn. Untap them.</text>
</card>
<card>
<name>Minotaur Younghorn</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66702-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Minotaur</type>
<pt>2/2</pt>
<tablerow>2</tablerow>
<text>Haste, Minotaur Younghorn attacks each turn if able.</text>
</card>
<card>
<name>Dance of Flame</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66812-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Enchantment</type>
<tablerow>1</tablerow>
<text>Whenever a Reveler attacks, Dance of Flame deals 1 damage to each player.</text>
</card>
<card>
<name>The Provider</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66764-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>Exile The Provider: Put two +1/+1 counters on target creature you control. You gain life equal to that creature's toughness.</text>
</card>
<card>
<name>Refreshing Elixir</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66714-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Artifact</type>
<tablerow>1</tablerow>
<text>At the beginning of the Horde's precombat main phase, reveal an additional card from the top of the Horde's library. The Horde casts that card.
Hero's Reward — When Refreshing Elixir is put into a graveyard from anywhere, each player gains 5 life.</text>
</card>
<card>
<name>Rollicking Throng</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66803-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Human Reveler</type>
<pt>1/3</pt>
<tablerow>2</tablerow>
<text>When Rollicking Throng enters the battlefield, reveal the top card of Xenagos's library and Xenagos casts that card. (This ability doesn't trigger as the game begins.)</text>
</card>
<card>
<name>Impulsive Destruction</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66807-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Each player may sacrifice an artifact or enchantment. Impulsive Destruction deals 3 damage to each player who didn't sacrifice a permanent this way.</text>
</card>
<card>
<name>Unquenchable Fury</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66710-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Each Minotaur can't be blocked this turn except by two or more creatures.</text>
</card>
<card>
<name>Maddened Oread</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66814-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Enchantment Creature — Nymph Reveler</type>
<pt>4/2</pt>
<tablerow>2</tablerow>
<text>As long as five or more Revelers are on the battlefield, Maddened Oread attacks each turn if able. Hero's Reward - When Maddened Oread leaves the battlefield, each player gains 3 life.</text>
</card>
<card>
<name>Xenagos's Scorn</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66810-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Xenagos Ascended gains trample until end of turn and attacks this turn if able.</text>
</card>
<card>
<name>Shrieking Titan Head</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66604-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Elite Creature — Head</type>
<pt>0/8</pt>
<tablerow>2</tablerow>
<text>At the beginning of the Hydra's end step, each player discards a card.
Hero's Reward — When Savage Vigor Head leaves the battlefield, each player gains 4 life and draws a card.</text>
</card>
<card>
<name>Xenagos's Strike</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66811-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Xenagos's Strike deals 4 damage to each player.</text>
</card>
<card>
<name>Snapping Fang Head</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66605-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Elite Creature — Head</type>
<pt>0/8</pt>
<tablerow>2</tablerow>
<text>At the beginning of the Hydra's end step, Snapping Fang Head deals 1 damage to each player.
Hero's Reward — When Savage Vigor Head leaves the battlefield, each player gains 4 life and draws a card.</text>
</card>
<card>
<name>Massacre Totem</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66712-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Artifact</type>
<tablerow>1</tablerow>
<text>At the beginning of the Horde's precombat main phase, reveal an additional card from the top of the Horde's library. The Horde casts that card. Hero's Reward — When Altar of Mogis is put into a graveyard from anywhere, the Horde sacrifies two Minotaurs.</text>
</card>
<card>
<name>Spear of the General</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66827-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero Artifact — Equipment</type>
<tablerow>1</tablerow>
<text>Equipped creature gets +2/+0 and has first strike. Equip {2}</text>
</card>
<card>
<name>Minotaur Goreseeker</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66701-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Minotaur</type>
<pt>3/2</pt>
<tablerow>2</tablerow>
<text>Haste, Minotaur Goreseeker attacks each turn if able.</text>
</card>
<card>
<name>Swallow the Hero Whole</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66613-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>1</tablerow>
<text>Each player exiles a creature he or she controls. Until the Hydra's next turn, when a Head leaves the battlefield, return the exiled cards to the battlefield under their owners' control.</text>
</card>
<card>
<name>The Philosopher</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66631-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>{2}, {T}: Tap target creature.</text>
</card>
<card>
<name>Disorienting Glower</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66606-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Player's can't cast spells until the Hydra's next turn.</text>
</card>
<card>
<name>The Destined</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66878-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>Spells you cast that target a creature you control cost {2} less to cast</text>
</card>
<card>
<name>Altar of Mogis</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66711-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Artifact</type>
<tablerow>1</tablerow>
<text>At the beginning of the Horde's precombat main phase, reveal an additional card from the top of the Horde's library. The Horde casts that card. Hero's Reward — When Massacre Totem is put into a graveyard from anywhere, put the top seven cards of the Horde's library into its graveyard.</text>
</card>
<card>
<name>The Warrior</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66651-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>{T}: Target creature you control gains haste until end of turn.</text>
</card>
<card>
<name>Touch of the Horned God</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66709-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Whenever a Minotaur attacks this turn, it gains deathtouch until end of turn.</text>
</card>
<card>
<name>Ravenous Brute Head</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66602-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Elite Creature — Head</type>
<pt>0/6</pt>
<tablerow>2</tablerow>
<text>Hero's Reward — When Ravenous Brute Head leaves the battlefield, each player gains 2 life and draws a card.</text>
</card>
<card>
<name>The Savant</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66734-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>Exile The Savant: Tap all creatures your opponent control. Those creatures don't untap during their controllers' next untap steps.</text>
</card>
<card>
<name>Dance of Panic</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66813-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Enchantment</type>
<tablerow>1</tablerow>
<text>As long as five or more Revelers are on the battlefield, all Revelers have haste and attack each turn if able.</text>
</card>
<card>
<name>Savage Vigor Head</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66603-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Elite Creature — Head</type>
<pt>0/8</pt>
<tablerow>2</tablerow>
<text>At the beginning of the Hydra's end step, reveal the top card of the Hydra's library and the Hydra casts that card. Hero's Reward — When Savage Vigor Head leaves the battlefield, each player gains 4 life and draws a card.</text>
</card>
<card>
<name>Intervention of Keranos</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66708-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>At the beginning of combat this turn, Intervention of Keranos deals 3 damage to each creature.</text>
</card>
<card>
<name>Distract the Hydra</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66607-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Each player may sacrifice a creature. Each player who sacrificed a creature this way chooses a Head and taps it. Each player who didn't sacrifice a creature loses 3 life.</text>
</card>
<card>
<name>The Slayer</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66683-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>You start the game with an additional 7 life.</text>
</card>
<card>
<name>Impulsive Return</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66808-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Return two cards named Ecstatic Piper from Xenagos's graveyard to the battlefield. At the beginning of combat this turn, Impulsive Return deals damage to each player equal to the number of Revelers on the battlefield.</text>
</card>
<card>
<name>Phoberos Reaver</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66704-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Minotaur</type>
<pt>2/3</pt>
<tablerow>0</tablerow>
<text>Haste, Phoberos Reaver attacks each turn if able.</text>
</card>
<card>
<name>Bow of the Hunter</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66867-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero Artifact — Equipment</type>
<tablerow>1</tablerow>
<text>Equipped creature has &quot;{T}: This creature deals 2 damage to target creature of player.&quot; Equip {2}</text>
</card>
<card>
<name>Torn Between Heads</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66614-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Tap up to two Heads. They don't untap during the Hydra's next untap step. Torn Between Heads deals 5 damage to each player.</text>
</card>
<card>
<name>Noxious Hydra Breath</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66611-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Choose one — Noxious Hydra Breath deals 5 damage to each player; or destroy each tapped non-Head creature.</text>
</card>
<card>
<name>Vitality Salve</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66715-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Artifact</type>
<tablerow>1</tablerow>
<text>At the beginning of the Horde's precombat main phase, reveal an additional card from the top of the Horde's library. The Horde casts that card. Hero's Reward — When Vitality Salve is put into a graveyard from anywhere, each player returns a creature card from his or her graveyard to the battlefield.</text>
</card>
<card>
<name>Lash of the Tyrant</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66847-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero Artifact — Equipment</type>
<tablerow>1</tablerow>
<text>Equipped creature gets +1/+2 and has deathtouch. Equip {2}</text>
</card>
<card>
<name>Reckless Minotaur</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66705-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Minotaur</type>
<pt>4/1</pt>
<tablerow>2</tablerow>
<text>Haste, Reckless Minotaur attacks each turn if able. At the beginning of the end step, destroy Reckless Minotaur.</text>
</card>
<card>
<name>Hydra's Impenetrable Hide</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66609-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Each Head gains indestructible until the end of the Hydra's next turn.</text>
</card>
<card>
<name>Axe of the Warmonger</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66857-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero Artifact — Equipment</type>
<tablerow>1</tablerow>
<text>Equipped creature gets +2/+1 and has haste. Equip {2}</text>
</card>
<card>
<name>The Harvester</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66672-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>{T}: Draw a card, then discard a card.</text>
</card>
<card>
<name>Hydra Head</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66601-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Head</type>
<pt>0/3</pt>
<tablerow>2</tablerow>
<text>Hero's Reward — When Hydra Head leaves the battlefield, each player gains 2 life.</text>
</card>
<card>
<name>The Explorer</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66775-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>You may play an additional land on each of your turns.</text>
</card>
<card>
<name>Grown from the Stump</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66608-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Put exactly two cards named Hydra Head onto the battlefield from the Hydra's graveyard. If you can't, reveal cards from the top of the Hydra's library until you reveal a Head card. Put that card onto the battlefield and the rest into the Hydra's graveyard.</text>
</card>
<card>
<name>The Warmonger</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66754-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>Exile The Warmonger: Creature you control get +2/+0 and gain haste until end of turn.</text>
</card>
<card>
<name>Unified Lunge</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66615-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Unified Lunge deals X damage to each player, where X is the number of Heads on the battlefield.</text>
</card>
<card>
<name>The Tyrant</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66744-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>0</tablerow>
<text>Exile The Tyrant: Creature your opponents control get -1/-1 until end of turn.</text>
</card>
<card>
<name>The Champion</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66889-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>{2}, {T}, Exile The Champion: Search your library for a legendary artifact card that isn't a creature, reveal it, and put it onto the battlefield. Then shuffle your library.</text>
</card>
<card>
<name>Plundered Statue</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66713-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Artifact</type>
<tablerow>1</tablerow>
<text>At the beginning of the Horde's precombat main phase, reveal an additional card from the top of the Horde's library. The Horde casts that card. Hero's Reward — When Plundered Statue is put into a graveyard from anywhere, each player draws a card.</text>
</card>
<card>
<name>The Protector</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66621-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>{T}: Prevent the next 1 damage that would be dealt to target creature or player this turn.</text>
</card>
<card>
<name>Impulsive Charge</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66806-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>At the beginning of combat this turn, all Revelers gain haste until end of turn and attack this combat if able.</text>
</card>
<card>
<name>Pheres-Band Revelers</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66802-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Centaur Reveler</type>
<pt>4/4</pt>
<tablerow>2</tablerow>
<text>Hero's Reward - When Pheres-Band Revelers leaves the battlefield, each player draws a card.</text>
</card>
<card>
<name>The Hunter</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66661-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>{T}: Target creature you control gets +1/+1 until end of turn.</text>
</card>
<card>
<name>Ecstatic Piper</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66801-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Satyr Reveler</type>
<pt>2/2</pt>
<tablerow>2</tablerow>
<text>When Ecstatic Piper enters the battlefield, Xenagos Ascended attacks this turn if able Hero's Reward - When Ecstatic Piper leaves the battlefield, each player gains 2 life.</text>
</card>
<card>
<name>Consuming Rage</name>
<set picURL="http://deckmaster.info/images/cards/BNG/-66706-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>Whenever a Minotaur attacks this turn, it gets +2/+0 until end of turn. Destroy that creature at end of combat.</text>
</card>
<card>
<name>Xenagos Ascended</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66815-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Legendary Enchantment Creature — God</type>
<pt>6/5</pt>
<tablerow>2</tablerow>
<text>Xenagos Ascended can't leave the battlefield as long as a Reveler is on the battlefield. When Xenagos Ascended leaves the battlefield, each player wins the game.</text>
</card>
<card>
<name>Wild Maenads</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66805-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Human Reveler</type>
<pt>3/1</pt>
<tablerow>2</tablerow>
<text>First strike, Hero's Reward - When Wild Maenads leaves the battlefield, each player gains 3 life.</text>
</card>
<card>
<name>Serpent Dancers</name>
<set picURL="http://deckmaster.info/images/cards/-66804-hr.jpg">TK</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Creature — Human Reveler</type>
<pt>2/4</pt>
<tablerow>2</tablerow>
<text>Deathtouch, Hero's Reward - When Serpent Dancers leaves the battlefield, each player draws a card.</text>
</card>
<card>
<name>Neck Tangle</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66610-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Sorcery</type>
<tablerow>3</tablerow>
<text>If there are five or more Heads on the battlefield, tap two of them and they don't untap during the Hydra's next untap step. Otherwise, reveal the top card of the Hydra's library and the Hydra casts that card.</text>
</card>
<card>
<name>The Avenger</name>
<set picURL="http://deckmaster.info/images/cards/THS/-66641-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero</type>
<tablerow>1</tablerow>
<text>{3}, {T}: Target creature you control gains deathtouch until end of turn.</text>
</card>
<card>
<name>Cloak of the Philosopher</name>
<set picURL="http://deckmaster.info/images/cards/JOU/-66837-hr.jpg">CTH</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Hero Artifact — Equipment</type>
<tablerow>1</tablerow>
<text>Equipped creature gets +1/+1. {2}: Untap equipped creature. Equip {2}</text>
</card>
<card>
<name>Garruk the Slayer</name>
<set picURL="http://s3.gatheringmagic.com.s3.amazonaws.com/uploads/2014/06/23/garruk-the-slayer.png">M15</set>
<color></color>
<manacost></manacost>
<cmc>0</cmc>
<type>Planeswalker - Garruk</type>
<tablerow>2</tablerow>
<text>0: Put a 2/2 green Wolf creature token onto the battlefield.
+4: Target Wolf creature gets +1/+0 and gains deathtouch until end of turn.
-10: Destroy target creature. Put loyalty counters on Garruk the Slayer equal to that creature's toughness.
-25: Destroy all creatures Garruk the Slayer doesn't control.</text>
<loyalty>20</loyalty>
</card>
</cards>
</cockatrice_carddatabase>

View File

@ -1,13 +0,0 @@
## Scripts
[`update_image_links`](https://github.com/Cockatrice/Magic-Token/blob/master/scripts/update_image_links.py)
This script changes the links from the old CDN on c1.scryfall.com to the new CDN on cards.scryfall.io.
It reads a tokens.xml file containing CDN picURLs of either version, makes requests to the Scryfall API
to receive the most up-to-date URLs for each token, and generates an output file.
`--inplace / -i` will update the input file
`--output / -o` will create a new file of your choice
Basic call to run the script:
```
python3 update_image_links.py tokens.xml -i
```

View File

@ -1,185 +0,0 @@
"""
This script parses a token.xml file, collects the picURLs of cards within, and replaces
the links to Scryfall images with up-to-date URLs by querying Scryfall's API.
"""
from xml.sax import saxutils, make_parser, handler
from urllib.parse import urlsplit
from urllib.request import Request, urlopen
import itertools
import json
import sys
import time
import os
import tempfile
import pathlib
import shutil
SCRYFALL_MAX_LIST_SIZE = 75
def cards_collection(identifiers):
"""
Get information about a set of cards using the Scryfall API.
This simply returns a list of dictionaries representing the Card objects as
returned by the /card/collection Scryfall API.
If the list of identifiers is larger than Scryfall's API limit,
cards_collection automatically splits the list into smaller chunks and
makes multiple requests.
"""
start_time = 0
n = 0
while n < len(identifiers):
chunk = identifiers[n:n + SCRYFALL_MAX_LIST_SIZE]
print("Requesting chunk {}-{}/{}...".format(n, n + len(chunk), len(identifiers)))
n += SCRYFALL_MAX_LIST_SIZE
payload = json.dumps({'identifiers': chunk}).encode('utf-8')
req = Request('https://api.scryfall.com/cards/collection', payload,
headers={'Content-Type': 'application/json'})
# Rate limiting
cur_time = time.time()
delta_time = cur_time - start_time
if delta_time < 0.1:
time.sleep(0.1 - delta_time)
start_time = time.time()
with urlopen(req) as f:
list_obj = json.load(f)
assert not list_obj.get('has_more', False)
assert 'warnings' not in list_obj
yield from list_obj['data']
def parse_picurl(picurl):
"""
Parse a Scryfall picURL into its components.
The Scryfall picURL must be in one of those forms:
- https://c1.scryfall.com/file/scryfall-cards/<version>/<face>/*/*/<uuid>.jpg
- https://cards.scryfall.io/<version>/<face>/*/*/<uuid>.jpg
If it is, a dictionary with keys 'uuid', 'version' and 'face' is returned.
Otherwise, an empty dictionary is returned.
"""
obj = {}
urlinfo = urlsplit(picurl)
if urlinfo.netloc == 'c1.scryfall.com':
parts = urlinfo.path.split('/')
obj['version'] = parts[3]
obj['face'] = parts[4]
obj['uuid'] = parts[-1].rsplit('.')[0]
elif urlinfo.netloc == 'cards.scryfall.io':
parts = urlinfo.path.split('/')
obj['version'] = parts[1]
obj['face'] = parts[2]
obj['uuid'] = parts[-1].rsplit('.')[0]
return obj
class URLCollector(handler.ContentHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.urls = []
def startElement(self, name, attrs):
if name == 'set' and 'picURL' in attrs:
obj = parse_picurl(attrs['picURL'])
if obj:
assert 'uuid' in obj
self.urls.append(obj)
class URLRewriter(handler.LexicalHandler, saxutils.XMLGenerator):
def __init__(self, images, **kwargs):
super().__init__(**kwargs)
self._images = images
self.started = False
def startElement(self, name, attrs):
self.started = True
if name == 'set' and 'picURL' in attrs:
obj = parse_picurl(attrs['picURL'])
if 'uuid' in obj and obj['uuid'] in self._images:
new_url = self._images[obj['uuid']][obj['face']][obj['version']]
if parse_picurl(new_url) != obj:
raise RuntimeError(
"URL `{}` was rewritten to `{}` that no longer resolves to the same card (hint: update parse_picurl).".format(
attrs['picURL'], new_url))
attrs = dict(attrs, picURL=new_url)
super().startElement(name, attrs)
def endDocument(self):
super().endDocument()
self._write('\n')
def comment(self, content):
self.ignorableWhitespace('<!--{}-->{}'.format(content, '' if self.started else '\n'))
def collect_urls(fname):
parser = make_parser()
uc = URLCollector()
parser.setContentHandler(uc)
parser.parse(fname)
return uc.urls
def rewrite_urls(fname, images, *, out=None):
parser = make_parser()
ur = URLRewriter(images, out=out, encoding='UTF-8')
parser.setContentHandler(ur)
parser.setProperty(handler.property_lexical_handler, ur)
parser.parse(fname)
def main(fname, *, out=None):
urls = collect_urls(fname)
identifiers = {obj['uuid'] for obj in urls}
identifiers = [{'id': uuid} for uuid in identifiers]
images = {}
for card in cards_collection(identifiers):
assert card['id'] not in images
if 'image_uris' in card:
images[card['id']] = {'front': card['image_uris']}
else:
assert 'card_faces' in card and len(card['card_faces']) == 2
images[card['id']] = {
'front': card['card_faces'][0]['image_uris'],
'back': card['card_faces'][1]['image_uris'],
}
rewrite_urls(fname, images, out=out)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(
description='Helper script to refresh scryfall image URLs')
parser.add_argument('filename', nargs='?', default='tokens.xml')
output_group = parser.add_mutually_exclusive_group(required=True)
output_group.add_argument('--output', '-o')
output_group.add_argument('--inplace', '-i', action='store_true')
ns = parser.parse_args()
if ns.inplace:
outpath = pathlib.Path(ns.filename)
else:
outpath = pathlib.Path(ns.output)
fd, temppath = tempfile.mkstemp(dir=outpath.parent)
try:
main(ns.filename, out=os.fdopen(fd, mode='w+b'))
os.replace(temppath, outpath)
except:
os.unlink(temppath)
raise

14464
tokens.xml

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
20260307
20210713