A Discord bot that's stupid and made by me, Alento. It contains a modular framework relying on PyYAML and Discord.py, with multiple helpers for bot developers, and multiple modules for bot users.
- Python 3.8+
- discord.py
- ruamel.yaml
As of writing this, several discord.py dependencies do not have 3.9 wheels built for Windows. You are better off specifically installing Python 3.8.x in that case
- Ubuntu 20.04 with Python 3.8.6
- Ubuntu 20.10 with Python 3.8.6
- Mint 19.3 with Python 3.8.6
This section contains instructions on how to get the bot up and running.
- Clone or download this repository and put it on disk. Unzip if needed and open a terminal/command prompt in the repository folder.
- (Optional) Create a virtual environment inside the cloned/unzipped repo (
python3 -m venv venv
) and activate it. (source venv/bin/activate
)- Ubuntu (Linux) -
python3 -m venv venv
to create andsource venv/bin/activate
to activate. - Windows -
py -m venv venv
to create andvenv/Scripts/activate.bat
to activate.
- Ubuntu (Linux) -
- Install the dependencies.
- Ubuntu (Linux) -
pip3 install -r requirements.txt
- Windows -
py -m pip install -r requirements.txt
- Ubuntu (Linux) -
If installed correctly, running start.py
(Windows, py start.py
- Ubuntu, python3 start.py
) should yield logger
messages and exit immediately.
One of the logger messages should mention putting your discord token in config.yaml
, which should be done at this
step. You can get your token from the bot portion of your application.
You need to have both PRESENCE INTENT and SERVER MEMBERS INTENT enabled for your bot.
Inside config.yaml
you can change the bot prefix, where data is stored, and the invite link for the invite
command.
- If you want to use existing modules, uncomment the ones you wish to use in
start.py
- Run
start.py
- Ubuntu (Linux) -
python3 start.py
- Windows -
py start.py
- Ubuntu (Linux) -
This section contains instructions on how to use the various helpers that the bot gives you to aid in Discord bot development.
- Alento Bot begins Initialization.
- Storage begins Initialization.
- Config is loaded.
- Data directories are created. (if needed)
- Cache, guild, and user storage are initialized.
- Logging is configured.
- Legacy Module is initialized and added to Alento Bot.
- Storage begins Initialization.
- Modules are added to Alento Bot.
- Modules added are initialized. Note, modules may not be initialized in the order they are added.
- Caches are likely added and loaded here.
- Guild and User data are likely added here.
- Cogs are initialized.
- Autosave loop is started but does not save on the first run.
- Timer loop is started.
- Load method for all modules is called.
- Bot does final setup.
- Checks for Discord token. If not found, aborts running the bot.
- Changes prefix if changed in config.
- Begins the Discord.py bot loop.
This allows for easy adding and removing specific sections of the bot, but still allows for the flexibility of having a class controlling the cogs.
Example code for a module with a single cog:
from alento_bot import BaseModule
from discord.ext import commands
class ExampleModule(BaseModule):
def __init__(self, *args):
BaseModule.__init__(self, *args)
self.add_cog(ExampleCog())
class ExampleCog(commands.Cog, name="Example Cog"):
@commands.command(name="example")
async def example_command(self, context: commands.Context):
await context.send("Hello there!")
Base module bits:
BaseModule.add_cog()
: Used to add cogs to the module.BaseModule.load()
: Overwide with no arguments (other than self) to have code execute on bot startup.BaseModule.save()
: Override with no arguments (other than self) to have code execute on bot shutdown.BaseModule.bot
: Gives access to thediscord.ext.commands.Bot
object.BaseModule.storage
: Gives access to theStorageManager
object.BaseModule.timer
: Gives access to the upcoming Timer object.
This makes it trivial to store variables on your disk as a central cache, per-guild data, or per-user data, all with autoloading and saving.
Cache is aimed to be a single, cross-server/user storage class. Guilds is meant to be per-guild (AKA server), user is per user.
Example code for all three types of storage:
from alento_bot import StorageManager, BaseModule, user_data_transformer, guild_data_transformer, cache_transformer
from discord.ext import commands
@cache_transformer(name="example_cache_data")
class ExampleCacheData:
def __init__(self):
self.global_uses = 0
@user_data_transformer(name="example_user_data")
class ExampleUserData:
def __init__(self):
self.guild_uses = 0
@guild_data_transformer(name="example_guild_data")
class ExampleGuildData:
def __init__(self):
self.user_uses = 0
class ExampleModule(BaseModule):
def __init__(self, *args):
BaseModule.__init__(self, *args)
self.cache = self.storage.caches.register_cache("example_cache_data", self.cache)
self.storage.guilds.register_data_name("example_guid_data", ExampleGuildData)
self.storage.users.register_data_name("example_user_data", ExampleUserData)
self.add_cog(ExampleCog(self.storage, self.cache))
class ExampleCog(commands.Cog, name="Example"):
def __init__(self, storage: StorageManager, cache: ExampleCacheData):
self.storage = storage
self.cache = cache
@commands.guild_only()
@commands.command(name="example", description="Example description text.", brief="Example brief text.")
async def example_command(self, context: commands.Context, *args):
guild_data = self.storage.guilds.get(context.guild.id, "example_guild_data")
user_data = self.storage.users.get(context.author.id, "example_user_data")
self.cache.global_uses += 1
guild_data.guild_uses += 1
user_data.user_uses += 1
await context.send(f"Example: Global uses `{self.cache.global_uses}`, Server uses {guild_data.guild_uses}, "
f"User uses: {user_data.user_uses}")
This is used to easily add not-very-accurate timed events to the bot. Roughly once a minute, the bot will loop through all the given timer events and check if they should be executed, so expect up to 1 minute of inaccuracy. No timer data is saved to disk.
To make a timer, you must provide a unique identifier string (could be your module name + datetime the timer goes off), the datetime the coroutine should be run at, and a coroutine to execute.
Example timer:
from datetime import datetime, timedelta
class TimerTestCog(commands.Cog):
def __init__(self, timer: TimerManager):
self.timer = timer
@commands.command(name="fivemintimer")
async def fivemintimer(self, context):
time_now = datetime.utcnow()
self.timer.add_timer(f"fivemintimer_{time_now}", time_now + timedelta(minutes=5),
self.on_five_minute_timer(context))
async def on_five_minute_timer(self, context):
await context.send("It's been roughly 5 minutes!")