diff --git a/bot.py b/bot.py deleted file mode 100644 index 8ff3035..0000000 --- a/bot.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Bot (GLaDOS) -============ - -Description ------------ -Allow to make and run a Bot instance which will communicate to the V5 server and IRC one. -""" - -from secrets import USER, PASSWORD -from irc import IRC -from v5 import V5 - - -class Bot: - """Run the connexion between IRC's server and V5 one. - - Attributes - ---------- - irc : IRC, public - IRC wrapper which handle communication with IRC server. - v5 : V5, public - V5 wrapper which handle communication with V5 server. - channels : list, public - The channels the bot will listen. - - Methods - ------- - start : NoneType, public - Runs the bot and connects it to IRC and V5 servers. - """ - def __init__(self, irc_params: tuple, v5_params: tuple, channels: list): - """Initialize the Bot instance. - - Parameters - ---------- - irc_params : tuple - Contains the IRC server informations (host, port) - v5_params : tuple - Contains the V5 server informations (host, port) - channels : list - Contains the names of the channels on which the bot will connect. - """ - self.irc = IRC(*irc_params) - self.channels = channels - self.v5 = V5(v5_params, self.irc) - - def start(self): - """Starts the bot and connect it to the given IRC and V5 servers.""" - # Start IRC - self.irc.start(USER, PASSWORD) - - # Join channels - for channel in self.channels: - self.irc.join(channel) - - # Start v5 handler - self.v5.start() - - # Run IRC - self.irc.run() diff --git a/irc.py b/irc.py deleted file mode 100644 index 39cc974..0000000 --- a/irc.py +++ /dev/null @@ -1,268 +0,0 @@ -""" -irc (GLaDOS) -============ - -Description ------------ -Manage the IRC layer of GLaDOS. -""" - -import logging -import re -import socket -import ssl - -from functools import wraps -from queue import Queue -from threading import Thread - - -class IRC: - """Manage connexion to an IRC server, authentication and callbacks. - - Attributes - ---------- - connected : bool, public - If the bot is connected to an IRC server or not. - - socket : ssl.SSLSocket, private - The IRC's socket. - inbox : Queue, private - Queue of the incomming messages. - handler : Thread, private - callbacks : list, private - List of the registred callbacks. - - Methods - ------- - start : NoneType, public - Starts the IRC layer and manage authentication. - run : NoneType, public - Mainloop, allows to handle public messages. - send : NoneType, public - Sends a message to a given channel. - receive : Message, public - Same as ``run`` for private messages. - join : NoneType, public - Allows to join a given channel. - on : function, public - Add a callback on a given message. - - handle : NoneType, private - Handles the ping and store incoming messages into the inbox attribute. - send : NoneType, private - Send message to a target. - recv : str, private - Get the oldest incoming message and returns it. - waitfor : str, private - Wait for a raw message that matches the given condition. - """ - def __init__(self, host: str, port: int): - """Initialize an IRC wrapper. - - Parameters - ---------- - host : str - The adress of the IRC server. - port : int - The port of the IRC server. - """ - - # Public attributes - self.connected = False # Simple lock - - # Private attributes - self.__socket = ssl.create_default_context().wrap_socket( - socket.create_connection((host, port)), - server_hostname=host - ) - self.__inbox = Queue() - self.__handler = Thread(target=self.__handle) - self.__callbacks = [] - - # Public methods - def start(self, nick: str, password: str): - """Start the IRC layer. Manage authentication as well. - - Parameters - ---------- - nick : str - The username for login and nickname once connected. - password : str - The password for authentification. - """ - 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.connected = True - - def run(self): - """Handle new messages.""" - while True: - message = self.receive() - logging.info("received %s", message) - if message is not None: - for event, callback in self.__callbacks: - if event(message): - logging.info("matched %s", event.__name__) - callback(message) - - 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. - - Returns - ------- - msg : Message - The incoming processed private message. - """ - while True: - message = self.__recv() - if " PRIVMSG " in message: - msg = Message(message) - if msg: - return msg - - def join(self, channel: str): - """Join a channel. - - Parameters - ---------- - channel : str - The name of the channel to join. - """ - self.__send(f"JOIN {channel}") - logging.info("joined %s", channel) - - def on(self, event): - """Adds a callback to the IRC handler. - - Parameters - ---------- - event : function - ``event`` must taking in parameter a Message and returning ``True`` if the callback - should be executed on the message. - - Returns - ------- - callback : function - The callback function. - """ - def callback(func): - @wraps(func) - def wrapper(message): - func(message) - self.__callbacks.append((event, wrapper)) - logging.info("added callback %s", func.__name__) - return wrapper - - return callback - - - # 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): - """Wrap and encode raw message to send. - - Parameters - ---------- - raw : str - The raw message to send. - """ - self.__socket.send(f"{raw}\r\n".encode()) - - def __recv(self): - """Get the older received message and returns it. - - Returns - ------- - m : str - The raw content of the message to return. - """ - msg = self.__inbox.get() - return msg - - def __waitfor(self, condition): - """Wait for a raw message that matches the condition. - - Parameters - ---------- - condition : function - ``condition`` is a function that must taking a raw message in parameter and returns a - boolean. - - Returns - ------- - msg : str - The last message received that doesn't match the condition. - """ - msg = self.__recv() - while not condition(msg): - msg = self.__recv() - return msg - - -class Message: - """Parse the raw message in three fields : author, the channel, and text. - - Attributes - ---------- - pattern : re.Pattern, public - The message parsing pattern. - author : str, public - The message's author. - to : str, public - The message's origin (channel or DM). - text : str, public - The message's content. - """ - pattern = re.compile( - r"^:(?P[\w.~|\-\[\]]+)(?:!(?P\S+))? PRIVMSG (?P\S+) :(?P.+)" - ) - - def __init__(self, raw: str): - match = re.search(Message.pattern, raw) - if match: - self.author = match.group("author") - self.to = match.group("to") - self.text = match.group("text") - logging.debug("sucessfully parsed %s into %s", raw, self.__str__()) - else: - self.author = "" - self.to = "" - self.text = "" - logging.warning("failed to parse %s into valid message", raw) - - def __str__(self): - return f"{self.author} to {self.to}: {self.text}" diff --git a/v5.py b/v5.py deleted file mode 100644 index 25dfaa7..0000000 --- a/v5.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -v5 (GLaDOS) -=========== - -Description ------------ -Manage the V5 layer of GLaDOS. -""" - -import logging -import socket -from threading import Thread -from functools import wraps - - -class V5: - """Manage connexion beetween the bot and the V5 server, and manage callbacks. - - Attributes - ---------- - irc : irc.IRC, public - An IRC instance. - - sock : ssl.SSLSocket, private - The V5 socket. - handler : Thread, private - callbacks : list, private - List of the registred callbacks. - - Methods - ------- - start : NoneType, public - Start v5 handler. - on : function, public - Add a callback to the v5 handler. - - handle : NoneType, private - Handle the incoming messages and callbacks. - """ - - def __init__(self, v5_params: tuple, irc): - """Initialize V5 handle. - - Parameters - ---------- - v5 : tuple - The information on V5 server (host, port). - irc : irc.IRC - An initialized IRC instance. - """ - self.irc = irc - self.__sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) - self.__sock.bind(v5_params) - self.__handler = Thread(target=self.__handle) - self.__callbacks = [] - - def start(self): - """Start v5 handler.""" - self.__handler.start() - logging.info("started") - - def on(self, event): - """Adds a callback to the v5 handler. - - Parameters - ---------- - event : function - ``event`` is a function taking in parameter a list of channels and a string, and return - ``True`` if the callback should be executed. - """ - def callback(func): - @wraps(func) - def wrapper(channels, message): - func(channels, message) - self.__callbacks.append((event, wrapper)) - return wrapper - - return callback - - def __handle(self): - """Handle the incoming messages and callbacks.""" - while True: - data, addr = self.__sock.recvfrom(4096) - data = data.decode() - logging.debug("received %s", data) - channels, message = data.split(":", 1) - channels = channels.split(" ") - - for event, callback in self.__callbacks: - if event(channels, message): - logging.info("passed %s", event.__name__) - callback(channels, message)