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
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()

View File

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

View File

@ -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()

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)