[alpha] Migrated irc socket client from abots, added irc parser

This commit is contained in:
aewens 2019-05-10 23:34:10 +02:00
parent e2541d644f
commit e47d8d0441
3 changed files with 170 additions and 34 deletions

153
client.py
View File

@ -1,7 +1,8 @@
#!/opt/abots/env/bin/python3
from abots.net import IRCSocketClient
from components import IRCSocketClient
from abots.helpers import Logger, infinitedict, isnumeric
from time import sleep
from os import remove as delete_file
from os.path import isfile
@ -20,7 +21,7 @@ irc_host = "localhost"
irc_port = 6667
irc_timeout = 3
irc_client = IRCSocketClient(irc_host, irc_port, daemon=True)#, timeout=irc_timeout)
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")
@ -29,44 +30,128 @@ events = irc_client.queues.get("events")
#sleep(2)
irc_client.ready.wait()
"""
:center.tilde.chat NOTICE * :*** Looking up your hostname...
:center.tilde.chat NOTICE * :*** Could not resolve your hostname: Domain not found; using your IP address (127.0.0.1) instead.
:center.tilde.chat CAP * LS :account-notify account-tag away-notify batch cap-notify chghost echo-message extended-join invite-notify message-tags sasl server-time
:center.tilde.chat 001 babili|test :Welcome to the tilde.chat IRC Network babili|test!babili|test@127.0.0.1
:center.tilde.chat 002 babili|test :Your host is center.tilde.chat, running version InspIRCd-3
:center.tilde.chat 003 babili|test :This server was created 17:19:44 May 06 2019
"""
def parse(message):
prefix, command, params = message.lstrip(":").rstrip("\r\n").split(" ", 2)
# from section 2.3.1 of rfc1459
def parse_message(message):
meta = dict()
meta["source"] = "nick" if "!" in prefix else "server"
meta["type"] = "numeric" if isnumeric(command) else "alpha"
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"
first = False
server = None
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}")
while True:
for letter in irc_client.recv():
if letter is not None:# or len(letter) > 0:
#meta = parse(letter)
#print(meta)
#source = meta["source"]
#if not first and source == "server":
# server = source
# first = True
#elif context == server and command == "CAP":
logger.debug(letter)
sleep(1)
#for letter in irc_client.recv():
# if letter is not None:
# logger.debug(letter)
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
components/__init__.py Normal file
View File

@ -0,0 +1 @@
from components.socket import IRCSocketClient

50
components/socket.py Executable file
View File

@ -0,0 +1,50 @@
"""
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):
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