Documentation
¶
Overview ¶
Package cardmarket reads Cardmarket's marketplace: the signed API that answers for products, expansions and listings, and the catalog files the site publishes for anyone to download. The entities are named as https://apiv2.cardmarket.com/ws/documentation names them, so a field can be looked up in one place rather than translated first.
Index ¶
- Constants
- func BuildURL(idProduct, idGame int, affiliate string, foil bool) string
- func DefaultArticleFilter(onlyEnglish bool) map[string]string
- func GameFromName(name string) int
- func GameName(idGame int) string
- func ProductVersion(product *Product) int
- func SearchURL(name string, idGame int, affiliate string) string
- type APIError
- type Article
- type ArticlePrice
- type ArticleSeller
- type Catalog
- type CatalogData
- type CatalogExpansion
- type CatalogMeta
- type CatalogProduct
- type Client
- func (mkm *Client) Articles(ctx context.Context, id int, options map[string]string, page, maxResults int) ([]Article, error)
- func (mkm *Client) ExpansionSingles(ctx context.Context, id int) ([]Product, error)
- func (mkm *Client) Expansions(ctx context.Context, gameID int) ([]Expansion, error)
- func (mkm *Client) Product(ctx context.Context, id int) (*Product, error)
- func (mkm *Client) RequestNo() int
- type Expansion
- type Link
- type Localization
- type PriceGuide
- type Product
- type ProductList
Constants ¶
const ( GameMagic = iota + 1 GameWorldOfWarcraft GameYuGiOh GameTheSpoils GamePokemon GameForceOfWill GameCardfightVanguard GameFinalFantasy GameWeissSchwarz GameDragoborne GameMyLittlePony GameDragonBallSuper GameStarWarsDestiny GameFleshAndBlood GameDigimon GameOnePiece GameLorcana GameBattleSpiritsSaga GameStarWarsUnlimited GameRiftbound )
The games Cardmarket carries, as their API numbers them.
const (
// MaxEntities is how many results one request may ask for
MaxEntities = 100
)
Variables ¶
This section is empty.
Functions ¶
func BuildURL ¶
BuildURL builds the storefront link for a product, carrying an affiliate tag when one is given.
func DefaultArticleFilter ¶
DefaultArticleFilter is the filter a price should be read through: played or better, from a seller with a record, and neither signed nor altered. Anything looser prices a card off a listing nobody would buy.
func GameFromName ¶
GameFromName is the inverse, matching case-insensitively so a caller can hand over the name it already knows a game by ("lorcana") instead of translating to an id first; an unnamed game is Magic. Unknown games answer 0, which the URL builders reject: a game Cardmarket does not carry yields no link at all rather than one pointing at a path it does not serve.
func GameName ¶
GameName returns the game as Cardmarket spells it, or "" for a game whose catalog is not covered.
func ProductVersion ¶
ProductVersion reads that index off a product, or zero where it carries none. The marketplace publishes it in the name for some games and only in the address for others - of the catalogs we walk, 31,523 of Yu-Gi-Oh's 86,628 products name it and not one of Pokemon's 72,752 does, though its shelves are full of it - so both are read and the name wins.
Types ¶
type APIError ¶
type APIError struct {
Description string `json:"mkm_error_description"`
InternalCode any `json:"internal_error_code"`
PostDataField any `json:"post_data_field"`
StatusCode int `json:"http_status_code"`
StatusDescription string `json:"http_status_code_description"`
}
APIError is the body Cardmarket answers a rejected request with. The description is the only part meant for a person; the rest says which of the request it objected to.
type Article ¶
type Article struct {
IDArticle int `json:"idArticle"`
IDProduct int `json:"idProduct"`
Language struct {
IDLanguage int `json:"idLanguage"`
LanguageName string `json:"languageName"`
} `json:"language"`
Comments string `json:"comments"`
Price float64 `json:"price"`
IDCurrency int `json:"idCurrency"`
CurrencyCode string `json:"currencyCode"`
Prices []ArticlePrice `json:"prices,omitempty"`
Count int `json:"count"`
InShoppingCart bool `json:"inShoppingCart"`
Condition string `json:"condition"`
Product struct {
Name string `json:"enName"`
LocName string `json:"locName"`
Expansion string `json:"expansion"`
IDExpansion int `json:"idExpansion"`
SetCode string `json:"abbreviation"`
Number string `json:"nr"`
Rarity string `json:"rarity"`
IDGame int `json:"idGame"`
ExpIcon int `json:"expIcon"`
} `json:"product"`
Seller ArticleSeller `json:"seller"`
IsFoil bool `json:"isFoil"`
IsFirstEd bool `json:"isFirstEd"`
IsReverseHolo bool `json:"isReverseHolo"`
IsSigned bool `json:"isSigned"`
IsAltered bool `json:"isAltered"`
Links []Link `json:"links,omitempty"`
}
Article is one seller's listing on a product.
The finish flags are the game's own and not one vocabulary: a Magic listing carries IsFoil and neither of the others, a Pokemon listing carries IsFirstEd and IsReverseHolo and no IsFoil at all. A flag absent from a game's listings decodes false, so read the one its game uses.
type ArticlePrice ¶
type ArticlePrice struct {
Price float64 `json:"price"`
IDCurrency int `json:"idCurrency"`
CurrencyCode string `json:"currencyCode"`
}
ArticlePrice is one listing's price in one currency. A listing is quoted in the seller's currency and in every currency the marketplace converts it to.
type ArticleSeller ¶
type ArticleSeller struct {
IDUser int `json:"idUser"`
Username string `json:"username"`
IsCommercial int `json:"isCommercial"`
IsSeller bool `json:"isSeller"`
Reputation int `json:"reputation"`
SellCount int `json:"sellCount"`
SoldItems int `json:"soldItems"`
OnVacation bool `json:"onVacation"`
// AvgShippingTime is in days, and RiskGroup and LossPercentage are the
// marketplace's own read on whether an order will arrive.
AvgShippingTime int `json:"avgShippingTime"`
RiskGroup int `json:"riskGroup"`
LossPercentage string `json:"lossPercentage"`
Address struct {
Country string `json:"country"`
} `json:"address"`
}
ArticleSeller is who is selling, minus the fields naming the person. The marketplace answers a seller's first name, email, phone, VAT number and legal information beside their trading record; none of that is needed to judge a price, so none of it is decoded and none of it can leak into a log of an article.
type Catalog ¶
type Catalog struct {
Meta CatalogMeta `json:"meta"`
Data CatalogData `json:"data"`
}
Catalog is a published Cardmarket catalog: every singles product mapped to the printings it sells, which is what a price run reads instead of walking the API.
Two programs write this file and they fill different halves of it. MTGJSON publishes Magic's, with the uuids of the printings each product stands for and no rarity, version or expansion code. cmd/mkmcatalog walks the API for every other game, which answers the rarity and the version and the code but knows nothing of any datastore's uuids. A field absent from one producer is absent from every product it writes, not missing from a particular row.
type CatalogData ¶
type CatalogData struct {
Expansions map[int]CatalogExpansion `json:"expansions"`
Products map[int]CatalogProduct `json:"products"`
}
CatalogData is the file's two tables. JSON has only string keys, so the ids are written as strings and read back as the numbers they are, which encoding/json does on its own for an integer-keyed map.
type CatalogExpansion ¶
CatalogExpansion names one expansion of the catalog. Code is the marketplace's own abbreviation, which is where the foreign catalogs say what they are; MTGJSON's file does not carry it.
type CatalogMeta ¶
CatalogMeta says when the file was built and by which version of whatever built it. Both are carried through and neither is enforced: the two producers version themselves differently and a reader that refused an unfamiliar string would refuse a file it can read perfectly well.
type CatalogProduct ¶
type CatalogProduct struct {
ExpansionID int `json:"expansionId"`
Name string `json:"name"`
Number string `json:"number,omitempty"`
Rarity string `json:"rarity,omitempty"`
// Version is the index the marketplace counts a card's printings with
// where one shelf sells several - the second Budew of a stamp
// programme, the master-ball pattern beside the poke-ball one. It is
// the only thing telling two products of one name and number apart, and
// zero where the product carries none. See ProductVersion for where it
// is read from, which is not the same field in every game.
Version int `json:"version,omitempty"`
// UUIDs are the printings the product sells, as MTGJSON links them.
// Only Magic's file carries any.
UUIDs []string `json:"uuids,omitempty"`
}
CatalogProduct is one product of the catalog: what the marketplace calls it, where it files it, and the printings it stands for.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client reads Cardmarket's API, which signs every request with an app token and secret.
func NewClient ¶
NewClient returns a client signing with the given app credentials.
The API is very sensitive to concurrent requests and answers a burst with 429s, so the retry is deliberately patient: up to 20 attempts, each waiting a jittered 2 to 10 seconds multiplied by the attempt number. That is up to half an hour of backoff for one unlucky request, which is the intended behaviour against a rate limiter but is not a bound anyone should rely on - give long walks a context deadline.
func (*Client) Articles ¶
func (mkm *Client) Articles(ctx context.Context, id int, options map[string]string, page, maxResults int) ([]Article, error)
Articles returns the listings on a product, one page at a time. Pages start at zero, and the API requires both bounds: asking for a page size without a start is refused.
func (*Client) ExpansionSingles ¶
ExpansionSingles returns every single card in one expansion.
func (*Client) Expansions ¶
Expansions returns every expansion of one game.
type Expansion ¶
type Expansion struct {
IDExpansion int `json:"idExpansion"`
Name string `json:"enName"`
Localization []Localization `json:"localization,omitempty"`
// SetCode is the marketplace's own abbreviation, which is the only
// field telling a foreign catalog from the English one it shadows.
SetCode string `json:"abbreviation"`
Icon int `json:"icon"`
ReleaseDate string `json:"releaseDate"`
IsReleased bool `json:"isReleased"`
IDGame int `json:"idGame"`
Links []Link `json:"links,omitempty"`
}
Expansion is a set as Cardmarket files it.
type Link ¶
type Link struct {
Rel string `json:"rel"`
Href string `json:"href"`
Method string `json:"method"`
}
Link is one HATEOAS entry, which every entity carries: what the link is for, where it points, and the method that follows it.
type Localization ¶
type Localization struct {
Name string `json:"name"`
IDLanguage int `json:"idLanguage"`
LanguageName string `json:"languageName"`
}
Localization is a name as one language spells it. Every named entity carries the five languages the marketplace trades in.
type PriceGuide ¶
type PriceGuide struct {
IDProduct int `json:"idProduct"`
AvgSellPrice float64 `json:"avg"`
LowPrice float64 `json:"low"`
TrendPrice float64 `json:"trend"`
FoilAvgSellPrice float64 `json:"avg-foil"`
FoilLowPrice float64 `json:"low-foil"`
FoilTrendPrice float64 `json:"trend-foil"`
// Pokemon's guide names the second printing's prices "holo" rather
// than "foil", and publishes them for the cards sold in a reverse
// holo and for no others; see SecondPrinting.
HoloAvgSellPrice float64 `json:"avg-holo"`
HoloLowPrice float64 `json:"low-holo"`
HoloTrendPrice float64 `json:"trend-holo"`
AvgDay1 float64 `json:"avg1"`
AvgDay7 float64 `json:"avg7"`
AvgDay30 float64 `json:"avg30"`
FoilAvgDay1 float64 `json:"avg1-foil"`
FoilAvgDay7 float64 `json:"avg7-foil"`
FoilAvgDay30 float64 `json:"avg30-foil"`
}
PriceGuide is one product's published prices: the low, the trend, and the averages Cardmarket derives rather than any single listing.
func DownloadPriceGuide ¶
func DownloadPriceGuide(ctx context.Context, gameID int) ([]PriceGuide, error)
DownloadPriceGuide downloads the published price guide for one game.
func (PriceGuide) SecondPrinting ¶
func (pg PriceGuide) SecondPrinting(gameID int) (low, trend float64)
SecondPrinting names the prices of the printing sold beside the product's default one, under whichever heading the game's guide publishes them. Most games sell a foil beside a plain card; Pokemon sells a reverse holo, and its guide says so.
type Product ¶
type Product struct {
IDProduct int `json:"idProduct"`
IDMetaproduct int `json:"idMetaproduct"`
// CountReprints is how many products the metaproduct bundles, which is
// how many times the card has been printed across every expansion.
CountReprints int `json:"countReprints"`
Name string `json:"enName"`
LocName string `json:"locName"`
Localization []Localization `json:"localization,omitempty"`
Website string `json:"website"`
// Image is a pre-signed address that expires an hour after the request
// that returned it. It is worth reading and never worth storing.
Image string `json:"image"`
GameName string `json:"gameName"`
CategoryName string `json:"categoryName"`
IDGame int `json:"idGame"`
Number string `json:"number"`
// Rarity is what the marketplace calls the printing's rarity. It is the
// game's own vocabulary where the game has one - Common through Secret
// Rare for Pokemon - and "Promo" for a whole promotional shelf.
Rarity string `json:"rarity"`
// ExpansionName and ExpansionIcon are the ExpansionSingles form.
ExpansionName string `json:"expansionName"`
ExpansionIcon int `json:"expansionIcon"`
// ExpansionCode is the marketplace's own abbreviation of the
// expansion, read off the catalog rather than the product
ExpansionCode string `json:"-"`
// Expansion and PriceGuide are the single-product form.
Expansion struct {
IDExpansion int `json:"idExpansion"`
Name string `json:"enName"`
ExpansionIcon int `json:"expansionIcon"`
} `json:"expansion"`
// PriceGuide is keyed by the marketplace's own headings - SELL, LOW,
// LOWEX, LOWFOIL, AVG, TREND, TRENDFOIL. The documentation spells the
// third LOWEX+; the API sends LOWEX.
PriceGuide map[string]float64 `json:"priceGuide"`
CountArticles int `json:"countArticles"`
CountFoils int `json:"countFoils"`
Links []Link `json:"links,omitempty"`
}
Product is one catalog entry, a card or a sealed item.
Two of its fields depend on which request built it, the marketplace answering the same entity two ways: ExpansionName and ExpansionIcon are filled by ExpansionSingles, and Expansion and PriceGuide by Product. A product read one way carries nothing of the other's.
type ProductList ¶
type ProductList struct {
IDProduct int `json:"idProduct"`
Name string `json:"name"`
CategoryID int `json:"idCategory"`
CategoryName string `json:"categoryName"`
ExpansionID int `json:"idExpansion"`
MetacardID int `json:"idMetacard"`
DateAdded string `json:"dateAdded"`
}
ProductList is one entry of the catalog dump, which names products without pricing them.
func DownloadProductListSealed ¶
func DownloadProductListSealed(ctx context.Context, gameID int) ([]ProductList, error)
DownloadProductListSealed downloads the catalog of one game's sealed product.
func DownloadProductListSingles ¶
func DownloadProductListSingles(ctx context.Context, gameID int) ([]ProductList, error)
DownloadProductListSingles downloads the catalog of one game's singles.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
mkmcatalog
command
Command mkmcatalog walks one game's Cardmarket catalog - every expansion and the products it shelves - and writes it in the shape a price run reads, so the twice-daily runs stop crawling the API.
|
Command mkmcatalog walks one game's Cardmarket catalog - every expansion and the products it shelves - and writes it in the shape a price run reads, so the twice-daily runs stop crawling the API. |
|
mkmpriceguide
command
Command mkmpriceguide downloads Cardmarket's published catalog files for a game: the price guide, or the product list for singles or sealed product.
|
Command mkmpriceguide downloads Cardmarket's published catalog files for a game: the price guide, or the product list for singles or sealed product. |