#!/usr/bin/env python3 import asyncio import logging import pickle from time import time from meetbot_cfg import config # Initialized channels from the configuration CHANS = {} # Pickle DB PICKLE_DB = "meetbot.pickle" 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 existing pickle database") host = config["host"] port = config["port"] nick = config["nick"] try: logging.info("Connecting to %s/%d", host, port) reader, writer = await asyncio.open_connection(host, port) logging.debug("--> %s/%d: CAP LS 302", host, port) msg = "CAP LS 302\r\n" writer.write(msg.encode("utf-8")) await writer.drain() logging.debug("--> %s/%d: NICK %s", host, port, nick) msg = f"NICK {nick}\r\n" writer.write(msg.encode("utf-8")) await writer.drain() logging.debug("--> %s/%d: USER %s * 0 %s", host, port, nick, nick) msg = f"USER {nick} * 0 :{nick}\r\n" writer.write(msg.encode("utf-8")) await writer.drain() msg = await reader.readline() msg = msg.decode("utf-8") logging.debug("<-- %s/%d: %s", host, port, msg) logging.debug("--> %s/%d: CAP REQ :no-history", host, port) msg = "CAP REQ :no-history\r\n" writer.write(msg.encode("utf-8")) await writer.drain() logging.debug("--> %s/%d: CAP REQ :no-autojoin", host, port) msg = "CAP REQ :no-autojoin\r\n" writer.write(msg.encode("utf-8")) await writer.drain() msg = await reader.readline() msg = msg.decode("utf-8") logging.debug("<-- %s/%d: %s", host, port, msg) logging.debug("--> %s/%d: CAP END", host, port) msg = "CAP END\r\n" writer.write(msg.encode("utf-8")) await writer.drain() msg = await reader.readline() msg = msg.decode("utf-8") logging.debug("<-- %s/%d: %s", host, port, msg) for chan in config["channels"]: chan = chan["name"] logging.debug("--> %s/%d: JOIN %s", host, port, chan) msg = f"JOIN {chan}\r\n" writer.write(msg.encode("utf-8")) await writer.drain() channels = [chan["name"] for chan in config["channels"]] logging.info("%s/%d: Listening to channels: %s", host, port, channels) elapsed = 0 while True: msg = await reader.readline() msg = msg.decode("utf-8") if not msg: continue split_msg = msg.split(" ") command = split_msg[1] chan = split_msg[2] nick_c = split_msg[0][1:].rsplit("!", 1)[0] logging.debug("<-- %s/%d: %s", host, port, msg.rstrip()) if command == "PRIVMSG": msg_title = msg.split(" ")[3][1:].rstrip() if not msg_title: continue if msg_title == "!start": logging.info("%s: Got !start", chan) if not CHANS.get(chan): CHANS[chan] = {} CHANS[chan]["topics"] = {} topics = CHANS[chan]["topics"] reply = f"PRIVMSG {chan} :Meeting started\r\n" writer.write(reply.encode("utf-8")) await writer.drain() if len(topics) == 0: reply = f"PRIVMSG {chan} :No topics\r\n" writer.write(reply.encode("utf-8")) await writer.drain() continue reply = f"PRIVMSG {chan} :Topics:\r\n" writer.write(reply.encode("utf-8")) await writer.drain() for i, topic in enumerate(topics): reply = f"PRIVMSG {chan} :{i+1}. {topic}\r\n" writer.write(reply.encode("utf-8")) await writer.drain() 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() elapsed = time() continue if msg_title == "!end": logging.info("%s: Got !end", chan) reply = f"PRIVMSG {chan} :Elapsed time: {round((time() - elapsed)/60, 1)} min\r\n" elapsed = 0 writer.write(reply.encode("utf-8")) await writer.drain() reply = f"PRIVMSG {chan} :Meeting ended\r\n" writer.write(reply.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: continue topic = topic[4].rstrip() + f" (by {nick_c})" if topic == "": continue if not CHANS.get(chan): CHANS[chan] = {} CHANS[chan]["topics"] = {} topics = CHANS[chan]["topics"] if topic not in topics: topics.append(topic) CHANS[chan]["topics"] = topics reply = f"PRIVMSG {chan} :Added topic: {topic}\r\n" else: reply = f"PRIVMSG {chan} :Topic already in list\r\n" writer.write(reply.encode("utf-8")) await writer.drain() continue if msg_title == "!deltopic": logging.info("%s: Got !deltopic", chan) if not CHANS.get(chan): CHANS[chan] = {} CHANS[chan]["topics"] = {} topics = CHANS[chan]["topics"] if len(topics) == 0: reply = f"PRIVMSG {chan} :No topics\r\n" writer.write(reply.encode("utf-8")) await writer.drain() continue try: topic = msg.split(" ", 4) topic = int(topic[4].rstrip()) del topics[topic-1] CHANS[chan]["topics"] = topics reply = f"PRIVMSG {chan} :Removed topic {topic}\r\n" writer.write(reply.encode("utf-8")) await writer.drain() except: reply = f"PRIVMSG {chan} :Topic not found\r\n" writer.write(reply.encode("utf-8")) await writer.drain() continue if msg_title == "!list": logging.info("%s: Got !list", chan) if not CHANS.get(chan): CHANS[chan] = {} CHANS[chan]["topics"] = [] topics = CHANS[chan]["topics"] if len(topics) == 0: reply = f"PRIVMSG {chan} :No topics\r\n" else: reply = f"PRIVMSG {chan} :Topics:\r\n" writer.write(reply.encode("utf-8")) await writer.drain() for i, topic in enumerate(topics): reply = f"PRIVMSG {chan} :{i+1}. {topic}\r\n" writer.write(reply.encode("utf-8")) await writer.drain() continue if msg_title == "!next": logging.info("%s: Got !next", chan) if not CHANS.get(chan): CHANS[chan] = {} CHANS[chan]["topics"] = [] topics = CHANS[chan]["topics"] reply = f"PRIVMSG {chan} :Elapsed time: {round((time() - elapsed)/60, 1)}, min\r\n" writer.write(reply.encode("utf-8")) await writer.drain() elapsed = time() if len(topics) == 0: reply = f"PRIVMSG {chan} :No further topics\r\n" else: cur_topic = topics.pop(0) CHANS[chan]["topics"] = topics reply = f"PRIVMSG {chan} :Current topic: {cur_topic}\r\n" writer.write(reply.encode("utf-8")) await writer.drain() continue except KeyboardInterrupt: return except ConnectionRefusedError: logging.error("%s/%d: Connection refused", host, port) return finally: reply = "QUIT\r\n" writer.write(reply.encode("utf-8")) await writer.drain() writer.close() await writer.wait_closed() if __name__ == "__main__": from sys import argv debug = "-v" in argv try: asyncio.run(main(debug=debug)) except KeyboardInterrupt: print("\rCaught ^C, saving pickle and exiting") with open(PICKLE_DB, "wb") as fdesc: pickle.dump(CHANS, fdesc, protocol=pickle.HIGHEST_PROTOCOL)