Просмотр исходного кода

ircd/script: Extend meetbot functionality.

Luther Blissett 3 лет назад
Родитель
Сommit
b2da1c8964

+ 13 - 14
bin/ircd/script/README.md

@@ -1,19 +1,18 @@
-# IRC Private Channel Bot
+IRC Bots
+========
 
-*meeting_bot_secret.py* is an upgraded version of the *meeting_bot.py* (for IRC). This version allows for multiple channels, including private ones.
+## `meetbot`
 
-**Note:**
+`meetbot.py` is a bot used to keep topics for IRC channels, that can
+be discussed on meetings. Multiple channels can be configured and this
+is done through `meetbot_cfg.py`.
 
-* `{user}` needs to be exchanged with your *username*.
-* Every channel runs a bot on a different thread - be careful how many bots you deploy!
-* Never add secrets to the repo config file - always copy to a local dir!
+**Notes:**
+* Never add secrets to the public repo config!
 
+### Setup
 
-**Setup**
-
-* Donwload  *meeting_bot_secret.py* and *meeting_bot_secret_config.json* 
-* Copy meeting_bot_secret_config.json to `/home/{user}/.config/darkfi`
-* Open the config and set up all the channels and the values of name and secret (if not private, secret must be {null})
-* Change `{user}` to your *username* in *meeting_bot_secret.py* in the path of `load_config()` function
-* Navigate terminal to the folder where is *meeting_bot_secret.py*
-* Run the bot: `$ python meeting_bot_secret.py`
+* Download `meetbot.py` and `meetbot_cfg.py` 
+* Edit `meetbot_cfg.py` for your needs.
+* Navigate terminal to the folder where `meetbot.py` is.
+* Run the bot: `$ python meetbot.py`

+ 167 - 0
bin/ircd/script/meetbot.py

@@ -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())

+ 23 - 0
bin/ircd/script/meetbot_cfg.py

@@ -0,0 +1,23 @@
+config = {
+    # IRC server host
+    "host": "127.0.0.1",
+
+    # IRC server port
+    "port": 6667,
+
+    # IRC nickname
+    "nick": "meetbot",
+    "channels": [
+        {
+            "name": "#foo",
+            "secret": None,
+        },
+        {
+            "name": "#secret_channel",
+            # TODO: This is useless right now, but it would be nice
+            # to add a CAP in ircd to give all incoming PRIVMSG to be
+            # able to check them.
+            "secret": "HNEKcUmwsspdaL9b8sFn45b8Rf3bzv1LdYS1JVNvkPGL",
+        },
+    ],
+}

+ 0 - 91
bin/ircd/script/meeting_bot.py

@@ -1,91 +0,0 @@
-import asyncio
-
-
-async def start():
-    host = "127.0.0.1"
-    port = 6667
-    channel = "#dev"
-    nickname = "meeting_bot"
-
-    print(f"Start a connection {host}:{port}")
-    reader, writer = await asyncio.open_connection(host, port)
-
-    print("Send CAP msg")
-    cap_msg = f"CAP REQ : no-history \r\n"
-    writer.write(cap_msg.encode('utf8'))
-
-    print("Send NICK msg")
-    nick_msg = f"NICK {nickname} \r\n"
-    writer.write(nick_msg.encode('utf8'))
-
-    print("Send CAP END msg")
-    cap_end_msg = f"CAP END \r\n"
-    writer.write(cap_end_msg.encode('utf8'))
-
-    print(f"Send JOIN msg: {channel}")
-    join_msg = f"JOIN {channel} \r\n"
-    writer.write(join_msg.encode('utf8'))
-
-    topics = []
-
-    print("Start...")
-    while True:
-        msg = await reader.read(1024)
-        msg = msg.decode('utf8').strip()
-
-        if not msg:
-            continue
-
-        command = msg.split(" ")[1]
-
-        if command == "PRIVMSG":
-
-            msg_title = msg.split(" ")[3][1:]
-
-            if not msg_title:
-                continue
-
-            reply = None
-
-            if msg_title == "!start":
-                reply = f"PRIVMSG {channel} :meeting started \r\n"
-                msg_title = "!list"
-
-            if msg_title == "!end":
-                reply = f"PRIVMSG {channel} :meeting end \r\n"
-                topics = []
-
-            if msg_title == "!topic":
-                topic = msg.split(" ", 4)
-                if len(topic) != 5:
-                    continue
-                topic = topic[4]
-                topics.append(topic)
-                reply = f"PRIVMSG {channel} :add topic: {topic} \r\n"
-
-            if msg_title == "!list":
-                rep = f"PRIVMSG {channel} :topics: \r\n"
-                writer.write(rep.encode('utf8'))
-
-                for i, topic in enumerate(topics, 1):
-                    rep = f"PRIVMSG {channel} :{i}-{topic} \r\n"
-                    writer.write(rep.encode('utf8'))
-                await writer.drain()
-
-            if msg_title == "!next":
-                if len(topics) == 0:
-                    reply = f"PRIVMSG {channel} :no topics \r\n"
-                else:
-                    tp = topics.pop(0)
-                    reply = f"PRIVMSG {channel} :current topic: {tp} \r\n"
-
-            if reply != None:
-                writer.write(reply.encode('utf8'))
-                await writer.drain()
-
-        if command == "QUIT":
-            break
-
-    writer.close()
-
-asyncio.run(start())

