/scryfall-client

Primary LanguageTypeScriptMIT LicenseMIT

Scryfall Client

A simple (unofficial) wrapper for the Scryfall API.

Installation

npm install --save scryfall-client

The module is a singleton object that can make requests to the Scryfall API.

import scryfall from "scryfall-client";

When using on a node server, you must also set the User Agent (a requirement from the Scryfall API). When used in the browser, no other configuration is required, the browser's user agent will be used instead of your custom one.

scryfall.setUserAgent("MyPackage/1.2.3");

// now you can use the scryfall-client package

Why?

For the most part, this is a wrapper around the Scryfall API that provides convenience methods for the resulting API objects.

The goal is to provide a convenient way to get data from the Scryfall API in a succinct manner. For the most part, each API object will mirror the documented properties on the Scryfall API and include a set of helper methods to make navigating the data easier. If your project uses TypeScript, the response objects will be typed according to Scryfall's documentation.

The one key difference between the response objects returned from the raw API and this module is that for endpoints that return the list object, the raw API returns an object with some properties about the list (has_more, next_page, total_cards) and a data property that is an array of other API objects (cards, prints, rulings, etc). This module returns an Array-like object of the data directly, with the properties attached to the object. Similarly, endpoints that return the catalog object returns an Array-like object containing the data, as well as the other properties on the catalog.

scryfall.search("o:vigilance t:equipment").then(function (list) {
  list.has_more; // whether or not there is an additional page of results, `true` or `false`
  list.total_cards; // the total number of cards returned from search

  var names = list.map(function (card) {
    // the list object can use any Array method
    return card.name;
  });
});

If your request returns no results or is otherwise unsuccessful, the Promise will reject.

scryfall
  .search("foobarbaz")
  .then(function (list) {
    // will never get here
  })
  .catch(function (err) {
    err; // a 404 error
  });

The other responses (Card, MagicSet, etc) are wrappers around the objects and any devitations or additional methods are noted in the Models section.

Basic Usage

search(searchString: string, options?: object) -> Promise<List<Card>>

Perform a Scryfall search, where searchString is the q parameter.

scryfall.search("o:vigilance t:equipment").then(function (list) {
  list.has_more; // whether or not there is an additional page of results, `true` or `false`
  list.total_cards; // the total number of cards returned from search

  var names = list.map(function (card) {
    // the list object can use any Array method
    return card.name;
  });
});

You can pass additional query params as an options object. format and pretty are not supported.

scryfall
  .search("o:vigilance t:equipment", {
    unique: "prints",
    order: "usd",
    dir: "desc",
    include_extras: true,
    include_multilingual: true,
    include_variations: true,
    page: 2,
  })
  .then(function (list) {
    // do something with list of cards
  });

autocomplete(searchString: string, options?: object) -> Promise<Catalog>

Perform a Scryfall autocomplete search, where searchString is the q parameter.

scryfall.autocomplete("Thal").then(function (list) {
  // [
  //   "Thallid",
  //   "Thalakos Seer",
  //   "Thalakos Scout",
  //   "Thalakos Sentry",
  //   "Thalia's Lancers",
  //   "Thallid Devourer",
  //   "Thallid Omnivore",
  //   "Thalakos Drifters",
  //   "Thalakos Mistfolk",
  //   "Thalakos Lowlands",
  //   "Thalakos Deceiver",
  //   "Thallid Soothsayer",
  //   "Thallid Germinator",
  //   "Thalia's Lieutenant",
  //   "Thalia's Geistcaller",
  //   "Thallid Shell-Dweller",
  //   "Thalia, Heretic Cathar",
  //   "Thalakos Dreamsower",
  //   "Thalia, Guardian of Thraben",
  //   "Thorn Thallid"
  // ]
});

You can pass additional query params as an options object. format and pretty are not supported.

