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()
|