diff --git a/.gitignore b/.gitignore index 19e0948..c0da501 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ # Application specific -abots-git/ -abots/ abots *.log +*.vim *.swp +*.swo test_*.py settings.json diff --git a/client.py b/client.py index 0b63281..fec7a3b 100755 --- a/client.py +++ b/client.py @@ -1,12 +1,10 @@ #!/usr/bin/env python3 -from components import IRCSocketClient, Composer, hooks +from components import IRCSocketClient, Composer +from plugins import * from abots.net import PrefixSocketClient -from abots.events import CoroEvent from abots.helpers import Logger, infinitedict, isnumeric, coroutine, generator -from abots.helpers import jsto, jots -from time import sleep from os import remove as delete_file from os.path import isfile @@ -20,131 +18,19 @@ settings["stream"]["formatter"] = "%(message)s" logger = Logger(logname, settings=settings) logger.start() -composer = Composer(hooks, logger) +composer = Composer(logger) -irc_host = "irc.tilde.chat"#"localhost" +irc_host = "irc.freenode.net"#"irc.tilde.chat"#"localhost" irc_port = 6697 irc_timeout = 3 irc_args = irc_host, irc_port, irc_timeout, True irc_client, inbox, outbox, events = composer.start_socket(*irc_args) irc_client.ready.wait() +composer.set_state("irc-host", irc_host) -with open("settings.json", "r") as config: - settings = jsto(config.read()) - assert isinstance(settings, dict), "Expected dict" - irc_settings = settings.get("irc", dict()) - name = irc_settings.get("name", "") - password = irc_settings.get("password", "") - author = irc_settings.get("author", "") - auto_join = irc_settings.get("auto-join", list()) - requirements = irc_settings.get("requirements", list()) - require_met = CoroEvent() +irc_client.send("USER babili - - -") +irc_client.send("NICK babili") -def off(composer, sock): - sock.send("QUIT bye-bye!") - sleep(1) - sock.stop() - composer.kill_switch.set() - -@composer.register_hook -def log(cancel, event): - sock, line = (yield) - meta = composer.parse_message(line) - if meta.get("command", None) == "ERROR": - logger.error(meta.get("raw")) - event.set() - logger.debug(meta.get("raw")) - -@composer.register_hook -def pivot(cancel, event): - sock, line = (yield) - meta = composer.parse_message(line) - tags = meta.get("tags", dict()) - if tags.get("account", None) != author: - return - if meta.get("command", None) != "PRIVMSG": - return - params = composer.cleanup(meta.get("params", "")) - source, message = params.split(" ", 1) - if message == "!quit": - cancel() - event.set() - off(composer, sock) - elif message == "!debug": - logger.debug(meta) - - -@composer.register_hook -def identify(cancel, event): - sock, line = (yield) - meta = composer.parse_message(line) - if meta.get("nick", None) != "NickServ": - return - if meta.get("command", None) != "NOTICE": - return - params = composer.cleanup(meta.get("params", "")) - nickserv_complain = "please choose a different nick" - if nickserv_complain not in params: - return - cancel() - sleep(0.01) - sock.send(f"PRIVMSG NickServ IDENTIFY {password}") - sleep(1) - composer.subscribe("log") - for channel in auto_join: - sock.send(f"JOIN {channel}") - composer.subscribe("pivot") - #off(composer, sock) - event.set() - -@composer.register_hook -def init(cancel, event): - sock, line = (yield) - meta = composer.parse_message(line) - if meta.get("source", None) != "server": - return - if meta.get("command", None) != "NOTICE": - return - if "hostname" not in meta.get("params", ""): - return - cancel() - logger.debug(meta["raw"]) - sleep(0.01) - sock.send("CAP LS") - event.set() - -@composer.register_hook -def caps(cancel, event): - sock, line = (yield) - meta = composer.parse_message(line) - if meta.get("source", None) != "server": - return - if meta.get("command", None) != "CAP": - return - 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}") - for cap in capabilities.split(" "): - if cap in requirements: - requirements.remove(cap) - if len(requirements) == 0 and not require_met.is_set(): - require_met.set() - if not require_met.is_set(): - cancel() - event.set() - off(composer, sock) - elif "* ACK" in params: - cancel() - logger.debug(f"ACK!") - event.set() - if require_met.is_set(): - composer.subscribe("identify") - sleep(0.01) - sock.send("CAP END") - sock.send(f"USER {name} - - -") - sock.send(f"NICK {name}") - -composer.subscribe_many(["init", "caps"]) -composer.get_messages(irc_client) +#composer.subscribe("startup/init") +#composer.subscribe_many(["startup/init", "debug/log"]) +#composer.get_messages(irc_client) diff --git a/components/__init__.py b/components/__init__.py deleted file mode 100644 index ee90ec1..0000000 --- a/components/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 59bb7d6..0000000 --- a/components/composer.py +++ /dev/null @@ -1,121 +0,0 @@ -from components import IRCSocketClient -from abots.events import CoroEvent -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, hooks, logger=None): - self.hooks = hooks - self.logger = logger - self._listeners = dict() - self.kill_switch = CoroEvent() - - 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_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() - 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 subscribe_many(self, hook_names): - for hook_name in hook_names: - self.subscribe(hook_name) - - def unsubscribe(self, key): - def cancel(): - try: - del self._listeners[key] - except KeyError: - pass - return 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 = "" - 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") - listeners.send(cache + lines[0].strip("\r")) - for line in lines[1:-1]: - listeners.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] diff --git a/components/socket.py b/components/socket.py deleted file mode 100755 index 1b61ebc..0000000 --- a/components/socket.py +++ /dev/null @@ -1,51 +0,0 @@ -""" - -IRC Socket Client -================= - -Formatted to read and write IRC socket data - -""" - -from abots.net import SocketClient - -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): - if len(args) > 0: - formatted = message.format(*args) - else: - formatted = message - packaged = f"{formatted}\r\n" - return packaged.encode() - - #def recv(self): - # data = "" - # for buf in self._obtain(self._outbox): - # data = data + buf - # return data - - # See rfc2812 - #max_message_length = 512 - #while True:#len(data) <= max_message_length: - # # Automatically break loop to prevent infinite loop - # # Allow at least twice the needed iterations to occur exiting loop - # # Force bufsize to cap out at buffer_size - # try: - # packet = self.sock.recv()#self.buffer_size) - # # The socket can either be broken or no longer open at all - # except (BrokenPipeError, OSError) as e: - # #if not isinstance(e, sock_timeout): - # # self._attempt_reconnect() - # continue - # #print(packet.decode()) - # if len(packet) == 0: - # break - # data = data + packet - ##print(data.decode()) - #return data.decode() if decode else data diff --git a/components/utils.py b/components/utils.py deleted file mode 100644 index 6a4e5e8..0000000 --- a/components/utils.py +++ /dev/null @@ -1,4 +0,0 @@ -#from abots.helpers import infinitedict -from collections import defaultdict - -hooks = defaultdict(lambda: None)