diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2022-11-08 15:15:43 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2022-11-08 15:15:43 +0100 |
| commit | ad1465d2e17cdcd5e7dafcc0f9550e95199c8cb7 (patch) | |
| tree | c8b2493dc4f52c6b5680dc4f2da66e97075135e5 | |
| parent | 3e80b30b9819aa5c94221e4776baa7d62ea5df88 (diff) | |
| download | geldschieberbot-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.py | 136 |
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__": |
