Skip to content

Instantly share code, notes, and snippets.

@bynect
Last active May 2, 2024 19:08
Show Gist options
  • Save bynect/2d7317b4e2a3e926f2ce392e5e7cca4b to your computer and use it in GitHub Desktop.
Save bynect/2d7317b4e2a3e926f2ce392e5e7cca4b to your computer and use it in GitHub Desktop.
Simple and effective example of bot and cogs made with discord.py (rewrite).
"""
This example bot is structured in multiple files and is made with the goal of showcasing commands, events and cogs.
Although this example is not intended as a complete bot, but as a reference aimed to give you a basic understanding for
creating your bot, feel free to use these examples and point out any issue.
+ These examples are made with educational purpose and there are plenty of docstrings and explanation about most of the code.
+ This example is made with Python 3.8.5 and Discord.py 1.4.0a (rewrite).
Documentation:
+ Discord.py latest: https://discordpy.readthedocs.io/en/latest/
+ Migration to rewrite: https://discordpy.readthedocs.io/en/latest/migrating.html
+ Commands documentation: https://discordpy.readthedocs.io/en/latest/ext/commands/commands.html
+ Cogs documentation: https://discordpy.readthedocs.io/en/latest/ext/commands/cogs.html
+ Tasks documentation: https://discordpy.readthedocs.io/en/latest/ext/tasks/index.html
The example files are organized in this directory structure:
...
🗀bot
-bot.py
🗀cogs
-dev.py
-tools.py
-quote.py
"""
from discord.ext import commands, tasks
import discord
from os import listdir
TOKEN = '<your-bot-token>'#DON'T SHARE NOR MAKE PUBLIC YOUR BOT TOKEN.
def get_prefix(bot, message):
"""This function returns a Prefix for our bot's commands.
Args:
bot (commands.Bot): The bot that is invoking this function.
message (discord.Message): The message that is invoking.
Returns:
string or iterable conteining strings: A string containing prefix or an iterable containing prefixes
Notes:
Through a database (or even a json) this function can be modified to returns per server prefixes.
This function should returns only strings or iterable containing strings.
This function shouldn't returns numeric values (int, float, complex).
Empty strings as the prefix always matches, and should be avoided, at least in guilds.
"""
if not isinstance(message.guild, discord.Guild):
"""Checks if the bot isn't inside of a guild.
Returns a prefix string if true, otherwise passes.
"""
return '!'
return ['!', '?', '>']
bot = commands.Bot(command_prefix=get_prefix, description='A simple example of bot made with Discord.py')
if __name__=='__main__':
"""Loads the cogs from the `./cogs` folder.
Notes:
The cogs are .py files.
The cogs are named in this format `{cog_dir}.{cog_filename_without_extension}`_.
"""
for cog in listdir('./cogs'):
if cog.endswith('.py') == True:
bot.load_extension(f'cogs.{cog[:-3]}')
@bot.event
#This is the decorator for events (outside of cogs).
async def on_ready():
"""This coroutine is called when the bot is connected to Discord.
Note:
`on_ready` doesn't take any arguments.
Documentation:
https://discordpy.readthedocs.io/en/latest/api.html#discord.on_ready
"""
print(f'{bot.user.name} is online and ready!')
#Prints a message with the bot name.
change_status.start()
#Starts the task `change_status`_.
statuslist = cycle([
'Pythoning',
'Doing stuff...',
])
@tasks.loop(seconds=16)
async def change_status():
"""This is a background task that loops every 16 seconds.
The coroutine looped with this task will change status over time.
The statuses used are in the cycle list called `statuslist`_.
Documentation:
https://discordpy.readthedocs.io/en/latest/ext/tasks/index.html
"""
await bot.change_presence(activity=discord.Game(next(statuslist)))
#Changes the bot status to `Pythoning`_.
@bot.command()
#This is the decorator for commands (outside of cogs).
async def greet(ctx):
"""This coroutine sends a greeting message when called by the command.
Note:
All commands must be preceded by the bot prefix.
"""
await ctx.send(f'Hello {ctx.message.author.mention}!')
#The bot send a message on the channel that is being invoked in and mention the invoker.
bot.run(TOKEN)#Runs the bot with its token. Don't put code below this command.
from discord.ext import commands
import discord
from sys import version_info as sysv
from os import listdir
class Dev(commands.Cog):
"""This is a cog with owner-only commands.
Note:
All cogs inherits from `commands.Cog`_.
All cogs are classes, so they need self as first argument in their methods.
All cogs use different decorators for commands and events (see example below).
All cogs needs a setup function (see below).
Documentation:
https://discordpy.readthedocs.io/en/latest/ext/commands/cogs.html
"""
def __init__(self, bot):
self.bot = bot
@commands.Cog.listener()
#This is the decorator for events (inside of cogs).
async def on_ready(self):
print(f'Python {sysv.major}.{sysv.minor}.{sysv.micro} - Disord.py {discord.__version__}\n')
#Prints on the shell the version of Python and Discord.py installed in our computer.
@commands.command(name='reloadall', hidden=True)#This command is hidden from the help menu.
#This is the decorator for commands (inside of cogs).
@commands.is_owner()
#Only the owner (or owners) can use the commands decorated with this.
async def reload_all(self, ctx):
"""This commands reloads all the cogs in the `./cogs` folder.
Note:
This command can be used only from the bot owner.
This command is hidden from the help menu.
This command deletes its messages after 20 seconds."""
message = await ctx.send('Reloading...')
await ctx.message.delete()
try:
for cog in listdir('./cogs'):
if cog.endswith('.py') == True:
self.bot.reload_extension(f'cogs.{cog[:-3]}')
except Exception as exc:
await message.edit(content=f'An error has occurred: {exc}', delete_after=20)
else:
await message.edit(content='All cogs have been reloaded.', delete_after=20)
def check_cog(self, cog):
"""Returns the name of the cog in the correct format.
Args:
self
cog (str): The cogname to check
Returns:
cog if cog starts with `cogs.`, otherwise an fstring with this format`cogs.{cog}`_.
Note:
All cognames are made lowercase with `.lower()`_.
"""
if (cog.lower()).startswith('cogs.') == True:
return cog.lower()
return f'cogs.{cog.lower()}'
@commands.command(name='load', hidden=True)
@commands.is_owner()
async def load_cog(self, ctx, *, cog: str):
"""This commands loads the selected cog, as long as that cog is in the `./cogs` folder.
Args:
cog (str): The name of the cog to load. The name is checked with `.check_cog(cog)`_.
Note:
This command can be used only from the bot owner.
This command is hidden from the help menu.
This command deletes its messages after 20 seconds.
"""
message = await ctx.send('Loading...')
await ctx.message.delete()
try:
self.bot.load_extension(self.check_cog(cog))
except Exception as exc:
await message.edit(content=f'An error has occurred: {exc}', delete_after=20)
else:
await message.edit(content=f'{self.check_cog(cog)} has been loaded.', delete_after=20)
@commands.command(name='unload', hidden=True)
@commands.is_owner()
async def unload_cog(self, ctx, *, cog: str):
"""This commands unloads the selected cog, as long as that cog is in the `./cogs` folder.
Args:
cog (str): The name of the cog to unload. The name is checked with `.check_cog(cog)`_.
Note:
This command can be used only from the bot owner.
This command is hidden from the help menu.
This command deletes its messages after 20 seconds.
"""
message = await ctx.send('Unloading...')
await ctx.message.delete()
try:
self.bot.unload_extension(self.check_cog(cog))
except Exception as exc:
await message.edit(content=f'An error has occurred: {exc}', delete_after=20)
else:
await message.edit(content=f'{self.check_cog(cog)} has been unloaded.', delete_after=20)
@commands.command(name='reload', hidden=True)
@commands.is_owner()
async def reload_cog(self, ctx, *, cog: str):
"""This commands reloads the selected cog, as long as that cog is in the `./cogs` folder.
Args:
cog (str): The name of the cog to reload. The name is checked with `.check_cog(cog)`_.
Note:
This command can be used only from the bot owner.
This command is hidden from the help menu.
This command deletes its messages after 20 seconds.
"""
message = await ctx.send('Reloading...')
await ctx.message.delete()
try:
self.bot.reload_extension(self.check_cog(cog))
except Exception as exc:
await message.edit(content=f'An error has occurred: {exc}', delete_after=20)
else:
await message.edit(content=f'{self.check_cog(cog)} has been reloaded.', delete_after=20)
def setup(bot):
"""Every cog needs a setup function like this."""
bot.add_cog(Dev(bot))
from discord.ext import commands, tasks
import discord
import aiohttp
class Quote(commands.Cog):
"""This is a cog with a quote command and a quote retriever.
Note:
All cogs inherits from `commands.Cog`_.
All cogs are classes.
All cogs needs a setup function (see below).
Documentation:
https://discordpy.readthedocs.io/en/latest/ext/commands/cogs.html
"""
def __init__(self, bot):
self.bot = bot
self.qod = ''#Stores the quote of the day
self.qod_auth = ''#Stores the quote author
self.refresh_quote.start()#Starts the task loop
@tasks.loop(hours=1.0)
async def refresh_quote(self):
"""Refreshes the qod and the quote author every hour.
Note:
Made due to performance optimization.
"""
qod, author = await self.get_quote()
self.qod = qod
self.qod_auth = author
async def get_quote(self):
"""Gets the quote of the day from the They Said So API.
Note:
This function uses `aiohttp` to get the quote of the day and its author.
"""
async with aiohttp.ClientSession() as session:
async with session.get('https://quotes.rest/qod') as resp:
if resp.status == 200:#Checks if the request is successfull
qod = (await resp.json())['contents']['quotes'][0]['quote']
author = (await resp.json())['contents']['quotes'][0]['author']
return qod, author
@commands.command(name='quote', aliases=['qod',], description='Sends the quote of the day.')
async def quote(self, ctx):
"""Sends the quote of the day in the channel where the command invoken."""
await ctx.send(f'>>> {self.qod} -_{self.qod_auth}_')#`>>>` is discord markup for quotes.
def setup(bot):
"""Every cog needs a setup function like this."""
bot.add_cog(Quote(bot))
from discord.ext import commands
import discord
import datetime
class Tools(commands.Cog):
"""This is a cog with simple and useful commands.
Note:
All cogs inherits from `commands.Cog`_.
All cogs are classes, so they need self as first argument in their methods.
All cogs use different decorators for commands and events (see example in dev.py).
All cogs needs a setup function (see below).
Documentation:
https://discordpy.readthedocs.io/en/latest/ext/commands/cogs.html
"""
def __init__(self, bot):
self.bot = bot
@commands.command(name='echo')
async def echo(self, ctx, *, message):
"""This command outputs the string that is being passed as argument.
Args:
self
ctx
*, message (this sets the 'consume rest' behaviour for arguments)
"""
await ctx.message.delete()
await ctx.send(message)
@commands.command(name='ping')
async def ping(self, ctx):
"""Sends a message with bot's latency in ms in the channel where the command has been invoked.
Note:
`bot.latency` outputs the latency in seconds.
"""
await ctx.send(f'🏓 {round(self.bot.latency * 1000)} ms.')
@commands.command(name='info')
async def get_info(self, ctx):
"""This coroutine sends an embedded message with some info.
Documentation:
https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed
"""
#embed example
embed = discord.Embed(
title = 'Info',
description = 'An info message using an embed!',
colour = discord.Colour.blurple()#0x7289da
timestamp = datetime.datetime.utcnow()
)
#embed fields
embed.set_footer(text=f'this bot is running on {len(self.bot.guilds)}')
embed.add_field(name='Version', value='0.1', inline=True)
embed.add_field(name='Language', value='Python 3.8', inline=True)
embed.set_author(name='nect', url='https://gist.github.com/bynect', icon_url='http://tiny.cc/nect-user-pic')
await ctx.send(embed = embed)
def setup(bot):
"""Every cog needs a setup function like this."""
bot.add_cog(Tools(bot))
@Doge229
Copy link

Doge229 commented Sep 7, 2023

You are a god amongst men for this, thank you!

@bynect
Copy link
Author

bynect commented Sep 8, 2023

You are a god amongst men for this, thank you!

Glad you liked it 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment