From 25d3b9e95c0c1c6ed3da18c482ec5884dd407edc Mon Sep 17 00:00:00 2001 From: aewens Date: Sun, 19 May 2019 02:44:24 -0400 Subject: [PATCH] Fixed hooks system, abstracted into components utilities --- client.py | 59 +++++----------- components/__init__.py | 1 + components/composer.py | 151 +++++++++++------------------------------ components/utils.py | 4 ++ 4 files changed, 60 insertions(+), 155 deletions(-) create mode 100644 components/utils.py diff --git a/client.py b/client.py index ec6db74..0b63281 100755 --- a/client.py +++ b/client.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from components import IRCSocketClient, Composer +from components import IRCSocketClient, Composer, hooks from abots.net import PrefixSocketClient from abots.events import CoroEvent from abots.helpers import Logger, infinitedict, isnumeric, coroutine, generator @@ -20,7 +20,7 @@ settings["stream"]["formatter"] = "%(message)s" logger = Logger(logname, settings=settings) logger.start() -composer = Composer(logger) +composer = Composer(hooks, logger) irc_host = "irc.tilde.chat"#"localhost" irc_port = 6697 @@ -46,8 +46,8 @@ def off(composer, sock): sock.stop() composer.kill_switch.set() -@generator -def log(event, cancel): +@composer.register_hook +def log(cancel, event): sock, line = (yield) meta = composer.parse_message(line) if meta.get("command", None) == "ERROR": @@ -55,8 +55,8 @@ def log(event, cancel): event.set() logger.debug(meta.get("raw")) -@generator -def pivot(event, cancel): +@composer.register_hook +def pivot(cancel, event): sock, line = (yield) meta = composer.parse_message(line) tags = meta.get("tags", dict()) @@ -74,8 +74,8 @@ def pivot(event, cancel): logger.debug(meta) -@generator -def identify(event, cancel): +@composer.register_hook +def identify(cancel, event): sock, line = (yield) meta = composer.parse_message(line) if meta.get("nick", None) != "NickServ": @@ -90,15 +90,15 @@ def identify(event, cancel): sleep(0.01) sock.send(f"PRIVMSG NickServ IDENTIFY {password}") sleep(1) - composer.register(log) + composer.subscribe("log") for channel in auto_join: sock.send(f"JOIN {channel}") - composer.register(pivot) + composer.subscribe("pivot") #off(composer, sock) event.set() -@generator -def init(event, cancel): +@composer.register_hook +def init(cancel, event): sock, line = (yield) meta = composer.parse_message(line) if meta.get("source", None) != "server": @@ -113,8 +113,8 @@ def init(event, cancel): sock.send("CAP LS") event.set() -@generator -def caps(event, cancel): +@composer.register_hook +def caps(cancel, event): sock, line = (yield) meta = composer.parse_message(line) if meta.get("source", None) != "server": @@ -139,39 +139,12 @@ def caps(event, cancel): cancel() logger.debug(f"ACK!") event.set() - logger.debug(require_met.is_set()) if require_met.is_set(): - composer.register(identify) + composer.subscribe("identify") sleep(0.01) sock.send("CAP END") sock.send(f"USER {name} - - -") sock.send(f"NICK {name}") -composer.register_many([init, caps]) +composer.subscribe_many(["init", "caps"]) 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() diff --git a/components/__init__.py b/components/__init__.py index 3d10f1e..ee90ec1 100644 --- a/components/__init__.py +++ b/components/__init__.py @@ -1,2 +1,3 @@ from components.socket import IRCSocketClient from components.composer import Composer +from components.utils import hooks diff --git a/components/composer.py b/components/composer.py index 5b0f6e8..59bb7d6 100644 --- a/components/composer.py +++ b/components/composer.py @@ -1,15 +1,18 @@ from components import IRCSocketClient 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 os import remove as delete_file from os.path import isfile +from functools import wraps class Composer: - def __init__(self, logger): + def __init__(self, hooks, logger=None): + self.hooks = hooks self.logger = logger - self._targets = dict() + self._listeners = dict() self.kill_switch = CoroEvent() def start_socket(self, host, port, timeout=None, secure=False): @@ -20,48 +23,58 @@ class Composer: events = sock.queues.get("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() - 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): - for target in targets: - self.register(target) + def subscribe_many(self, hook_names): + for hook_name in hook_names: + self.subscribe(hook_name) - def withdraw(self, key): + def unsubscribe(self, key): def cancel(): try: - del self._targets[key] + del self._listeners[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() + @generator + def send_to_listeners(self, sock): + line = (yield) + for key in list(self._listeners): + listener = self._listeners.get(key, None) + if listener is None: + return + try: + listener.send((sock, line)) + except StopIteration: + cancel = self.unsubscribe(key) + cancel() def get_messages(self, sock): cache = "" - targets = self.send_to_targets(sock) + listeners = self.send_to_listeners(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")) + listeners.send(cache + lines[0].strip("\r")) for line in lines[1:-1]: - targets.send(line.strip("\r")) + listeners.send(line.strip("\r")) cache = lines[-1] else: cache = cache + recv @@ -106,89 +119,3 @@ class Composer: 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() diff --git a/components/utils.py b/components/utils.py new file mode 100644 index 0000000..6a4e5e8 --- /dev/null +++ b/components/utils.py @@ -0,0 +1,4 @@ +#from abots.helpers import infinitedict +from collections import defaultdict + +hooks = defaultdict(lambda: None)