deltabot-cli 3.1.1__tar.gz → 3.2.2__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-3.1.1/deltabot_cli.egg-info → deltabot-cli-3.2.2}/PKG-INFO +1 -1
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/cli.py +104 -35
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/client.py +20 -27
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/events.py +1 -13
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2/deltabot_cli.egg-info}/PKG-INFO +1 -1
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/.github/workflows/python-ci.yml +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/.gitignore +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/LICENSE +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/README.md +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/__init__.py +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/_utils.py +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/const.py +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/rpc.py +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/utils.py +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/SOURCES.txt +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/dependency_links.txt +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/requires.txt +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/top_level.txt +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/examples/echobot.py +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/examples/echobot_advanced.py +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/pylama.ini +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/pyproject.toml +0 -0
- {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/setup.cfg +0 -0
|
@@ -108,10 +108,16 @@ class BotCli:
|
|
|
108
108
|
self.add_generic_option(
|
|
109
109
|
"--config-dir",
|
|
110
110
|
"-c",
|
|
111
|
-
help="
|
|
111
|
+
help="program configuration folder (default: %(default)s)",
|
|
112
112
|
metavar="PATH",
|
|
113
113
|
default=config_dir,
|
|
114
114
|
)
|
|
115
|
+
self.add_generic_option(
|
|
116
|
+
"--account",
|
|
117
|
+
"-a",
|
|
118
|
+
help="operate over this account only when running any subcommand",
|
|
119
|
+
metavar="ADDR",
|
|
120
|
+
)
|
|
115
121
|
|
|
116
122
|
init_parser = self.add_subcommand(_init_cmd, name="init")
|
|
117
123
|
init_parser.add_argument("addr", help="the e-mail address to use")
|
|
@@ -123,6 +129,8 @@ class BotCli:
|
|
|
123
129
|
|
|
124
130
|
self.add_subcommand(_serve_cmd, name="serve")
|
|
125
131
|
self.add_subcommand(_qr_cmd, name="qr")
|
|
132
|
+
self.add_subcommand(_list_cmd, name="list")
|
|
133
|
+
self.add_subcommand(_remove_cmd, name="remove")
|
|
126
134
|
|
|
127
135
|
def get_accounts_dir(self, args: Namespace) -> str:
|
|
128
136
|
"""Get bot's account folder."""
|
|
@@ -142,9 +150,12 @@ class BotCli:
|
|
|
142
150
|
"""Get account id for address.
|
|
143
151
|
If no account exists with the given address, zero is returned.
|
|
144
152
|
"""
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
try:
|
|
154
|
+
return int(addr)
|
|
155
|
+
except ValueError:
|
|
156
|
+
for accid in rpc.get_all_account_ids():
|
|
157
|
+
if addr == self.get_address(rpc, accid):
|
|
158
|
+
return accid
|
|
148
159
|
return 0
|
|
149
160
|
|
|
150
161
|
def get_address(self, rpc: Rpc, accid: int) -> str:
|
|
@@ -175,35 +186,10 @@ class BotCli:
|
|
|
175
186
|
self._parser.parse_args(["-h"])
|
|
176
187
|
|
|
177
188
|
|
|
178
|
-
def _serve_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
179
|
-
"""start processing messages"""
|
|
180
|
-
rpc = bot.rpc
|
|
181
|
-
accounts = rpc.get_all_account_ids()
|
|
182
|
-
addrs = []
|
|
183
|
-
for accid in accounts:
|
|
184
|
-
if rpc.is_configured(accid):
|
|
185
|
-
addrs.append(rpc.get_config(accid, "configured_addr"))
|
|
186
|
-
else:
|
|
187
|
-
bot.logger.error(f"account {accid} not configured")
|
|
188
|
-
if len(addrs) != 0:
|
|
189
|
-
bot.logger.info(f"Listening at: {', '.join(addrs)}")
|
|
190
|
-
cli._on_start(bot, args) # noqa
|
|
191
|
-
while True:
|
|
192
|
-
try:
|
|
193
|
-
bot.run_forever()
|
|
194
|
-
except KeyboardInterrupt:
|
|
195
|
-
return
|
|
196
|
-
except Exception as ex: # pylint:disable=W0703
|
|
197
|
-
bot.logger.exception(ex)
|
|
198
|
-
time.sleep(5)
|
|
199
|
-
else:
|
|
200
|
-
bot.logger.error("There are no configured accounts to serve")
|
|
201
|
-
|
|
202
|
-
|
|
203
189
|
def _init_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
204
190
|
"""initialize the account"""
|
|
205
191
|
|
|
206
|
-
def on_progress(event: AttrDict) -> None:
|
|
192
|
+
def on_progress(bot: Bot, _accid: int, event: AttrDict) -> None:
|
|
207
193
|
if event.comment:
|
|
208
194
|
bot.logger.info(event.comment)
|
|
209
195
|
pbar.set_progress(event.progress)
|
|
@@ -214,7 +200,13 @@ def _init_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
|
214
200
|
except JsonRpcError as err:
|
|
215
201
|
bot.logger.error(err)
|
|
216
202
|
|
|
217
|
-
|
|
203
|
+
if args.account:
|
|
204
|
+
accid = cli.get_account(bot.rpc, args.account)
|
|
205
|
+
if not accid:
|
|
206
|
+
bot.logger.error(f"unknown account: {args.account!r}")
|
|
207
|
+
return
|
|
208
|
+
else:
|
|
209
|
+
accid = cli.get_or_create_account(bot.rpc, args.addr)
|
|
218
210
|
|
|
219
211
|
bot.logger.info("Starting configuration process...")
|
|
220
212
|
pbar = ConfigProgressBar()
|
|
@@ -230,9 +222,46 @@ def _init_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
|
230
222
|
bot.logger.info("Account configured successfully.")
|
|
231
223
|
|
|
232
224
|
|
|
225
|
+
def _serve_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
226
|
+
"""start processing messages"""
|
|
227
|
+
rpc = bot.rpc
|
|
228
|
+
if args.account:
|
|
229
|
+
accounts = [cli.get_account(rpc, args.account)]
|
|
230
|
+
if not accounts[0]:
|
|
231
|
+
bot.logger.error(f"unknown account: {args.account!r}")
|
|
232
|
+
return
|
|
233
|
+
else:
|
|
234
|
+
accounts = rpc.get_all_account_ids()
|
|
235
|
+
addrs = []
|
|
236
|
+
for accid in accounts:
|
|
237
|
+
if rpc.is_configured(accid):
|
|
238
|
+
addrs.append(rpc.get_config(accid, "configured_addr"))
|
|
239
|
+
else:
|
|
240
|
+
bot.logger.error(f"account {accid} not configured")
|
|
241
|
+
if len(addrs) != 0:
|
|
242
|
+
bot.logger.info(f"Listening at: {', '.join(addrs)}")
|
|
243
|
+
cli._on_start(bot, args) # noqa
|
|
244
|
+
while True:
|
|
245
|
+
try:
|
|
246
|
+
bot.run_forever(accounts[0] if args.account else 0)
|
|
247
|
+
except KeyboardInterrupt:
|
|
248
|
+
return
|
|
249
|
+
except Exception as ex: # pylint:disable=W0703
|
|
250
|
+
bot.logger.exception(ex)
|
|
251
|
+
time.sleep(5)
|
|
252
|
+
else:
|
|
253
|
+
bot.logger.error("There are no configured accounts to serve")
|
|
254
|
+
|
|
255
|
+
|
|
233
256
|
def _config_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
234
257
|
"""set/get account configuration values"""
|
|
235
|
-
|
|
258
|
+
if args.account:
|
|
259
|
+
accounts = [cli.get_account(bot.rpc, args.account)]
|
|
260
|
+
if not accounts[0]:
|
|
261
|
+
bot.logger.error(f"unknown account: {args.account!r}")
|
|
262
|
+
return
|
|
263
|
+
else:
|
|
264
|
+
accounts = bot.rpc.get_all_account_ids()
|
|
236
265
|
for accid in accounts:
|
|
237
266
|
addr = cli.get_address(bot.rpc, accid)
|
|
238
267
|
print(f"Account #{accid} ({addr}):")
|
|
@@ -259,9 +288,15 @@ def _config_cmd_for_acc(bot: Bot, accid: int, args: Namespace) -> None:
|
|
|
259
288
|
print(f"{key}={value!r}")
|
|
260
289
|
|
|
261
290
|
|
|
262
|
-
def _qr_cmd(cli: BotCli, bot: Bot,
|
|
291
|
+
def _qr_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
263
292
|
"""get bot's verification QR"""
|
|
264
|
-
|
|
293
|
+
if args.account:
|
|
294
|
+
accounts = [cli.get_account(bot.rpc, args.account)]
|
|
295
|
+
if not accounts[0]:
|
|
296
|
+
bot.logger.error(f"unknown account: {args.account!r}")
|
|
297
|
+
return
|
|
298
|
+
else:
|
|
299
|
+
accounts = bot.rpc.get_all_account_ids()
|
|
265
300
|
for accid in accounts:
|
|
266
301
|
addr = cli.get_address(bot.rpc, accid)
|
|
267
302
|
print(f"Account #{accid} ({addr}):")
|
|
@@ -278,7 +313,41 @@ def _qr_cmd_for_acc(bot: Bot, accid: int) -> None:
|
|
|
278
313
|
code = qrcode.QRCode()
|
|
279
314
|
code.add_data(qrdata)
|
|
280
315
|
code.print_ascii(invert=True)
|
|
281
|
-
fragment = qrdata.split(":", maxsplit=1)[1].replace("#", "&",
|
|
316
|
+
fragment = qrdata.split(":", maxsplit=1)[1].replace("#", "&", 1)
|
|
282
317
|
print(f"https://i.delta.chat/#{fragment}")
|
|
283
318
|
else:
|
|
284
319
|
bot.logger.error("account not configured")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _list_cmd(cli: BotCli, bot: Bot, _args: Namespace) -> None:
|
|
323
|
+
"""show a list of existing bot accounts"""
|
|
324
|
+
rpc = bot.rpc
|
|
325
|
+
accounts = rpc.get_all_account_ids()
|
|
326
|
+
for accid in accounts:
|
|
327
|
+
addr = cli.get_address(rpc, accid)
|
|
328
|
+
if not rpc.is_configured(accid):
|
|
329
|
+
addr = addr + " (not configured)"
|
|
330
|
+
print(f"#{accid} - {addr}")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _remove_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
|
|
334
|
+
"""remove Delta Chat accounts from the bot"""
|
|
335
|
+
if args.account:
|
|
336
|
+
accid = cli.get_account(bot.rpc, args.account)
|
|
337
|
+
if not accid:
|
|
338
|
+
bot.logger.error(f"unknown account: {args.account!r}")
|
|
339
|
+
return
|
|
340
|
+
else:
|
|
341
|
+
accounts = bot.rpc.get_all_account_ids()
|
|
342
|
+
if len(accounts) == 1:
|
|
343
|
+
accid = accounts[0]
|
|
344
|
+
else:
|
|
345
|
+
bot.logger.error(
|
|
346
|
+
"There are more than one account, to remove one of them, pass the account"
|
|
347
|
+
" address with -a/--account option"
|
|
348
|
+
)
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
addr = cli.get_address(bot.rpc, accid)
|
|
352
|
+
bot.rpc.remove_account(accid)
|
|
353
|
+
print(f"Account #{accid} ({addr}) removed successfully.")
|
|
@@ -34,7 +34,6 @@ class Client:
|
|
|
34
34
|
self.rpc = rpc
|
|
35
35
|
self.logger = logger or logging
|
|
36
36
|
self._hooks: Dict[type, Set[tuple]] = {}
|
|
37
|
-
self._should_process_messages = 0
|
|
38
37
|
self.add_hooks(hooks or [])
|
|
39
38
|
|
|
40
39
|
def add_hooks(self, hooks: Iterable[Tuple[HookCallback, Union[type, EventFilter]]]) -> None:
|
|
@@ -46,24 +45,12 @@ class Client:
|
|
|
46
45
|
if isinstance(event, type):
|
|
47
46
|
event = event()
|
|
48
47
|
assert isinstance(event, EventFilter)
|
|
49
|
-
self._should_process_messages += int(
|
|
50
|
-
isinstance(
|
|
51
|
-
event,
|
|
52
|
-
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
|
53
|
-
),
|
|
54
|
-
)
|
|
55
48
|
self._hooks.setdefault(type(event), set()).add((hook, event))
|
|
56
49
|
|
|
57
50
|
def remove_hook(self, hook: HookCallback, event: Union[type, EventFilter]) -> None:
|
|
58
51
|
"""Unregister hook from the given event filter."""
|
|
59
52
|
if isinstance(event, type):
|
|
60
53
|
event = event()
|
|
61
|
-
self._should_process_messages -= int(
|
|
62
|
-
isinstance(
|
|
63
|
-
event,
|
|
64
|
-
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
|
65
|
-
),
|
|
66
|
-
)
|
|
67
54
|
self._hooks.get(type(event), set()).remove((hook, event))
|
|
68
55
|
|
|
69
56
|
def configure(self, accid: int, email: str, password: str, **kwargs) -> None:
|
|
@@ -74,11 +61,13 @@ class Client:
|
|
|
74
61
|
self.rpc.configure(accid)
|
|
75
62
|
self.logger.debug(f"Account {accid} configured")
|
|
76
63
|
|
|
77
|
-
def run_forever(self) -> None:
|
|
78
|
-
"""Process events forever.
|
|
79
|
-
|
|
64
|
+
def run_forever(self, accid: int = 0) -> None:
|
|
65
|
+
"""Process events forever.
|
|
66
|
+
if accid != 0, only that account will be started.
|
|
67
|
+
"""
|
|
68
|
+
self.run_until(lambda _: False, accid)
|
|
80
69
|
|
|
81
|
-
def run_until(self, func: Callable[[AttrDict], bool]) -> AttrDict:
|
|
70
|
+
def run_until(self, func: Callable[[AttrDict], bool], accid: int = 0) -> AttrDict:
|
|
82
71
|
"""Process events until the given callable evaluates to True.
|
|
83
72
|
|
|
84
73
|
The callable should accept an AttrDict object representing the
|
|
@@ -86,10 +75,15 @@ class Client:
|
|
|
86
75
|
evaluates to True.
|
|
87
76
|
"""
|
|
88
77
|
self.logger.debug("Listening to incoming events...")
|
|
89
|
-
|
|
90
|
-
for accid in self.rpc.get_all_account_ids():
|
|
78
|
+
if accid:
|
|
91
79
|
if self.rpc.is_configured(accid):
|
|
80
|
+
self.rpc.start_io(accid)
|
|
92
81
|
self._process_messages(accid) # Process old messages.
|
|
82
|
+
else:
|
|
83
|
+
self.rpc.start_io_for_all_accounts()
|
|
84
|
+
for acc_id in self.rpc.get_all_account_ids():
|
|
85
|
+
if self.rpc.is_configured(acc_id):
|
|
86
|
+
self._process_messages(acc_id) # Process old messages.
|
|
93
87
|
while True:
|
|
94
88
|
raw_event = self.rpc.get_next_event()
|
|
95
89
|
accid = raw_event.context_id
|
|
@@ -173,14 +167,13 @@ class Client:
|
|
|
173
167
|
)
|
|
174
168
|
|
|
175
169
|
def _process_messages(self, accid: int) -> None:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
self.rpc.markseen_msgs(accid, [msgid])
|
|
170
|
+
for msgid in self.rpc.get_next_msgs(accid):
|
|
171
|
+
msg = self.rpc.get_message(accid, msgid)
|
|
172
|
+
if msg.from_id not in [SpecialContactId.SELF, SpecialContactId.DEVICE]:
|
|
173
|
+
self._on_new_msg(accid, msg)
|
|
174
|
+
if msg.is_info and msg.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE:
|
|
175
|
+
self._handle_info_msg(accid, msg)
|
|
176
|
+
self.rpc.set_config(accid, "last_msg_id", str(msgid))
|
|
184
177
|
|
|
185
178
|
|
|
186
179
|
class Bot(Client):
|
|
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
|
|
19
19
|
from ._utils import AttrDict
|
|
20
20
|
from .client import Bot
|
|
21
21
|
FilterCallback = Callable[["Bot", "AttrDict"], bool]
|
|
22
|
-
HookCallback = Callable[["Bot", "AttrDict"], None]
|
|
22
|
+
HookCallback = Callable[["Bot", int, "AttrDict"], None]
|
|
23
23
|
HookDecorator = Callable[[HookCallback], HookCallback]
|
|
24
24
|
|
|
25
25
|
|
|
@@ -103,9 +103,6 @@ class RawEvent(EventFilter):
|
|
|
103
103
|
class NewMessage(EventFilter):
|
|
104
104
|
"""Matches whenever a new message arrives.
|
|
105
105
|
|
|
106
|
-
Warning: registering a handler for this event will cause the messages
|
|
107
|
-
to be marked as read. Its usage is mainly intended for bots.
|
|
108
|
-
|
|
109
106
|
:param pattern: if set, this Pattern will be used to filter the message by its text
|
|
110
107
|
content.
|
|
111
108
|
:param command: If set, only match messages with the given command (ex. /help).
|
|
@@ -187,9 +184,6 @@ class NewMessage(EventFilter):
|
|
|
187
184
|
class MemberListChanged(EventFilter):
|
|
188
185
|
"""Matches when a group member is added or removed.
|
|
189
186
|
|
|
190
|
-
Warning: registering a handler for this event will cause the messages
|
|
191
|
-
to be marked as read. Its usage is mainly intended for bots.
|
|
192
|
-
|
|
193
187
|
:param added: If set to True only match if a member was added, if set to False
|
|
194
188
|
only match if a member was removed. If omitted both, member additions
|
|
195
189
|
and removals, will be matched.
|
|
@@ -218,9 +212,6 @@ class MemberListChanged(EventFilter):
|
|
|
218
212
|
class GroupImageChanged(EventFilter):
|
|
219
213
|
"""Matches when the group image is changed.
|
|
220
214
|
|
|
221
|
-
Warning: registering a handler for this event will cause the messages
|
|
222
|
-
to be marked as read. Its usage is mainly intended for bots.
|
|
223
|
-
|
|
224
215
|
:param deleted: If set to True only match if the image was deleted, if set to False
|
|
225
216
|
only match if a new image was set. If omitted both, image changes and
|
|
226
217
|
removals, will be matched.
|
|
@@ -249,9 +240,6 @@ class GroupImageChanged(EventFilter):
|
|
|
249
240
|
class GroupNameChanged(EventFilter):
|
|
250
241
|
"""Matches when the group name is changed.
|
|
251
242
|
|
|
252
|
-
Warning: registering a handler for this event will cause the messages
|
|
253
|
-
to be marked as read. Its usage is mainly intended for bots.
|
|
254
|
-
|
|
255
243
|
:param func: A Callable that should accept the bot and event as input parameter,
|
|
256
244
|
and return a bool value indicating whether the event should be dispatched or not.
|
|
257
245
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|