Fixed hooks system, abstracted into components utilities
This commit is contained in:
parent
9178887fa7
commit
25d3b9e95c
59
client.py
59
client.py
|
@ -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()
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
#from abots.helpers import infinitedict
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
hooks = defaultdict(lambda: None)
|
Loading…
Reference in New Issue