diff --git a/UPDATES.md b/UPDATES.md index 361cc1eb..7ca037da 100644 --- a/UPDATES.md +++ b/UPDATES.md @@ -2,6 +2,12 @@ Here is the list of all the updates that I made on this template. +### Vesion 5.2 (30 September 2022) + +* Added `load`, `reload` and `unload` commands. +* Added `sync` and `unsync` commands. +* Code refactoring and cleanup. + ### Version 5.1 (12 September 2022) * Added the `help` command once again diff --git a/bot.py b/bot.py index 9afa4dd0..444a3cfd 100644 --- a/bot.py +++ b/bot.py @@ -3,7 +3,7 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ import asyncio @@ -13,14 +13,12 @@ import random import sqlite3 import sys - from contextlib import closing import discord from discord import Interaction -from discord.ext import tasks, commands -from discord.ext.commands import Bot -from discord.ext.commands import Context +from discord.ext import commands, tasks +from discord.ext.commands import Bot, Context import exceptions @@ -73,7 +71,8 @@ """ # intents.message_content = True -bot = Bot(command_prefix=commands.when_mentioned_or(config["prefix"]), intents=intents, help_command=None) +bot = Bot(command_prefix=commands.when_mentioned_or( + config["prefix"]), intents=intents, help_command=None) def init_db(): @@ -97,6 +96,7 @@ def connect_db(): bot.config = config bot.db = connect_db() + @bot.event async def on_ready() -> None: """ @@ -108,7 +108,6 @@ async def on_ready() -> None: print(f"Running on: {platform.system()} {platform.release()} ({os.name})") print("-------------------") status_task.start() - await bot.tree.sync() @tasks.loop(minutes=1.0) @@ -145,7 +144,8 @@ async def on_command_completion(context: Context) -> None: print( f"Executed {executed_command} command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})") else: - print(f"Executed {executed_command} command by {context.author} (ID: {context.author.id}) in DMs") + print( + f"Executed {executed_command} command by {context.author} (ID: {context.author.id}) in DMs") @bot.event @@ -222,4 +222,4 @@ async def load_cogs() -> None: init_db() asyncio.run(load_cogs()) -bot.run(config["token"]) \ No newline at end of file +bot.run(config["token"]) diff --git a/cogs/fun.py b/cogs/fun.py index 4f885ed2..7ae13786 100644 --- a/cogs/fun.py +++ b/cogs/fun.py @@ -3,7 +3,7 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ import random @@ -13,7 +13,6 @@ from discord import app_commands from discord.ext import commands from discord.ext.commands import Context - from helpers import checks @@ -66,7 +65,10 @@ async def callback(self, interaction: discord.Interaction): bot_choice_index = choices[bot_choice] result_embed = discord.Embed(color=0x9C84EF) - result_embed.set_author(name=interaction.user.name, icon_url=interaction.user.avatar.url) + result_embed.set_author( + name=interaction.user.name, + icon_url=interaction.user.avatar.url + ) if user_choice_index == bot_choice_index: result_embed.description = f"**That's a draw!**\nYou've chosen {user_choice} and I've chosen {bot_choice}." @@ -104,7 +106,7 @@ def __init__(self, bot): async def randomfact(self, context: Context) -> None: """ Get a random fact. - + :param context: The hybrid command context. """ # This will prevent your bot from stopping everything when doing a web request - see: https://discordpy.readthedocs.io/en/stable/faq.html#how-do-i-make-a-web-request @@ -132,7 +134,7 @@ async def randomfact(self, context: Context) -> None: async def coinflip(self, context: Context) -> None: """ Make a coin flip, but give your bet before. - + :param context: The hybrid command context. """ buttons = Choice() @@ -143,7 +145,6 @@ async def coinflip(self, context: Context) -> None: message = await context.send(embed=embed, view=buttons) await buttons.wait() # We wait for the user to click a button. result = random.choice(["heads", "tails"]) - print(buttons.value) if buttons.value == result: embed = discord.Embed( description=f"Correct! You guessed `{buttons.value}` and I flipped the coin to `{result}`.", @@ -164,7 +165,7 @@ async def coinflip(self, context: Context) -> None: async def rock_paper_scissors(self, context: Context) -> None: """ Play the rock paper scissors game against the bot. - + :param context: The hybrid command context. """ view = RockPaperScissorsView() diff --git a/cogs/general.py b/cogs/general.py index 8de84503..0de7e76a 100644 --- a/cogs/general.py +++ b/cogs/general.py @@ -3,7 +3,7 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ import platform diff --git a/cogs/moderation.py b/cogs/moderation.py index ab1e799e..5cc905ba 100644 --- a/cogs/moderation.py +++ b/cogs/moderation.py @@ -3,16 +3,14 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ import discord from discord import app_commands from discord.ext import commands from discord.ext.commands import Context - -from helpers import checks -from helpers import db_manager +from helpers import checks, db_manager class Moderation(commands.Cog, name="moderation"): @@ -112,7 +110,7 @@ async def nick(self, context: Context, user: discord.User, nickname: str = None) async def ban(self, context: Context, user: discord.User, reason: str = "Not specified") -> None: """ Bans a user from the server. - + :param context: The hybrid command context. :param user: The user that should be banned from the server. :param reason: The reason for the ban. Default is "Not specified". @@ -176,7 +174,8 @@ async def warning_add(self, context: Context, user: discord.User, reason: str = :param reason: The reason for the warn. Default is "Not specified". """ member = context.guild.get_member(user.id) or await context.guild.fetch_member(user.id) - total = db_manager.add_warn(user.id, context.guild.id, context.author.id, reason) + total = db_manager.add_warn( + user.id, context.guild.id, context.author.id, reason) embed = discord.Embed( title="User Warned!", description=f"**{member}** was warned by **{context.author}**!\nTotal warns for this user: {total}", @@ -227,13 +226,13 @@ async def warning_add(self, context: Context, user: discord.User, warn_id: int) async def warning_list(self, context: Context, user: discord.User): """ Shows the warnings of a user in the server. - + :param context: The hybrid command context. :param user: The user you want to get the warnings of. """ warnings_list = db_manager.get_warnings(user.id, context.guild.id) embed = discord.Embed( - title = f"Warnings of {user}", + title=f"Warnings of {user}", color=0x9C84EF ) description = "" diff --git a/cogs/owner.py b/cogs/owner.py index f6aef40b..c6b9ad0e 100644 --- a/cogs/owner.py +++ b/cogs/owner.py @@ -3,23 +3,195 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ import json +import os +import sys import discord +from discord import app_commands from discord.ext import commands from discord.ext.commands import Context - -from helpers import checks -from helpers import db_manager +from helpers import checks, db_manager class Owner(commands.Cog, name="owner"): def __init__(self, bot): self.bot = bot + @commands.command( + name="sync", + description="Synchonizes the slash commands.", + ) + @app_commands.describe(scope="The scope of the sync. Can be `global` or `guild`") + @checks.is_owner() + async def sync(self, context: Context, scope: str) -> None: + """ + Synchonizes the slash commands. + + :param context: The command context. + :param scope: The scope of the sync. Can be `global` or `guild`. + """ + + if scope == "global": + await context.bot.tree.sync() + embed = discord.Embed( + title="Slash Commands Sync", + description="Slash commands have been globally synchronized.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + elif scope == "guild": + context.bot.tree.copy_global_to(guild=context.guild) + await context.bot.tree.sync(guild=context.guild) + embed = discord.Embed( + title="Slash Commands Sync", + description="Slash commands have been synchronized in this guild.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Invalid Scope", + description="The scope must be `global` or `guild`.", + color=0xE02B2B + ) + await context.send(embed=embed) + + @commands.command( + name="unsync", + description="Unsynchonizes the slash commands.", + ) + @app_commands.describe(scope="The scope of the sync. Can be `global`, `current_guild` or `guild`") + @checks.is_owner() + async def unsync(self, context: Context, scope: str) -> None: + """ + Unsynchonizes the slash commands. + + :param context: The command context. + :param scope: The scope of the sync. Can be `global`, `current_guild` or `guild`. + """ + + if scope == "global": + context.bot.tree.clear_commands(guild=None) + await context.bot.tree.sync() + embed = discord.Embed( + title="Slash Commands Unsync", + description="Slash commands have been globally unsynchronized.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + elif scope == "guild": + context.bot.tree.clear_commands(guild=context.guild) + await context.bot.tree.sync(guild=context.guild) + embed = discord.Embed( + title="Slash Commands Unsync", + description="Slash commands have been unsynchronized in this guild.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Invalid Scope", + description="The scope must be `global` or `guild`.", + color=0xE02B2B + ) + await context.send(embed=embed) + + @commands.hybrid_command( + name="load", + description="Load a cog", + ) + @app_commands.describe(cog="The name of the cog to load") + @checks.is_owner() + async def load(self, context: Context, cog: str) -> None: + """ + The bot will load the given cog. + + :param context: The hybrid command context. + :param cog: The name of the cog to load. + """ + try: + await self.bot.load_extension(f"cogs.{cog}") + except Exception: + embed = discord.Embed( + title="Error!", + description=f"Could not load the `{cog}` cog.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Load", + description=f"Successfully loaded the `{cog}` cog.", + color=0x9C84EF + ) + await context.send(embed=embed) + + @commands.hybrid_command( + name="unload", + description="Unloads a cog.", + ) + @app_commands.describe(cog="The name of the cog to unload") + @checks.is_owner() + async def unload(self, context: Context, cog: str) -> None: + """ + The bot will unload the given cog. + + :param context: The hybrid command context. + :param cog: The name of the cog to unload. + """ + try: + await self.bot.unload_extension(f"cogs.{cog}") + except Exception: + embed = discord.Embed( + title="Error!", + description=f"Could not unload the `{cog}` cog.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Unload", + description=f"Successfully unloaded the `{cog}` cog.", + color=0x9C84EF + ) + await context.send(embed=embed) + + @commands.hybrid_command( + name="reload", + description="Reloads a cog.", + ) + @app_commands.describe(cog="The name of the cog to reload") + @checks.is_owner() + async def reload(self, context: Context, cog: str) -> None: + """ + The bot will reload the given cog. + + :param context: The hybrid command context. + :param cog: The name of the cog to reload. + """ + try: + await self.bot.reload_extension(f"cogs.{cog}") + except Exception: + embed = discord.Embed( + title="Error!", + description=f"Could not reload the `{cog}` cog.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Reload", + description=f"Successfully reloaded the `{cog}` cog.", + color=0x9C84EF + ) + await context.send(embed=embed) + @commands.hybrid_command( name="shutdown", description="Make the bot shutdown.", @@ -42,6 +214,7 @@ async def shutdown(self, context: Context) -> None: name="say", description="The bot will say anything you want.", ) + @app_commands.describe(message="The message that should be repeated by the bot") @checks.is_owner() async def say(self, context: Context, message: str) -> None: """ @@ -56,6 +229,7 @@ async def say(self, context: Context, message: str) -> None: name="embed", description="The bot will say anything you want, but within embeds.", ) + @app_commands.describe(message="The message that should be repeated by the bot") @checks.is_owner() async def embed(self, context: Context, message: str) -> None: """ @@ -88,6 +262,7 @@ async def blacklist(self, context: Context) -> None: name="add", description="Lets you add a user from not being able to use the bot.", ) + @app_commands.describe(user="The user that should be added to the blacklist") @checks.is_owner() async def blacklist_add(self, context: Context, user: discord.User) -> None: """ @@ -103,7 +278,8 @@ async def blacklist_add(self, context: Context, user: discord.User) -> None: description=f"**{user.name}** is not in the blacklist.", color=0xE02B2B ) - return await context.send(embed=embed) + await context.send(embed=embed) + return total = db_manager.add_user_to_blacklist(user_id) embed = discord.Embed( title="User Blacklisted", @@ -120,8 +296,9 @@ async def blacklist_add(self, context: Context, user: discord.User) -> None: name="remove", description="Lets you remove a user from not being able to use the bot.", ) + @app_commands.describe(user="The user that should be removed from the blacklist.") @checks.is_owner() - async def blacklist_remove(self, context: Context, user: discord.User): + async def blacklist_remove(self, context: Context, user: discord.User) -> None: """ Lets you remove a user from not being able to use the bot. @@ -135,7 +312,8 @@ async def blacklist_remove(self, context: Context, user: discord.User): description=f"**{user.name}** is already in the blacklist.", color=0xE02B2B ) - return await context.send(embed=embed) + await context.send(embed=embed) + return total = db_manager.remove_user_from_blacklist(user_id) embed = discord.Embed( title="User removed from blacklist", diff --git a/cogs/template.py b/cogs/template.py index 8c7c18c2..07171687 100644 --- a/cogs/template.py +++ b/cogs/template.py @@ -3,12 +3,11 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ from discord.ext import commands from discord.ext.commands import Context - from helpers import checks @@ -17,8 +16,8 @@ class Template(commands.Cog, name="template"): def __init__(self, bot): self.bot = bot - # Here you can just add your own commands, you'll always need to provide "self" as first parameter. + @commands.hybrid_command( name="testcommand", description="This is a testing command that does nothing.", diff --git a/exceptions/__init__.py b/exceptions/__init__.py index 9c9c3acd..f6e4a1d0 100644 --- a/exceptions/__init__.py +++ b/exceptions/__init__.py @@ -3,7 +3,7 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ from discord.ext import commands diff --git a/helpers/checks.py b/helpers/checks.py index 62cca157..df76c404 100644 --- a/helpers/checks.py +++ b/helpers/checks.py @@ -3,14 +3,13 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ import json -from typing import TypeVar, Callable +from typing import Callable, TypeVar from discord.ext import commands - from exceptions import * from helpers import db_manager diff --git a/helpers/db_manager.py b/helpers/db_manager.py index c4f5855f..08495220 100644 --- a/helpers/db_manager.py +++ b/helpers/db_manager.py @@ -3,15 +3,16 @@ Description: This is a template to create your own discord bot in python. -Version: 5.1 +Version: 5.2 """ import sqlite3 + def is_blacklisted(user_id: int) -> bool: """ This function will check if a user is blacklisted. - + :param user_id: The ID of the user that should be checked. :return: True if the user is blacklisted, False if not. """ @@ -26,7 +27,7 @@ def is_blacklisted(user_id: int) -> bool: def add_user_to_blacklist(user_id: int) -> int: """ This function will add a user based on its ID in the blacklist. - + :param user_id: The ID of the user that should be added into the blacklist. """ connection = sqlite3.connect("database/database.db") @@ -41,7 +42,7 @@ def add_user_to_blacklist(user_id: int) -> int: def remove_user_from_blacklist(user_id: int) -> int: """ This function will remove a user based on its ID from the blacklist. - + :param user_id: The ID of the user that should be removed from the blacklist. """ connection = sqlite3.connect("database/database.db") @@ -56,18 +57,21 @@ def remove_user_from_blacklist(user_id: int) -> int: def add_warn(user_id: int, server_id: int, moderator_id: int, reason: str) -> int: """ This function will add a warn to the database. - + :param user_id: The ID of the user that should be warned. :param reason: The reason why the user should be warned. """ connection = sqlite3.connect("database/database.db") cursor = connection.cursor() # Get the last `id` - rows = cursor.execute("SELECT id FROM warns WHERE user_id=? AND server_id=? ORDER BY id DESC LIMIT 1", (user_id, server_id,)).fetchone() + rows = cursor.execute( + "SELECT id FROM warns WHERE user_id=? AND server_id=? ORDER BY id DESC LIMIT 1", (user_id, server_id,)).fetchone() warn_id = rows[0]+1 if rows is not None else 1 - cursor.execute("INSERT INTO warns(id, user_id, server_id, moderator_id, reason) VALUES (?, ?, ?, ?, ?)", (warn_id, user_id, server_id, moderator_id, reason,)) + cursor.execute("INSERT INTO warns(id, user_id, server_id, moderator_id, reason) VALUES (?, ?, ?, ?, ?)", + (warn_id, user_id, server_id, moderator_id, reason,)) connection.commit() - rows = cursor.execute("SELECT COUNT(*) FROM warns WHERE user_id=? AND server_id=?", (user_id, server_id,)).fetchone()[0] + rows = cursor.execute( + "SELECT COUNT(*) FROM warns WHERE user_id=? AND server_id=?", (user_id, server_id,)).fetchone()[0] connection.close() return rows @@ -75,23 +79,26 @@ def add_warn(user_id: int, server_id: int, moderator_id: int, reason: str) -> in def remove_warn(warn_id: int, user_id: int, server_id: int) -> int: """ This function will remove a warn from the database. - + :param warn_id: The ID of the warn. :param user_id: The ID of the user that was warned. :param server_id: The ID of the server where the user has been warned """ connection = sqlite3.connect("database/database.db") cursor = connection.cursor() - cursor.execute("DELETE FROM warns WHERE id=? AND user_id=? AND server_id=?", (warn_id, user_id, server_id,)) + cursor.execute("DELETE FROM warns WHERE id=? AND user_id=? AND server_id=?", + (warn_id, user_id, server_id,)) connection.commit() - rows = cursor.execute("SELECT COUNT(*) FROM warns WHERE user_id=? AND server_id=?", (user_id, server_id,)).fetchone()[0] + rows = cursor.execute( + "SELECT COUNT(*) FROM warns WHERE user_id=? AND server_id=?", (user_id, server_id,)).fetchone()[0] connection.close() return rows + def get_warnings(user_id: int, server_id: int) -> list: """ This function will get all the warnings of a user. - + :param user_id: The ID of the user that should be checked. :param server_id: The ID of the server that should be checked. :return: A list of all the warnings of the user. @@ -101,4 +108,4 @@ def get_warnings(user_id: int, server_id: int) -> list: cursor.execute("SELECT user_id, server_id, moderator_id, reason, strftime('%s', created_at), id FROM warns WHERE user_id=? AND server_id=?", (user_id, server_id,)) result = cursor.fetchall() connection.close() - return result \ No newline at end of file + return result