scryfall
  .search("o:vigilance t:equipment", {
    unique: "prints",
    order: "usd",
    dir: "desc",
    include_extras: true,
    include_multilingual: true,
    include_variations: true,
    page: 2,
  })
  .then(function (list) {
    // do something with list of cards
  });

getCard(idOrName: string, kind?: string = "scryfall") -> Promise<Card>

You can get a card object through a variety of API routes. By default, just passing an ID will return a card by looking up the Scryfall ID.

Available kind options are:

  • id (alias for scryfall)
  • scryfall
  • multiverse
  • arena
  • mtgo (Magic Online ID)
  • tcg (TCG Player ID)
  • fuzzyName
  • name (alias for fuzzyName)
  • exactName

Find card by Scryfall id:

scryfall.getCard("scryfall-id").then(function (card) {
  // do something with card
});

// can also be expicit about it by providing a kind
scryfall.getCard("scryfall-id", "scryfall").then(function (card) {
  // do something with card
});

Find card by multiverse id:

scryfall.getCard(1234, "multiverse").then(function (card) {
  // do something with card
});

Find card by arena id:

scryfall.getCard(1234, "arena").then(function (card) {
  // do something with card
});

Find card by Magic Online id:

scryfall.getCard(1234, "mtgo").then(function (card) {
  // do something with card
});

Find card by TCG Player id:

scryfall.getCard("id", "tcg").then(function (card) {
  // do something with card
});

Find card by fuzzy name:

scryfall.getCard("fuzzy name", "fuzzyName").then(function (card) {
  // do something with card
});

Alternatively, just use name alias:

scryfall.getCard("fuzzy name", "name").then(function (card) {
  // do something with card
});

Find card by exact name:

scryfall.getCard("exact name", "exactName").then(function (card) {
  // do something with card
});

getCardNamed(name: string, options?: object) -> Promise<Card>

scryfall.getCardNamed("fuzzy name").then(function (card) {
  // do something with card
});

Get exact name:

scryfall.getCardNamed("exact name", { kind: "exact" }).then(function (card) {
  // do something with card
});

Get a card, but specify the printing based on set code:

scryfall.getCardNamed("teferi time", { set: "dom" }).then(function (card) {
  // do something with card
});

random(searchString?: string) -> Promise<Card>

Get a random card:

scryfall.random().then(function (card) {
  // do something with card
});

Get a random card that matches a search string:

scryfall.random("rarity:mythic").then(function (card) {
  // random card that has been printed at mythic
});

getCardBySetCodeAndCollectorNumber(setCode: string, collectorNumber: string, lang?: string) -> Promise<Card>

Find a card by passing the set code and collector number.

scryfall
  .getCardBySetCodeAndCollectorNumber("set code", "123")
  .then(function (card) {
    // do something with card
  });

Optionally pass a language parameter:

scryfall
  .getCardBySetCodeAndCollectorNumber("set code", "123", "es")
  .then(function (card) {
    // do something with card
  });

getCards(page: number = 1) -> Promise<List<Card>>

Fetch all the cards:

scryfall.getCards().then(function (cards) {
  // do something with cards
});

Fetch all the cards starting on a page other than 1:

scryfall.getCards(5).then(function (cards) {
  // do something with cards
});

getSets() -> Promise<List<MagicSet>>

Perform a lookup for all the sets.

scryfall.getSets().then(function (sets) {
  sets.forEach(function (set) {
    // do something with set
  });
});

getSet(setCodeOrScryfallId: string) -> Promise<MagicSet>

Perform a lookup for a particular Magic set by the code or Scryfall ID.

scryfall.getSet("dom").then(function (set) {
  set.name; // "Dominaria"
  set.code; // "dom"
});
scryfall.getSet("2ec77b94-6d47-4891-a480-5d0b4e5c9372").then(function (set) {
  set.name; // "Ultimate Masters"
  set.code; // "uma"
});

getSetByTcgId(tcgId: number) -> Promise<MagicSet>