+ 0 - 121
bin/ircd/script/meeting_bot_secret.py

@@ -1,121 +0,0 @@
-#TODO:
-# Expand home path
-
-import logging
-import threading
-import time
-import json
-import asyncio
-
-def load_config():
-    with open('/home/{user}/.config/darkfi/meeting_bot_config.json', 'r') as config:
-        data = json.load(config)    
-    logging.info(f"Config loaded: {data}")
-    return data
-
-def thread_run(host, port, nickname, channel):
-    asyncio.run(channel_listen(host, port, nickname, channel))
-
-async def channel_listen(host, port, nickname, channel):
-    logging.info(f"Starting listening to channel: {channel['name']}")
-
-    logging.info(f"Start a connection {host}:{port}")
-    reader, writer = await asyncio.open_connection(host, port)
-
-    logging.info("Send CAP msg")
-    cap_msg = f"CAP REQ : no-history \r\n"
-    writer.write(cap_msg.encode('utf8'))
-
-    logging.info("Send NICK msg")
-    nick_msg = f"NICK {nickname} \r\n"
-    writer.write(nick_msg.encode('utf8'))
-
-    logging.info("Send CAP END msg")
-    cap_end_msg = f"CAP END \r\n"
-    writer.write(cap_end_msg.encode('utf8'))
-
-    logging.info(f"Send JOIN msg: {channel['name']}")
-    join_msg = f"JOIN {channel['name']} \r\n"
-    writer.write(join_msg.encode('utf8'))
-
-    topics = []
-
-    logging.info("Start...")
-    while True:
-        msg = await reader.read(1024)
-        msg = msg.decode('utf8').strip()
-
-        if not msg:
-            continue
-
-        command = msg.split(" ")[1]
-
-        if command == "PRIVMSG":
-
-            msg_title = msg.split(" ")[3][1:]
-
-            if not msg_title:
-                continue
-
-            reply = None
-
-            if msg_title == "!start":
-                reply = f"PRIVMSG {channel['name']} :meeting started \r\n"
-                msg_title = "!list"
-
-            if msg_title == "!end":
-                reply = f"PRIVMSG {channel['name']} :meeting end \r\n"
-                topics = []
-
-            if msg_title == "!topic":
-                topic = msg.split(" ", 4)
-                if len(topic) != 5:
-                    continue
-                topic = topic[4]
-                topics.append(topic)
-                reply = f"PRIVMSG {channel['name']} :add topic: {topic} \r\n"
-
-            if msg_title == "!list":
-                rep = f"PRIVMSG {channel['name']} :topics: \r\n"
-                writer.write(rep.encode('utf8'))
-
-                for i, topic in enumerate(topics, 1):
-                    rep = f"PRIVMSG {channel['name']} :{i}-{topic} \r\n"
-                    writer.write(rep.encode('utf8'))
-                await writer.drain()
-
-            if msg_title == "!next":
-                if len(topics) == 0:
-                    reply = f"PRIVMSG {channel['name']} :no topics \r\n"
-                else:
-                    tp = topics.pop(0)
-                    reply = f"PRIVMSG {channel['name']} :current topic: {tp} \r\n"
-
-            if reply != None:
-                writer.write(reply.encode('utf8'))
-                await writer.drain()
-
-        if command == "QUIT":
-            break
-
-    writer.close()
-
-if __name__ == "__main__":
-    format = "%(asctime)s: %(message)s"
-    logging.basicConfig(format=format, level=logging.INFO,
-                        datefmt="%H:%M:%S")
-
-    data = load_config()
-
-    threads = list()
-    for index in range(len(data['channels'])):
-        logging.info(f"Main    : create and start thread {index}.")
-        x = threading.Thread(target=thread_run, args=(data['host'], data['port'], data['nickname'], data['channels'][index],))
-        threads.append(x)
-        x.start()
-
-    for index, thread in enumerate(threads):
-        logging.info(f"Main    : before joining thread {index}.")
-        thread.join()
-        logging.info(f"Main    : thread {index} done")
-

+ 0 - 20
bin/ircd/script/meeting_bot_secret_config.json

@@ -1,20 +0,0 @@
-{
-	"host":"127.0.0.1",
-	"port":6667,
-	"nickname":"meeting_bot",
-	"channels":
-	[
-		{
-			"name":"#dev",
-			"secret":null
-		},
-		{
-			"name":"#enter_channel_name",
-			"secret":"#enter_secret"
-		},
-		{
-			"name":"#enter_channel_name_2",
-			"secret":"#enter_secret_2"
-        	}
-	]
-}