meetbot: Better debugging, and persistent channel data (including topics).

This commit is contained in:
Luther Blissett
2022-09-24 22:22:41 +02:00
parent 43ea13ddae
commit 94107fed31
2 changed files with 112 additions and 55 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.pyc *.pyc
*.pickle
*.sage.py *.sage.py
*.zk.bin *.zk.bin
*_circuit_layout.png *_circuit_layout.png

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import asyncio import asyncio
import logging import logging
import pickle
from base58 import b58decode from base58 import b58decode
from nacl.public import PrivateKey, Box from nacl.public import PrivateKey, Box
@@ -10,131 +11,171 @@ from meetbot_cfg import config
# Initialized channels from the configuration # Initialized channels from the configuration
CHANS = {} CHANS = {}
# Pickle DB
PICKLE_DB = "meetbot.pickle"
# TODO: while this is nice to support, it would perhaps be better to do it
# all over the same connection rather than opening a socket for each channel.
async def channel_listen(host, port, nick, chan): async def channel_listen(host, port, nick, chan):
global CHANS logging.info("%s: Connecting to %s:%s", chan, host, port)
logging.info(f"Connecting to {host}:{port}")
reader, writer = await asyncio.open_connection(host, port) reader, writer = await asyncio.open_connection(host, port)
logging.info(f"{host}:{port} Send CAP msg") logging.debug("%s: Send CAP msg", chan)
cap_msg = "CAP REQ : no-history\r\n" msg = "CAP REQ : no-history\r\n"
writer.write(cap_msg.encode("utf-8")) writer.write(msg.encode("utf-8"))
logging.info(f"{host}:{port} Send NICK msg") logging.debug("%s: Send NICK msg", chan)
nick_msg = f"NICK {nick}\r\n" msg = f"NICK {nick}\r\n"
writer.write(nick_msg.encode("utf-8")) writer.write(msg.encode("utf-8"))
logging.info(f"{host}:{port} Send CAP END msg") logging.debug("%s: Send CAP END msg", chan)
cap_end_msg = "CAP END\r\n" msg = "CAP END\r\n"
writer.write(cap_end_msg.encode("utf-8")) writer.write(msg.encode("utf-8"))
logging.info(f"{host}:{port} Send JOIN msg for {chan}") logging.debug("%s: Send JOIN msg", chan)
join_msg = f"JOIN {chan}\r\n" msg = f"JOIN {chan}\r\n"
writer.write(join_msg.encode("utf-8")) writer.write(msg.encode("utf-8"))
logging.info(f"{host}:{port} Listening to channel: {chan}") logging.info("%s: Listening to channel", chan)
while True: while True:
msg = await reader.read(1024) msg = await reader.readline()
msg = msg.decode("utf8") msg = msg.decode("utf8")
if not msg: if not msg:
continue continue
command = msg.split(" ")[1] command = msg.split(" ")[1]
logging.debug("%s: Recv: %s", chan, msg.rstrip())
if command == "PRIVMSG": if command == "PRIVMSG":
msg_title = msg.split(" ")[3][1:].rstrip() msg_title = msg.split(" ")[3][1:].rstrip()
if not msg_title: if not msg_title:
logging.info("Got empty PRIVMSG, ignoring") logging.info("%s: Recv empty PRIVMSG, ignoring", chan)
continue continue
if msg_title == "!start": if msg_title == "!start":
logging.info("%s: Got !start", chan)
topics = CHANS[chan]["topics"] topics = CHANS[chan]["topics"]
reply = f"PRIVMSG {chan} :Meeting started\r\n" reply = f"PRIVMSG {chan} :Meeting started"
writer.write(reply.encode("utf-8")) logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
reply = f"PRIVMSG {chan} :Topics:\r\n" if len(topics) == 0:
writer.write(reply.encode("utf-8")) reply = f"PRIVMSG {chan} :No topics"
logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain()
continue
reply = f"PRIVMSG {chan} :Topics:"
logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
for i, topic in enumerate(topics): for i, topic in enumerate(topics):
reply = f"PRIVMSG {chan} :1. {topic}\r\n" reply = f"PRIVMSG {chan} :{i+1}. {topic}"
writer.write(reply.encode("utf-8")) logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
if len(topics) > 0: cur_topic = topics.pop(0)
cur_topic = topics.pop(0) reply = f"PRIVMSG {chan} :Current topic: {cur_topic}\r\n"
reply = f"PRIVMSG {chan} :Current topic: {cur_topic}\r\n"
else:
reply = f"PRIVMSG {chan} :No further topics\r\n"
CHANS[chan]["topics"] = topics CHANS[chan]["topics"] = topics
writer.write(reply.encode("utf-8")) writer.write(reply.encode("utf-8"))
await writer.drain() await writer.drain()
continue continue
if msg_title == "!end": if msg_title == "!end":
reply = f"PRIVMSG {chan} :Meeting ended\r\n" logging.info("%s: Got !end", chan)
writer.write(reply.encode("utf-8")) reply = f"PRIVMSG {chan} :Meeting ended"
logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
continue continue
if msg_title == "!topic": if msg_title == "!topic":
logging.info("%s: Got !topic", chan)
topic = msg.split(" ", 4) topic = msg.split(" ", 4)
if len(topic) != 5: if len(topic) != 5:
logging.debug("%s: Topic msg len not 5, skipping", chan)
continue continue
topic = topic[4].rstrip() topic = topic[4].rstrip()
if topic == "": if topic == "":
logging.debug("%s: Topic message empty, skipping", chan)
continue continue
topics = CHANS[chan]["topics"] topics = CHANS[chan]["topics"]
topics.append(topic) topics.append(topic)
CHANS[chan]["topics"] = topics CHANS[chan]["topics"] = topics
reply = f"PRIVMSG {chan} :Added topic: {topic}\r\n" logging.debug("%s: Appended topic to channel topics", chan)
writer.write(reply.encode("utf-8"))
reply = f"PRIVMSG {chan} :Added topic: {topic}"
logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
continue continue
if msg_title == "!list": if msg_title == "!list":
logging.info("%s: Got !list", chan)
topics = CHANS[chan]["topics"] topics = CHANS[chan]["topics"]
if len(topics) == 0: if len(topics) == 0:
reply = f"PRIVMSG {chan} :No set topics\r\n" reply = f"PRIVMSG {chan} :No topics"
else: else:
reply = f"PRIVMSG {chan} :Topics:\r\n" reply = f"PRIVMSG {chan} :Topics:"
writer.write(reply.encode("utf-8"))
logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
for i, topic in enumerate(topics): for i, topic in enumerate(topics):
reply = f"PRIVMSG {chan} :1. {topic}\r\n" reply = f"PRIVMSG {chan} :{i+1}. {topic}"
writer.write(reply.encode("utf-8")) logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
continue continue
if msg_title == "!next": if msg_title == "!next":
logging.info("%s: Got !next", chan)
topics = CHANS[chan]["topics"] topics = CHANS[chan]["topics"]
if len(topics) == 0: if len(topics) == 0:
reply = f"PRIVMSG {chan} :No further topics\r\n" reply = f"PRIVMSG {chan} :No further topics"
else: else:
cur_topic = topics.pop(0) cur_topic = topics.pop(0)
CHANS[chan]["topics"] = topics CHANS[chan]["topics"] = topics
reply = f"PRIVMSG {chan} :Current topic: {cur_topic}\r\n" reply = f"PRIVMSG {chan} :Current topic: {cur_topic}"
writer.write(reply.encode("utf-8")) logging.info("%s: Send: %s", chan, reply)
writer.write((reply + "\r\n").encode("utf-8"))
await writer.drain() await writer.drain()
continue continue
return return
async def main(): async def main(debug=False):
format = "%(asctime)s: %(message)s" global CHANS
logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S")
loglevel = logging.DEBUG if debug else logging.INFO
logfmt = "%(asctime)s [%(levelname)s]\t%(message)s"
logging.basicConfig(format=logfmt,
level=loglevel,
datefmt="%Y-%m-%d %H:%M:%S")
try:
with open(PICKLE_DB, "rb") as pickle_fd:
CHANS = pickle.load(pickle_fd)
logging.info("Loaded pickle database")
except:
logging.info("Did not find pickle database")
for i in config["channels"]: for i in config["channels"]:
name = i["name"] name = i["name"]
logging.info(f"Found config for channel {name}") logging.info("Found config for channel %s", name)
# TODO: This will be useful when ircd has a CAP that tells it to # TODO: This will be useful when ircd has a CAP that tells it to
# give **all** messages to the connected client, no matter if ircd # give **all** messages to the connected client, no matter if ircd
@@ -143,20 +184,25 @@ async def main():
# they can rather only be held by this bot. In turn this means the bot # they can rather only be held by this bot. In turn this means the bot
# can be deployed with any ircd. # can be deployed with any ircd.
if i["secret"]: if i["secret"]:
logging.info(f"Instantiating NaCl box for {name}") logging.info("Instantiating NaCl box for %s", name)
sk = b58decode(i["secret"].encode("utf-8")) secret = b58decode(i["secret"].encode("utf-8"))
sk = PrivateKey(sk) secret = PrivateKey(secret)
pk = sk.public_key public = secret.public_key
box = Box(sk, pk) box = Box(secret, public)
else: else:
box = None box = None
CHANS[name] = {} if not CHANS.get(name):
CHANS[name] = {}
if not CHANS[name].get("topics"):
CHANS[name]["topics"] = []
CHANS[name]["box"] = box CHANS[name]["box"] = box
CHANS[name]["topics"] = []
coroutines = [] coroutines = []
for i in CHANS.keys(): for i in CHANS.keys():
logging.debug("Creating async task for %s", i)
task = asyncio.create_task( task = asyncio.create_task(
channel_listen(config["host"], config["port"], config["nick"], i)) channel_listen(config["host"], config["port"], config["nick"], i))
coroutines.append(task) coroutines.append(task)
@@ -164,4 +210,14 @@ async def main():
await asyncio.gather(*coroutines) await asyncio.gather(*coroutines)
asyncio.run(main()) if __name__ == "__main__":
from sys import argv
DBG = bool(len(argv) == 2 and argv[1] == "-v")
try:
asyncio.run(main(debug=DBG))
except:
print("\rCaught ^C, saving pickle and exiting")
with open(PICKLE_DB, "wb") as fdesc:
pickle.dump(CHANS, fdesc, protocol=pickle.HIGHEST_PROTOCOL)