Perform a lookup for a particular Magic set by the TCG Player ID.

scryfall.getSetByTcgId(1909).then(function (set) {
  set.name; // "Amonkhet Invocations"
  set.code; // "mp2"
});

getCollection(identifiers: object[]) -> Promise<Array<Card>>

Perform a Scryfall collections request, where identifiers is the identifiers parameter.

scryfall
  .getCollection([
    {
      id: "683a5707-cddb-494d-9b41-51b4584ded69",
    },
    {
      name: "Ancient Tomb",
    },
    {
      set: "mrd",
      collector_number: "150",
    },
  ])
  .then(function (collection) {
    collection[0].name; // Lodestone Golem
    collection[1].name; // Ancient Tomb
    collection[2].name; // Chalice of the Void
  });

Normally, the collection API endpoint restricts requests to 75 identifiers. This module will automatically batch the requests in increments of 75 identifiers and then resolve with a flattened array of cards (not a List object).

Advanced Usage

get(url: string, query?: object) -> Promise

If the exact API call you're looking for is missing, you can make a get request directly to any of the API endpoints. It will return a Promise that resolves with the result.

scryfall.get("cards/random").then(function (card) {
  card; // a random card
});

You can pass a second argument that will be converted to a query string:

scryfall
  .get("cards/search", {
    q: "o:vigilance t:equipment",
  })
  .then(function (list) {
    list.forEach(function (card) {
      console.log(card.name);
    });
  });

post(url: string, body?: object) -> Promise

You can also call post with a post body:

scryfall
  .post("cards/collection", {
    identifiers: [
      {
        id: "some-id",
      },
      {
        set: "some-set-code",
        collector_number: "some-collector-number",
      },
    ],
  })
  .then(function (list) {
    list.forEach(function (card) {
      console.log(card.name);
    });
  });

getSymbolUrl

As a convenience, you can call getSymbolUrl with a symbol character to generate the Scryfall url for the symbols svg:

scryfall.getSymbolUrl("W"); // 'https://img.scryfall.com/symbology/W.svg'

scryfall.getSymbolUrl("{U}"); // 'https://img.scryfall.com/symbology/U.svg'

setTextTransformer

A function that yields the text of the fields of the card, whatever the function returns will replace the text.

The main use case is for transforming the mana symbol notation. You can use the getSymbolUrl method in conjunction with the textTransformer function to embed the mana symbols on a web page.

scryfall.setTextTransform(function (text) {
    var matches = text.match(/{(.)(\/(.))?}/g);

    if (matches) {
      matches.forEach(function (symbol) {
        var key = symbol.slice(1, -1);

        text = text.replace(
          symbol,
          '<img src="' + scryfall.getSymbolUrl(key) + '"/>'
        );
      });
    }

    return text;
  },
});

scryfall
  .get("cards/named", {
    exact: "River Hoopoe",
  })
  .then(function (card) {
    // Flying\n<img src="https://img.scryfall.com/symbology/3.svg" /><img src="https://img.scryfall.com/symbology/G.svg" /><img src="https://img.scryfall.com/symbology/U.svg" />: You gain 2 life and draw a card.
    card.oracle_text;
  });

It is probably easiest to copy the function above as is, and replace the second argument in text.replace with your own string where $1 is the first match and $3 is the second match if the mana symbols is a split symbol (such as with hybrid mana).

slackify

If using this module within Slack, you may want the mana symbols converted automatically to the emoji that Scryfall provides.

scryfall.slackify();

scryfall.get("cards/random").then(function (card) {
  card.mana_cost; // ':mana-2::mana-G:
});

discordify

If using this module within Discord, you may want the mana symbols converted automatically to the emoji that Scryfall provides.

scryfall.discordify();

scryfall.get("cards/random").then(function (card) {
  card.mana_cost; // ':mana2::manaG:
});

resetTextTransform

To reset the text transformation to the defaults, use resetTextTransform.

