Răsfoiți Sursa

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

Luther Blissett 3 ani în urmă
părinte
comite
94107fed31
2 a modificat fișierele cu 112 adăugiri și 55 ștergeri
  1. 1 0
      .gitignore
  2. 111 55
      bin/ircd/script/meetbot.py

+ 1 - 0
.gitignore

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

+ 111 - 55
bin/ircd/script/meetbot.py

@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 import asyncio
 import logging
+import pickle
 
 from base58 import b58decode
 from nacl.public import PrivateKey, Box
@@ -10,131 +11,171 @@ from meetbot_cfg import config
 # Initialized channels from the configuration
 CHANS = {}
 
+# Pickle DB
+PICKLE_DB = "meetbot.pickle"
 
-async def channel_listen(host, port, nick, chan):
-    global CHANS
 
-    logging.info(f"Connecting to {host}:{port}")
+# 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):
+    logging.info("%s: Connecting to %s:%s", chan, host, port)
     reader, writer = await asyncio.open_connection(host, port)
 
-    logging.info(f"{host}:{port} Send CAP msg")
-    cap_msg = "CAP REQ : no-history\r\n"
-    writer.write(cap_msg.encode("utf-8"))
+    logging.debug("%s: Send CAP msg", chan)
+    msg = "CAP REQ : no-history\r\n"
+    writer.write(msg.encode("utf-8"))
 
-    logging.info(f"{host}:{port} Send NICK msg")
-    nick_msg = f"NICK {nick}\r\n"
-    writer.write(nick_msg.encode("utf-8"))
+    logging.debug("%s: Send NICK msg", chan)
+    msg = f"NICK {nick}\r\n"
+    writer.write(msg.encode("utf-8"))
 
-    logging.info(f"{host}:{port} Send CAP END msg")
-    cap_end_msg = "CAP END\r\n"
-    writer.write(cap_end_msg.encode("utf-8"))
+    logging.debug("%s: Send CAP END msg", chan)
+    msg = "CAP END\r\n"
+    writer.write(msg.encode("utf-8"))
 
-    logging.info(f"{host}:{port} Send JOIN msg for {chan}")
-    join_msg = f"JOIN {chan}\r\n"
-    writer.write(join_msg.encode("utf-8"))
+    logging.debug("%s: Send JOIN msg", chan)
+    msg = f"JOIN {chan}\r\n"
+    writer.write(msg.encode("utf-8"))
 
-    logging.info(f"{host}:{port} Listening to channel: {chan}")
+    logging.info("%s: Listening to channel", chan)
     while True:
-        msg = await reader.read(1024)
+        msg = await reader.readline()
         msg = msg.decode("utf8")
         if not msg:
             continue
 
         command = msg.split(" ")[1]
+        logging.debug("%s: Recv: %s", chan, msg.rstrip())
 
         if command == "PRIVMSG":
             msg_title = msg.split(" ")[3][1:].rstrip()
             if not msg_title:
-                logging.info("Got empty PRIVMSG, ignoring")
+                logging.info("%s: Recv empty PRIVMSG, ignoring", chan)
                 continue
 
             if msg_title == "!start":
+                logging.info("%s: Got !start", chan)
                 topics = CHANS[chan]["topics"]
-                reply = f"PRIVMSG {chan} :Meeting started\r\n"
-                writer.write(reply.encode("utf-8"))
+                reply = f"PRIVMSG {chan} :Meeting started"
+                logging.info("%s: Send: %s", chan, reply)
+                writer.write((reply + "\r\n").encode("utf-8"))
                 await writer.drain()
 
-                reply = f"PRIVMSG {chan} :Topics:\r\n"
-                writer.write(reply.encode("utf-8"))
+                if len(topics) == 0:
+                    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()
 
                 for i, topic in enumerate(topics):
-                    reply = f"PRIVMSG {chan} :1. {topic}\r\n"
-                    writer.write(reply.encode("utf-8"))
+                    reply = f"PRIVMSG {chan} :{i+1}. {topic}"
+                    logging.info("%s: Send: %s", chan, reply)
+                    writer.write((reply + "\r\n").encode("utf-8"))
                     await writer.drain()
 
