diff --git a/bridge.py b/bridge.py index 50ca987..e6ac94b 100644 --- a/bridge.py +++ b/bridge.py @@ -1,35 +1,35 @@ -import logging - - -from irc import IRC -from shoutbox import Shoutbox -from cookies import cookies -from sasl import nick, password -from users import users - - -LOG_FORMAT = "%(asctime)s [%(levelname)s] <%(filename)s> %(funcName)s: %(message)s" -logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) - -channels = ["hs", "projets", "annonces"] - -irc = IRC('irc.planet-casio.com', 6697) -shoutbox = Shoutbox(cookies) - - -@irc.on(lambda m: m.to[1:] in channels) -def handle_irc(m): - shoutbox.post(m.author, m.text, m.to[1:], users) - - -@shoutbox.on(lambda m: m.channel in channels and m.author != "IRC" and not m.text.endswith("[IRC]")) -def handle_shoutbox(m): - irc.send(f"#{m.channel}", f"{m.author}: {m.text}") - - -irc.start("Shoutbox", password, nick) -for c in channels: - irc.join(f"#{c}") - -shoutbox.run() -irc.run() +import logging + + +from irc import IRC +from shoutbox import Shoutbox +from cookies import cookies +from sasl import nick, password +from users import users + + +LOG_FORMAT = "%(asctime)s [%(levelname)s] <%(filename)s> %(funcName)s: %(message)s" +logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + +channels = ["hs", "projets", "annonces"] + +irc = IRC('irc.planet-casio.com', 6697) +shoutbox = Shoutbox(cookies) + + +@irc.on(lambda m: m.to[1:] in channels) +def handle_irc(m): + shoutbox.post(m.author, m.text, m.to[1:], users) + + +@shoutbox.on(lambda m: m.channel in channels and m.author != "IRC" and not m.text.endswith("[IRC]")) +def handle_shoutbox(m): + irc.send(f"#{m.channel}", f"{m.author}: {m.text}") + + +irc.start("Shoutbox", password, nick) +for c in channels: + irc.join(f"#{c}") + +shoutbox.run() +irc.run() diff --git a/irc.py b/irc.py index 974b552..aca523a 100644 --- a/irc.py +++ b/irc.py @@ -1,133 +1,133 @@ -# Manage the IRC layer of GLaDOS - -import logging, re, socket, ssl -from functools import wraps -from queue import Queue -from threading import Thread - -class IRC(object): - def __init__(self, host, port): - """ Initialize an IRC wrapper """ - # 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, password, sasl_nick=None): - """ Start the IRC layer. Manage authentication as well """ - sasl_nick = sasl_nick or nick - 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 {sasl_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(f"received {message}") - if message is not None: - for event, callback in self._callbacks: - if event(message): - logging.info(f"matched {event.__name__}") - callback(message) - - def send(self, target, message): - """ Send a message to the specified target (channel or user) """ - self._send(f"PRIVMSG {target} :{message}") - - def receive(self): - """ Receive a private message """ - while True: - message = self._recv() - if " PRIVMSG " in message: - msg = IRCMessage(message) - if msg: - return msg - - def join(self, channel): - """ Join a channel """ - self._send(f"JOIN {channel}") - logging.info(f"joined {channel}") - - def on(self, event): - """ Adds a callback to the IRC handler - Event is a function taking in parameter a IRCMessage and returning - True if the callback should be executed on the message """ - - def callback(func): - @wraps(func) - def wrapper(message): - func(message) - self._callbacks.append((event, wrapper)) - logging.info(f"added callback {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 m in data.split('\r\n'): - # Manage ping - if m.startswith("PING"): - self._send(m.replace("PING", "PONG")) - # Or add a new message to inbox - elif len(m): - self._inbox.put(m) - logging.debug(f"received {m}") - - def _send(self, raw): - """ Wrap and encode raw message to send """ - self._socket.send(f"{raw}\r\n".encode()) - - def _recv(self): - m = self._inbox.get() - return m - - def _waitfor(self, condition): - """ Wait for a raw message that matches the condition """ - msg = self._recv() - while not condition(msg): - msg = self._recv() - return msg - - -class IRCMessage(object): - r = re.compile("^:(?P[\w.~|]+)(?:!(?P\S+))? PRIVMSG (?P\S+) :(?P.+)") - - def __init__(self, raw): - match = re.search(IRCMessage.r, raw) - if match: - self.author = match.group("author") - self.to = match.group("to") - self.text = match.group("text") - logging.debug(f"sucessfully parsed {raw} into {self}") - else: - self.author = "" - self.to = "" - self.text = "" - logging.warning(f"failed to parse {raw} into valid message") - - def __str__(self): - return f"{self.author} to {self.to}: {self.text}" +# Manage the IRC layer of GLaDOS + +import logging, re, socket, ssl +from functools import wraps +from queue import Queue +from threading import Thread + +class IRC(object): + def __init__(self, host, port): + """ Initialize an IRC wrapper """ + # 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, password, sasl_nick=None): + """ Start the IRC layer. Manage authentication as well """ + sasl_nick = sasl_nick or nick + 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 {sasl_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(f"received {message}") + if message is not None: + for event, callback in self._callbacks: + if event(message): + logging.info(f"matched {event.__name__}") + callback(message) + + def send(self, target, message): + """ Send a message to the specified target (channel or user) """ + self._send(f"PRIVMSG {target} :{message}") + + def receive(self): + """ Receive a private message """ + while True: + message = self._recv() + if " PRIVMSG " in message: + msg = IRCMessage(message) + if msg: + return msg + + def join(self, channel): + """ Join a channel """ + self._send(f"JOIN {channel}") + logging.info(f"joined {channel}") + + def on(self, event): + """ Adds a callback to the IRC handler + Event is a function taking in parameter a IRCMessage and returning + True if the callback should be executed on the message """ + + def callback(func): + @wraps(func) + def wrapper(message): + func(message) + self._callbacks.append((event, wrapper)) + logging.info(f"added callback {func.__name__}") + return wrapper + + return callback + + + # Private methods + + def _handle(self, store=True): + """ Handle raw messages from irc and manage ping """ + while True: + # Get incoming messages + data = self._socket.recv(4096).decode() + + # Split multiple lines + for m in data.split('\r\n'): + # Manage ping + if m.startswith("PING"): + self._send(m.replace("PING", "PONG")) + # Or add a new message to inbox + elif len(m): + self._inbox.put(m) + logging.debug(f"received {m}") + + def _send(self, raw): + """ Wrap and encode raw message to send """ + self._socket.send(f"{raw}\r\n".encode()) + + def _recv(self): + m = self._inbox.get() + return m + + def _waitfor(self, condition): + """ Wait for a raw message that matches the condition """ + msg = self._recv() + while not condition(msg): + msg = self._recv() + return msg + + +class IRCMessage(object): + r = re.compile("^:(?P[\w.~|]+)(?:!(?P\S+))? PRIVMSG (?P\S+) :(?P.+)") + + def __init__(self, raw): + match = re.search(IRCMessage.r, raw) + if match: + self.author = match.group("author") + self.to = match.group("to") + self.text = match.group("text") + logging.debug(f"sucessfully parsed {raw} into {self}") + else: + self.author = "" + self.to = "" + self.text = "" + logging.warning(f"failed to parse {raw} into valid message") + + def __str__(self): + return f"{self.author} to {self.to}: {self.text}" diff --git a/shoutbox.py b/shoutbox.py index 1999249..0c0000f 100644 --- a/shoutbox.py +++ b/shoutbox.py @@ -1,68 +1,68 @@ -import json -import requests as r -import logging -import time -from functools import wraps -from threading import Thread - - -class Shoutbox(object): - def __init__(self, cookies): - self.channels = {'annonces': 0, 'projets': 0, 'hs': 0} - self.cookies = cookies - self._callbacks = [] - - for channel, last_id in self.channels.items(): - messages = json.loads(r.get(f"https://www.planet-casio.com/Fr/shoutbox/api/read?since={last_id}&channel={channel}&format=text").text)['messages'] - for m in messages: - self.channels[channel] = m['id'] - - def run(self): - def handler(): - while True: - for channel, last_id in self.channels.items(): - messages = json.loads(r.get(f"https://www.planet-casio.com/Fr/shoutbox/api/read?since={last_id}&channel={channel}&format=text").text)['messages'] - for m in messages: - logging.debug(m) - self.channels[channel] = m['id'] - message = SBMessage(m, channel) - for event, callback in self._callbacks: - if event(message): - logging.info(f"matched {event.__name__}") - callback(message) - time.sleep(1) - Thread(target=handler).start() - - def on(self, event): - """ Adds a callback to the IRC handler - Event is a function taking in parameter a SBMessage and returning - True if the callback should be executed on the message """ - - def callback(func): - @wraps(func) - def wrapper(message): - func(message) - self._callbacks.append((event, wrapper)) - logging.info(f"added callback {func.__name__}") - return wrapper - - return callback - - def post(self, user, msg, channel, users): - if any(user in t for t in users): - for i in users: - if i[1] == user: - r.post("https://www.planet-casio.com/Fr/shoutbox/api/post-as", - data={"user": i[0], "message": msg, "channel": channel}, - cookies=self.cookies) - else: - r.post("https://www.planet-casio.com/Fr/shoutbox/api/post-as", - data={"user": "IRC", "message": f"{user} : {msg}", "channel": channel}, - cookies=self.cookies) - - -class SBMessage(object): - def __init__(self, raw, channel): - self.author = raw['author'] - self.channel = channel - self.text = raw['content'] +import json +import requests as r +import logging +import time +from functools import wraps +from threading import Thread + + +class Shoutbox(object): + def __init__(self, cookies): + self.channels = {'annonces': 0, 'projets': 0, 'hs': 0} + self.cookies = cookies + self._callbacks = [] + + for channel, last_id in self.channels.items(): + messages = json.loads(r.get(f"https://www.planet-casio.com/Fr/shoutbox/api/read?since={last_id}&channel={channel}&format=text").text)['messages'] + for m in messages: + self.channels[channel] = m['id'] + + def run(self): + def handler(): + while True: + for channel, last_id in self.channels.items(): + messages = json.loads(r.get(f"https://www.planet-casio.com/Fr/shoutbox/api/read?since={last_id}&channel={channel}&format=text").text)['messages'] + for m in messages: + logging.debug(m) + self.channels[channel] = m['id'] + message = SBMessage(m, channel) + for event, callback in self._callbacks: + if event(message): + logging.info(f"matched {event.__name__}") + callback(message) + time.sleep(1) + Thread(target=handler).start() + + def on(self, event): + """ Adds a callback to the IRC handler + Event is a function taking in parameter a SBMessage and returning + True if the callback should be executed on the message """ + + def callback(func): + @wraps(func) + def wrapper(message): + func(message) + self._callbacks.append((event, wrapper)) + logging.info(f"added callback {func.__name__}") + return wrapper + + return callback + + def post(self, user, msg, channel, users): + if any(user in t for t in users): + for i in users: + if i[1] == user: + r.post("https://www.planet-casio.com/Fr/shoutbox/api/post-as", + data={"user": i[0], "message": msg, "channel": channel}, + cookies=self.cookies) + else: + r.post("https://www.planet-casio.com/Fr/shoutbox/api/post-as", + data={"user": "IRC", "message": f"{user} : {msg}", "channel": channel}, + cookies=self.cookies) + + +class SBMessage(object): + def __init__(self, raw, channel): + self.author = raw['author'] + self.channel = channel + self.text = raw['content']