irc-client/components/composer.py

195 lines
6.7 KiB
Python

from components import IRCSocketClient
from abots.events import CoroEvent
from abots.helpers import Logger, infinitedict, isnumeric, coroutine, sha256
from time import sleep
from os import remove as delete_file
from os.path import isfile
class Composer:
def __init__(self, logger):
self.logger = logger
self._targets = dict()
self.kill_switch = CoroEvent()
def start_socket(self, host, port, timeout=None, secure=False):
sock = IRCSocketClient(host, port, timeout=timeout, secure=secure)
sock.start()
inbox = sock.queues.get("inbox")
outbox = sock.queues.get("outbox")
events = sock.queues.get("events")
return sock, inbox, outbox, events
def register(self, target):
key = sha256()
self._targets[key] = target(self.withdraw(key))
def register_many(self, targets):
for target in targets:
self.register(target)
def withdraw(self, key):
def cancel():
try:
del self._targets[key]
except KeyError:
pass
return cancel
@coroutine
def send_to_targets(self, sock):
while True:
line = (yield)
for key in list(self._targets):
target = self._targets.get(key, None)
if target is None:
continue
try:
target.send((sock, line))
except StopIteration:
cancel = self.withdraw(key)
cancel()
def get_messages(self, sock):
cache = ""
targets = self.send_to_targets(sock)
while not self.kill_switch.is_set():
for recv in sock.recv():
if recv is None or len(recv) == 0:
continue
if "\n" in recv:
lines = recv.split("\n")
targets.send(cache + lines[0].strip("\r"))
for line in lines[1:-1]:
targets.send(line.strip("\r"))
cache = lines[-1]
else:
cache = cache + recv
return cache
# from section 2.3.1 of rfc1459
def parse_message(self, message):
meta = dict()
meta["raw"] = message
if message is None or len(message) == 0:
return meta
has_tags = message[0] == "@"
meta["has_tags"] = has_tags
if has_tags:
tags, message = message.lstrip("@").split(" ", 1)
meta["tags"] = dict()
for tag in tags.split(";"):
key, value = tag.split("=", 1)
meta["tags"][key] = value
has_prefix = message[0] == ":"
message = message.lstrip(":")
meta["has_prefix"] = has_prefix
if not has_prefix:
command, params = message.lstrip(":").split(" ", 1)
meta["type"] = "alpha"
else:
prefix, command, params = message.split(" ", 2)
source = "nick" if "!" in prefix else "server"
meta["prefix"] = prefix
meta["source"] = source
meta["type"] = "numeric" if isnumeric(command) else "alpha"
if source == "nick":
nick, user_info = prefix.split("!", 1)
user, host = user_info.split("@", 1)
meta["nick"] = nick
meta["user"] = user
meta["host"] = host
meta["command"] = command
meta["params"] = params
return meta
def cleanup(self, parameters, dirt=":"):
params = parameters.partition(dirt)
return params[0] + params[2]
# NOTE - collect *might* not be needed, most testing is needed
def request(self, collect=None):
response = irc_client.recv()
if collect is not None:
response = collect + response
complete = response[-2:] == "\r\n"
lines = response.split("\r\n")
collect = None if complete else lines[-1]
if not complete:
lines = lines[:-1]
return lines, collect
def need(self, collect, values, matches=dict()):
assert isinstance(collect, str) or collect is None, "Expected str|None"
assert isinstance(values, str), "Expected str"
assert isinstance(matches, dict), "Expected dict"
if "," in values:
values = values.split(",")
elif values != "*":
values = [values]
lines, collect = request(collect)
results = list()
for line in lines:
meta = parse_message(line)
matched = True
for key, match in matches.items():
if meta.get(key, None) != match:
matched = False
break
if matched:
if values == "*":
results.append(meta)
continue
results.append({value: meta.get(value, None) for value in values})
return results, collect
def need_one(self, collect, values, matches=dict()):
results, collect = need(collect, values, matches)
if len(results) == 1:
return results[0], collect
return dict(), collect
def need_param(self, collect, matches=dict(), default=""):
result, collect = need_one(collect, "params", matches)
return result.get("params", default), collect
def extract(self, response, key, after, dirt=":"):
value = response.get(key, None)
if value is None:
return None
bisect = cleanup(value, dirt).split(after, 1)
if len(bisect) != 2:
return None
before, result = bisect
return result.strip()
def extract_params(self, collect, after, matches, dirt=":"):
result, collect = need_one(collect, "params", matches)
return extract(result, "params", after), collect
#name = "babili"
#collect = None
#irc_client.send("CAP LS")
#caps, collect = extract_params(collect, "* LS",
# {"source": "server", "command": "CAP"})
#if caps is not None:
# logger.debug(caps)
# irc_client.send(f"CAP REQ :{caps}")
#status, collect = need_param(collect, {"source": "server", "command": "CAP"})
#ack = "ACK" in status
#logger.debug("ACK!")
#irc_client.send("CAP END")
#irc_client.send(f"USER {name} - - -")
#irc_client.send(f"NICK {name}")
#responses, collect = need(collect, "params",
# {"source": "nick", "nick": "NickServ"})
#nickserv_complain = "please choose a different nick"
#register = False
#for response in responses:
# if nickserv_complain in response.get("params", ""):
# register = True
# break
#if register:
# logger.debug("Needs to register bot now")
#irc_client.stop()