-                if len(topics) > 0:
-                    cur_topic = topics.pop(0)
-                    reply = f"PRIVMSG {chan} :Current topic: {cur_topic}\r\n"
-                else:
-                    reply = f"PRIVMSG {chan} :No further topics\r\n"
-
+                cur_topic = topics.pop(0)
+                reply = f"PRIVMSG {chan} :Current topic: {cur_topic}\r\n"
                 CHANS[chan]["topics"] = topics
-
                 writer.write(reply.encode("utf-8"))
                 await writer.drain()
                 continue
 
             if msg_title == "!end":
-                reply = f"PRIVMSG {chan} :Meeting ended\r\n"
-                writer.write(reply.encode("utf-8"))
+                logging.info("%s: Got !end", chan)
+                reply = f"PRIVMSG {chan} :Meeting ended"
+                logging.info("%s: Send: %s", chan, reply)
+                writer.write((reply + "\r\n").encode("utf-8"))
                 await writer.drain()
                 continue
 
             if msg_title == "!topic":
+                logging.info("%s: Got !topic", chan)
                 topic = msg.split(" ", 4)
+
                 if len(topic) != 5:
+                    logging.debug("%s: Topic msg len not 5, skipping", chan)
                     continue
+
                 topic = topic[4].rstrip()
+
                 if topic == "":
+                    logging.debug("%s: Topic message empty, skipping", chan)
                     continue
+
                 topics = CHANS[chan]["topics"]
                 topics.append(topic)
                 CHANS[chan]["topics"] = topics
-                reply = f"PRIVMSG {chan} :Added topic: {topic}\r\n"
-                writer.write(reply.encode("utf-8"))
+                logging.debug("%s: Appended topic to channel topics", chan)
+
+                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()
                 continue
 
             if msg_title == "!list":
+                logging.info("%s: Got !list", chan)
                 topics = CHANS[chan]["topics"]
                 if len(topics) == 0:
-                    reply = f"PRIVMSG {chan} :No set topics\r\n"
+                    reply = f"PRIVMSG {chan} :No topics"
                 else:
-                    reply = f"PRIVMSG {chan} :Topics:\r\n"
-                writer.write(reply.encode("utf-8"))
+                    reply = f"PRIVMSG {chan} :Topics:"
+
+                logging.info("%s: Send: %s", chan, reply)
+                writer.write((reply + "\r\n").encode("utf-8"))
                 await writer.drain()
 
                 for i, topic in enumerate(topics):
-                    reply = f"PRIVMSG {chan} :1. {topic}\r\n"
-                    writer.write(reply.encode("utf-8"))
+                    reply = f"PRIVMSG {chan} :{i+1}. {topic}"
+                    logging.info("%s: Send: %s", chan, reply)
+                    writer.write((reply + "\r\n").encode("utf-8"))
                     await writer.drain()
 
                 continue
 
             if msg_title == "!next":
+                logging.info("%s: Got !next", chan)
                 topics = CHANS[chan]["topics"]
                 if len(topics) == 0:
-                    reply = f"PRIVMSG {chan} :No further topics\r\n"
+                    reply = f"PRIVMSG {chan} :No further topics"
                 else:
                     cur_topic = topics.pop(0)
                     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()
                 continue
 
     return
 
 
-async def main():
-    format = "%(asctime)s: %(message)s"
-    logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S")
+async def main(debug=False):
+    global CHANS
+
+    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"]:
         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
         # 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
         # can be deployed with any ircd.
         if i["secret"]:
-            logging.info(f"Instantiating NaCl box for {name}")
-            sk = b58decode(i["secret"].encode("utf-8"))
-            sk = PrivateKey(sk)
-            pk = sk.public_key
-            box = Box(sk, pk)
+            logging.info("Instantiating NaCl box for %s", name)
+            secret = b58decode(i["secret"].encode("utf-8"))
+            secret = PrivateKey(secret)
+            public = secret.public_key
+            box = Box(secret, public)
         else:
             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]["topics"] = []
 
     coroutines = []
     for i in CHANS.keys():
+        logging.debug("Creating async task for %s", i)
         task = asyncio.create_task(
             channel_listen(config["host"], config["port"], config["nick"], i))
         coroutines.append(task)
@@ -164,4 +210,14 @@ async def main():
     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)