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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deltabot-cli
3
- Version: 0.2.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-client>=1.127.0
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 repetitive
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-py
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.chat.send_text(event.text)
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 repetitive
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-py
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.chat.send_text(event.text)
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,8 @@
1
+ """Library to help with Delta Chat bot development"""
2
+
3
+ # pylama:ignore=W0611,W0401
4
+ from ._utils import AttrDict
5
+ from .cli import BotCli
6
+ from .client import Bot
7
+ from .const import *
8
+ from .rpc import JsonRpcError, Rpc
@@ -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 = events.HookCollection()
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, events.EventFilter]) -> Callable: # noqa
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(const.COMMAND_PREFIX):
68
+ if not event.command.startswith(COMMAND_PREFIX):
66
69
  return True
67
- for hook in self._bot._hooks.get(events.NewMessage, []): # pylint:disable=W0212
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(self._serve_cmd, name="serve")
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
- deltachat = DeltaChat(rpc)
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 = deltachat.get_system_info().deltachat_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
- def _serve_cmd(self, bot: Bot, args: Namespace) -> None:
162
- """start processing messages"""
163
- if bot.is_configured():
164
- self._on_start(bot, args)
165
- while True:
166
- try:
167
- bot.run_forever()
168
- except KeyboardInterrupt:
169
- return
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("Account is not configured")
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, events.RawEvent(EventType.CONFIGURE_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
- bot.account.set_config(args.option, args.value)
252
+ rpc.set_config(accid, args.option, args.value)
209
253
 
210
254
  if args.option:
211
255
  try:
212
- value = bot.account.get_config(args.option)
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 = bot.account.get_config("sys.config_keys") or ""
261
+ keys = rpc.get_config(accid, "sys.config_keys") or ""
218
262
  for key in keys.split():
219
- value = bot.account.get_config(key)
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
- qrdata, _ = bot.account.get_qr_code()
226
- code = qrcode.QRCode()
227
- code.add_data(qrdata)
228
- code.print_ascii(invert=True)
229
- print(qrdata)
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")