#!/usr/bin/env python3 # Copyright (c) 2023 Florian Fischer. All rights reserved. # # This file is part of geldschieberbot. # # geldschieberbot is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # geldschieberbot is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # geldschieberbot found in the LICENSE file. If not, # see . """Run geldschieberbot on all received messages and send replies""" import argparse from datetime import datetime import json import os import subprocess import sys import typing as T from geldschieberbot import Geldschieberbot from models import Reply, Quote def send(_cmd, msgs: list[Reply], quote: T.Optional[Quote] = None): """Send a message with optional attachment""" for msg in msgs: cmd = _cmd 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 die(msg: str, status=1): """Exit because an error ocurred""" print(msg, file=sys.stderr) sys.exit(status) def single_shot(bot: Geldschieberbot, send_cmd: str, no_quote: bool): """Handle all messages read from stdin and run scheduled commands""" # Read messages from stdin for line in sys.stdin.read().splitlines(): try: envelope = json.loads(line)['envelope'] except json.JSONDecodeError: print(datetime.now(), line, "not valid json") continue quote = None if not no_quote: quote = Quote(timestamp=envelope['timestamp'], author=envelope['source']) send(send_cmd, bot.handle(envelope), quote) send(send_cmd, bot.run_scheduled_cmds()) def dbus_loop(bot: Geldschieberbot, send_cmd: str, no_quote: bool): """Handle all messages read from stdin and run scheduled commands""" from pydbus import SessionBus # type: ignore from gi.repository import GLib from base64 import b64encode bus = SessionBus() loop = GLib.MainLoop() signal = bus.get('org.asamk.Signal') # NOTE: when daemon was started without explicit `-u USERNAME`, replace the line above with # signal = bus.get("org.asamk.Signal", "/org/asamk/Signal/_YOURPHONENUMBER") def on_msg_recv(timestamp, source, groupid, message, _): quote = None if not no_quote: quote = Quote(timestamp=timestamp, author=source) envelope = { "source": source, "dataMessage": { "timestamp": timestamp, "message": message, "groupInfo": { "groupId": b64encode(bytes(groupid)).decode() } } } logfile_path = f'msgs/msg{datetime.now().isoformat()}.log' with open(logfile_path, 'w', encoding='utf-8') as msglog: print(f'{envelope}', file=msglog) send(send_cmd, bot.handle(envelope), quote) bot.save_state() signal.onMessageReceived = on_msg_recv def run_scheduled_cmds(): send(send_cmd, bot.run_scheduled_cmds()) bot.save_state() return True GLib.timeout_add_seconds(30, run_scheduled_cmds) loop.run() def main(): """Read messages from stdin and send the bot's replies back""" parser = argparse.ArgumentParser() parser.add_argument('-d', '--dry-run', help='do not persist changes', action='store_true') parser.add_argument('-f', '--state-file', help='the state file') parser.add_argument('-g', '--group-id', help='the group id to listen to') parser.add_argument('--send-cmd', help='the shell command used to send messages') parser.add_argument('--dbus', action='store_true', help='receive messages via dbus in a loop') parser.add_argument('-nq', '--no-quote', help='not quote the message causing the reply', action='store_true') args = parser.parse_args() if args.dry_run: print("Dry Run no changes will apply!") state_path = args.state_file or os.environ['GSB_STATE_FILE'] if not state_path: die('A state path must be provided') send_cmd = args.send_cmd or os.environ['GSB_SEND_CMD'] if not send_cmd: die('A send command must be provided') group_id = args.group_id or os.environ['GSB_GROUP_ID'] if not group_id: die('A group id must be provided') with Geldschieberbot(state_path, group_id, dry_run=args.dry_run) as bot: if not args.dbus: single_shot(bot, send_cmd, args.no_quote) else: dbus_loop(bot, send_cmd, args.no_quote) if __name__ == "__main__": main()