mirror of
https://github.com/chatmail/core.git
synced 2026-06-30 03:26:36 +03:00
Compare commits
1 Commits
v1.159.5
...
shadowsock
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a0abee4cb |
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,39 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [1.159.5] - 2025-05-14
|
||||
|
||||
### Fixes
|
||||
|
||||
- Don't change webxdc self-addr when saving and loading draft ([#6854](https://github.com/chatmail/core/pull/6854)).
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Remove duplicate miniz_oxide dependency.
|
||||
- Update async-smtp to 0.10.2.
|
||||
|
||||
## [1.159.4] - 2025-05-13
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add missing documentation to deltachat-rpc-client.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Better avatar quality ([#6822](https://github.com/chatmail/core/pull/6822)).
|
||||
- Update iroh from 0.33.0 to 0.35.0 ([#6687](https://github.com/chatmail/core/pull/6687)).
|
||||
- Other dependency updates.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Emit progress(0) in case AEAP is tried.
|
||||
- Replace `FuturesUnordered` from `futures` with `JoinSet` from `tokio`.
|
||||
- Fix order of operations when handling "vc-request-with-auth" ([#6850](https://github.com/chatmail/core/pull/6850)).
|
||||
- Generate rfc724_mid when creating Message ([#6704](https://github.com/chatmail/core/pull/6704))
|
||||
|
||||
### Tests
|
||||
|
||||
- Profile data is attached to group leave messages.
|
||||
|
||||
## [1.159.3] - 2025-04-24
|
||||
|
||||
### CI
|
||||
@@ -6301,5 +6267,3 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.159.1]: https://github.com/chatmail/core/compare/v1.159.0..v1.159.1
|
||||
[1.159.2]: https://github.com/chatmail/core/compare/v1.159.1..v1.159.2
|
||||
[1.159.3]: https://github.com/chatmail/core/compare/v1.159.2..v1.159.3
|
||||
[1.159.4]: https://github.com/chatmail/core/compare/v1.159.3..v1.159.4
|
||||
[1.159.5]: https://github.com/chatmail/core/compare/v1.159.4..v1.159.5
|
||||
|
||||
960
Cargo.lock
generated
960
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.159.5"
|
||||
version = "1.159.3"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.82"
|
||||
@@ -43,7 +43,7 @@ async-broadcast = "0.7.2"
|
||||
async-channel = { workspace = true }
|
||||
async-imap = { version = "0.10.4", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.10", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.17", default-features = false, features = ["deflate", "tokio-fs"] }
|
||||
base64 = { workspace = true }
|
||||
brotli = { version = "8", default-features=false, features = ["std"] }
|
||||
@@ -56,14 +56,14 @@ fd-lock = "4"
|
||||
futures-lite = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hex = "0.4.0"
|
||||
hickory-resolver = "0.25.2"
|
||||
hickory-resolver = "=0.25.0-alpha.5"
|
||||
http-body-util = "0.1.3"
|
||||
humansize = "2"
|
||||
hyper = "1"
|
||||
hyper-util = "0.1.11"
|
||||
image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh-gossip = { version = "0.35", default-features = false, features = ["net"] }
|
||||
iroh = { version = "0.35", default-features = false }
|
||||
iroh-gossip = { version = "0.33", default-features = false, features = ["net"] }
|
||||
iroh = { version = "0.33", default-features = false }
|
||||
kamadak-exif = "0.6.1"
|
||||
libc = { workspace = true }
|
||||
mail-builder = { version = "0.4.2", default-features = false }
|
||||
@@ -91,7 +91,7 @@ serde_urlencoded = "0.7.1"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
shadowsocks = { version = "1.22.0", default-features = false, features = ["aead-cipher", "aead-cipher-2022"] }
|
||||
shadowsocks = { version = "1.23.1", default-features = false, features = ["aead-cipher", "aead-cipher-2022"] }
|
||||
smallvec = "1.15.0"
|
||||
strum = "0.27"
|
||||
strum_macros = "0.27"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.159.5"
|
||||
version = "1.159.3"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.159.5"
|
||||
version = "1.159.3"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.159.5"
|
||||
"version": "1.159.3"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.159.5"
|
||||
version = "1.159.3"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "1.159.5"
|
||||
version = "1.159.3"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Delta Chat JSON-RPC high-level API."""
|
||||
"""Delta Chat JSON-RPC high-level API"""
|
||||
|
||||
from ._utils import AttrDict, run_bot_cli, run_client_cli
|
||||
from .account import Account
|
||||
|
||||
@@ -115,7 +115,7 @@ def _run_cli(
|
||||
|
||||
|
||||
def extract_addr(text: str) -> str:
|
||||
"""Extract email address from the given text."""
|
||||
"""extract email address from the given text."""
|
||||
match = re.match(r".*\((.+@.+)\)", text)
|
||||
if match:
|
||||
text = match.group(1)
|
||||
@@ -124,7 +124,7 @@ def extract_addr(text: str) -> str:
|
||||
|
||||
|
||||
def parse_system_image_changed(text: str) -> Optional[Tuple[str, bool]]:
|
||||
"""Return image changed/deleted info from parsing the given system message text."""
|
||||
"""return image changed/deleted info from parsing the given system message text."""
|
||||
text = text.lower()
|
||||
match = re.match(r"group image (changed|deleted) by (.+).", text)
|
||||
if match:
|
||||
@@ -143,7 +143,7 @@ def parse_system_title_changed(text: str) -> Optional[Tuple[str, str]]:
|
||||
|
||||
|
||||
def parse_system_add_remove(text: str) -> Optional[Tuple[str, str, str]]:
|
||||
"""Return add/remove info from parsing the given system message text.
|
||||
"""return add/remove info from parsing the given system message text.
|
||||
|
||||
returns a (action, affected, actor) tuple.
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""Account module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
@@ -36,10 +34,7 @@ class Account:
|
||||
return next_event
|
||||
|
||||
def clear_all_events(self):
|
||||
"""Remove all queued-up events for a given account.
|
||||
|
||||
Useful for tests.
|
||||
"""
|
||||
"""Removes all queued-up events for a given account. Useful for tests."""
|
||||
self._rpc.clear_all_events(self.id)
|
||||
|
||||
def remove(self) -> None:
|
||||
@@ -48,9 +43,7 @@ class Account:
|
||||
|
||||
def clone(self) -> "Account":
|
||||
"""Clone given account.
|
||||
|
||||
This uses backup-transfer via iroh, i.e. the 'Add second device' feature.
|
||||
"""
|
||||
This uses backup-transfer via iroh, i.e. the 'Add second device' feature."""
|
||||
future = self._rpc.provide_backup.future(self.id)
|
||||
qr = self._rpc.get_backup_qr(self.id)
|
||||
new_account = self.manager.add_account()
|
||||
@@ -87,7 +80,7 @@ class Account:
|
||||
return self._rpc.get_config(self.id, key)
|
||||
|
||||
def update_config(self, **kwargs) -> None:
|
||||
"""Update config values."""
|
||||
"""update config values."""
|
||||
for key, value in kwargs.items():
|
||||
self.set_config(key, value)
|
||||
|
||||
@@ -106,12 +99,10 @@ class Account:
|
||||
"""Parse QR code contents.
|
||||
|
||||
This function takes the raw text scanned
|
||||
and checks what can be done with it.
|
||||
"""
|
||||
and checks what can be done with it."""
|
||||
return self._rpc.check_qr(self.id, qr)
|
||||
|
||||
def set_config_from_qr(self, qr: str):
|
||||
"""Set configuration values from a QR code."""
|
||||
self._rpc.set_config_from_qr(self.id, qr)
|
||||
|
||||
@futuremethod
|
||||
@@ -126,7 +117,7 @@ class Account:
|
||||
|
||||
@futuremethod
|
||||
def list_transports(self):
|
||||
"""Return the list of all email accounts that are used as a transport in the current profile."""
|
||||
"""Returns the list of all email accounts that are used as a transport in the current profile."""
|
||||
transports = yield self._rpc.list_transports.future(self.id)
|
||||
return transports
|
||||
|
||||
@@ -167,8 +158,7 @@ class Account:
|
||||
def import_vcard(self, vcard: str) -> list[Contact]:
|
||||
"""Import vCard.
|
||||
|
||||
Return created or modified contacts in the order they appear in vCard.
|
||||
"""
|
||||
Return created or modified contacts in the order they appear in vCard."""
|
||||
contact_ids = self._rpc.import_vcard_contents(self.id, vcard)
|
||||
return [Contact(self, contact_id) for contact_id in contact_ids]
|
||||
|
||||
@@ -237,12 +227,12 @@ class Account:
|
||||
|
||||
@property
|
||||
def self_contact(self) -> Contact:
|
||||
"""Account's identity as a Contact."""
|
||||
"""This account's identity as a Contact."""
|
||||
return Contact(self, SpecialContactId.SELF)
|
||||
|
||||
@property
|
||||
def device_contact(self) -> Chat:
|
||||
"""Account's device contact."""
|
||||
"""This account's device contact."""
|
||||
return Contact(self, SpecialContactId.DEVICE)
|
||||
|
||||
def get_chatlist(
|
||||
@@ -300,7 +290,8 @@ class Account:
|
||||
return Chat(self, chat_id)
|
||||
|
||||
def secure_join(self, qrdata: str) -> Chat:
|
||||
"""Continue a Setup-Contact or Verified-Group-Invite protocol started on another device.
|
||||
"""Continue a Setup-Contact or Verified-Group-Invite protocol started on
|
||||
another device.
|
||||
|
||||
The function returns immediately and the handshake runs in background, sending
|
||||
and receiving several messages.
|
||||
@@ -370,26 +361,22 @@ class Account:
|
||||
def wait_for_incoming_msg(self):
|
||||
"""Wait for incoming message and return it.
|
||||
|
||||
Consumes all events before the next incoming message event.
|
||||
"""
|
||||
Consumes all events before the next incoming message event."""
|
||||
return self.get_message_by_id(self.wait_for_incoming_msg_event().msg_id)
|
||||
|
||||
def wait_for_securejoin_inviter_success(self):
|
||||
"""Wait until SecureJoin process finishes successfully on the inviter side."""
|
||||
while True:
|
||||
event = self.wait_for_event()
|
||||
if event["kind"] == "SecurejoinInviterProgress" and event["progress"] == 1000:
|
||||
break
|
||||
|
||||
def wait_for_securejoin_joiner_success(self):
|
||||
"""Wait until SecureJoin process finishes successfully on the joiner side."""
|
||||
while True:
|
||||
event = self.wait_for_event()
|
||||
if event["kind"] == "SecurejoinJoinerProgress" and event["progress"] == 1000:
|
||||
break
|
||||
|
||||
def wait_for_reactions_changed(self):
|
||||
"""Wait for reaction change event."""
|
||||
return self.wait_for_event(EventType.REACTIONS_CHANGED)
|
||||
|
||||
def get_fresh_messages_in_arrival_order(self) -> list[Message]:
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""Chat module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import calendar
|
||||
@@ -91,8 +89,7 @@ class Chat:
|
||||
def set_ephemeral_timer(self, timer: int) -> None:
|
||||
"""Set ephemeral timer of this chat in seconds.
|
||||
|
||||
0 means the timer is disabled, use 1 for immediate deletion.
|
||||
"""
|
||||
0 means the timer is disabled, use 1 for immediate deletion."""
|
||||
self._rpc.set_chat_ephemeral_timer(self.account.id, self.id, timer)
|
||||
|
||||
def get_encryption_info(self) -> str:
|
||||
@@ -202,12 +199,12 @@ class Chat:
|
||||
return snapshot
|
||||
|
||||
def get_messages(self, info_only: bool = False, add_daymarker: bool = False) -> list[Message]:
|
||||
"""Get the list of messages in this chat."""
|
||||
"""get the list of messages in this chat."""
|
||||
msgs = self._rpc.get_message_ids(self.account.id, self.id, info_only, add_daymarker)
|
||||
return [Message(self.account, msg_id) for msg_id in msgs]
|
||||
|
||||
def get_fresh_message_count(self) -> int:
|
||||
"""Get number of fresh messages in this chat."""
|
||||
"""Get number of fresh messages in this chat"""
|
||||
return self._rpc.get_fresh_msg_cnt(self.account.id, self.id)
|
||||
|
||||
def mark_noticed(self) -> None:
|
||||
|
||||
@@ -48,7 +48,6 @@ class Client:
|
||||
self.add_hooks(hooks or [])
|
||||
|
||||
def add_hooks(self, hooks: Iterable[tuple[Callable, Union[type, EventFilter]]]) -> None:
|
||||
"""Register multiple hooks."""
|
||||
for hook, event in hooks:
|
||||
self.add_hook(hook, event)
|
||||
|
||||
@@ -78,11 +77,9 @@ class Client:
|
||||
self._hooks.get(type(event), set()).remove((hook, event))
|
||||
|
||||
def is_configured(self) -> bool:
|
||||
"""Return True if the client is configured."""
|
||||
return self.account.is_configured()
|
||||
|
||||
def configure(self, email: str, password: str, **kwargs) -> None:
|
||||
"""Configure the client."""
|
||||
self.account.set_config("addr", email)
|
||||
self.account.set_config("mail_pw", password)
|
||||
for key, value in kwargs.items():
|
||||
@@ -201,6 +198,5 @@ class Bot(Client):
|
||||
"""Simple bot implementation that listens to events of a single account."""
|
||||
|
||||
def configure(self, email: str, password: str, **kwargs) -> None:
|
||||
"""Configure the bot."""
|
||||
kwargs.setdefault("bot", "1")
|
||||
super().configure(email, password, **kwargs)
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
"""Constants module."""
|
||||
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
COMMAND_PREFIX = "/"
|
||||
|
||||
|
||||
class ContactFlag(IntEnum):
|
||||
"""Bit flags for get_contacts() method."""
|
||||
|
||||
VERIFIED_ONLY = 0x01
|
||||
ADD_SELF = 0x02
|
||||
|
||||
|
||||
class ChatlistFlag(IntEnum):
|
||||
"""Bit flags for get_chatlist() method."""
|
||||
|
||||
ARCHIVED_ONLY = 0x01
|
||||
NO_SPECIALS = 0x02
|
||||
ADD_ALLDONE_HINT = 0x04
|
||||
@@ -22,8 +16,6 @@ class ChatlistFlag(IntEnum):
|
||||
|
||||
|
||||
class SpecialContactId(IntEnum):
|
||||
"""Special contact IDs."""
|
||||
|
||||
SELF = 1
|
||||
INFO = 2 # centered messages as "member added", used in all chats
|
||||
DEVICE = 5 # messages "update info" in the device-chat
|
||||
@@ -31,7 +23,7 @@ class SpecialContactId(IntEnum):
|
||||
|
||||
|
||||
class EventType(str, Enum):
|
||||
"""Core event types."""
|
||||
"""Core event types"""
|
||||
|
||||
INFO = "Info"
|
||||
SMTP_CONNECTED = "SmtpConnected"
|
||||
@@ -79,7 +71,7 @@ class EventType(str, Enum):
|
||||
|
||||
|
||||
class ChatId(IntEnum):
|
||||
"""Special chat IDs."""
|
||||
"""Special chat ids"""
|
||||
|
||||
TRASH = 3
|
||||
ARCHIVED_LINK = 6
|
||||
@@ -88,7 +80,7 @@ class ChatId(IntEnum):
|
||||
|
||||
|
||||
class ChatType(IntEnum):
|
||||
"""Chat type."""
|
||||
"""Chat types"""
|
||||
|
||||
UNDEFINED = 0
|
||||
SINGLE = 100
|
||||
@@ -98,7 +90,7 @@ class ChatType(IntEnum):
|
||||
|
||||
|
||||
class ChatVisibility(str, Enum):
|
||||
"""Chat visibility types."""
|
||||
"""Chat visibility types"""
|
||||
|
||||
NORMAL = "Normal"
|
||||
ARCHIVED = "Archived"
|
||||
@@ -106,7 +98,7 @@ class ChatVisibility(str, Enum):
|
||||
|
||||
|
||||
class DownloadState(str, Enum):
|
||||
"""Message download state."""
|
||||
"""Message download state"""
|
||||
|
||||
DONE = "Done"
|
||||
AVAILABLE = "Available"
|
||||
@@ -167,14 +159,14 @@ class MessageState(IntEnum):
|
||||
|
||||
|
||||
class MessageId(IntEnum):
|
||||
"""Special message IDs."""
|
||||
"""Special message ids"""
|
||||
|
||||
DAYMARKER = 9
|
||||
LAST_SPECIAL = 9
|
||||
|
||||
|
||||
class CertificateChecks(IntEnum):
|
||||
"""Certificate checks mode."""
|
||||
"""Certificate checks mode"""
|
||||
|
||||
AUTOMATIC = 0
|
||||
STRICT = 1
|
||||
@@ -182,7 +174,7 @@ class CertificateChecks(IntEnum):
|
||||
|
||||
|
||||
class Connectivity(IntEnum):
|
||||
"""Connectivity states."""
|
||||
"""Connectivity states"""
|
||||
|
||||
NOT_CONNECTED = 1000
|
||||
CONNECTING = 2000
|
||||
@@ -191,7 +183,7 @@ class Connectivity(IntEnum):
|
||||
|
||||
|
||||
class KeyGenType(IntEnum):
|
||||
"""Type of the key to generate."""
|
||||
"""Type of the key to generate"""
|
||||
|
||||
DEFAULT = 0
|
||||
RSA2048 = 1
|
||||
@@ -201,21 +193,21 @@ class KeyGenType(IntEnum):
|
||||
|
||||
# "Lp" means "login parameters"
|
||||
class LpAuthFlag(IntEnum):
|
||||
"""Authorization flags."""
|
||||
"""Authorization flags"""
|
||||
|
||||
OAUTH2 = 0x2
|
||||
NORMAL = 0x4
|
||||
|
||||
|
||||
class MediaQuality(IntEnum):
|
||||
"""Media quality setting."""
|
||||
"""Media quality setting"""
|
||||
|
||||
BALANCED = 0
|
||||
WORSE = 1
|
||||
|
||||
|
||||
class ProviderStatus(IntEnum):
|
||||
"""Provider status according to manual testing."""
|
||||
"""Provider status according to manual testing"""
|
||||
|
||||
OK = 1
|
||||
PREPARATION = 2
|
||||
@@ -223,7 +215,7 @@ class ProviderStatus(IntEnum):
|
||||
|
||||
|
||||
class PushNotifyState(IntEnum):
|
||||
"""Push notifications state."""
|
||||
"""Push notifications state"""
|
||||
|
||||
NOT_CONNECTED = 0
|
||||
HEARTBEAT = 1
|
||||
@@ -231,7 +223,7 @@ class PushNotifyState(IntEnum):
|
||||
|
||||
|
||||
class ShowEmails(IntEnum):
|
||||
"""Show emails mode."""
|
||||
"""Show emails mode"""
|
||||
|
||||
OFF = 0
|
||||
ACCEPTED_CONTACTS = 1
|
||||
@@ -239,7 +231,7 @@ class ShowEmails(IntEnum):
|
||||
|
||||
|
||||
class SocketSecurity(IntEnum):
|
||||
"""Socket security."""
|
||||
"""Socket security"""
|
||||
|
||||
AUTOMATIC = 0
|
||||
SSL = 1
|
||||
@@ -248,7 +240,7 @@ class SocketSecurity(IntEnum):
|
||||
|
||||
|
||||
class VideochatType(IntEnum):
|
||||
"""Video chat URL type."""
|
||||
"""Video chat URL type"""
|
||||
|
||||
UNKNOWN = 0
|
||||
BASICWEBRTC = 1
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""Contact module."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -13,7 +11,8 @@ if TYPE_CHECKING:
|
||||
|
||||
@dataclass
|
||||
class Contact:
|
||||
"""Contact API.
|
||||
"""
|
||||
Contact API.
|
||||
|
||||
Essentially a wrapper for RPC, account ID and a contact ID.
|
||||
"""
|
||||
@@ -46,9 +45,8 @@ class Contact:
|
||||
self._rpc.change_contact_name(self.account.id, self.id, name)
|
||||
|
||||
def get_encryption_info(self) -> str:
|
||||
"""Get a multi-line encryption info.
|
||||
|
||||
Encryption info contains your fingerprint and the fingerprint of the contact.
|
||||
"""Get a multi-line encryption info, containing your fingerprint and
|
||||
the fingerprint of the contact.
|
||||
"""
|
||||
return self._rpc.get_contact_encryption_info(self.account.id, self.id)
|
||||
|
||||
@@ -68,5 +66,4 @@ class Contact:
|
||||
)
|
||||
|
||||
def make_vcard(self) -> str:
|
||||
"""Make a vCard for the contact."""
|
||||
return self.account.make_vcard([self])
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""Account manager module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -12,13 +10,12 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class DeltaChat:
|
||||
"""Delta Chat accounts manager.
|
||||
|
||||
"""
|
||||
Delta Chat accounts manager.
|
||||
This is the root of the object oriented API.
|
||||
"""
|
||||
|
||||
def __init__(self, rpc: "Rpc") -> None:
|
||||
"""Initialize account manager."""
|
||||
self.rpc = rpc
|
||||
|
||||
def add_account(self) -> Account:
|
||||
@@ -40,7 +37,9 @@ class DeltaChat:
|
||||
self.rpc.stop_io_for_all_accounts()
|
||||
|
||||
def maybe_network(self) -> None:
|
||||
"""Indicate that the network conditions might have changed."""
|
||||
"""Indicate that the network likely has come back or just that the network
|
||||
conditions might have changed.
|
||||
"""
|
||||
self.rpc.maybe_network()
|
||||
|
||||
def get_system_info(self) -> AttrDict:
|
||||
|
||||
@@ -36,7 +36,7 @@ class EventFilter(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def __hash__(self) -> int:
|
||||
"""Object's unique hash."""
|
||||
"""Object's unique hash"""
|
||||
|
||||
@abstractmethod
|
||||
def __eq__(self, other) -> bool:
|
||||
@@ -52,7 +52,9 @@ class EventFilter(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, event):
|
||||
"""Return True-like value if the event passed the filter."""
|
||||
"""Return True-like value if the event passed the filter and should be
|
||||
used, or False-like value otherwise.
|
||||
"""
|
||||
|
||||
|
||||
class RawEvent(EventFilter):
|
||||
@@ -80,17 +82,31 @@ class RawEvent(EventFilter):
|
||||
return False
|
||||
|
||||
def filter(self, event: "AttrDict") -> bool:
|
||||
"""Filter an event.
|
||||
|
||||
Return true if the event should be processed.
|
||||
"""
|
||||
if self.types and event.kind not in self.types:
|
||||
return False
|
||||
return self._call_func(event)
|
||||
|
||||
|
||||
class NewMessage(EventFilter):
|
||||
"""Matches whenever a new message arrives."""
|
||||
"""Matches whenever a new message arrives.
|
||||
|
||||
Warning: registering a handler for this event will cause the messages
|
||||
to be marked as read. Its usage is mainly intended for bots.
|
||||
|
||||
:param pattern: if set, this Pattern will be used to filter the message by its text
|
||||
content.
|
||||
:param command: If set, only match messages with the given command (ex. /help).
|
||||
Setting this property implies `is_info==False`.
|
||||
:param is_bot: If set to True only match messages sent by bots, if set to None
|
||||
match messages from bots and users. If omitted or set to False
|
||||
only messages from users will be matched.
|
||||
:param is_info: If set to True only match info/system messages, if set to False
|
||||
only match messages that are not info/system messages. If omitted
|
||||
info/system messages as well as normal messages will be matched.
|
||||
:param func: A Callable function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -105,25 +121,6 @@ class NewMessage(EventFilter):
|
||||
is_info: Optional[bool] = None,
|
||||
func: Optional[Callable[["AttrDict"], bool]] = None,
|
||||
) -> None:
|
||||
"""Initialize a new message filter.
|
||||
|
||||
Warning: registering a handler for this event will cause the messages
|
||||
to be marked as read. Its usage is mainly intended for bots.
|
||||
|
||||
:param pattern: if set, this Pattern will be used to filter the message by its text
|
||||
content.
|
||||
:param command: If set, only match messages with the given command (ex. /help).
|
||||
Setting this property implies `is_info==False`.
|
||||
:param is_bot: If set to True only match messages sent by bots, if set to None
|
||||
match messages from bots and users. If omitted or set to False
|
||||
only messages from users will be matched.
|
||||
:param is_info: If set to True only match info/system messages, if set to False
|
||||
only match messages that are not info/system messages. If omitted
|
||||
info/system messages as well as normal messages will be matched.
|
||||
:param func: A Callable function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
super().__init__(func=func)
|
||||
self.is_bot = is_bot
|
||||
self.is_info = is_info
|
||||
@@ -162,7 +159,6 @@ class NewMessage(EventFilter):
|
||||
return False
|
||||
|
||||
def filter(self, event: "AttrDict") -> bool:
|
||||
"""Return true if if the event is a new message event."""
|
||||
if self.is_bot is not None and self.is_bot != event.message_snapshot.is_bot:
|
||||
return False
|
||||
if self.is_info is not None and self.is_info != event.message_snapshot.is_info:
|
||||
@@ -203,7 +199,6 @@ class MemberListChanged(EventFilter):
|
||||
return False
|
||||
|
||||
def filter(self, event: "AttrDict") -> bool:
|
||||
"""Return true if if the event is a member addition event."""
|
||||
if self.added is not None and self.added != event.member_added:
|
||||
return False
|
||||
return self._call_func(event)
|
||||
@@ -236,7 +231,6 @@ class GroupImageChanged(EventFilter):
|
||||
return False
|
||||
|
||||
def filter(self, event: "AttrDict") -> bool:
|
||||
"""Return True if event is matched."""
|
||||
if self.deleted is not None and self.deleted != event.image_deleted:
|
||||
return False
|
||||
return self._call_func(event)
|
||||
@@ -262,12 +256,13 @@ class GroupNameChanged(EventFilter):
|
||||
return False
|
||||
|
||||
def filter(self, event: "AttrDict") -> bool:
|
||||
"""Return True if event is matched."""
|
||||
return self._call_func(event)
|
||||
|
||||
|
||||
class HookCollection:
|
||||
"""Helper class to collect event hooks that can later be added to a Delta Chat client."""
|
||||
"""
|
||||
Helper class to collect event hooks that can later be added to a Delta Chat client.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._hooks: set[tuple[Callable, Union[type, EventFilter]]] = set()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""Message module."""
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
@@ -47,7 +45,6 @@ class Message:
|
||||
return None
|
||||
|
||||
def get_sender_contact(self) -> Contact:
|
||||
"""Return sender contact."""
|
||||
from_id = self.get_snapshot().from_id
|
||||
return self.account.get_contact_by_id(from_id)
|
||||
|
||||
@@ -56,11 +53,6 @@ class Message:
|
||||
self._rpc.markseen_msgs(self.account.id, [self.id])
|
||||
|
||||
def continue_autocrypt_key_transfer(self, setup_code: str) -> None:
|
||||
"""Continue the Autocrypt Setup Message key transfer.
|
||||
|
||||
This function can be called on received Autocrypt Setup Message
|
||||
to import the key encrypted with the provided setup code.
|
||||
"""
|
||||
self._rpc.continue_autocrypt_key_transfer(self.account.id, self.id, setup_code)
|
||||
|
||||
def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None:
|
||||
@@ -70,7 +62,6 @@ class Message:
|
||||
self._rpc.send_webxdc_status_update(self.account.id, self.id, update, description)
|
||||
|
||||
def get_webxdc_status_updates(self, last_known_serial: int = 0) -> list:
|
||||
"""Return a list of Webxdc status updates for Webxdc instance message."""
|
||||
return json.loads(self._rpc.get_webxdc_status_updates(self.account.id, self.id, last_known_serial))
|
||||
|
||||
def get_info(self) -> str:
|
||||
@@ -78,7 +69,6 @@ class Message:
|
||||
return self._rpc.get_message_info(self.account.id, self.id)
|
||||
|
||||
def get_webxdc_info(self) -> dict:
|
||||
"""Get info from a Webxdc message in JSON format."""
|
||||
return self._rpc.get_webxdc_info(self.account.id, self.id)
|
||||
|
||||
def wait_until_delivered(self) -> None:
|
||||
@@ -90,10 +80,8 @@ class Message:
|
||||
|
||||
@futuremethod
|
||||
def send_webxdc_realtime_advertisement(self):
|
||||
"""Send an advertisement to join the realtime channel."""
|
||||
yield self._rpc.send_webxdc_realtime_advertisement.future(self.account.id, self.id)
|
||||
|
||||
@futuremethod
|
||||
def send_webxdc_realtime_data(self, data) -> None:
|
||||
"""Send data to the realtime channel."""
|
||||
yield self._rpc.send_webxdc_realtime_data.future(self.account.id, self.id, list(data))
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""Pytest plugin module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
@@ -15,30 +13,24 @@ from .rpc import Rpc
|
||||
|
||||
|
||||
class ACFactory:
|
||||
"""Test account factory."""
|
||||
|
||||
def __init__(self, deltachat: DeltaChat) -> None:
|
||||
self.deltachat = deltachat
|
||||
|
||||
def get_unconfigured_account(self) -> Account:
|
||||
"""Create a new unconfigured account."""
|
||||
account = self.deltachat.add_account()
|
||||
account.set_config("verified_one_on_one_chats", "1")
|
||||
return account
|
||||
|
||||
def get_unconfigured_bot(self) -> Bot:
|
||||
"""Create a new unconfigured bot."""
|
||||
return Bot(self.get_unconfigured_account())
|
||||
|
||||
def get_credentials(self) -> (str, str):
|
||||
"""Generate new credentials for chatmail account."""
|
||||
domain = os.getenv("CHATMAIL_DOMAIN")
|
||||
username = "ci-" + "".join(random.choice("2345789acdefghjkmnpqrstuvwxyz") for i in range(6))
|
||||
return f"{username}@{domain}", f"{username}${username}"
|
||||
|
||||
@futuremethod
|
||||
def new_configured_account(self):
|
||||
"""Create a new configured account."""
|
||||
addr, password = self.get_credentials()
|
||||
account = self.get_unconfigured_account()
|
||||
params = {"addr": addr, "password": password}
|
||||
@@ -48,7 +40,6 @@ class ACFactory:
|
||||
return account
|
||||
|
||||
def new_configured_bot(self) -> Bot:
|
||||
"""Create a new configured bot."""
|
||||
addr, password = self.get_credentials()
|
||||
bot = self.get_unconfigured_bot()
|
||||
bot.configure(addr, password)
|
||||
@@ -56,13 +47,11 @@ class ACFactory:
|
||||
|
||||
@futuremethod
|
||||
def get_online_account(self):
|
||||
"""Create a new account and start I/O."""
|
||||
account = yield self.new_configured_account.future()
|
||||
account.bring_online()
|
||||
return account
|
||||
|
||||
def get_online_accounts(self, num: int) -> list[Account]:
|
||||
"""Create multiple online accounts."""
|
||||
futures = [self.get_online_account.future() for _ in range(num)]
|
||||
return [f() for f in futures]
|
||||
|
||||
@@ -77,10 +66,6 @@ class ACFactory:
|
||||
return ac_clone
|
||||
|
||||
def get_accepted_chat(self, ac1: Account, ac2: Account) -> Chat:
|
||||
"""Create a new 1:1 chat between ac1 and ac2 accepted on both sides.
|
||||
|
||||
Returned chat is a chat with ac2 from ac1 point of view.
|
||||
"""
|
||||
ac2.create_chat(ac1)
|
||||
return ac1.create_chat(ac2)
|
||||
|
||||
@@ -92,7 +77,6 @@ class ACFactory:
|
||||
file: Optional[str] = None,
|
||||
group: Optional[str] = None,
|
||||
) -> Message:
|
||||
"""Send a message."""
|
||||
if not from_account:
|
||||
from_account = (self.get_online_accounts(1))[0]
|
||||
to_contact = from_account.create_contact(to_account)
|
||||
@@ -111,7 +95,6 @@ class ACFactory:
|
||||
file: Optional[str] = None,
|
||||
group: Optional[str] = None,
|
||||
) -> AttrDict:
|
||||
"""Send a message and wait until recipient processes it."""
|
||||
self.send_message(
|
||||
to_account=to_client.account,
|
||||
from_account=from_account,
|
||||
@@ -125,7 +108,6 @@ class ACFactory:
|
||||
|
||||
@pytest.fixture
|
||||
def rpc(tmp_path) -> AsyncGenerator:
|
||||
"""RPC client fixture."""
|
||||
rpc_server = Rpc(accounts_dir=str(tmp_path / "accounts"))
|
||||
with rpc_server:
|
||||
yield rpc_server
|
||||
@@ -133,7 +115,6 @@ def rpc(tmp_path) -> AsyncGenerator:
|
||||
|
||||
@pytest.fixture
|
||||
def acfactory(rpc) -> AsyncGenerator:
|
||||
"""Return account factory fixture."""
|
||||
return ACFactory(DeltaChat(rpc))
|
||||
|
||||
|
||||
@@ -151,7 +132,7 @@ def data():
|
||||
raise Exception("Data path cannot be found")
|
||||
|
||||
def get_path(self, bn):
|
||||
"""Return path of file or None if it doesn't exist."""
|
||||
"""return path of file or None if it doesn't exist."""
|
||||
fn = os.path.join(self.path, *bn.split("/"))
|
||||
assert os.path.exists(fn)
|
||||
return fn
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""JSON-RPC client module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
@@ -14,19 +12,16 @@ from typing import Any, Iterator, Optional
|
||||
|
||||
|
||||
class JsonRpcError(Exception):
|
||||
"""JSON-RPC error."""
|
||||
pass
|
||||
|
||||
|
||||
class RpcFuture:
|
||||
"""RPC future waiting for RPC call result."""
|
||||
|
||||
def __init__(self, rpc: "Rpc", request_id: int, event: Event):
|
||||
self.rpc = rpc
|
||||
self.request_id = request_id
|
||||
self.event = event
|
||||
|
||||
def __call__(self):
|
||||
"""Wait for the future to return the result."""
|
||||
self.event.wait()
|
||||
response = self.rpc.request_results.pop(self.request_id)
|
||||
if "error" in response:
|
||||
@@ -37,19 +32,17 @@ class RpcFuture:
|
||||
|
||||
|
||||
class RpcMethod:
|
||||
"""RPC method."""
|
||||
|
||||
def __init__(self, rpc: "Rpc", name: str):
|
||||
self.rpc = rpc
|
||||
self.name = name
|
||||
|
||||
def __call__(self, *args) -> Any:
|
||||
"""Call JSON-RPC method synchronously."""
|
||||
"""Synchronously calls JSON-RPC method."""
|
||||
future = self.future(*args)
|
||||
return future()
|
||||
|
||||
def future(self, *args) -> Any:
|
||||
"""Call JSON-RPC method asynchronously."""
|
||||
"""Asynchronously calls JSON-RPC method."""
|
||||
request_id = next(self.rpc.id_iterator)
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
@@ -65,13 +58,8 @@ class RpcMethod:
|
||||
|
||||
|
||||
class Rpc:
|
||||
"""RPC client."""
|
||||
|
||||
def __init__(self, accounts_dir: Optional[str] = None, **kwargs):
|
||||
"""Initialize RPC client.
|
||||
|
||||
The given arguments will be passed to subprocess.Popen().
|
||||
"""
|
||||
"""The given arguments will be passed to subprocess.Popen()"""
|
||||
if accounts_dir:
|
||||
kwargs["env"] = {
|
||||
**kwargs.get("env", os.environ),
|
||||
@@ -93,7 +81,6 @@ class Rpc:
|
||||
self.events_thread: Thread
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start RPC server subprocess."""
|
||||
if sys.version_info >= (3, 11):
|
||||
self.process = subprocess.Popen(
|
||||
"deltachat-rpc-server",
|
||||
@@ -143,7 +130,6 @@ class Rpc:
|
||||
self.close()
|
||||
|
||||
def reader_loop(self) -> None:
|
||||
"""Process JSON-RPC responses from the RPC server process output."""
|
||||
try:
|
||||
while line := self.process.stdout.readline():
|
||||
response = json.loads(line)
|
||||
@@ -171,13 +157,12 @@ class Rpc:
|
||||
logging.exception("Exception in the writer loop")
|
||||
|
||||
def get_queue(self, account_id: int) -> Queue:
|
||||
"""Get event queue corresponding to the given account ID."""
|
||||
if account_id not in self.event_queues:
|
||||
self.event_queues[account_id] = Queue()
|
||||
return self.event_queues[account_id]
|
||||
|
||||
def events_loop(self) -> None:
|
||||
"""Request new events and distributes them between queues."""
|
||||
"""Requests new events and distributes them between queues."""
|
||||
try:
|
||||
while True:
|
||||
if self.closing:
|
||||
@@ -193,12 +178,12 @@ class Rpc:
|
||||
logging.exception("Exception in the event loop")
|
||||
|
||||
def wait_for_event(self, account_id: int) -> Optional[dict]:
|
||||
"""Wait for the next event from the given account and returns it."""
|
||||
"""Waits for the next event from the given account and returns it."""
|
||||
queue = self.get_queue(account_id)
|
||||
return queue.get()
|
||||
|
||||
def clear_all_events(self, account_id: int):
|
||||
"""Remove all queued-up events for a given account. Useful for tests."""
|
||||
"""Removes all queued-up events for a given account. Useful for tests."""
|
||||
queue = self.get_queue(account_id)
|
||||
try:
|
||||
while True:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.159.5"
|
||||
version = "1.159.3"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "1.159.5"
|
||||
"version": "1.159.3"
|
||||
}
|
||||
|
||||
11
deny.toml
11
deny.toml
@@ -10,6 +10,9 @@ ignore = [
|
||||
# Unmaintained instant
|
||||
"RUSTSEC-2024-0384",
|
||||
|
||||
# Unmaintained backoff
|
||||
"RUSTSEC-2025-0012",
|
||||
|
||||
# Unmaintained paste
|
||||
"RUSTSEC-2024-0436",
|
||||
]
|
||||
@@ -22,6 +25,7 @@ ignore = [
|
||||
skip = [
|
||||
{ name = "async-channel", version = "1.9.0" },
|
||||
{ name = "bitflags", version = "1.3.2" },
|
||||
{ name = "core-foundation", version = "0.9.4" },
|
||||
{ name = "event-listener", version = "2.5.3" },
|
||||
{ name = "generator", version = "0.7.5" },
|
||||
{ name = "getrandom", version = "0.2.12" },
|
||||
@@ -30,18 +34,20 @@ skip = [
|
||||
{ name = "linux-raw-sys", version = "0.4.14" },
|
||||
{ name = "loom", version = "0.5.6" },
|
||||
{ name = "netlink-packet-route", version = "0.17.1" },
|
||||
{ name = "nom", version = "7.1.3" },
|
||||
{ name = "nix", version = "0.26.4" },
|
||||
{ name = "nix", version = "0.27.1" },
|
||||
{ name = "rand_chacha", version = "0.3.1" },
|
||||
{ name = "rand_core", version = "0.6.4" },
|
||||
{ name = "rand", version = "0.8.5" },
|
||||
{ name = "redox_syscall", version = "0.3.5" },
|
||||
{ name = "regex-automata", version = "0.1.10" },
|
||||
{ name = "regex-syntax", version = "0.6.29" },
|
||||
{ name = "rtnetlink", version = "0.13.1" },
|
||||
{ name = "rustix", version = "0.38.44" },
|
||||
{ name = "security-framework", version = "2.11.1" },
|
||||
{ name = "strum_macros", version = "0.26.2" },
|
||||
{ name = "strum", version = "0.26.2" },
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "lru", version = "0.12.3" },
|
||||
{ name = "thiserror-impl", version = "1.0.69" },
|
||||
{ name = "thiserror", version = "1.0.69" },
|
||||
{ name = "wasi", version = "0.11.0+wasi-snapshot-preview1" },
|
||||
@@ -78,7 +84,6 @@ allow = [
|
||||
"MPL-2.0",
|
||||
"Unicode-3.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Unlicense",
|
||||
"Zlib",
|
||||
]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "1.159.5"
|
||||
version = "1.159.3"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.8"
|
||||
|
||||
@@ -1 +1 @@
|
||||
2025-05-14
|
||||
2025-04-24
|
||||
@@ -5,10 +5,12 @@ use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::task::{JoinHandle, JoinSet};
|
||||
use tokio::task::JoinHandle;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
@@ -302,6 +304,12 @@ impl Accounts {
|
||||
/// This is an auxiliary function and not part of public API.
|
||||
/// Use [Accounts::background_fetch] instead.
|
||||
async fn background_fetch_no_timeout(accounts: Vec<Context>, events: Events) {
|
||||
async fn background_fetch_and_log_error(account: Context) {
|
||||
if let Err(error) = account.background_fetch().await {
|
||||
warn!(account, "{error:#}");
|
||||
}
|
||||
}
|
||||
|
||||
events.emit(Event {
|
||||
id: 0,
|
||||
typ: EventType::Info(format!(
|
||||
@@ -309,15 +317,11 @@ impl Accounts {
|
||||
accounts.len()
|
||||
)),
|
||||
});
|
||||
let mut set = JoinSet::new();
|
||||
for account in accounts {
|
||||
set.spawn(async move {
|
||||
if let Err(error) = account.background_fetch().await {
|
||||
warn!(account, "{error:#}");
|
||||
}
|
||||
});
|
||||
}
|
||||
set.join_all().await;
|
||||
let mut futures_unordered: FuturesUnordered<_> = accounts
|
||||
.into_iter()
|
||||
.map(background_fetch_and_log_error)
|
||||
.collect();
|
||||
while futures_unordered.next().await.is_some() {}
|
||||
}
|
||||
|
||||
/// Auxiliary function for [Accounts::background_fetch].
|
||||
|
||||
@@ -995,7 +995,6 @@ impl ChatId {
|
||||
transaction.execute(
|
||||
"INSERT INTO msgs (
|
||||
chat_id,
|
||||
rfc724_mid,
|
||||
from_id,
|
||||
timestamp,
|
||||
type,
|
||||
@@ -1005,10 +1004,9 @@ impl ChatId {
|
||||
param,
|
||||
hidden,
|
||||
mime_in_reply_to)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?);",
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?);",
|
||||
(
|
||||
self,
|
||||
&msg.rfc724_mid,
|
||||
ContactId::SELF,
|
||||
time(),
|
||||
msg.viewtype,
|
||||
|
||||
@@ -95,7 +95,6 @@ impl BackupProvider {
|
||||
pub async fn prepare(context: &Context) -> Result<Self> {
|
||||
let relay_mode = RelayMode::Disabled;
|
||||
let endpoint = Endpoint::builder()
|
||||
.tls_x509() // For compatibility with iroh <0.34.0
|
||||
.alpns(vec![BACKUP_ALPN.to_vec()])
|
||||
.relay_mode(relay_mode)
|
||||
.bind()
|
||||
@@ -302,11 +301,7 @@ pub async fn get_backup2(
|
||||
) -> Result<()> {
|
||||
let relay_mode = RelayMode::Disabled;
|
||||
|
||||
let endpoint = Endpoint::builder()
|
||||
.tls_x509() // For compatibility with iroh <0.34.0
|
||||
.relay_mode(relay_mode)
|
||||
.bind()
|
||||
.await?;
|
||||
let endpoint = Endpoint::builder().relay_mode(relay_mode).bind().await?;
|
||||
|
||||
let conn = endpoint.connect(node_addr, BACKUP_ALPN).await?;
|
||||
let (mut send_stream, mut recv_stream) = conn.open_bi().await?;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use data_encoding::BASE32_NOPAD;
|
||||
use futures_lite::StreamExt;
|
||||
use iroh::{Endpoint, NodeAddr, NodeId, PublicKey, RelayMode, RelayUrl, SecretKey};
|
||||
use iroh::{Endpoint, NodeAddr, NodeId, PublicKey, RelayMap, RelayMode, RelayUrl, SecretKey};
|
||||
use iroh_gossip::net::{Event, Gossip, GossipEvent, JoinOptions, GOSSIP_ALPN};
|
||||
use iroh_gossip::proto::TopicId;
|
||||
use parking_lot::Mutex;
|
||||
@@ -245,7 +245,7 @@ impl Context {
|
||||
.as_ref()
|
||||
.and_then(|conf| conf.iroh_relay.clone())
|
||||
{
|
||||
RelayMode::Custom(RelayUrl::from(relay_url).into())
|
||||
RelayMode::Custom(RelayMap::from_url(RelayUrl::from(relay_url)))
|
||||
} else {
|
||||
// FIXME: this should be RelayMode::Disabled instead.
|
||||
// Currently using default relays because otherwise Rust tests fail.
|
||||
@@ -253,7 +253,6 @@ impl Context {
|
||||
};
|
||||
|
||||
let endpoint = Endpoint::builder()
|
||||
.tls_x509() // For compatibility with iroh <0.34.0
|
||||
.secret_key(secret_key)
|
||||
.alpns(vec![GOSSIP_ALPN.to_vec()])
|
||||
.relay_mode(relay_mode)
|
||||
@@ -272,7 +271,8 @@ impl Context {
|
||||
|
||||
let router = iroh::protocol::Router::builder(endpoint)
|
||||
.accept(GOSSIP_ALPN, gossip.clone())
|
||||
.spawn();
|
||||
.spawn()
|
||||
.await?;
|
||||
|
||||
Ok(Iroh {
|
||||
router,
|
||||
|
||||
@@ -4,7 +4,6 @@ pub(crate) mod data;
|
||||
|
||||
use anyhow::Result;
|
||||
use deltachat_contact_tools::EmailAddress;
|
||||
use hickory_resolver::name_server::TokioConnectionProvider;
|
||||
use hickory_resolver::{config, Resolver, TokioResolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -180,14 +179,14 @@ impl ProviderOptions {
|
||||
/// This does not work at least on some Androids, therefore we fallback
|
||||
/// to the default `ResolverConfig` which uses eg. to google's `8.8.8.8` or `8.8.4.4`.
|
||||
fn get_resolver() -> Result<TokioResolver> {
|
||||
if let Ok(resolver) = TokioResolver::builder_tokio() {
|
||||
return Ok(resolver.build());
|
||||
if let Ok(resolver) = Resolver::tokio_from_system_conf() {
|
||||
return Ok(resolver);
|
||||
}
|
||||
let resolver = Resolver::builder_with_config(
|
||||
let resolver = Resolver::tokio(
|
||||
config::ResolverConfig::default(),
|
||||
TokioConnectionProvider::default(),
|
||||
config::ResolverOpts::default(),
|
||||
);
|
||||
Ok(resolver.build())
|
||||
Ok(resolver)
|
||||
}
|
||||
|
||||
/// Returns provider for the given an e-mail address.
|
||||
|
||||
@@ -374,6 +374,14 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
);
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
if !verify_sender_by_fingerprint(context, &fingerprint, contact_id).await? {
|
||||
warn!(
|
||||
context,
|
||||
"Ignoring {step} message because of fingerprint mismatch."
|
||||
);
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
info!(context, "Fingerprint verified.",);
|
||||
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
|
||||
let Some(auth) = mime_message.get_header(HeaderDef::SecureJoinAuth) else {
|
||||
warn!(
|
||||
@@ -400,14 +408,6 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
}
|
||||
};
|
||||
|
||||
if !verify_sender_by_fingerprint(context, &fingerprint, contact_id).await? {
|
||||
warn!(
|
||||
context,
|
||||
"Ignoring {step} message because of fingerprint mismatch."
|
||||
);
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
|
||||
let contact_addr = Contact::get_by_id(context, contact_id)
|
||||
.await?
|
||||
.get_addr()
|
||||
@@ -427,7 +427,6 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
);
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
info!(context, "Fingerprint verified via Auth code.",);
|
||||
contact_id.regossip_keys(context).await?;
|
||||
ContactId::scaleup_origin(context, &[contact_id], Origin::SecurejoinInvited).await?;
|
||||
// for setup-contact, make Alice's one-to-one chat with Bob visible
|
||||
@@ -435,6 +434,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
if !join_vg {
|
||||
ChatId::create_for_contact(context, contact_id).await?;
|
||||
}
|
||||
info!(context, "Auth verified.",);
|
||||
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
|
||||
inviter_progress(context, contact_id, 600);
|
||||
if let Some(group_chat_id) = group_chat_id {
|
||||
|
||||
@@ -951,34 +951,3 @@ async fn test_parallel_setup_contact() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_wrong_auth_token() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
// Bob should already have Alice's key
|
||||
// so that he can directly send vc-request-with-auth
|
||||
tcm.send_recv(alice, bob, "hi").await;
|
||||
|
||||
let alice_qr = get_securejoin_qr(alice, None).await?;
|
||||
println!("{}", &alice_qr);
|
||||
let invalid_alice_qr = alice_qr.replace("&s=", "&s=INVALIDAUTHTOKEN&someotherkey=");
|
||||
|
||||
join_securejoin(bob, &invalid_alice_qr).await?;
|
||||
let sent = bob.pop_sent_msg().await;
|
||||
|
||||
let msg = alice.parse_msg(&sent).await;
|
||||
assert_eq!(
|
||||
msg.get_header(HeaderDef::SecureJoin).unwrap(),
|
||||
"vc-request-with-auth"
|
||||
);
|
||||
|
||||
alice.recv_msg_trash(&sent).await;
|
||||
|
||||
let alice_bob_contact = alice.add_or_lookup_contact(bob).await;
|
||||
assert!(!alice_bob_contact.is_forward_verified(alice).await?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2210,7 +2210,6 @@ async fn test_self_addr_consistency() -> Result<()> {
|
||||
include_bytes!("../../test-data/webxdc/minimal.xdc"),
|
||||
)?;
|
||||
alice_chat.set_draft(alice, Some(&mut instance)).await?;
|
||||
let mut instance = alice_chat.get_draft(alice).await?.unwrap();
|
||||
let self_addr = instance.get_webxdc_self_addr(alice).await?;
|
||||
let sent = alice.send_msg(alice_chat, &mut instance).await;
|
||||
let db_msg = Message::load_from_db(alice, sent.sender_msg_id).await?;
|
||||
|
||||
Reference in New Issue
Block a user