| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- #!/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())
|