aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2022-11-08 15:15:43 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2022-11-08 15:15:43 +0100
commitad1465d2e17cdcd5e7dafcc0f9550e95199c8cb7 (patch)
treec8b2493dc4f52c6b5680dc4f2da66e97075135e5
parent3e80b30b9819aa5c94221e4776baa7d62ea5df88 (diff)
downloadgeldschieberbot-ad1465d2e17cdcd5e7dafcc0f9550e95199c8cb7.tar.gz
geldschieberbot-ad1465d2e17cdcd5e7dafcc0f9550e95199c8cb7.zip
move selection and sending messages from Geldschieberbot
In order to use the Geldschieberbot with a daemon we must extract sending and message selection from the Geldschieberbot class. In order to not break current 'on-shot' setups the geldschieberbot.py file compensates the extraction of configuration and message handling.
-rw-r--r--geldschieberbot.py136
1 files changed, 81 insertions, 55 deletions
diff --git a/geldschieberbot.py b/geldschieberbot.py
index 43dfce4..cb5d8b2 100644
--- a/geldschieberbot.py
+++ b/geldschieberbot.py
@@ -8,14 +8,9 @@ import json
import os
import subprocess
import sys
+import typing as t
-# Path where our data is stored persistent on disk
-STATE_FILE = os.environ["GSB_STATE_FILE"]
-
-GROUP_ID = os.environ["GSB_GROUP_ID"]
-
-SEND_CMD = os.environ["GSB_SEND_CMD"]
-GROUP_SEND_CMD = SEND_CMD + GROUP_ID
+DEFAULT_SEND_CMD = 'signal-cli send --message-from-stdin -g {group_id}'
@dataclass
@@ -25,6 +20,14 @@ class Quote:
author: str
+@dataclass
+class Reply:
+ """Class representing a reply from the bot"""
+ msg: str
+ attachment: t.Optional[str] = None
+ quote: t.Optional[Quote] = None
+
+
def to_cent(euro):
"""Parse string containing euros into a cent value"""
if '.' in euro:
@@ -57,8 +60,9 @@ class Geldschieberbot:
State of the geldschieberbot
"""
- def load_state(self, state_path=STATE_FILE):
+ def load_state(self, state_path=None):
"""Load state from disk"""
+ state_path = state_path or self.state_path
if os.path.isfile(state_path):
with open(state_path, 'r', encoding='utf-8') as state_f:
self.state = json.load(state_f)
@@ -87,8 +91,9 @@ class Geldschieberbot:
self.changes = self.state["changes"]
self.aliases = self.state.setdefault("aliases", {})
- def save_state(self, state_path=STATE_FILE):
+ def save_state(self, state_path=None):
"""Load state from disk"""
+ state_path = state_path or self.state_path
with open(state_path, 'w', encoding='utf-8') as f:
json.dump(self.state, f)
@@ -102,15 +107,6 @@ class Geldschieberbot:
self.balance[donor][recipient] += amount
self.balance[recipient][donor] -= amount
- def send(self, msg, attachment=None, cmd=SEND_CMD, quote: Quote = None):
- """Send a message with optional attachment"""
- if not self.quiet:
- if attachment:
- cmd += f' -a {attachment}'
- if quote:
- cmd += f' --quote-timestamp={quote.timestamp} --quote-author={quote.author}'
- subprocess.run(cmd.split(' '), input=msg.encode(), check=False)
-
def create_summary(self, user, include=None) -> str:
"""Create a summary for a user"""
msg = ''
@@ -854,7 +850,7 @@ class Geldschieberbot:
return {'err': 'you must register first'}
msg = f'State from {datetime.now().date().isoformat()}'
- return {'msg': msg, 'attachment': STATE_FILE}
+ return {'msg': msg, 'attachment': self.state_path}
def schedule(self, sender, args, msg) -> dict[str, str]: # pylint: disable=unused-argument
"""Schedule a command for periodic execution"""
@@ -974,11 +970,10 @@ class Geldschieberbot:
self.aliases[alias] = users
return {'msg': f'New alias "{alias}" registered'}
- def __init__(self, dry_run=False, quote_cmd=True):
+ def __init__(self, state_path, dry_run=False):
+ self.state_path = state_path
self.dry_run = dry_run # Run without changing the stored state
- self.quote_cmd = quote_cmd # Quote the message causing the reply
self.load_state()
- self.quiet = False # Run without sending messages
self.record_changes = True # Should changes be recorded
# Command dispatch table
@@ -1044,45 +1039,38 @@ class Geldschieberbot:
if self.record_changes and not self.dry_run:
self.changes[user].append(change)
- def handle(self, envelope: dict):
+ def handle(self, envelope: dict) -> list[Reply]:
"""Parse and respond to a message"""
sender_number = envelope["source"]
- if not "dataMessage" in envelope or not envelope[
- "dataMessage"] or not envelope["dataMessage"]["message"]:
- return
-
message = envelope["dataMessage"]
- if message["groupInfo"] and message["groupInfo"]["groupId"] != GROUP_ID:
- return
-
body = [l.strip() for l in message["message"].lower().splitlines()]
if len(body) == 0 or not body[0].startswith('!'):
- return
+ return []
- quote = Quote(timestamp=message["timestamp"], author=sender_number)
args = body[0].split(' ')
cmd = args[0][1:]
- if cmd in self.cmds:
- ret = self.cmds[cmd](sender_number, args, body)
- if 'err' in ret:
- self.send(f'ERROR: {ret["err"]}')
- else:
- if not 'msg' in ret:
- print(ret)
- self.send(ret['msg'],
- attachment=ret.get('attachment', None),
- quote=quote if self.quote_cmd else None)
- else:
- self.send(
- 'ERROR: unknown cmd. Enter !help for a list of commands.')
+ if not cmd in self.cmds:
+ return [
+ Reply(
+ 'ERROR: unknown cmd. Enter !help for a list of commands.')
+ ]
+
+ ret = self.cmds[cmd](sender_number, args, body)
+ if 'err' in ret:
+ return [Reply(f'ERROR: {ret["err"]}')]
+
+ assert 'msg' in ret
self.save_state()
+ return [Reply(ret['msg'], attachment=ret.get('attachment', None))]
- def run_scheduled_cmds(self):
+ def run_scheduled_cmds(self) -> list[Reply]:
"""Progress the scheduled commands"""
self.record_changes = False
+ replies: list[Reply] = []
+
now = datetime.now().date()
week_delta = timedelta(days=7)
for name, cmd in self.scheduled_cmds.items():
@@ -1108,31 +1096,56 @@ class Geldschieberbot:
d = d + week_delta
if d <= now:
- self.send("Running {} command {} for {} triggered on {}\n".
+ replies.append(
+ Reply("Running {} command {} for {} triggered on {}\n".
format(cmd["schedule"],
name, self.num2name[cmd["sender"]],
- d.isoformat()))
+ d.isoformat())))
ret = self.cmds[cmd["cmd"][0]](cmd["sender"], cmd["cmd"],
"")
if 'err' in ret:
- self.send("ERROR: " + ret['err'])
+ replies.append(Reply(f'ERROR: {ret["err"]}'))
else:
- self.send(ret['msg'],
- attachment=ret.get('attachment', None))
+ replies.append(
+ Reply(ret['msg'],
+ attachment=ret.get('attachment', None)))
cmd["last_time"] = d.isoformat()
else:
break
+ return replies
+
+
+def send(cmd, msgs: list[Reply], quote: Quote = None):
+ """Send a message with optional attachment"""
+ for msg in msgs:
+ if msg.attachment:
+ cmd += f' -a {msg.attachment}'
+ if quote:
+ cmd += f' --quote-timestamp={quote.timestamp} --quote-author={quote.author}'
+ subprocess.run(cmd.split(' '), input=msg.msg.encode(), check=False)
+
def main():
+ # Path where our data is stored persistent on disk
+ state_file = os.environ["GSB_STATE_FILE"]
+
+ group_id = os.environ["GSB_GROUP_ID"]
+
+ send_cmd = os.environ["GSB_SEND_CMD"] or DEFAULT_SEND_CMD
+
parser = argparse.ArgumentParser()
parser.add_argument('-d',
'--dry-run',
help='do not persist changes',
action='store_true')
+ parser.add_argument('-q',
+ '--quiet',
+ help='send no messages',
+ action='store_true')
parser.add_argument('-nq',
'--no-quote',
help='not quote the message causing the reply',
@@ -1142,19 +1155,32 @@ def main():
if args.dry_run:
print("Dry Run no changes will apply!")
- bot = Geldschieberbot(dry_run=args.dry_run, quote_cmd=not args.no_quote)
+ bot = Geldschieberbot(state_file, dry_run=args.dry_run)
# Read cmds from stdin
for l in sys.stdin.read().splitlines():
try:
- message = json.loads(l)["envelope"]
+ envelope = json.loads(l)["envelope"]
except json.JSONDecodeError:
print(datetime.now(), l, "not valid json")
continue
- bot.handle(message)
+ if not "dataMessage" in envelope or not envelope[
+ "dataMessage"] or not envelope["dataMessage"]["message"]:
+ continue
+
+ message = envelope["dataMessage"]
+ if not message[
+ "groupInfo"] or message["groupInfo"]["groupId"] != group_id:
+ continue
+
+ quote = Quote(timestamp=message["timestamp"],
+ author=envelope["source"])
+ msgs = bot.handle(envelope)
+ send(send_cmd, msgs, quote=quote if not args.no_quote else None)
- bot.run_scheduled_cmds()
+ msgs = bot.run_scheduled_cmds()
+ send(send_cmd, msgs)
if __name__ == "__main__":