Fixed hooks system, abstracted into components utilities

This commit is contained in:
aewens 2019-05-19 02:44:24 -04:00
parent 9178887fa7
commit 25d3b9e95c
4 changed files with 60 additions and 155 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from components import IRCSocketClient, Composer from components import IRCSocketClient, Composer, hooks
from abots.net import PrefixSocketClient from abots.net import PrefixSocketClient
from abots.events import CoroEvent from abots.events import CoroEvent
from abots.helpers import Logger, infinitedict, isnumeric, coroutine, generator from abots.helpers import Logger, infinitedict, isnumeric, coroutine, generator
@ -20,7 +20,7 @@ settings["stream"]["formatter"] = "%(message)s"
logger = Logger(logname, settings=settings) logger = Logger(logname, settings=settings)
logger.start() logger.start()
composer = Composer(logger) composer = Composer(hooks, logger)
irc_host = "irc.tilde.chat"#"localhost" irc_host = "irc.tilde.chat"#"localhost"
irc_port = 6697 irc_port = 6697
@ -46,8 +46,8 @@ def off(composer, sock):
sock.stop() sock.stop()
composer.kill_switch.set() composer.kill_switch.set()
@generator @composer.register_hook
def log(event, cancel): def log(cancel, event):
sock, line = (yield) sock, line = (yield)
meta = composer.parse_message(line) meta = composer.parse_message(line)
if meta.get("command", None) == "ERROR": if meta.get("command", None) == "ERROR":
@ -55,8 +55,8 @@ def log(event, cancel):
event.set() event.set()
logger.debug(meta.get("raw")) logger.debug(meta.get("raw"))
@generator @composer.register_hook
def pivot(event, cancel): def pivot(cancel, event):
sock, line = (yield) sock, line = (yield)
meta = composer.parse_message(line) meta = composer.parse_message(line)
tags = meta.get("tags", dict()) tags = meta.get("tags", dict())
@ -74,8 +74,8 @@ def pivot(event, cancel):
logger.debug(meta) logger.debug(meta)
@generator @composer.register_hook
def identify(event, cancel): def identify(cancel, event):
sock, line = (yield) sock, line = (yield)
meta = composer.parse_message(line) meta = composer.parse_message(line)
if meta.get("nick", None) != "NickServ": if meta.get("nick", None) != "NickServ":
@ -90,15 +90,15 @@ def identify(event, cancel):
sleep(0.01) sleep(0.01)
sock.send(f"PRIVMSG NickServ IDENTIFY {password}") sock.send(f"PRIVMSG NickServ IDENTIFY {password}")
sleep(1) sleep(1)
composer.register(log) composer.subscribe("log")
for channel in auto_join: for channel in auto_join:
sock.send(f"JOIN {channel}") sock.send(f"JOIN {channel}")
composer.register(pivot) composer.subscribe("pivot")
#off(composer, sock) #off(composer, sock)
event.set() event.set()
@generator @composer.register_hook
def init(event, cancel): def init(cancel, event):
sock, line = (yield) sock, line = (yield)
meta = composer.parse_message(line) meta = composer.parse_message(line)
if meta.get("source", None) != "server": if meta.get("source", None) != "server":
@ -113,8 +113,8 @@ def init(event, cancel):
sock.send("CAP LS") sock.send("CAP LS")
event.set() event.set()
@generator @composer.register_hook
def caps(event, cancel): def caps(cancel, event):
sock, line = (yield) sock, line = (yield)
meta = composer.parse_message(line) meta = composer.parse_message(line)
if meta.get("source", None) != "server": if meta.get("source", None) != "server":
@ -139,39 +139,12 @@ def caps(event, cancel):
cancel() cancel()
logger.debug(f"ACK!") logger.debug(f"ACK!")
event.set() event.set()
logger.debug(require_met.is_set())
if require_met.is_set(): if require_met.is_set():
composer.register(identify) composer.subscribe("identify")
sleep(0.01) sleep(0.01)
sock.send("CAP END") sock.send("CAP END")
sock.send(f"USER {name} - - -") sock.send(f"USER {name} - - -")
sock.send(f"NICK {name}") sock.send(f"NICK {name}")
composer.register_many([init, caps]) composer.subscribe_many(["init", "caps"])
composer.get_messages(irc_client) composer.get_messages(irc_client)
#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}")
#sleep(2)
#irc_client.send("VERSION")
#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()

