Skip to content

Examples


ScurryKit Examples

Examples have a counterpart that uses ScurryKit, an optional helper library built on top of ScurryPy.

To run ScurryKit examples, install it with:

pip install scurry-kit

Basic Event


Demonstrates responding to the READY event.

# --- Core library imports ---
from scurrypy import Client, ReadyEvent

# --- Setup bot ---
client = Client(token=TOKEN)

async def on_ready(event: ReadyEvent):
    print("Bot is online!")

client.add_event_listener("READY", on_ready)

# --- Run the bot ---
client.run()
# --- Core library imports ---
from scurrypy import Client, EventTypes, ReadyEvent
from scurry_kit import EventsAddon, setup_default_logger

# --- Logger ---
logger = setup_default_logger()

# --- Setup bot and addons ---
client = Client(token=TOKEN)
events = EventsAddon(client)

@events.listen(EventTypes.READY)
async def on_ready(bot: Client, event: ReadyEvent):
    print("Bot is online!")

# --- Run the bot ---
client.run()

Basic Prefix Command


Demonstrates registering and responding to a prefix command.

Legacy

While prefix commands are supported, they are deemed a legacy feature.

Discord encourages using interactions. See the next example.

# --- Core library imports ---
from scurrypy import Client, Intents, EventTypes, MessageCreateEvent

client = Client(token=TOKEN, intents=Intents.set(message_content=True))

# --- Setup bot ---
async def on_ping(event: MessageCreateEvent):
    if not event.content:
        return

    if not event.content.startswith('!ping'):
        return

    await client.channel(event.channel_id).send("Pong!")

client.add_event_listener(EventTypes.MESSAGE_CREATE, on_ping)

# --- Run the bot ---
client.run()
# --- Core library imports ---
from scurrypy import Client, Intents, Channel
from scurry_kit import PrefixAddon, setup_default_logger

# --- Logger ---
logger = setup_default_logger()

# --- Setup bot and addons ---
client = Client(token=TOKEN, intents=Intents.set(message_content=True))
prefixes = PrefixAddon(client, APP_ID, '!')

@prefixes.listen("ping")
async def ping_cmd(bot: Client, channel: Channel):
    await channel.send("Pong!")

# --- Run the bot ---
client.run()

Basic Slash Command


Demonstrates registering and responding to a slash command interaction.

# --- Core library imports ---
from scurrypy import Client, EventTypes, InteractionEvent, SlashCommandPart

# --- Setup bot ---
client = Client(token=TOKEN)

async def on_greet(event: InteractionEvent):
    if event.data.name != "greet":
        return

    await client.interaction(event.id, event.token).respond("Hello!")

async def create_commands():
    await client.guild_command(APP_ID, GUILD_ID).create(
        SlashCommandPart("greet", "Greet the bot!")
    )

client.add_startup_hook(create_commands)
client.add_event_listener(EventTypes.INTERACTION_CREATE, on_greet)

# --- Run the bot ---
client.run()
# --- Core library imports ---
from scurrypy import Client, Interaction
from scurry_kit import CommandsAddon, setup_default_logger

# --- Logger ---
logger = setup_default_logger()

# --- Setup bot and addons ---
client = Client(token=TOKEN)
commands = CommandsAddon(client, APP_ID)

@commands.slash_command("hello", "Say hello", guild_ids=GUILD_ID) # specify guild ID for guild command
async def hello(bot: Client, interaction: Interaction):
    await interaction.respond("Hello!")

# --- Run the bot ---
client.run()

About guild commands

Guild commands appear instantly when registered to a guild.

Global commands can take up to 1 hour to propagate.

Component Interactions


Demonstrates building and responding to a button interaction.

# --- Core library imports ---
from scurrypy import (
    Client, 
    EventTypes, InteractionEvent, InteractionTypes, 
    SlashCommandPart, 
    MessagePart, ActionRowPart, ButtonPart, ButtonStyles
)

# --- Setup bot ---
client = Client(token=TOKEN)

async def create_commands():
    await client.guild_command(APP_ID, GUILD_ID).create(
        SlashCommandPart('button_demo', 'A command with a button!')
    )

async def handle_button(event: InteractionEvent):
    if event.type == InteractionTypes.APPLICATION_COMMAND:
        if event.data.name != 'button_demo':
            return

        await client.interaction(event.id, event.token).respond(
            MessagePart(
                content='Press the button!',
                components=[
                    ActionRowPart([
                        ButtonPart(ButtonStyles.PRIMARY, 'btn_demo', 'Press me!')
                    ])
                ]
            )
        )

    elif event.type == InteractionTypes.MESSAGE_COMPONENT:
        if event.data.custom_id != 'btn_demo':
            return

        await client.interaction(event.id, event.token).update("You pressed the button!")

client.add_startup_hook(create_commands)
client.add_event_listener(EventTypes.INTERACTION_CREATE, handle_button)

# --- Run the bot ---
client.run()
# --- Core library imports ---
from scurrypy import Client, Interaction, MessagePart
from scurry_kit import CommandsAddon, ComponentsAddon, ActionRowBuilder as A, setup_default_logger

# --- Logger ---
logger = setup_default_logger()

# --- Setup bot and addons ---
client = Client(token=TOKEN)
commands = CommandsAddon(client, APP_ID)
components = ComponentsAddon(client)

@commands.slash_command("button_demo", "Show a button", guild_ids=GUILD_ID)
async def button_demo(bot: Client, interaction: Interaction):

    my_btns = A.row([
        A.primary(custom_id='demo_btn', label='Press')
    ])

    await interaction.respond(
        MessagePart(
            content="Click me!",
            components=[my_btns]
        )
    )

@components.button("demo_btn")
async def on_button(bot: Client, component: Interaction):
    await component.update("You pressed the button!")

