mirror of
				https://gitlab.com/ddorn/tfjm-discord-bot.git
				synced 2025-10-31 00:59:51 +01:00 
			
		
		
		
	✨ CustomBot with full reload
This commit is contained in:
		| @@ -1,4 +1,6 @@ | ||||
| import code | ||||
| import sys | ||||
| from importlib import reload | ||||
| from pprint import pprint | ||||
|  | ||||
| import discord | ||||
| @@ -8,13 +10,20 @@ from discord.ext.commands import Cog | ||||
| from discord.utils import get | ||||
|  | ||||
| from src.constants import * | ||||
| from src.core import CustomBot | ||||
|  | ||||
|  | ||||
| COGS_SHORTCUTS = {"d": "dev", "ts": "teams", "t": "tirages", "m": "misc", "e": "errors"} | ||||
| COGS_SHORTCUTS = { | ||||
|     "d": "tirages", | ||||
|     "e": "errors", | ||||
|     "m": "misc", | ||||
|     "t": "teams", | ||||
|     "u": "src.utils", | ||||
|     "v": "dev", | ||||
| } | ||||
|  | ||||
|  | ||||
| class DevCog(Cog, name="Dev tools"): | ||||
|     def __init__(self, bot: Bot): | ||||
|     def __init__(self, bot: CustomBot): | ||||
|         self.bot = bot | ||||
|  | ||||
|     @command(name="interrupt") | ||||
| @@ -69,6 +78,11 @@ class DevCog(Cog, name="Dev tools"): | ||||
|         possibles: `teams`, `tirages`, `dev`. | ||||
|         """ | ||||
|  | ||||
|         if name is None: | ||||
|             self.bot.reload() | ||||
|             await ctx.send(":tada: The bot was reloaded !") | ||||
|             return | ||||
|  | ||||
|         names = [name] if name else list(COGS_SHORTCUTS.values()) | ||||
|  | ||||
|         for name in names: | ||||
|   | ||||
| @@ -5,8 +5,8 @@ import discord | ||||
| from discord.ext.commands import * | ||||
| from discord.utils import maybe_coroutine | ||||
|  | ||||
| from src.errors import UnwantedCommand | ||||
|  | ||||
| from src.core import CustomBot | ||||
| from src.errors import UnwantedCommand, TfjmError | ||||
|  | ||||
| # Global variable and function because I'm too lazy to make a metaclass | ||||
| handlers = {} | ||||
| @@ -29,6 +29,9 @@ def handles(error_type): | ||||
| class ErrorsCog(Cog): | ||||
|     """This cog defines all the handles for errors.""" | ||||
|  | ||||
|     def __init__(self, bot: CustomBot): | ||||
|         self.bot = bot | ||||
|  | ||||
|     @Cog.listener() | ||||
|     async def on_command_error(self, ctx: Context, error: CommandError): | ||||
|         print(repr(error), file=sys.stderr) | ||||
| @@ -47,10 +50,11 @@ class ErrorsCog(Cog): | ||||
|             msg = await maybe_coroutine(handler, self, ctx, error) | ||||
|  | ||||
|         if msg: | ||||
|             await ctx.send(msg) | ||||
|             message = await ctx.send(msg) | ||||
|             await self.bot.wait_for_bin(ctx.message.author, message) | ||||
|  | ||||
|     @handles(UnwantedCommand) | ||||
|     async def on_unwanted_command(self, ctx, error): | ||||
|     async def on_unwanted_command(self, ctx, error: UnwantedCommand): | ||||
|         await ctx.message.delete() | ||||
|         author: discord.Message | ||||
|         await ctx.author.send( | ||||
| @@ -59,14 +63,19 @@ class ErrorsCog(Cog): | ||||
|             + "\nC'est pas grave, c'est juste pour ne pas encombrer " | ||||
|             "le chat lors du tirage." | ||||
|         ) | ||||
|         await ctx.author.send("Raison: " + error.original.msg) | ||||
|         await ctx.author.send("Raison: " + error.msg) | ||||
|  | ||||
|     @handles(TfjmError) | ||||
|     async def on_tfjm_error(self, ctx: Context, error: TfjmError): | ||||
|         msg = await ctx.send(error.msg) | ||||
|         await self.bot.wait_for_bin(ctx.author, msg) | ||||
|  | ||||
|     @handles(CommandInvokeError) | ||||
|     async def on_command_invoke_error(self, ctx, error): | ||||
|         specific_handler = handlers.get(type(error.original)) | ||||
|  | ||||
|         if specific_handler: | ||||
|             return await specific_handler(self, ctx, error) | ||||
|             return await specific_handler(self, ctx, error.original) | ||||
|  | ||||
|         traceback.print_tb(error.original.__traceback__, file=sys.stderr) | ||||
|         return ( | ||||
| @@ -91,4 +100,4 @@ class ErrorsCog(Cog): | ||||
|  | ||||
|  | ||||
| def setup(bot): | ||||
|     bot.add_cog(ErrorsCog()) | ||||
|     bot.add_cog(ErrorsCog(bot)) | ||||
|   | ||||
| @@ -17,13 +17,15 @@ from discord.ext.commands import ( | ||||
|     Group, | ||||
| ) | ||||
|  | ||||
| from src import utils | ||||
| from src.constants import * | ||||
| from src.constants import Emoji | ||||
| from src.utils import has_role | ||||
| from src.core import CustomBot | ||||
| from src.utils import has_role, start_time | ||||
|  | ||||
|  | ||||
| class MiscCog(Cog, name="Divers"): | ||||
|     def __init__(self, bot: Bot): | ||||
|     def __init__(self, bot: CustomBot): | ||||
|         self.bot = bot | ||||
|         self.show_hidden = False | ||||
|         self.verify_checks = True | ||||
| @@ -55,6 +57,7 @@ class MiscCog(Cog, name="Divers"): | ||||
|  | ||||
|         await message.add_reaction(Emoji.JOY) | ||||
|         await message.add_reaction(Emoji.SOB) | ||||
|         await self.bot.wait_for_bin(ctx.message.author, message) | ||||
|  | ||||
|     @command(name="status") | ||||
|     @commands.has_role(Role.CNO) | ||||
| @@ -65,7 +68,7 @@ class MiscCog(Cog, name="Divers"): | ||||
|         benevoles = [g for g in guild.members if has_role(g, Role.BENEVOLE)] | ||||
|         participants = [g for g in guild.members if has_role(g, Role.PARTICIPANT)] | ||||
|         no_role = [g for g in guild.members if g.top_role == guild.default_role] | ||||
|         uptime = datetime.timedelta(seconds=round(time() - START_TIME)) | ||||
|         uptime = datetime.timedelta(seconds=round(time() - start_time())) | ||||
|  | ||||
|         infos = { | ||||
|             "Bénévoles": len(benevoles), | ||||
|   | ||||
| @@ -7,13 +7,14 @@ from discord.ext.commands import Cog, Bot, group, Context | ||||
| from discord.utils import get, find | ||||
|  | ||||
| from src.constants import * | ||||
| from src.core import CustomBot | ||||
| from src.utils import has_role | ||||
|  | ||||
| Team = namedtuple("Team", ["name", "trigram", "tournoi", "secret", "status"]) | ||||
|  | ||||
|  | ||||
| class TeamsCog(Cog, name="Teams"): | ||||
|     def __init__(self, bot: Bot): | ||||
|     def __init__(self, bot: CustomBot): | ||||
|         self.bot = bot | ||||
|         self.teams = self.load_teams() | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,7 @@ from discord.ext.commands import group, Cog, Context | ||||
| from discord.utils import get | ||||
|  | ||||
| from src.constants import * | ||||
| from src.core import CustomBot | ||||
| from src.errors import TfjmError, UnwantedCommand | ||||
|  | ||||
| __all__ = ["Tirage", "TirageCog"] | ||||
| @@ -606,7 +607,7 @@ class TirageOrderPhase(OrderPhase): | ||||
|  | ||||
| class TirageCog(Cog, name="Tirages"): | ||||
|     def __init__(self, bot): | ||||
|         self.bot: commands.Bot = bot | ||||
|         self.bot: CustomBot = bot | ||||
|  | ||||
|         # We retrieve the global variable. | ||||
|         # We don't want tirages to be ust an attribute | ||||
| @@ -627,13 +628,21 @@ class TirageCog(Cog, name="Tirages"): | ||||
|         if channel in self.tirages: | ||||
|             await self.tirages[channel].dice(ctx, n) | ||||
|         else: | ||||
|             if n == 0: | ||||
|                 raise TfjmError(f"Un dé sans faces ? Le concept m'intéresse...") | ||||
|             if n < 1: | ||||
|                 raise TfjmError(f"Je ne peux pas lancer un dé à {n} faces, désolé.") | ||||
|                 raise TfjmError( | ||||
|                     f"Je ne peux pas lancer un dé avec un " | ||||
|                     f"nombre négatif faces, désolé." | ||||
|                 ) | ||||
|             if len(str(n)) > 1900: | ||||
|                 raise TfjmError( | ||||
|                     "Oulà... Je sais que la taille ça ne compte pas, " | ||||
|                     "mais là il est vraiment gros ton dé !" | ||||
|                 ) | ||||
|  | ||||
|             dice = random.randint(1, n) | ||||
|             await ctx.send( | ||||
|                 f"Le dé à {n} face{'s' * (n > 1)} s'est arrêté sur... **{dice}**" | ||||
|             ) | ||||
|             await ctx.send(f"{ctx.author.mention} : {Emoji.DICE} {dice}") | ||||
|  | ||||
|     @commands.command( | ||||
|         name="random-problem", | ||||
|   | ||||
| @@ -59,6 +59,8 @@ class Role: | ||||
| class Emoji: | ||||
|     JOY = "😂" | ||||
|     SOB = "😭" | ||||
|     BIN = "🗑️" | ||||
|     DICE = "🎲" | ||||
|  | ||||
|  | ||||
| class File: | ||||
| @@ -71,3 +73,8 @@ class File: | ||||
| with open(File.TOP_LEVEL / "data" / "problems") as f: | ||||
|     PROBLEMS = f.read().splitlines() | ||||
| MAX_REFUSE = len(PROBLEMS) - 4  # -5 usually but not in 2020 because of covid-19 | ||||
|  | ||||
|  | ||||
| def setup(bot): | ||||
|     # Just so we can reload the constants | ||||
|     pass | ||||
|   | ||||
							
								
								
									
										72
									
								
								src/core.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/core.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| import asyncio | ||||
| import sys | ||||
| from importlib import reload | ||||
|  | ||||
| import psutil | ||||
| from discord import User, Message, Reaction | ||||
| from discord.ext.commands import Bot | ||||
|  | ||||
|  | ||||
| __all__ = ["CustomBot"] | ||||
|  | ||||
| from discord.utils import get | ||||
|  | ||||
| from src.constants import Emoji | ||||
|  | ||||
|  | ||||
| class CustomBot(Bot): | ||||
|     """ | ||||
|     This is the same as a discord bot except | ||||
|     for class reloading and it provides hints | ||||
|     for the type checker about the modules | ||||
|     that are added by extensions. | ||||
|     """ | ||||
|  | ||||
|     def __str__(self): | ||||
|         return f"{self.__class__.__name__}:{hex(id(self.__class__))} obj at {hex(id(self))}" | ||||
|  | ||||
|     def reload(self): | ||||
|         cls = self.__class__ | ||||
|         module_name = cls.__module__ | ||||
|         old_module = sys.modules[module_name] | ||||
|  | ||||
|         print("Trying to reload the bot.") | ||||
|         try: | ||||
|             # del sys.modules[module_name] | ||||
|             module = reload(old_module) | ||||
|             self.__class__ = getattr(module, cls.__name__, cls) | ||||
|         except: | ||||
|             print("Could not reload the bot :/") | ||||
|             raise | ||||
|         print("The bot has reloaded !") | ||||
|  | ||||
|     async def wait_for_bin(bot: Bot, user: User, *msgs: Message, timeout=300): | ||||
|         """Wait for timeout seconds for `user` to delete the messages.""" | ||||
|  | ||||
|         msgs = list(msgs) | ||||
|  | ||||
|         assert msgs, "No messages in wait_for_bin" | ||||
|  | ||||
|         for m in msgs: | ||||
|             await m.add_reaction(Emoji.BIN) | ||||
|  | ||||
|         def check(reaction: Reaction, u): | ||||
|             return ( | ||||
|                 user == u | ||||
|                 and any(m.id == reaction.message.id for m in msgs) | ||||
|                 and str(reaction.emoji) == Emoji.BIN | ||||
|             ) | ||||
|  | ||||
|         try: | ||||
|             while msgs: | ||||
|                 reaction, u = await bot.wait_for( | ||||
|                     "reaction_add", check=check, timeout=timeout | ||||
|                 ) | ||||
|                 the_msg = get(msgs, id=reaction.message.id) | ||||
|                 await the_msg.delete() | ||||
|                 msgs.remove(the_msg) | ||||
|         except asyncio.TimeoutError: | ||||
|             pass | ||||
|  | ||||
|         for m in msgs: | ||||
|             await m.clear_reaction(Emoji.BIN) | ||||
| @@ -1,12 +1,13 @@ | ||||
| #!/bin/python | ||||
|  | ||||
| from discord.ext import commands | ||||
|  | ||||
| from src.constants import * | ||||
|  | ||||
|  | ||||
| # We allow "! " to catch people that put a space in their commands. | ||||
| # It must be in first otherwise "!" always match first and the space is not recognised | ||||
| bot = commands.Bot(("! ", "!")) | ||||
| from src.core import CustomBot | ||||
|  | ||||
| bot = CustomBot(("! ", "!")) | ||||
|  | ||||
| # Global variable to hold the tirages. | ||||
| # We *want* it to be global so we can reload the tirages cog without | ||||
| @@ -25,6 +26,7 @@ bot.load_extension("src.cogs.errors") | ||||
| bot.load_extension("src.cogs.misc") | ||||
| bot.load_extension("src.cogs.teams") | ||||
| bot.load_extension("src.cogs.tirages") | ||||
| bot.load_extension("src.utils") | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|   | ||||
							
								
								
									
										22
									
								
								src/utils.py
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								src/utils.py
									
									
									
									
									
								
							| @@ -1,6 +1,12 @@ | ||||
| import asyncio | ||||
| from typing import Sequence | ||||
|  | ||||
| import psutil | ||||
| from discord import Message | ||||
| from discord import Message, Member, User, Reaction | ||||
| from discord.ext.commands import Context, Bot | ||||
| from discord.utils import get | ||||
|  | ||||
| from src.constants import Emoji | ||||
|  | ||||
|  | ||||
| def has_role(member, role: str): | ||||
| @@ -9,17 +15,9 @@ def has_role(member, role: str): | ||||
|     return any(r.name == role for r in member.roles) | ||||
|  | ||||
|  | ||||
| async def send_and_bin(bot: Bot, ctx: Context, msg=None, *, embed=None): | ||||
|     """Send a message and wait 5min for the author to delete it.""" | ||||
|  | ||||
|     message: Message = await ctx.send(msg, embed=embed) | ||||
|  | ||||
|     await msg | ||||
|  | ||||
|  | ||||
| def start_time(): | ||||
| def start_time(self): | ||||
|     return psutil.Process().create_time() | ||||
|  | ||||
|  | ||||
| def setup(bot): | ||||
|     bot.send_and_bin = send_and_bin | ||||
| def setup(bot: Bot): | ||||
|     pass | ||||
|   | ||||
		Reference in New Issue
	
	Block a user