yugipedia-gql
v0.5.0
Published
A GraphQL wrapper around the MediaWiki API for Yugipedia. Designed to make the queries and results more intuitive and simple.
Downloads
96
Maintainers
Readme
Yugipedia GQL Wrapper
A GraphQL wrapper around the MediaWiki API for Yugipedia. Designed to make the queries and results more intuitive and simple.
⚠️ This wrapper is in its early stages. As new requirements are set and the full design/structure is realized, be prepared for drastic, breaking changes on a regular basis. See the change log for info on recent updates.
⚠️ The codebase on github isn't always synced up with the version hosted on NPM. It is likely the github version of the code is ahead in features and bug fixes, but might also be less stable. Clone it with optimistic caution for the latest updates.
Installation
You'll need to have NodeJS installed on your computer. It's best to have the most up-to-date LTS version installed. Installing NodeJS should also install NPM as a command line interface by default.
You can verify both NodeJS and NPM are installed properly by running the following command in your terminal/emulator of choice:
node -v
npm -v
Finally, cd into (open) your directory of choice through your terminal/emulator and create a new npm project:
cd "REPLACE\\WITH\\YOUR\\PATH"
touch index.js
npm init -y
npm pkg set type="module"
npm i yugipedia-gql
API
class Yugipedia
Creates a Yugipedia API entity capable of performing basic operations.
new Yugipedia(options: {
userAgent: {
name: string,
contact: string,
reason?: string
},
hydratePrototype?: boolean,
cache?: {
path?: string,
ttl?: {
years?: number,
months?: number,
days?: number,
hours?: number,
minutes?: number,
seconds?: number,
}
}
}): Yugipedia
|Argument Name|Type|Optional|Default Value|Description|
|-|-|-|-|-|
|options.userAgent.name
|string|no||The best thing to refer to you as|
|options.userAgent.contact
|string|no||The contact details to get a hold of you in case the devs have a question or need to reach out|
|[options.userAgent.reason]
|string|yes|"Data Collection for Personal Use [Yugipedia-GQL]"
|The reason you're using the API|
|[options.hydratePrototype]
|boolean|yes|true
|The returned data's prototype is rehydrated as the GraphQL library nullifies it. This is mostly aesthetic, so if it causes issues, set this to false|
|[options.cache]
|object|yes|See below|The cache settings object. Set to false (not recommended) if you don't want caching|
|[options.cache.path]
|string|yes|"{cwd}/yugipedia-gql-cache"
|The path to the cache file
|[options.cache.ttl]
|object|yes|{ days: 30 }
|The amount of time after data has been retrieved before it should expire
Yugipedia.prototype.query
Yugipedia.prototype.query(
gqlQueryString: string,
variables?: {[key]: unknown}
): {
data: {[key]: unknown},
errors: null | [{
culprit: string,
code: number,
log: {
message: string,
payload: unknown
}
}],
warnings: null | [{
culprit: string,
code: number,
log: {
message: string,
payload: unknown
}
}]
}
|Argument Name|Type|Optional|Default Value|Description|
|-|-|-|-|-|
|gqlQueryString
|string|no||The GraphQL query string|
|variables
|object|yes|{}
|The variables to use with the query|
Usage
A general understanding of the GraphQL language is highly recommended before attempting to use this API wrapper.
💡 There is no need to roll your own rate limiter as all queries are rate limited to one page per second maximum to align with the wishes of the API devs. (See: Request limit).
Example:
import Yugipedia from "yugipedia-gql"
const api = new Yugipedia({
userAgent: {
name: "John Smith", // replace with your name
contact: "[email protected]", // replace with your contact method
reason: "Testing the GraphQL wrapper for Yugipedia (https://www.npmjs.com/package/yugipedia-gql)"
}
})
const queryString = `#graphql
query {
card(searchTerm: "Dark Magician") {
name {
english
korean {
html
}
}
stats {
attribute
attack
defense
}
types
}
}
`
const result = await api.query(queryString)
console.dir(result, { depth: null })
The above result should be:
{
data: {
query: {
card: {
name: { english: 'Dark Magician', korean: { html: '블랙 매지션' } },
stats: { attribute: 'Dark', attack: '2500', defense: '2100' },
types: [ 'Spellcaster', 'Normal' ]
}
}
},
errors: null,
warnings: null
}
Explanation
Currently, there are only two root queries
card(searchTerm: String!): Card
set(searchTerm: String!): Set
The searchTerm
for each root query can be anything you think will match a page of that type on Yugipedia. So, that means if Yugipedia recognizes it and can redirect to it, you're probably in business. For example, let's say we wanted info on the Legend of Blue Eyes White Dragon set. We could use the set
root query with any of the following searchTerm
's:
"LOB"
"LDD"
"LOB-EN"
"Legend of Blue Eyes White Dragon"
All of these should end up querying the correct set we were looking for. And for the Blue-Eyes White Dragon card, for example, we could query the card
root query with the following searchTerm
's and we could expect accurate results:
"BEWD"
"Blue-Eyes White Dragon"
"Blue Eyes White Dragon"
"LOB-EN001"
"89631139"
(the card's password)"...etc."
The wrapper will do its best to take the searchTerm
you provide and resolve it to the page it thinks you want, but be assured it will not make any assumptions should it find ambiguous results. It strictly relies on the underlying API's ability to resolve page name redirects, but will make some case and symbol adjustments to help find things should there be a slight discrepancy. More on how redirects are handled can be found in this section.
The basic structure of a query result will have the following shape:
{
data: {
[key: queryTitle]: {
[key: rootQueryName]: {
...unknown // whatever your requested data shape looks like
}
}
},
errors: null | [{
culprit: string,
code: number,
log: {
message: string,
payload?: unknown
}
}],
warnings: null | [{
culprit: string,
code: number,
log: {
message: string,
payload?: unknown
}
}], // same signature as the errors key
}
Stay tuned, more details and explanations to come...
Redirects
This wrapper will make an attempt to resolve redirects as best as possible. It does so by coercing lowercase, uppercase, propercase, titlecase, and sentencecase variations of your provided searchTerms
s and querying them all against the API efficiently. To test this, you can try a set
query with each of the following set names for the Legend of Blue Eyes White Dragon set; they should all succeed.
"lob"
"Lob"
"LOB"
"Legend of Blue Eyes White Dragon"
"legend of blue eyes white dragon"
"legend_of_blue_eyes_white_dragon"
"leGEND Of Blue eyeS whitE DRaGON"
It's not infallible, unfortunately. Currently it can't handle spelling mistakes. Perhaps I add some AI to it in the future 😁🤖
Caching
Data caching is handled for you. Every individual property you look up will be saved, and subsequent queries asking for that data will search the cache first before querying the API. There is a very important caveat with the way caching is handled that you should take into account. The code controlling the API requests is designed to batch all requests. As such, all of the data of the request must exist in the cache already if the cache is to be used at all, otherwise it will be ignored and refreshed. Let's visualize this:
Example:
query {
card(searchTerm: "Dark Magician") {
name {
english
}
stats {
attack
}
}
}
Given the query above, I'd be getting the English name and attack properties. Now, let's say I realize I also want to get the defense stat:
query {
card(searchTerm: "Dark Magician") {
name {
english
}
stats {
attack
defense
}
}
}
Because the defense stat doesn't exist in the cache based on our previous request the code will discard the cached data and request all of the properties from scratch.
The reasoning behind this behavior lies in the program trying to be as efficient as possible. While it may sound counter-intuitive to discard pre-existing data in the name of efficiency, not doing so would actually trigger a potential cascade of granular requests trying to account for different combinations of missing data, thus increasing the number of requests to the API and your wait time. By instead performing a fresh request, we're able to batch everything into a single request (sorta, this API gets weird).
So, why is this useful to you? For two reasons. The first being that if you understand how the caching works, you'll be more likely to understand why sometimes your queries seem near-instantaneous and why sometimes they'll be delayed by a couple seconds. The other is if you want to tinker with the code yourself. This isn't thoroughly documented in the codebase, and I don't plan to either, so laying it out here gives you what you need.
Errors/Warnings
The errors and warnings keys you'll receive are nullable, meaning they will be null
if nothing populated them. If something exists, they will be an array of errors or warnings respectively.
Basic Categories:
3xx
- These are reserved for warnings4xx
- These are reserved for errors originating from data collection, such as scraping and/or API errors5xx
- These are reserved for internal errors, specifically GQL query syntax errors
Breakdown - 3xx:
300 <Missing Data>
- Describes data that is missing when it was expected by a parser or formatter.301 <Missing/Corrupted Data>
- Describes data that is missing or corrupted as a resource on the fetched endpoint. (to be clear, it's data that should exist, but for some reason doesn't)
Breakdown - 4xx:
400 <Bad Request>
- The data that was requested doesn't match the data that was found. For instance, if a name you provided for thecard
query matches a set instead, etc.402 <Scrape Failed>
- A scraping request failed. Most likely this will be caused by Yugipedia not being in good health for one reason or another.403 <API Error>
- The underlying SMW/MW API produced an error.404 <Data Not Found>
- The data you requested doesn't exist as a resource on the providing server. This will most likely occur due to spelling errors.
Breakdown - 5xx:
500 <Internal Error>
- These errors are produced by the underlying GraphQL interpreter, usually denoting syntax errors or issues. If you find this error causes consistent, repeatable issues for you, please create an issue and I'll take a look at it.501 <Unknown Error>
- The error that was raised is hasn't been classified or foreseen. These will be random, uncaught and untested errors that are thrown by anyone from anywhere down the chain. These shouldn't be common, but still might occur.
FatalError:
You may sometimes be presented with a FatalError. These errors are internal to the scraping and data collection code and are 9.9x out of 10 caused by my missing a bug, or even potentially a breaking change to a website or API the code is using. If you see this kind of error, please create an issue immediately as it's likely not a fluke.
Schema
Below are some helpful descriptions of various fields found on root types. The entire schema definition can be found here. More detailed descriptions can be found here.
card(searchTerm: String!): Card <RootQuery>
Card.actions <Actions>
- specific actions this card takesCard.anti <AntiOrPro>
- cards that are targeted by this cardCard.appearsIn <[String!]>
- titles of media in which this card has appearedCard.cardType <String>
- this card's type (monster, spell, trap, etc.)Card.charactersDepicted <[String!]>
- what characters are seen in the art of this cardCard.debutDate <DebutDate>
- the dates this card debuted for specific formatsCard.deckType <String>
- which deck type this card belongs to (main, side, etc.)Card.description <CardText>
- 'lore' or description box text of this cardCard.effectTypes <[String!]>
- what types of effects this card performsCard.image <CardImage>
- details on images, including names and linksCard.isReal <Boolean>
- denotes if the card exists in the physical ocg/tcgCard.konamiID <String>
- the database ID Konami uses for this cardCard.limitation <String>
- limitation text provided by this cardCard.materials <Materials>
- materials required or used for this card in its lifetimeCard.mediums <[String!]>
- the formats in which this card exists (ogc, tcg, games, etc.) (different with releases in that this is more general)Card.mentions <[Card!]>
- the cards mentioned by this cardCard.miscTags <[String!]>
- tags/search properties that don't have their own specific categoryCard.name <CardText>
- the name of this cardCard.page <WikiPage>
- meta details on the wiki page for this cardCard.password <String>
- the password of this cardCard.pendulum <Pendulum>
- pendulum details on this cardCard.print <PrintDetails>
- print details on the card, such as notes and type (new, reprint, etc.) (only available when queried through a set)Card.pro <AntiOrPro>
- cards that are supported by this cardCard.rarity <String>
- the rarity of this card (only available when queried through a set)Card.related <Related>
- page names representing pages that are related to this cardCard.releases <[String!]>
- the specific release titles this card is associated with (different with mediums in that this is more specific)Card.setCategory <String>
- the category of this card in relation to its set (Variant card, Booster pack, etc.) (only available when queried through a set)Card.setCode <String>
- the set code of this card (only available when queried through a set)Card.stats <Stats>
- stats on this card, such as attack, defense, level, etc.Card.status <Status>
- the status given a card in official formats (limited, forbidden, etc.)Card.summonedBy <[Card!]>
- the cards that summon this card, typically used on token cardsCard.types <[String!]>
- the types (warrior/effect/etc.) or spell/trap properties (continuous/equip/etc.) this card hasCard.usedBy <[String!]>
- characters and their decks in which this card were used
💡 The
card
API is mainly focused on physical cards. It shouldn't throw an error (untested) when retrieving non-physical cards, such as video game cards, anime cards, etc., but the properties specific to those pages have been neglected for now. There is currently no plan to implement these properties as it would require many, many hours to scrape the"All cards"
category members' properties to compile a complete and accurate list.
set(searchTerm: String!): Set <RootQuery>
Set.cards <CardList>
- the cards that are part of this set's setlist (previous versions of this field were quite slow but have now been heavily optimized. what took 2.5 minutes before for a query to LOB now takes less than 20 seconds. have fun!)Set.code <ProductCode>
- the product codes for this set (ISBN, etc.)Set.coverCards <[Card!]>
- the cards that appear on the packaging for this setSet.format <String>
- the format in regards to forbidden and limited lists (not common on sets, but does exist here and there)Set.image <String>
- the main image used in the wiki for this setSet.konamiID <SetKonamiDatabaseID>
- the database ID used by Konami for this setSet.mediums <[String!]>
- the formats in which this set exists (ogc, tcg, games, etc.)Set.name <LocaleText>
- the name of this setSet.page <WikiPage>
- meta details on the wiki page for this setSet.parent <Set>
- the parent set to this setSet.prefix <Prefix>
- the prefixes for this setSet.promotionalSeries <String>
- the promotional series this set belongs to (core boosters, etc.)Set.regionalPrefix <Prefix>
- the region-specific prefixes for this setSet.releaseDate <SetReleaseDate>
- this set's release dateSet.type <String>
- the type of set this set is (booster, tin, etc.)