|
|
@@ -0,0 +1,167 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+import asyncio
|
|
|
+import logging
|
|
|
+
|
|
|
+from base58 import b58decode
|
|
|
+from nacl.public import PrivateKey, Box
|
|
|
+
|
|
|
+from meetbot_cfg import config
|
|
|
+
|
|
|
+# Initialized channels from the configuration
|
|
|
+CHANS = {}
|
|
|
+
|
|
|
+
|
|
|
+async def channel_listen(host, port, nick, chan):
|
|
|
+ global CHANS
|
|
|
+
|
|
|
+ logging.info(f"Connecting to {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.info(f"{host}:{port} Send NICK msg")
|
|
|
+ nick_msg = f"NICK {nick}\r\n"
|
|
|
+ writer.write(nick_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.info(f"{host}:{port} Send JOIN msg for {chan}")
|
|
|
+ join_msg = f"JOIN {chan}\r\n"
|
|
|
+ writer.write(join_msg.encode("utf-8"))
|
|
|
+
|
|
|
+ logging.info(f"{host}:{port} Listening to channel: {chan}")
|
|
|
+ while True:
|
|
|
+ msg = await reader.read(1024)
|
|
|
+ msg = msg.decode("utf8")
|
|
|
+ if not msg:
|
|
|
+ continue
|
|
|
+
|
|
|
+ command = msg.split(" ")[1]
|
|
|
+
|
|
|
+ if command == "PRIVMSG":
|
|
|
+ msg_title = msg.split(" ")[3][1:].rstrip()
|
|
|
+ if not msg_title:
|
|
|
+ logging.info("Got empty PRIVMSG, ignoring")
|
|
|
+ continue
|
|
|
+
|
|
|
+ if msg_title == "!start":
|
|
|
+ topics = CHANS[chan]["topics"]
|
|
|
+ reply = f"PRIVMSG {chan} :Meeting started\r\n"
|
|
|
+ writer.write(reply.encode("utf-8"))
|
|
|
+ await writer.drain()
|
|
|
+
|
|
|
+ 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} :1. {topic}\r\n"
|
|
|
+ writer.write(reply.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"
|
|
|
+
|
|
|
+ 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"))
|
|
|
+ await writer.drain()
|
|
|
+ continue
|
|
|
+
|
|
|
+ if msg_title == "!topic":
|
|
|
+ topic = msg.split(" ", 4)
|
|
|
+ if len(topic) != 5:
|
|
|
+ continue
|
|
|
+ topic = topic[4].rstrip()
|
|
|
+ if topic == "":
|
|
|
+ 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"))
|
|
|
+ await writer.drain()
|
|
|
+ continue
|
|
|
+
|
|
|
+ if msg_title == "!list":
|
|
|
+ topics = CHANS[chan]["topics"]
|
|
|
+ if len(topics) == 0:
|
|
|
+ reply = f"PRIVMSG {chan} :No set 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} :1. {topic}\r\n"
|
|
|
+ writer.write(reply.encode("utf-8"))
|
|
|
+ await writer.drain()
|
|
|
+
|
|
|
+ continue
|
|
|
+
|
|
|
+ if msg_title == "!next":
|
|
|
+ topics = CHANS[chan]["topics"]
|
|
|
+ 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
|
|
|
+
|
|
|
+ return
|
|
|
+
|
|
|
+
|
|
|
+async def main():
|
|
|
+ format = "%(asctime)s: %(message)s"
|
|
|
+ logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S")
|
|
|
+
|
|
|
+ for i in config["channels"]:
|
|
|
+ name = i["name"]
|
|
|
+ logging.info(f"Found config for channel {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
|
|
|
+ # itself has a configured secret or not.
|
|
|
+ # This way the ircd itself doesn't have to keep channel secrets, but
|
|
|
+ # 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)
|
|
|
+ else:
|
|
|
+ box = None
|
|
|
+
|
|
|
+ CHANS[name] = {}
|
|
|
+ CHANS[name]["box"] = box
|
|
|
+ CHANS[name]["topics"] = []
|
|
|
+
|
|
|
+ coroutines = []
|
|
|
+ for i in CHANS.keys():
|
|
|
+ task = asyncio.create_task(
|
|
|
+ channel_listen(config["host"], config["port"], config["nick"], i))
|
|
|
+ coroutines.append(task)
|
|
|
+
|
|
|
+ await asyncio.gather(*coroutines)
|
|
|
+
|
|
|
+
|
|
|
+asyncio.run(main())
|