scryfall.resetTextTransform();

setApiRequestDelayTime

By default, there is about 50-100 milliseceond delay between requests (as recomended by Scryfall). You can configure this value.

scryfall.setApiRequestDelayTime(500);

scryfall
  .get("cards/random")
  .then(function (card) {
    // do something with card

    // will wait 500 milliseconds before initiating this request
    return scryfall.get("cards/random");
  })
  .then(function (card) {
    // do something with card
  });

resetApiRequestDelayTime

To reset the delay time back to the default, use resetApiRequestDelayTime.

scryfall.resetApiRequestDelayTime();

Models

As a convenience, there are a number of API objects with special methods.

Card

Representing a card object. Normally, only double faced cards will have a card_faces array, but the Card instance will always include a card_faces array, defaulting to the main attributes from the card if the card does not have multiple faces.

getRulings() -> Promise<List>

Returns a Promise that resolves with a list of rulings objects

scryfall
  .get("cards/named", {
    fuzzy: "aust com",
  })
  .then(function (card) {
    return card.getRulings();
  })
  .then(function (list) {
    list.forEach(function (ruling) {
      console.log(ruling.published_at);
      console.log(ruling.comment);
    });
  });

getSet() -> Promise<MagicSet>

Returns a Promise that resolves with the set object for the card.

scryfall
  .get("cards/named", {
    exact: "the Scarab God",
  })
  .then(function (card) {
    return card.getSet();
  })
  .then(function (set) {
    set.name; // the name of the set
  });

getPrints() -> Promise

Returns a Promise that resolves with a list of card objects for each printing of the card.

scryfall
  .get("cards/named", {
    exact: "windfall",
  })
  .then(function (card) {
    return card.getPrints();
  })
  .then(function (list) {
    var sets = list.map(function (card) {
      return card.set;
    });
  });

isLegal(String format) -> Boolean

Returns true or false for provided format. As of the writing of this documentation, the valid values are:

'standard'
'future'
'frontier'
'modern'
'legacy'
'pauper'
'vintage'
'penny'
'commander'
'1v1'
'duel'
'brawl'

As more formats are added, isLegal will support them automatically (as it takes its list of valid values from the API response itself).

isLegal will return true if Scryfall lists the card as legal or restricted and false otherwise.

scryfall
  .get("cards/search", {
    q: "format:standard r:r",
  })
  .then(function (list) {
    var aCard = list[0];

    aCard.isLegal("standard"); // true
    aCard.isLegal("pauper"); // false
  });

getImage(String size='normal') -> String

Returns the image url of the specified size. Defaults to the normal size.

As of the writing of this documentation, the valid values are:

'small'
'normal'
'large'
'png'
'art_crop'
'border_crop'

If additional formats are added, getImage will support them automatically (as it takes its list of valid values from the API response itself).

scryfall
  .get("cards/named", {
    exact: "windfall",
  })
  .then(function (card) {
    const img = card.getImage();
    img; // set an img tag's src to this
  });

getBackImage(String size='normal') -> String

Returns the image url of the back of the card. In almost all cases, this will return Scryfall's URL for the backside of a card. For transform cards. It will return the image url for the back face of the card.

The default format parameter is 'normal'. As of the writing of this documentation, the valid values are:

'small'
'normal'
'large'
'png'
'art_crop'
'border_crop'

If additional formats are added, getBackImage will support them automatically (as it takes its list of valid values from the API response itself).

If a non-doublesided card is used with getBackImage, the size parameter will be ignored.

// A Magic card without a back face
scryfall
  .get("cards/named", {
    exact: "windfall",
  })
  .then(function (card) {
    const img = card.getBackImage(); // http://cards.scryfall.io/back.png
  });

// A transform card
scryfall
  .get("cards/named", {
    exact: "docent of perfection",
  })
  .then(function (card) {
    const img = card.getBackImage(); // the img url for Final Iteration
  });

