parent
d48db4e659
commit
1d365359ee
|
|
@ -4,10 +4,13 @@ from time import monotonic
|
|||
from hashlib import sha256
|
||||
import json
|
||||
from functools import reduce
|
||||
from typing import Iterable
|
||||
|
||||
bot = LXMFBot("HSWro Conference Bot")
|
||||
PASS = '34a77e61000b2ba1f56201332ef64d93f9cdc60e63ab48bc255102586ef7e592'
|
||||
WRONG_LOGIN_BAN_DURATION = 60
|
||||
WRONG_LOGIN_BAN_DURATION = 30
|
||||
USERS_NAME = "hswroconference:users"
|
||||
BANNED_NAME = "hswroconference:banned"
|
||||
|
||||
|
||||
def check_password(password: str) -> bool:
|
||||
|
|
@ -17,18 +20,18 @@ def check_password(password: str) -> bool:
|
|||
|
||||
|
||||
def ban_sender(r, userhash, until):
|
||||
r.rpush("hswroconference:banned", str(json.dumps({
|
||||
r.rpush(BANNED_NAME, str(json.dumps({
|
||||
"hash": userhash,
|
||||
"until": str(until),
|
||||
})))
|
||||
|
||||
|
||||
def check_banned(r, userhash):
|
||||
for raw in r.lrange("hswroconference:banned", 0, -1):
|
||||
for raw in r.lrange(BANNED_NAME, 0, -1):
|
||||
m = json.loads(raw.decode('utf-8'))
|
||||
if m['hash'] == userhash:
|
||||
if monotonic() > float(m['until']):
|
||||
r.lrem("hswroconference:banned", 0, raw)
|
||||
r.lrem(BANNED_NAME, 0, raw)
|
||||
print(f"Ban expired for {userhash}, unbanning")
|
||||
return False
|
||||
return True
|
||||
|
|
@ -40,7 +43,7 @@ def check_name(username: str) -> bool:
|
|||
return all(map(lambda c: c.isalnum(), username))
|
||||
|
||||
|
||||
def handle_login(msg, r, from_known_sender):
|
||||
def handle_login(msg, r, from_known_sender, known_senders: Iterable):
|
||||
def login_usage():
|
||||
msg.reply("Usage: `/login [NICK] [PASSWORD]`")
|
||||
parts = msg.content.split()
|
||||
|
|
@ -48,43 +51,79 @@ def handle_login(msg, r, from_known_sender):
|
|||
login_usage()
|
||||
return
|
||||
assert parts[0] == "/login"
|
||||
if from_known_sender:
|
||||
msg.reply("You are already logged in.")
|
||||
return
|
||||
if not check_password(parts[2]):
|
||||
ban_sender(r, msg.sender, monotonic() + WRONG_LOGIN_BAN_DURATION)
|
||||
return
|
||||
if not check_name(parts[1]):
|
||||
msg.reply("Name can only consist of alphanumeric characters.")
|
||||
return
|
||||
r.rpush("hswroconference:users", str(json.dumps({
|
||||
r.rpush(USERS_NAME, str(json.dumps({
|
||||
"hash": msg.sender,
|
||||
"nick": parts[1],
|
||||
})))
|
||||
msg.reply(f"Logged in as {parts[1]}.")
|
||||
send_to(f"{parts[1]} joined the chat.", map(
|
||||
lambda x: x['hash'], known_senders))
|
||||
|
||||
|
||||
def handle_logout(msg, r, from_known_sender, known_senders: Iterable):
|
||||
if not from_known_sender:
|
||||
return handle_help(msg)
|
||||
for raw in r.lrange(USERS_NAME, 0, -1):
|
||||
parsed = json.loads(raw.decode('utf-8'))
|
||||
if msg.sender == parsed['hash']:
|
||||
r.lrem(USERS_NAME, 0, raw)
|
||||
msg.reply(f"User {parsed['nick']} logged out.")
|
||||
send_to(f"{parsed['nick']} left the chat.", map(
|
||||
lambda x: x['hash'],
|
||||
filter(lambda x: x['hash'] != parsed['hash'], known_senders)))
|
||||
return
|
||||
|
||||
|
||||
def handle_help(msg):
|
||||
msg.reply("""Supported commands:
|
||||
/login [NICK] [PASSWORD] - Logs user in. Partyline messages will be sent.
|
||||
/logout - Logs user out. Partyline messages will stop.
|
||||
/help - Displays this message.""")
|
||||
|
||||
|
||||
def send_to(text: str, receiver_hashes: Iterable[str]):
|
||||
for receiver in receiver_hashes:
|
||||
bot.send(receiver, text)
|
||||
|
||||
|
||||
@bot.received
|
||||
def echo_msg(msg):
|
||||
def handle_msg(msg):
|
||||
r = Redis(host='localhost', port=6379, db=0)
|
||||
if check_banned(r, msg.sender):
|
||||
print(f"Message from banned user: {msg.sender}")
|
||||
return
|
||||
known_senders = list(map(lambda x: json.loads(x.decode('utf-8')),
|
||||
r.lrange("hswroconference:users", 0, -1)))
|
||||
r.lrange(USERS_NAME, 0, -1)))
|
||||
sender_info = reduce(lambda x, y: y if (y["hash"] == msg.sender) else x,
|
||||
known_senders, None)
|
||||
from_known_sender = sender_info is not None
|
||||
assert isinstance(msg.content, str)
|
||||
if msg.content.startswith("/login"):
|
||||
handle_login(msg, r, from_known_sender)
|
||||
return
|
||||
return handle_login(msg, r, from_known_sender, known_senders)
|
||||
if msg.content.startswith("/logout"):
|
||||
return handle_logout(msg, r, from_known_sender, known_senders)
|
||||
if msg.content.startswith("/help"):
|
||||
return handle_help(msg)
|
||||
if msg.content.startswith("/"):
|
||||
return handle_help(msg)
|
||||
|
||||
if not from_known_sender:
|
||||
msg.reply("Please identify with `/login [NICK] [PASSWORD]`")
|
||||
print(f"Received a message from unknown user. Ignoring.")
|
||||
else:
|
||||
print(f"Forwarding message from {sender_info['nick']}")
|
||||
for r in known_senders:
|
||||
if r["hash"] == msg.sender:
|
||||
continue
|
||||
bot.send(r["hash"], f"<{sender_info['nick']}> {msg.content}")
|
||||
receiver_hashes = map(lambda x: x['hash'], filter(
|
||||
lambda x: x['hash'] != msg.sender, known_senders))
|
||||
send_to(f"<{sender_info['nick']}> {msg.content}", receiver_hashes)
|
||||
|
||||
|
||||
bot.run()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ from typing import NamedTuple, Optional, Callable
|
|||
class LXMFBot:
|
||||
path_request_timeout = 15 # seconds
|
||||
|
||||
def __init__(self, name, announce=360):
|
||||
def __init__(self, name, announce=360,
|
||||
outbound_propagation_node="bf294c725196ff3a996bcba1eb9c16fb"):
|
||||
self.name = name
|
||||
self.announce_time = announce # seconds
|
||||
self.delivery_callbacks = []
|
||||
|
|
@ -38,6 +39,8 @@ class LXMFBot:
|
|||
RNS.log('Loaded identity from file', RNS.LOG_INFO)
|
||||
self.router = LXMRouter(
|
||||
storagepath=os.path.join(self.config_path, "router"))
|
||||
self.router.set_outbound_propagation_node(
|
||||
bytes.fromhex(outbound_propagation_node))
|
||||
self.router.register_delivery_callback(self._message_received)
|
||||
self.source = self.router.register_delivery_identity(
|
||||
self.id, display_name=name)
|
||||
|
|
@ -76,6 +79,27 @@ class LXMFBot:
|
|||
msg = SimpleNamespace(**obj)
|
||||
callback(msg)
|
||||
|
||||
def message_notification(self, message):
|
||||
""" Taken from NomadNet """
|
||||
if message.state == LXMessage.FAILED and \
|
||||
hasattr(message, "try_propagation_on_fail") and \
|
||||
message.try_propagation_on_fail:
|
||||
if hasattr(message, "stamp_generation_failed") and \
|
||||
message.stamp_generation_failed:
|
||||
RNS.log(f"Could not send {message} due " +
|
||||
"to a stamp generation failure", RNS.LOG_ERROR)
|
||||
else:
|
||||
RNS.log("Direct delivery of "+str(message) +
|
||||
" failed. Retrying as propagated message.",
|
||||
RNS.LOG_VERBOSE)
|
||||
message.try_propagation_on_fail = None
|
||||
message.delivery_attempts = 0
|
||||
if hasattr(message, "next_delivery_attempt"):
|
||||
del message.next_delivery_attempt
|
||||
message.packed = None
|
||||
message.desired_method = LXMessage.PROPAGATED
|
||||
self.router.handle_outbound(message)
|
||||
|
||||
def send(self, destination, message, title='Reply',
|
||||
originally_path_requested=monotonic()):
|
||||
recipent_hash = bytes.fromhex(destination)
|
||||
|
|
@ -91,6 +115,8 @@ class LXMFBot:
|
|||
lxm = LXMessage(dest, self.source, message,
|
||||
title=title, desired_method=LXMessage.DIRECT)
|
||||
lxm.try_propagation_on_fail = True
|
||||
lxm.register_delivery_callback(self.message_notification)
|
||||
lxm.register_failed_callback(self.message_notification)
|
||||
RNS.log(f"Will send a message to {destination}")
|
||||
self.queue.put(lambda: self.router.handle_outbound(lxm))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue