This guide shows the philosophical difference between the two libraries. For in-depth ScurryPy patterns, see the Examples page
Both discord.py and ScurryPy can build the same bots. The difference is in how they approach complexity.
discord.py hides it behind framework abstractions. ScurryPy shows it explicitly in your code.
Below is discord.py's "basic bot" example implemented in both libraries. Notice they're roughly the same length, but one uses framework magic, while the other uses plain Python.
# This example requires the 'members' and 'message_content' privileged intents to function.importdiscordfromdiscord.extimportcommandsimportrandomdescription="""An example bot to showcase the discord.ext.commands extensionmodule.There are a number of utility commands being showcased here."""intents=discord.Intents.default()intents.members=Trueintents.message_content=Truebot=commands.Bot(command_prefix='?',description=description,intents=intents)@bot.eventasyncdefon_ready():# Tell the type checker that User is filled up at this pointassertbot.userisnotNoneprint(f'Logged in as {bot.user} (ID: {bot.user.id})')print('------')@bot.command()asyncdefadd(ctx,left:int,right:int):"""Adds two numbers together."""awaitctx.send(left+right)@bot.command()asyncdefroll(ctx,dice:str):"""Rolls a dice in NdN format."""try:rolls,limit=map(int,dice.split('d'))exceptException:awaitctx.send('Format has to be in NdN!')returnresult=', '.join(str(random.randint(1,limit))forrinrange(rolls))awaitctx.send(result)@bot.command(description='For when you wanna settle the score some other way')asyncdefchoose(ctx,*choices:str):"""Chooses between multiple choices."""awaitctx.send(random.choice(choices))@bot.command()asyncdefrepeat(ctx,times:int,content='repeating...'):"""Repeats a message multiple times."""foriinrange(times):awaitctx.send(content)@bot.command()asyncdefjoined(ctx,member:discord.Member):"""Says when a member joined."""# Joined at can be None in very bizarre cases so just handle that as wellifmember.joined_atisNone:awaitctx.send(f'{member} has no join date.')else:awaitctx.send(f'{member} joined {discord.utils.format_dt(member.joined_at)}')bot.run('token')
# --- Environment setup ---importosfromdotenvimportload_dotenvload_dotenv()TOKEN=os.getenv("BOT_TOKEN")APP_ID=0GUILD_ID=0# --- Core library imports ---fromscurrypyimportClient,Intents,EventTypes,ReadyEvent,MessageCreateEvent,Channel,GuildMemberAddEventintents=Intents.set(message_content=True,guild_members=True)client=Client(token=TOKEN,intents=intents)importrandomasyncdefon_ready(event:ReadyEvent):bot_user=event.userprint(f"Logged in as {bot_user.username} (ID: {bot_user.id})")client.add_event_listener(EventTypes.READY,on_ready)asyncdefadd(channel:Channel,a:int,b:int):"""Adds two numbers together."""awaitchannel.send(f"{a} + {b} = **{a+b}**")asyncdefroll(channel:Channel,dice:str):"""Rolls a dice in NdN format."""try:rolls,limit=map(int,dice.split('d'))except:awaitchannel.send('Format has to be in NdN!')returnresult=', '.join(str(random.randint(1,limit))forrinrange(rolls))awaitchannel.send(result)asyncdefchoose(channel:Channel,choices):"""Chooses between multiple choices."""awaitchannel.send(f"{random.choice(choices)}")asyncdefrepeat(channel:Channel,times:int):"""Repeats a message multiple times."""for_inrange(times):awaitchannel.send("Repeating...")WELCOME_CHANNEL_ID=0asyncdefjoined(event:GuildMemberAddEvent):member=event.userchannel=client.channel(WELCOME_CHANNEL_ID)# create a channel resource to send the messageifevent.joined_atisNone:awaitchannel.send(f"{member.username} has no join date.")else:awaitchannel.send(f"{member.username} joined {event.joined_at}")client.add_event_listener(EventTypes.GUILD_MEMBER_ADD,joined)asyncdefdispatch(event:MessageCreateEvent):"""Manual command dispatcher (equivalent to discord.py commands.Bot)."""ifnotevent.content:return# ignore event for this callback if no content is present (likely missing intent)ifevent.author.id==APP_ID:return# ignore bot messagesifnotevent.content.startswith('!'):return# ignore non-command messagescommand,*args=event.content.split(' ')channel=client.channel(event.channel_id,context=event)try:matchcommand:case'!roll':awaitroll(channel,args[0])case'!choose':awaitchoose(channel,args)case'!repeat':awaitrepeat(channel,int(args[0]))case'!add':awaitadd(channel,int(args[0]),int(args[1]))case_:awaitchannel.send(f"Command '{command}' not recognized!")exceptIndexError:awaitchannel.send("Invalid command arguments!")client.add_event_listener(EventTypes.MESSAGE_CREATE,dispatch)client.run()
Check out the Examples to see ScurryPy in action, or dive into Getting Started to build your first bot.
Want the best of both worlds? Try ScurryKit, a framework built on ScurryPy that provides decorator-style convenience while keeping the architecture clean.