View File

@ -1,2 +1,3 @@
from components.socket import IRCSocketClient from components.socket import IRCSocketClient
from components.composer import Composer from components.composer import Composer
from components.utils import hooks

View File

@ -1,15 +1,18 @@
from components import IRCSocketClient from components import IRCSocketClient
from abots.events import CoroEvent from abots.events import CoroEvent
from abots.helpers import Logger, infinitedict, isnumeric, coroutine, sha256 from abots.helpers import Logger, infinitedict, isnumeric, sha256
from abots.helpers import coroutine, generator
from time import sleep from time import sleep
from os import remove as delete_file from os import remove as delete_file
from os.path import isfile from os.path import isfile
from functools import wraps
class Composer: class Composer:
def __init__(self, logger): def __init__(self, hooks, logger=None):
self.hooks = hooks
self.logger = logger self.logger = logger
self._targets = dict() self._listeners = dict()
self.kill_switch = CoroEvent() self.kill_switch = CoroEvent()
def start_socket(self, host, port, timeout=None, secure=False): def start_socket(self, host, port, timeout=None, secure=False):
@ -20,48 +23,58 @@ class Composer:
events = sock.queues.get("events") events = sock.queues.get("events")
return sock, inbox, outbox, events return sock, inbox, outbox, events
def register(self, target): def register_hook(self, func):
hook_name = func.__name__
if hook_name in list(self.hooks):
return
self.hooks[hook_name] = func
return generator(func)
def subscribe(self, hook_name):
key = sha256() key = sha256()
self._targets[key] = target(self.withdraw(key)) hook = self.hooks[hook_name]
if hook is not None:
event = CoroEvent()
gen_hook = generator(hook)
self._listeners[key] = gen_hook(self.unsubscribe(key), event)
def register_many(self, targets): def subscribe_many(self, hook_names):
for target in targets: for hook_name in hook_names:
self.register(target) self.subscribe(hook_name)
def withdraw(self, key): def unsubscribe(self, key):
def cancel(): def cancel():
try: try:
del self._targets[key] del self._listeners[key]
except KeyError: except KeyError:
pass pass
return cancel return cancel
@coroutine @generator
def send_to_targets(self, sock): def send_to_listeners(self, sock):
while True: line = (yield)
line = (yield) for key in list(self._listeners):
for key in list(self._targets): listener = self._listeners.get(key, None)
target = self._targets.get(key, None) if listener is None:
if target is None: return
continue try:
try: listener.send((sock, line))
target.send((sock, line)) except StopIteration:
except StopIteration: cancel = self.unsubscribe(key)
cancel = self.withdraw(key) cancel()
cancel()
def get_messages(self, sock): def get_messages(self, sock):
cache = "" cache = ""
targets = self.send_to_targets(sock) listeners = self.send_to_listeners(sock)
while not self.kill_switch.is_set(): while not self.kill_switch.is_set():
for recv in sock.recv(): for recv in sock.recv():
if recv is None or len(recv) == 0: if recv is None or len(recv) == 0:
continue continue
if "\n" in recv: if "\n" in recv:
lines = recv.split("\n") lines = recv.split("\n")
targets.send(cache + lines[0].strip("\r")) listeners.send(cache + lines[0].strip("\r"))
for line in lines[1:-1]: for line in lines[1:-1]:
targets.send(line.strip("\r")) listeners.send(line.strip("\r"))
cache = lines[-1] cache = lines[-1]
else: else:
cache = cache + recv cache = cache + recv
@ -106,89 +119,3 @@ class Composer:
def cleanup(self, parameters, dirt=":"): def cleanup(self, parameters, dirt=":"):
params = parameters.partition(dirt) params = parameters.partition(dirt)
return params[0] + params[2] 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()

4
components/utils.py Normal file
View File

@ -0,0 +1,4 @@
#from abots.helpers import infinitedict
from collections import defaultdict
hooks = defaultdict(lambda: None)