Compare commits

...

331 Commits

Author SHA1 Message Date
link2xt
1040bc551f chore(release): prepare for 1.124.0 2023-10-04 21:12:38 +00:00
iequidoo
5aa0205c80 fix: Add protected-headers directive to Content-Type of encrypted/signed MIME (#2302)
Add protected-headers="v1" directive to Content-Type of an encrypted/signed MIME so that other MUAs
like Thunderbird display the true message Subject instead of "...".
2023-10-04 19:58:08 +00:00
link2xt
210a4ebcbe ci: test async python bindings with Python 3.11 2023-10-04 19:56:56 +00:00
link2xt
7fc2b06b3f ci(mypy): ignore distutils
Python 3.12 removed `distutils`.
`distutils` from `setuptools` work fine,
but have not typing stubs:
https://github.com/python/typeshed/issues/10255
2023-10-04 19:56:56 +00:00
link2xt
1177c19a43 bulid: build wheels for Python 3.12 and PyPy 3.10 2023-10-04 19:56:56 +00:00
link2xt
8a2417f32d ci: test with Python 3.12 and PyPy 3.10 2023-10-04 19:56:56 +00:00
link2xt
a154347834 fix(deltachat-rpc-client): increase stdio buffer to 64 MiB
Otherwise readline() gets stuck when JSON-RPC response
is longer that 64 KiB.
2023-10-04 16:08:15 +00:00
link2xt
d51adf2aa0 feat(deltachat-rpc-client): log exceptions when long-running tasks die
For example, reader_loop() may die
if readline() tries to read too large line
and thows an exception.
We want to at least log the exception in this case.
2023-10-04 08:22:50 +00:00
link2xt
a5f0c1613e fix: add Let's Encrypt root certificate to reqwest
This certificate does not exist on older Android phones.
It is already added manually for IMAP and SMTP,
this commit adds the same certificate for HTTP requests.
2023-10-03 19:35:39 +00:00
link2xt
7c4bcf9004 api!: deprecate get_next_media() 2023-10-03 16:08:25 +00:00
B. Petersen
2dd44d5f89 fix: cap percentage in connectivity layout to 100%
it may happen that percentages larger than 100% are reported by the provider,
eg. for some time a storage usage of 120% may be accepted.

while we should report the values "as is" to the user,
for the bar, percentages larger 100% will destroy the layout.
2023-10-01 14:54:37 +00:00
link2xt
f656cb29be fix: ignore special chats in get_similar_chat_ids()
For unknown reason trash chat contains members in some existing databases.
Workaround this by ignoring chats_contacts entries with special chat_id.
2023-10-01 07:38:13 +00:00
link2xt
53230b6eb0 chore(cargo): update webpki to fix RUSTSEC-2023-0052 2023-10-01 00:04:45 +00:00
iequidoo
80ca59f152 feat: Remove extra members from the local list in sake of group membership consistency (#3782)
9bd7ab72 brings a possibility of group membership inconsistency to the original Hocuri's algo
described and implemented in e12e026b in sake of security so that nobody can add themselves to a
group by forging "InReplyTo" and other headers. This commit fixes the problem by removing group
members locally if we see a discrepancy with the "To" list in the received message as it is better
for privacy than adding absent members locally. But it shouldn't be a big problem if somebody missed
a member addition, because they will likely recreate the member list from the next received
message. The problem occurs only if that "somebody" managed to reply earlier. Really, it's a problem
for big groups with high message rate, but let it be for now.

Also:
- Query chat contacts from the db only once.
- Update chat contacts in the only transaction, otherwise we can just break the chat contact list
  halfway.
- Allow classic MUA messages to remove group members if a parent message is missing. Currently it
  doesn't matter because unrelated messages go to new ad-hoc groups, but let this logic be outside
  of apply_group_changes(). Just in case if there will be a MUA preserving "Chat-Group-ID" header
  f.e.
2023-09-30 19:14:22 -03:00
link2xt
eb624e43c0 refactor: remove incomplete protected header code skipping Legacy Display Part
The code removed is an incomplete implementation of skipping
the Legacy Display Part specified in
https://www.ietf.org/archive/id/draft-autocrypt-lamps-protected-headers-02.html#section-5.2

The code does not fully implement the specification, e.g.
it does not check that there are exactly two parts.

Delta Chat and Thunderbird are not adding this part anyway,
and it is defined as "transitional" in the draft.

This also removes misplaced warning "Ignoring nested protected headers"
that is printed for every incoming Delta Chat message
since commit 5690c48863
which is part of the PR <https://github.com/deltachat/deltachat-core-rust/pull/982>.
2023-09-30 21:54:08 +00:00
link2xt
532e9cb09a refactor: ignore public key argument in dc_preconfigure_keypair()
Public key can be extracted from the secret key file.
2023-09-30 19:16:23 +00:00
link2xt
ef4d2a7ed0 api!(python): use dc_contact_get_verifier_id()
get_verifier() returns a Contact rather than an address now

dc_contact_get_verifier_addr() is unused.
2023-09-30 15:49:22 +00:00
link2xt
6d2ac30461 fix: do not put the status footer into reaction MIME parts 2023-09-29 16:38:55 +00:00
link2xt
33a203d56e fix: initialise last_msg_id to the highest known row id
Otherwise existing bots migrating to get_next_msgs()
are trying to process all the messages they have in the database.
2023-09-29 13:28:58 +00:00
link2xt
a19811f379 chore(cargo): update tungstenite to fix RUSTSEC-2023-0065
Used `cargo update -p axum`.
2023-09-29 13:08:04 +00:00
link2xt
f23023961e api!: return DC_CONTACT_ID_SELF from dc_contact_get_verifier_id() for directly verified contacts 2023-09-28 19:10:15 +00:00
link2xt
b463a0566e refactor: flatten create_or_lookup_mailinglist() 2023-09-28 15:20:51 +00:00
link2xt
38d5743c06 refactor: do not ignore errors in get_kml()
This removes unnecessary warning
"mimefactory: could not send location: No locations processed"
when there are no locations to send.
2023-09-28 15:19:33 +00:00
link2xt
6990312051 fix: trash only empty *text* parts when location.kml is attached
If the message contains other attachment parts
such as images, they should not go into trash.
2023-09-27 18:51:40 +00:00
link2xt
a7cf51868b test: test send_location 2023-09-27 18:51:40 +00:00
link2xt
815c1b9c49 refactor: resultify location::set() 2023-09-27 18:51:40 +00:00
link2xt
88bba83383 refactor: flatten process_report() 2023-09-26 16:02:14 +00:00
WofWca
b1d517398d refactor: improve comment about Ratelimit
A few people got the impression that if you send 6 messages
in a burst you'll only be able to send the next one in 60 seconds.
Hopefully this can resolve it.
2023-09-26 15:58:24 +00:00
link2xt
4e5b41f150 fix: require valid email addresses in dc_provider_new_from_email[_with_dns]() 2023-09-25 15:51:10 +00:00
B. Petersen
56b2361f01 reset document.update on forwarding
this fixes the test test_forward_webxdc_instance()
2023-09-25 15:20:57 +00:00
B. Petersen
968cc65323 test that update.document is not forwarded
the test is failing currently
2023-09-25 15:20:57 +00:00
link2xt
d0ee21e6dc refactor: flatten GENERATED_PREFIX check in receive_imf_inner 2023-09-25 10:35:07 +00:00
link2xt
a1345f2542 refactor: flatten lookup_chat_by_reply 2023-09-25 10:34:20 +00:00
link2xt
f290fe0871 fix: wrap base64-encoded parts to 76 characters
This is an RFC 2045 requirement for base64-encoded MIME parts.
Previously referenced RFC 5322 requirement
is a general Internet Message Format requirement
and is more generous.
2023-09-25 10:33:46 +00:00
link2xt
aa78e82fed chore(release): prepare for 1.123.0 2023-09-22 22:13:47 +00:00
link2xt
d4e670d5e9 chore(deps): update OpenSSL from 3.1.2 to 3.1.3 2023-09-22 21:57:36 +00:00
link2xt
4553c6521f api!: make dc_jsonrpc_blocking_call accept JSON-RPC request 2023-09-22 21:33:52 +00:00
link2xt
e72d527d88 api: make it possible to import secret key from a file
Previously it was required that a directory path is provided to the import API.
Now it is possible to point directly to the .asc file containing a secret key.

This allows UI to present a file selection dialog to the user
and let select the file directly.

Selecting a directory is still supported for backwards compatibility.
2023-09-20 16:18:45 +00:00
link2xt
f5c36043f6 fix(imex): use "default" in the filename of the default key
Previously the logic was inverted, default key was exported with a number
and all other keys were exported into "default".
2023-09-20 16:18:45 +00:00
iequidoo
b227ff87dc fix: lookup_chat_by_reply(): Skip undecipherable parent messages created by older versions (#4676)
It's just a dirty hack checking the error prefix, but as undecipherable messages are remembered in
the db now, the hack may be removed soon.
2023-09-18 14:55:56 +00:00
iequidoo
676f311f97 fix: lookup_chat_by_reply(): Skip not fully downloaded and undecipherable messages (#4676)
Such a message may be assigned to a wrong chat (e.g. undecipherable group msgs often get assigned to
the 1:1 chat with the sender). Add `DownloadState::Undecipherable` so that messages referencing
undecipherable ones don't go to that wrong chat too. Also do not reply to not fully downloaded
messages. Before `Message.error` was checked for that purpose, but a message can be error for many
reasons.
2023-09-18 14:55:56 +00:00
link2xt
f02299c06c chore(release): prepare for 1.122.0 2023-09-12 17:33:22 +00:00
link2xt
ed781af52c chore(cargo): update to OpenSSL 3.0
OpenSSL 1.1.1 has reached End of Life:
https://www.openssl.org/blog/blog/2023/09/11/eol-111/
2023-09-12 17:11:58 +00:00
link2xt
67043177a9 fix: reopen all connections on database passpharse change
Previously only one connection, the one used to change the key,
was working after passphrase change.

With this fix the whole pool of connections
is recreated on passphrase change, so there is no need
to reopen the database manually.
2023-09-12 16:34:26 +00:00
link2xt
49cc5fb673 feat: add RSA-4096 key generation support 2023-09-12 12:33:34 +00:00
link2xt
68c95dee17 refactor(pgp): add constants for encryption algorithm and hash
These constants are current defaults in `pgp` crate,
this change would prevent accidental change due to rPGP upgrade
and make it easier to change in a single place.
2023-09-12 11:12:59 +02:00
iequidoo
9bd7ab7280 fix: apply_group_changes(): Forbid membership changes from possible non-members (#3782)
It can be not good for membership consistency if we missed a message adding a member, but improves
security because nobody can add themselves to a group from now on.
2023-09-12 00:30:02 -03:00
link2xt
7a359f6318 build(python): add link to mastodon into projects.urls
Such links are displayed on PyPI with mastodon icon.
2023-09-11 04:47:55 +00:00
link2xt
38b31aa88d fix: do not block new group chats if 1:1 chat is blocked
1:1 chat may be blocked while the contact is not
if 1:1 chat was created as a result of scanning
a verified group join QR code with the contact
as the inviter. In this case 1:1 chat is blocked to hide it
while the contact is unblocked.
2023-09-10 21:44:56 +00:00
iequidoo
e12e026bd8 fix: Switch to original Hocuri's group membership consistency algo (#3782)(#4624)
- If we don't know the parent (=In-Reply-To) message, then completely recreate the group member list
  (i.e. use the member list of the incoming message) (because we assume that we missed some messages
  & have a wrong group state).
- If the message has a "Chat-Group-Member-Removed: member@example.com" header, then remove this
  member.
- If the message has a "Chat-Group-Member-Added: member@example.com" header, then add this member.

That means:
- Remove checks for the presense of `ContactId::SELF` in the group. Thus all recipients of a message
  take the same decision about group membership changes, no matter if they are in the group
  currently. This fixes a situation when a recipient thinks it's not a member because it missed a
  message about its addition before.
  NOTE: But always recreate membership list if SELF has been added. The older versions of DC don't
  always set "In-Reply-To" to the latest message they sent, but to the latest delivered message (so
  it's a race), so we need this heuristic currently.
- Recreate the group member list if we don't know the parent (=In-Reply-To) message, even if the
  sender isn't in the group as per our view, because we missed some messages and our view may be
  stale.
2023-09-09 16:22:13 -03:00
iequidoo
212fbc125c fix: ChatId::parent_query(): Don't filter out OutPending and OutFailed messages
The new message for which `parent_query()` is done may assume that it will be received in a context
affected by those messages, e.g. they could add new members to a group and the new message will
contain them in "To:". Anyway recipients must be prepared to orphaned references.
2023-09-09 16:22:13 -03:00
link2xt
2939de013b api(jsonrpc): return only chat IDs for similar chats
This is already the way `get_chatlist_entries` works.

`get_similar_chatlist_entries` is renamed into
`get_similar_chat_ids` because return values are not entries anymore.
2023-09-08 18:47:31 +00:00
link2xt
0562e23ee0 chore(cargo): bump webpki from 0.22.0 to 0.22.1 2023-09-08 07:00:18 +00:00
link2xt
a8551510cd chore(release): prepare for 1.121.0 2023-09-06 20:53:00 +00:00
link2xt
087f6edd0c refactor: make set_msg_failed accept &mut Message instead of MsgId 2023-09-06 20:33:37 +00:00
link2xt
d6b7ee04a0 docs: document range argument of render_webxdc_status_update_object() 2023-09-06 20:33:37 +00:00
link2xt
d5c5ff8b3f refactor: accept &mut Message in create_send_msg_job() 2023-09-06 20:33:37 +00:00
link2xt
dc4396a699 fix: update passed by reference Message in prepare_msg_raw() 2023-09-06 20:33:37 +00:00
link2xt
a74b00c3f9 refactor: move try_calc_and_set_dimensions() call to prepare_msg_blob() 2023-09-06 20:33:37 +00:00
link2xt
2fdb9f8b7e fix: do not ignore errors in try_calc_and_set_dimensions 2023-09-06 20:33:37 +00:00
link2xt
80fac3f1b8 refactor: use let-else in resend_msgs() 2023-09-06 20:33:37 +00:00
link2xt
17a6c88cc7 api: add Message.set_file_from_bytes() API 2023-09-06 20:33:37 +00:00
link2xt
1ba69dbb9b fix: reset MIME type if passed to set_file value is None 2023-09-06 20:33:37 +00:00
link2xt
ab1c7ebbe2 refactor: remove unnecessary local variable save_param 2023-09-06 20:33:37 +00:00
link2xt
ee715da078 fix: do not ignore chat loading errors in forward_msgs() 2023-09-06 20:33:37 +00:00
link2xt
27e177dc05 refactor: resultify set_msg_failed() 2023-09-06 20:33:37 +00:00
link2xt
7aac4bfc83 docs: document WebXDC-related SQL tables 2023-09-06 20:33:37 +00:00
link2xt
7b24f9b7a4 docs: comment that msgs_status_updates.update_item_read column is unused 2023-09-06 20:33:37 +00:00
link2xt
b36b902eeb build: build node packages on Ubuntu 18.04
Debian 10 has glibc 2.28, so packages built with it
do not work on Ubuntu 18.04.
2023-09-06 17:23:04 +00:00
link2xt
30024abb6c feat: add API to get similar chats 2023-09-05 16:47:19 +00:00
link2xt
1d9702e9e7 refactor: add ChatId::get_timestamp() 2023-09-05 16:47:19 +00:00
link2xt
ee2eae63d6 fix: do not allow dots at the end of email addresses 2023-09-05 13:14:37 +00:00
link2xt
cd477936b5 api: add dc_context_change_passphrase() 2023-09-05 12:41:31 +00:00
link2xt
dbe9d7e34e refactor(provider): create provider database entries at compile time
According to cargo-llvm-lines it reduces the number of lines
in the crate by 0.7%.
2023-09-05 00:03:34 +00:00
link2xt
49f143e0d5 refactor: make get_abs_path non-generic
Generic functions compile into multiple implementations,
increasing number of lines for LLVM to process,
code size and compilation times.
2023-09-04 00:43:24 +00:00
link2xt
9d7bdf369d refactor: use slices instead of vectors in provider database 2023-09-03 20:19:58 +00:00
adbenitez
a270db1d87 remove aiodns optional dependency from required dependencies 2023-09-03 00:32:08 +00:00
iequidoo
7c7cd9cc80 fix: Return from dc_get_chatlist(DC_GCL_FOR_FORWARDING) only chats where we can send (#4616)
I.e. exclude from the list the following chats as well:
- Read-only mailing lists.
- Chats we're not a member of.

But as for ProtectionBroken chats, we return them, as that may happen to a verified chat at any
time. It may be confusing if a chat that is normally in the list disappears suddenly. The UI need to
deal with that case anyway.

(cherry picked from commit 83ef25e7de)
2023-09-01 12:28:41 -03:00
iequidoo
47d465e6e4 fix: Save mime headers for messages not signed with a known key (#4557)
If a message is unsigned or signed with an unknown key, `MimeMessage::was_encrypted()` returns
false. So, it mustn't be checked when deciding whether to look into
`MimeMessage::decoded_data`. Looking through git history one can see that it's just a wrong check
left in the code for historical reasons.
2023-08-31 14:07:12 -03:00
link2xt
03d3e0578f refactor(deltachat-rpc-client): drop support for keyword arguments
All Rust methods have positional arguments and it is not going to change.
2023-08-31 00:46:04 +00:00
iequidoo
440a442f30 fix: Allow membership changes by a MUA if we're not in the group (#4624)
Other MUAs don't set add/remove headers, so the only way for them to re-add us to the group is to
add us to To/CC/wherever. Previously it worked only for other members that are still in the group so
that they properly handled our re-addition, but we didn't.
2023-08-29 22:19:15 -03:00
link2xt
1da52d7d1d refactor: use split_once() instead of regexp in heuristically_parse_ndn
Reduce dependency on the huge `regex` crate.
2023-08-29 18:41:07 +00:00
link2xt
4d74f625d3 chore(release): prepare for 1.120.0 2023-08-28 11:54:38 +00:00
link2xt
0a94fbc735 fix: update async-imap to 0.9.1 to fix memory leak 2023-08-28 09:04:28 +00:00
link2xt
9ef34890fa chore: fix beta clippy warnings 2023-08-28 04:09:52 +00:00
iequidoo
3e07f2c173 fix: prepare_msg_blob(): If cannot recode image, don't use it if it has Exif
We mustn't send images with Exif as it can leak metadata such as location, camera model, etc.
2023-08-27 23:16:19 -03:00
iequidoo
ee28298d7f fix: W/a sending images sent as stickers on some platforms (#4611)
Check if a sticker has at least one fully transparent corner and otherwise change the Sticker type
to Image. This would fix both Android and iOS at the same time and prevent similar bug on future
platforms that may get this bug like Ubuntu Touch.
2023-08-27 23:16:19 -03:00
link2xt
62aed13880 refactor: hide pgp module from public API 2023-08-27 22:03:00 +00:00
link2xt
bffe934acc refactor: hide accounts.rs constants from public API 2023-08-27 22:03:00 +00:00
link2xt
87ffcaf03e build: update to Rust 1.72.0 2023-08-25 23:04:47 +00:00
link2xt
2635146328 build: update to Zig 0.11.0 2023-08-25 21:20:11 +00:00
link2xt
d727d85f6d chore(python): fix ruff 0.0.286 warnings 2023-08-25 20:57:44 +00:00
link2xt
81a7af10c7 chore(python): fix lint errors 2023-08-25 01:14:24 +00:00
link2xt
4a6e94f8ab build(deny): ignore RUSTSEC-2023-0052 2023-08-25 01:04:02 +00:00
link2xt
146fe50e20 build(cargo-deny): ignore RUSTSEC-2022-0093
It is an API issue that can only be fixed in rPGP and iroh upstream.
2023-08-25 01:03:56 +00:00
link2xt
9bf2850fb1 ci: run on push to stable branch 2023-08-25 00:58:14 +00:00
iequidoo
ba2c36548e fix: Delete messages from SMTP queue only on user demand (#4579)
I.e. from delete_msgs(). Otherwise messages must not be deleted from there, e.g. if a message is
ephemeral, but a network outage lasts longer than the ephemeral message timer, the message still
must be sent upon a successful reconnection.
2023-08-24 23:24:24 +00:00
Simon Laux
d07c743cdc api(jsonrpc): add resend_messages 2023-08-24 22:04:47 +00:00
link2xt
d70c1d48b5 chore(release) prepare for 1.119.1 2023-08-06 16:49:06 +00:00
link2xt
a8e0cb9b5a fix: update xattr from 1.0.0 to 1.0.1 to fix UnsupportedPlatformError import
See the folowing PRs on the `xattr` repository:
- https://github.com/Stebalien/xattr/pull/34
- https://github.com/Stebalien/xattr/pull/38
2023-08-06 16:45:58 +00:00
link2xt
6ea9a8988b test(webxdc): ensure unknown WebXDC update properties do not result in an error 2023-08-06 16:27:29 +00:00
iequidoo
45e35b3571 feat: Guess message viewtype from "application/octet-stream" attachment extension (#4378) 2023-08-05 16:34:06 -03:00
link2xt
e43f9066d8 chore(release): prepare for 1.119.0 2023-08-03 17:03:26 +00:00
link2xt
bba6c8f15a fix: emit MsgsChanged event with correct chat id for replaced messages
Previously an event with DC_CHAT_ID_TRASH was emitted.
2023-07-28 21:51:08 +00:00
iequidoo
55aaec744a feat: Make dc_msg_get_filename() return the original attachment filename (#4309)
It can be used e.g. as a default in the file saving dialog. Also display the original filename in
the message info. For these purposes add Param::Filename in addition to Param::File and use it as an
attachment filename in sent emails.
2023-07-27 14:31:14 -03:00
link2xt
2f24eddb7d fix: base64-encode webxdc updates
Webxdc update messages may contain
long lines that get hard-wrapped
and may corrupt JSON if the message
is not encrypted.

base64-encode the update part
to avoid hard wrapping.

This is not necessary for encrypted
messages, but does not introduce
size overhead as OpenPGP messages
are compressed.
2023-07-26 15:06:29 +00:00
link2xt
a33c91afa9 fix(webxdc): check if status update sender is the member of the correct chat 2023-07-24 22:53:30 +00:00
link2xt
d52f2883cf feat(deltachat-rpc-client): add MSG_DELETED constant 2023-07-24 18:40:32 +00:00
link2xt
b872953bc5 fix(deltachat-jsonrpc): make MessageObject.text non-optional
This is already non-optional on the `deltachat` crate side
since <https://github.com/deltachat/deltachat-core-rust/pull/4517>
2023-07-24 18:36:56 +00:00
link2xt
c1cb6eef08 refactor: improve IMAP logs 2023-07-23 12:19:50 +00:00
B. Petersen
200b808c27 add tests for deletion of webxdc status-updates 2023-07-23 09:16:28 +00:00
B. Petersen
d572d960e5 delete old webxdc status updates during housekeeping 2023-07-23 09:16:28 +00:00
link2xt
5db75128ba fix: accept WebXDC updates in mailing lists 2023-07-22 01:30:52 +00:00
link2xt
fbd2fc8ead test: add test_webxdc_download_on_demand
The test checks that if webxdc update is too large to
download with the current `download_limit`,
it is applied afterwards when the user manually downloads the update message.
2023-07-21 21:23:44 +00:00
link2xt
bc73c16df7 chore: fix compiler warnings 2023-07-21 09:33:59 +00:00
link2xt
0a50bad555 fix(deltachat-rpc-server): update tokio-tar to fix backup import
tokio-tar 0.3.0 prints message "create_dir_all ..." to stdout during import.
tokio-tar 0.3.1 has removed this debug output which broke stdio JSON-RPC protocol.
2023-07-17 17:07:13 +00:00
link2xt
82c0058129 test: add basic import/export test for async python 2023-07-17 17:07:13 +00:00
link2xt
1bd307a26a api(deltachat-rpc-client): add Account.{import,export}_backup methods 2023-07-17 17:07:13 +00:00
link2xt
740f43a2d6 fix: do not resync IMAP after initial configuration
If there was no previous `configured_addr`,
then there is no need to run IMAP resync.
2023-07-17 16:38:09 +00:00
link2xt
c14f45a8f5 fix(imap): avoid IMAP move loops when DeltaChat folder is aliased
Some Dovecot servers are configured
to alias "INBOX.DeltaChat" and "DeltaChat" to the same folder.
In this case Delta Chat moves new emails from "INBOX"
to "DeltaChat", but then discovers the same mail in "INBOX.DeltaChat"
and tries to move it to "DeltaChat" again.
Each time a MOVE command is issued to move the message
from "INBOX.DeltaChat" to "DeltaChat", the message gets a new UID.

To prevent such IMAP move loop between aliased folders,
we do not move the message if we have already seen it on IMAP,
i.e. we have its Message-ID in the `imap` table.
Note that we do not use `rfc724_mid_exists`,
because it checks the `msgs` table and would consider
BCC-self messages seen even before we see them in the Inbox,
preventing their move to the DeltaChat folder.

Duplicate messages and messages without Message-IDs
are not moved anymore, but this is better
than having an infinite move loop.
2023-07-14 11:21:31 +00:00
link2xt
8269116dba chore(python): fix ruff warnings 2023-07-12 19:49:12 +00:00
link2xt
db941ccf88 fix: return valid MsgId from receive_imf() when the message is replaced
receive_imf() calls add_parts()
which INSERTs or UPDATEs the message using UPSERT [1].
It then uses last_insert_rowid() to get
the ID of the inserted message.
However, it is incorrect to use last_insert_rowid()
if an UPDATE was executed instead of INSERT.
The solution is to use `RETURNING id` clause
to make the UPSERT statement return message ID in any case [2].

The fix is tested in test_webxdc_update_for_not_downloaded_instance()
and with a debug_assert!.

[1] https://www.sqlite.org/lang_UPSERT.html
[2] https://sqlite.org/forum/forumpost/9ce3bc1c4a85c15f
2023-07-11 08:43:01 +00:00
link2xt
a464cbdfe6 chore: rustfmt 2023-07-11 00:03:57 +00:00
link2xt
ea4a0530b8 docs: update default value for show_emails in dc_set_config() documentation 2023-07-08 09:25:09 +02:00
link2xt
9d3b2d4844 chore(release): prepare for 1.118.0 2023-07-07 17:01:03 +00:00
link2xt
c312280ab3 build(git-cliff): do not fail if commit.footers is undefined 2023-07-07 16:41:10 +00:00
link2xt
572b99a2e1 Rewrite member added/removed messages even if the change is not allowed
PR https://github.com/deltachat/deltachat-core-rust/pull/4529
2023-07-06 13:47:56 +00:00
link2xt
3992b5a063 refactor: move handle_mdn and handle_ndn to mimeparser and make them private
Previously handle_mdn was erroneously exposed in the public API.
2023-07-06 12:34:49 +00:00
link2xt
b97cb4b55e chore(cargo): update Cargo.lock 2023-07-06 12:33:58 +00:00
link2xt
64c218f1ea fix: rewrite "member added" message even if change is not allowed 2023-07-06 01:36:42 +00:00
link2xt
deed790950 test: test that "member added" message is rewritten even if self is not in chat 2023-07-06 01:36:23 +00:00
link2xt
b33ae3cd0f fix: rewrite "member removed" message even if change is not allowed 2023-07-06 01:36:23 +00:00
link2xt
9480699362 test: test that "member removed" message is rewritten if self is not in chat 2023-07-06 01:36:08 +00:00
link2xt
94c190e844 fix: use different member added/removal messages locally and on the network
This commit adds new stock strings
"I added member ...",
"I removed member ..." and
"I left the group" that are sent over the network
and are visible in classic MUAs like Thunderbird.

Member name in these messages uses authname
instead of the display name,
so the name set locally does not get leaked when
a member is added or removed.
2023-07-05 16:16:30 +00:00
link2xt
578e47666f api!: replace message::get_msg_info() with MsgId.get_info() 2023-07-05 14:22:37 +00:00
link2xt
7eeced50d1 chore(cargo): bump backtrace from 0.3.67 to 0.3.68 2023-07-04 19:19:27 +00:00
link2xt
46e127ad27 chore(cargo): bump hermit-abi from 0.3.1 to 0.3.2
0.3.1 is yanked
2023-07-04 16:56:19 +00:00
link2xt
4891849e28 chore: add LICENSE file to deltachat-rpc-client
This gets packaged into the wheel when you run `python -m build`.
2023-07-03 23:01:46 +00:00
link2xt
e0dd83d538 chore: update MPL 2.0 license text
New text was downloaded from https://www.mozilla.org/en-US/MPL/,
specifically https://www.mozilla.org/media/MPL/2.0/index.f75d2927d3c1.txt

The difference is that the URL is changed from http:// to https://
2023-07-04 00:52:31 +02:00
link2xt
aac8bb950c chore(python): add "Topic :: Communications :: Chat" classifier 2023-07-03 22:49:14 +00:00
link2xt
bf21796bc0 chore(python): change bindings status to production/stable 2023-07-03 22:48:51 +00:00
link2xt
9cbf413064 chore(deltachat-rpc-client): add Trove classifiers 2023-07-03 21:22:10 +00:00
dependabot[bot]
1b57eb4d8d chore(cargo): bump serde from 1.0.164 to 1.0.166
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.164 to 1.0.166.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.164...v1.0.166)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 20:31:10 +00:00
link2xt
5152e702bd chore(cargo): bump num-derive from 0.3.3 to 0.4.0 2023-07-03 19:49:58 +00:00
link2xt
c80f1a1997 chore(cargo): bump toml from 0.7.4 to 0.7.5 2023-07-03 19:41:31 +00:00
link2xt
88759c815b refactor(python): flatten the API of deltachat module 2023-07-03 16:19:31 +00:00
link2xt
9c68fac4b6 api!: make Message.text non-optional
Message.set_text() and Message.get_text() are modified accordingly
to accept String and return String.

Messages which previously contained None text
are now represented as messages with empty text.
Use Message.set_text("".to_string())
instead of Message.set_text(None).
2023-07-03 15:36:32 +00:00
dependabot[bot]
8e17e400b3 chore(cargo): bump uuid from 1.3.3 to 1.4.0
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.3.3 to 1.4.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.3.3...1.4.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 00:20:16 +00:00
dependabot[bot]
dae3857db8 chore(cargo): bump syn from 2.0.18 to 2.0.23
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.18 to 2.0.23.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.18...2.0.23)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 23:45:42 +00:00
dependabot[bot]
695f71e124 chore(cargo): bump strum from 0.24.1 to 0.25.0
Bumps [strum](https://github.com/Peternator7/strum) from 0.24.1 to 0.25.0.
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/commits)

---
updated-dependencies:
- dependency-name: strum
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 23:45:32 +00:00
link2xt
2d30afd212 fix: do not run simplify() on dehtml() output
simplify() is written to process incoming plaintext messages
and extract footers and quotes from them.
Incoming messages contain various quote styles
and simplify() implements heuristics to detects them.

If dehtml() output is processed by simplify(),
simplify() heuristics may erroneously detect
footers and quotes in produced plaintext.

dehtml() should directly detect quotes
instead of converting them to plaintext quotes
for parsing with simplify().
2023-07-02 23:12:13 +00:00
link2xt
5fe94e8bce docs(dehtml): document AddText variants 2023-07-02 23:12:13 +00:00
link2xt
1351f71632 fix(remove_contact_from_chat): do not emit event if the contact was not removed 2023-07-02 23:00:49 +00:00
link2xt
d42322b38b fix(remove_contact_from_chat): bubble up chat loading errors 2023-07-02 23:00:49 +00:00
link2xt
ce6876c418 fix(remove_contact_from_chat): do not ignore database errors when loading the contact 2023-07-02 23:00:49 +00:00
link2xt
2a6b7d9766 api(contact): add Contact::get_by_id_optional() API 2023-07-02 23:00:49 +00:00
link2xt
fa1924da2b api!(contact): remove Contact::load_from_db() in favor of Contact::get_by_id() 2023-07-02 23:00:49 +00:00
link2xt
d5214eb192 refactor: check if the contact is blocked with a dedicated SQL query
Avoid loading unnecessary fields from the database.
2023-07-02 23:00:49 +00:00
dependabot[bot]
c47324d671 chore(cargo): bump regex from 1.8.3 to 1.8.4
Bumps [regex](https://github.com/rust-lang/regex) from 1.8.3 to 1.8.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.8.3...1.8.4)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 21:49:32 +00:00
dependabot[bot]
3f8ec5ec56 chore(cargo): bump testdir from 0.7.3 to 0.8.0
Bumps [testdir](https://github.com/flub/testdir) from 0.7.3 to 0.8.0.
- [Changelog](https://github.com/flub/testdir/blob/main/CHANGELOG.md)
- [Commits](https://github.com/flub/testdir/compare/v0.7.3...v0.8.0)

---
updated-dependencies:
- dependency-name: testdir
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 21:49:23 +00:00
dependabot[bot]
fab504b54c chore(cargo): bump url from 2.3.1 to 2.4.0
Bumps [url](https://github.com/servo/rust-url) from 2.3.1 to 2.4.0.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.3.1...v2.4.0)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 20:25:59 +00:00
dependabot[bot]
dd32430ade chore(cargo): bump strum_macros from 0.24.3 to 0.25.0
Bumps [strum_macros](https://github.com/Peternator7/strum) from 0.24.3 to 0.25.0.
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/commits)

---
updated-dependencies:
- dependency-name: strum_macros
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 20:23:28 +00:00
link2xt
eb943625a6 chore(cargo): bump num_cpus from 1.15.0 to 1.16.0 2023-07-02 20:19:40 +00:00
dependabot[bot]
32ac4a01ca chore(cargo): bump tempfile from 3.5.0 to 3.6.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.5.0 to 3.6.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 19:36:50 +00:00
dependabot[bot]
f01a9d7d5c chore(cargo): bump rustyline from 11.0.0 to 12.0.0
Bumps [rustyline](https://github.com/kkawakam/rustyline) from 11.0.0 to 12.0.0.
- [Release notes](https://github.com/kkawakam/rustyline/releases)
- [Changelog](https://github.com/kkawakam/rustyline/blob/master/History.md)
- [Commits](https://github.com/kkawakam/rustyline/compare/v11.0.0...v12.0.0)

---
updated-dependencies:
- dependency-name: rustyline
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 18:33:04 +00:00
dependabot[bot]
a5db7104c2 chore(cargo): bump quote from 1.0.28 to 1.0.29
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.28 to 1.0.29.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.28...1.0.29)

---
updated-dependencies:
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 18:03:20 +00:00
dependabot[bot]
18aeb14003 chore(cargo): bump quick-xml from 0.28.2 to 0.29.0
Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.28.2 to 0.29.0.
- [Release notes](https://github.com/tafia/quick-xml/releases)
- [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md)
- [Commits](https://github.com/tafia/quick-xml/compare/v0.28.2...v0.29.0)

---
updated-dependencies:
- dependency-name: quick-xml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 18:02:37 +00:00
dependabot[bot]
4ad2d6e340 chore(cargo): bump sha2 from 0.10.6 to 0.10.7
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.6 to 0.10.7.
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.6...sha2-v0.10.7)

---
updated-dependencies:
- dependency-name: sha2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 17:40:20 +00:00
dependabot[bot]
ce9cd54993 chore(cargo): bump serde_json from 1.0.96 to 1.0.99
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.96 to 1.0.99.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.96...v1.0.99)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 15:37:55 +00:00
dependabot[bot]
23f540f9f9 chore(cargo): bump serde from 1.0.163 to 1.0.164
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.163 to 1.0.164.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.163...v1.0.164)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 15:37:23 +00:00
dependabot[bot]
f994b2d8e4 chore(cargo): bump log from 0.4.18 to 0.4.19
Bumps [log](https://github.com/rust-lang/log) from 0.4.18 to 0.4.19.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.18...0.4.19)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 15:37:05 +00:00
dependabot[bot]
6e42b85a36 Merge pull request #4495 from deltachat/dependabot/cargo/tokio-1.29.1 2023-07-02 04:17:26 +00:00
link2xt
d69e42377d chore(deps): update human-panic from 1.1.4 to 1.1.5 2023-07-02 01:20:35 +00:00
link2xt
de9330b52f chore(deps): update libc from 0.2.146 to 0.2.147 2023-07-02 01:19:13 +00:00
dependabot[bot]
01d1c4c04b chore(cargo): bump tokio from 1.29.0 to 1.29.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.29.0 to 1.29.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.29.0...tokio-1.29.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-02 01:18:16 +00:00
link2xt
7d98978269 chore: rustfmt 2023-07-02 01:17:12 +00:00
link2xt
5024f48609 refactor: add error context to Message::load_from_db() 2023-07-01 19:40:23 +00:00
link2xt
e975568122 docs: fix a typo in get_for_contact documentation 2023-07-01 19:39:50 +00:00
link2xt
1f71c69325 fix: update tokio to 1.29.0
This fixes panic after sending 29 offline messages.
2023-06-28 09:11:57 +00:00
Hocuri
b80ec8507c test: reproduce tokio panic 2023-06-28 08:40:23 +00:00
link2xt
3a3f3542d9 chore: remove libm entry from deny.toml 2023-06-27 14:34:26 +00:00
link2xt
657c5fa947 chore(deps): update iana-time-zone-haiku to 0.1.2
This removes `cxx` dependency.
2023-06-27 14:33:34 +00:00
link2xt
7d0b25c209 chore(deps): update ed25519-dalek 2023-06-27 14:30:27 +00:00
link2xt
8d26303cad refactor(simplify): remove local variable empty_body 2023-06-25 12:55:21 +00:00
Simon Laux
0d8a76593a fix: make avatar image work on more platforms (use xlink:href)
Without it delta touch (qt) can not render the avatar image
and also inkscape does not show it either.
2023-06-22 15:42:08 +00:00
dependabot[bot]
7b49fb2eb6 chore(deps): bump openssl from 0.10.48 to 0.10.55 in /fuzz
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.48 to 0.10.55.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.48...openssl-v0.10.55)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 15:32:38 +00:00
link2xt
efa37dd283 fix: preserve indentation when converting plaintext to HTML 2023-06-22 13:24:40 +00:00
link2xt
323e44da04 test: make plaintext to HTML conversion tests non-async 2023-06-22 13:24:40 +00:00
link2xt
70efd0f10a refactor: use with statement with multiple contexts
Otherwise `ruff` check SIM117 fails.
2023-06-21 00:30:48 +00:00
link2xt
fcec81b4c1 chore(cargo): update openssl to 0.10.55
This fixes https://rustsec.org/advisories/RUSTSEC-2023-0044
2023-06-20 23:37:41 +00:00
link2xt
dd806b2d88 test: add make-python-testenv.sh script
This scripts makes it easy to (re)create python testing environment.
2023-06-19 16:24:03 +00:00
link2xt
5659c1b9c2 refactor: do not treat messages without headers as a special case 2023-06-19 12:00:03 +00:00
link2xt
d538d29b94 docs: document how to regenerate Node.js constants before the release 2023-06-17 14:30:11 +00:00
link2xt
b4209fac2e ci(concourse): install venv before trying to use it
This is a follow-up to cbe1671104

I changed `apt-get` arguments for x86_64 glibc builds,
but forgot to change it for x86_64 musl, aarch64 glibc and aarch64 musl.

Because of this, `upload-wheels` task failed with a message:
```
The virtual environment was not created successfully because ensurepip is not
available.  On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

    apt install python3.11-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.
```
2023-06-16 19:15:05 +00:00
link2xt
4d6dfa120e docs: add missing links for 1.116.0 and 1.117.0 to the changelog 2023-06-16 17:18:04 +00:00
link2xt
f92108be1d chore(release): prepare for 1.117.0 2023-06-16 16:42:17 +00:00
link2xt
00cb72f04d fix(dehtml): do not insert unnecessary newlines when parsing <p> tags
Previously, parsing of `<p>Foo</p><p>Bar</p>`
resulted in `\n\nFoo\n\n\n\nBar\n\n`.

Now it results in `Foo\n\nBar`.
2023-06-16 16:27:14 +00:00
link2xt
92e34d67e6 chore: add openrpc.json to .gitignore 2023-06-16 14:32:09 +00:00
link2xt
65bff8339f chore: update generated node files 2023-06-16 14:27:45 +00:00
Sebastian Klähn
768f8175e6 api(rust): Add api endpoint get_status_update (#4468)
* start

* derive default

* make some webxdc file public

* shorten code

* Add from<u32> for chatid

* reduce changes to a bare minimum

* fix nested errors

* @hocuris fixes

* fix @link2xt changes

---------

Co-authored-by: septias <xxsebastian.kleahnxx@gmail.com>
2023-06-15 15:35:57 +02:00
link2xt
c3f352aff1 fix(dehtml): skip links with empty text 2023-06-14 15:41:38 +00:00
link2xt
5ac2d1b8cb ci: remove mergeable configuration
Mergeable is disabled because it was requiring
that PR title follows conventional commit notation
even when PR consisted of multiple commits
and was not planned to be squash merged.
2023-06-14 14:59:33 +00:00
link2xt
8214b2b8c1 docs: document how conventional commits interact with squash merges 2023-06-14 14:59:33 +00:00
link2xt
53ab8a3b35 fix: update to async-imap 0.9.0 to remove deprecated ouroboros dependency
`ouroboros` is deprecated with a security advisory recommending
switch to `self_cell` crate:
https://rustsec.org/advisories/RUSTSEC-2023-0042

async-imap 0.9.0 depends on `self_cell` instead of `ouroboros`.
2023-06-14 15:46:42 +02:00
link2xt
cbe1671104 ci(concourse): install devpi in a virtual environment
This commit solves the "error: externally-managed-environment"
which started appearing since Debian 12 release.
`debian` is used as an Docker image to run devpi.
2023-06-13 16:51:53 +00:00
link2xt
0d0e223238 test(python): replace legacy tmpdir fixture with tmp_path 2023-06-12 15:14:25 +00:00
Simon Laux
4767f1ce74 docs: readme remove legacy from dc-node, mark napi as experimental 2023-06-10 00:29:59 +02:00
link2xt
1a62b6d77f refactor: rename MimeMessage.header into MimeMessage.headers 2023-06-09 22:20:46 +00:00
link2xt
915008d474 build: use 1 codegen-units for release builds 2023-06-08 16:25:14 +00:00
link2xt
9646766793 build: disable unused brotli feature "ffi-api" 2023-06-08 00:02:11 +00:00
link2xt
e948ec3256 test: regression test for case-sensitive comparison of gossip header to contact address 2023-06-07 19:50:42 +00:00
link2xt
9ab9d2eb7b fix: ignore address case when comparing the To: field to Autocrypt-Gossip: 2023-06-07 19:50:42 +00:00
link2xt
437f8c48c4 api(python): make Contact.is_verified() return bool 2023-06-07 19:50:42 +00:00
link2xt
e6d9a49187 api: emit DC_EVENT_MSGS_CHANGED per chat when messages are deleted 2023-06-07 09:07:21 +00:00
link2xt
33a014eea4 feat: add MsgDeleted event 2023-06-07 09:07:21 +00:00
link2xt
9be871ccf6 fix: emit DC_EVENT_MSGS_CHANGED without IDs when the message expires
Specifying msg IDs that cannot be loaded in the event payload
results in an error when the UI tries to load the message.
Instead, emit an event without IDs
to make the UI reload the whole messagelist.
2023-06-07 09:07:21 +00:00
Sebastian Klähn
6eb8abe535 feat: new group membership update algorithm
New algorithm improves group consistency
in cases of missing messages,
restored old backups and replies from classic MUAs.

Co-authored-by: Hocuri <hocuri@gmx.de>
Co-authored-by: link2xt <link2xt@testrun.org>
2023-06-06 23:49:55 +00:00
link2xt
91bf87fa80 fix: update from yanked libc 0.2.145 to 0.2.146
https://github.com/rust-lang/libc/issues/3264
2023-06-06 22:45:17 +00:00
link2xt
a2599ef08a ci: run cargo check with musl libc 2023-06-06 22:45:17 +00:00
link2xt
22d0a4bb32 build: use Rust 1.70.0 to compile deltachat-rpc-server releases 2023-06-06 22:45:17 +00:00
link2xt
7a160033b6 chore(release): prepare for 1.116.0 2023-06-05 19:11:07 +00:00
link2xt
3442748be7 ci: update clippy to 1.70.0 2023-06-05 18:52:30 +00:00
dependabot[bot]
d451bcfbe3 Merge pull request #4452 from deltachat/dependabot/cargo/percent-encoding-2.3.0 2023-06-05 18:51:57 +00:00
link2xt
b2993242e4 docs(python): document pytest fixtures
These docstrings are displayed by `pytest --fixtures`
when `deltachat` package is installed from PyPI.
2023-06-05 18:34:27 +00:00
dependabot[bot]
5eaa9eeed2 chore(cargo): bump percent-encoding from 2.2.0 to 2.3.0
Bumps [percent-encoding](https://github.com/servo/rust-url) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: percent-encoding
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 11:04:55 +00:00
dependabot[bot]
3ed2ac8f0c Merge pull request #4429 from deltachat/dependabot/cargo/uuid-1.3.3 2023-06-05 11:00:45 +00:00
dependabot[bot]
0145203f7b Merge pull request #4432 from deltachat/dependabot/cargo/proptest-1.2.0 2023-06-05 10:29:23 +00:00
dependabot[bot]
59588b319e cargo: bump uuid from 1.3.2 to 1.3.3
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.3.2 to 1.3.3.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.3.2...1.3.3)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 10:14:10 +00:00
link2xt
f917c7de6b chore(cargo): bump criterion from 0.4.0 to 0.5.1 2023-06-05 10:12:27 +00:00
dependabot[bot]
84888fa4c4 cargo: bump proptest from 1.1.0 to 1.2.0
Bumps [proptest](https://github.com/proptest-rs/proptest) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/proptest-rs/proptest/releases)
- [Changelog](https://github.com/proptest-rs/proptest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/proptest-rs/proptest/compare/v1.1.0...v1.2.0)

---
updated-dependencies:
- dependency-name: proptest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-04 22:42:06 +00:00
link2xt
e0b1644488 ci: run node.js lint on Windows 2023-06-04 22:39:15 +00:00
link2xt
4beba8ce3c chore(gitattributes): configure LF line endings for JavaScript files 2023-06-04 22:39:15 +00:00
dependabot[bot]
bc521a685d chore(cargo): bump once_cell from 1.17.1 to 1.18.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.17.1 to 1.18.0.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.17.1...v1.18.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-04 18:54:12 -03:00
dependabot[bot]
33caa0f499 Merge pull request #4441 from deltachat/dependabot/cargo/tokio-1.28.2 2023-06-04 18:10:06 +00:00
dependabot[bot]
033ce41c0f Merge pull request #4450 from deltachat/dependabot/cargo/libc-0.2.145 2023-06-04 18:09:43 +00:00
dependabot[bot]
88a62e1f6e chore(cargo): bump libc from 0.2.142 to 0.2.145
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.142 to 0.2.145.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.142...0.2.145)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-04 14:24:50 +00:00
dependabot[bot]
dd30f6ab7d cargo: bump tokio from 1.28.0 to 1.28.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.28.0 to 1.28.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.28.0...tokio-1.28.2)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-04 14:24:16 +00:00
dependabot[bot]
140d116d98 Merge pull request #4438 from deltachat/dependabot/cargo/chrono-0.4.26 2023-06-04 14:23:24 +00:00
dependabot[bot]
d96b783909 cargo: bump chrono from 0.4.24 to 0.4.26
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.24 to 0.4.26.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.24...v0.4.26)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-04 13:41:48 +00:00
dependabot[bot]
572c7f2efb Merge pull request #4434 from deltachat/dependabot/cargo/base64-0.21.2 2023-06-04 13:40:51 +00:00
link2xt
bcd6c226f6 ci: document why node prebuilds are built in a container 2023-06-04 13:30:24 +00:00
link2xt
bae61746f8 fix: build deltachat-node prebuilds on Debian 10
This reduces glibc version requirement
and makes sure it does not increase
as Ubuntu version on CI runners is updated.
2023-06-04 12:35:12 +00:00
dependabot[bot]
31f2766074 cargo: bump base64 from 0.21.0 to 0.21.2
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.21.0 to 0.21.2.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.21.0...v0.21.2)

---
updated-dependencies:
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-04 10:33:01 +00:00
link2xt
b06c8baa9c chore(cargo): bump pretty_env_logger from 0.4.0 to 0.5.0 2023-06-04 10:31:29 +00:00
dependabot[bot]
1e479fe4a3 Merge pull request #4431 from deltachat/dependabot/cargo/serde-1.0.163 2023-06-04 09:13:42 +00:00
link2xt
8ea8ee02ed ci: add top comments to GH Actions workflows 2023-06-02 20:19:57 +00:00
link2xt
55bc556bcf ci: use working-directory instead of cd command 2023-06-02 17:00:27 +00:00
link2xt
3b6d21301b ci: remove broken node-delete-preview.yml workflow
Old previews should be deleted by the cronjob
running on the `download.delta.chat` server.
2023-06-02 16:50:15 +00:00
dependabot[bot]
472195c7d9 cargo: bump serde from 1.0.160 to 1.0.163
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.160 to 1.0.163.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.160...v1.0.163)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-02 15:26:34 +00:00
dependabot[bot]
afb8b5ce55 Merge pull request #4435 from deltachat/dependabot/cargo/toml-0.7.4 2023-06-02 15:25:40 +00:00
dependabot[bot]
de3c82ef43 cargo: bump toml from 0.7.3 to 0.7.4
Bumps [toml](https://github.com/toml-rs/toml) from 0.7.3 to 0.7.4.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.7.3...toml-v0.7.4)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-02 13:49:25 +00:00
dependabot[bot]
4255ae4c2d cargo: bump log from 0.4.17 to 0.4.18
Bumps [log](https://github.com/rust-lang/log) from 0.4.17 to 0.4.18.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.17...0.4.18)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-02 12:28:21 +00:00
dependabot[bot]
4b4e2f700e Merge pull request #4437 from deltachat/dependabot/cargo/syn-2.0.18 2023-06-02 12:27:32 +00:00
dependabot[bot]
81fde5c680 Merge pull request #4436 from deltachat/dependabot/cargo/reqwest-0.11.18 2023-06-02 12:26:56 +00:00
dependabot[bot]
5340a7d033 cargo: bump syn from 2.0.15 to 2.0.18
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.15 to 2.0.18.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.15...2.0.18)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-02 10:31:52 +00:00
dependabot[bot]
fc82d728fc cargo: bump reqwest from 0.11.17 to 0.11.18
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.17 to 0.11.18.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.17...v0.11.18)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-02 10:31:34 +00:00
link2xt
136e9179e9 ci(dependabot): use chore prefix for dependency updates 2023-06-02 10:30:34 +00:00
link2xt
31e19ca56c chore(cargo): bump regex from 1.8.1 to 1.8.3 2023-06-02 10:28:58 +00:00
link2xt
f2b02b7bb0 docs: document how to get Concourse CI secrets from pass 2023-06-01 19:50:42 +00:00
link2xt
646ace8e7a chore: add link to the changelog 2023-06-01 12:54:12 +00:00
link2xt
a2495716b6 Merge tag 'v1.112.10' 2023-06-01 12:53:10 +00:00
link2xt
0f579c6415 chore(release): prepare for 1.112.10 2023-06-01 12:49:20 +00:00
link2xt
3eddc9164c fix: disable fetch_existing_msgs setting by default
This caused too many problems after switching
the default setting for `show_emails`
from DC_SHOW_EMAILS_OFF to DC_SHOW_EMAILS_ALL
in <https://github.com/deltachat/deltachat-core-rust/pull/4019>

There is a topic <https://support.delta.chat/t/setting-no-chats-only-for-show-classic-e-mails-showing-classic-emails/2481>
on the forum with multiple requests to revert this setting
due to old emails being downloaded.
2023-06-01 11:11:36 +00:00
link2xt
dd29fae49b fix: update h2 to fix RUSTSEC-2023-0034 2023-05-31 19:00:20 +00:00
Franz Heinzmann (Frando)
5b435d11c7 feat(jsonrpc): generate OpenRPC definitions
When running `cargo test` in the deltachat-jsonrpc folder,
a new file `openrpc/openrpc.json` will be created with an
[OpenRPC](https://spec.open-rpc.org/) definition.

It can be copy-pasted into the
[OpenRPC playground](https://playground.open-rpc.org/)
and used to generate clients in other languages.
2023-05-21 22:11:07 +00:00
Hocuri
b9b0d20e8d test: Add golden tests infrastructure (#4395) 2023-05-21 22:10:33 +00:00
link2xt
c68a2e3820 ci: require Python 3.8 for deltachat-rpc-client
With Python 3.7 asynchronous tests randomly fail
with "RuntimeError: Event loop is closed" during shutdown.
Backtrace of the error includes `SafeChildWatcher` calls.

Python 3.8 has replaced `SafeChildWatcher`
with a new `ThreadedChildWatcher` by default [1]
as a bugfix for
"asyncio.create_subprocess_exec() only works with main event loop" bug [2].

Python 3.7 scheduled end of life is 2023-06-27
according to <https://devguide.python.org/versions/>.

[1] https://github.com/python/cpython/pull/14344
[2] https://bugs.python.org/issue35621
2023-05-21 13:08:03 +00:00
link2xt
c7ad0b1f4f test: make test_group_with_removed_message_id less flaky
It failed in CI once, apparently
because "Gr." was generated inside the group ID.
2023-05-21 11:50:41 +00:00
link2xt
0dd9e3a77e docs: add contributing guidelines
Co-authored-by: Hocuri <hocuri@gmx.de>
2023-05-20 10:34:02 +00:00
link2xt
d27e3d085e ci: remove incorrect comment 2023-05-19 11:03:26 +00:00
link2xt
10b2aa5350 chore: remove release.toml
We are not using cargo-release at the moment.
2023-05-19 09:25:27 +00:00
link2xt
3a29a555bf test(python): make test_mdn_asymmetric less flaky
Normally the message has UID 1,
but this is not true when account is reused.
In this case the message may be
"Marked messages 2 in folder DeltaChat as seen."
and the test times out waiting for
"Marked messages 1 in folder DeltaChat as seen."
2023-05-18 18:39:40 +00:00
link2xt
f024f396bf docs: document release process in RELEASE.md 2023-05-17 10:53:26 +00:00
link2xt
24d52c5909 build(set_core_version.py): expect release date in the changelog
git-cliff adds the date to the changelog automatically,
so set_core_version.py only needs to check that the date is in place.
2023-05-16 22:47:28 +00:00
link2xt
f9dc8edbcb feat: add more context to message loading errors 2023-05-16 19:40:38 +00:00
link2xt
2e6f98f4e4 api: add dc_jsonrpc_blocking_call() 2023-05-15 19:55:13 +00:00
link2xt
50431d8cfe build(git-cliff): add "Documentation" section 2023-05-15 15:34:16 +00:00
link2xt
55fcd589db build(git-cliff): put "ci" commits into "CI" section of changelog 2023-05-15 15:34:16 +00:00
link2xt
081178d623 ci(mergeable): allow PR titles to start with "ci" and "build" 2023-05-15 15:34:16 +00:00
link2xt
92d5857150 build(git-cliff): update the link to configuration documentation 2023-05-15 15:34:16 +00:00
link2xt
bb45c249a3 build(git-cliff): add unconventional commits to "Other" section 2023-05-15 15:34:16 +00:00
link2xt
8796e0472a build(git-cliff): add scope to changelog entries 2023-05-15 15:34:16 +00:00
link2xt
3bd16ba045 build(git-cliff): add "Build system" section 2023-05-15 15:34:16 +00:00
link2xt
4b7ff6f003 build(git-cliff): add period at the end of changelog entry titles
Make it consistent with the rest of the changelog.
2023-05-15 15:34:16 +00:00
link2xt
53449ea5b3 build(git-cliff): remove git-cliff footer 2023-05-15 15:34:16 +00:00
link2xt
3b381c4862 docs: update instructions for python devenv 2023-05-15 15:33:26 +00:00
link2xt
ce729263a5 chore(release): prepare for v1.115.0 2023-05-13 18:45:51 +00:00
link2xt
67480999c0 feat: do not cut incoming text if "bot" config is set 2023-05-13 15:53:48 +00:00
Simon Laux
fb8b9f60ce api: jsonrpc: new MessageReadReceipt type and get_message_read_receipts(account_id, message_id) jsonrpc method 2023-05-12 16:32:52 +02:00
Simon Laux
9ed36d4e05 api: rust: add get_msg_read_receipts(context, msg_id) - get the contacts that send read receipts for a message 2023-05-12 16:32:52 +02:00
link2xt
e3c01d76c4 fix: do not return an error from send_msg_to_smtp if retry limit is exceeded
This prevents sending the other messages for which the limit was not exceeded.
2023-05-12 13:26:37 +00:00
link2xt
cb7f96449d Merge tag 'v1.112.9' into 'master' 2023-05-12 11:35:30 +00:00
link2xt
e4f4dacaf0 chore(release): prepare for 1.112.9 2023-05-12 11:17:54 +00:00
link2xt
9fc1fe74ad fix: delete smtp rows when message sending is cancelled 2023-05-12 11:17:23 +00:00
link2xt
991089d98e feat: improve SMTP logging 2023-05-12 10:07:55 +00:00
link2xt
c7a250da31 fix: fetch at most 100 existing messages even if EXISTS was not received
According to RFC 3501, EXISTS must always be sent in response to SELECT.
But if the server does not send it for some reason,
async-imap uses the default value, so we will essentially fetch `1:*`
and downloading all messages may take a long time.
2023-05-12 10:07:29 +00:00
link2xt
c5b6bad956 fix: recreate smtp table with AUTOINCREMENT
This ensures the rows cannot be confused accidentally
when referred by row_id.
2023-05-12 09:53:58 +00:00
link2xt
c4534ff621 feat: improve SMTP logging 2023-05-12 09:53:32 +00:00
Hocuri
b2c299fa82 fix: Don't put a double dot at the end of error messages (#4398) 2023-05-11 18:43:22 +02:00
link2xt
68aa15950a fix: fix PR URL template in git-cliff configuration (#4396) 2023-05-11 17:43:52 +02:00
Asiel Díaz Benítez
4a593a8d7e api!:jsonrpc: sort reactions in descending order (#4388)
convert `JSONRPCReactions.reactions` to a `Vec<JSONRPCReaction>` with unique reactions and their count, sorted in descending order.
---------

Co-authored-by: link2xt <link2xt@testrun.org>
2023-05-11 08:29:43 -04:00
Hocuri
2328ba54be feat: Generate our changelogs using git-cliff (#4393) 2023-05-10 18:18:42 +02:00
link2xt
e216dfd655 fix: delete smtp rows when message sending is cancelled 2023-05-10 16:08:00 +00:00
link2xt
86472aba2c refactor: iterate over msg_ids without .iter() 2023-05-10 14:32:38 +00:00
link2xt
15ce54edfb Update set_core_version.py with new release process 2023-05-10 12:00:14 +00:00
Sebastian Klähn
b7bbb3ee9a Allow sending a raw webxdc status update (#4386)
- create new api endpoint

Co-authored-by: bjoern <r10s@b44t.com>

* renaming function

---------

Co-authored-by: septias <xxsebastian.kleahnxx@gmail.com>
Co-authored-by: bjoern <r10s@b44t.com>
2023-05-09 21:15:09 +02:00
link2xt
d3236e79fd fix: fetch at most 100 existing messages even if EXISTS was not received
According to RFC 3501, EXISTS must always be sent in response to SELECT.
But if the server does not send it for some reason,
async-imap uses the default value, so we will essentially fetch `1:*`
and downloading all messages may take a long time.
2023-05-09 10:56:01 +00:00
Simon Laux
aa212b2b7e jsonrpc: MessageSearchResult, always include chat_name(not an option anymore), also add author_id and chat_type (#4374)
* jsonrpc: `MessageSearchResult`, always include `chat_name`(not an option anymore), also add `author_id` and `chat_type`

* replace .ok_or_else with anyhow context
2023-05-09 10:22:32 +00:00
Simon Laux
a0c51b3c3a fix: jsonrpc: typescript client: fix types of events in event emitter (#4373)
* fix: jsonrpc: typescript client: fix types of events in event emitter

* add ts ignore to remove ts error (when building in ts5)

doing the types right via casting is to complicated IMO and has no real benefit here.
2023-05-09 09:57:49 +00:00
link2xt
e50d7724e3 feat: build deltachat-rpc-server releases for x86_64 macOS 2023-05-08 19:04:38 +00:00
meganoahj
0d30e66dda add thumbnail for image in chatlist item (#4311)
* add thumbnail for image in chatlist item

* added preview for stickers and gifs also

* added last_message_type and last_message_id to ChatListItemFetchResult

addresses link2ext comment

* code optimization

fixes:
https://github.com/deltachat/deltachat-core-rust/pull/4311#discussion_r1162791672

* code formatting

* fix clippy

* add changelog entry

---------

Co-authored-by: meganoahj <s3ccg@testrun.org>
Co-authored-by: Simon Laux <mobile.info@simonlaux.de>
2023-05-08 19:44:43 +02:00
Asiel Díaz Benítez
5c3df7e452 jsonrpc: use camel-case for Event struct and move events.rs to types folder (#4372)
* use camel-case for Event struct and move events.rs to types folder

---------

Co-authored-by: link2xt <link2xt@testrun.org>
2023-05-08 06:49:19 -04:00
dependabot[bot]
3efbe5b1ef cargo: bump regex from 1.7.3 to 1.8.1 (#4361) 2023-05-07 13:31:29 +00:00
dependabot[bot]
52cfba06ea Merge pull request #4356 from deltachat/dependabot/cargo/dirs-5.0.1 2023-05-06 17:58:24 +00:00
dependabot[bot]
afba63603e cargo: bump dirs from 5.0.0 to 5.0.1
Bumps [dirs](https://github.com/soc/dirs-rs) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/soc/dirs-rs/releases)
- [Commits](https://github.com/soc/dirs-rs/commits)

---
updated-dependencies:
- dependency-name: dirs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-06 11:36:44 +00:00
dependabot[bot]
3a25d6a44e Merge pull request #4362 from deltachat/dependabot/cargo/human-panic-1.1.4 2023-05-06 11:35:30 +00:00
dependabot[bot]
0688895022 Merge pull request #4360 from deltachat/dependabot/cargo/axum-0.6.18 2023-05-05 21:35:26 +00:00
dependabot[bot]
ce0e5416e6 cargo: bump human-panic from 1.1.3 to 1.1.4
Bumps [human-panic](https://github.com/rust-cli/human-panic) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/rust-cli/human-panic/releases)
- [Changelog](https://github.com/rust-cli/human-panic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/human-panic/compare/v1.1.3...v1.1.4)

---
updated-dependencies:
- dependency-name: human-panic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-05 21:24:47 +00:00
link2xt
7918a6801e scripts/deny.sh: update package cache before running cargo deny 2023-05-05 21:23:10 +00:00
link2xt
6af631e8df Add JSON-RPC API to get reactions 2023-05-05 20:49:55 +00:00
dependabot[bot]
625ecaa9b5 cargo: bump axum from 0.6.12 to 0.6.18
Bumps [axum](https://github.com/tokio-rs/axum) from 0.6.12 to 0.6.18.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.6.12...axum-v0.6.18)

---
updated-dependencies:
- dependency-name: axum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-05 20:31:03 +00:00
dependabot[bot]
24fe1b9c15 Merge pull request #4364 from deltachat/dependabot/cargo/tokio-1.28.0 2023-05-05 20:29:57 +00:00
link2xt
ba36d09c70 Make the bots automatically accept group chat contact requests 2023-05-05 16:37:34 +00:00
link2xt
f57be7187e Update deny.toml 2023-05-05 13:23:44 +00:00
dependabot[bot]
6a00338f79 cargo: bump tokio from 1.27.0 to 1.28.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.27.0 to 1.28.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.27.0...tokio-1.28.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-05 10:48:24 +00:00
dependabot[bot]
16906210e1 Merge pull request #4354 from deltachat/dependabot/cargo/tokio-util-0.7.8 2023-05-05 10:47:12 +00:00
dependabot[bot]
f9b4540387 Merge pull request #4358 from deltachat/dependabot/cargo/syn-2.0.15 2023-05-05 10:46:48 +00:00
dependabot[bot]
9755438d0d Merge pull request #4366 from deltachat/dependabot/cargo/serde-1.0.160 2023-05-05 10:44:58 +00:00
dependabot[bot]
fe9534ed7d cargo: bump serde from 1.0.159 to 1.0.160
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.159 to 1.0.160.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.159...v1.0.160)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 10:55:01 +00:00
dependabot[bot]
134c9ada68 cargo: bump syn from 2.0.13 to 2.0.15
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.13 to 2.0.15.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.13...2.0.15)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 10:54:57 +00:00
dependabot[bot]
a3240452ff cargo: bump tokio-util from 0.7.7 to 0.7.8
Bumps [tokio-util](https://github.com/tokio-rs/tokio) from 0.7.7 to 0.7.8.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.7...tokio-util-0.7.8)

---
updated-dependencies:
- dependency-name: tokio-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 10:54:42 +00:00
157 changed files with 9557 additions and 4159 deletions

8
.gitattributes vendored
View File

@@ -2,6 +2,14 @@
# ensures this even if the user has not set core.autocrlf.
* text=auto
# Checkout JavaScript files with LF line endings
# to prevent `prettier` from reporting errors on Windows.
*.js eol=lf
*.jsx eol=lf
*.ts eol=lf
*.tsx eol=lf
*.json eol=lf
# This directory contains email messages verbatim, and changing CRLF to
# LF will corrupt them.
test-data/** text=false

View File

@@ -5,5 +5,5 @@ updates:
schedule:
interval: "monthly"
commit-message:
prefix: "cargo"
prefix: "chore(cargo)"
open-pull-requests-limit: 50

26
.github/mergeable.yml vendored
View File

@@ -1,26 +0,0 @@
version: 2
mergeable:
- when: pull_request.*
name: "Changelog check"
validate:
- do: or
validate:
- do: description
must_include:
regex: "#skip-changelog"
- do: and
validate:
- do: dependent
changed:
file: "src/**"
required: ["CHANGELOG.md"]
- do: dependent
changed:
file: "deltachat-ffi/src/**"
required: ["CHANGELOG.md"]
fail:
- do: checks
status: "action_required"
payload:
title: Changelog might need an update
summary: "Check if CHANGELOG.md needs an update or add #skip-changelog to the PR description."

View File

@@ -1,3 +1,7 @@
# GitHub Actions workflow to
# lint Rust and Python code
# and run Rust tests, Python tests and async Python tests.
name: Rust CI
# Cancel previously started workflow runs
@@ -11,6 +15,7 @@ on:
push:
branches:
- master
- stable
env:
RUSTFLAGS: -Dwarnings
@@ -20,7 +25,7 @@ jobs:
name: Lint Rust
runs-on: ubuntu-latest
env:
RUSTUP_TOOLCHAIN: 1.68.2
RUSTUP_TOOLCHAIN: 1.72.0
steps:
- uses: actions/checkout@v3
- name: Install rustfmt and clippy
@@ -34,6 +39,10 @@ jobs:
- name: Check
run: cargo check --workspace --all-targets --all-features
# Check with musl libc target which is used for `deltachat-rpc-server` releases.
- name: Check musl
run: scripts/zig-musl-check.sh
cargo_deny:
name: cargo deny
runs-on: ubuntu-latest
@@ -173,15 +182,15 @@ jobs:
include:
# Currently used Rust version.
- os: ubuntu-latest
python: 3.11
python: 3.12
- os: macos-latest
python: 3.11
python: 3.12
# PyPy tests
- os: ubuntu-latest
python: pypy3.9
python: pypy3.10
- os: macos-latest
python: pypy3.9
python: pypy3.10
# Minimum Supported Python Version = 3.7
# This is the minimum version for which manylinux Python wheels are
@@ -222,7 +231,10 @@ jobs:
fail-fast: false
matrix:
include:
# Currently used Rust version.
# Async Python bindings do not depend on Python version,
# but are tested on Python 3.11 until Python 3.12 support
# is added to `aiohttp` dependency:
# https://github.com/aio-libs/aiohttp/issues/7646
- os: ubuntu-latest
python: 3.11
- os: macos-latest
@@ -230,15 +242,16 @@ jobs:
# PyPy tests
- os: ubuntu-latest
python: pypy3.9
python: pypy3.10
- os: macos-latest
python: pypy3.9
python: pypy3.10
# Minimum Supported Python Version = 3.7
# This is the minimum version for which manylinux Python wheels are
# built. Test it with minimum supported Rust version.
# Minimum Supported Python Version = 3.8
#
# Python 3.7 has at least one known bug related to starting subprocesses
# in asyncio programs: <https://bugs.python.org/issue35621>
- os: ubuntu-latest
python: 3.7
python: 3.8
runs-on: ${{ matrix.os }}
steps:

View File

@@ -1,4 +1,10 @@
# Manually triggered action to build deltachat-rpc-server binaries.
# GitHub Actions workflow
# to build `deltachat-rpc-server` binaries
# and upload them to the release.
#
# The workflow is automatically triggered on releases.
# It can also be triggered manually
# to produce binary artifacts for testing.
name: Build deltachat-rpc-server binaries
@@ -84,9 +90,28 @@ jobs:
path: target/${{ matrix.target}}/release/${{ matrix.path }}
if-no-files-found: error
build_macos:
name: Build deltachat-rpc-server for macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Setup rust target
run: rustup target add x86_64-apple-darwin
- name: Build
run: cargo build --release --package deltachat-rpc-server --target x86_64-apple-darwin --features vendored
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: deltachat-rpc-server-x86_64-macos
path: target/x86_64-apple-darwin/release/deltachat-rpc-server
if-no-files-found: error
publish:
name: Upload binaries to the release
needs: ["build_linux", "build_windows"]
needs: ["build_linux", "build_windows", "build_macos"]
permissions:
contents: write
runs-on: "ubuntu-latest"
@@ -97,7 +122,7 @@ jobs:
- name: Compose dist/ directory
run: |
mkdir dist
for x in x86_64 i686 aarch64 armv7 win32.exe win64.exe; do
for x in x86_64 i686 aarch64 armv7 win32.exe win64.exe x86_64-macos; do
mv "deltachat-rpc-server-$x"/* "dist/deltachat-rpc-server-$x"
done

View File

@@ -1,3 +1,6 @@
# GitHub Actions workflow
# to automatically approve PRs made by Dependabot.
name: Dependabot auto-approve
on: pull_request

View File

@@ -38,13 +38,12 @@ jobs:
node --version
echo $DELTACHAT_JSONRPC_TAR_GZ
- name: Install dependencies without running scripts
run: |
cd deltachat-jsonrpc/typescript
npm install --ignore-scripts
working-directory: deltachat-jsonrpc/typescript
run: npm install --ignore-scripts
- name: Package
shell: bash
working-directory: deltachat-jsonrpc/typescript
run: |
cd deltachat-jsonrpc/typescript
npm run build
npm pack .
ls -lah

View File

@@ -22,24 +22,19 @@ jobs:
- name: Add Rust cache
uses: Swatinem/rust-cache@v2
- name: npm install
run: |
cd deltachat-jsonrpc/typescript
npm install
working-directory: deltachat-jsonrpc/typescript
run: npm install
- name: Build TypeScript, run Rust tests, generate bindings
run: |
cd deltachat-jsonrpc/typescript
npm run build
working-directory: deltachat-jsonrpc/typescript
run: npm run build
- name: Run integration tests
run: |
cd deltachat-jsonrpc/typescript
npm run test
working-directory: deltachat-jsonrpc/typescript
run: npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
- name: make sure websocket server version still builds
run: |
cd deltachat-jsonrpc
cargo build --bin deltachat-jsonrpc-server --features webserver
working-directory: deltachat-jsonrpc
run: cargo build --bin deltachat-jsonrpc-server --features webserver
- name: Run linter
run: |
cd deltachat-jsonrpc/typescript
npm run prettier:check
working-directory: deltachat-jsonrpc/typescript
run: npm run prettier:check

View File

@@ -1,31 +0,0 @@
# documentation: https://github.com/deltachat/sysadmin/tree/master/download.delta.chat
name: Delete node PR previews
on:
pull_request:
types: [closed]
jobs:
delete:
runs-on: ubuntu-latest
steps:
- name: Get Pull Request ID
id: getid
run: |
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
- name: Renaming
run: |
# create empty file to copy it over the outdated deliverable on download.delta.chat
echo "This preview build is outdated and has been removed." > empty
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
- name: Replace builds with dummy files
uses: horochx/deploy-via-scp@v1.0.1
with:
user: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
host: "download.delta.chat"
port: 22
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
remote: "/var/www/html/download/node/preview/"

View File

@@ -1,3 +1,8 @@
# GitHub Actions workflow to build
# Node.js bindings documentation
# and upload it to the web server.
# Built documentation is available at <https://js.delta.chat/>
name: Generate & upload node.js documentation
on:
@@ -17,8 +22,8 @@ jobs:
node-version: 16.x
- name: npm install and generate documentation
working-directory: node
run: |
cd node
npm i --ignore-scripts
npx typedoc
mv docs js

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
os: [macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -46,13 +46,12 @@ jobs:
- name: Install dependencies & build
if: steps.cache.outputs.cache-hit != 'true'
run: |
cd node
npm install --verbose
working-directory: node
run: npm install --verbose
- name: Build Prebuild
working-directory: node
run: |
cd node
npm run prebuildify
tar -zcvf "${{ matrix.os }}.tar.gz" -C prebuilds .
@@ -62,10 +61,81 @@ jobs:
name: ${{ matrix.os }}
path: node/${{ matrix.os }}.tar.gz
prebuild-linux:
name: Prebuild Linux
runs-on: ubuntu-latest
# Build Linux prebuilds inside a container with old glibc for backwards compatibility.
# Debian 10 contained glibc 2.28 at the time of the writing (2023-06-04): https://packages.debian.org/buster/libc6
# Ubuntu 18.04 is at the End of Standard Support since June 2023, but it contains glibc 2.27,
# so we are using it to support Ubuntu 18.04 setups that are still not upgraded.
container: ubuntu:18.04
steps:
# Working directory is owned by 1001:1001 by default.
# Change it to our user.
- name: Change working directory owner
run: chown root:root .
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
- run: apt-get update
# Python is needed for node-gyp
- name: Install curl, python and compilers
run: apt-get install -y curl build-essential python3
- name: Install Rust
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: System info
run: |
rustc -vV
rustup -vV
cargo -vV
npm --version
node --version
- name: Cache node modules
uses: actions/cache@v3
with:
path: |
${{ env.APPDATA }}/npm-cache
~/.npm
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
- name: Cache cargo index
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/
~/.cargo/git
target
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
- name: Install dependencies & build
if: steps.cache.outputs.cache-hit != 'true'
working-directory: node
run: npm install --verbose
- name: Build Prebuild
working-directory: node
run: |
npm run prebuildify
tar -zcvf "linux.tar.gz" -C prebuilds .
- name: Upload Prebuild
uses: actions/upload-artifact@v3
with:
name: linux
path: node/linux.tar.gz
pack-module:
needs: prebuild
needs: [prebuild, prebuild-linux]
name: Package deltachat-node and upload to download.delta.chat
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Install tree
run: sudo apt install tree
@@ -96,10 +166,10 @@ jobs:
npm --version
node --version
echo $DELTACHAT_NODE_TAR_GZ
- name: Download Ubuntu prebuild
- name: Download Linux prebuild
uses: actions/download-artifact@v1
with:
name: ubuntu-20.04
name: linux
- name: Download macOS prebuild
uses: actions/download-artifact@v1
with:
@@ -111,11 +181,11 @@ jobs:
- shell: bash
run: |
mkdir node/prebuilds
tar -xvzf ubuntu-20.04/ubuntu-20.04.tar.gz -C node/prebuilds
tar -xvzf linux/linux.tar.gz -C node/prebuilds
tar -xvzf macos-latest/macos-latest.tar.gz -C node/prebuilds
tar -xvzf windows-latest/windows-latest.tar.gz -C node/prebuilds
tree node/prebuilds
rm -rf ubuntu-20.04 macos-latest windows-latest
rm -rf linux macos-latest windows-latest
- name: Install dependencies without running scripts
run: |
npm install --ignore-scripts

View File

@@ -1,3 +1,6 @@
# GitHub Actions workflow
# to test Node.js bindings.
name: "node.js tests"
# Cancel previously started workflow runs
@@ -52,25 +55,13 @@ jobs:
- name: Install dependencies & build
if: steps.cache.outputs.cache-hit != 'true'
run: |
cd node
npm install --verbose
working-directory: node
run: npm install --verbose
- name: Test
timeout-minutes: 10
if: runner.os != 'Windows'
run: |
cd node
npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
- name: Run tests on Windows, except lint
timeout-minutes: 10
if: runner.os == 'Windows'
run: |
cd node
npm run test:mocha
working-directory: node
run: npm run test
env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"

View File

@@ -1,4 +1,5 @@
# Manually triggered action to build a Windows repl.exe which users can
# Manually triggered GitHub Actions workflow
# to build a Windows repl.exe which users can
# download to debug complex bugs.
name: Build Windows REPL .exe

View File

@@ -1,3 +1,7 @@
# GitHub Actions workflow
# to build `deltachat_fii` crate documentation
# and upload it to <https://cffi.delta.chat/>
name: Build & Deploy Documentation on cffi.delta.chat
on:

View File

@@ -1,12 +1,426 @@
# Changelog
## Unreleased
## [1.124.0] - 2023-10-04
### API-Changes
- [**breaking**] Return `DC_CONTACT_ID_SELF` from `dc_contact_get_verifier_id()` for directly verified contacts.
- Deprecate `dc_contact_get_verifier_addr`.
- python: use `dc_contact_get_verifier_id()`. `get_verifier()` returns a Contact rather than an address now.
- Deprecate `get_next_media()`.
- Ignore public key argument in `dc_preconfigure_keypair()`. Public key is extracted from the private key.
### Fixes
- Wrap base64-encoded parts to 76 characters.
- Require valid email addresses in `dc_provider_new_from_email[_with_dns]`.
- Do not trash messages with attachments and no text when `location.kml` is attached ([#4749](https://github.com/deltachat/deltachat-core-rust/issues/4749)).
- Initialise `last_msg_id` to the highest known row id. This ensures bots migrated from older version to `dc_get_next_msgs()` API do not process all previous messages from scratch.
- Do not put the status footer into reaction MIME parts.
- Ignore special chats in `get_similar_chat_ids()`. This prevents trash chat from showing up in similar chat list ([#4756](https://github.com/deltachat/deltachat-core-rust/issues/4756)).
- Cap percentage in connectivity layout to 100% ([#4765](https://github.com/deltachat/deltachat-core-rust/pull/4765)).
- Add Let's Encrypt root certificate to `reqwest`. This should allow scanning `DCACCOUNT` QR-codes on older Android phones when the server has a Let's Encrypt certificate.
- deltachat-rpc-client: Increase stdio buffer to 64 MiB to avoid Python bots crashing when trying to load large messages via a JSON-RPC call.
- Add `protected-headers` directive to Content-Type of encrypted messages with attachments ([#2302](https://github.com/deltachat/deltachat-core-rust/issues/2302)). This makes Thunderbird show encrypted Subject for Delta Chat messages.
- webxdc: Reset `document.update` on forwarding. This fixes the test `test_forward_webxdc_instance()`.
### Features / Changes
- Remove extra members from the local list in sake of group membership consistency ([#3782](https://github.com/deltachat/deltachat-core-rust/issues/3782)).
- deltachat-rpc-client: Log exceptions when long-running tasks die.
### Build
- Build wheels for Python 3.12 and PyPy 3.10.
## [1.123.0] - 2023-09-22
### API-Changes
- Make it possible to import secret key from a file with `DC_IMEX_IMPORT_SELF_KEYS`.
- [**breaking**] Make `dc_jsonrpc_blocking_call` accept JSON-RPC request.
### Fixes
- `lookup_chat_by_reply()`: Skip not fully downloaded and undecipherable messages ([#4676](https://github.com/deltachat/deltachat-core-rust/pull/4676)).
- `lookup_chat_by_reply()`: Skip undecipherable parent messages created by older versions ([#4676](https://github.com/deltachat/deltachat-core-rust/pull/4676)).
- imex: Use "default" in the filename of the default key.
### Miscellaneous Tasks
- Update OpenSSL from 3.1.2 to 3.1.3.
## [1.122.0] - 2023-09-12
### API-Changes
- jsonrpc: Return only chat IDs for similar chats.
### Fixes
- Reopen all connections on database passpharse change.
- Do not block new group chats if 1:1 chat is blocked.
- Improve group membership consistency algorithm ([#3782](https://github.com/deltachat/deltachat-core-rust/pull/3782))([#4624](https://github.com/deltachat/deltachat-core-rust/pull/4624)).
- Forbid membership changes from possible non-members ([#3782](https://github.com/deltachat/deltachat-core-rust/pull/3782)).
- `ChatId::parent_query()`: Don't filter out OutPending and OutFailed messages.
### Build system
- Update to OpenSSL 3.0.
- Bump webpki from 0.22.0 to 0.22.1.
- python: Add link to Mastodon into projects.urls.
### Features / Changes
- Add RSA-4096 key generation support.
### Refactor
- pgp: Add constants for encryption algorithm and hash.
## [1.121.0] - 2023-09-06
### API-Changes
- Add `dc_context_change_passphrase()`.
- Add `Message.set_file_from_bytes()` API.
- Add experimental API to get similar chats.
### Build system
- Build node packages on Ubuntu 18.04 instead of Debian 10.
This reduces the requirement for glibc version from 2.28 to 2.27.
### Fixes
- Allow membership changes by a MUA if we're not in the group ([#4624](https://github.com/deltachat/deltachat-core-rust/pull/4624)).
- Save mime headers for messages not signed with a known key ([#4557](https://github.com/deltachat/deltachat-core-rust/pull/4557)).
- Return from `dc_get_chatlist(DC_GCL_FOR_FORWARDING)` only chats where we can send ([#4616](https://github.com/deltachat/deltachat-core-rust/pull/4616)).
- Do not allow dots at the end of email addresses.
- deltachat-rpc-client: Remove `aiodns` optional dependency from required dependencies.
`aiodns` depends on `pycares` which [fails to install in Termux](https://github.com/saghul/aiodns/issues/98).
## [1.120.0] - 2023-08-28
### API-Changes
- jsonrpc: Add `resend_messages`.
### Fixes
- Update async-imap to 0.9.1 to fix memory leak.
- Delete messages from SMTP queue only on user demand ([#4579](https://github.com/deltachat/deltachat-core-rust/pull/4579)).
- Do not send images without transparency as stickers ([#4611](https://github.com/deltachat/deltachat-core-rust/pull/4611)).
- `prepare_msg_blob()`: do not use the image if it has Exif metadata but the image cannot be recoded.
### Refactor
- Hide accounts.rs constants from public API.
- Hide pgp module from public API.
### Build system
- Update to Zig 0.11.0.
- Update to Rust 1.72.0.
### CI
- Run on push to stable branch.
### Miscellaneous Tasks
- python: Fix lint errors.
- python: Fix `ruff` 0.0.286 warnings.
- Fix beta clippy warnings.
## [1.119.1] - 2023-08-06
Bugfix release attempting to fix the [iOS build error](https://github.com/deltachat/deltachat-core-rust/issues/4610).
### Features / Changes
- Guess message viewtype from "application/octet-stream" attachment extension ([#4378](https://github.com/deltachat/deltachat-core-rust/pull/4378)).
### Fixes
- Update `xattr` from 1.0.0 to 1.0.1 to fix UnsupportedPlatformError import.
### Tests
- webxdc: Ensure unknown WebXDC update properties do not result in an error.
## [1.119.0] - 2023-08-03
### Fixes
- imap: Avoid IMAP move loops when DeltaChat folder is aliased.
- imap: Do not resync IMAP after initial configuration.
- webxdc: Accept WebXDC updates in mailing lists.
- webxdc: Base64-encode WebXDC updates to prevent corruption of large unencrypted WebXDC updates.
- webxdc: Delete old webxdc status updates during housekeeping.
- Return valid MsgId from `receive_imf()` when the message is replaced.
- Emit MsgsChanged event with correct chat id for replaced messages.
- deltachat-rpc-server: Update tokio-tar to fix backup import.
### Features / Changes
- deltachat-rpc-client: Add `MSG_DELETED` constant.
- Make `dc_msg_get_filename()` return the original attachment filename ([#4309](https://github.com/deltachat/deltachat-core-rust/pull/4309)).
### API-Changes
- deltachat-rpc-client: Add `Account.{import,export}_backup` methods.
- deltachat-jsonrpc: Make `MessageObject.text` non-optional.
### Documentation
- Update default value for `show_emails` in `dc_set_config()` documentation.
### Refactor
- Improve IMAP logs.
### Tests
- Add basic import/export test for async python.
- Add `test_webxdc_download_on_demand`.
- Add tests for deletion of webxdc status-updates.
## [1.118.0] - 2023-07-07
### API-Changes
- [**breaking**] Remove `Contact::load_from_db()` in favor of `Contact::get_by_id()`.
- Add `Contact::get_by_id_optional()` API.
- [**breaking**] Make `Message.text` non-optional.
- [**breaking**] Replace `message::get_msg_info()` with `MsgId.get_info()`.
- Move `handle_mdn` and `handle_ndn` to mimeparser and make them private.
Previously `handle_mdn` was erroneously exposed in the public API.
- python: flatten the API of `deltachat` module.
### Fixes
- Use different member added/removal messages locally and on the network.
- Update tokio to 1.29.1 to fix core panic after sending 29 offline messages ([#4414](https://github.com/deltachat/deltachat-core-rust/issues/4414)).
- Make SVG avatar image work on more platforms (use `xlink:href`).
- Preserve indentation when converting plaintext to HTML.
- Do not run simplify() on dehtml() output.
- Rewrite member added/removed messages even if the change is not allowed PR ([#4529](https://github.com/deltachat/deltachat-core-rust/pull/4529)).
### Documentation
- Document how to regenerate Node.js constants before the release.
### Build system
- git-cliff: Do not fail if commit.footers is undefined.
### Other
- Dependency updates.
- Update MPL 2.0 license text.
- Add LICENSE file to deltachat-rpc-client.
- deltachat-rpc-client: Add Trove classifiers.
- python: Change bindings status to production/stable.
### Tests
- Add `make-python-testenv.sh` script.
## [1.117.0] - 2023-06-15
### Features
- New group membership update algorithm.
New algorithm improves group consistency
in cases of missing messages,
restored old backups and replies from classic MUAs.
- Add `DC_EVENT_MSG_DELETED` event.
This event notifies the UI about the message
being deleted from the messagelist, e.g. when the message expires
or the user deletes it.
### Fixes
- Emit `DC_EVENT_MSGS_CHANGED` without IDs when the message expires.
Specifying msg IDs that cannot be loaded in the event payload
results in an error when the UI tries to load the message.
Instead, emit an event without IDs
to make the UI reload the whole messagelist.
- Ignore address case when comparing the `To:` field to `Autocrypt-Gossip:`.
This bug resulted in failure to propagate verification
if the contact list already contained a new verified group member
with a non-lowercase address.
- dehtml: skip links with empty text.
Links like `<a href="https://delta.chat/"></a>` in HTML mails are now skipped
instead of being converted to a link without a label like `[](https://delta.chat/)`.
- dehtml: Do not insert unnecessary newlines when parsing `<p>` tags.
- Update from yanked `libc` 0.2.145 to 0.2.146.
- Update to async-imap 0.9.0 to remove deprecated `ouroboros` dependency.
### API-Changes
- Emit `DC_EVENT_MSGS_CHANGED` per chat when messages are deleted.
Previously a single event with zero chat ID was emitted.
- python: make `Contact.is_verified()` return bool.
- rust: add API endpoint `get_status_update` ([#4468](https://github.com/deltachat/deltachat-core-rust/pull/4468)).
- rust: make `WebxdcManifest` type public.
### Build system
- Use Rust 1.70.0 to compile deltachat-rpc-server releases.
- Disable unused `brotli` feature `ffi-api` and use 1 codegen-units for release builds to reduce the size of the binaries.
### CI
- Run `cargo check` with musl libc.
- concourse: Install devpi in a virtual environment.
- Remove [mergeable](https://mergeable.us/) configuration.
### Documentation
- README: mark napi.rs bindings as experimental. CFFI bindings are not legacy and are the recommended Node.js bindings currently.
- CONTRIBUTING: document how conventional commits interact with squash merges.
### Refactor
- Rename `MimeMessage.header` into `MimeMessage.headers`.
- Derive `Default` trait for `WebxdcManifest`.
### Tests
- Regression test for case-sensitive comparison of gossip header to contact address.
- Multiple new group consistency tests in Rust.
- python: Replace legacy `tmpdir` fixture with `tmp_path`.
## [1.116.0] - 2023-06-05
### API-Changes
- Add `dc_jsonrpc_blocking_call()`.
### Changes
- BREAKING: jsonrpc:
- `get_chatlist_items_by_entries` now takes only chatids instead of `ChatListEntries`
- `get_chatlist_entries` now returns `Vec<u32>` of chatids instead of `ChatListEntries`
- Generate OpenRPC definitions for JSON-RPC.
- Add more context to message loading errors.
### Fixes
- Build deltachat-node prebuilds on Debian 10.
### Documentation
- Document release process in `RELEASE.md`.
- Add contributing guidelines `CONTRIBUTING.md`.
- Update instructions for python devenv.
- python: Document pytest fixtures.
### Tests
- python: Make `test_mdn_asymmetric` less flaky.
- Make `test_group_with_removed_message_id` less flaky.
- Add golden tests infrastructure ([#4395](https://github.com/deltachat/deltachat-core-rust/pull/4395)).
### Build system
- git-cliff: Changelog generation improvements.
- `set_core_version.py`: Expect release date in the changelog.
### CI
- Require Python 3.8 for deltachat-rpc-client.
- mergeable: Allow PR titles to start with "ci" and "build".
- Remove incorrect comment.
- dependabot: Use `chore` prefix for dependency updates.
- Remove broken `node-delete-preview.yml` workflow.
- Add top comments to GH Actions workflows.
- Run node.js lint on Windows.
- Update clippy to 1.70.0.
### Miscellaneous Tasks
- Remove release.toml.
- gitattributes: Configure LF line endings for JavaScript files.
- Update dependencies
## [1.112.10] - 2023-06-01
### Fixes
- Disable `fetch_existing_msgs` setting by default.
- Update `h2` to fix RUSTSEC-2023-0034.
## [1.115.0] - 2023-05-12
### JSON-RPC API Changes
- Sort reactions in descending order ([#4388](https://github.com/deltachat/deltachat-core-rust/pull/4388)).
- Add API to get reactions outside the message snapshot.
- `get_chatlist_items_by_entries` now takes only chatids instead of `ChatListEntries`.
- `get_chatlist_entries` now returns `Vec<u32>` of chatids instead of `ChatListEntries`.
- `JSONRPCReactions.reactions` is now a `Vec<JSONRPCReaction>` with unique reactions and their count, sorted in descending order.
- `Event`: `context_id` property is now called `contextId`.
- Expand `MessageSearchResult`:
- Always include `chat_name`(not an option anymore).
- Add `author_id`, `chat_type`, `chat_color`, `is_chat_protected`, `is_chat_contact_request`, `is_chat_archived`.
- `author_name` now contains the overridden sender name.
- `ChatListItemFetchResult` gets new properties: `summary_preview_image`, `last_message_type` and `last_message_id`
- New `MessageReadReceipt` type and `get_message_read_receipts(account_id, message_id)` jsonrpc method.
### API Changes
- New rust API `send_webxdc_status_update_struct` to send a `StatusUpdateItem`.
- Add `get_msg_read_receipts(context, msg_id)` - get the contacts that send read receipts for a message.
### Features / Changes
- Build deltachat-rpc-server releases for x86\_64 macOS.
- Generate changelogs using git-cliff ([#4393](https://github.com/deltachat/deltachat-core-rust/pull/4393), [#4396](https://github.com/deltachat/deltachat-core-rust/pull/4396)).
- Improve SMTP logging.
- Do not cut incoming text if "bot" config is set.
### Fixes
- JSON-RPC: typescript client: fix types of events in event emitter ([#4373](https://github.com/deltachat/deltachat-core-rust/pull/4373)).
- Fetch at most 100 existing messages even if EXISTS was not received ([#4383](https://github.com/deltachat/deltachat-core-rust/pull/4383)).
- Don't put a double dot at the end of error messages ([#4398](https://github.com/deltachat/deltachat-core-rust/pull/4398)).
- Recreate `smtp` table with AUTOINCREMENT `id` ([#4390](https://github.com/deltachat/deltachat-core-rust/pull/4390)).
- Do not return an error from `send_msg_to_smtp` if retry limit is exceeded.
- Make the bots automatically accept group chat contact requests ([#4377](https://github.com/deltachat/deltachat-core-rust/pull/4377)).
- Delete `smtp` rows when message sending is cancelled ([#4391](https://github.com/deltachat/deltachat-core-rust/pull/4391)).
### Refactor
- Iterate over `msg_ids` without .iter().
## [1.112.9] - 2023-05-12
### Fixes
- Fetch at most 100 existing messages even if EXISTS was not received.
- Delete `smtp` rows when message sending is cancelled.
### Changes
- Improve SMTP logging.
## [1.114.0] - 2023-04-24
@@ -2439,5 +2853,18 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.112.6]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.5...v1.112.6
[1.112.7]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.6...v1.112.7
[1.112.8]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.7...v1.112.8
[1.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.8...v1.113.0
[1.112.9]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.8...v1.112.9
[1.112.10]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.112.10
[1.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.113.0
[1.114.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.113.0...v1.114.0
[1.115.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.114.0...v1.115.0
[1.116.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.115.0...v1.116.0
[1.117.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.116.0...v1.117.0
[1.118.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.117.0...v1.118.0
[1.119.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.118.0...v1.119.0
[1.119.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.0...v1.119.1
[1.120.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.1...v1.120.0
[1.121.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.120.0...v1.121.0
[1.122.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.121.0...v1.122.0
[1.123.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.122.0...v1.123.0
[1.124.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.123.0...v1.124.0

92
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,92 @@
# Contributing guidelines
## Reporting bugs
If you found a bug, [report it on GitHub](https://github.com/deltachat/deltachat-core-rust/issues).
If the bug you found is specific to
[Android](https://github.com/deltachat/deltachat-android/issues),
[iOS](https://github.com/deltachat/deltachat-ios/issues) or
[Desktop](https://github.com/deltachat/deltachat-desktop/issues),
report it to the corresponding repository.
## Proposing features
If you have a feature request, create a new topic on the [forum](https://support.delta.chat/).
## Contributing code
If you want to contribute a code, [open a Pull Request](https://github.com/deltachat/deltachat-core-rust/pulls).
If you have write access to the repository,
push a branch named `<username>/<feature>`
so it is clear who is responsible for the branch,
and open a PR proposing to merge the change.
Otherwise fork the repository and create a branch in your fork.
You can find the list of good first issues
and a link to this guide
on the contributing page: <https://github.com/deltachat/deltachat-core-rust/contribute>
### Coding conventions
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
Commit messages follow the [Conventional Commits] notation.
We use [git-cliff] to generate the changelog from commit messages before the release.
With **`git cliff --unreleased`**, you can check how the changelog entry for your commit will look.
The following prefix types are used:
- `feat`: Features, e.g. "feat: Pause IO for BackupProvider". If you are unsure what's the category of your commit, you can often just use `feat`.
- `fix`: Bug fixes, e.g. "fix: delete `smtp` rows when message sending is cancelled"
- `api`: API changes, e.g. "api(rust): add `get_msg_read_receipts(context, msg_id)`"
- `refactor`: Refactorings, e.g. "refactor: iterate over `msg_ids` without `.iter()`"
- `perf`: Performance improvements, e.g. "perf: improve SQLite performance with `PRAGMA synchronous=normal`"
- `test`: Test changes and improvements to the testing framework.
- `build`: Build system and tool configuration changes, e.g. "build(git-cliff): put "ci" commits into "CI" section of changelog"
- `ci`: CI configuration changes, e.g. "ci: limit artifact retention time for `libdeltachat.a` to 1 day"
- `docs`: Documentation changes, e.g. "docs: add contributing guidelines"
- `chore`: miscellaneous tasks, e.g. "chore: add `.DS_Store` to `.gitignore`"
Release preparation commits are marked as "chore(release): prepare for vX.Y.Z".
If you intend to squash merge the PR from the web interface,
make sure the PR title follows the conventional commits notation
as it will end up being a commit title.
Otherwise make sure each commit title follows the conventional commit notation.
#### Breaking Changes
Use a `!` to mark breaking changes, e.g. "api!: Remove `dc_chat_can_send`".
Alternatively, breaking changes can go into the commit description, e.g.:
```
fix: Fix race condition and db corruption when a message was received during backup
BREAKING CHANGE: You have to call `dc_stop_io()`/`dc_start_io()` before/after `dc_imex(DC_IMEX_EXPORT_BACKUP)`
```
#### Multiple Changes in one PR
If you have multiple changes in one PR, create multiple conventional commits, and then do a rebase merge. Otherwise, you should usually do a squash merge.
[Clippy]: https://doc.rust-lang.org/clippy/
[Conventional Commits]: https://www.conventionalcommits.org/
[git-cliff]: https://git-cliff.org/
### Reviewing
Once a PR has an approval and passes CI, it can be merged.
PRs from a branch created in the main repository, i.e. authored by those who have write access, are merged by their authors.
This is to ensure that PRs are merged as intended by the author,
e.g. as a squash merge, by rebasing from the web interface or manually from the command line.
If you do not have access to the repository and created a PR from a fork,
ask the maintainers to merge the PR and say how it should be merged.
## Other ways to contribute
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).

1703
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.114.0"
version = "1.124.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.65"
@@ -23,6 +23,7 @@ opt-level = "z"
lto = true
panic = 'abort'
opt-level = "z"
codegen-units = 1
[patch.crates-io]
quinn-udp = { git = "https://github.com/quinn-rs/quinn", branch="main" }
@@ -35,13 +36,13 @@ ratelimit = { path = "./deltachat-ratelimit" }
anyhow = "1"
async-channel = "1.8.0"
async-imap = { version = "0.8.0", default-features = false, features = ["runtime-tokio"] }
async-imap = { version = "0.9.1", default-features = false, features = ["runtime-tokio"] }
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] }
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
backtrace = "0.3"
base64 = "0.21"
brotli = "3.3"
brotli = { version = "3.3", default-features=false, features = ["std"] }
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
@@ -58,19 +59,19 @@ lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master"
libc = "0.2"
mailparse = "0.14"
mime = "0.3.17"
num_cpus = "1.15"
num-derive = "0.3"
num_cpus = "1.16"
num-derive = "0.4"
num-traits = "0.2"
once_cell = "1.17.0"
percent-encoding = "2.2"
once_cell = "1.18.0"
percent-encoding = "2.3"
parking_lot = "0.12"
pgp = { version = "0.10", default-features = false }
pretty_env_logger = { version = "0.4", optional = true }
pretty_env_logger = { version = "0.5", optional = true }
qrcodegen = "1.7.0"
quick-xml = "0.28"
quick-xml = "0.29"
rand = "0.8"
regex = "1.7"
reqwest = { version = "0.11.17", features = ["json"] }
regex = "1.8"
reqwest = { version = "0.11.18", features = ["json"] }
rusqlite = { version = "0.29", features = ["sqlcipher"] }
rust-hsluv = "0.1"
sanitize-filename = "0.4"
@@ -79,8 +80,8 @@ serde = { version = "1.0", features = ["derive"] }
sha-1 = "0.10"
sha2 = "0.10"
smallvec = "1"
strum = "0.24"
strum_macros = "0.24"
strum = "0.25"
strum_macros = "0.25"
tagger = "4.3.4"
textwrap = "0.16.0"
thiserror = "1"
@@ -88,7 +89,7 @@ tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
tokio-io-timeout = "1.2.0"
tokio-stream = { version = "0.1.14", features = ["fs"] }
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
tokio-util = "0.7.7"
tokio-util = "0.7.8"
toml = "0.7"
trust-dns-resolver = "0.22"
url = "2"
@@ -96,14 +97,15 @@ uuid = { version = "1", features = ["serde", "v4"] }
[dev-dependencies]
ansi_term = "0.12.0"
criterion = { version = "0.4.0", features = ["async_tokio"] }
criterion = { version = "0.5.1", features = ["async_tokio"] }
futures-lite = "1.13"
log = "0.4"
pretty_env_logger = "0.4"
pretty_env_logger = "0.5"
proptest = { version = "1", default-features = false, features = ["std"] }
tempfile = "3"
testdir = "0.7.3"
testdir = "0.8.0"
tokio = { version = "1", features = ["parking_lot", "rt-multi-thread", "macros"] }
pretty_assertions = "1.3.0"
[workspace]
members = [

View File

@@ -361,7 +361,7 @@ Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE

View File

@@ -167,8 +167,8 @@ Language bindings are available for:
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
- **Node.js**
- over cffi (legacy): \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
- over jsonrpc built with napi.rs: \[[📂 source](https://github.com/deltachat/napi-jsonrpc) | [📦 npm](https://www.npmjs.com/package/@deltachat/napi-jsonrpc)\]
- over cffi: \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
- over jsonrpc built with napi.rs (experimental): \[[📂 source](https://github.com/deltachat/napi-jsonrpc) | [📦 npm](https://www.npmjs.com/package/@deltachat/napi-jsonrpc)\]
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
- **Go**
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]

21
RELEASE.md Normal file
View File

@@ -0,0 +1,21 @@
# Releasing a new version of DeltaChat core
For example, to release version 1.116.0 of the core, do the following steps.
1. Resolve all [blocker issues](https://github.com/deltachat/deltachat-core-rust/labels/blocker).
2. Run `npm run build:core:constants` in the root of the repository
and commit generated `node/constants.js`, `node/events.js` and `node/lib/constants.js`.
3. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
4. Update the version by running `scripts/set_core_version.py 1.116.0`.
5. Commit the changes as `chore(release): prepare for 1.116.0`.
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
6. Tag the release: `git tag -a v1.116.0`.
7. Push the release tag: `git push origin v1.116.0`.
8. Create a GitHub release: `gh release create v1.116.0 -n ''`.

79
cliff.toml Normal file
View File

@@ -0,0 +1,79 @@
# configuration file for git-cliff
# see https://git-cliff.org/docs/configuration/
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = false
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/deltachat/deltachat-core-rust/pull/${2}))"}, # replace pull request / issue numbers
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features / Changes"},
{ message = "^fix", group = "Fixes"},
{ message = "^api", group = "API-Changes" },
{ message = "^refactor", group = "Refactor"},
{ message = "^perf", group = "Performance"},
{ message = "^test", group = "Tests"},
{ message = "^style", group = "Styling"},
{ message = "^chore\\(release\\): prepare for", skip = true},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ message = "^build", group = "Build system"},
{ message = "^docs", group = "Documentation"},
{ message = "^ci", group = "CI"},
{ message = ".*", group = "Other"},
# { body = ".*security", group = "Security"},
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = true
# filter out the commits that are not matched by commit parsers
filter_commits = true
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
#skip_tags = "v0.1.0-beta.1"
# regex for ignoring tags
ignore_tags = ""
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
# limit the number of commits included in the changelog.
# limit_commits = 42
[changelog]
# changelog header
header = """
# Changelog\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}[**breaking**] {% endif %}\
{% if commit.scope %}{{ commit.scope }}: {% endif %}\
{{ commit.message | upper_first }}.\
{% if commit.footers is defined %}\
{% for footer in commit.footers %}{% if 'BREAKING CHANGE' in footer.token %}
{% raw %} {% endraw %}- {{ footer.value }}\
{% endif %}{% endfor %}\
{% endif%}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.114.0"
version = "1.124.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
@@ -24,7 +24,8 @@ tokio = { version = "1", features = ["rt-multi-thread"] }
anyhow = "1"
thiserror = "1"
rand = "0.8"
once_cell = "1.17.0"
once_cell = "1.18.0"
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
[features]
default = ["vendored"]

View File

@@ -301,6 +301,19 @@ dc_context_t* dc_context_new_closed (const char* dbfile);
int dc_context_open (dc_context_t *context, const char* passphrase);
/**
* Changes the passphrase on the open database.
* Existing database must already be encrypted and the passphrase cannot be NULL or empty.
* It is impossible to encrypt unencrypted database with this method and vice versa.
*
* @memberof dc_context_t
* @param context The context object.
* @param passphrase The new passphrase.
* @return 1 on success, 0 on error.
*/
int dc_context_change_passphrase (dc_context_t* context, const char* passphrase);
/**
* Returns 1 if database is open.
*
@@ -420,17 +433,19 @@ char* dc_get_blobdir (const dc_context_t* context);
* 0=watch all folders normally (default)
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
* show direct replies to chats only (default),
* show direct replies to chats only,
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
* also show all mails of confirmed contacts,
* DC_SHOW_EMAILS_ALL (2)=
* also show mails of unconfirmed contacts.
* also show mails of unconfirmed contacts (default).
* - `key_gen_type` = DC_KEY_GEN_DEFAULT (0)=
* generate recommended key type (default),
* DC_KEY_GEN_RSA2048 (1)=
* generate RSA 2048 keypair
* DC_KEY_GEN_ED25519 (2)=
* generate Ed25519 keypair
* generate Curve25519 keypair
* DC_KEY_GEN_RSA4096 (3)=
* generate RSA 4096 keypair
* - `save_mime_headers` = 1=save mime headers
* and make dc_get_mime_headers() work for subsequent calls,
* 0=do not save mime headers (default)
@@ -461,8 +476,9 @@ char* dc_get_blobdir (const dc_context_t* context);
* If no type is prefixed, the videochat is handled completely in a browser.
* - `bot` = Set to "1" if this is a bot.
* Prevents adding the "Device messages" and "Saved messages" chats,
* adds Auto-Submitted header to outgoing messages
* and accepts contact requests automatically (calling dc_accept_chat() is not needed for bots).
* adds Auto-Submitted header to outgoing messages,
* accepts contact requests automatically (calling dc_accept_chat() is not needed for bots)
* and does not cut large incoming text messages.
* - `last_msg_id` = database ID of the last message processed by the bot.
* This ID and IDs below it are guaranteed not to be returned
* by dc_get_next_msgs() and dc_wait_next_msgs().
@@ -808,7 +824,7 @@ void dc_maybe_network (dc_context_t* context);
* @param context The context as created by dc_context_new().
* @param addr The e-mail address of the user. This must match the
* configured_addr setting of the context as well as the UID of the key.
* @param public_data ASCII armored public key.
* @param public_data Ignored, actual public key is extracted from secret_data.
* @param secret_data ASCII armored secret key.
* @return 1 on success, 0 on failure.
*/
@@ -1320,6 +1336,20 @@ int dc_get_msg_cnt (dc_context_t* context, uint32_t ch
int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t chat_id);
/**
* Returns a list of similar chats.
*
* @warning This is an experimental API which may change or be removed in the future.
*
* @memberof dc_context_t
* @param context The context object as returned from dc_context_new().
* @param chat_id The ID of the chat for which to find similar chats.
* @return The list of similar chats.
* On errors, NULL is returned.
* Must be freed using dc_chatlist_unref() when no longer used.
*/
dc_chatlist_t* dc_get_similar_chatlist (dc_context_t* context, uint32_t chat_id);
/**
* Estimate the number of messages that will be deleted
@@ -1451,6 +1481,7 @@ dc_array_t* dc_get_chat_media (dc_context_t* context, uint32_t ch
* Typically used to implement the "next" and "previous" buttons
* in a gallery or in a media player.
*
* @deprecated Deprecated 2023-10-03, use dc_get_chat_media() and navigate the returned array instead.
* @memberof dc_context_t
* @param context The context object as returned from dc_context_new().
* @param msg_id The ID of the current message from which the next or previous message should be searched.
@@ -2254,6 +2285,7 @@ dc_contact_t* dc_get_contact (dc_context_t* context, uint32_t co
*
* - **DC_IMEX_IMPORT_SELF_KEYS** (2) - Import private keys found in the directory given as `param1`.
* The last imported key is made the default keys unless its name contains the string `legacy`. Public keys are not imported.
* If `param1` is a filename, import the private key from the file and make it the default.
*
* While dc_imex() returns immediately, the started job may take a while,
* you can stop it using dc_stop_ongoing_process(). During execution of the job,
@@ -3977,16 +4009,17 @@ char* dc_msg_get_text (const dc_msg_t* msg);
*/
char* dc_msg_get_subject (const dc_msg_t* msg);
/**
* Find out full path, file name and extension of the file associated with a
* message.
* Find out full path of the file associated with a message.
*
* Typically files are associated with images, videos, audios, documents.
* Plain text messages do not have a file.
* File name may be mangled. To obtain the original attachment filename use dc_msg_get_filename().
*
* @memberof dc_msg_t
* @param msg The message object.
* @return The full path, the file name, and the extension of the file associated with the message.
* @return The full path (with file name and extension) of the file associated with the message.
* If there is no file associated with the message, an empty string is returned.
* NULL is never returned and the returned value must be released using dc_str_unref().
*/
@@ -3994,14 +4027,13 @@ char* dc_msg_get_file (const dc_msg_t* msg);
/**
* Get a base file name without the path. The base file name includes the extension; the path
* is not returned. To get the full path, use dc_msg_get_file().
* Get an original attachment filename, with extension but without the path. To get the full path,
* use dc_msg_get_file().
*
* @memberof dc_msg_t
* @param msg The message object.
* @return The base file name plus the extension without part. If there is no file
* associated with the message, an empty string is returned. The returned
* value must be released using dc_str_unref().
* @return The attachment filename. If there is no file associated with the message, an empty string
* is returned. The returned value must be released using dc_str_unref().
*/
char* dc_msg_get_filename (const dc_msg_t* msg);
@@ -5016,6 +5048,7 @@ int dc_contact_is_verified (dc_contact_t* contact);
* A string containing the verifiers address. If it is the same address as the contact itself,
* we verified the contact ourself. If it is an empty string, we don't have verifier
* information or the contact is not verified.
* @deprecated 2023-09-28, use dc_contact_get_verifier_id instead
*/
char* dc_contact_get_verifier_addr (dc_contact_t* contact);
@@ -5028,7 +5061,7 @@ char* dc_contact_get_verifier_addr (dc_contact_t* contact);
* @memberof dc_contact_t
* @param contact The contact object.
* @return
* The `ContactId` of the verifiers address. If it is the same address as the contact itself,
* The contact ID of the verifier. If it is DC_CONTACT_ID_SELF,
* we verified the contact ourself. If it is 0, we don't have verifier information or
* the contact is not verified.
*/
@@ -5719,6 +5752,17 @@ void dc_jsonrpc_request(dc_jsonrpc_instance_t* jsonrpc_instance, const char* req
*/
char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
/**
* Make a JSON-RPC call and return a response.
*
* @memberof dc_jsonrpc_instance_t
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
* @param input JSON-RPC request.
* @return JSON-RPC response as string, must be freed using dc_str_unref() after usage.
* If there is no response, NULL is returned.
*/
char* dc_jsonrpc_blocking_call(dc_jsonrpc_instance_t* jsonrpc_instance, const char *input);
/**
* @class dc_event_emitter_t
*
@@ -6066,6 +6110,15 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_MSG_READ 2015
/**
* A single message is deleted.
*
* @param data1 (int) chat_id
* @param data2 (int) msg_id
*/
#define DC_EVENT_MSG_DELETED 2016
/**
* Chat changed. The name or the image of a chat group was changed or members were added or removed.
* Or the verify state of a chat has changed.
@@ -6244,6 +6297,7 @@ void dc_event_unref(dc_event_t* event);
#define DC_KEY_GEN_DEFAULT 0
#define DC_KEY_GEN_RSA2048 1
#define DC_KEY_GEN_ED25519 2
#define DC_KEY_GEN_RSA4096 3
/**

View File

@@ -29,7 +29,7 @@ use deltachat::contact::{Contact, ContactId, Origin};
use deltachat::context::Context;
use deltachat::ephemeral::Timer as EphemeralTimer;
use deltachat::imex::BackupProvider;
use deltachat::key::DcKey;
use deltachat::key::{DcKey, DcSecretKey};
use deltachat::message::MsgId;
use deltachat::net::read_url_blob;
use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
@@ -167,6 +167,24 @@ pub unsafe extern "C" fn dc_context_open(
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_context_change_passphrase(
context: *mut dc_context_t,
passphrase: *const libc::c_char,
) -> libc::c_int {
if context.is_null() {
eprintln!("ignoring careless call to dc_context_change_passphrase()");
return 0;
}
let ctx = &*context;
let passphrase = to_string_lossy(passphrase);
block_on(ctx.change_passphrase(passphrase))
.context("dc_context_change_passphrase() failed")
.log_err(ctx)
.is_ok() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_context_is_open(context: *mut dc_context_t) -> libc::c_int {
if context.is_null() {
@@ -527,6 +545,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::MsgDelivered { .. } => 2010,
EventType::MsgFailed { .. } => 2012,
EventType::MsgRead { .. } => 2015,
EventType::MsgDeleted { .. } => 2016,
EventType::ChatModified(_) => 2020,
EventType::ChatEphemeralTimerModified { .. } => 2021,
EventType::ContactsChanged(_) => 2030,
@@ -574,6 +593,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| EventType::MsgDelivered { chat_id, .. }
| EventType::MsgFailed { chat_id, .. }
| EventType::MsgRead { chat_id, .. }
| EventType::MsgDeleted { chat_id, .. }
| EventType::ChatModified(chat_id)
| EventType::ChatEphemeralTimerModified { chat_id, .. } => chat_id.to_u32() as libc::c_int,
EventType::ContactsChanged(id) | EventType::LocationChanged(id) => {
@@ -631,7 +651,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::IncomingMsg { msg_id, .. }
| EventType::MsgDelivered { msg_id, .. }
| EventType::MsgFailed { msg_id, .. }
| EventType::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int,
| EventType::MsgRead { msg_id, .. }
| EventType::MsgDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
EventType::SecurejoinInviterProgress { progress, .. }
| EventType::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int,
EventType::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int,
@@ -674,6 +695,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::MsgDelivered { .. }
| EventType::MsgFailed { .. }
| EventType::MsgRead { .. }
| EventType::MsgDeleted { .. }
| EventType::ChatModified(_)
| EventType::ContactsChanged(_)
| EventType::LocationChanged(_)
@@ -783,7 +805,7 @@ pub unsafe extern "C" fn dc_maybe_network(context: *mut dc_context_t) {
pub unsafe extern "C" fn dc_preconfigure_keypair(
context: *mut dc_context_t,
addr: *const libc::c_char,
public_data: *const libc::c_char,
_public_data: *const libc::c_char,
secret_data: *const libc::c_char,
) -> i32 {
if context.is_null() {
@@ -793,8 +815,8 @@ pub unsafe extern "C" fn dc_preconfigure_keypair(
let ctx = &*context;
block_on(async move {
let addr = tools::EmailAddress::new(&to_string_lossy(addr))?;
let public = key::SignedPublicKey::from_asc(&to_string_lossy(public_data))?.0;
let secret = key::SignedSecretKey::from_asc(&to_string_lossy(secret_data))?.0;
let public = secret.split_public_key()?;
let keypair = key::KeyPair {
addr,
public,
@@ -1238,6 +1260,30 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt(
})
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_similar_chatlist(
context: *mut dc_context_t,
chat_id: u32,
) -> *mut dc_chatlist_t {
if context.is_null() {
eprintln!("ignoring careless call to dc_get_similar_chatlist()");
return ptr::null_mut();
}
let ctx = &*context;
let chat_id = ChatId::new(chat_id);
match block_on(chat_id.get_similar_chatlist(ctx))
.context("failed to get similar chatlist")
.log_err(ctx)
{
Ok(list) => {
let ffi_list = ChatlistWrapper { context, list };
Box::into_raw(Box::new(ffi_list))
}
Err(_) => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_estimate_deletion_cnt(
context: *mut dc_context_t,
@@ -1385,6 +1431,7 @@ pub unsafe extern "C" fn dc_get_chat_media(
}
#[no_mangle]
#[allow(deprecated)]
pub unsafe extern "C" fn dc_get_next_media(
context: *mut dc_context_t,
msg_id: u32,
@@ -1873,13 +1920,10 @@ pub unsafe extern "C" fn dc_get_msg_info(
return "".strdup();
}
let ctx = &*context;
block_on(async move {
message::get_msg_info(ctx, MsgId::new(msg_id))
.await
.unwrap_or_log_default(ctx, "failed to get msg id")
.strdup()
})
let msg_id = MsgId::new(msg_id);
block_on(msg_id.get_info(ctx))
.unwrap_or_log_default(ctx, "failed to get msg id")
.strdup()
}
#[no_mangle]
@@ -2524,7 +2568,12 @@ pub unsafe extern "C" fn dc_set_location(
}
let ctx = &*context;
block_on(location::set(ctx, latitude, longitude, accuracy)) as _
block_on(async move {
location::set(ctx, latitude, longitude, accuracy)
.await
.log_err(ctx)
.unwrap_or_default()
}) as libc::c_int
}
#[no_mangle]
@@ -3304,7 +3353,7 @@ pub unsafe extern "C" fn dc_msg_get_text(msg: *mut dc_msg_t) -> *mut libc::c_cha
return "".strdup();
}
let ffi_msg = &*msg;
ffi_msg.message.get_text().unwrap_or_default().strdup()
ffi_msg.message.get_text().strdup()
}
#[no_mangle]
@@ -3689,7 +3738,7 @@ pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::
return;
}
let ffi_msg = &mut *msg;
ffi_msg.message.set_text(to_opt_string_lossy(text))
ffi_msg.message.set_text(to_string_lossy(text))
}
#[no_mangle]
@@ -4488,7 +4537,14 @@ pub unsafe extern "C" fn dc_provider_new_from_email(
let ctx = &*context;
match block_on(provider::get_provider_info(ctx, addr.as_str(), true)) {
match block_on(provider::get_provider_info_by_addr(
ctx,
addr.as_str(),
true,
))
.log_err(ctx)
.unwrap_or_default()
{
Some(provider) => provider,
None => ptr::null_mut(),
}
@@ -4515,11 +4571,14 @@ pub unsafe extern "C" fn dc_provider_new_from_email_with_dns(
match socks5_enabled {
Ok(socks5_enabled) => {
match block_on(provider::get_provider_info(
match block_on(provider::get_provider_info_by_addr(
ctx,
addr.as_str(),
socks5_enabled,
)) {
))
.log_err(ctx)
.unwrap_or_default()
{
Some(provider) => provider,
None => ptr::null_mut(),
}
@@ -5039,4 +5098,28 @@ mod jsonrpc {
.map(|result| serde_json::to_string(&result).unwrap_or_default().strdup())
.unwrap_or(ptr::null_mut())
}
#[no_mangle]
pub unsafe extern "C" fn dc_jsonrpc_blocking_call(
jsonrpc_instance: *mut dc_jsonrpc_instance_t,
input: *const libc::c_char,
) -> *mut libc::c_char {
if jsonrpc_instance.is_null() {
eprintln!("ignoring careless call to dc_jsonrpc_blocking_call()");
return ptr::null_mut();
}
let api = &*jsonrpc_instance;
let input = to_string_lossy(input);
let res = block_on(api.handle.process_incoming(&input));
match res {
Some(message) => {
if let Ok(message) = serde_json::to_string(&message) {
message.strdup()
} else {
ptr::null_mut()
}
}
None => ptr::null_mut(),
}
}
}

View File

@@ -1,3 +1,4 @@
openrpc/openrpc.json
accounts/
.cargo

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.114.0"
version = "1.124.0"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"
@@ -15,25 +15,26 @@ required-features = ["webserver"]
anyhow = "1"
deltachat = { path = ".." }
num-traits = "0.2"
schemars = "0.8.11"
serde = { version = "1.0", features = ["derive"] }
tempfile = "3.3.0"
tempfile = "3.6.0"
log = "0.4"
async-channel = { version = "1.8.0" }
futures = { version = "0.3.28" }
serde_json = "1.0.96"
yerpc = { version = "0.4.3", features = ["anyhow_expose"] }
serde_json = "1.0.99"
yerpc = { version = "0.5.1", features = ["anyhow_expose", "openrpc"] }
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
tokio = { version = "1.27.0" }
tokio = { version = "1.29.1" }
sanitize-filename = "0.4"
walkdir = "2.3.3"
base64 = "0.21"
# optional dependencies
axum = { version = "0.6.12", optional = true, features = ["ws"] }
axum = { version = "0.6.18", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
tokio = { version = "1.27.0", features = ["full", "rt-multi-thread"] }
tokio = { version = "1.29.1", features = ["full", "rt-multi-thread"] }
[features]

View File

@@ -4,6 +4,7 @@ use std::{collections::HashMap, str::FromStr};
use anyhow::{anyhow, bail, ensure, Context, Result};
pub use deltachat::accounts::Accounts;
use deltachat::message::get_msg_read_receipts;
use deltachat::qr::Qr;
use deltachat::{
chat::{
@@ -18,13 +19,11 @@ use deltachat::{
context::get_info,
ephemeral::Timer,
imex, location,
message::{
self, delete_msgs, get_msg_info, markseen_msgs, Message, MessageState, MsgId, Viewtype,
},
message::{self, delete_msgs, markseen_msgs, Message, MessageState, MsgId, Viewtype},
provider::get_provider_info,
qr,
qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg},
reaction::send_reaction,
reaction::{get_msg_reactions, send_reaction},
securejoin,
stock_str::StockMessage,
webxdc::StatusUpdateSerial,
@@ -35,20 +34,19 @@ use tokio::sync::{watch, Mutex, RwLock};
use walkdir::WalkDir;
use yerpc::rpc;
pub mod events;
pub mod types;
use num_traits::FromPrimitive;
use types::account::Account;
use types::chat::FullChat;
use types::contact::ContactObject;
use types::events::Event;
use types::http::HttpResponse;
use types::message::MessageData;
use types::message::MessageObject;
use types::message::{MessageData, MessageObject, MessageReadReceipt};
use types::provider_info::ProviderInfo;
use types::reactions::JSONRPCReactions;
use types::webxdc::WebxdcMessageInfo;
use self::events::Event;
use self::types::message::MessageLoadResult;
use self::types::{
chat::{BasicChat, JSONRPCChatVisibility, MuteDuration},
@@ -144,7 +142,11 @@ impl CommandApi {
}
}
#[rpc(all_positional, ts_outdir = "typescript/generated")]
#[rpc(
all_positional,
ts_outdir = "typescript/generated",
openrpc_outdir = "openrpc"
)]
impl CommandApi {
/// Test function.
async fn sleep(&self, delay: f64) {
@@ -564,6 +566,21 @@ impl CommandApi {
Ok(l)
}
/// Returns chats similar to the given one.
///
/// Experimental API, subject to change without notice.
async fn get_similar_chat_ids(&self, account_id: u32, chat_id: u32) -> Result<Vec<u32>> {
let ctx = self.get_context(account_id).await?;
let chat_id = ChatId::new(chat_id);
let list = chat_id
.get_similar_chat_ids(&ctx)
.await?
.into_iter()
.map(|(chat_id, _metric)| chat_id.to_u32())
.collect();
Ok(list)
}
async fn get_chatlist_items_by_entries(
&self,
account_id: u32,
@@ -897,7 +914,7 @@ impl CommandApi {
) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(text));
msg.set_text(text);
let message_id =
deltachat::chat::add_device_msg(&ctx, Some(&label), Some(&mut msg)).await?;
Ok(message_id.to_u32())
@@ -1115,7 +1132,25 @@ impl CommandApi {
/// max. text returned by dc_msg_get_text() (about 30000 characters).
async fn get_message_info(&self, account_id: u32, message_id: u32) -> Result<String> {
let ctx = self.get_context(account_id).await?;
get_msg_info(&ctx, MsgId::new(message_id)).await
MsgId::new(message_id).get_info(&ctx).await
}
/// Returns contacts that sent read receipts and the time of reading.
async fn get_message_read_receipts(
&self,
account_id: u32,
message_id: u32,
) -> Result<Vec<MessageReadReceipt>> {
let ctx = self.get_context(account_id).await?;
let receipts = get_msg_read_receipts(&ctx, MsgId::new(message_id))
.await?
.iter()
.map(|(contact_id, ts)| MessageReadReceipt {
contact_id: contact_id.to_u32(),
timestamp: *ts,
})
.collect();
Ok(receipts)
}
/// Asks the core to start downloading a message fully.
@@ -1318,7 +1353,7 @@ impl CommandApi {
) -> Result<()> {
let ctx = self.get_context(account_id).await?;
let contact_id = ContactId::new(contact_id);
let contact = Contact::load_from_db(&ctx, contact_id).await?;
let contact = Contact::get_by_id(&ctx, contact_id).await?;
let addr = contact.get_addr();
Contact::create(&ctx, &name, addr).await?;
Ok(())
@@ -1392,6 +1427,10 @@ impl CommandApi {
///
/// one combined call for getting chat::get_next_media for both directions
/// the manual chat::get_next_media in only one direction is not exposed by the jsonrpc yet
///
/// Deprecated 2023-10-03, use `get_chat_media` method
/// and navigate the returned array instead.
#[allow(deprecated)]
async fn get_neighboring_chat_media(
&self,
account_id: u32,
@@ -1689,6 +1728,20 @@ impl CommandApi {
forward_msgs(&ctx, &message_ids, ChatId::new(chat_id)).await
}
/// Resend messages and make information available for newly added chat members.
/// Resending sends out the original message, however, recipients and webxdc-status may differ.
/// Clients that already have the original message can still ignore the resent message as
/// they have tracked the state by dedicated updates.
///
/// Some messages cannot be resent, eg. info-messages, drafts, already pending messages or messages that are not sent by SELF.
///
/// message_ids all message IDs that should be resend. All messages must belong to the same chat.
async fn resend_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
let ctx = self.get_context(account_id).await?;
let message_ids: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
chat::resend_msgs(&ctx, &message_ids).await
}
async fn send_sticker(
&self,
account_id: u32,
@@ -1721,6 +1774,21 @@ impl CommandApi {
Ok(message_id.to_u32())
}
/// Returns reactions to the message.
async fn get_message_reactions(
&self,
account_id: u32,
message_id: u32,
) -> Result<Option<JSONRPCReactions>> {
let ctx = self.get_context(account_id).await?;
let reactions = get_msg_reactions(&ctx, MsgId::new(message_id)).await?;
if reactions.is_empty() {
Ok(None)
} else {
Ok(Some(reactions.into()))
}
}
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
let mut message = Message::new(if let Some(viewtype) = data.viewtype {
@@ -1730,9 +1798,7 @@ impl CommandApi {
} else {
Viewtype::Text
});
if data.text.is_some() {
message.set_text(data.text);
}
message.set_text(data.text.unwrap_or_default());
if data.html.is_some() {
message.set_html(data.html);
}
@@ -1913,7 +1979,7 @@ impl CommandApi {
let ctx = self.get_context(account_id).await?;
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(text));
msg.set_text(text);
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
Ok(message_id.to_u32())
@@ -1936,9 +2002,7 @@ impl CommandApi {
} else {
Viewtype::Text
});
if text.is_some() {
message.set_text(text);
}
message.set_text(text.unwrap_or_default());
if let Some(file) = file {
message.set_file(file, None);
}
@@ -1982,9 +2046,7 @@ impl CommandApi {
} else {
Viewtype::Text
});
if text.is_some() {
draft.set_text(text);
}
draft.set_text(text.unwrap_or_default());
if let Some(file) = file {
draft.set_file(file, None);
}

View File

@@ -6,7 +6,7 @@ use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(tag = "type")]
pub enum Account {
#[serde(rename_all = "camelCase")]

View File

@@ -1,6 +1,6 @@
use std::time::{Duration, SystemTime};
use anyhow::{anyhow, bail, Context as _, Result};
use anyhow::{bail, Context as _, Result};
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
use deltachat::chat::{Chat, ChatId};
use deltachat::constants::Chattype;
@@ -13,7 +13,7 @@ use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
use super::contact::ContactObject;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct FullChat {
id: u32,
@@ -53,7 +53,7 @@ impl FullChat {
contacts.push(
ContactObject::try_from_dc_contact(
context,
Contact::load_from_db(context, *contact_id)
Contact::get_by_id(context, *contact_id)
.await
.context("failed to load contact")?,
)
@@ -74,7 +74,7 @@ impl FullChat {
let was_seen_recently = if chat.get_type() == Chattype::Single {
match contact_ids.get(0) {
Some(contact) => Contact::load_from_db(context, *contact)
Some(contact) => Contact::get_by_id(context, *contact)
.await
.context("failed to load contact for was_seen_recently")?
.was_seen_recently(),
@@ -92,10 +92,7 @@ impl FullChat {
is_protected: chat.is_protected(),
profile_image, //BLOBS ?
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
chat_type: chat
.get_type()
.to_u32()
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
is_unpromoted: chat.is_unpromoted(),
is_self_talk: chat.is_self_talk(),
contacts,
@@ -124,7 +121,7 @@ impl FullChat {
/// - can_send
///
/// used when you only need the basic metadata of a chat like type, name, profile picture
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct BasicChat {
id: u32,
@@ -158,10 +155,7 @@ impl BasicChat {
is_protected: chat.is_protected(),
profile_image, //BLOBS ?
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
chat_type: chat
.get_type()
.to_u32()
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
is_unpromoted: chat.is_unpromoted(),
is_self_talk: chat.is_self_talk(),
color,
@@ -172,7 +166,7 @@ impl BasicChat {
}
}
#[derive(Clone, Serialize, Deserialize, TypeDef)]
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
pub enum MuteDuration {
NotMuted,
Forever,
@@ -197,7 +191,7 @@ impl MuteDuration {
}
}
#[derive(Clone, Serialize, Deserialize, TypeDef)]
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "ChatVisibility")]
pub enum JSONRPCChatVisibility {
Normal,

View File

@@ -12,8 +12,9 @@ use serde::Serialize;
use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
use super::message::MessageViewtype;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(tag = "type")]
pub enum ChatListItemFetchResult {
#[serde(rename_all = "camelCase")]
@@ -26,6 +27,8 @@ pub enum ChatListItemFetchResult {
summary_text1: String,
summary_text2: String,
summary_status: u32,
/// showing preview if last chat message is image
summary_preview_image: Option<String>,
is_protected: bool,
is_group: bool,
fresh_message_counter: usize,
@@ -42,6 +45,8 @@ pub enum ChatListItemFetchResult {
/// contact id if this is a dm chat (for view profile entry in context menu)
dm_chat_contact: Option<u32>,
was_seen_recently: bool,
last_message_type: Option<MessageViewtype>,
last_message_id: Option<u32>,
},
#[serde(rename_all = "camelCase")]
ArchiveLink { fresh_message_counter: usize },
@@ -72,6 +77,8 @@ pub(crate) async fn get_chat_list_item_by_id(
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
let summary_text2 = summary.text.to_owned();
let summary_preview_image = summary.thumbnail_path;
let visibility = chat.get_visibility();
let avatar_path = chat
@@ -79,12 +86,15 @@ pub(crate) async fn get_chat_list_item_by_id(
.await?
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
let last_updated = match last_msgid {
let (last_updated, message_type) = match last_msgid {
Some(id) => {
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
Some(last_message.get_timestamp() * 1000)
(
Some(last_message.get_timestamp() * 1000),
Some(last_message.get_viewtype().into()),
)
}
None => None,
None => (None, None),
};
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
@@ -94,7 +104,7 @@ pub(crate) async fn get_chat_list_item_by_id(
let (dm_chat_contact, was_seen_recently) = if chat.get_type() == Chattype::Single {
let contact = chat_contacts.get(0);
let was_seen_recently = match contact {
Some(contact) => Contact::load_from_db(ctx, *contact)
Some(contact) => Contact::get_by_id(ctx, *contact)
.await
.context("contact")?
.was_seen_recently(),
@@ -119,6 +129,7 @@ pub(crate) async fn get_chat_list_item_by_id(
summary_text1,
summary_text2,
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
summary_preview_image,
is_protected: chat.is_protected(),
is_group: chat.get_type() == Chattype::Group,
fresh_message_counter,
@@ -133,5 +144,7 @@ pub(crate) async fn get_chat_list_item_by_id(
is_broadcast: chat.get_type() == Chattype::Broadcast,
dm_chat_contact,
was_seen_recently,
last_message_type: message_type,
last_message_id: last_msgid.map(|id| id.to_u32()),
})
}

View File

@@ -6,7 +6,7 @@ use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Contact", rename_all = "camelCase")]
pub struct ContactObject {
address: String,

View File

@@ -2,7 +2,8 @@ use deltachat::{Event as CoreEvent, EventType as CoreEventType};
use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Event {
/// Event payload.
event: EventType,
@@ -20,7 +21,7 @@ impl From<CoreEvent> for Event {
}
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(tag = "type")]
pub enum EventType {
/// The library-user may write an informational string to the log.
@@ -173,6 +174,13 @@ pub enum EventType {
msg_id: u32,
},
/// A single message is deleted.
#[serde(rename_all = "camelCase")]
MsgDeleted {
chat_id: u32,
msg_id: u32,
},
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
/// See setChatName(), setChatProfileImage(), addContactToChat()
@@ -346,6 +354,10 @@ impl From<CoreEventType> for EventType {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
},
CoreEventType::MsgDeleted { chat_id, msg_id } => MsgDeleted {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
},
CoreEventType::ChatModified(chat_id) => ChatModified {
chat_id: chat_id.to_u32(),
},

View File

@@ -2,7 +2,7 @@ use deltachat::net::HttpResponse as CoreHttpResponse;
use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
pub struct HttpResponse {
/// base64-encoded response body.
blob: String,

View File

@@ -2,7 +2,7 @@ use deltachat::location::Location;
use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Location", rename_all = "camelCase")]
pub struct JsonrpcLocation {
pub location_id: u32,

View File

@@ -1,7 +1,7 @@
use anyhow::{anyhow, Result};
use anyhow::{Context as _, Result};
use deltachat::chat::Chat;
use deltachat::chat::ChatItem;
use deltachat::constants::Chattype;
use deltachat::chat::ChatVisibility;
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::download;
@@ -10,8 +10,7 @@ use deltachat::message::MsgId;
use deltachat::message::Viewtype;
use deltachat::reaction::get_msg_reactions;
use num_traits::cast::ToPrimitive;
use serde::Deserialize;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
@@ -19,14 +18,14 @@ use super::contact::ContactObject;
use super::reactions::JSONRPCReactions;
use super::webxdc::WebxdcMessageInfo;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase", tag = "variant")]
pub enum MessageLoadResult {
Message(MessageObject),
LoadingError { error: String },
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Message", rename_all = "camelCase")]
pub struct MessageObject {
id: u32,
@@ -35,7 +34,7 @@ pub struct MessageObject {
quote: Option<MessageQuote>,
parent_id: Option<u32>,
text: Option<String>,
text: String,
has_location: bool,
has_html: bool,
view_type: MessageViewtype,
@@ -86,7 +85,7 @@ pub struct MessageObject {
reactions: Option<JSONRPCReactions>,
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(tag = "kind")]
enum MessageQuote {
JustText {
@@ -114,8 +113,12 @@ impl MessageObject {
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
let message = Message::load_from_db(context, msg_id).await?;
let sender_contact = Contact::load_from_db(context, message.get_from_id()).await?;
let sender = ContactObject::try_from_dc_contact(context, sender_contact).await?;
let sender_contact = Contact::get_by_id(context, message.get_from_id())
.await
.context("failed to load sender contact")?;
let sender = ContactObject::try_from_dc_contact(context, sender_contact)
.await
.context("failed to load sender contact object")?;
let file_bytes = message.get_filebytes(context).await?.unwrap_or_default();
let override_sender_name = message.get_override_sender_name();
@@ -132,7 +135,9 @@ impl MessageObject {
let quote = if let Some(quoted_text) = message.quoted_text() {
match message.quoted_message(context).await? {
Some(quote) => {
let quote_author = Contact::load_from_db(context, quote.get_from_id()).await?;
let quote_author = Contact::get_by_id(context, quote.get_from_id())
.await
.context("failed to load quote author contact")?;
Some(MessageQuote::WithMessage {
text: quoted_text,
message_id: quote.get_id().to_u32(),
@@ -160,7 +165,9 @@ impl MessageObject {
None
};
let reactions = get_msg_reactions(context, msg_id).await?;
let reactions = get_msg_reactions(context, msg_id)
.await
.context("failed to load message reactions")?;
let reactions = if reactions.is_empty() {
None
} else {
@@ -180,7 +187,7 @@ impl MessageObject {
state: message
.get_state()
.to_u32()
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
.context("state conversion to number failed")?,
error: message.error(),
timestamp: message.get_timestamp(),
@@ -203,7 +210,7 @@ impl MessageObject {
videochat_type: match message.get_videochat_type() {
Some(vct) => Some(
vct.to_u32()
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
.context("videochat type conversion to number failed")?,
),
None => None,
},
@@ -230,7 +237,7 @@ impl MessageObject {
}
}
#[derive(Serialize, Deserialize, TypeDef)]
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Viewtype")]
pub enum MessageViewtype {
Unknown,
@@ -306,11 +313,12 @@ impl From<MessageViewtype> for Viewtype {
}
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
pub enum DownloadState {
Done,
Available,
Failure,
Undecipherable,
InProgress,
}
@@ -320,12 +328,13 @@ impl From<download::DownloadState> for DownloadState {
download::DownloadState::Done => DownloadState::Done,
download::DownloadState::Available => DownloadState::Available,
download::DownloadState::Failure => DownloadState::Failure,
download::DownloadState::Undecipherable => DownloadState::Undecipherable,
download::DownloadState::InProgress => DownloadState::InProgress,
}
}
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
pub enum SystemMessageType {
Unknown,
GroupNameChanged,
@@ -380,7 +389,7 @@ impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
}
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MessageNotificationInfo {
id: u32,
@@ -438,14 +447,22 @@ impl MessageNotificationInfo {
}
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MessageSearchResult {
id: u32,
author_profile_image: Option<String>,
/// if sender name if overridden it will show it as ~alias
author_name: String,
author_color: String,
chat_name: Option<String>,
author_id: u32,
chat_profile_image: Option<String>,
chat_color: String,
chat_name: String,
chat_type: u32,
is_chat_protected: bool,
is_chat_contact_request: bool,
is_chat_archived: bool,
message: String,
timestamp: i64,
}
@@ -454,30 +471,44 @@ impl MessageSearchResult {
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
let message = Message::load_from_db(context, msg_id).await?;
let chat = Chat::load_from_db(context, message.get_chat_id()).await?;
let sender = Contact::load_from_db(context, message.get_from_id()).await?;
let sender = Contact::get_by_id(context, message.get_from_id()).await?;
let profile_image = match sender.get_profile_image(context).await? {
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
None => None,
};
let chat_profile_image = match chat.get_profile_image(context).await? {
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
None => None,
};
let author_name = if let Some(name) = message.get_override_sender_name() {
format!("~{name}")
} else {
sender.get_display_name().to_owned()
};
let chat_color = color_int_to_hex_string(chat.get_color(context).await?);
Ok(Self {
id: msg_id.to_u32(),
author_profile_image: profile_image,
author_name: sender.get_display_name().to_owned(),
author_name,
author_color: color_int_to_hex_string(sender.get_color()),
chat_name: if chat.get_type() == Chattype::Single {
Some(chat.get_name().to_owned())
} else {
None
},
message: message.get_text().unwrap_or_default(),
author_id: sender.id.to_u32(),
chat_name: chat.get_name().to_owned(),
chat_color,
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
chat_profile_image,
is_chat_protected: chat.is_protected(),
is_chat_contact_request: chat.is_contact_request(),
is_chat_archived: chat.get_visibility() == ChatVisibility::Archived,
message: message.get_text(),
timestamp: message.get_timestamp(),
})
}
}
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase", rename = "MessageListItem", tag = "kind")]
pub enum JSONRPCMessageListItem {
Message {
@@ -503,7 +534,7 @@ impl From<ChatItem> for JSONRPCMessageListItem {
}
}
#[derive(Deserialize, TypeDef)]
#[derive(Deserialize, Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MessageData {
pub text: Option<String>,
@@ -514,3 +545,10 @@ pub struct MessageData {
pub override_sender_name: Option<String>,
pub quoted_message_id: Option<u32>,
}
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MessageReadReceipt {
pub contact_id: u32,
pub timestamp: i64,
}

View File

@@ -2,6 +2,7 @@ pub mod account;
pub mod chat;
pub mod chat_list;
pub mod contact;
pub mod events;
pub mod http;
pub mod location;
pub mod message;

View File

@@ -3,7 +3,7 @@ use num_traits::cast::ToPrimitive;
use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ProviderInfo {
pub before_login_hint: String,

View File

@@ -2,7 +2,7 @@ use deltachat::qr::Qr;
use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Qr", rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum QrObject {

View File

@@ -1,23 +1,37 @@
use std::collections::BTreeMap;
use deltachat::contact::ContactId;
use deltachat::reaction::Reactions;
use serde::Serialize;
use typescript_type_def::TypeDef;
/// A single reaction emoji.
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Reaction", rename_all = "camelCase")]
pub struct JSONRPCReaction {
/// Emoji.
emoji: String,
/// Emoji frequency.
count: usize,
/// True if we reacted with this emoji.
is_from_self: bool,
}
/// Structure representing all reactions to a particular message.
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "Reactions", rename_all = "camelCase")]
pub struct JSONRPCReactions {
/// Map from a contact to it's reaction to message.
reactions_by_contact: BTreeMap<u32, Vec<String>>,
/// Unique reactions and their count
reactions: BTreeMap<String, u32>,
/// Unique reactions and their count, sorted in descending order.
reactions: Vec<JSONRPCReaction>,
}
impl From<Reactions> for JSONRPCReactions {
fn from(reactions: Reactions) -> Self {
let mut reactions_by_contact: BTreeMap<u32, Vec<String>> = BTreeMap::new();
let mut unique_reactions: BTreeMap<String, u32> = BTreeMap::new();
for contact_id in reactions.contacts() {
let reaction = reactions.get(contact_id);
@@ -30,18 +44,29 @@ impl From<Reactions> for JSONRPCReactions {
.map(|emoji| emoji.to_owned())
.collect();
reactions_by_contact.insert(contact_id.to_u32(), emojis.clone());
for emoji in emojis {
if let Some(x) = unique_reactions.get_mut(&emoji) {
*x += 1;
} else {
unique_reactions.insert(emoji, 1);
}
}
}
let self_reactions = reactions_by_contact.get(&ContactId::SELF.to_u32());
let mut reactions_v = Vec::new();
for (emoji, count) in reactions.emoji_sorted_by_frequency() {
let is_from_self = if let Some(self_reactions) = self_reactions {
self_reactions.contains(&emoji)
} else {
false
};
let reaction = JSONRPCReaction {
emoji,
count,
is_from_self,
};
reactions_v.push(reaction)
}
JSONRPCReactions {
reactions_by_contact,
reactions: unique_reactions,
reactions: reactions_v,
}
}
}

View File

@@ -8,7 +8,7 @@ use typescript_type_def::TypeDef;
use super::maybe_empty_string_to_option;
#[derive(Serialize, TypeDef)]
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename = "WebxdcMessageInfo", rename_all = "camelCase")]
pub struct WebxdcMessageInfo {
/// The name of the app.

View File

@@ -1,5 +1,4 @@
pub mod api;
pub use api::events;
pub use yerpc;
#[cfg(test)]

View File

@@ -55,5 +55,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.114.0"
"version": "1.124.0"
}

View File

@@ -1,33 +1,28 @@
import * as T from "../generated/types.js";
import { EventType } from "../generated/types.js";
import * as RPC from "../generated/jsonrpc.js";
import { RawClient } from "../generated/client.js";
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
import { TinyEmitter } from "@deltachat/tiny-emitter";
type DCWireEvent<T extends Event> = {
event: T;
contextId: number;
};
// export type Events = Record<
// Event["type"] | "ALL",
// (event: DeltaChatEvent<Event>) => void
// >;
type Events = { ALL: (accountId: number, event: Event) => void } & {
[Property in Event["type"]]: (
type Events = { ALL: (accountId: number, event: EventType) => void } & {
[Property in EventType["type"]]: (
accountId: number,
event: Extract<Event, { type: Property }>
event: Extract<EventType, { type: Property }>
) => void;
};
type ContextEvents = { ALL: (event: Event) => void } & {
[Property in Event["type"]]: (
event: Extract<Event, { type: Property }>
type ContextEvents = { ALL: (event: EventType) => void } & {
[Property in EventType["type"]]: (
event: Extract<EventType, { type: Property }>
) => void;
};
export type DcEvent = Event;
export type DcEventType<T extends Event["type"]> = Extract<Event, { type: T }>;
export type DcEvent = EventType;
export type DcEventType<T extends EventType["type"]> = Extract<
EventType,
{ type: T }
>;
export class BaseDeltaChat<
Transport extends BaseTransport<any>
@@ -50,16 +45,17 @@ export class BaseDeltaChat<
async eventLoop(): Promise<void> {
while (true) {
const event = await this.rpc.getNextEvent();
this.emit(event.event.type, event.context_id, event.event as any);
this.emit("ALL", event.context_id, event.event as any);
//@ts-ignore
this.emit(event.event.type, event.contextId, event.event);
this.emit("ALL", event.contextId, event.event);
if (this.contextEmitters[event.context_id]) {
this.contextEmitters[event.context_id].emit(
if (this.contextEmitters[event.contextId]) {
this.contextEmitters[event.contextId].emit(
event.event.type,
//@ts-ignore
event.event as any
);
this.contextEmitters[event.context_id].emit("ALL", event.event as any);
this.contextEmitters[event.contextId].emit("ALL", event.event as any);
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.114.0"
version = "1.124.0"
license = "MPL-2.0"
edition = "2021"
@@ -9,10 +9,10 @@ ansi_term = "0.12.1"
anyhow = "1"
deltachat = { path = "..", features = ["internals"]}
dirs = "5"
log = "0.4.16"
pretty_env_logger = "0.4"
log = "0.4.19"
pretty_env_logger = "0.5"
rusqlite = "0.29"
rustyline = "11"
rustyline = "12"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
[features]

View File

@@ -187,6 +187,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
DownloadState::Available => " [⬇ Download available]",
DownloadState::InProgress => " [⬇ Download in progress...]",
DownloadState::Failure => " [⬇ Download failed]",
DownloadState::Undecipherable => " [⬇ Decryption failed]",
};
let temp2 = timestamp_to_str(msg.get_timestamp());
@@ -199,7 +200,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
if msg.has_location() { "📍" } else { "" },
&contact_name,
contact_id,
msgtext.unwrap_or_default(),
msgtext,
if msg.has_html() { "[HAS-HTML]" } else { "" },
if msg.get_from_id() == ContactId::SELF {
""
@@ -805,15 +806,30 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
}
"chatinfo" => {
ensure!(sel_chat.is_some(), "No chat selected.");
let sel_chat_id = sel_chat.as_ref().unwrap().get_id();
let contacts =
chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?;
let contacts = chat::get_chat_contacts(&context, sel_chat_id).await?;
println!("Memberlist:");
log_contactlist(&context, &contacts).await?;
println!("{} contacts", contacts.len());
let similar_chats = sel_chat_id.get_similar_chat_ids(&context).await?;
if !similar_chats.is_empty() {
println!("Similar chats: ");
for (similar_chat_id, metric) in similar_chats {
let similar_chat = Chat::load_from_db(&context, similar_chat_id).await?;
println!(
"{} (#{}) {:.1}",
similar_chat.name,
similar_chat_id,
100.0 * metric
);
}
}
println!(
"{} contacts\nLocation streaming: {}",
contacts.len(),
"Location streaming: {}",
location::is_sending_locations_to_chat(
&context,
Some(sel_chat.as_ref().unwrap().get_id())
@@ -878,7 +894,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let latitude = arg1.parse()?;
let longitude = arg2.parse()?;
let continue_streaming = location::set(&context, latitude, longitude, 0.).await;
let continue_streaming = location::set(&context, latitude, longitude, 0.).await?;
if continue_streaming {
println!("Success, streaming should be continued.");
} else {
@@ -912,9 +928,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
Viewtype::File
});
msg.set_file(arg1, None);
if !arg2.is_empty() {
msg.set_text(Some(arg2.to_string()));
}
msg.set_text(arg2.to_string());
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
}
"sendhtml" => {
@@ -926,11 +940,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let mut msg = Message::new(Viewtype::Text);
msg.set_html(Some(html.to_string()));
msg.set_text(Some(if arg2.is_empty() {
msg.set_text(if arg2.is_empty() {
path.file_name().unwrap().to_string_lossy().to_string()
} else {
arg2.to_string()
}));
});
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
}
"sendsyncmsg" => match context.send_sync_msg().await? {
@@ -979,7 +993,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
if !arg1.is_empty() {
let mut draft = Message::new(Viewtype::Text);
draft.set_text(Some(arg1.to_string()));
draft.set_text(arg1.to_string());
sel_chat
.as_ref()
.unwrap()
@@ -1003,7 +1017,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"Please specify text to add as device message."
);
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(arg1.to_string()));
msg.set_text(arg1.to_string());
chat::add_device_msg(&context, None, Some(&mut msg)).await?;
}
"listmedia" => {
@@ -1090,7 +1104,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"msginfo" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = MsgId::new(arg1.parse()?);
let res = message::get_msg_info(&context, id).await?;
let res = id.get_info(&context).await?;
println!("{res}");
}
"download" => {

View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -6,8 +6,22 @@ build-backend = "setuptools.build_meta"
name = "deltachat-rpc-client"
description = "Python client for Delta Chat core JSON-RPC interface"
dependencies = [
"aiohttp",
"aiodns"
"aiohttp"
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: AsyncIO",
"Intended Audience :: Developers",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS :: MacOS X",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Communications :: Chat",
"Topic :: Communications :: Email"
]
dynamic = [
"version"

View File

@@ -259,3 +259,11 @@ class Account:
)
fresh_msg_ids = sorted(await self._rpc.get_fresh_msgs(self.id))
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
async def export_backup(self, path, passphrase: str = "") -> None:
"""Export backup."""
await self._rpc.export_backup(self.id, str(path), passphrase)
async def import_backup(self, path, passphrase: str = "") -> None:
"""Import backup."""
await self._rpc.import_backup(self.id, str(path), passphrase)

View File

@@ -45,6 +45,7 @@ class EventType(str, Enum):
MSG_DELIVERED = "MsgDelivered"
MSG_FAILED = "MsgFailed"
MSG_READ = "MsgRead"
MSG_DELETED = "MsgDeleted"
CHAT_MODIFIED = "ChatModified"
CHAT_EPHEMERAL_TIMER_MODIFIED = "ChatEphemeralTimerModified"
CONTACTS_CHANGED = "ContactsChanged"

View File

@@ -1,6 +1,6 @@
import json
from dataclasses import dataclass
from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING, Optional, Union
from ._utils import AttrDict
from .contact import Contact
@@ -35,6 +35,13 @@ class Message:
snapshot["message"] = self
return snapshot
async def get_reactions(self) -> Optional[AttrDict]:
"""Get message reactions."""
reactions = await self._rpc.get_message_reactions(self.account.id, self.id)
if reactions:
return AttrDict(reactions)
return None
async def mark_seen(self) -> None:
"""Mark the message as seen."""
await self._rpc.markseen_msgs(self.account.id, [self.id])

View File

@@ -16,9 +16,8 @@ async def get_temp_credentials() -> dict:
# Replace default 5 minute timeout with a 1 minute timeout.
timeout = aiohttp.ClientTimeout(total=60)
async with aiohttp.ClientSession() as session:
async with session.post(url, timeout=timeout) as response:
return json.loads(await response.text())
async with aiohttp.ClientSession() as session, session.post(url, timeout=timeout) as response:
return json.loads(await response.text())
class ACFactory:

View File

@@ -1,5 +1,6 @@
import asyncio
import json
import logging
import os
from typing import Any, Dict, Optional
@@ -28,10 +29,16 @@ class Rpc:
self.events_task: asyncio.Task
async def start(self) -> None:
# Use buffer of 64 MiB.
# Default limit as of Python 3.11 is 2**16 bytes, this is too low for some JSON-RPC responses,
# such as loading large HTML message content.
limit = 2**26
self.process = await asyncio.create_subprocess_exec(
"deltachat-rpc-server",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
limit=limit,
**self._kwargs,
)
self.id = 0
@@ -57,16 +64,20 @@ class Rpc:
await self.close()
async def reader_loop(self) -> None:
while True:
line = await self.process.stdout.readline() # noqa
if not line: # EOF
break
response = json.loads(line)
if "id" in response:
fut = self.request_events.pop(response["id"])
fut.set_result(response)
else:
print(response)
try:
while True:
line = await self.process.stdout.readline() # noqa
if not line: # EOF
break
response = json.loads(line)
if "id" in response:
fut = self.request_events.pop(response["id"])
fut.set_result(response)
else:
print(response)
except Exception:
# Log an exception if the reader loop dies.
logging.exception("Exception in the reader loop")
async def get_queue(self, account_id: int) -> asyncio.Queue:
if account_id not in self.event_queues:
@@ -75,13 +86,17 @@ class Rpc:
async def events_loop(self) -> None:
"""Requests new events and distributes them between queues."""
while True:
if self.closing:
return
event = await self.get_next_event()
account_id = event["context_id"]
queue = await self.get_queue(account_id)
await queue.put(event["event"])
try:
while True:
if self.closing:
return
event = await self.get_next_event()
account_id = event["contextId"]
queue = await self.get_queue(account_id)
await queue.put(event["event"])
except Exception:
# Log an exception if the event loop dies.
logging.exception("Exception in the event loop")
async def wait_for_event(self, account_id: int) -> Optional[dict]:
"""Waits for the next event from the given account and returns it."""
@@ -89,16 +104,14 @@ class Rpc:
return await queue.get()
def __getattr__(self, attr: str):
async def method(*args, **kwargs) -> Any:
async def method(*args) -> Any:
self.id += 1
request_id = self.id
assert not (args and kwargs), "Mixing positional and keyword arguments"
request = {
"jsonrpc": "2.0",
"method": attr,
"params": kwargs or args,
"params": args,
"id": self.id,
}
data = (json.dumps(request) + "\n").encode()

View File

@@ -101,6 +101,16 @@ async def test_account(acfactory) -> None:
assert await alice.get_fresh_messages()
assert await alice.get_next_messages()
# Test sending empty message.
assert len(await bob.wait_next_messages()) == 0
await alice_chat_bob.send_text("")
messages = await bob.wait_next_messages()
assert len(messages) == 1
message = messages[0]
snapshot = await message.get_snapshot()
assert snapshot.text == ""
await bob.mark_seen_messages([message])
group = await alice.create_group("test group")
await group.add_contact(alice_contact_bob)
group_msg = await group.send_message(text="hello")
@@ -239,6 +249,10 @@ async def test_message(acfactory) -> None:
await message.mark_seen()
await message.send_reaction("😎")
reactions = await message.get_reactions()
assert reactions
snapshot = await message.get_snapshot()
assert reactions == snapshot.reactions
@pytest.mark.asyncio()
@@ -331,3 +345,13 @@ async def test_wait_next_messages(acfactory) -> None:
assert len(next_messages) == 1
snapshot = await next_messages[0].get_snapshot()
assert snapshot.text == "Hello!"
@pytest.mark.asyncio()
async def test_import_export(acfactory, tmp_path) -> None:
alice = await acfactory.new_configured_account()
await alice.export_backup(tmp_path)
files = list(tmp_path.glob("*.tar"))
alice2 = await acfactory.get_unconfigured_account()
await alice2.import_backup(files[0])

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.114.0"
version = "1.124.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -17,11 +17,11 @@ anyhow = "1"
env_logger = { version = "0.10.0" }
futures-lite = "1.13.0"
log = "0.4"
serde_json = "1.0.96"
serde_json = "1.0.99"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.27.0", features = ["io-std"] }
tokio-util = "0.7.7"
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
tokio = { version = "1.29.1", features = ["io-std"] }
tokio-util = "0.7.8"
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
[features]
default = ["vendored"]

View File

@@ -1,7 +1,7 @@
//! Delta Chat core RPC server.
//!
//! It speaks JSON Lines over stdio.
use std::env;
///! Delta Chat core RPC server.
///!
///! It speaks JSON Lines over stdio.
use std::path::PathBuf;
use std::sync::Arc;

View File

@@ -2,9 +2,7 @@
unmaintained = "allow"
ignore = [
"RUSTSEC-2020-0071",
# Only affects windows if using non-default allocator (and unmaintained).
"RUSTSEC-2021-0145",
"RUSTSEC-2022-0093",
]
[bans]
@@ -13,12 +11,11 @@ ignore = [
# when upgrading.
# Please keep this list alphabetically sorted.
skip = [
{ name = "ahash", version = "0.7.6" },
{ name = "base16ct", version = "0.1.1" },
{ name = "base64", version = "<0.21" },
{ name = "bitflags", version = "1.3.2" },
{ name = "block-buffer", version = "<0.10" },
{ name = "clap_lex", version = "0.2.4" },
{ name = "clap", version = "3.2.23" },
{ name = "convert_case", version = "0.4.0" },
{ name = "curve25519-dalek", version = "3.2.0" },
{ name = "darling_core", version = "<0.14" },
@@ -28,12 +25,12 @@ skip = [
{ name = "digest", version = "<0.10" },
{ name = "ed25519-dalek", version = "1.0.1" },
{ name = "ed25519", version = "1.5.3" },
{ name = "env_logger", version = "<0.10" },
{ name = "getrandom", version = "<0.2" },
{ name = "hermit-abi", version = "<0.3" },
{ name = "humantime", version = "<2.1" },
{ name = "hashbrown", version = "<0.14.0" },
{ name = "idna", version = "<0.3" },
{ name = "libm", version = "0.1.4" },
{ name = "indexmap", version = "<2.0.0" },
{ name = "linux-raw-sys", version = "0.3.8" },
{ name = "num-derive", version = "0.3.3" },
{ name = "pem-rfc7468", version = "0.6.0" },
{ name = "pkcs8", version = "0.9.0" },
{ name = "quick-error", version = "<2.0" },
@@ -41,20 +38,28 @@ skip = [
{ name = "rand_core", version = "<0.6" },
{ name = "rand", version = "<0.8" },
{ name = "redox_syscall", version = "0.2.16" },
{ name = "regex-syntax", version = "0.6.29" },
{ name = "rustix", version = "0.37.21" },
{ name = "sec1", version = "0.3.0" },
{ name = "sha2", version = "<0.10" },
{ name = "signature", version = "1.6.4" },
{ name = "socket2", version = "0.4.9" },
{ name = "spin", version = "<0.9.6" },
{ name = "spki", version = "0.6.0" },
{ name = "syn", version = "1.0.109" },
{ name = "time", version = "<0.3" },
{ name = "wasi", version = "<0.11" },
{ name = "windows_aarch64_msvc", version = "<0.42" },
{ name = "windows_i686_gnu", version = "<0.42" },
{ name = "windows_i686_msvc", version = "<0.42" },
{ name = "windows-sys", version = "<0.45" },
{ name = "windows_x86_64_gnu", version = "<0.42" },
{ name = "windows_x86_64_msvc", version = "<0.42" },
{ name = "windows_aarch64_gnullvm", version = "<0.48" },
{ name = "windows_aarch64_msvc", version = "<0.48" },
{ name = "windows_i686_gnu", version = "<0.48" },
{ name = "windows_i686_msvc", version = "<0.48" },
{ name = "windows-sys", version = "<0.48" },
{ name = "windows-targets", version = "<0.48" },
{ name = "windows_x86_64_gnullvm", version = "<0.48" },
{ name = "windows", version = "0.32.0" },
{ name = "windows_x86_64_gnu", version = "<0.48" },
{ name = "windows_x86_64_msvc", version = "<0.48" },
{ name = "winreg", version = "0.10.1" },
]

128
fuzz/Cargo.lock generated
View File

@@ -2,12 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "abao"
version = "0.2.0"
@@ -60,19 +54,13 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.20"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]]
name = "aliasable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
@@ -189,12 +177,11 @@ dependencies = [
[[package]]
name = "async-imap"
version = "0.7.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8379e2f1cdeb79afd2006932d7e8f64993fc0f7386d0ebc37231c90b05968c25"
checksum = "da93622739d458dd9a6abc1abf0e38e81965a5824a3b37f9500437c82a8bb572"
dependencies = [
"async-channel",
"async-native-tls 0.4.0",
"base64 0.21.0",
"byte-pool",
"chrono",
@@ -203,25 +190,13 @@ dependencies = [
"log",
"nom",
"once_cell",
"ouroboros",
"pin-utils",
"self_cell",
"stop-token",
"thiserror",
"tokio",
]
[[package]]
name = "async-native-tls"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d57d4cec3c647232e1094dc013546c0b33ce785d8aeb251e1f20dfaf8a9a13fe"
dependencies = [
"native-tls",
"thiserror",
"tokio",
"url",
]
[[package]]
name = "async-native-tls"
version = "0.5.0"
@@ -556,9 +531,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "byte-pool"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c7230ddbb427b1094d477d821a99f3f54d36333178eeb806e279bcdcecf0ca"
checksum = "c2f1b21189f50b5625efa6227cf45e9d4cfdc2e73582df2b879e9689e78a7158"
dependencies = [
"crossbeam-queue",
"stable_deref_trait",
@@ -951,12 +926,12 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.112.6"
version = "1.117.0"
dependencies = [
"anyhow",
"async-channel",
"async-imap",
"async-native-tls 0.5.0",
"async-native-tls",
"async-smtp",
"async_zip",
"backtrace",
@@ -979,6 +954,7 @@ dependencies = [
"lettre_email",
"libc",
"mailparse 0.14.0",
"mime",
"num-derive",
"num-traits",
"num_cpus",
@@ -1244,7 +1220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369"
dependencies = [
"serde",
"signature 1.6.4",
"signature 2.1.0",
]
[[package]]
@@ -1701,9 +1677,9 @@ checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]]
name = "futures-lite"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand",
"futures-core",
@@ -2397,9 +2373,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mime"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
@@ -2651,9 +2627,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.17.0"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "opaque-debug"
@@ -2663,9 +2639,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.48"
version = "0.10.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
@@ -2704,11 +2680,10 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.83"
version = "0.9.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
dependencies = [
"autocfg",
"cc",
"libc",
"openssl-src",
@@ -2716,29 +2691,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "ouroboros"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca"
dependencies = [
"aliasable",
"ouroboros_macro",
]
[[package]]
name = "ouroboros_macro"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d"
dependencies = [
"Inflector",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.107",
]
[[package]]
name = "overload"
version = "0.1.1"
@@ -2865,9 +2817,9 @@ dependencies = [
[[package]]
name = "percent-encoding"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pgp"
@@ -3316,13 +3268,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.0"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.7.2",
]
[[package]]
@@ -3331,7 +3283,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax",
"regex-syntax 0.6.28",
]
[[package]]
@@ -3341,10 +3293,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "reqwest"
version = "0.11.16"
name = "regex-syntax"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]]
name = "reqwest"
version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [
"base64 0.21.0",
"bytes",
@@ -3690,6 +3648,12 @@ dependencies = [
"libc",
]
[[package]]
name = "self_cell"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6"
[[package]]
name = "semver"
version = "1.0.17"
@@ -4249,9 +4213,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.11"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
dependencies = [
"futures-core",
"pin-project-lite",
@@ -4275,9 +4239,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.7"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
dependencies = [
"bytes",
"futures-core",

View File

@@ -48,6 +48,7 @@ module.exports = {
DC_EVENT_LOCATION_CHANGED: 2035,
DC_EVENT_MSGS_CHANGED: 2000,
DC_EVENT_MSGS_NOTICED: 2008,
DC_EVENT_MSG_DELETED: 2016,
DC_EVENT_MSG_DELIVERED: 2010,
DC_EVENT_MSG_FAILED: 2012,
DC_EVENT_MSG_READ: 2015,
@@ -89,6 +90,7 @@ module.exports = {
DC_KEY_GEN_DEFAULT: 0,
DC_KEY_GEN_ED25519: 2,
DC_KEY_GEN_RSA2048: 1,
DC_KEY_GEN_RSA4096: 3,
DC_LP_AUTH_NORMAL: 4,
DC_LP_AUTH_OAUTH2: 2,
DC_MEDIA_QUALITY_BALANCED: 0,

View File

@@ -22,6 +22,7 @@ module.exports = {
2010: 'DC_EVENT_MSG_DELIVERED',
2012: 'DC_EVENT_MSG_FAILED',
2015: 'DC_EVENT_MSG_READ',
2016: 'DC_EVENT_MSG_DELETED',
2020: 'DC_EVENT_CHAT_MODIFIED',
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
2030: 'DC_EVENT_CONTACTS_CHANGED',

View File

@@ -48,6 +48,7 @@ export enum C {
DC_EVENT_LOCATION_CHANGED = 2035,
DC_EVENT_MSGS_CHANGED = 2000,
DC_EVENT_MSGS_NOTICED = 2008,
DC_EVENT_MSG_DELETED = 2016,
DC_EVENT_MSG_DELIVERED = 2010,
DC_EVENT_MSG_FAILED = 2012,
DC_EVENT_MSG_READ = 2015,
@@ -89,6 +90,7 @@ export enum C {
DC_KEY_GEN_DEFAULT = 0,
DC_KEY_GEN_ED25519 = 2,
DC_KEY_GEN_RSA2048 = 1,
DC_KEY_GEN_RSA4096 = 3,
DC_LP_AUTH_NORMAL = 4,
DC_LP_AUTH_OAUTH2 = 2,
DC_MEDIA_QUALITY_BALANCED = 0,
@@ -307,6 +309,7 @@ export const EventId2EventName: { [key: number]: string } = {
2010: 'DC_EVENT_MSG_DELIVERED',
2012: 'DC_EVENT_MSG_FAILED',
2015: 'DC_EVENT_MSG_READ',
2016: 'DC_EVENT_MSG_DELETED',
2020: 'DC_EVENT_CHAT_MODIFIED',
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
2030: 'DC_EVENT_CONTACTS_CHANGED',

View File

@@ -446,7 +446,8 @@ describe('Offline Tests with unconfigured account', function () {
context.setChatProfileImage(chatId, imagePath)
const blobPath = context.getChat(chatId).getProfileImage()
expect(blobPath.startsWith(blobs)).to.be.true
expect(blobPath.endsWith(image)).to.be.true
expect(blobPath.includes('image')).to.be.true
expect(blobPath.endsWith('.jpeg')).to.be.true
context.setChatProfileImage(chatId, null)
expect(context.getChat(chatId).getProfileImage()).to.be.equal(

View File

@@ -60,5 +60,5 @@
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.114.0"
"version": "1.124.0"
}

View File

@@ -357,7 +357,7 @@ Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE

View File

@@ -74,7 +74,9 @@ Developing the bindings
If you want to develop or debug the bindings,
you can create a testing development environment using `tox`::
tox -c python --devenv env
export DCC_RS_DEV="$PWD"
export DCC_RS_TARGET=debug
tox -c python --devenv env -e py
. env/bin/activate
Inside this environment the bindings are installed

View File

@@ -2,34 +2,34 @@
high level API reference
========================
- :class:`deltachat.account.Account` (your main entry point, creates the
- :class:`deltachat.Account` (your main entry point, creates the
other classes)
- :class:`deltachat.contact.Contact`
- :class:`deltachat.chat.Chat`
- :class:`deltachat.message.Message`
- :class:`deltachat.Contact`
- :class:`deltachat.Chat`
- :class:`deltachat.Message`
Account
-------
.. autoclass:: deltachat.account.Account
.. autoclass:: deltachat.Account
:members:
Contact
-------
.. autoclass:: deltachat.contact.Contact
.. autoclass:: deltachat.Contact
:members:
Chat
----
.. autoclass:: deltachat.chat.Chat
.. autoclass:: deltachat.Chat
:members:
Message
-------
.. autoclass:: deltachat.message.Message
.. autoclass:: deltachat.Message
:members:

View File

@@ -32,25 +32,13 @@ class GroupTrackingPlugin:
@account_hookimpl
def ac_member_added(self, chat, contact, actor, message):
print(
"ac_member_added {} to chat {} from {}".format(
contact.addr,
chat.id,
actor or message.get_sender_contact().addr,
),
)
print(f"ac_member_added {contact.addr} to chat {chat.id} from {actor or message.get_sender_contact().addr}")
for member in chat.get_contacts():
print(f"chat member: {member.addr}")
@account_hookimpl
def ac_member_removed(self, chat, contact, actor, message):
print(
"ac_member_removed {} from chat {} by {}".format(
contact.addr,
chat.id,
actor or message.get_sender_contact().addr,
),
)
print(f"ac_member_removed {contact.addr} from chat {chat.id} by {actor or message.get_sender_contact().addr}")
def main(argv=None):

View File

@@ -24,8 +24,6 @@ def test_echo_quit_plugin(acfactory, lp):
lp.sec("creating a temp account to contact the bot")
(ac1,) = acfactory.get_online_accounts(1)
botproc.await_resync()
lp.sec("sending a message to the bot")
bot_contact = ac1.create_contact(botproc.addr)
bot_chat = bot_contact.create_chat()
@@ -54,8 +52,6 @@ def test_group_tracking_plugin(acfactory, lp):
ac1.add_account_plugin(FFIEventLogger(ac1))
ac2.add_account_plugin(FFIEventLogger(ac2))
botproc.await_resync()
lp.sec("creating bot test group with bot")
bot_contact = ac1.create_contact(botproc.addr)
ch = ac1.create_group_chat("bot test group")

View File

@@ -24,3 +24,5 @@ ignore_missing_imports = True
[mypy-imap_tools.*]
ignore_missing_imports = True
[mypy-distutils.*]
ignore_missing_imports = True

View File

@@ -11,10 +11,11 @@ authors = [
{ name = "holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Programming Language :: Python :: 3",
"Topic :: Communications :: Chat",
"Topic :: Communications :: Email",
"Topic :: Software Development :: Libraries",
]
@@ -33,6 +34,7 @@ dynamic = [
"Home" = "https://github.com/deltachat/deltachat-core-rust/"
"Bug Tracker" = "https://github.com/deltachat/deltachat-core-rust/issues"
"Documentation" = "https://py.delta.chat/"
"Mastodon" = "https://chaos.social/@delta"
[project.entry-points.pytest11]
"deltachat.testplugin" = "deltachat.testplugin"

View File

@@ -6,7 +6,7 @@ from array import array
from contextlib import contextmanager
from email.utils import parseaddr
from threading import Event
from typing import Any, Dict, Generator, List, Optional, Union, TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Union
from . import const, hookspec
from .capi import ffi, lib
@@ -195,7 +195,7 @@ class Account:
assert res != ffi.NULL, f"config value not found for: {name!r}"
return from_dc_charpointer(res)
def _preconfigure_keypair(self, addr: str, public: str, secret: str) -> None:
def _preconfigure_keypair(self, addr: str, secret: str) -> None:
"""See dc_preconfigure_keypair() in deltachat.h.
In other words, you don't need this.
@@ -203,7 +203,7 @@ class Account:
res = lib.dc_preconfigure_keypair(
self._dc_context,
as_dc_charpointer(addr),
as_dc_charpointer(public),
ffi.NULL,
as_dc_charpointer(secret),
)
if res == 0:
@@ -427,7 +427,7 @@ class Account:
assert dc_chatlist != ffi.NULL
chatlist = []
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
for i in range(lib.dc_chatlist_get_cnt(dc_chatlist)):
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
chatlist.append(Chat(self, chat_id))
return chatlist

View File

@@ -71,13 +71,16 @@ class Contact:
"""Unblock this contact. Messages from this contact will be retrieved (again)."""
return lib.dc_block_contact(self.account._dc_context, self.id, False)
def is_verified(self):
def is_verified(self) -> bool:
"""Return True if the contact is verified."""
return lib.dc_contact_is_verified(self._dc_contact)
return lib.dc_contact_is_verified(self._dc_contact) == 2
def get_verifier(self, contact):
def get_verifier(self, contact) -> Optional["Contact"]:
"""Return the address of the contact that verified the contact."""
return from_dc_charpointer(lib.dc_contact_get_verifier_addr(contact._dc_contact))
verifier_id = lib.dc_contact_get_verifier_id(contact._dc_contact)
if verifier_id == 0:
return None
return Contact(self.account, verifier_id)
def get_profile_image(self) -> Optional[str]:
"""Get contact profile image.

View File

@@ -15,7 +15,7 @@ def as_dc_charpointer(obj):
def iter_array(dc_array_t, constructor: Callable[[int], T]) -> Generator[T, None, None]:
for i in range(0, lib.dc_array_get_cnt(dc_array_t)):
for i in range(lib.dc_array_get_cnt(dc_array_t)):
yield constructor(lib.dc_array_get_id(dc_array_t, i))

View File

@@ -9,11 +9,11 @@ from contextlib import contextmanager
from queue import Empty, Queue
from . import const
from .account import Account
from .capi import ffi, lib
from .cutil import from_optional_dc_charpointer
from .hookspec import account_hookimpl
from .message import map_system_message
from .account import Account
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):

View File

@@ -486,6 +486,9 @@ class Message:
dc_msg = ffi.gc(lib.dc_get_msg(self.account._dc_context, self.id), lib.dc_msg_unref)
return lib.dc_msg_get_download_state(dc_msg)
def download_full(self) -> None:
lib.dc_download_full_msg(self.account._dc_context, self.id)
# some code for handling DC_MSG_* view types
@@ -507,8 +510,7 @@ def get_viewtype_code_from_name(view_type_name):
if code is not None:
return code
raise ValueError(
"message typecode not found for {!r}, "
"available {!r}".format(view_type_name, list(_view_type_mapping.keys())),
f"message typecode not found for {view_type_name!r}, available {list(_view_type_mapping.keys())!r}",
)

View File

@@ -9,7 +9,7 @@ import threading
import time
import weakref
from queue import Queue
from typing import Callable, List, Optional, Dict, Set
from typing import Callable, Dict, List, Optional, Set
import pytest
import requests
@@ -137,6 +137,9 @@ def pytest_report_header(config, startdir):
@pytest.fixture(scope="session")
def testprocess(request):
"""Return live account configuration manager.
The returned object is a :class:`TestProcess` object."""
return TestProcess(pytestconfig=request.config)
@@ -231,6 +234,8 @@ def write_dict_to_dir(dic, target_dir):
@pytest.fixture()
def data(request):
"""Test data."""
class Data:
def __init__(self) -> None:
# trying to find test data heuristically
@@ -473,10 +478,9 @@ class ACFactory:
except IndexError:
pass
else:
fname_pub = self.data.read_path(f"key/{keyname}-public.asc")
fname_sec = self.data.read_path(f"key/{keyname}-secret.asc")
if fname_pub and fname_sec:
account._preconfigure_keypair(addr, fname_pub, fname_sec)
if fname_sec:
account._preconfigure_keypair(addr, fname_sec)
return True
print(f"WARN: could not use preconfigured keys for {addr!r}")
@@ -614,6 +618,7 @@ class ACFactory:
@pytest.fixture()
def acfactory(request, tmpdir, testprocess, data):
"""Account factory."""
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testprocess, data=data)
yield am
if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
@@ -676,21 +681,17 @@ class BotProcess:
print("+++IGN:", line)
ignored.append(line)
def await_resync(self):
self.fnmatch_lines(
"""
*Resync: collected * message IDs in folder INBOX*
""",
)
@pytest.fixture()
def tmp_db_path(tmpdir):
"""Return a path inside the temporary directory where the database can be created."""
return tmpdir.join("test.db").strpath
@pytest.fixture()
def lp():
"""Log printer fixture."""
class Printer:
def sec(self, msg: str) -> None:
print()

View File

@@ -1,6 +1,6 @@
from queue import Queue
from threading import Event
from typing import List, TYPE_CHECKING
from typing import TYPE_CHECKING, List
from .hookspec import Global, account_hookimpl

View File

@@ -15,6 +15,6 @@ class TestEmpty:
def test_prepare_setup_measurings(self, acfactory):
acfactory.get_online_accounts(BENCH_NUM)
@pytest.mark.parametrize("num", range(0, BENCH_NUM + 1))
@pytest.mark.parametrize("num", range(BENCH_NUM + 1))
def test_setup_online_accounts(self, acfactory, num):
acfactory.get_online_accounts(num)

View File

@@ -8,7 +8,7 @@ import pytest
import deltachat
def test_db_busy_error(acfactory, tmpdir):
def test_db_busy_error(acfactory):
starttime = time.time()
log_lock = threading.RLock()

View File

@@ -1,6 +1,7 @@
import sys
import pytest
import deltachat as dc
class TestGroupStressTests:
@@ -149,9 +150,8 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
assert msg.is_encrypted()
lp.sec("ac2: Check that ac2 verified ac1")
# If we verified the contact ourselves then verifier addr == contact addr
ac2_ac1_contact = ac2.get_contacts()[0]
assert ac2.get_self_contact().get_verifier(ac2_ac1_contact) == ac1_addr
assert ac2.get_self_contact().get_verifier(ac2_ac1_contact).id == dc.const.DC_CONTACT_ID_SELF
lp.sec("ac2: send message and let ac1 read it")
chat2.send_text("world")
@@ -176,9 +176,9 @@ def test_qr_verified_group_and_chatting(acfactory, lp):
lp.sec("ac2: Check that ac1 verified ac3 for ac2")
ac2_ac1_contact = ac2.get_contacts()[0]
assert ac2.get_self_contact().get_verifier(ac2_ac1_contact) == ac1_addr
assert ac2.get_self_contact().get_verifier(ac2_ac1_contact).id == dc.const.DC_CONTACT_ID_SELF
ac2_ac3_contact = ac2.get_contacts()[1]
assert ac2.get_self_contact().get_verifier(ac2_ac3_contact) == ac1_addr
assert ac2.get_self_contact().get_verifier(ac2_ac3_contact).addr == ac1_addr
lp.sec("ac2: send message and let ac3 read it")
chat2.send_text("hi")
@@ -494,7 +494,7 @@ def test_multidevice_sync_seen(acfactory, lp):
assert "Expires: " in ac1_clone_message.get_message_info()
def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
def test_see_new_verified_member_after_going_online(acfactory, tmp_path, lp):
"""The test for the bug #3836:
- Alice has two devices, the second is offline.
- Alice creates a verified group and sends a QR invitation to Bob.
@@ -507,9 +507,10 @@ def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
for ac in [ac1, ac1_offl]:
ac.set_config("bcc_self", "1")
acfactory.bring_accounts_online()
dir = tmpdir.mkdir("exportdir")
ac1.export_self_keys(dir.strpath)
ac1_offl.import_self_keys(dir.strpath)
dir = tmp_path / "exportdir"
dir.mkdir()
ac1.export_self_keys(str(dir))
ac1_offl.import_self_keys(str(dir))
ac1_offl.stop_io()
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
@@ -541,7 +542,7 @@ def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
ac1.set_config("bcc_self", "0")
def test_use_new_verified_group_after_going_online(acfactory, tmpdir, lp):
def test_use_new_verified_group_after_going_online(acfactory, tmp_path, lp):
"""Another test for the bug #3836:
- Bob has two devices, the second is offline.
- Alice creates a verified group and sends a QR invitation to Bob.
@@ -556,9 +557,10 @@ def test_use_new_verified_group_after_going_online(acfactory, tmpdir, lp):
for ac in [ac2, ac2_offl]:
ac.set_config("bcc_self", "1")
acfactory.bring_accounts_online()
dir = tmpdir.mkdir("exportdir")
ac2.export_self_keys(dir.strpath)
ac2_offl.import_self_keys(dir.strpath)
dir = tmp_path / "exportdir"
dir.mkdir()
ac2.export_self_keys(str(dir))
ac2_offl.import_self_keys(str(dir))
ac2_offl.stop_io()
lp.sec("ac1: create verified-group QR, ac2 scans and joins")

View File

@@ -2,18 +2,18 @@ import os
import queue
import sys
import time
import base64
from datetime import datetime, timezone
import pytest
from imap_tools import AND, U
from deltachat import const
from deltachat.hookspec import account_hookimpl
from deltachat.message import Message
import deltachat as dc
from deltachat import account_hookimpl, Message
from deltachat.tracker import ImexTracker
def test_basic_imap_api(acfactory, tmpdir):
def test_basic_imap_api(acfactory, tmp_path):
ac1, ac2 = acfactory.get_online_accounts(2)
chat12 = acfactory.get_accepted_chat(ac1, ac2)
@@ -28,7 +28,7 @@ def test_basic_imap_api(acfactory, tmpdir):
imap2.mark_all_read()
assert imap2.get_unread_cnt() == 0
imap2.dump_imap_structures(tmpdir, logfile=sys.stdout)
imap2.dump_imap_structures(tmp_path, logfile=sys.stdout)
imap2.shutdown()
@@ -36,8 +36,8 @@ def test_basic_imap_api(acfactory, tmpdir):
def test_configure_generate_key(acfactory, lp):
# A slow test which will generate new keys.
acfactory.remove_preconfigured_keys()
ac1 = acfactory.new_online_configuring_account(key_gen_type=str(const.DC_KEY_GEN_RSA2048))
ac2 = acfactory.new_online_configuring_account(key_gen_type=str(const.DC_KEY_GEN_ED25519))
ac1 = acfactory.new_online_configuring_account(key_gen_type=str(dc.const.DC_KEY_GEN_RSA2048))
ac2 = acfactory.new_online_configuring_account(key_gen_type=str(dc.const.DC_KEY_GEN_ED25519))
acfactory.bring_accounts_online()
chat = acfactory.get_accepted_chat(ac1, ac2)
@@ -72,35 +72,37 @@ def test_configure_canceled(acfactory):
pass
def test_configure_unref(tmpdir):
def test_configure_unref(tmp_path):
"""Test that removing the last reference to the context during ongoing configuration
does not result in use-after-free."""
from deltachat.capi import ffi, lib
path = tmpdir.mkdir("test_configure_unref").join("dc.db").strpath
dc_context = lib.dc_context_new(ffi.NULL, path.encode("utf8"), ffi.NULL)
path = tmp_path / "test_configure_unref"
path.mkdir()
dc_context = lib.dc_context_new(ffi.NULL, str(path / "dc.db").encode("utf8"), ffi.NULL)
lib.dc_set_config(dc_context, "addr".encode("utf8"), "foo@x.testrun.org".encode("utf8"))
lib.dc_set_config(dc_context, "mail_pw".encode("utf8"), "abc".encode("utf8"))
lib.dc_configure(dc_context)
lib.dc_context_unref(dc_context)
def test_export_import_self_keys(acfactory, tmpdir, lp):
def test_export_import_self_keys(acfactory, tmp_path, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
dir = tmpdir.mkdir("exportdir")
export_files = ac1.export_self_keys(dir.strpath)
dir = tmp_path / "exportdir"
dir.mkdir()
export_files = ac1.export_self_keys(str(dir))
assert len(export_files) == 2
for x in export_files:
assert x.startswith(dir.strpath)
assert x.startswith(str(dir))
(key_id,) = ac1._evtracker.get_info_regex_groups(r".*xporting.*KeyId\((.*)\).*")
ac1._evtracker.consume_events()
lp.sec("exported keys (private and public)")
for name in os.listdir(dir.strpath):
lp.indent(dir.strpath + os.sep + name)
for name in dir.iterdir():
lp.indent(str(dir / name))
lp.sec("importing into existing account")
ac2.import_self_keys(dir.strpath)
ac2.import_self_keys(str(dir))
(key_id2,) = ac2._evtracker.get_info_regex_groups(r".*stored.*KeyId\((.*)\).*")
assert key_id2 == key_id
@@ -156,62 +158,65 @@ def test_one_account_send_bcc_setting(acfactory, lp):
assert len(list(ac1.direct_imap.conn.fetch(AND(seen=True)))) == 1
def test_send_file_twice_unicode_filename_mangling(tmpdir, acfactory, lp):
def test_send_file_twice_unicode_filename_mangling(tmp_path, acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
chat = acfactory.get_accepted_chat(ac1, ac2)
basename = "somedäüta.html.zip"
p = os.path.join(tmpdir.strpath, basename)
with open(p, "w") as f:
f.write("some data")
basename = "somedäüta"
ext = ".html.zip"
p = tmp_path / (basename + ext)
p.write_text("some data")
def send_and_receive_message():
lp.sec("ac1: prepare and send attachment + text to ac2")
msg1 = Message.new_empty(ac1, "file")
msg1.set_text("withfile")
msg1.set_file(p)
msg1.set_file(str(p))
chat.send_msg(msg1)
lp.sec("ac2: receive message")
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > dc.const.DC_CHAT_ID_LAST_SPECIAL
return ac2.get_message_by_id(ev.data2)
msg = send_and_receive_message()
assert msg.text == "withfile"
assert open(msg.filename).read() == "some data"
assert msg.filename.endswith(basename)
msg.filename.index(basename)
assert msg.filename.endswith(ext)
msg2 = send_and_receive_message()
assert msg2.text == "withfile"
assert open(msg2.filename).read() == "some data"
assert msg2.filename.endswith("html.zip")
msg2.filename.index(basename)
assert msg2.filename.endswith(ext)
assert msg.filename != msg2.filename
def test_send_file_html_attachment(tmpdir, acfactory, lp):
def test_send_file_html_attachment(tmp_path, acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
chat = acfactory.get_accepted_chat(ac1, ac2)
basename = "test.html"
basename = "test"
ext = ".html"
content = "<html><body>text</body>data"
p = os.path.join(tmpdir.strpath, basename)
with open(p, "w") as f:
# write wrong html to see if core tries to parse it
# (it shouldn't as it's a file attachment)
f.write(content)
p = tmp_path / (basename + ext)
# write wrong html to see if core tries to parse it
# (it shouldn't as it's a file attachment)
p.write_text(content)
lp.sec("ac1: prepare and send attachment + text to ac2")
chat.send_file(p, mime_type="text/html")
chat.send_file(str(p), mime_type="text/html")
lp.sec("ac2: receive message")
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > dc.const.DC_CHAT_ID_LAST_SPECIAL
msg = ac2.get_message_by_id(ev.data2)
assert open(msg.filename).read() == content
assert msg.filename.endswith(basename)
msg.filename.index(basename)
assert msg.filename.endswith(ext)
def test_html_message(acfactory, lp):
@@ -324,6 +329,59 @@ def test_webxdc_message(acfactory, data, lp):
assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True)))) == 1
def test_webxdc_huge_update(acfactory, data, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
chat = ac1.create_chat(ac2)
msg1 = Message.new_empty(ac1, "webxdc")
msg1.set_text("message1")
msg1.set_file(data.get_path("webxdc/minimal.xdc"))
msg1 = chat.send_msg(msg1)
assert msg1.is_webxdc()
assert msg1.filename
msg2 = ac2._evtracker.wait_next_incoming_message()
assert msg2.is_webxdc()
payload = "A" * 1000
assert msg1.send_status_update({"payload": payload}, "some test data")
ac2._evtracker.get_matching("DC_EVENT_WEBXDC_STATUS_UPDATE")
update = msg2.get_status_updates()[0]
assert update["payload"] == payload
def test_webxdc_download_on_demand(acfactory, data, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
acfactory.introduce_each_other([ac1, ac2])
chat = acfactory.get_accepted_chat(ac1, ac2)
msg1 = Message.new_empty(ac1, "webxdc")
msg1.set_text("message1")
msg1.set_file(data.get_path("webxdc/minimal.xdc"))
msg1 = chat.send_msg(msg1)
assert msg1.is_webxdc()
assert msg1.filename
msg2 = ac2._evtracker.wait_next_incoming_message()
assert msg2.is_webxdc()
lp.sec("ac2 sets download limit")
ac2.set_config("download_limit", "100")
assert msg1.send_status_update({"payload": base64.b64encode(os.urandom(50000))}, "some test data")
ac2_update = ac2._evtracker.wait_next_incoming_message()
assert ac2_update.download_state == dc.const.DC_DOWNLOAD_AVAILABLE
assert not msg2.get_status_updates()
ac2_update.download_full()
ac2._evtracker.get_matching("DC_EVENT_WEBXDC_STATUS_UPDATE")
assert msg2.get_status_updates()
# Get a event notifying that the message disappeared from the chat.
msgs_changed_event = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
assert msgs_changed_event.data1 == msg2.chat.id
assert msgs_changed_event.data2 == 0
def test_mvbox_sentbox_threads(acfactory, lp):
lp.sec("ac1: start with mvbox thread")
ac1 = acfactory.new_online_configuring_account(mvbox_move=True, sentbox_watch=True)
@@ -351,7 +409,42 @@ def test_move_works(acfactory):
# Message is downloaded
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > dc.const.DC_CHAT_ID_LAST_SPECIAL
def test_move_avoids_loop(acfactory):
"""Test that the message is only moved once.
This is to avoid busy loop if moved message reappears in the Inbox
or some scanned folder later.
For example, this happens on servers that alias `INBOX.DeltaChat` to `DeltaChat` folder,
so the message moved to `DeltaChat` appears as a new message in the `INBOX.DeltaChat` folder.
We do not want to move this message from `INBOX.DeltaChat` to `DeltaChat` again.
"""
ac1 = acfactory.new_online_configuring_account()
ac2 = acfactory.new_online_configuring_account(mvbox_move=True)
acfactory.bring_accounts_online()
ac1_chat = acfactory.get_accepted_chat(ac1, ac2)
ac1_chat.send_text("Message 1")
# Message is moved to the DeltaChat folder and downloaded.
ac2_msg1 = ac2._evtracker.wait_next_incoming_message()
assert ac2_msg1.text == "Message 1"
# Move the message to the INBOX again.
ac2.direct_imap.select_folder("DeltaChat")
ac2.direct_imap.conn.move(["*"], "INBOX")
ac1_chat.send_text("Message 2")
ac2_msg2 = ac2._evtracker.wait_next_incoming_message()
assert ac2_msg2.text == "Message 2"
# Check that Message 1 is still in the INBOX folder
# and Message 2 is in the DeltaChat folder.
ac2.direct_imap.select_folder("INBOX")
assert len(ac2.direct_imap.get_all_messages()) == 1
ac2.direct_imap.select_folder("DeltaChat")
assert len(ac2.direct_imap.get_all_messages()) == 1
def test_move_works_on_self_sent(acfactory):
@@ -396,9 +489,12 @@ def test_forward_messages(acfactory, lp):
lp.sec("ac2: check new chat has a forwarded message")
assert chat3.is_promoted()
messages = chat3.get_messages()
assert len(messages) == 1
msg = messages[-1]
assert msg.is_forwarded()
ac2.delete_messages(messages)
ev = ac2._evtracker.get_matching("DC_EVENT_MSG_DELETED")
assert ev.data2 == messages[0].id
assert not chat3.get_messages()
@@ -531,8 +627,8 @@ def test_send_and_receive_message_markseen(acfactory, lp):
lp.step("1")
for _i in range(2):
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL
assert ev.data1 > dc.const.DC_CHAT_ID_LAST_SPECIAL
assert ev.data2 > dc.const.DC_MSG_ID_LAST_SPECIAL
lp.step("2")
# Check that ac1 marks the read receipt as read.
@@ -703,7 +799,7 @@ def test_mdn_asymmetric(acfactory, lp):
assert len(chat.get_messages()) == 1
# Wait for the message to be marked as seen on IMAP.
ac1._evtracker.get_info_contains("Marked messages 1 in folder DeltaChat as seen.")
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
# MDN is received even though MDNs are already disabled
assert msg_out.is_out_mdn_received()
@@ -1204,7 +1300,7 @@ def test_quote_encrypted(acfactory, lp):
assert msg_in.is_encrypted() == quoted_msg.is_encrypted()
def test_quote_attachment(tmpdir, acfactory, lp):
def test_quote_attachment(tmp_path, acfactory, lp):
"""Test that replies with an attachment and a quote are received correctly."""
ac1, ac2 = acfactory.get_online_accounts(2)
@@ -1219,15 +1315,14 @@ def test_quote_attachment(tmpdir, acfactory, lp):
assert received_message.text == "hi"
basename = "attachment.txt"
p = os.path.join(tmpdir.strpath, basename)
with open(p, "w") as f:
f.write("data to send")
p = tmp_path / basename
p.write_text("data to send")
lp.sec("ac2 sends a reply to ac1")
chat2 = received_message.create_chat()
reply = Message.new_empty(ac2, "file")
reply.set_text("message reply")
reply.set_file(p)
reply.set_file(str(p))
reply.quote = received_message
chat2.send_msg(reply)
@@ -1334,7 +1429,7 @@ def test_send_and_receive_image(acfactory, lp, data):
assert m == msg_in
def test_reaction_to_partially_fetched_msg(acfactory, lp, tmpdir):
def test_reaction_to_partially_fetched_msg(acfactory, lp, tmp_path):
"""See https://github.com/deltachat/deltachat-core-rust/issues/3688 "Partially downloaded
messages are received out of order".
@@ -1369,10 +1464,9 @@ def test_reaction_to_partially_fetched_msg(acfactory, lp, tmpdir):
lp.sec("sending small+large messages from ac1 to ac2")
msgs = []
msgs.append(chat.send_text("hi"))
path = tmpdir.join("large")
with open(path, "wb") as fout:
fout.write(os.urandom(download_limit + 1))
msgs.append(chat.send_file(path.strpath))
path = tmp_path / "large"
path.write_bytes(os.urandom(download_limit + 1))
msgs.append(chat.send_file(str(path)))
lp.sec("sending a reaction to the large message from ac1 to ac2")
react_str = "\N{THUMBS UP SIGN}"
@@ -1385,7 +1479,7 @@ def test_reaction_to_partially_fetched_msg(acfactory, lp, tmpdir):
lp.sec("wait for ac2 to receive a reaction")
msg2 = ac2._evtracker.wait_next_reactions_changed()
assert msg2.get_sender_contact().addr == ac1_addr
assert msg2.download_state == const.DC_DOWNLOAD_AVAILABLE
assert msg2.download_state == dc.const.DC_DOWNLOAD_AVAILABLE
assert reactions_queue.get() == msg2
reactions = msg2.get_reactions()
contacts = reactions.get_contacts()
@@ -1431,7 +1525,7 @@ def test_reactions_for_a_reordering_move(acfactory, lp):
assert reactions.get_by_contact(contacts[0]) == react_str
def test_import_export_online_all(acfactory, tmpdir, data, lp):
def test_import_export_online_all(acfactory, tmp_path, data, lp):
(ac1,) = acfactory.get_online_accounts(1)
lp.sec("create some chat content")
@@ -1443,10 +1537,10 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
chat1.send_image(original_image_path)
# Add another 100KB file that ensures that the progress is smooth enough
path = tmpdir.join("attachment.txt")
with open(path, "w") as file:
path = tmp_path / "attachment.txt"
with path.open("w") as file:
file.truncate(100000)
chat1.send_file(path.strpath)
chat1.send_file(str(path))
def assert_account_is_proper(ac):
contacts = ac.get_contacts(query="some1")
@@ -1464,12 +1558,13 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
assert_account_is_proper(ac1)
backupdir = tmpdir.mkdir("backup")
backupdir = tmp_path / "backup"
backupdir.mkdir()
lp.sec(f"export all to {backupdir}")
with ac1.temp_plugin(ImexTracker()) as imex_tracker:
ac1.stop_io()
ac1.imex(backupdir.strpath, const.DC_IMEX_EXPORT_BACKUP)
ac1.imex(str(backupdir), dc.const.DC_IMEX_EXPORT_BACKUP)
# check progress events for export
assert imex_tracker.wait_progress(1, progress_upper_limit=249)
@@ -1487,7 +1582,7 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
ac2 = acfactory.get_unconfigured_account()
lp.sec("get latest backup file")
path2 = ac2.get_latest_backupfile(backupdir.strpath)
path2 = ac2.get_latest_backupfile(str(backupdir))
assert path2 == path
lp.sec("import backup and check it's proper")
@@ -1505,10 +1600,10 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
lp.sec(f"Second-time export all to {backupdir}")
ac1.stop_io()
path2 = ac1.export_all(backupdir.strpath)
path2 = ac1.export_all(str(backupdir))
assert os.path.exists(path2)
assert path2 != path
assert ac2.get_latest_backupfile(backupdir.strpath) == path2
assert ac2.get_latest_backupfile(str(backupdir)) == path2
def test_ac_setup_message(acfactory, lp):
@@ -1584,6 +1679,69 @@ def test_qr_join_chat(acfactory, lp):
ac1._evtracker.wait_securejoin_inviter_progress(1000)
def test_qr_new_group_unblocked(acfactory, lp):
"""Regression test for a bug intoduced in core v1.113.0.
ac2 scans a verified group QR code created by ac1.
This results in creation of a blocked 1:1 chat with ac1 on ac2,
but ac1 contact is not blocked on ac2.
Then ac1 creates a group, adds ac2 there and promotes it by sending a message.
ac2 should receive a message and create a contact request for the group.
Due to a bug previously ac2 created a blocked group.
"""
ac1, ac2 = acfactory.get_online_accounts(2)
ac1_chat = ac1.create_group_chat("Group for joining", verified=True)
qr = ac1_chat.get_join_qr()
ac2.qr_join_chat(qr)
ac1._evtracker.wait_securejoin_inviter_progress(1000)
ac1_new_chat = ac1.create_group_chat("Another group")
ac1_new_chat.add_contact(ac2)
ac1_new_chat.send_text("Hello!")
# Receive "Member added" message.
ac2._evtracker.wait_next_incoming_message()
# Receive "Hello!" message.
ac2_msg = ac2._evtracker.wait_next_incoming_message()
assert ac2_msg.text == "Hello!"
assert ac2_msg.chat.is_contact_request()
def test_qr_email_capitalization(acfactory, lp):
"""Regression test for a bug
that resulted in failure to propagate verification via gossip in a verified group
when the database already contained the contact with a different email address capitalization.
"""
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
# ac1 adds ac2 as a contact with an email address in uppercase.
ac2_addr_uppercase = ac2.get_config("addr").upper()
lp.sec(f"ac1 creates a contact for ac2 ({ac2_addr_uppercase})")
ac1.create_contact(ac2_addr_uppercase)
lp.sec("ac3 creates a verified group with a QR code")
chat = ac3.create_group_chat("hello", verified=True)
qr = chat.get_join_qr()
lp.sec("ac1 joins a verified group via a QR code")
ac1_chat = ac1.qr_join_chat(qr)
msg = ac1._evtracker.wait_next_incoming_message()
assert msg.text == "Member Me ({}) added by {}.".format(ac1.get_config("addr"), ac3.get_config("addr"))
assert len(ac1_chat.get_contacts()) == 2
lp.sec("ac2 joins a verified group via a QR code")
ac2.qr_join_chat(qr)
ac1._evtracker.wait_next_incoming_message()
# ac1 should see both ac3 and ac2 as verified.
assert len(ac1_chat.get_contacts()) == 3
for contact in ac1_chat.get_contacts():
assert contact.is_verified()
def test_set_get_contact_avatar(acfactory, data, lp):
lp.sec("configuring ac1 and ac2")
ac1, ac2 = acfactory.get_online_accounts(2)
@@ -1800,15 +1958,15 @@ def test_connectivity(acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
ac1.set_config("scan_all_folders_debounce_secs", "0")
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTED)
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTED)
lp.sec("Test stop_io() and start_io()")
ac1.stop_io()
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_NOT_CONNECTED)
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
ac1.start_io()
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTING)
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_CONNECTING, const.DC_CONNECTIVITY_CONNECTED)
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTING)
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_CONNECTING, dc.const.DC_CONNECTIVITY_CONNECTED)
lp.sec(
"Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, "
@@ -1829,8 +1987,8 @@ def test_connectivity(acfactory, lp):
ac2.create_chat(ac1).send_text("Hi 2")
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_CONNECTED, const.DC_CONNECTIVITY_WORKING)
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_WORKING, const.DC_CONNECTIVITY_CONNECTED)
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_CONNECTED, dc.const.DC_CONNECTIVITY_WORKING)
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_WORKING, dc.const.DC_CONNECTIVITY_CONNECTED)
msgs = ac1.create_chat(ac2).get_messages()
assert len(msgs) == 2
@@ -1840,7 +1998,7 @@ def test_connectivity(acfactory, lp):
ac1.maybe_network()
while 1:
assert ac1.get_connectivity() == const.DC_CONNECTIVITY_CONNECTED
assert ac1.get_connectivity() == dc.const.DC_CONNECTIVITY_CONNECTED
if ac1.all_work_done():
break
ac1._evtracker.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
@@ -1855,7 +2013,7 @@ def test_connectivity(acfactory, lp):
ac1.maybe_network()
while 1:
assert ac1.get_connectivity() == const.DC_CONNECTIVITY_CONNECTED
assert ac1.get_connectivity() == dc.const.DC_CONNECTIVITY_CONNECTED
if ac1.all_work_done():
break
ac1._evtracker.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
@@ -1864,10 +2022,10 @@ def test_connectivity(acfactory, lp):
ac1.set_config("configured_mail_pw", "abc")
ac1.stop_io()
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_NOT_CONNECTED)
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
ac1.start_io()
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTING)
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_NOT_CONNECTED)
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTING)
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
def test_fetch_deleted_msg(acfactory, lp):
@@ -2350,9 +2508,9 @@ def test_archived_muted_chat(acfactory, lp):
lp.sec("wait for ac2 to receive DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK")
while 1:
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
if ev.data1 == const.DC_CHAT_ID_ARCHIVED_LINK:
if ev.data1 == dc.const.DC_CHAT_ID_ARCHIVED_LINK:
assert ev.data2 == 0
archive = ac2.get_chat_by_id(const.DC_CHAT_ID_ARCHIVED_LINK)
archive = ac2.get_chat_by_id(dc.const.DC_CHAT_ID_ARCHIVED_LINK)
assert archive.count_fresh_messages() == 1
assert chat2.count_fresh_messages() == 1
break

View File

@@ -30,28 +30,28 @@ def wait_msgs_changed(account, msgs_list):
class TestOnlineInCreation:
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
def test_increation_not_blobdir(self, tmp_path, acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
chat = ac1.create_chat(ac2)
lp.sec("Creating in-creation file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
src = tmpdir.join("file.txt").ensure(file=1)
assert str(tmp_path) != ac1.get_blobdir()
src = tmp_path / "file.txt"
src.touch()
with pytest.raises(Exception):
chat.prepare_message_file(src.strpath)
chat.prepare_message_file(str(src))
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
def test_no_increation_copies_to_blobdir(self, tmp_path, acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2)
chat = ac1.create_chat(ac2)
lp.sec("Creating file outside of blobdir")
assert tmpdir.strpath != ac1.get_blobdir()
src = tmpdir.join("file.txt")
src.write("hello there\n")
chat.send_file(src.strpath)
blob_src = os.path.join(ac1.get_blobdir(), "file.txt")
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
assert str(tmp_path) != ac1.get_blobdir()
src = tmp_path / "file.txt"
src.write_text("hello there\n")
msg = chat.send_file(str(src))
assert msg.filename.startswith(os.path.join(ac1.get_blobdir(), "file"))
assert msg.filename.endswith(".txt")
def test_forward_increation(self, acfactory, data, lp):
ac1, ac2 = acfactory.get_online_accounts(2)

View File

@@ -4,12 +4,11 @@ from datetime import datetime, timedelta, timezone
import pytest
from deltachat import Account, const
import deltachat as dc
from deltachat.capi import ffi, lib
from deltachat.cutil import iter_array
from deltachat.hookspec import account_hookimpl
from deltachat.message import Message
from deltachat.tracker import ImexFailed
from deltachat import Account, account_hookimpl, Message
@pytest.mark.parametrize(
@@ -52,26 +51,25 @@ def test_parse_system_add_remove(msgtext, res):
class TestOfflineAccountBasic:
def test_wrong_db(self, tmpdir):
p = tmpdir.join("hello.db")
p.write("123")
account = Account(p.strpath)
def test_wrong_db(self, tmp_path):
p = tmp_path / "hello.db"
p.write_text("123")
account = Account(str(p))
assert not account.is_open()
def test_os_name(self, tmpdir):
p = tmpdir.join("hello.db")
def test_os_name(self, tmp_path):
p = tmp_path / "hello.db"
# we can't easily test if os_name is used in X-Mailer
# outgoing messages without a full Online test
# but we at least check Account accepts the arg
ac1 = Account(p.strpath, os_name="solarpunk")
ac1 = Account(str(p), os_name="solarpunk")
ac1.get_info()
def test_preconfigure_keypair(self, acfactory, data):
ac = acfactory.get_unconfigured_account()
alice_public = data.read_path("key/alice-public.asc")
alice_secret = data.read_path("key/alice-secret.asc")
assert alice_public and alice_secret
ac._preconfigure_keypair("alice@example.org", alice_public, alice_secret)
assert alice_secret
ac._preconfigure_keypair("alice@example.org", alice_secret)
def test_getinfo(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
@@ -299,13 +297,13 @@ class TestOfflineChat:
assert not d["draft"] if chat.get_draft() is None else chat.get_draft()
def test_group_chat_creation_with_translation(self, ac1):
ac1.set_stock_translation(const.DC_STR_GROUP_NAME_CHANGED_BY_YOU, "abc %1$s xyz %2$s")
ac1.set_stock_translation(dc.const.DC_STR_GROUP_NAME_CHANGED_BY_YOU, "abc %1$s xyz %2$s")
ac1._evtracker.consume_events()
with pytest.raises(ValueError):
ac1.set_stock_translation(const.DC_STR_FILE, "xyz %1$s")
ac1.set_stock_translation(dc.const.DC_STR_FILE, "xyz %1$s")
ac1._evtracker.get_matching("DC_EVENT_WARNING")
with pytest.raises(ValueError):
ac1.set_stock_translation(const.DC_STR_CONTACT_NOT_VERIFIED, "xyz %2$s")
ac1.set_stock_translation(dc.const.DC_STR_CONTACT_NOT_VERIFIED, "xyz %2$s")
ac1._evtracker.get_matching("DC_EVENT_WARNING")
with pytest.raises(ValueError):
ac1.set_stock_translation(500, "xyz %1$s")
@@ -481,6 +479,19 @@ class TestOfflineChat:
contact2 = ac1.create_contact("display1 <x@example.org>", "real")
assert contact2.name == "real"
def test_send_lots_of_offline_msgs(self, acfactory):
ac1 = acfactory.get_pseudo_configured_account()
ac1.set_config("configured_mail_server", "example.org")
ac1.set_config("configured_mail_user", "example.org")
ac1.set_config("configured_mail_pw", "example.org")
ac1.set_config("configured_send_server", "example.org")
ac1.set_config("configured_send_user", "example.org")
ac1.set_config("configured_send_pw", "example.org")
ac1.start_io()
chat = ac1.create_contact("some1@example.org", name="some1").create_chat()
for i in range(50):
chat.send_text("hello")
def test_create_chat_simple(self, acfactory):
ac1 = acfactory.get_pseudo_configured_account()
contact1 = ac1.create_contact("some1@example.org", name="some1")
@@ -496,22 +507,22 @@ class TestOfflineChat:
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
def test_import_export_on_unencrypted_acct(self, acfactory, tmpdir):
backupdir = tmpdir.mkdir("backup")
def test_import_export_on_unencrypted_acct(self, acfactory, tmp_path):
backupdir = tmp_path / "backup"
backupdir.mkdir()
ac1 = acfactory.get_pseudo_configured_account()
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
# send a text message
msg = chat.send_text("msg1")
# send a binary file
bin = tmpdir.join("some.bin")
with bin.open("w") as f:
f.write("\00123" * 10000)
msg = chat.send_file(bin.strpath)
bin = tmp_path / "some.bin"
bin.write_bytes(b"\00123" * 10000)
msg = chat.send_file(str(bin))
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
assert not list(backupdir.iterdir())
ac1.stop_io()
path = ac1.export_all(backupdir.strpath)
path = ac1.export_all(str(backupdir))
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account()
ac2.import_all(path)
@@ -525,27 +536,27 @@ class TestOfflineChat:
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_import_export_on_encrypted_acct(self, acfactory, tmpdir):
def test_import_export_on_encrypted_acct(self, acfactory, tmp_path):
passphrase1 = "passphrase1"
passphrase2 = "passphrase2"
backupdir = tmpdir.mkdir("backup")
backupdir = tmp_path / "backup"
backupdir.mkdir()
ac1 = acfactory.get_pseudo_configured_account(passphrase=passphrase1)
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
# send a text message
msg = chat.send_text("msg1")
# send a binary file
bin = tmpdir.join("some.bin")
with bin.open("w") as f:
f.write("\00123" * 10000)
msg = chat.send_file(bin.strpath)
bin = tmp_path / "some.bin"
bin.write_bytes(b"\00123" * 10000)
msg = chat.send_file(str(bin))
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
assert not list(backupdir.iterdir())
ac1.stop_io()
path = ac1.export_all(backupdir.strpath)
path = ac1.export_all(str(backupdir))
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account(closed=True)
@@ -580,27 +591,27 @@ class TestOfflineChat:
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_import_export_with_passphrase(self, acfactory, tmpdir):
def test_import_export_with_passphrase(self, acfactory, tmp_path):
passphrase = "test_passphrase"
wrong_passphrase = "wrong_passprase"
backupdir = tmpdir.mkdir("backup")
backupdir = tmp_path / "backup"
backupdir.mkdir()
ac1 = acfactory.get_pseudo_configured_account()
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
# send a text message
msg = chat.send_text("msg1")
# send a binary file
bin = tmpdir.join("some.bin")
with bin.open("w") as f:
f.write("\00123" * 10000)
msg = chat.send_file(bin.strpath)
bin = tmp_path / "some.bin"
bin.write_bytes(b"\00123" * 10000)
msg = chat.send_file(str(bin))
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
assert not list(backupdir.iterdir())
ac1.stop_io()
path = ac1.export_all(backupdir.strpath, passphrase)
path = ac1.export_all(str(backupdir), passphrase)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account()
@@ -619,7 +630,7 @@ class TestOfflineChat:
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmpdir):
def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmp_path):
"""
Test that account passphrase isn't lost if backup failed to be imported.
See https://github.com/deltachat/deltachat-core-rust/issues/3379
@@ -627,24 +638,24 @@ class TestOfflineChat:
acct_passphrase = "passphrase1"
bak_passphrase = "passphrase2"
wrong_passphrase = "wrong_passprase"
backupdir = tmpdir.mkdir("backup")
backupdir = tmp_path / "backup"
backupdir.mkdir()
ac1 = acfactory.get_pseudo_configured_account()
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
# send a text message
msg = chat.send_text("msg1")
# send a binary file
bin = tmpdir.join("some.bin")
with bin.open("w") as f:
f.write("\00123" * 10000)
msg = chat.send_file(bin.strpath)
bin = tmp_path / "some.bin"
bin.write_bytes(b"\00123" * 10000)
msg = chat.send_file(str(bin))
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
assert not list(backupdir.iterdir())
ac1.stop_io()
path = ac1.export_all(backupdir.strpath, bak_passphrase)
path = ac1.export_all(str(backupdir), bak_passphrase)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account(closed=True)
@@ -805,7 +816,7 @@ class TestOfflineChat:
lp.sec("check message count of only system messages (without daymarkers)")
dc_array = ffi.gc(
lib.dc_get_chat_msgs(ac1._dc_context, chat.id, const.DC_GCM_INFO_ONLY, 0),
lib.dc_get_chat_msgs(ac1._dc_context, chat.id, dc.const.DC_GCM_INFO_ONLY, 0),
lib.dc_array_unref,
)
assert len(list(iter_array(dc_array, lambda x: x))) == 2

View File

@@ -1,7 +1,8 @@
import os
import json
from queue import Queue
from deltachat import capi, const, cutil, register_global_plugin
import deltachat as dc
from deltachat import capi, cutil, register_global_plugin
from deltachat.capi import ffi, lib
from deltachat.hookspec import global_hookimpl
from deltachat.testplugin import (
@@ -9,6 +10,7 @@ from deltachat.testplugin import (
create_dict_from_files_in_path,
write_dict_to_dir,
)
from deltachat.cutil import from_optional_dc_charpointer
# from deltachat.account import EventLogger
@@ -64,16 +66,17 @@ class TestACSetup:
assert pc._account2state[ac1] == pc.IDLEREADY
assert pc._account2state[ac2] == pc.IDLEREADY
def test_store_and_retrieve_configured_account_cache(self, acfactory, tmpdir):
def test_store_and_retrieve_configured_account_cache(self, acfactory, tmp_path):
ac1 = acfactory.get_pseudo_configured_account()
holder = acfactory._acsetup.testprocess
assert holder.cache_maybe_store_configured_db_files(ac1)
assert not holder.cache_maybe_store_configured_db_files(ac1)
acdir = tmpdir.mkdir("newaccount")
acdir = tmp_path / "newaccount"
acdir.mkdir()
addr = ac1.get_config("addr")
target_db_path = acdir.join("db").strpath
assert holder.cache_maybe_retrieve_configured_db_files(addr, target_db_path)
assert len(os.listdir(acdir)) >= 2
target_db_path = acdir / "db"
assert holder.cache_maybe_retrieve_configured_db_files(addr, str(target_db_path))
assert sum(1 for _ in acdir.iterdir()) >= 2
def test_liveconfig_caching(acfactory, monkeypatch):
@@ -111,40 +114,40 @@ def test_dc_close_events(acfactory):
shutdowns.get(timeout=2)
def test_wrong_db(tmpdir):
p = tmpdir.join("hello.db")
def test_wrong_db(tmp_path):
p = tmp_path / "hello.db"
# write an invalid database file
p.write("x123" * 10)
p.write_bytes(b"x123" * 10)
context = lib.dc_context_new(ffi.NULL, p.strpath.encode("ascii"), ffi.NULL)
context = lib.dc_context_new(ffi.NULL, str(p).encode("ascii"), ffi.NULL)
assert not lib.dc_context_is_open(context)
def test_empty_blobdir(tmpdir):
db_fname = tmpdir.join("hello.db")
def test_empty_blobdir(tmp_path):
db_fname = tmp_path / "hello.db"
# Apparently some client code expects this to be the same as passing NULL.
ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), b""),
lib.dc_context_new(ffi.NULL, str(db_fname).encode("ascii"), b""),
lib.dc_context_unref,
)
assert ctx != ffi.NULL
def test_event_defines():
assert const.DC_EVENT_INFO == 100
assert const.DC_CONTACT_ID_SELF
assert dc.const.DC_EVENT_INFO == 100
assert dc.const.DC_CONTACT_ID_SELF
def test_sig():
sig = capi.lib.dc_event_has_string_data
assert not sig(const.DC_EVENT_MSGS_CHANGED)
assert sig(const.DC_EVENT_INFO)
assert sig(const.DC_EVENT_WARNING)
assert sig(const.DC_EVENT_ERROR)
assert sig(const.DC_EVENT_SMTP_CONNECTED)
assert sig(const.DC_EVENT_IMAP_CONNECTED)
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT)
assert sig(const.DC_EVENT_IMEX_FILE_WRITTEN)
assert not sig(dc.const.DC_EVENT_MSGS_CHANGED)
assert sig(dc.const.DC_EVENT_INFO)
assert sig(dc.const.DC_EVENT_WARNING)
assert sig(dc.const.DC_EVENT_ERROR)
assert sig(dc.const.DC_EVENT_SMTP_CONNECTED)
assert sig(dc.const.DC_EVENT_IMAP_CONNECTED)
assert sig(dc.const.DC_EVENT_SMTP_MESSAGE_SENT)
assert sig(dc.const.DC_EVENT_IMEX_FILE_WRITTEN)
def test_markseen_invalid_message_ids(acfactory):
@@ -173,10 +176,10 @@ def test_provider_info_none():
assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
def test_get_info_open(tmpdir):
db_fname = tmpdir.join("test.db")
def test_get_info_open(tmp_path):
db_fname = tmp_path / "test.db"
ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL),
lib.dc_context_new(ffi.NULL, str(db_fname).encode("ascii"), ffi.NULL),
lib.dc_context_unref,
)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
@@ -215,3 +218,35 @@ def test_logged_ac_process_ffi_failure(acfactory):
assert "ac_process_ffi_event" in res
assert "ZeroDivisionError" in res
assert "Traceback" in res
def test_jsonrpc_blocking_call(tmp_path):
accounts_fname = tmp_path / "accounts"
accounts = ffi.gc(
lib.dc_accounts_new(ffi.NULL, str(accounts_fname).encode("ascii")),
lib.dc_accounts_unref,
)
jsonrpc = ffi.gc(lib.dc_jsonrpc_init(accounts), lib.dc_jsonrpc_unref)
res = json.loads(
from_optional_dc_charpointer(
lib.dc_jsonrpc_blocking_call(
jsonrpc,
json.dumps(
{"jsonrpc": "2.0", "method": "check_email_validity", "params": ["alice@example.org"], "id": "123"},
).encode("utf-8"),
),
),
)
assert res == {"jsonrpc": "2.0", "id": "123", "result": True}
res = json.loads(
from_optional_dc_charpointer(
lib.dc_jsonrpc_blocking_call(
jsonrpc,
json.dumps(
{"jsonrpc": "2.0", "method": "check_email_validity", "params": ["alice"], "id": "456"},
).encode("utf-8"),
),
),
)
assert res == {"jsonrpc": "2.0", "id": "456", "result": False}

View File

@@ -1 +1 @@
2023-04-24
2023-10-04

View File

@@ -1,3 +0,0 @@
pre-release-commit-message = "chore({{crate_name}}): release {{version}}"
pro-release-commit-message = "chore({{crate_name}}): starting development cycle for {{next_version}}"
no-dev-version = true

View File

@@ -18,6 +18,10 @@ and an own build machine.
- `remote_tests_rust.sh` rsyncs to the build machine and runs
`run-rust-test.sh` remotely on the build machine.
- `make-python-testenv.sh` creates or updates local python test development environment.
Reusing the same environment is faster than running `run-python-test.sh` which always
recreates environment from scratch and runs additional lints.
- `run-doxygen.sh` generates C-docs which are then uploaded to https://c.delta.chat/
- `run_all.sh` builds Python wheels

View File

@@ -12,10 +12,15 @@ where `secret.yml` contains the following secrets:
```
c.delta.chat:
private_key: |
-----BEGIN RSA PRIVATE KEY-----
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
-----END OPENSSH PRIVATE KEY-----
devpi:
login: dc
password: ...
```
Secrets can be read from the password manager:
```
fly -t b1 set-pipeline -c docs_wheels.yml -p docs_wheels -l <(pass show delta/b1.delta.chat/secret.yml)
```

View File

@@ -153,11 +153,13 @@ jobs:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*manylinux201*
apt-get install -y --no-install-recommends python3-pip python3-setuptools python3-venv
python3 -m venv env
env/bin/pip install --upgrade pip
env/bin/pip install devpi
env/bin/devpi use https://m.devpi.net/dc/master
env/bin/devpi login ((devpi.login)) --password ((devpi.password))
env/bin/devpi upload py-wheels/*manylinux201*
- name: python-aarch64
plan:
@@ -223,11 +225,13 @@ jobs:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*manylinux201*
apt-get install -y --no-install-recommends python3-pip python3-setuptools python3-venv
python3 -m venv env
env/bin/pip install --upgrade pip
env/bin/pip install devpi
env/bin/devpi use https://m.devpi.net/dc/master
env/bin/devpi login ((devpi.login)) --password ((devpi.password))
env/bin/devpi upload py-wheels/*manylinux201*
- name: python-musl-x86_64
plan:
@@ -293,11 +297,13 @@ jobs:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*musllinux_1_1_x86_64*
apt-get install -y --no-install-recommends python3-pip python3-setuptools python3-venv
python3 -m venv env
env/bin/pip install --upgrade pip
env/bin/pip install devpi
env/bin/devpi use https://m.devpi.net/dc/master
env/bin/devpi login ((devpi.login)) --password ((devpi.password))
env/bin/devpi upload py-wheels/*musllinux_1_1_x86_64*
- name: python-musl-aarch64
plan:
@@ -363,8 +369,10 @@ jobs:
- -ec
- |
apt-get update -y
apt-get install -y --no-install-recommends python3-pip python3-setuptools
pip3 install devpi
devpi use https://m.devpi.net/dc/master
devpi login ((devpi.login)) --password ((devpi.password))
devpi upload py-wheels/*musllinux_1_1_aarch64*
apt-get install -y --no-install-recommends python3-pip python3-setuptools python3-venv
python3 -m venv env
env/bin/pip install --upgrade pip
env/bin/pip install devpi
env/bin/devpi use https://m.devpi.net/dc/master
env/bin/devpi login ((devpi.login)) --password ((devpi.password))
env/bin/devpi upload py-wheels/*musllinux_1_1_aarch64*

View File

@@ -7,7 +7,7 @@ set -euo pipefail
#
# Avoid using rustup here as it depends on reading /proc/self/exe and
# has problems running under QEMU.
RUST_VERSION=1.68.0
RUST_VERSION=1.72.0
ARCH="$(uname -m)"
test -f "/lib/libc.musl-$ARCH.so.1" && LIBC=musl || LIBC=gnu

View File

@@ -44,7 +44,7 @@ def file2url(f):
def process_opt(data):
if not "opt" in data:
return "Default::default()"
return "ProviderOptions::new()"
opt = "ProviderOptions {\n"
opt_data = data.get("opt", "")
for key in opt_data:
@@ -54,7 +54,7 @@ def process_opt(data):
if value in {"True", "False"}:
value = value.lower()
opt += " " + key + ": " + value + ",\n"
opt += " ..Default::default()\n"
opt += " ..ProviderOptions::new()\n"
opt += " }"
return opt
@@ -62,7 +62,7 @@ def process_opt(data):
def process_config_defaults(data):
if not "config_defaults" in data:
return "None"
defaults = "Some(vec![\n"
defaults = "Some(&[\n"
config_defaults = data.get("config_defaults", "")
for key in config_defaults:
value = str(config_defaults[key])
@@ -96,11 +96,11 @@ def process_data(data, file):
raise TypeError("domain used twice: " + domain)
domains_set.add(domain)
domains += ' ("' + domain + '", &*' + file2varname(file) + "),\n"
domains += ' ("' + domain + '", &' + file2varname(file) + "),\n"
comment += domain + ", "
ids = ""
ids += ' ("' + file2id(file) + '", &*' + file2varname(file) + "),\n"
ids += ' ("' + file2id(file) + '", &' + file2varname(file) + "),\n"
server = ""
has_imap = False
@@ -155,18 +155,18 @@ def process_data(data, file):
provider += (
"static "
+ file2varname(file)
+ ": Lazy<Provider> = Lazy::new(|| Provider {\n"
+ ": Provider = Provider {\n"
)
provider += ' id: "' + file2id(file) + '",\n'
provider += " status: Status::" + status.capitalize() + ",\n"
provider += ' before_login_hint: "' + before_login_hint + '",\n'
provider += ' after_login_hint: "' + after_login_hint + '",\n'
provider += ' overview_page: "' + file2url(file) + '",\n'
provider += " server: vec![\n" + server + " ],\n"
provider += " server: &[\n" + server + " ],\n"
provider += " opt: " + opt + ",\n"
provider += " config_defaults: " + config_defaults + ",\n"
provider += " oauth2_authorizer: " + oauth2 + ",\n"
provider += "});\n\n"
provider += "};\n\n"
else:
raise TypeError("SMTP and IMAP must be specified together or left out both")

View File

@@ -1,2 +1,6 @@
#!/bin/sh
# Update package cache without changing the lockfile.
cargo update --dry-run
cargo deny --workspace --all-features check -D warnings

21
scripts/make-python-testenv.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
#
# Script to create or update a python development environment.
# It rebuilds the core and bindings as needed.
#
# After running the script, you can either
# run `pytest` directly with `env/bin/pytest python/`
# or activate the environment with `. env/bin/activacte`
# and run `pytest` from there.
set -euo pipefail
export DCC_RS_TARGET=debug
export DCC_RS_DEV="$PWD"
cargo build -p deltachat_ffi --features jsonrpc
if test -d env; then
env/bin/pip install -e python --force-reinstall
else
tox -e py --devenv env
env/bin/pip install --upgrade pip
fi

View File

@@ -31,7 +31,7 @@ unset DCC_NEW_TMP_EMAIL
# Try to build wheels for a range of interpreters, but don't fail if they are not available.
# E.g. musllinux_1_1 does not have PyPy interpreters as of 2022-07-10
tox --workdir "$TOXWORKDIR" -e py37,py38,py39,py310,py311,pypy37,pypy38,pypy39 --skip-missing-interpreters true
tox --workdir "$TOXWORKDIR" -e py37,py38,py39,py310,py311,py312,pypy37,pypy38,pypy39,pypy310 --skip-missing-interpreters true
auditwheel repair "$TOXWORKDIR"/wheelhouse/deltachat* -w "$TOXWORKDIR/wheelhouse"

View File

@@ -7,6 +7,7 @@ import pathlib
import re
import subprocess
from argparse import ArgumentParser
from pathlib import Path
rex = re.compile(r'version = "(\S+)"')
@@ -95,22 +96,14 @@ def main():
today = datetime.date.today().isoformat()
if "alpha" not in newversion:
changelog_name = "CHANGELOG.md"
changelog_tmpname = changelog_name + ".tmp"
changelog_tmp = open(changelog_tmpname, "w")
found = False
for line in open(changelog_name):
## 1.25.0
if line == f"## [{newversion}]\n":
line = f"## [{newversion}] - {today}\n"
for line in Path("CHANGELOG.md").open():
if line == f"## [{newversion}] - {today}\n":
found = True
changelog_tmp.write(line)
if not found:
raise SystemExit(
f"{changelog_name} contains no entry for version: {newversion}"
)
changelog_tmp.close()
os.rename(changelog_tmpname, changelog_name)
for toml_filename in toml_list:
replace_toml_version(toml_filename, newversion)
@@ -128,11 +121,15 @@ def main():
subprocess.call(["git", "add", "-u"])
# subprocess.call(["cargo", "update", "-p", "deltachat"])
print("after commit, on master make sure to: ")
print("")
print("After commit, make sure to:")
print()
print(f" git tag -a v{newversion}")
print(f" git push origin v{newversion}")
print("")
print(f" gh release create v{newversion} -n ''")
print()
print("Merge release branch into `master` if the release")
print("is made on a stable branch.")
print()
if __name__ == "__main__":

28
scripts/zig-musl-check.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
#
# Run `cargo check` with musl libc.
# This requires `zig` to compile vendored openssl.
set -x
set -e
unset RUSTFLAGS
# Pin Rust version to avoid uncontrolled changes in the compiler and linker flags.
export RUSTUP_TOOLCHAIN=1.72.0
ZIG_VERSION=0.11.0
# Download Zig
rm -fr "$ZIG_VERSION" "zig-linux-x86_64-$ZIG_VERSION.tar.xz"
wget "https://ziglang.org/builds/zig-linux-x86_64-$ZIG_VERSION.tar.xz"
tar xf "zig-linux-x86_64-$ZIG_VERSION.tar.xz"
export PATH="$PWD/zig-linux-x86_64-$ZIG_VERSION:$PATH"
rustup target add x86_64-unknown-linux-musl
CC="$PWD/scripts/zig-cc" \
TARGET_CC="$PWD/scripts/zig-cc" \
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER="$PWD/scripts/zig-cc" \
LD="$PWD/scripts/zig-cc" \
ZIG_TARGET="x86_64-linux-musl" \
cargo check --release --target x86_64-unknown-linux-musl -p deltachat_ffi --features jsonrpc

View File

@@ -8,9 +8,9 @@ set -e
unset RUSTFLAGS
# Pin Rust version to avoid uncontrolled changes in the compiler and linker flags.
export RUSTUP_TOOLCHAIN=1.68.1
export RUSTUP_TOOLCHAIN=1.72.0
ZIG_VERSION=0.11.0-dev.2213+515e1c93e
ZIG_VERSION=0.11.0
# Download Zig
rm -fr "$ZIG_VERSION" "zig-linux-x86_64-$ZIG_VERSION.tar.xz"

View File

@@ -296,10 +296,10 @@ impl Accounts {
}
/// Configuration file name.
pub const CONFIG_NAME: &str = "accounts.toml";
const CONFIG_NAME: &str = "accounts.toml";
/// Database file name.
pub const DB_NAME: &str = "dc.db";
const DB_NAME: &str = "dc.db";
/// Account manager configuration file.
#[derive(Debug, Clone, PartialEq)]

View File

@@ -357,7 +357,6 @@ mod tests {
use super::*;
use crate::aheader::EncryptPreference;
use crate::e2ee;
use crate::message;
use crate::mimeparser;
use crate::peerstate::Peerstate;
use crate::securejoin::get_securejoin_qr;
@@ -705,7 +704,7 @@ Authentication-Results: dkim=";
let received = tcm
.try_send_recv(&alice, &bob2, "My credit card number is 1234")
.await;
assert!(!received.text.as_ref().unwrap().contains("1234"));
assert!(!received.text.contains("1234"));
assert!(received.error.is_some());
tcm.section("Turns out bob2 wasn't an attacker at all, Bob just has a new phone and DKIM just stopped working.");
@@ -786,7 +785,7 @@ Authentication-Results: dkim=";
.insert_str(0, "List-Post: <mailto:deltachat-community.example.net>\n");
let rcvd = alice.recv_msg(&sent).await;
assert!(!rcvd.get_showpadlock());
assert_eq!(&rcvd.text.unwrap(), "hellooo in the mailinglist again");
assert_eq!(&rcvd.text, "hellooo in the mailinglist again");
Ok(())
}
@@ -825,7 +824,9 @@ Authentication-Results: dkim=";
// Disallowing keychanges is disabled for now:
// assert!(rcvd.error.unwrap().contains("DKIM failed"));
// The message info should contain a warning:
assert!(message::get_msg_info(&bob, rcvd.id)
assert!(rcvd
.id
.get_info(&bob)
.await
.unwrap()
.contains("KEYCHANGES NOT ALLOWED"));

View File

@@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};
use anyhow::{format_err, Context as _, Result};
use futures::StreamExt;
use image::{DynamicImage, ImageFormat, ImageOutputFormat};
use image::{DynamicImage, GenericImageView, ImageFormat, ImageOutputFormat};
use num_traits::FromPrimitive;
use tokio::io::AsyncWriteExt;
use tokio::{fs, io};
@@ -323,18 +323,35 @@ impl<'a> BlobObject<'a> {
MediaQuality::Worse => constants::WORSE_AVATAR_SIZE,
};
let maybe_sticker = &mut false;
let strict_limits = true;
// max_bytes is 20_000 bytes: Outlook servers don't allow headers larger than 32k.
// 32 / 4 * 3 = 24k if you account for base64 encoding. To be safe, we reduced this to 20k.
if let Some(new_name) =
self.recode_to_size(context, blob_abs, img_wh, 20_000, strict_limits)?
{
if let Some(new_name) = self.recode_to_size(
context,
blob_abs,
maybe_sticker,
img_wh,
20_000,
strict_limits,
)? {
self.name = new_name;
}
Ok(())
}
pub async fn recode_to_image_size(&mut self, context: &Context) -> Result<()> {
/// Recodes an image pointed by a [BlobObject] so that it fits into limits on the image width,
/// height and file size specified by the config.
///
/// On some platforms images are passed to the core as [`crate::message::Viewtype::Sticker`] in
/// which case `maybe_sticker` flag should be set. We recheck if an image is a true sticker
/// assuming that it must have at least one fully transparent corner, otherwise this flag is
/// reset.
pub async fn recode_to_image_size(
&mut self,
context: &Context,
maybe_sticker: &mut bool,
) -> Result<()> {
let blob_abs = self.to_abs_path();
let (img_wh, max_bytes) =
match MediaQuality::from_i32(context.get_config_int(Config::MediaQuality).await?)
@@ -347,9 +364,14 @@ impl<'a> BlobObject<'a> {
MediaQuality::Worse => (constants::WORSE_IMAGE_SIZE, constants::WORSE_IMAGE_BYTES),
};
let strict_limits = false;
if let Some(new_name) =
self.recode_to_size(context, blob_abs, img_wh, max_bytes, strict_limits)?
{
if let Some(new_name) = self.recode_to_size(
context,
blob_abs,
maybe_sticker,
img_wh,
max_bytes,
strict_limits,
)? {
self.name = new_name;
}
Ok(())
@@ -358,20 +380,37 @@ impl<'a> BlobObject<'a> {
/// If `!strict_limits`, then if `max_bytes` is exceeded, reduce the image to `img_wh` and just
/// proceed with the result.
fn recode_to_size(
&self,
&mut self,
context: &Context,
mut blob_abs: PathBuf,
maybe_sticker: &mut bool,
mut img_wh: u32,
max_bytes: usize,
strict_limits: bool,
) -> Result<Option<String>> {
tokio::task::block_in_place(move || {
let mut img = image::open(&blob_abs).context("image decode failure")?;
let mut no_exif = false;
let no_exif_ref = &mut no_exif;
let res = tokio::task::block_in_place(move || {
let (nr_bytes, exif) = self.metadata()?;
*no_exif_ref = exif.is_none();
let mut img = image::open(&blob_abs).context("image decode failure")?;
let orientation = exif.as_ref().map(|exif| exif_orientation(exif, context));
let mut encoded = Vec::new();
let mut changed_name = None;
if *maybe_sticker {
let x_max = img.width().saturating_sub(1);
let y_max = img.height().saturating_sub(1);
*maybe_sticker = img.in_bounds(x_max, y_max)
&& (img.get_pixel(0, 0).0[3] == 0
|| img.get_pixel(x_max, 0).0[3] == 0
|| img.get_pixel(0, y_max).0[3] == 0
|| img.get_pixel(x_max, y_max).0[3] == 0);
}
if *maybe_sticker && exif.is_none() {
return Ok(None);
}
img = match orientation {
Some(90) => img.rotate90(),
Some(180) => img.rotate180(),
@@ -469,7 +508,21 @@ impl<'a> BlobObject<'a> {
}
Ok(changed_name)
})
});
match res {
Ok(_) => res,
Err(err) => {
if !strict_limits && no_exif {
warn!(
context,
"Cannot recode image, using original data: {err:#}.",
);
Ok(None)
} else {
Err(err)
}
}
}
}
/// Returns image file size and Exif.
@@ -860,10 +913,18 @@ mod tests {
file.metadata().await.unwrap().len()
}
let blob = BlobObject::new_from_path(&t, &avatar_blob).await.unwrap();
let mut blob = BlobObject::new_from_path(&t, &avatar_blob).await.unwrap();
let maybe_sticker = &mut false;
let strict_limits = true;
blob.recode_to_size(&t, blob.to_abs_path(), 1000, 3000, strict_limits)
.unwrap();
blob.recode_to_size(
&t,
blob.to_abs_path(),
maybe_sticker,
1000,
3000,
strict_limits,
)
.unwrap();
assert!(file_size(&avatar_blob).await <= 3000);
assert!(file_size(&avatar_blob).await > 2000);
tokio::task::block_in_place(move || {
@@ -923,6 +984,7 @@ mod tests {
async fn test_recode_image_1() {
let bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
send_image_check_mediaquality(
Viewtype::Image,
Some("0"),
bytes,
"jpg",
@@ -936,6 +998,7 @@ mod tests {
.await
.unwrap();
send_image_check_mediaquality(
Viewtype::Image,
Some("1"),
bytes,
"jpg",
@@ -955,6 +1018,7 @@ mod tests {
// The "-rotated" files are rotated by 270 degrees using the Exif metadata
let bytes = include_bytes!("../test-data/image/rectangle2000x1800-rotated.jpg");
let img_rotated = send_image_check_mediaquality(
Viewtype::Image,
Some("0"),
bytes,
"jpg",
@@ -974,6 +1038,7 @@ mod tests {
let bytes = buf.into_inner();
let img_rotated = send_image_check_mediaquality(
Viewtype::Image,
Some("1"),
&bytes,
"jpg",
@@ -994,6 +1059,7 @@ mod tests {
let bytes = include_bytes!("../test-data/image/screenshot.png");
send_image_check_mediaquality(
Viewtype::Image,
Some("0"),
bytes,
"png",
@@ -1008,6 +1074,7 @@ mod tests {
.unwrap();
send_image_check_mediaquality(
Viewtype::Image,
Some("1"),
bytes,
"png",
@@ -1020,12 +1087,29 @@ mod tests {
)
.await
.unwrap();
// This will be sent as Image, see [`BlobObject::maybe_sticker`] for explanation.
send_image_check_mediaquality(
Viewtype::Sticker,
Some("0"),
bytes,
"png",
false, // no Exif
1920,
1080,
0,
1920,
1080,
)
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_recode_image_huge_jpg() {
let bytes = include_bytes!("../test-data/image/screenshot.jpg");
send_image_check_mediaquality(
Viewtype::Image,
Some("0"),
bytes,
"jpg",
@@ -1059,6 +1143,7 @@ mod tests {
#[allow(clippy::too_many_arguments)]
async fn send_image_check_mediaquality(
viewtype: Viewtype,
media_quality_config: Option<&str>,
bytes: &[u8],
extension: &str,
@@ -1090,7 +1175,7 @@ mod tests {
assert!(exif.is_none());
}
let mut msg = Message::new(Viewtype::Image);
let mut msg = Message::new(viewtype);
msg.set_file(file.to_str().unwrap(), None);
let chat = alice.create_chat(&bob).await;
let sent = alice.send_msg(chat.id, &mut msg).await;
@@ -1104,6 +1189,7 @@ mod tests {
);
let bob_msg = bob.recv_msg(&sent).await;
assert_eq!(bob_msg.get_viewtype(), Viewtype::Image);
assert_eq!(bob_msg.get_width() as u32, compressed_width);
assert_eq!(bob_msg.get_height() as u32, compressed_height);
let file = bob_msg.get_file(&bob).unwrap();

Some files were not shown because too many files have changed in this diff Show More