deltabot-cli 0.2.0__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/PKG-INFO +7 -19
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/README.md +5 -15
- deltabot-cli-1.0.0/deltabot_cli/__init__.py +8 -0
- deltabot-cli-1.0.0/deltabot_cli/_utils.py +213 -0
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/deltabot_cli/cli.py +102 -43
- deltabot-cli-1.0.0/deltabot_cli/client.py +187 -0
- deltabot-cli-1.0.0/deltabot_cli/const.py +127 -0
- deltabot-cli-1.0.0/deltabot_cli/events.py +290 -0
- deltabot-cli-1.0.0/deltabot_cli/rpc.py +145 -0
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/deltabot_cli.egg-info/PKG-INFO +7 -19
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/deltabot_cli.egg-info/SOURCES.txt +4 -0
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/deltabot_cli.egg-info/requires.txt +1 -4
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/pyproject.toml +3 -6
- deltabot-cli-0.2.0/deltabot_cli/__init__.py +0 -6
- deltabot-cli-0.2.0/deltabot_cli/_utils.py +0 -36
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/LICENSE +0 -0
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/deltabot_cli.egg-info/dependency_links.txt +0 -0
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/deltabot_cli.egg-info/top_level.txt +0 -0
- {deltabot-cli-0.2.0 → deltabot-cli-1.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deltabot-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Library to speedup Delta Chat bot development
|
|
5
5
|
Author-email: adbenitez <adbenitez@hispanilandia.net>
|
|
6
6
|
Keywords: deltachat,bot,deltabot-cli
|
|
@@ -10,7 +10,7 @@ Classifier: Intended Audience :: Developers
|
|
|
10
10
|
Requires-Python: >=3.7
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: deltachat-rpc-
|
|
13
|
+
Requires-Dist: deltachat-rpc-server>=1.133.2
|
|
14
14
|
Requires-Dist: appdirs>=1.4.4
|
|
15
15
|
Requires-Dist: rich>=12.6.0
|
|
16
16
|
Requires-Dist: qrcode>=7.4.2
|
|
@@ -21,8 +21,6 @@ Requires-Dist: isort; extra == "dev"
|
|
|
21
21
|
Requires-Dist: pylint; extra == "dev"
|
|
22
22
|
Requires-Dist: pylama; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest; extra == "dev"
|
|
24
|
-
Provides-Extra: server
|
|
25
|
-
Requires-Dist: deltachat-rpc-server>=1.127.0; extra == "server"
|
|
26
24
|
|
|
27
25
|
# deltabot-cli for Python
|
|
28
26
|
|
|
@@ -32,21 +30,15 @@ Requires-Dist: deltachat-rpc-server>=1.127.0; extra == "server"
|
|
|
32
30
|
|
|
33
31
|
Library to speedup Delta Chat bot development.
|
|
34
32
|
|
|
35
|
-
With this library you can focus on writing your event/message processing logic and let us handle the
|
|
36
|
-
process of creating the bot CLI.
|
|
33
|
+
With this library you can focus on writing your event/message processing logic and let us handle the
|
|
34
|
+
repetitive process of creating the bot CLI.
|
|
37
35
|
|
|
38
36
|
## Install
|
|
39
37
|
|
|
40
38
|
```sh
|
|
41
|
-
pip install deltabot-cli
|
|
39
|
+
pip install deltabot-cli
|
|
42
40
|
```
|
|
43
41
|
|
|
44
|
-
### Installing deltachat-rpc-server
|
|
45
|
-
|
|
46
|
-
This package depends on a standalone Delta Chat RPC server `deltachat-rpc-server` program.
|
|
47
|
-
To install it check:
|
|
48
|
-
https://github.com/deltachat/deltachat-core-rust/tree/master/deltachat-rpc-server
|
|
49
|
-
|
|
50
42
|
## Usage
|
|
51
43
|
|
|
52
44
|
Example echo-bot written with deltabot-cli:
|
|
@@ -57,16 +49,14 @@ from deltabot_cli import BotCli, events
|
|
|
57
49
|
|
|
58
50
|
cli = BotCli("echobot")
|
|
59
51
|
|
|
60
|
-
|
|
61
52
|
@cli.on(events.RawEvent)
|
|
62
53
|
def log_event(event):
|
|
63
54
|
logging.info(event)
|
|
64
55
|
|
|
65
|
-
|
|
66
56
|
@cli.on(events.NewMessage)
|
|
67
57
|
def echo(event):
|
|
68
|
-
event.
|
|
69
|
-
|
|
58
|
+
msg = event.msg
|
|
59
|
+
event.rpc.misc_send_text_message(event.accid, msg.chat_id, msg.text)
|
|
70
60
|
|
|
71
61
|
if __name__ == "__main__":
|
|
72
62
|
cli.start()
|
|
@@ -76,5 +66,3 @@ If you run the above script you will have a bot CLI, that allows to configure an
|
|
|
76
66
|
A progress bar is displayed while the bot is configuring, and logs are pretty-printed.
|
|
77
67
|
|
|
78
68
|
For more examples check the [examples](https://github.com/deltachat-bot/deltabot-cli-py/tree/master/examples) folder.
|
|
79
|
-
|
|
80
|
-
**Note:** deltabot-cli uses [deltachat-rpc-client](https://github.com/deltachat/deltachat-core-rust/tree/master/deltachat-rpc-client) library, check its documentation and examples to better understand how to use deltabot-cli.
|
|
@@ -6,21 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
Library to speedup Delta Chat bot development.
|
|
8
8
|
|
|
9
|
-
With this library you can focus on writing your event/message processing logic and let us handle the
|
|
10
|
-
process of creating the bot CLI.
|
|
9
|
+
With this library you can focus on writing your event/message processing logic and let us handle the
|
|
10
|
+
repetitive process of creating the bot CLI.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
pip install deltabot-cli
|
|
15
|
+
pip install deltabot-cli
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
### Installing deltachat-rpc-server
|
|
19
|
-
|
|
20
|
-
This package depends on a standalone Delta Chat RPC server `deltachat-rpc-server` program.
|
|
21
|
-
To install it check:
|
|
22
|
-
https://github.com/deltachat/deltachat-core-rust/tree/master/deltachat-rpc-server
|
|
23
|
-
|
|
24
18
|
## Usage
|
|
25
19
|
|
|
26
20
|
Example echo-bot written with deltabot-cli:
|
|
@@ -31,16 +25,14 @@ from deltabot_cli import BotCli, events
|
|
|
31
25
|
|
|
32
26
|
cli = BotCli("echobot")
|
|
33
27
|
|
|
34
|
-
|
|
35
28
|
@cli.on(events.RawEvent)
|
|
36
29
|
def log_event(event):
|
|
37
30
|
logging.info(event)
|
|
38
31
|
|
|
39
|
-
|
|
40
32
|
@cli.on(events.NewMessage)
|
|
41
33
|
def echo(event):
|
|
42
|
-
event.
|
|
43
|
-
|
|
34
|
+
msg = event.msg
|
|
35
|
+
event.rpc.misc_send_text_message(event.accid, msg.chat_id, msg.text)
|
|
44
36
|
|
|
45
37
|
if __name__ == "__main__":
|
|
46
38
|
cli.start()
|
|
@@ -50,5 +42,3 @@ If you run the above script you will have a bot CLI, that allows to configure an
|
|
|
50
42
|
A progress bar is displayed while the bot is configuring, and logs are pretty-printed.
|
|
51
43
|
|
|
52
44
|
For more examples check the [examples](https://github.com/deltachat-bot/deltabot-cli-py/tree/master/examples) folder.
|
|
53
|
-
|
|
54
|
-
**Note:** deltabot-cli uses [deltachat-rpc-client](https://github.com/deltachat/deltachat-core-rust/tree/master/deltachat-rpc-client) library, check its documentation and examples to better understand how to use deltabot-cli.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Utilities"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from threading import Thread
|
|
7
|
+
from typing import TYPE_CHECKING, Callable, Iterable, Optional, Tuple, Type, Union
|
|
8
|
+
|
|
9
|
+
from rich.progress import track
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .client import Client
|
|
13
|
+
from .events import EventFilter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigProgressBar:
|
|
17
|
+
"""Display a configuration Progress Bar."""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self.progress = 0
|
|
21
|
+
self.total = 1000
|
|
22
|
+
self.tracker = track(range(self.total), description="Configuring...")
|
|
23
|
+
|
|
24
|
+
def set_progress(self, progress: int) -> None:
|
|
25
|
+
"""Set the progress bar progress."""
|
|
26
|
+
if progress == 0:
|
|
27
|
+
self.progress = -1
|
|
28
|
+
else:
|
|
29
|
+
progress = progress - self.progress
|
|
30
|
+
for _ in zip(self.tracker, range(progress)):
|
|
31
|
+
pass
|
|
32
|
+
self.progress += progress
|
|
33
|
+
|
|
34
|
+
def close(self) -> None:
|
|
35
|
+
"""Finish the progress bar"""
|
|
36
|
+
self.tracker.close()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AttrDict(dict):
|
|
40
|
+
"""Dictionary that allows accessing values using the "dot notation" as attributes."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
43
|
+
super().__init__(
|
|
44
|
+
{
|
|
45
|
+
_camel_to_snake(key): to_attrdict(value)
|
|
46
|
+
for key, value in dict(*args, **kwargs).items()
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __getattr__(self, attr):
|
|
51
|
+
if attr in self:
|
|
52
|
+
return self[attr]
|
|
53
|
+
raise AttributeError("Attribute not found: " + str(attr))
|
|
54
|
+
|
|
55
|
+
def __setattr__(self, attr, val):
|
|
56
|
+
if attr in self:
|
|
57
|
+
raise AttributeError("Attribute-style access is read only")
|
|
58
|
+
super().__setattr__(attr, val)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _camel_to_snake(name: str) -> str:
|
|
62
|
+
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
63
|
+
name = re.sub("__([A-Z])", r"_\1", name)
|
|
64
|
+
name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name)
|
|
65
|
+
return name.lower()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def to_attrdict(obj):
|
|
69
|
+
if isinstance(obj, AttrDict):
|
|
70
|
+
return obj
|
|
71
|
+
if isinstance(obj, dict):
|
|
72
|
+
return AttrDict(obj)
|
|
73
|
+
if isinstance(obj, list):
|
|
74
|
+
return [to_attrdict(elem) for elem in obj]
|
|
75
|
+
return obj
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def run_client_cli(
|
|
79
|
+
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
|
80
|
+
argv: Optional[list] = None,
|
|
81
|
+
**kwargs,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Run a simple command line app, using the given hooks.
|
|
84
|
+
|
|
85
|
+
Extra keyword arguments are passed to the internal Rpc object.
|
|
86
|
+
"""
|
|
87
|
+
from .client import Client # noqa
|
|
88
|
+
|
|
89
|
+
_run_cli(Client, hooks, argv, **kwargs)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def run_bot_cli(
|
|
93
|
+
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
|
94
|
+
argv: Optional[list] = None,
|
|
95
|
+
**kwargs,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Run a simple bot command line using the given hooks.
|
|
98
|
+
|
|
99
|
+
Extra keyword arguments are passed to the internal Rpc object.
|
|
100
|
+
"""
|
|
101
|
+
from .client import Bot # noqa
|
|
102
|
+
|
|
103
|
+
_run_cli(Bot, hooks, argv, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _run_cli(
|
|
107
|
+
client_type: Type["Client"],
|
|
108
|
+
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
|
109
|
+
argv: Optional[list] = None,
|
|
110
|
+
**kwargs,
|
|
111
|
+
) -> None:
|
|
112
|
+
from .rpc import Rpc # noqa
|
|
113
|
+
|
|
114
|
+
if argv is None:
|
|
115
|
+
argv = sys.argv
|
|
116
|
+
|
|
117
|
+
parser = argparse.ArgumentParser(prog=argv[0] if argv else None)
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"accounts_dir",
|
|
120
|
+
help="accounts folder (default: current working directory)",
|
|
121
|
+
nargs="?",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument("--email", action="store", help="email address")
|
|
124
|
+
parser.add_argument("--password", action="store", help="password")
|
|
125
|
+
args = parser.parse_args(argv[1:])
|
|
126
|
+
|
|
127
|
+
with Rpc(accounts_dir=args.accounts_dir, **kwargs) as rpc:
|
|
128
|
+
core_version = rpc.get_system_info().deltachat_core_version
|
|
129
|
+
accounts = rpc.get_all_account_ids()
|
|
130
|
+
accid = accounts[0] if accounts else rpc.add_account()
|
|
131
|
+
|
|
132
|
+
client = client_type(rpc, hooks)
|
|
133
|
+
client.logger.debug("Running deltachat core %s", core_version)
|
|
134
|
+
if not rpc.is_configured(accid):
|
|
135
|
+
assert args.email, "Account is not configured and email must be provided"
|
|
136
|
+
assert args.password, "Account is not configured and password must be provided"
|
|
137
|
+
configure_thread = Thread(
|
|
138
|
+
target=client.configure, args=(accid, args.email, args.password)
|
|
139
|
+
)
|
|
140
|
+
configure_thread.start()
|
|
141
|
+
client.run_forever()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def extract_addr(text: str) -> str:
|
|
145
|
+
"""extract email address from the given text."""
|
|
146
|
+
match = re.match(r".*\((.+@.+)\)", text)
|
|
147
|
+
if match:
|
|
148
|
+
text = match.group(1)
|
|
149
|
+
text = text.rstrip(".")
|
|
150
|
+
return text.strip()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def parse_system_image_changed(text: str) -> Optional[Tuple[str, bool]]:
|
|
154
|
+
"""return image changed/deleted info from parsing the given system message text."""
|
|
155
|
+
text = text.lower()
|
|
156
|
+
match = re.match(r"group image (changed|deleted) by (.+).", text)
|
|
157
|
+
if match:
|
|
158
|
+
action, actor = match.groups()
|
|
159
|
+
return (extract_addr(actor), action == "deleted")
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def parse_system_title_changed(text: str) -> Optional[Tuple[str, str]]:
|
|
164
|
+
text = text.lower()
|
|
165
|
+
match = re.match(r'group name changed from "(.+)" to ".+" by (.+).', text)
|
|
166
|
+
if match:
|
|
167
|
+
old_title, actor = match.groups()
|
|
168
|
+
return (extract_addr(actor), old_title)
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def parse_system_add_remove(text: str) -> Optional[Tuple[str, str, str]]:
|
|
173
|
+
"""return add/remove info from parsing the given system message text.
|
|
174
|
+
|
|
175
|
+
returns a (action, affected, actor) tuple.
|
|
176
|
+
"""
|
|
177
|
+
# You removed member a@b.
|
|
178
|
+
# You added member a@b.
|
|
179
|
+
# Member Me (x@y) removed by a@b.
|
|
180
|
+
# Member x@y added by a@b
|
|
181
|
+
# Member With space (tmp1@x.org) removed by tmp2@x.org.
|
|
182
|
+
# Member With space (tmp1@x.org) removed by Another member (tmp2@x.org).",
|
|
183
|
+
# Group left by some one (tmp1@x.org).
|
|
184
|
+
# Group left by tmp1@x.org.
|
|
185
|
+
text = text.lower()
|
|
186
|
+
|
|
187
|
+
match = re.match(r"member (.+) (removed|added) by (.+)", text)
|
|
188
|
+
if match:
|
|
189
|
+
affected, action, actor = match.groups()
|
|
190
|
+
return action, extract_addr(affected), extract_addr(actor)
|
|
191
|
+
|
|
192
|
+
match = re.match(r"you (removed|added) member (.+)", text)
|
|
193
|
+
if match:
|
|
194
|
+
action, affected = match.groups()
|
|
195
|
+
return action, extract_addr(affected), "me"
|
|
196
|
+
|
|
197
|
+
if text.startswith("group left by "):
|
|
198
|
+
addr = extract_addr(text[13:])
|
|
199
|
+
if addr:
|
|
200
|
+
return "removed", addr, addr
|
|
201
|
+
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def parse_docstring(txt) -> tuple:
|
|
206
|
+
"""parse docstring, returning a tuple with short and long description"""
|
|
207
|
+
description = txt
|
|
208
|
+
i = txt.find(".")
|
|
209
|
+
if i == -1:
|
|
210
|
+
help_ = txt
|
|
211
|
+
else:
|
|
212
|
+
help_ = txt[: i + 1]
|
|
213
|
+
return help_, description
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Command Line Interface application"""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
4
5
|
import time
|
|
@@ -8,11 +9,13 @@ from typing import Callable, Optional, Set, Union
|
|
|
8
9
|
|
|
9
10
|
import qrcode
|
|
10
11
|
from appdirs import user_config_dir
|
|
11
|
-
from deltachat_rpc_client import AttrDict, Bot, DeltaChat, EventType, Rpc, const, events
|
|
12
|
-
from deltachat_rpc_client.rpc import JsonRpcError
|
|
13
12
|
from rich.logging import RichHandler
|
|
14
13
|
|
|
15
|
-
from ._utils import ConfigProgressBar, parse_docstring
|
|
14
|
+
from ._utils import AttrDict, ConfigProgressBar, parse_docstring
|
|
15
|
+
from .client import Bot
|
|
16
|
+
from .const import COMMAND_PREFIX, EventType
|
|
17
|
+
from .events import EventFilter, HookCollection, NewMessage, RawEvent
|
|
18
|
+
from .rpc import JsonRpcError, Rpc
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class BotCli:
|
|
@@ -28,12 +31,12 @@ class BotCli:
|
|
|
28
31
|
self.log_level = log_level
|
|
29
32
|
self._parser = ArgumentParser(app_name)
|
|
30
33
|
self._subparsers = self._parser.add_subparsers(title="subcommands")
|
|
31
|
-
self._hooks =
|
|
34
|
+
self._hooks = HookCollection()
|
|
32
35
|
self._init_hooks: Set[Callable[[Bot, Namespace], None]] = set()
|
|
33
36
|
self._start_hooks: Set[Callable[[Bot, Namespace], None]] = set()
|
|
34
37
|
self._bot: Bot
|
|
35
38
|
|
|
36
|
-
def on(self, event: Union[type,
|
|
39
|
+
def on(self, event: Union[type, EventFilter]) -> Callable: # noqa
|
|
37
40
|
"""Register decorated function as listener for the given event."""
|
|
38
41
|
return self._hooks.on(event)
|
|
39
42
|
|
|
@@ -62,9 +65,9 @@ class BotCli:
|
|
|
62
65
|
func(bot, args)
|
|
63
66
|
|
|
64
67
|
def is_not_known_command(self, event: AttrDict) -> bool:
|
|
65
|
-
if not event.command.startswith(
|
|
68
|
+
if not event.command.startswith(COMMAND_PREFIX):
|
|
66
69
|
return True
|
|
67
|
-
for hook in self._bot._hooks.get(
|
|
70
|
+
for hook in self._bot._hooks.get(NewMessage, []): # pylint:disable=W0212
|
|
68
71
|
if event.command == hook[1].command:
|
|
69
72
|
return False
|
|
70
73
|
return True
|
|
@@ -77,7 +80,7 @@ class BotCli:
|
|
|
77
80
|
|
|
78
81
|
def add_subcommand(
|
|
79
82
|
self,
|
|
80
|
-
func: Callable[[Bot, Namespace], None],
|
|
83
|
+
func: Callable[["BotCli", Bot, Namespace], None],
|
|
81
84
|
**kwargs,
|
|
82
85
|
) -> ArgumentParser:
|
|
83
86
|
"""Add a subcommand to the CLI."""
|
|
@@ -123,7 +126,7 @@ class BotCli:
|
|
|
123
126
|
config_parser.add_argument("option", help="option name", nargs="?")
|
|
124
127
|
config_parser.add_argument("value", help="option value to set", nargs="?")
|
|
125
128
|
|
|
126
|
-
self.add_subcommand(
|
|
129
|
+
self.add_subcommand(_serve_cmd, name="serve")
|
|
127
130
|
self.add_subcommand(_qr_cmd, name="qr")
|
|
128
131
|
|
|
129
132
|
def get_accounts_dir(self, args: Namespace) -> str:
|
|
@@ -132,6 +135,28 @@ class BotCli:
|
|
|
132
135
|
os.makedirs(args.config_dir)
|
|
133
136
|
return os.path.join(args.config_dir, "accounts")
|
|
134
137
|
|
|
138
|
+
def get_or_create_account(self, rpc: Rpc, addr: str) -> int:
|
|
139
|
+
"""Get account for address, if no account exists create a new one."""
|
|
140
|
+
accid = self.get_account(rpc, addr)
|
|
141
|
+
if not accid:
|
|
142
|
+
accid = rpc.add_account()
|
|
143
|
+
rpc.set_config(accid, "addr", addr)
|
|
144
|
+
return accid
|
|
145
|
+
|
|
146
|
+
def get_account(self, rpc: Rpc, addr: str) -> int:
|
|
147
|
+
"""Get account id for address.
|
|
148
|
+
If no account exists with the given address, zero is returned.
|
|
149
|
+
"""
|
|
150
|
+
for accid in rpc.get_all_account_ids():
|
|
151
|
+
if addr == self.get_address(rpc, accid):
|
|
152
|
+
return accid
|
|
153
|
+
return 0
|
|
154
|
+
|
|
155
|
+
def get_address(self, rpc: Rpc, accid: int) -> str:
|
|
156
|
+
if rpc.is_configured(accid):
|
|
157
|
+
return rpc.get_config(accid, "configured_addr")
|
|
158
|
+
return rpc.get_config(accid, "addr")
|
|
159
|
+
|
|
135
160
|
def start(self) -> None:
|
|
136
161
|
"""Start running the bot and processing incoming messages."""
|
|
137
162
|
self.init_parser()
|
|
@@ -144,37 +169,43 @@ class BotCli:
|
|
|
144
169
|
accounts_dir = self.get_accounts_dir(args)
|
|
145
170
|
|
|
146
171
|
with Rpc(accounts_dir=accounts_dir) as rpc:
|
|
147
|
-
|
|
148
|
-
accounts = deltachat.get_all_accounts()
|
|
149
|
-
account = accounts[0] if accounts else deltachat.add_account()
|
|
150
|
-
|
|
151
|
-
self._bot = Bot(account, self._hooks)
|
|
172
|
+
self._bot = Bot(rpc, self._hooks)
|
|
152
173
|
self._on_init(self._bot, args)
|
|
153
174
|
|
|
154
|
-
core_version =
|
|
175
|
+
core_version = rpc.get_system_info().deltachat_core_version
|
|
155
176
|
self._bot.logger.info("Running deltachat core %s", core_version)
|
|
156
177
|
if "cmd" in args:
|
|
157
|
-
args.cmd(self._bot, args)
|
|
178
|
+
args.cmd(self, self._bot, args)
|
|
158
179
|
else:
|
|
159
180
|
self._parser.parse_args(["-h"])
|
|
160
181
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
except Exception as ex: # pylint:disable=W0703
|
|
171
|
-
logging.exception(ex)
|
|
172
|
-
time.sleep(5)
|
|
182
|
+
|
|
183
|
+
def _serve_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
184
|
+
"""start processing messages"""
|
|
185
|
+
rpc = bot.rpc
|
|
186
|
+
accounts = rpc.get_all_account_ids()
|
|
187
|
+
addrs = []
|
|
188
|
+
for accid in accounts:
|
|
189
|
+
if rpc.is_configured(accid):
|
|
190
|
+
addrs.append(rpc.get_config(accid, "configured_addr"))
|
|
173
191
|
else:
|
|
174
|
-
logging.error("
|
|
192
|
+
logging.error(f"account {accid} not configured")
|
|
193
|
+
if len(addrs) != 0:
|
|
194
|
+
logging.info(f"Listening at: {', '.join(addrs)}")
|
|
195
|
+
cli._on_start(bot, args) # noqa
|
|
196
|
+
while True:
|
|
197
|
+
try:
|
|
198
|
+
bot.run_forever()
|
|
199
|
+
except KeyboardInterrupt:
|
|
200
|
+
return
|
|
201
|
+
except Exception as ex: # pylint:disable=W0703
|
|
202
|
+
logging.exception(ex)
|
|
203
|
+
time.sleep(5)
|
|
204
|
+
else:
|
|
205
|
+
logging.error("There are no configured accounts to serve")
|
|
175
206
|
|
|
176
207
|
|
|
177
|
-
def _init_cmd(bot: Bot, args: Namespace) -> None:
|
|
208
|
+
def _init_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
178
209
|
"""initialize the account"""
|
|
179
210
|
|
|
180
211
|
def on_progress(event: AttrDict) -> None:
|
|
@@ -184,13 +215,15 @@ def _init_cmd(bot: Bot, args: Namespace) -> None:
|
|
|
184
215
|
|
|
185
216
|
def configure() -> None:
|
|
186
217
|
try:
|
|
187
|
-
bot.configure(email=args.addr, password=args.password)
|
|
218
|
+
bot.configure(accid, email=args.addr, password=args.password)
|
|
188
219
|
except JsonRpcError as err:
|
|
189
220
|
logging.error(err)
|
|
190
221
|
|
|
222
|
+
accid = cli.get_or_create_account(bot.rpc, args.addr)
|
|
223
|
+
|
|
191
224
|
logging.info("Starting configuration process...")
|
|
192
225
|
pbar = ConfigProgressBar()
|
|
193
|
-
bot.add_hook(on_progress,
|
|
226
|
+
bot.add_hook(on_progress, RawEvent(EventType.CONFIGURE_PROGRESS))
|
|
194
227
|
task = Thread(target=configure)
|
|
195
228
|
task.start()
|
|
196
229
|
bot.run_until(lambda _: pbar.progress in (-1, pbar.total))
|
|
@@ -202,28 +235,54 @@ def _init_cmd(bot: Bot, args: Namespace) -> None:
|
|
|
202
235
|
logging.info("Account configured successfully.")
|
|
203
236
|
|
|
204
237
|
|
|
205
|
-
def _config_cmd(bot: Bot, args: Namespace) -> None:
|
|
238
|
+
def _config_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
206
239
|
"""set/get account configuration values"""
|
|
240
|
+
accounts = bot.rpc.get_all_account_ids()
|
|
241
|
+
for accid in accounts:
|
|
242
|
+
addr = cli.get_address(bot.rpc, accid)
|
|
243
|
+
print(f"Account #{accid} ({addr}):")
|
|
244
|
+
_config_cmd_for_acc(bot.rpc, accid, args)
|
|
245
|
+
print("")
|
|
246
|
+
if not accounts:
|
|
247
|
+
logging.error("There are no accounts yet, add a new account using the init subcommand")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _config_cmd_for_acc(rpc: Rpc, accid: int, args: Namespace) -> None:
|
|
207
251
|
if args.value:
|
|
208
|
-
|
|
252
|
+
rpc.set_config(accid, args.option, args.value)
|
|
209
253
|
|
|
210
254
|
if args.option:
|
|
211
255
|
try:
|
|
212
|
-
value =
|
|
256
|
+
value = rpc.get_config(accid, args.option)
|
|
213
257
|
print(f"{args.option}={value!r}")
|
|
214
258
|
except JsonRpcError:
|
|
215
259
|
logging.error("Unknown configuration option: %s", args.option)
|
|
216
260
|
else:
|
|
217
|
-
keys =
|
|
261
|
+
keys = rpc.get_config(accid, "sys.config_keys") or ""
|
|
218
262
|
for key in keys.split():
|
|
219
|
-
value =
|
|
263
|
+
value = rpc.get_config(accid, key)
|
|
220
264
|
print(f"{key}={value!r}")
|
|
221
265
|
|
|
222
266
|
|
|
223
|
-
def _qr_cmd(bot: Bot, _args: Namespace) -> None:
|
|
267
|
+
def _qr_cmd(cli: BotCli, bot: Bot, _args: Namespace) -> None:
|
|
224
268
|
"""get bot's verification QR"""
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
269
|
+
accounts = bot.rpc.get_all_account_ids()
|
|
270
|
+
for accid in accounts:
|
|
271
|
+
addr = cli.get_address(bot.rpc, accid)
|
|
272
|
+
print(f"Account #{accid} ({addr}):")
|
|
273
|
+
_qr_cmd_for_acc(bot.rpc, accid)
|
|
274
|
+
print("")
|
|
275
|
+
if not accounts:
|
|
276
|
+
logging.error("There are no accounts yet, add a new account using the init subcommand")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _qr_cmd_for_acc(rpc: Rpc, accid: int) -> None:
|
|
280
|
+
"""get bot's verification QR"""
|
|
281
|
+
if rpc.is_configured(accid):
|
|
282
|
+
qrdata, _ = rpc.get_chat_securejoin_qr_code_svg(accid, None)
|
|
283
|
+
code = qrcode.QRCode()
|
|
284
|
+
code.add_data(qrdata)
|
|
285
|
+
code.print_ascii(invert=True)
|
|
286
|
+
print(qrdata)
|
|
287
|
+
else:
|
|
288
|
+
logging.error("account not configured")
|