#!/usr/bin/env python3 """Daemon connecting signal-cli and geldschieberbot The old approach of running signal-cli receive in a loop and passing its output to geldschieberbot is not really feasable to deploy as a service. """ import argparse import pathlib import json import os import subprocess import sys from geldschieberbot import Geldschieberbot, Reply class SignalCli: """simple signal-cli jsonrpc wrapper This wrapper manages the lifetime and handles th communication with signal-cli. The signal-cli JSON RPC service documentation is available at: https://github.com/AsamK/signal-cli/wiki/JSON-RPC-service """ def __init__(self, group_id='', user=''): self.group_id = group_id self.user = user self.rpc_id = 0 signal_cli_cmd = 'signal-cli jsonRpc' if self.user: signal_cli_cmd += f' -u {user}' self.signal = subprocess.Popen(signal_cli_cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) def send(self, msgs: list[Reply], group_id=''): """Send one or more messages""" print(msgs) for msg in msgs: call_id = self.rpc_id self.rpc_id += 1 params = {'message': msg.msg} if group_id: params['groupId'] = group_id if msg.attachment: params['attachment'] = msg.attachment if msg.quote: params['quoteTimestamp'] = msg.quote.timestamp params['quoteAuthor'] = msg.quote.author method_call = { 'jsonrpc': '2.0', 'method': 'send', 'id': call_id, 'params': params } print(method_call) json.dump(method_call, self.signal.stdin) def receive(self) -> dict: """Consumes all JSON RPC message until a recv notification is received""" while True: line = self.signal.stdout.readline() data = json.loads(line) # method call response if 'id' in data: assert data['id'] <= self.rpc_id if 'error' in data: print( f'ERROR: {data["error"]["code"]} - {data["error"]["message"]}', file=sys.stderr) continue if data['method'] == 'receive': envelope = data['params']['envelope'] print(envelope) return envelope def main(): """Main entry point connecting signal-cli geldschieberbot""" # Load the environment parser = argparse.ArgumentParser() parser.add_argument('-sf', '--state', help='the geldschieberbot state file to use') parser.add_argument('-g', '--group-id', help='the signal group id to monitor') args = parser.parse_args() group_id = args.group_id or os.environ.get('GSB_GROUP_ID', None) if not group_id: print('ERROR: No group id provided', file=sys.stderr) sys.exit(1) _state_path = args.state or os.environ.get('GSB_STATE_FILE', None) if not _state_path: print('WARNING: No state file provided using "state.json"') _state_path = 'state.json' state_path = pathlib.Path(_state_path) import pdb # pdb.set_trace() bot = Geldschieberbot(state_path) # signal-cli init signal_cli = SignalCli(group_id) while True: msg = signal_cli.receive() if not 'dataMessage' in msg: continue signal_cli.send(bot.handle(msg), group_id=group_id) signal_cli.send(bot.run_scheduled_cmds(), group_id=group_id) bot.save_state() if __name__ == '__main__': main()