deltachat-rpc-client 1.139.2__tar.gz → 2.24.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.
Potentially problematic release.
This version of deltachat-rpc-client might be problematic. Click here for more details.
- {deltachat_rpc_client-1.139.2/src/deltachat_rpc_client.egg-info → deltachat_rpc_client-2.24.0}/PKG-INFO +9 -4
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/README.md +2 -1
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/pyproject.toml +16 -4
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/__init__.py +1 -1
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/_utils.py +6 -5
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/account.py +170 -35
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/chat.py +30 -5
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/client.py +6 -3
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/const.py +65 -26
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/contact.py +8 -5
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/deltachat.py +6 -5
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/events.py +31 -26
- deltachat_rpc_client-2.24.0/src/deltachat_rpc_client/message.py +127 -0
- deltachat_rpc_client-2.24.0/src/deltachat_rpc_client/pytestplugin.py +187 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/rpc.py +24 -15
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0/src/deltachat_rpc_client.egg-info}/PKG-INFO +9 -4
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client.egg-info/SOURCES.txt +5 -2
- deltachat_rpc_client-2.24.0/tests/test_account_events.py +30 -0
- deltachat_rpc_client-2.24.0/tests/test_calls.py +109 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/tests/test_chatlist_events.py +17 -22
- deltachat_rpc_client-2.24.0/tests/test_iroh_webxdc.py +229 -0
- deltachat_rpc_client-2.24.0/tests/test_key_transfer.py +49 -0
- deltachat_rpc_client-2.24.0/tests/test_multidevice.py +113 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/tests/test_securejoin.py +287 -201
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/tests/test_something.py +485 -68
- deltachat_rpc_client-2.24.0/tests/test_vcard.py +27 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/tests/test_webxdc.py +8 -13
- deltachat_rpc_client-1.139.2/src/deltachat_rpc_client/direct_imap.py +0 -226
- deltachat_rpc_client-1.139.2/src/deltachat_rpc_client/message.py +0 -72
- deltachat_rpc_client-1.139.2/src/deltachat_rpc_client/pytestplugin.py +0 -126
- deltachat_rpc_client-1.139.2/src/deltachat_rpc_client.egg-info/requires.txt +0 -1
- deltachat_rpc_client-1.139.2/tests/test_vcard.py +0 -15
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/LICENSE +0 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/setup.cfg +0 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/py.typed +0 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client.egg-info/dependency_links.txt +0 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client.egg-info/entry_points.txt +0 -0
- {deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: deltachat-rpc-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.24.0
|
|
4
4
|
Summary: Python client for Delta Chat core JSON-RPC interface
|
|
5
5
|
Classifier: Development Status :: 5 - Production/Stable
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -12,11 +12,15 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
18
|
Classifier: Topic :: Communications :: Chat
|
|
16
19
|
Classifier: Topic :: Communications :: Email
|
|
20
|
+
Requires-Python: >=3.8
|
|
17
21
|
Description-Content-Type: text/markdown
|
|
18
22
|
License-File: LICENSE
|
|
19
|
-
|
|
23
|
+
Dynamic: license-file
|
|
20
24
|
|
|
21
25
|
# Delta Chat RPC python client
|
|
22
26
|
|
|
@@ -45,7 +49,8 @@ $ pip install .
|
|
|
45
49
|
## Testing
|
|
46
50
|
|
|
47
51
|
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
|
48
|
-
2.
|
|
52
|
+
2. Install tox `pip install -U tox`
|
|
53
|
+
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
|
49
54
|
|
|
50
55
|
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
|
51
56
|
|
|
@@ -25,7 +25,8 @@ $ pip install .
|
|
|
25
25
|
## Testing
|
|
26
26
|
|
|
27
27
|
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
|
28
|
-
2.
|
|
28
|
+
2. Install tox `pip install -U tox`
|
|
29
|
+
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
|
29
30
|
|
|
30
31
|
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
|
31
32
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deltachat-rpc-client"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.24.0"
|
|
8
8
|
description = "Python client for Delta Chat core JSON-RPC interface"
|
|
9
9
|
classifiers = [
|
|
10
10
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -17,13 +17,14 @@ classifiers = [
|
|
|
17
17
|
"Programming Language :: Python :: 3.9",
|
|
18
18
|
"Programming Language :: Python :: 3.10",
|
|
19
19
|
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
20
23
|
"Topic :: Communications :: Chat",
|
|
21
24
|
"Topic :: Communications :: Email"
|
|
22
25
|
]
|
|
23
26
|
readme = "README.md"
|
|
24
|
-
|
|
25
|
-
"imap-tools",
|
|
26
|
-
]
|
|
27
|
+
requires-python = ">=3.8"
|
|
27
28
|
|
|
28
29
|
[tool.setuptools.package-data]
|
|
29
30
|
deltachat_rpc_client = [
|
|
@@ -66,7 +67,18 @@ lint.select = [
|
|
|
66
67
|
|
|
67
68
|
"RUF006" # asyncio-dangling-task
|
|
68
69
|
]
|
|
70
|
+
lint.ignore = [
|
|
71
|
+
"PLC0415" # `import` should be at the top-level of a file
|
|
72
|
+
]
|
|
69
73
|
line-length = 120
|
|
70
74
|
|
|
71
75
|
[tool.isort]
|
|
72
76
|
profile = "black"
|
|
77
|
+
|
|
78
|
+
[dependency-groups]
|
|
79
|
+
dev = [
|
|
80
|
+
"imap-tools",
|
|
81
|
+
"pytest",
|
|
82
|
+
"pytest-timeout",
|
|
83
|
+
"pytest-xdist",
|
|
84
|
+
]
|
{deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/_utils.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import os
|
|
2
3
|
import re
|
|
3
4
|
import sys
|
|
4
5
|
from threading import Thread
|
|
@@ -89,8 +90,8 @@ def _run_cli(
|
|
|
89
90
|
help="accounts folder (default: current working directory)",
|
|
90
91
|
nargs="?",
|
|
91
92
|
)
|
|
92
|
-
parser.add_argument("--email", action="store", help="email address")
|
|
93
|
-
parser.add_argument("--password", action="store", help="password")
|
|
93
|
+
parser.add_argument("--email", action="store", help="email address", default=os.getenv("DELTACHAT_EMAIL"))
|
|
94
|
+
parser.add_argument("--password", action="store", help="password", default=os.getenv("DELTACHAT_PASSWORD"))
|
|
94
95
|
args = parser.parse_args(argv[1:])
|
|
95
96
|
|
|
96
97
|
with Rpc(accounts_dir=args.accounts_dir, **kwargs) as rpc:
|
|
@@ -114,7 +115,7 @@ def _run_cli(
|
|
|
114
115
|
|
|
115
116
|
|
|
116
117
|
def extract_addr(text: str) -> str:
|
|
117
|
-
"""
|
|
118
|
+
"""Extract email address from the given text."""
|
|
118
119
|
match = re.match(r".*\((.+@.+)\)", text)
|
|
119
120
|
if match:
|
|
120
121
|
text = match.group(1)
|
|
@@ -123,7 +124,7 @@ def extract_addr(text: str) -> str:
|
|
|
123
124
|
|
|
124
125
|
|
|
125
126
|
def parse_system_image_changed(text: str) -> Optional[Tuple[str, bool]]:
|
|
126
|
-
"""
|
|
127
|
+
"""Return image changed/deleted info from parsing the given system message text."""
|
|
127
128
|
text = text.lower()
|
|
128
129
|
match = re.match(r"group image (changed|deleted) by (.+).", text)
|
|
129
130
|
if match:
|
|
@@ -142,7 +143,7 @@ def parse_system_title_changed(text: str) -> Optional[Tuple[str, str]]:
|
|
|
142
143
|
|
|
143
144
|
|
|
144
145
|
def parse_system_add_remove(text: str) -> Optional[Tuple[str, str, str]]:
|
|
145
|
-
"""
|
|
146
|
+
"""Return add/remove info from parsing the given system message text.
|
|
146
147
|
|
|
147
148
|
returns a (action, affected, actor) tuple.
|
|
148
149
|
"""
|
{deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/account.py
RENAMED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
"""Account module."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
5
|
+
import json
|
|
3
6
|
from dataclasses import dataclass
|
|
4
7
|
from typing import TYPE_CHECKING, Optional, Union
|
|
5
8
|
from warnings import warn
|
|
@@ -26,18 +29,36 @@ class Account:
|
|
|
26
29
|
def _rpc(self) -> "Rpc":
|
|
27
30
|
return self.manager.rpc
|
|
28
31
|
|
|
29
|
-
def wait_for_event(self) -> AttrDict:
|
|
32
|
+
def wait_for_event(self, event_type=None) -> AttrDict:
|
|
30
33
|
"""Wait until the next event and return it."""
|
|
31
|
-
|
|
34
|
+
while True:
|
|
35
|
+
next_event = AttrDict(self._rpc.wait_for_event(self.id))
|
|
36
|
+
if event_type is None or next_event.kind == event_type:
|
|
37
|
+
return next_event
|
|
32
38
|
|
|
33
39
|
def clear_all_events(self):
|
|
34
|
-
"""
|
|
40
|
+
"""Remove all queued-up events for a given account.
|
|
41
|
+
|
|
42
|
+
Useful for tests.
|
|
43
|
+
"""
|
|
35
44
|
self._rpc.clear_all_events(self.id)
|
|
36
45
|
|
|
37
46
|
def remove(self) -> None:
|
|
38
47
|
"""Remove the account."""
|
|
39
48
|
self._rpc.remove_account(self.id)
|
|
40
49
|
|
|
50
|
+
def clone(self) -> "Account":
|
|
51
|
+
"""Clone given account.
|
|
52
|
+
|
|
53
|
+
This uses backup-transfer via iroh, i.e. the 'Add second device' feature.
|
|
54
|
+
"""
|
|
55
|
+
future = self._rpc.provide_backup.future(self.id)
|
|
56
|
+
qr = self._rpc.get_backup_qr(self.id)
|
|
57
|
+
new_account = self.manager.add_account()
|
|
58
|
+
new_account._rpc.get_backup(new_account.id, qr)
|
|
59
|
+
future()
|
|
60
|
+
return new_account
|
|
61
|
+
|
|
41
62
|
def start_io(self) -> None:
|
|
42
63
|
"""Start the account I/O."""
|
|
43
64
|
self._rpc.start_io(self.id)
|
|
@@ -67,7 +88,7 @@ class Account:
|
|
|
67
88
|
return self._rpc.get_config(self.id, key)
|
|
68
89
|
|
|
69
90
|
def update_config(self, **kwargs) -> None:
|
|
70
|
-
"""
|
|
91
|
+
"""Update config values."""
|
|
71
92
|
for key, value in kwargs.items():
|
|
72
93
|
self.set_config(key, value)
|
|
73
94
|
|
|
@@ -83,9 +104,15 @@ class Account:
|
|
|
83
104
|
return self.get_config("selfavatar")
|
|
84
105
|
|
|
85
106
|
def check_qr(self, qr):
|
|
107
|
+
"""Parse QR code contents.
|
|
108
|
+
|
|
109
|
+
This function takes the raw text scanned
|
|
110
|
+
and checks what can be done with it.
|
|
111
|
+
"""
|
|
86
112
|
return self._rpc.check_qr(self.id, qr)
|
|
87
113
|
|
|
88
114
|
def set_config_from_qr(self, qr: str):
|
|
115
|
+
"""Set configuration values from a QR code."""
|
|
89
116
|
self._rpc.set_config_from_qr(self.id, qr)
|
|
90
117
|
|
|
91
118
|
@futuremethod
|
|
@@ -93,15 +120,28 @@ class Account:
|
|
|
93
120
|
"""Configure an account."""
|
|
94
121
|
yield self._rpc.configure.future(self.id)
|
|
95
122
|
|
|
123
|
+
@futuremethod
|
|
124
|
+
def add_or_update_transport(self, params):
|
|
125
|
+
"""Add a new transport."""
|
|
126
|
+
yield self._rpc.add_or_update_transport.future(self.id, params)
|
|
127
|
+
|
|
128
|
+
@futuremethod
|
|
129
|
+
def add_transport_from_qr(self, qr: str):
|
|
130
|
+
"""Add a new transport using a QR code."""
|
|
131
|
+
yield self._rpc.add_transport_from_qr.future(self.id, qr)
|
|
132
|
+
|
|
133
|
+
@futuremethod
|
|
134
|
+
def list_transports(self):
|
|
135
|
+
"""Return the list of all email accounts that are used as a transport in the current profile."""
|
|
136
|
+
transports = yield self._rpc.list_transports.future(self.id)
|
|
137
|
+
return transports
|
|
138
|
+
|
|
96
139
|
def bring_online(self):
|
|
97
140
|
"""Start I/O and wait until IMAP becomes IDLE."""
|
|
98
141
|
self.start_io()
|
|
99
|
-
|
|
100
|
-
event = self.wait_for_event()
|
|
101
|
-
if event.kind == EventType.IMAP_INBOX_IDLE:
|
|
102
|
-
break
|
|
142
|
+
self.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
|
103
143
|
|
|
104
|
-
def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
|
|
144
|
+
def create_contact(self, obj: Union[int, str, Contact, "Account"], name: Optional[str] = None) -> Contact:
|
|
105
145
|
"""Create a new Contact or return an existing one.
|
|
106
146
|
|
|
107
147
|
Calling this method will always result in the same
|
|
@@ -109,26 +149,63 @@ class Account:
|
|
|
109
149
|
with that e-mail address, it is unblocked and its display
|
|
110
150
|
name is updated if specified.
|
|
111
151
|
|
|
112
|
-
:param obj: email-address
|
|
152
|
+
:param obj: email-address, contact id or account.
|
|
113
153
|
:param name: (optional) display name for this contact.
|
|
114
154
|
"""
|
|
155
|
+
if isinstance(obj, Account):
|
|
156
|
+
vcard = obj.self_contact.make_vcard()
|
|
157
|
+
[contact] = self.import_vcard(vcard)
|
|
158
|
+
if name:
|
|
159
|
+
contact.set_name(name)
|
|
160
|
+
return contact
|
|
115
161
|
if isinstance(obj, int):
|
|
116
162
|
obj = Contact(self, obj)
|
|
117
163
|
if isinstance(obj, Contact):
|
|
118
164
|
obj = obj.get_snapshot().address
|
|
119
165
|
return Contact(self, self._rpc.create_contact(self.id, obj, name))
|
|
120
166
|
|
|
167
|
+
def make_vcard(self, contacts: list[Contact]) -> str:
|
|
168
|
+
"""Create vCard with the given contacts."""
|
|
169
|
+
assert all(contact.account == self for contact in contacts)
|
|
170
|
+
contact_ids = [contact.id for contact in contacts]
|
|
171
|
+
return self._rpc.make_vcard(self.id, contact_ids)
|
|
172
|
+
|
|
173
|
+
def import_vcard(self, vcard: str) -> list[Contact]:
|
|
174
|
+
"""Import vCard.
|
|
175
|
+
|
|
176
|
+
Return created or modified contacts in the order they appear in vCard.
|
|
177
|
+
"""
|
|
178
|
+
contact_ids = self._rpc.import_vcard_contents(self.id, vcard)
|
|
179
|
+
return [Contact(self, contact_id) for contact_id in contact_ids]
|
|
180
|
+
|
|
121
181
|
def create_chat(self, account: "Account") -> Chat:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
182
|
+
"""Create a 1:1 chat with another account."""
|
|
183
|
+
return self.create_contact(account).create_chat()
|
|
184
|
+
|
|
185
|
+
def get_device_chat(self) -> Chat:
|
|
186
|
+
"""Return device chat."""
|
|
187
|
+
return self.device_contact.create_chat()
|
|
125
188
|
|
|
126
189
|
def get_contact_by_id(self, contact_id: int) -> Contact:
|
|
127
190
|
"""Return Contact instance for the given contact ID."""
|
|
128
191
|
return Contact(self, contact_id)
|
|
129
192
|
|
|
130
193
|
def get_contact_by_addr(self, address: str) -> Optional[Contact]:
|
|
131
|
-
"""
|
|
194
|
+
"""Looks up a known and unblocked contact with a given e-mail address.
|
|
195
|
+
To get a list of all known and unblocked contacts, use contacts_get_contacts().
|
|
196
|
+
|
|
197
|
+
**POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
|
|
198
|
+
(e.g. an address-contact and a key-contact),
|
|
199
|
+
this looks up the most recently seen contact,
|
|
200
|
+
i.e. which contact is returned depends on which contact last sent a message.
|
|
201
|
+
If the user just clicked on a mailto: link, then this is the best thing you can do.
|
|
202
|
+
But **DO NOT** internally represent contacts by their email address
|
|
203
|
+
and do not use this function to look them up;
|
|
204
|
+
otherwise this function will sometimes look up the wrong contact.
|
|
205
|
+
Instead, you should internally represent contacts by their ids.
|
|
206
|
+
|
|
207
|
+
To validate an e-mail address independently of the contact database
|
|
208
|
+
use check_email_validity()."""
|
|
132
209
|
contact_id = self._rpc.lookup_contact_id_by_addr(self.id, address)
|
|
133
210
|
return contact_id and Contact(self, contact_id)
|
|
134
211
|
|
|
@@ -154,8 +231,8 @@ class Account:
|
|
|
154
231
|
def get_contacts(
|
|
155
232
|
self,
|
|
156
233
|
query: Optional[str] = None,
|
|
234
|
+
*,
|
|
157
235
|
with_self: bool = False,
|
|
158
|
-
verified_only: bool = False,
|
|
159
236
|
snapshot: bool = False,
|
|
160
237
|
) -> Union[list[Contact], list[AttrDict]]:
|
|
161
238
|
"""Get a filtered list of contacts.
|
|
@@ -163,12 +240,9 @@ class Account:
|
|
|
163
240
|
:param query: if a string is specified, only return contacts
|
|
164
241
|
whose name or e-mail matches query.
|
|
165
242
|
:param with_self: if True the self-contact is also included if it matches the query.
|
|
166
|
-
:param only_verified: if True only return verified contacts.
|
|
167
243
|
:param snapshot: If True return a list of contact snapshots instead of Contact instances.
|
|
168
244
|
"""
|
|
169
245
|
flags = 0
|
|
170
|
-
if verified_only:
|
|
171
|
-
flags |= ContactFlag.VERIFIED_ONLY
|
|
172
246
|
if with_self:
|
|
173
247
|
flags |= ContactFlag.ADD_SELF
|
|
174
248
|
|
|
@@ -180,9 +254,14 @@ class Account:
|
|
|
180
254
|
|
|
181
255
|
@property
|
|
182
256
|
def self_contact(self) -> Contact:
|
|
183
|
-
"""
|
|
257
|
+
"""Account's identity as a Contact."""
|
|
184
258
|
return Contact(self, SpecialContactId.SELF)
|
|
185
259
|
|
|
260
|
+
@property
|
|
261
|
+
def device_contact(self) -> Chat:
|
|
262
|
+
"""Account's device contact."""
|
|
263
|
+
return Contact(self, SpecialContactId.DEVICE)
|
|
264
|
+
|
|
186
265
|
def get_chatlist(
|
|
187
266
|
self,
|
|
188
267
|
query: Optional[str] = None,
|
|
@@ -226,20 +305,51 @@ class Account:
|
|
|
226
305
|
chats.append(AttrDict(item))
|
|
227
306
|
return chats
|
|
228
307
|
|
|
229
|
-
def create_group(self, name: str
|
|
308
|
+
def create_group(self, name: str) -> Chat:
|
|
230
309
|
"""Create a new group chat.
|
|
231
310
|
|
|
232
|
-
After creation,
|
|
311
|
+
After creation,
|
|
312
|
+
the group has only self-contact as member one member (see `SpecialContactId.SELF`)
|
|
313
|
+
and is in _unpromoted_ state.
|
|
314
|
+
This means, you can add or remove members, change the name,
|
|
315
|
+
the group image and so on without messages being sent to all group members.
|
|
316
|
+
|
|
317
|
+
This changes as soon as the first message is sent to the group members
|
|
318
|
+
and the group becomes _promoted_.
|
|
319
|
+
After that, all changes are synced with all group members
|
|
320
|
+
by sending status message.
|
|
321
|
+
|
|
322
|
+
To check, if a chat is still unpromoted, you can look at the `is_unpromoted` property of a chat
|
|
323
|
+
(see `get_full_snapshot()` / `get_basic_snapshot()`).
|
|
324
|
+
This may be useful if you want to show some help for just created groups.
|
|
233
325
|
"""
|
|
234
|
-
return Chat(self, self._rpc.create_group_chat(self.id, name,
|
|
326
|
+
return Chat(self, self._rpc.create_group_chat(self.id, name, False))
|
|
327
|
+
|
|
328
|
+
def create_broadcast(self, name: str) -> Chat:
|
|
329
|
+
"""Create a new, outgoing **broadcast channel**
|
|
330
|
+
(called "Channel" in the UI).
|
|
331
|
+
|
|
332
|
+
Broadcast channels are similar to groups on the sending device,
|
|
333
|
+
however, recipients get the messages in a read-only chat
|
|
334
|
+
and will not see who the other members are.
|
|
335
|
+
|
|
336
|
+
Called `broadcast` here rather than `channel`,
|
|
337
|
+
because the word "channel" already appears a lot in the code,
|
|
338
|
+
which would make it hard to grep for it.
|
|
339
|
+
|
|
340
|
+
After creation, the chat contains no recipients and is in _unpromoted_ state;
|
|
341
|
+
see `create_group()` for more information on the unpromoted state.
|
|
342
|
+
|
|
343
|
+
Returns the created chat.
|
|
344
|
+
"""
|
|
345
|
+
return Chat(self, self._rpc.create_broadcast(self.id, name))
|
|
235
346
|
|
|
236
347
|
def get_chat_by_id(self, chat_id: int) -> Chat:
|
|
237
348
|
"""Return the Chat instance with the given ID."""
|
|
238
349
|
return Chat(self, chat_id)
|
|
239
350
|
|
|
240
351
|
def secure_join(self, qrdata: str) -> Chat:
|
|
241
|
-
"""Continue a Setup-Contact or Verified-Group-Invite protocol started on
|
|
242
|
-
another device.
|
|
352
|
+
"""Continue a Setup-Contact or Verified-Group-Invite protocol started on another device.
|
|
243
353
|
|
|
244
354
|
The function returns immediately and the handshake runs in background, sending
|
|
245
355
|
and receiving several messages.
|
|
@@ -250,12 +360,16 @@ class Account:
|
|
|
250
360
|
"""
|
|
251
361
|
return Chat(self, self._rpc.secure_join(self.id, qrdata))
|
|
252
362
|
|
|
253
|
-
def get_qr_code(self) ->
|
|
254
|
-
"""Get Setup-Contact QR Code text
|
|
363
|
+
def get_qr_code(self) -> str:
|
|
364
|
+
"""Get Setup-Contact QR Code text.
|
|
255
365
|
|
|
256
|
-
|
|
366
|
+
This data needs to be transferred to another Delta Chat account
|
|
257
367
|
in a second channel, typically used by mobiles with QRcode-show + scan UX.
|
|
258
368
|
"""
|
|
369
|
+
return self._rpc.get_chat_securejoin_qr_code(self.id, None)
|
|
370
|
+
|
|
371
|
+
def get_qr_code_svg(self) -> tuple[str, str]:
|
|
372
|
+
"""Get Setup-Contact QR code text and SVG."""
|
|
259
373
|
return self._rpc.get_chat_securejoin_qr_code_svg(self.id, None)
|
|
260
374
|
|
|
261
375
|
def get_message_by_id(self, msg_id: int) -> Message:
|
|
@@ -292,28 +406,40 @@ class Account:
|
|
|
292
406
|
|
|
293
407
|
def wait_for_incoming_msg_event(self):
|
|
294
408
|
"""Wait for incoming message event and return it."""
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
409
|
+
return self.wait_for_event(EventType.INCOMING_MSG)
|
|
410
|
+
|
|
411
|
+
def wait_for_msgs_changed_event(self):
|
|
412
|
+
"""Wait for messages changed event and return it."""
|
|
413
|
+
return self.wait_for_event(EventType.MSGS_CHANGED)
|
|
414
|
+
|
|
415
|
+
def wait_for_msgs_noticed_event(self):
|
|
416
|
+
"""Wait for messages noticed event and return it."""
|
|
417
|
+
return self.wait_for_event(EventType.MSGS_NOTICED)
|
|
418
|
+
|
|
419
|
+
def wait_for_incoming_msg(self):
|
|
420
|
+
"""Wait for incoming message and return it.
|
|
421
|
+
|
|
422
|
+
Consumes all events before the next incoming message event.
|
|
423
|
+
"""
|
|
424
|
+
return self.get_message_by_id(self.wait_for_incoming_msg_event().msg_id)
|
|
299
425
|
|
|
300
426
|
def wait_for_securejoin_inviter_success(self):
|
|
427
|
+
"""Wait until SecureJoin process finishes successfully on the inviter side."""
|
|
301
428
|
while True:
|
|
302
429
|
event = self.wait_for_event()
|
|
303
430
|
if event["kind"] == "SecurejoinInviterProgress" and event["progress"] == 1000:
|
|
304
431
|
break
|
|
305
432
|
|
|
306
433
|
def wait_for_securejoin_joiner_success(self):
|
|
434
|
+
"""Wait until SecureJoin process finishes successfully on the joiner side."""
|
|
307
435
|
while True:
|
|
308
436
|
event = self.wait_for_event()
|
|
309
437
|
if event["kind"] == "SecurejoinJoinerProgress" and event["progress"] == 1000:
|
|
310
438
|
break
|
|
311
439
|
|
|
312
440
|
def wait_for_reactions_changed(self):
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if event.kind == EventType.REACTIONS_CHANGED:
|
|
316
|
-
return event
|
|
441
|
+
"""Wait for reaction change event."""
|
|
442
|
+
return self.wait_for_event(EventType.REACTIONS_CHANGED)
|
|
317
443
|
|
|
318
444
|
def get_fresh_messages_in_arrival_order(self) -> list[Message]:
|
|
319
445
|
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
|
@@ -342,3 +468,12 @@ class Account:
|
|
|
342
468
|
"""Import keys."""
|
|
343
469
|
passphrase = "" # Importing passphrase-protected keys is currently not supported.
|
|
344
470
|
self._rpc.import_self_keys(self.id, str(path), passphrase)
|
|
471
|
+
|
|
472
|
+
def initiate_autocrypt_key_transfer(self) -> None:
|
|
473
|
+
"""Send Autocrypt Setup Message."""
|
|
474
|
+
return self._rpc.initiate_autocrypt_key_transfer(self.id)
|
|
475
|
+
|
|
476
|
+
def ice_servers(self) -> list:
|
|
477
|
+
"""Return ICE servers for WebRTC configuration."""
|
|
478
|
+
ice_servers_json = self._rpc.ice_servers(self.id)
|
|
479
|
+
return json.loads(ice_servers_json)
|
{deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/chat.py
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Chat module."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import calendar
|
|
@@ -89,14 +91,19 @@ class Chat:
|
|
|
89
91
|
def set_ephemeral_timer(self, timer: int) -> None:
|
|
90
92
|
"""Set ephemeral timer of this chat in seconds.
|
|
91
93
|
|
|
92
|
-
0 means the timer is disabled, use 1 for immediate deletion.
|
|
94
|
+
0 means the timer is disabled, use 1 for immediate deletion.
|
|
95
|
+
"""
|
|
93
96
|
self._rpc.set_chat_ephemeral_timer(self.account.id, self.id, timer)
|
|
94
97
|
|
|
95
98
|
def get_encryption_info(self) -> str:
|
|
96
99
|
"""Return encryption info for this chat."""
|
|
97
100
|
return self._rpc.get_chat_encryption_info(self.account.id, self.id)
|
|
98
101
|
|
|
99
|
-
def get_qr_code(self) ->
|
|
102
|
+
def get_qr_code(self) -> str:
|
|
103
|
+
"""Get Join-Group QR code text."""
|
|
104
|
+
return self._rpc.get_chat_securejoin_qr_code(self.account.id, self.id)
|
|
105
|
+
|
|
106
|
+
def get_qr_code_svg(self) -> tuple[str, str]:
|
|
100
107
|
"""Get Join-Group QR code text and SVG data."""
|
|
101
108
|
return self._rpc.get_chat_securejoin_qr_code_svg(self.account.id, self.id)
|
|
102
109
|
|
|
@@ -120,6 +127,7 @@ class Chat:
|
|
|
120
127
|
html: Optional[str] = None,
|
|
121
128
|
viewtype: Optional[ViewType] = None,
|
|
122
129
|
file: Optional[str] = None,
|
|
130
|
+
filename: Optional[str] = None,
|
|
123
131
|
location: Optional[tuple[float, float]] = None,
|
|
124
132
|
override_sender_name: Optional[str] = None,
|
|
125
133
|
quoted_msg: Optional[Union[int, Message]] = None,
|
|
@@ -133,6 +141,7 @@ class Chat:
|
|
|
133
141
|
"html": html,
|
|
134
142
|
"viewtype": viewtype,
|
|
135
143
|
"file": file,
|
|
144
|
+
"filename": filename,
|
|
136
145
|
"location": location,
|
|
137
146
|
"overrideSenderName": override_sender_name,
|
|
138
147
|
"quotedMessageId": quoted_msg,
|
|
@@ -159,6 +168,11 @@ class Chat:
|
|
|
159
168
|
msg_id = self._rpc.send_sticker(self.account.id, self.id, path)
|
|
160
169
|
return Message(self.account, msg_id)
|
|
161
170
|
|
|
171
|
+
def resend_messages(self, messages: list[Message]) -> None:
|
|
172
|
+
"""Resend a list of messages to this chat."""
|
|
173
|
+
msg_ids = [msg.id for msg in messages]
|
|
174
|
+
self._rpc.resend_messages(self.account.id, msg_ids)
|
|
175
|
+
|
|
162
176
|
def forward_messages(self, messages: list[Message]) -> None:
|
|
163
177
|
"""Forward a list of messages to this chat."""
|
|
164
178
|
msg_ids = [msg.id for msg in messages]
|
|
@@ -168,13 +182,14 @@ class Chat:
|
|
|
168
182
|
self,
|
|
169
183
|
text: Optional[str] = None,
|
|
170
184
|
file: Optional[str] = None,
|
|
185
|
+
filename: Optional[str] = None,
|
|
171
186
|
quoted_msg: Optional[int] = None,
|
|
172
187
|
viewtype: Optional[str] = None,
|
|
173
188
|
) -> None:
|
|
174
189
|
"""Set draft message."""
|
|
175
190
|
if isinstance(quoted_msg, Message):
|
|
176
191
|
quoted_msg = quoted_msg.id
|
|
177
|
-
self._rpc.misc_set_draft(self.account.id, self.id, text, file, quoted_msg, viewtype)
|
|
192
|
+
self._rpc.misc_set_draft(self.account.id, self.id, text, file, filename, quoted_msg, viewtype)
|
|
178
193
|
|
|
179
194
|
def remove_draft(self) -> None:
|
|
180
195
|
"""Remove draft message."""
|
|
@@ -192,12 +207,12 @@ class Chat:
|
|
|
192
207
|
return snapshot
|
|
193
208
|
|
|
194
209
|
def get_messages(self, info_only: bool = False, add_daymarker: bool = False) -> list[Message]:
|
|
195
|
-
"""
|
|
210
|
+
"""Get the list of messages in this chat."""
|
|
196
211
|
msgs = self._rpc.get_message_ids(self.account.id, self.id, info_only, add_daymarker)
|
|
197
212
|
return [Message(self.account, msg_id) for msg_id in msgs]
|
|
198
213
|
|
|
199
214
|
def get_fresh_message_count(self) -> int:
|
|
200
|
-
"""Get number of fresh messages in this chat"""
|
|
215
|
+
"""Get number of fresh messages in this chat."""
|
|
201
216
|
return self._rpc.get_fresh_msg_cnt(self.account.id, self.id)
|
|
202
217
|
|
|
203
218
|
def mark_noticed(self) -> None:
|
|
@@ -234,6 +249,11 @@ class Chat:
|
|
|
234
249
|
contacts = self._rpc.get_chat_contacts(self.account.id, self.id)
|
|
235
250
|
return [Contact(self.account, contact_id) for contact_id in contacts]
|
|
236
251
|
|
|
252
|
+
def get_past_contacts(self) -> list[Contact]:
|
|
253
|
+
"""Get past contacts for this chat."""
|
|
254
|
+
past_contacts = self._rpc.get_past_chat_contacts(self.account.id, self.id)
|
|
255
|
+
return [Contact(self.account, contact_id) for contact_id in past_contacts]
|
|
256
|
+
|
|
237
257
|
def set_image(self, path: str) -> None:
|
|
238
258
|
"""Set profile image of this chat.
|
|
239
259
|
|
|
@@ -274,3 +294,8 @@ class Chat:
|
|
|
274
294
|
f.write(vcard.encode())
|
|
275
295
|
f.flush()
|
|
276
296
|
self._rpc.send_msg(self.account.id, self.id, {"viewtype": ViewType.VCARD, "file": f.name})
|
|
297
|
+
|
|
298
|
+
def place_outgoing_call(self, place_call_info: str) -> Message:
|
|
299
|
+
"""Starts an outgoing call."""
|
|
300
|
+
msg_id = self._rpc.place_outgoing_call(self.account.id, self.id, place_call_info)
|
|
301
|
+
return Message(self.account, msg_id)
|
{deltachat_rpc_client-1.139.2 → deltachat_rpc_client-2.24.0}/src/deltachat_rpc_client/client.py
RENAMED
|
@@ -48,6 +48,7 @@ class Client:
|
|
|
48
48
|
self.add_hooks(hooks or [])
|
|
49
49
|
|
|
50
50
|
def add_hooks(self, hooks: Iterable[tuple[Callable, Union[type, EventFilter]]]) -> None:
|
|
51
|
+
"""Register multiple hooks."""
|
|
51
52
|
for hook, event in hooks:
|
|
52
53
|
self.add_hook(hook, event)
|
|
53
54
|
|
|
@@ -77,14 +78,15 @@ class Client:
|
|
|
77
78
|
self._hooks.get(type(event), set()).remove((hook, event))
|
|
78
79
|
|
|
79
80
|
def is_configured(self) -> bool:
|
|
81
|
+
"""Return True if the client is configured."""
|
|
80
82
|
return self.account.is_configured()
|
|
81
83
|
|
|
82
84
|
def configure(self, email: str, password: str, **kwargs) -> None:
|
|
83
|
-
|
|
84
|
-
self.account.set_config("mail_pw", password)
|
|
85
|
+
"""Configure the client."""
|
|
85
86
|
for key, value in kwargs.items():
|
|
86
87
|
self.account.set_config(key, value)
|
|
87
|
-
|
|
88
|
+
params = {"addr": email, "password": password}
|
|
89
|
+
self.account.add_or_update_transport(params)
|
|
88
90
|
self.logger.debug("Account configured")
|
|
89
91
|
|
|
90
92
|
def run_forever(self) -> None:
|
|
@@ -198,5 +200,6 @@ class Bot(Client):
|
|
|
198
200
|
"""Simple bot implementation that listens to events of a single account."""
|
|
199
201
|
|
|
200
202
|
def configure(self, email: str, password: str, **kwargs) -> None:
|
|
203
|
+
"""Configure the bot."""
|
|
201
204
|
kwargs.setdefault("bot", "1")
|
|
202
205
|
super().configure(email, password, **kwargs)
|