Added coroutines system to components
This commit is contained in:
parent
e47d8d0441
commit
0cddba95e7
|
@ -3,6 +3,8 @@ abots-git/
|
|||
abots/
|
||||
abots
|
||||
*.log
|
||||
*.swp
|
||||
settings.json
|
||||
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
|
258
client.py
258
client.py
|
@ -1,7 +1,8 @@
|
|||
#!/opt/abots/env/bin/python3
|
||||
|
||||
from components import IRCSocketClient
|
||||
from abots.helpers import Logger, infinitedict, isnumeric
|
||||
from components import IRCSocketClient, Composer
|
||||
from abots.net import PrefixSocketClient
|
||||
from abots.helpers import Logger, infinitedict, isnumeric, coroutine
|
||||
|
||||
from time import sleep
|
||||
from os import remove as delete_file
|
||||
|
@ -17,141 +18,126 @@ settings["stream"]["formatter"] = "%(message)s"
|
|||
logger = Logger(logname, settings=settings)
|
||||
logger.start()
|
||||
|
||||
irc_host = "localhost"
|
||||
irc_port = 6667
|
||||
composer = Composer(logger)
|
||||
|
||||
irc_host = "irc.tilde.chat"#"localhost"
|
||||
irc_port = 6697
|
||||
irc_timeout = 3
|
||||
|
||||
irc_client = IRCSocketClient(irc_host, irc_port, timeout=irc_timeout)
|
||||
irc_client.start()
|
||||
inbox = irc_client.queues.get("inbox")
|
||||
outbox = irc_client.queues.get("outbox")
|
||||
events = irc_client.queues.get("events")
|
||||
|
||||
#sleep(2)
|
||||
irc_args = irc_host, irc_port, irc_timeout, True
|
||||
irc_client, inbox, outbox, events = composer.start_socket(*irc_args)
|
||||
irc_client.ready.wait()
|
||||
|
||||
# from section 2.3.1 of rfc1459
|
||||
def parse_message(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(parameters, dirt):
|
||||
params = parameters.partition(dirt)
|
||||
return params[0] + params[2]
|
||||
|
||||
# NOTE - collect *might* not be needed, most testing is needed
|
||||
def request(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(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(collect, values, matches=dict()):
|
||||
results, collect = need(collect, values, matches)
|
||||
if len(results) == 1:
|
||||
return results[0], collect
|
||||
return dict(), collect
|
||||
|
||||
def need_param(collect, matches=dict(), default=""):
|
||||
result, collect = need_one(collect, "params", matches)
|
||||
return result.get("params", default), collect
|
||||
|
||||
def extract(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(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()
|
||||
|
||||
@coroutine
|
||||
def log(cancel):
|
||||
try:
|
||||
while True:
|
||||
sock, line = (yield)
|
||||
meta = composer.parse_message(line)
|
||||
if meta.get("command", None) == "ERROR":
|
||||
logger.error(meta.get("raw"))
|
||||
continue
|
||||
logger.debug(meta.get("raw"))
|
||||
except GeneratorExit:
|
||||
pass
|
||||
|
||||
@coroutine
|
||||
def stopper(cancel):
|
||||
try:
|
||||
while True:
|
||||
sock, line = (yield)
|
||||
meta = composer.parse_message(line)
|
||||
if meta.get("nick", None) != "NickServ":
|
||||
continue
|
||||
if meta.get("command", None) != "NOTICE":
|
||||
continue
|
||||
params = composer.cleanup(meta.get("params", ""))
|
||||
nickserv_complain = "please choose a different nick"
|
||||
if nickserv_complain not in params:
|
||||
continue
|
||||
cancel()
|
||||
sleep(0.01)
|
||||
sock.send("QUIT bye-bye!")
|
||||
sleep(1)
|
||||
sock.stop()
|
||||
composer.kill_switch.set()
|
||||
break
|
||||
except GeneratorExit:
|
||||
pass
|
||||
|
||||
@coroutine
|
||||
def init(cancel):
|
||||
try:
|
||||
while True:
|
||||
sock, line = (yield)
|
||||
meta = composer.parse_message(line)
|
||||
if meta.get("source", None) != "server":
|
||||
continue
|
||||
if meta.get("command", None) != "NOTICE":
|
||||
continue
|
||||
if "hostname" not in meta.get("params", ""):
|
||||
continue
|
||||
cancel()
|
||||
logger.debug(meta["raw"])
|
||||
sleep(0.01)
|
||||
sock.send("CAP LS")
|
||||
break
|
||||
except GeneratorExit:
|
||||
pass
|
||||
|
||||
@coroutine
|
||||
def caps(cancel):
|
||||
try:
|
||||
while True:
|
||||
sock, line = (yield)
|
||||
meta = composer.parse_message(line)
|
||||
if meta.get("source", None) != "server":
|
||||
continue
|
||||
if meta.get("command", None) != "CAP":
|
||||
continue
|
||||
params = composer.cleanup(meta.get("params", ""))
|
||||
if "* LS" in params:
|
||||
capabilities = params.split("* LS", 1)[1].strip()
|
||||
logger.debug(f"Capabilities: {capabilities}")
|
||||
sock.send(f"CAP REQ :{capabilities}")
|
||||
elif "* ACK" in params:
|
||||
cancel()
|
||||
composer.register_many([log, stopper])
|
||||
logger.debug(f"ACK!")
|
||||
sleep(0.01)
|
||||
sock.send("CAP END")
|
||||
sock.send(f"USER {name} - - -")
|
||||
sock.send(f"NICK {name}")
|
||||
break
|
||||
except GeneratorExit:
|
||||
pass
|
||||
|
||||
composer.register_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()
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
from components.socket import IRCSocketClient
|
||||
from components.composer import Composer
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
from components import IRCSocketClient
|
||||
from abots.events import AsyncEvent
|
||||
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 = AsyncEvent()
|
||||
|
||||
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()
|
|
@ -13,6 +13,7 @@ from socket import timeout as sock_timeout
|
|||
|
||||
class IRCSocketClient(SocketClient):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["buffer_size"] = 512
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _format_message(self, message, *args):
|
||||
|
@ -23,11 +24,11 @@ class IRCSocketClient(SocketClient):
|
|||
packaged = f"{formatted}\r\n"
|
||||
return packaged.encode()
|
||||
|
||||
def recv(self):
|
||||
data = ""
|
||||
for buf in self._obtain(self._outbox):
|
||||
data = data + buf
|
||||
return data
|
||||
#def recv(self):
|
||||
# data = ""
|
||||
# for buf in self._obtain(self._outbox):
|
||||
# data = data + buf
|
||||
# return data
|
||||
|
||||
# See rfc2812
|
||||
#max_message_length = 512
|
||||
|
|
Loading…
Reference in New Issue