195 lines
6.7 KiB
Python
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()
|