getPrice(String type) -> String

Returns a string with the specifed price. If the price is not available for the specified type, '' will be returned.

If no type is specified, it will return the price for 'usd' if available. If 'usd' is not available, 'usd_foil' will be used. If 'usd_foil' is not available, 'eur' will be used. If 'eur' is not available, 'tix' will be used. If 'tix' is not available, '' will be returned.

scryfall
  .get("cards/named", {
    fuzzy: "animar soul",
  })
  .then(function (card) {
    card.getPrice(); // '11.25'
    card.getPrice("usd"); // '11.25'
    card.getPrice("usd_foil"); // '52.51'
  });

getTokens() -> Promise

Returns a Promise that resolves with a list of card objects for each token associated with the card.

scryfall
  .get("cards/named", {
    exact: "Bestial Menace",
  })
  .then(function (card) {
    return card.getTokens();
  })
  .then(function (list) {
    const imgs = list.map(function (card) {
      return card.getImage();
    });

    imgs.forEach(function (img) {
      // display image
    });
  });

getTaggerUrl() -> String (beta)

Returns a url for the tagger page for the card. This is derived from the card attributes based on the structure of the current tagger urls. If the structure changes, this method will no longer point to the correct url.

scryfall
  .get("cards/named", {
    exact: "Krenko, Mob Boss",
  })
  .then(function (card) {
    return card.getTaggerUrl();
  })
  .then(function (url) {
    url; // https://tagger.scryfall.com/card/ddt/52
  });

Catalog

An object representing a catalog object. This is an Array like object where the entries are the data attribute from the raw API. The rest of the properties are present on the Catalog.

List

An object representing a list object. This is an Array like object where the entries are the data attribute from the raw API. The rest of the properties are present on the List.

next() -> Promise<[List](#list>>

If the has_more property is true, then next() can be called to get the next page of results.

function collectCards(list, allCards) {
  allCards = allCards || [];
  allCards.push.apply(allCards, list);

  if (!list.has_more) {
    return allCards;
  }

  return list.next().then(function (newList) {
    return collectCards(newList, allCards);
  });
}

scryfall
  .get("cards/search", {
    q: "format:standard r:r",
  })
  .then(function (list) {
    return collectCards(list);
  })
  .then(function (allRareCardsInStandard) {
    // do something!!
  });

MagicSet

An object represnting a set object.

getCards() -> [List](#list)<[Card](#card)>

Resolves with a list containing all the cards in the set.

scryfall
  .get("sets/dom")
  .then(function (set) {
    return set.getCards();
  })
  .then(function (list) {
    list; // a list of cards for the set
  });

Other Objects

Any other objects are wrapped in a GenericScryfallResponse object.

wrap

The instance includes a wrap method which can be used to wrap a saved respone from Scryall into the API objects listed above. For instance, you may want to convert a Card object into a JSON string to save to your database; this method allows you to rebuild the Card object with all the helper methods without fetching it again from Scryfall.

scryfall.get("cards/random").then(function (card) {
  saveCardToDatabse(card);
});

// later

lookUpCardInDatabase(someId).then(function (cardData) {
  // cardData.getImage does not exist

  const card = scryfall.wrap(cardData);
  const img = card.getImage(); // the image of the card saved in the database
});

Browser Support

The source code is written in Typescript and transpiled to ES5.

The module makes use of the Promise object, so if this SDK is used in a browser that does not support promises, it will need to be polyfilled in your script.

Contributing Guidelines

Code Style

The code base uses Prettier. Run:

npm run pretty

Testing

To lint and run the unit tests, simply run:

npm test

To run just the unit tests, run:

npm run test:unit

To run just the linting command, run:

npm run lint

To run the integration tests, run:

npm run test:integration

Bugs

If you find a bug, feel free to open an issue or a Pull Request.

The same goes for missing Typescript properties. If the Scryfall API adds a new property, open an issue or PR to add it to the module.