mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 07:08:05 -05:00
ircd/script: Extend meetbot functionality.
This commit is contained in:
@@ -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
bin/ircd/script/meetbot.py
Executable file
167
bin/ircd/script/meetbot.py
Executable file
@@ -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
bin/ircd/script/meetbot_cfg.py
Normal file
23
bin/ircd/script/meetbot_cfg.py
Normal file
@@ -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",
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user