forked from devs/shoutbridge
commit
8c46906cc2
3 changed files with 227 additions and 0 deletions
@ -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() |
@ -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}" |
@ -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'] |
Loading…
Reference in new issue