Creare un bot Discord per i prezzi Pokémon
Crea un bot con il comando slash /price in circa 50 righe di Python usando l'SDK ufficiale pkmnprices: un client asincrono, carte tipizzate e prezzi di mercato aggiornati in discord.py v2.
Nel tuo server Discord di scambi la domanda è sempre la stessa, tutto il giorno:
«quanto vale questa carta?». In questo tutorial costruiamo un bot che risponde.
Scrivi /price charizard e ricevi il prezzo di mercato aggiornato in un embed
ordinato, preso dall'API di PkmnPrices tramite il suo SDK Python
ufficiale. Sono circa 50 righe di Python e alla fine lo avrai in funzione in locale.
Cosa imparerai
- Lo schema «prima cerca, poi recupera» usato dall'SDK (i risultati dell'elenco non contengono prezzi)
- Come registrare un comando slash con autocompletamento in discord.py v2
- Come l'SDK
pkmnpricesti dà già pronti un client asincrono, modelli tipizzati e i tentativi automatici
Cosa costruirai
Un solo comando slash. Un utente scrive /price <card name> e il bot risponde
con l'espansione della carta, la condizione e l'attuale prezzo di mercato in USD.
Niente database, niente scraping, niente HTTP scritto a mano: solo l'SDK e un
embed di Discord.
Dietro le quinte i passaggi sono due, perché l'API separa la ricerca dai prezzi.
client.cards.list() trova la carta e ne restituisce l'ID. client.cards.get()
restituisce quella carta con i prezzi allegati. Vale la pena dirlo chiaramente,
perché confonde molti: la chiamata di elenco non restituisce mai dati sui
prezzi. Per avere un numero devi sempre recuperare la singola carta. E siccome
un nome come charizard corrisponde a decine di stampe tra espansioni e varianti,
lasceremo all'utente la scelta di quella esatta prima di recuperarne il prezzo.
Prima di iniziare
Ti servono tre cose. Primo, Python 3.10 o successivo. Secondo, un'applicazione Discord con un token del bot dal Discord Developer Portal. Terzo, una chiave API di PkmnPrices, che puoi prendere gratis dalla dashboard.
Tieni i due segreti fuori dal codice. Vanno in un file .env:
DISCORD_TOKEN=your-bot-tokenPKMNPRICES_API_KEY=pk_your-api-key
Prepara il progetto
Crea una cartella, un ambiente virtuale e installa le dipendenze.
L'SDK pkmnprices include il proprio
client HTTP, i tentativi automatici e i modelli tipizzati, quindi non resta
nessuna infrastruttura dell'API da scrivere.
mkdir price-bot && cd price-botpython -m venv .venv && source .venv/bin/activatepip install pkmnprices discord.py python-dotenv
Conosci l'SDK
L'SDK si occupa al posto tuo di HTTP, tentativi automatici e tipi, quindi non c'è
nessuna classe client da scrivere. discord.py è asincrono, e l'SDK include un
client asincrono pensato proprio per questo: AsyncPkmnPrices. Una chiamata
cerca, una recupera.
from pkmnprices import AsyncPkmnPricesasync with AsyncPkmnPrices("pk_your-api-key") as client:page = await client.cards.list(name="charizard", per_page=5) # no pricescard = await client.cards.get(page.data[0].id, currency="usd") # with pricesprint(card.prices[0].market_price) # 285.0
list() restituisce una pagina con .data e .pagination; ogni carta è un
oggetto tipizzato, quindi usi card.name e card.set.name, non chiavi di
dizionario. get() aggiunge la lista prices, in cui ogni voce ha condition,
variant e market_price. Questa separazione è l'unico motivo per cui facciamo
due chiamate.
Costruisci il bot
Costruiremo bot.py in quattro piccoli pezzi: lo scheletro, l'avvio, una
funzione di supporto per l'embed e il comando vero e proprio. Inizia dallo
scheletro: gli import, un bot con gli intent predefiniti e nient'altro per ora.
# bot.pyimport osimport discordfrom discord import app_commandsfrom discord.ext import commandsfrom dotenv import load_dotenvfrom pkmnprices import AsyncPkmnPrices, UnauthorizedError, RateLimitError, CreditLimitErrorload_dotenv()intents = discord.Intents.default()bot = commands.Bot(command_prefix="!", intents=intents)
I comandi slash non hanno bisogno dell'intent message-content, quindi quelli
predefiniti bastano. command_prefix è obbligatorio per commands.Bot ma qui non
viene usato: puntiamo tutto sui comandi slash.
Registra il comando all'avvio
Due cose devono succedere una volta sola, quando il bot si avvia: creare un client
dell'SDK da riutilizzare per ogni richiesta e comunicare a Discord che il comando
esiste. Entrambe vanno in setup_hook, che discord.py esegue prima che il bot si
connetta.
@bot.eventasync def setup_hook():bot.api = AsyncPkmnPrices(os.environ["PKMNPRICES_API_KEY"])await bot.tree.sync()@bot.eventasync def on_ready():print(f"Logged in as {bot.user}")
È bot.tree.sync() a far comparire /price. Le sincronizzazioni globali si
propagano in qualche minuto, quindi durante lo sviluppo sincronizza su un solo
server per avere aggiornamenti immediati con
bot.tree.sync(guild=discord.Object(id=YOUR_GUILD_ID)).
Trasforma una carta in un embed
Tieni la presentazione fuori dal gestore del comando. Questa funzione di supporto prende una carta e restituisce un embed di Discord, con il verde del marchio sul lato. Dato che l'SDK restituisce modelli tipizzati, è tutto accesso ad attributi. Preferisce il prezzo Near Mint e, se manca, ripiega sul primo disponibile.
def build_embed(card):price = next((p for p in card.prices if p.condition == "Near Mint"),card.prices[0] if card.prices else None,)embed = discord.Embed(title=card.name, color=0x00E87B)if card.image_url:embed.set_thumbnail(url=card.image_url)embed.add_field(name="Set", value=card.set.name, inline=True)embed.add_field(name="Number", value=f"{card.number}/{card.total_set_number}", inline=True)embed.add_field(name="Condition", value=price.condition if price else "—", inline=True)embed.add_field(name="Market price",value=f"${price.market_price:.2f}" if price else "No data",inline=True,)embed.set_footer(text="Data from PkmnPrices")return embed
Definisci il comando /price
Ecco il problema della ricerca per nome: charizard corrisponde a decine di
carte, una per ogni espansione, ristampa e variante. Prendere il primo risultato
ne restituirebbe una a caso. La soluzione è l'autocompletamento di Discord
(nel prossimo passaggio): l'utente sceglie una corrispondenza reale mentre
scrive, e il valore dietro la sua scelta è l'ID della carta. Così il comando
riceve un ID e si limita a recuperarlo, con un ripiego per il testo libero
scritto senza scegliere.
@bot.tree.command(name="price", description="Look up the market price of a Pokémon card")@app_commands.describe(card="Start typing a card name, then pick one from the list")async def price(interaction: discord.Interaction, card: str):await interaction.response.defer()try:if card.isdigit():data = await bot.api.cards.get(int(card), currency="usd")else:page = await bot.api.cards.list(name=card, per_page=1)if not page.data:await interaction.followup.send(f"No card found for **{card}**.")returndata = await bot.api.cards.get(page.data[0].id, currency="usd")await interaction.followup.send(embed=build_embed(data))except Exception as err:await handle_error(interaction, err)
await interaction.response.defer() viene eseguito per primo: due chiamate di
andata e ritorno possono superare la finestra di tre secondi che Discord concede
per rispondere, e il defer ti fa guadagnare fino a quindici minuti.
(handle_error arriva nella prossima sezione.)
Lascia scegliere all'utente la carta giusta
La callback di autocompletamento scatta mentre l'utente scrive e restituisce fino a 25 opzioni, il massimo consentito da Discord. Abbinare il nome di ogni carta alla sua espansione trasforma un vago «Charizard» in una lista precisa: «Charizard · Base Set», «Charizard · Base Set 2» e così via. Il valore dietro ogni opzione è l'ID che il comando riceve.
@price.autocomplete("card")async def card_autocomplete(interaction: discord.Interaction, current: str):if len(current) < 3:return []page = await bot.api.cards.list(name=current, per_page=10)return [app_commands.Choice(name=f"{c.name} · {c.set.name}"[:100], value=str(c.id))for c in page.data]
Il controllo len(current) < 3 salta la ricerca finché la query non vale la
pena di essere eseguita, cosa importante perché questa funzione scatta a ogni
tasto premuto. Appoggiati alla cache della prossima sezione, così i prefissi
ripetuti non colpiscono l'API ogni volta.
Infine, la riga che fa partire tutto. Mettila in fondo al file:
bot.run(os.environ["DISCORD_TOKEN"])
Gestisci errori e limiti di richieste
Un bot che si rompe al primo input sbagliato non dura un giorno in un server affollato. L'SDK solleva eccezioni tipizzate, quindi gestisci esattamente i casi che ti interessano. I due che vale la pena intercettare riguardano l'autenticazione e i limiti:
async def handle_error(interaction, err):if isinstance(err, UnauthorizedError):await interaction.followup.send("API key is missing or invalid.")elif isinstance(err, (RateLimitError, CreditLimitError)):await interaction.followup.send("Rate or credit limit hit. Try again shortly.")else:print(err)await interaction.followup.send("Something went wrong. Try again in a moment.")
L'SDK ritenta già gli errori temporanei con backoff (puoi regolarlo con
AsyncPkmnPrices(key, max_retries=2)), quindi quando RateLimitError arriva al
tuo gestore il limite è reale. Il piano gratuito consente 60 richieste al minuto
e 100 crediti al giorno, e gli endpoint di elenco costano 1 credito per ogni
elemento restituito (documentazione sui limiti di richieste).
È nell'autocompletamento che si nasconde il costo: ogni ricerca di suggerimenti
restituisce fino a dieci carte e scatta mentre l'utente scrive, quindi una
singola ricerca può spendere più in suggerimenti che per il prezzo vero e proprio.
Due accorgimenti tengono i costi sotto controllo. Il controllo
len(current) < 3 taglia le query più corte; l'altro è una cache, così i
prefissi ripetuti riusano una sola risposta invece di pagarla di nuovo. I prezzi
si aggiornano ogni giorno, quindi una breve cache in memoria non ti costa nulla
in precisione:
import time_cache = {}TTL = 10 * 60 # secondsasync def search_cached(api, name):key = name.lower()hit = _cache.get(key)if hit and time.monotonic() - hit["at"] < TTL:return hit["data"]page = await api.cards.list(name=name, per_page=10)_cache[key] = {"data": page.data, "at": time.monotonic()}return page.data
Chiama search_cached(bot.api, current) dalla callback di autocompletamento al
posto di bot.api.cards.list, e un utente che scorre tra «char», «chari»,
«chariz» paga una ricerca sola, non tre.
Mettilo online
In locale ti basta un comando: python bot.py. Invita il bot in un server con il
generatore di URL OAuth2 del Developer Portal e lo scope
applications.commands, e /price comparirà nella lista dei comandi.
Per tenerlo sempre acceso va bene qualsiasi posto che esegua Python: un piccolo VPS, una piattaforma di container o un hosting per progetti amatoriali. L'unica regola rigida è quella dell'inizio: il token e la chiave API stanno nelle variabili d'ambiente, mai nel repository. Se la chiave API dovesse trapelare, rigenerala dalla dashboard.
Domande frequenti
- Perché usare l'SDK invece di chiamare direttamente l'API?
- L'SDK pkmnprices include un client asincrono (AsyncPkmnPrices) adatto a discord.py, oltre a modelli tipizzati, tentativi automatici con backoff ed eccezioni tipizzate come UnauthorizedError e RateLimitError. Tu scrivi la logica del bot, non l'infrastruttura HTTP.
- Funziona con il piano gratuito?
- Sì, con due limiti da conoscere. Il piano gratuito offre solo carte in inglese e consente 100 crediti al giorno a 60 richieste al minuto. Un comando /price con cache ci sta comodamente per un server piccolo o medio; un traffico intenso è il segnale per passare a un piano superiore.
- Quanti crediti costa un /price?
- La ricerca costa poco: 1 credito quando l'utente sceglie un suggerimento (solo il recupero della singola carta), oppure 2 con il ripiego sul testo libero. L'autocompletamento costa di più, perché ogni cards.list restituisce fino a 10 carte a 1 credito ciascuna. Il controllo dei 3 caratteri e la cache tengono la spesa sotto controllo.
- Il bot può mostrare lo storico dei prezzi o dei grafici?
- Sì. L'endpoint dello storico dei prezzi restituisce punti giornalieri su 7d, 30d, 90d o 365d. Trasformali in un'immagine e allegala all'embed per un comando /history, il passo successivo naturale quando /price funziona.
Per l'endpoint dello storico e ogni altra route, consulta il riferimento completo dell'API.
Per concludere
Ecco un bot dei prezzi funzionante: l'SDK ufficiale, un comando sincronizzato con autocompletamento e una risposta in embed, con gestione degli errori tipizzata e una cache per rispettare i limiti. Lo stesso schema in due passaggi, cercare un ID e poi recuperare i dati, funziona anche per i prodotti sigillati, le vendite su eBay e le offerte su Cardmarket.
Il riferimento completo dell'API copre ogni endpoint, e per iniziare a costruire ti basta una chiave gratuita dalla dashboard.

Scritto da
PkmnPrices
PkmnPrices è un’API per sviluppatori con i dati del GCC Pokémon: prezzi TCGPlayer giornalieri per oltre 80.000 carte, più di 800 espansioni e prodotti sigillati.