Skip to content

Discord Bot Integration

Create a Discord bot that responds to commands like /card Charizard with card info and current prices.

  • Discord slash command for card lookup
  • Search across all 5 TCGs
  • Display prices and card images
  • Handle multiple search results
  1. Create project

    Terminal window
    mkdir pulls-discord-bot && cd pulls-discord-bot
    npm init -y
    npm install @pulls/sdk discord.js dotenv
  2. Configure environment

    .env
    PULLS_API_KEY=pk_live_...
    DISCORD_TOKEN=...
    DISCORD_CLIENT_ID=...
  3. Register slash command

    src/register.ts
    import { REST, Routes, SlashCommandBuilder } from 'discord.js'
    import 'dotenv/config'
    const command = new SlashCommandBuilder()
    .setName('card')
    .setDescription('Look up a TCG card')
    .addStringOption(opt =>
    opt.setName('name')
    .setDescription('Card name to search')
    .setRequired(true)
    )
    .addStringOption(opt =>
    opt.setName('tcg')
    .setDescription('Filter by TCG')
    .addChoices(
    { name: 'Pokemon', value: 'pokemon' },
    { name: 'Magic', value: 'mtg' },
    { name: 'Yu-Gi-Oh!', value: 'yugioh' },
    { name: 'Lorcana', value: 'lorcana' },
    { name: 'One Piece', value: 'onepiece' },
    )
    )
    const rest = new REST().setToken(process.env.DISCORD_TOKEN!)
    await rest.put(
    Routes.applicationCommands(process.env.DISCORD_CLIENT_ID!),
    { body: [command.toJSON()] }
    )
    console.log('Registered /card command')
src/bot.ts
import { Client, GatewayIntentBits, EmbedBuilder } from 'discord.js'
import { PullsAPI } from '@pulls/sdk'
import 'dotenv/config'
const pulls = new PullsAPI({ apiKey: process.env.PULLS_API_KEY! })
const client = new Client({ intents: [GatewayIntentBits.Guilds] })
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return
if (interaction.commandName !== 'card') return
await interaction.deferReply()
const name = interaction.options.getString('name', true)
const tcg = interaction.options.getString('tcg')
try {
const results = await pulls.search.cards({
query: name,
tcg: tcg ?? undefined,
limit: 5
})
if (results.length === 0) {
await interaction.editReply(`No cards found for "${name}"`)
return
}
const card = results[0]
const embed = buildCardEmbed(card)
await interaction.editReply({ embeds: [embed] })
} catch (error) {
console.error(error)
await interaction.editReply('Error fetching card data')
}
})
function buildCardEmbed(card: Card): EmbedBuilder {
const tcgColors: Record<string, number> = {
pokemon: 0xFFCB05,
mtg: 0x000000,
yugioh: 0x8B4513,
lorcana: 0x4B0082,
onepiece: 0xDC143C,
}
return new EmbedBuilder()
.setTitle(card.name)
.setURL(`https://pulls.app/cards/${card.id}`)
.setColor(tcgColors[card.tcg] ?? 0x3b82f6)
.setThumbnail(card.imageUrl)
.addFields(
{ name: 'Set', value: card.set.name, inline: true },
{ name: 'TCG', value: card.tcg.toUpperCase(), inline: true },
{ name: 'Number', value: card.number ?? 'N/A', inline: true },
{ name: 'Market Price', value: `$${card.prices?.market?.toFixed(2) ?? 'N/A'}`, inline: true },
{ name: 'Low', value: `$${card.prices?.low?.toFixed(2) ?? 'N/A'}`, inline: true },
{ name: 'High', value: `$${card.prices?.high?.toFixed(2) ?? 'N/A'}`, inline: true },
)
.setFooter({ text: 'Powered by Pulls API' })
}
client.login(process.env.DISCORD_TOKEN!)
Terminal window
# First, register the command
npx ts-node src/register.ts
# Then start the bot
npx ts-node src/bot.ts

When a user runs /card Charizard tcg:pokemon:

┌─────────────────────────────────────┐
│ Charizard │
│ https://pulls.app/cards/base1-4 │
├─────────────────────────────────────┤
│ Set: Base Set │ TCG: POKEMON │
│ Number: 4/102 │ │
├─────────────────────────────────────┤
│ Market: $420.00 │ Low: $350.00 │
│ High: $550.00 │ │
├─────────────────────────────────────┤
│ Powered by Pulls API │
└─────────────────────────────────────┘
  • Add autocomplete for card names
  • Support graded price lookup (/graded PSA 10 Charizard)
  • Add price alerts (/watch sv7-001)
  • Include price history charts as images