From 8178a602fd2619e159e63640c9756a63c98d6eb3 Mon Sep 17 00:00:00 2001 From: Shadow15510 Date: Mon, 12 Jun 2023 09:50:59 +0200 Subject: [PATCH] new feature: add modules of commands instead of list of commands --- fun_cmnds.py | 13 ++++++ glados_cmnds.py | 31 +++++---------- irc_api/api.py | 99 +++++++++++++++++++++++++++++++++++---------- irc_api/irc.py | 104 ++++++++++++++++++++++-------------------------- main.py | 13 +++--- 5 files changed, 155 insertions(+), 105 deletions(-) create mode 100644 fun_cmnds.py diff --git a/fun_cmnds.py b/fun_cmnds.py new file mode 100644 index 0000000..9af762c --- /dev/null +++ b/fun_cmnds.py @@ -0,0 +1,13 @@ +from irc_api import api +from irc_api.api import auto_help + + +@api.channel("#glados") +@api.on(lambda m: "blague" in m.text.lower()) +def fun(bot, msg, *args): + bot.send(msg.to, "Mais je suis nuuuullle en blagues euh.") + + +@api.command("limite") +def history_limit(bot, msg, *args): + bot.send(msg.to, f"limite historique : {bot.history._History__limit}") \ No newline at end of file diff --git a/glados_cmnds.py b/glados_cmnds.py index 1963f7b..c3c1e39 100644 --- a/glados_cmnds.py +++ b/glados_cmnds.py @@ -1,5 +1,5 @@ from irc_api import api -from random import choice +from irc_api.api import auto_help @api.on(lambda m: "au revoir glados" in m.text.lower()) @@ -14,8 +14,7 @@ def greetings_bye(bot, msg, *args): "#glados." ) def greetings_hello(bot, msg, *args): - bot.send(msg.to, args) - bot.send(msg.to, f"Oh un utilisateur sur #glados :o") + bot.send(msg.to, f"Bonjour à toi") @api.command("test") @@ -26,22 +25,12 @@ def test(bot, msg, *args): @api.channel("#glados") def react_on_glados(bot, msg, *args): - """Répond de manière random à tout les messages posté sur #glados.""" - answers = ( - f"Yo {msg.author} o/", - "Alors, de quoi on parle ?", - "Ça va ?", - "Bon, tu as fini de spammer ou bien ?", - "Ça roule ?", - "Hey you! Ah, you finally awake…", - ) - bot.send(msg.to, choice(answers)) + """Répète le dernier message envoyé sur #glados.""" + if bot.history.get()[-1].author == msg.author: + bot.counter += 1 + else: + bot.counter = 1 - -commands = [ - api.auto_help, - greetings_bye, - greetings_hello, - test, - # react_on_glados - ] \ No newline at end of file + if bot.counter > 3: + bot.send(msg.to, f"Bon, {msg.author}, tu vas arrêter de spammer un jour ou bien ?") + bot.counter = 0 diff --git a/irc_api/api.py b/irc_api/api.py index 0fca241..e64beb5 100644 --- a/irc_api/api.py +++ b/irc_api/api.py @@ -1,5 +1,6 @@ import logging -from irc_api.irc import IRC +import re +from irc_api.irc import IRC, History PREFIX = "" @@ -83,7 +84,14 @@ class Bot: start : NoneType, public Runs the bot and connects it to IRC and V5 servers. """ - def __init__(self, auth: tuple, irc_params: tuple, channels: list, prefix: str=""): + def __init__( + self, + auth: tuple, + irc_params: tuple, + channels: list=["#general"], + *commands_modules, + **kwargs + ): """Initialize the Bot instance. Parameters @@ -96,50 +104,97 @@ class Bot: The prefix on which the bot will react. """ global PREFIX - PREFIX = prefix + if kwargs.get('prefix'): + PREFIX = kwargs.get('prefix') - self.auth = auth self.irc = IRC(*irc_params) + self.history = History(kwargs.get('limit')) self.channels = channels + self.auth = auth + self.callbacks = {} - self.send = self.irc.send + if commands_modules: + self.add_commands_modules(*commands_modules) def start(self): """Starts the bot and connect it to the given IRC and V5 servers.""" # Start IRC - self.irc.start(self.auth[0], self.auth[1]) + self.irc.connexion(self.auth[0], self.auth[1]) # Join channels for channel in self.channels: self.irc.join(channel) - # Run IRC - self.irc.run() + # mainloop + while True: + message = self.irc.receive() + self.history.add(message) + logging.info("received %s", message) + if message is not None: + for callback in self.callbacks.values(): + if not False in [event(message) for event in callback.events]: + logging.info("matched %s", callback.name) + callback(message, *parse(message.text)[callback.cmnd_type:]) - def add_command(self, command): - command.bot = self - self.irc.callbacks.append(command) - - def add_commands(self, commands): - """Add a package of commands to the bot. + def send(self, target: str, message: str): + """Send a message to the specified target (channel or user). Parameters ---------- - commands_pack : CommandsPack - A commands pack which contains command's instances. + target : str + The target of the message. It can be a channel or user (private message). + message : str + The content of the message to send. + """ + self.irc.send(f"PRIVMSG {target} :{message}") + + def add_command(self, command): + command.bot = self + self.callbacks[command.name] = command + + def add_commands(self, *commands): + """Add a list of commands to the bot. + + Parameters + ---------- + commands : list + A list of command's instances. """ for command in commands: self.add_command(command) + def add_commands_modules(self, *commands_modules): + for commands_module in commands_modules: + for cmnd_name in dir(commands_module): + cmnd = getattr(commands_module, cmnd_name) + if isinstance(cmnd, Command): + self.add_command(cmnd) + + def remove_command(self, command_name: str): + if command_name in self.callbacks.keys(): + self.callbacks.pop(command_name) + @command("aide") def auto_help(bot, msg, *args): """Aide des commandes disponibles.""" - known_cmnds = {cmnd.name: cmnd for cmnd in bot.irc.callbacks} - if args and args[0] in known_cmnds.keys(): + if args and args[0] in bot.callbacks.keys(): bot.send(msg.to, f"Aide sur la commande : {args[0]}") - bot.send(msg.to, f" {known_cmnds[args[0]].desc}") + bot.send(msg.to, f" {bot.callbacks[args[0]].desc}") else: - bot.irc.send(msg.to, f"Liste des commandes ({PREFIX}aide pour plus d'info)") - for cmnd in bot.irc.callbacks: - bot.irc.send(msg.to, f" – {cmnd.name}") + bot.send(msg.to, f"Liste des commandes ({PREFIX}aide pour plus d'info)") + for cmnd_name in bot.callbacks.keys(): + bot.send(msg.to, f" – {cmnd_name}") + + +def parse(message): + pattern = re.compile(r"((\"[\w\ \-\']+\"\ *)|(\'[\w\ \-\"]+\'\ *)|([\w\'\-]+\ *))") + args_to_return = [] + for match in re.findall(pattern, message): + match = match[0].strip().rstrip() + if (match.startswith("\"") and match.endswith("\"")) \ + or (match.startswith("'") and match.endswith("'")): + args_to_return.append(match[1: -1]) + else: + args_to_return.append(match) + return args_to_return diff --git a/irc_api/irc.py b/irc_api/irc.py index 0bbb4cf..f0df385 100644 --- a/irc_api/irc.py +++ b/irc_api/irc.py @@ -70,8 +70,6 @@ class IRC: # Public attributes self.connected = False # Simple lock - self.callbacks = [] - # Private attributes self.__socket = ssl.create_default_context().wrap_socket( @@ -82,7 +80,7 @@ class IRC: self.__handler = Thread(target=self.__handle) # Public methods - def start(self, nick: str, password: str): + def connexion(self, nick: str, password: str): """Start the IRC layer. Manage authentication as well. Parameters @@ -94,37 +92,14 @@ class IRC: """ self.__handler.start() - self.__send(f"USER {nick} * * :{nick}") - self.__send(f"NICK {nick}") - self.__waitfor(lambda m: "NOTICE" in m and "/AUTH" in m) - self.__send(f"AUTH {nick}:{password}") - self.__waitfor(lambda m: "You are now logged in" in m) + self.send(f"USER {nick} * * :{nick}") + self.send(f"NICK {nick}") + self.waitfor(lambda m: "NOTICE" in m and "/AUTH" in m) + self.send(f"AUTH {nick}:{password}") + self.waitfor(lambda m: "You are now logged in" in m) self.connected = True - def run(self): - """Handle new messages.""" - while True: - message = self.receive() - logging.info("received %s", message) - if message is not None: - for callback in self.callbacks: - if not False in [event(message) for event in callback.events]: - logging.info("matched %s", callback.name) - callback(message, *parse(message.text)[callback.cmnd_type:]) - - def send(self, target: str, message: str): - """Send a message to the specified target (channel or user). - - Parameters - ---------- - target : str - The target of the message. It can be a channel or user (private message). - message : str - The content of the message to send. - """ - self.__send(f"PRIVMSG {target} :{message}") - def receive(self): """Receive a private message. @@ -148,28 +123,10 @@ class IRC: channel : str The name of the channel to join. """ - self.__send(f"JOIN {channel}") + self.send(f"JOIN {channel}") logging.info("joined %s", channel) - # Private methods - def __handle(self): - """Handle raw messages from irc and manage ping.""" - while True: - # Get incoming messages - data = self.__socket.recv(4096).decode() - - # Split multiple lines - for msg in data.split('\r\n'): - # Manage ping - if msg.startswith("PING"): - self.__send(msg.replace("PING", "PONG")) - - # Or add a new message to inbox - elif len(msg): - self.__inbox.put(msg) - logging.debug("received %s", msg) - - def __send(self, raw: str): + def send(self, raw: str): """Wrap and encode raw message to send. Parameters @@ -179,7 +136,7 @@ class IRC: """ self.__socket.send(f"{raw}\r\n".encode()) - def __waitfor(self, condition): + def waitfor(self, condition): """Wait for a raw message that matches the condition. Parameters @@ -198,6 +155,44 @@ class IRC: msg = self.__inbox.get() return msg + # Private methods + def __handle(self): + """Handle raw messages from irc and manage ping.""" + while True: + # Get incoming messages + data = self.__socket.recv(4096).decode() + + # Split multiple lines + for msg in data.split('\r\n'): + # Manage ping + if msg.startswith("PING"): + self.send(msg.replace("PING", "PONG")) + + # Or add a new message to inbox + elif len(msg): + self.__inbox.put(msg) + logging.debug("received %s", msg) + + +class History: + def __init__(self, limit: int=100): + self.__content = [] + if limit: + self.__limit = limit + else: + self.__limit = 100 + + def __len__(self): + return len(self.__content) + + def add(self, elmnt): + if len(self.__content) == self.__limit: + self.__content.pop(0) + self.__content.append(elmnt) + + def get(self): + return self.__content + class Message: """Parse the raw message in three fields : author, the channel, and text. @@ -232,8 +227,3 @@ class Message: def __str__(self): return f"{self.author} to {self.to}: {self.text}" - - -def parse(message): - pattern = re.compile(r"(((\"|\')[\w\ \-]+(\"|\')\ *)|([\w\'\-]+\ *))") - return [match[0].strip("\"' ").rstrip("\"' ") for match in re.findall(pattern, message)] diff --git a/main.py b/main.py index b2b3e3d..7117e20 100755 --- a/main.py +++ b/main.py @@ -13,7 +13,8 @@ import re from irc_api import api -from glados_cmnds import commands +import glados_cmnds as gcmnds +import fun_cmnds as fcmnds from secrets import USER, PASSWORD @@ -21,14 +22,16 @@ LOG_FORMAT = "%(asctime)s [%(levelname)s] <%(filename)s> %(funcName)s: %(message logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG) -glados = api.Bot( +class MyBot(api.Bot): + counter = 1 + +glados = MyBot( (USER, PASSWORD), ('irc.planet-casio.com', 6697), ["#general", "#glados"], - "!" + gcmnds, fcmnds, + prefix="!", ) -glados.add_commands(commands) - glados.start()