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.
Files changed (23) hide show
  1. {deltabot-cli-3.1.1/deltabot_cli.egg-info → deltabot-cli-3.2.2}/PKG-INFO +1 -1
  2. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/cli.py +104 -35
  3. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/client.py +20 -27
  4. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/events.py +1 -13
  5. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2/deltabot_cli.egg-info}/PKG-INFO +1 -1
  6. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/.github/workflows/python-ci.yml +0 -0
  7. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/.gitignore +0 -0
  8. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/LICENSE +0 -0
  9. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/README.md +0 -0
  10. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/__init__.py +0 -0
  11. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/_utils.py +0 -0
  12. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/const.py +0 -0
  13. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/rpc.py +0 -0
  14. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli/utils.py +0 -0
  15. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/SOURCES.txt +0 -0
  16. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/dependency_links.txt +0 -0
  17. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/requires.txt +0 -0
  18. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/deltabot_cli.egg-info/top_level.txt +0 -0
  19. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/examples/echobot.py +0 -0
  20. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/examples/echobot_advanced.py +0 -0
  21. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/pylama.ini +0 -0
  22. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/pyproject.toml +0 -0
  23. {deltabot-cli-3.1.1 → deltabot-cli-3.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deltabot-cli
3
- Version: 3.1.1
3
+ Version: 3.2.2
4
4
  Summary: Library to speedup Delta Chat bot development
5
5
  Author-email: adbenitez <adb@merlinux.eu>
6
6
  License: Mozilla Public License Version 2.0
@@ -108,10 +108,16 @@ class BotCli:
108
108
  self.add_generic_option(
109
109
  "--config-dir",
110
110
  "-c",
111
- help="Program configuration folder (default: %(default)s)",
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
- for accid in rpc.get_all_account_ids():
146
- if addr == self.get_address(rpc, accid):
147
- return accid
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
- accid = cli.get_or_create_account(bot.rpc, args.addr)
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
- accounts = bot.rpc.get_all_account_ids()
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, _args: Namespace) -> None:
291
+ def _qr_cmd(cli: BotCli, bot: Bot, args: Namespace) -> None:
263
292
  """get bot's verification QR"""
264
- accounts = bot.rpc.get_all_account_ids()
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("#", "&", count=1)
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
- self.run_until(lambda _: False)
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
- self.rpc.start_io_for_all_accounts()
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
- if self._should_process_messages:
177
- for msgid in self.rpc.get_next_msgs(accid):
178
- msg = self.rpc.get_message(accid, msgid)
179
- if msg.from_id not in [SpecialContactId.SELF, SpecialContactId.DEVICE]:
180
- self._on_new_msg(accid, msg)
181
- if msg.is_info and msg.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE:
182
- self._handle_info_msg(accid, msg)
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
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deltabot-cli
3
- Version: 3.1.1
3
+ Version: 3.2.2
4
4
  Summary: Library to speedup Delta Chat bot development
5
5
  Author-email: adbenitez <adb@merlinux.eu>
6
6
  License: Mozilla Public License Version 2.0
File without changes
File without changes
File without changes
File without changes
File without changes