From afa3e1859fa98ff89ab85564b2abf90b02b6d343 Mon Sep 17 00:00:00 2001 From: Shadow15510 Date: Sun, 11 Jun 2023 14:30:18 +0200 Subject: [PATCH] rename folder --- glados_cmnds.py | 3 +- irc_api/__pycache__/commands.cpython-311.pyc | Bin 0 -> 6310 bytes irc_api/__pycache__/irc.cpython-311.pyc | Bin 0 -> 10200 bytes irc_api/__pycache__/secrets.cpython-311.pyc | Bin 0 -> 284 bytes irc_api/__pycache__/v5.cpython-311.pyc | Bin 0 -> 4160 bytes irc_api/commands.py | 130 +++++++++++ irc_api/irc.py | 234 +++++++++++++++++++ irc_api/v5.py | 92 ++++++++ main.py | 6 +- 9 files changed, 463 insertions(+), 2 deletions(-) create mode 100644 irc_api/__pycache__/commands.cpython-311.pyc create mode 100644 irc_api/__pycache__/irc.cpython-311.pyc create mode 100644 irc_api/__pycache__/secrets.cpython-311.pyc create mode 100644 irc_api/__pycache__/v5.cpython-311.pyc create mode 100644 irc_api/commands.py create mode 100644 irc_api/irc.py create mode 100644 irc_api/v5.py diff --git a/glados_cmnds.py b/glados_cmnds.py index 78d7b1b..ec98214 100644 --- a/glados_cmnds.py +++ b/glados_cmnds.py @@ -1,4 +1,5 @@ -from irc_bot_api import commands +from irc_api import commands + class GladosV4(commands.CommandsPack): @commands.command(name="bye", event=lambda m: "au revoir glados" in m.text.lower()) diff --git a/irc_api/__pycache__/commands.cpython-311.pyc b/irc_api/__pycache__/commands.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53a242bf8b48cd2f3ffa000e82ae229d36b24203 GIT binary patch literal 6310 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 Ai~s-t literal 0 HcmV?d00001 diff --git a/irc_api/__pycache__/v5.cpython-311.pyc b/irc_api/__pycache__/v5.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11cad63abc6f23f41571efef6279e3e6ae854424 GIT binary patch literal 4160 zcma)9QEwZ^5#Bo<$rDA%vc*VZBwN?Yj?LJngg8u6B{E<{j^$Qn(nvK@xdnCNt)#P$ zckJF#wh1Z_5CfJ0Jt%<#`5_NNkvcA%hd#8g{RstW5HN9o00RXI4e~hW|tXN zHFyi&WNO~dHs=j=xz~8U@ZjKF&EH8DlLjy;EpsG)TBFPE`%!pk3&FZEew9k zu6Q-2o&h5bXR?-Q(p%uEvOuYGlTwrWw_rJnYHGf(Hf1Z6o#CvUw~DN47z=9lhMkN= z6lfP5hv^FihuXZ`vTPjHSymZGn!Nn^$7hs$VZqR|T#)OU3a}O3v>nyV(j*_?#I~|G zK+(31P%lz9op2N^&*PG)bX^J!FvPZJFa-aY*%VJ595Yzzr^G;aB*{R zvlv~19;2Gl)N3sI3U!t(t!>4g+m6Z{u${I{I(s`$53G2G3jl%|etJ&BE{rRS1v87# zIiPbv(-gJog5p@*xmFu|ZqH)-7ubu(+_baP3`QQHP|+I){&)`|P$cC4f_ zd6lU-I~!;LBjy+OcmKn&SQ1Ml(;RkMB(DoT+wt)WjO)$!Hf~d|J@de}pUYPXnlr(q7G#qE|9e^8;}M2Hm*2@ys-ykWFwSybJ1cs6+xiD7ixuKU$vLy7z$y*0W>aAC!L760kGzVyi9V#7e2{cPG6jLBQu|V z{F}_=rOz*1aXT_sQ<)4(xYB}dY7Fiwt*wVez)n z0(kP`gVALxM@Q{t^=uYQjZRwGLXMh_J*v^X<=B}e-5K@DQS-{Z0anUjwp6>M;`QUzaxIoxY*_xxak!efl5oRnJXU`Yu-cE&{)^CPz0q zpUMMIrz!7DD&?wm?zmL&Y**< z!QbvYkS)Y2JiWHvD(5rFLwI}K>;Y0+5Pcmj@uuy=DV$e$5(SEf4-^wO426_AR7)o$ zb{MqXAZNH?!&+K`YD$I9}t9r@5kbo2KWdAKSMm;Ep61BGlewV{LWJRmr7m)sS$$hxpj;HVPr$g5%r zmm#V$xB(>&3ef+rafsX3OEbCx_-U!CZTT@j&xhyd6&m=MYC{lYI`Z& zh9|6axKFz)K)x040;Zf69Ni=Bl@0<6x0^e1JE~TNdx5D1&Hw$p(lLP!`gE1)RFFA2>JMIBZ-AL*7XLHI6_xj8~pK zWyMo~Ld#5kWLOX~`+U+5+#aVPIR!r0gFv1GOnYLR(&iTrZdQ6uR(npC{VxY)BEZmb zb_9qU!dXlW66BZ28$n)>!@)}ml$=-A8eYl%?8W|9xOEBS?KF_*;KokZk^0?AuIc-#P3Ufe{>EC#u6=Wz^6A9_+h66_ePlUI>$(lfAr$V=H4OcAU=0r>42 zz}+GpuTjOceaQI(U~E-@^cG%@w*B~YnkO>c3^4kCg}+(K|QB$86F6evmU_Ssu)UN={ug-gNf&09pf4*s_& zg$~*h$i@d7e^1E8(^Id1J)6}@4^-?@TK`D0RRomv=LY`3%JDe*yu7N|BDSTKwpDgHPjUpTy5T(kk(D z)%dx4zh0l*JYMVV-}>;I55JzcKe0Z!6Ybr&^5E>FSS6aQMw8`evZfsWA@b+QqjP@~ zD#|-m<(*3O==#*=Qmub@>&tJx+|E||->&w*y*~A91yxkIuQ?#?ZgJ}Pj3%CqCc9I*hDopQI1X2Vndr#55CyGS&5BSW25ER z==1s;;5JX3G{hGFjJc6aCTD2{1HuJYpv<2Ns^JCC4QDc%l?8T?-}u-YAkNQPb`sxU z3klD0FWdP^j3{JCaL`@^vex|6#lE0iCrHM+gU7Z{)CoNH6D1H-V68rh^aKa$q)CoP zgAj>>#B%{BPRQ&%cyNHQPIz-W9R9}?jyvzt*TGLr%Q9@QuyUWVR_(3hh5V5hlk;pG z1`wF`S3v3kK@jRv%c+ne+eq Jic#+5{{RGE$A16- literal 0 HcmV?d00001 diff --git a/irc_api/commands.py b/irc_api/commands.py new file mode 100644 index 0000000..e978ce3 --- /dev/null +++ b/irc_api/commands.py @@ -0,0 +1,130 @@ +from functools import wraps +import logging +from irc_api.secrets import USER, PASSWORD +from irc_api.irc import IRC +from irc_api.v5 import V5 + + +PREFIX = "" + +def command(name, event=None): + """Decorate a function and return a Command instance.""" + def decorator(func): + desc = name + if func.__doc__: + desc = func.__doc__ + return Command( + name=name, + desc=desc, + func=func, + event=event + ) + return decorator + + +class Command: + def __init__(self, name, desc, func, event=None): + self.name = name + self.desc = desc + self.func = func + if not event: + self.event = lambda m: m.text.startswith(PREFIX + name) + else: + self.event = event + + self.cmnd_pack = None + + def __call__(self, msg): + return self.func(self.cmnd_pack, msg) + + +class CommandsPack: + def __init__(self, bot): + self.bot = bot + + +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, prefix: str=""): + """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. + prefix : str, optionnal + The prefix on which the bot will react. + """ + global PREFIX + PREFIX = prefix + + self.irc = IRC(*irc_params) + self.v5 = V5(v5_params, self.irc) + self.channels = channels + + 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 hadndler + self.v5.start() + + # Run IRC + self.irc.run() + + def add_help(self): + help_callback = Command( + "aide", + "Affiche la liste des commandes disponibles.", + help_cmnd + ) + help_callback.cmnd_pack = self + self.irc.callbacks.append(help_callback) + + def add_commands_pack(self, commands_pack): + """Add a package of commands to the bot. + + Parameters + ---------- + commands_pack : CommandsPack + A commands pack which contains command's instances. + """ + cmnd_pack = commands_pack(self) + + for cmnd_name in dir(commands_pack): + if not cmnd_name.startswith("__") and not cmnd_name.endswith("__"): + cmnd = getattr(commands_pack, cmnd_name) + cmnd.cmnd_pack = cmnd_pack + + self.irc.callbacks.append(cmnd) + + +def help_cmnd(bot, msg): + """Documentation des fonctions disponibles.""" + bot.irc.send(msg.to, f"Aide des commandes") + for cmnd in bot.irc.callbacks: + bot.irc.send(msg.to, f" – {cmnd.name} : {cmnd.desc}") diff --git a/irc_api/irc.py b/irc_api/irc.py new file mode 100644 index 0000000..79c2087 --- /dev/null +++ b/irc_api/irc.py @@ -0,0 +1,234 @@ +""" +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. + callbacks : list, public + List of the registred callbacks. + + socket : ssl.SSLSocket, private + The IRC's socket. + inbox : Queue, private + Queue of the incomming messages. + handler : Thread, private + + 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 + self.callbacks = [] + + + # 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) + + # 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 callback in self.callbacks: + if callback.event(message): + logging.info("matched %s", callback.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.__inbox.get() + 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) + + # 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 __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.__inbox.get() + while not condition(msg): + msg = self.__inbox.get() + 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/irc_api/v5.py b/irc_api/v5.py new file mode 100644 index 0000000..25dfaa7 --- /dev/null +++ b/irc_api/v5.py @@ -0,0 +1,92 @@ +""" +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) diff --git a/main.py b/main.py index a351afc..5d1072a 100755 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ Create a bot's instance and manages it. import logging import re -from irc_bot_api import commands +from irc_api import commands from glados_cmnds import GladosV4, GladosV5 @@ -25,9 +25,13 @@ glados = commands.Bot( "!" ) + glados.add_commands_pack(GladosV4) glados.add_commands_pack(GladosV5) glados.add_help() +for i in glados.irc.callbacks: + print(i.name) + glados.start()