First commit

This commit is contained in:
Louis Gatin 2021-09-10 14:23:43 +02:00
commit 8c46906cc2
3 changed files with 227 additions and 0 deletions

35
bridge.py Normal file
View File

@ -0,0 +1,35 @@
import json
import logging
import requests as r
import time
from irc import IRC
from shoutbox import Shoutbox
from cookies import cookies
from sasl import nick, password
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(f"{m.author}: {m.text}", m.to[1:])
@shoutbox.on(lambda m: m.channel in channels and m.author != "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()

133
irc.py Normal file
View File

@ -0,0 +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<author>[\w.~|]+)(?:!(?P<host>\S+))? PRIVMSG (?P<to>\S+) :(?P<text>.+)")
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}"

59
shoutbox.py Normal file
View File

@ -0,0 +1,59 @@
import json
import requests as r
import logging
import time
from functools import wraps
from queue import Queue
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, msg, channel):
r.post("https://www.planet-casio.com/Fr/shoutbox/api/post",
data={"message": 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']