aboutsummaryrefslogtreecommitdiff
path: root/daemon.py
blob: 86db6008187ac9c07863abaa76162d4102f6550b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/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()