# --- Run the bot ---
client.run()

Need to Refresh Your Commands?

Consider deleting your old commands before registering new ones:

async def refresh_commands():
    commands = await client.command(APP_ID, GUILD_ID).fetch_all()

    for cmd in commands:
        await client.command(APP_ID, GUILD_ID, cmd.id).delete()

    # then create your new commands

client.add_startup_hook(refresh_commands)
...
If you are using ScurryKit, this should be done for you.

Stateful Bot


Demonstrates grouping state and behavior using a class-based addon.

# --- Core library imports ---
from scurrypy import Client, EventTypes, SlashCommandPart, InteractionEvent, InteractionTypes

import asyncio

# --- Setup bot and addons ---
class StatefulBot:
    def __init__(self, client: Client):
        self.bot = client

        self.user_points = {}
        self.dict_lock = asyncio.Lock()

        client.add_startup_hook(self.register_commands)
        client.add_event_listener(EventTypes.INTERACTION_CREATE, self.dispatch)

    async def register_commands(self):
        """Register slash commands on startup (before READY)."""

        bot_commands = self.bot.guild_command(APP_ID, GUILD_ID)
        commands = [
            SlashCommandPart('points', 'Check your points'),
            SlashCommandPart('addpoints', 'Give points')
        ]

        for cmd in commands:
            await bot_commands.create(cmd)

    async def points(self, event: InteractionEvent):
        """Get points for the invoking user."""
        user = event.member.user.id

        pts = self.user_points.get(user, 0)

        await self.bot.interaction(event.id, event.token).respond(f"You have {pts} points!")

    async def addpoints(self, event: InteractionEvent):
        """Add points for the invoking user."""
        user = event.member.user.id

        async with self.dict_lock:
            self.user_points[user] = self.user_points.get(user, 0) + 1

        await self.bot.interaction(event.id, event.token).respond("Point added!")

    async def dispatch(self, event: InteractionEvent):
        """Main entry point for commands."""
        if event.type != InteractionTypes.APPLICATION_COMMAND:
            return # ignore non-command interactions

        match event.data.name:
            case 'points':
                await self.points(event)
            case 'addpoints':
                await self.addpoints(event)
            case _:
                await self.bot.interaction(event.id, event.token).respond(f"No command named '{event.data.name}'!")

client = Client(token=TOKEN)

StatefulBot(client)

# --- Run the bot ---
client.run()
# --- Core library imports ---
from scurrypy import Client, Interaction, InteractionEvent
from scurry_kit import CommandsAddon, setup_default_logger

# --- Logger ---
logger = setup_default_logger()

# --- Setup bot and addons ---
client = Client(token=TOKEN)
commands = CommandsAddon(client, APP_ID)

import asyncio
user_points = {}
dict_lock = asyncio.Lock()

@commands.slash_command("points", "Check your points", guild_ids=GUILD_ID)
async def points(bot: Client, interaction: Interaction):
    event: InteractionEvent = interaction.context
    user = event.member.user.id

    pts = user_points.get(user, 0)
    await interaction.respond(f"You have {pts} points!")

@commands.slash_command("addpoints", "Give points", guild_ids=GUILD_ID)
async def addpoints(bot: Client, interaction: Interaction):
    event: InteractionEvent = interaction.context
    user = event.member.user.id

    # lock for async
    async with dict_lock:
        user_points[user] = user_points.get(user, 0) + 1

    await interaction.respond("Point added!")

# --- Run the bot ---
client.run()

Stateful Bot: An Alternative Pattern


Demonstrates the same stateful bot example, but with state separated from bot to show no one object owns state.

# --- Core library imports ---
from scurrypy import Client, EventTypes, SlashCommandPart, InteractionEvent, InteractionTypes

import asyncio

# --- Setup bot state separate from event handlers ---
class StatefulBot:
    def __init__(self, client: Client):
        self.bot = client

        self.user_points = {}
        self.dict_lock = asyncio.Lock()

    def get_points(self, user_id: int):
        """Get points for the invoking user."""
        return self.user_points.get(user_id, 0)

    async def addpoints(self, user_id: int):
        """Add points for the invoking user."""
        async with self.dict_lock:
            self.user_points[user_id] = self.user_points.get(user_id, 0) + 1

# --- Setup bot and bot state ---
client = Client(token=TOKEN)

async def register_commands():
    """Register slash commands on startup (before READY)."""
    bot_commands = client.guild_command(APP_ID, GUILD_ID)
    commands = [
        SlashCommandPart('points', 'Check your points'),
        SlashCommandPart('addpoints', 'Give points')
    ]

    for cmd in commands:
        await bot_commands.create(cmd)

client.add_startup_hook(register_commands)

bot_state = StatefulBot(client)

async def on_points(event: InteractionEvent):
    pts = bot_state.get_points(event.member.user.id)
    await client.interaction(event.id, event.token).respond(f"You have {pts} points!")

async def on_add_points(event: InteractionEvent):
    await bot_state.addpoints(event.member.user.id)
    await client.interaction(event.id, event.token).respond("Point added!")

async def dispatch(event: InteractionEvent):
    """Main entry point for commands."""
    if event.type != InteractionTypes.APPLICATION_COMMAND:
        return # ignore non-command interactions

    match event.data.name:
        case 'points':
            await on_points(event)
        case 'addpoints':
            await on_add_points(event)
        case _:
            await client.interaction(event.id, event.token).respond(f"No command named '{event.data.name}'!")

client.add_event_listener(EventTypes.INTERACTION_CREATE, dispatch)

# --- Run the bot ---
client.run()

More Examples...


Looking for more examples? Check out the ScurryKit project.

Want more customization? Check out using Addons.