mirror of
https://github.com/chatmail/core.git
synced 2026-07-08 00:15:13 +03:00
Compare commits
47 Commits
1.93.0
...
webxdc_del
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a79815a13 | ||
|
|
6877f16b63 | ||
|
|
70979c55fa | ||
|
|
2e2fa95298 | ||
|
|
f506b6882c | ||
|
|
fb564aedb2 | ||
|
|
3271d509cc | ||
|
|
24d9345ea0 | ||
|
|
7cff681234 | ||
|
|
54b10106bd | ||
|
|
484aa54ed6 | ||
|
|
417bddfa16 | ||
|
|
1e4e799f2e | ||
|
|
33b18e3014 | ||
|
|
96ce8eb851 | ||
|
|
8de5e964e0 | ||
|
|
43150195d4 | ||
|
|
065c7af9a0 | ||
|
|
949370ad63 | ||
|
|
4b91a88bc9 | ||
|
|
d04a0c8f2f | ||
|
|
6f6618d46f | ||
|
|
90eb39439a | ||
|
|
9813265ef4 | ||
|
|
8acbf9babf | ||
|
|
97d7c20549 | ||
|
|
4eddd7f616 | ||
|
|
217217bd2b | ||
|
|
37eeb55a5b | ||
|
|
d32da8e4d5 | ||
|
|
da3052fdc2 | ||
|
|
97982ef93a | ||
|
|
25bff21edd | ||
|
|
b2b22c8b85 | ||
|
|
5663b1c539 | ||
|
|
c68a4543db | ||
|
|
88dc8a389a | ||
|
|
e7b6c689ce | ||
|
|
6ec9b0a0b0 | ||
|
|
682ec563c8 | ||
|
|
8eb4d9bf08 | ||
|
|
977a8cf33f | ||
|
|
db496b82fb | ||
|
|
9d578884f9 | ||
|
|
84ae27744a | ||
|
|
54df44d930 | ||
|
|
f79b16c244 |
32
CHANGELOG.md
32
CHANGELOG.md
@@ -3,10 +3,42 @@
|
||||
## Unreleased
|
||||
|
||||
### API-Changes
|
||||
- breaking change: replace `dc_accounts_event_emitter_t` with `dc_event_emitter_t` #3422
|
||||
|
||||
Type `dc_accounts_event_emitter_t` is removed.
|
||||
`dc_accounts_get_event_emitter()` returns `dc_event_emitter_t` now, so
|
||||
`dc_get_next_event()` should be used instead of `dc_accounts_get_next_event`
|
||||
and `dc_event_emitter_unref()` should be used instead of
|
||||
`dc_accounts_event_emitter_unref`.
|
||||
- add `dc_contact_was_seen_recently()` #3560
|
||||
- jsonrpc: add functions: #3586, #3587
|
||||
- `deleteChat()`
|
||||
- `getChatEncryptionInfo()`
|
||||
- `getChatSecurejoinQrCodeSvg()`
|
||||
- `leaveGroup()`
|
||||
- `removeContactFromChat()`
|
||||
- `addContactToChat()`
|
||||
- `deleteMessages()`
|
||||
- `getMessageInfo()`
|
||||
- jsonrpc: add `is_broadcast` property to `ChatListItemFetchResult` #3584
|
||||
- jsonrpc: add `was_seen_recently` property to `ChatListItemFetchResult`, `FullChat` and `Contact` #3584
|
||||
- jsonrpc: add `webxdc_info` property to `Message` #3588
|
||||
|
||||
### Changes
|
||||
- order contact lists by "last seen";
|
||||
this affects `dc_get_chat_contacts()`, `dc_get_contacts()` and `dc_get_blocked_contacts()` #3562
|
||||
- add `internet_access` flag to `dc_msg_get_webxdc_info()` #3516
|
||||
- `DC_EVENT_WEBXDC_INSTANCE_DELETED` is emitted when a message containing a webxdc gets deleted #3105
|
||||
- `DC_EVENT_WEBXDC_BUSY_UPDATING` is emitted when a new update has to be sent by an webxdc #3320
|
||||
- `DC_EVENT_WEBXDC_UP_TO_DATE` is emitted when a webxdc has sent all updates #3320
|
||||
|
||||
### Fixes
|
||||
- do not emit notifications for blocked chats #3557
|
||||
- Show attached .eml files correctly #3561
|
||||
- Auto accept contact requests if `Config::Bot` is set for a client #3567
|
||||
- Don't prepend the subject to chat messages in mailinglists
|
||||
- fix `set_core_version.py` script to also update version in `deltachat-jsonrpc/typescript/package.json` #3585
|
||||
- Reject webxcd-updates from contacts who are not group members #3568
|
||||
|
||||
|
||||
## 1.93.0
|
||||
|
||||
90
Cargo.lock
generated
90
Cargo.lock
generated
@@ -87,9 +87,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.61"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8"
|
||||
checksum = "a26fa4d7e3f2eebadf743988fc8aec9fa9a9e82611acafd77c1462ed6262440a"
|
||||
|
||||
[[package]]
|
||||
name = "ascii_utils"
|
||||
@@ -497,9 +497,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.21"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb"
|
||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
@@ -946,7 +946,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha-1 0.10.0",
|
||||
"sha2 0.10.2",
|
||||
"sha2 0.10.3",
|
||||
"smallvec",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
@@ -965,7 +965,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.86.0"
|
||||
version = "1.93.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
@@ -1356,9 +1356,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fast-socks5"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25bef00d863e757b64a737857143b5425061271a4149ff5d81201fb4c9bfc01c"
|
||||
checksum = "d2687b5a6108f18ba8621e0e618a3be1dcc2768632dad24b7cea1f87975375a9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
@@ -1451,9 +1451,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||
checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1466,9 +1466,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -1476,15 +1476,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -1493,9 +1493,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
@@ -1514,9 +1514,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1525,21 +1525,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
|
||||
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1797,12 +1797,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.42"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9512e544c25736b82aebbd2bf739a47c8a1c935dfcc3a6adcde10e35cd3cd468"
|
||||
checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
@@ -1979,9 +1979,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.129"
|
||||
version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2285,9 +2285,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@@ -3105,9 +3105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.143"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3124,9 +3124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.143"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
|
||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3135,9 +3135,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.83"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||
dependencies = [
|
||||
"itoa 1.0.3",
|
||||
"ryu",
|
||||
@@ -3195,9 +3195,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.2"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
||||
checksum = "899bf02746a2c92bf1053d9327dadb252b01af1f81f90cdb902411f518bc7215"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
@@ -3419,18 +3419,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.32"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||
checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.32"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||
checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -46,7 +46,7 @@ native-tls = "0.2"
|
||||
num_cpus = "1.13"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
once_cell = "1.13.0"
|
||||
once_cell = "1.13.1"
|
||||
percent-encoding = "2.0"
|
||||
pgp = { version = "0.8", default-features = false }
|
||||
pretty_env_logger = { version = "0.4", optional = true }
|
||||
|
||||
@@ -25,7 +25,7 @@ tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||
anyhow = "1"
|
||||
thiserror = "1"
|
||||
rand = "0.7"
|
||||
once_cell = "1.13.0"
|
||||
once_cell = "1.13.1"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
|
||||
@@ -22,9 +22,11 @@ typedef struct _dc_lot dc_lot_t;
|
||||
typedef struct _dc_provider dc_provider_t;
|
||||
typedef struct _dc_event dc_event_t;
|
||||
typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
typedef struct _dc_accounts_event_emitter dc_accounts_event_emitter_t;
|
||||
typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t;
|
||||
|
||||
// Alias for backwards compatibility, use dc_event_emitter_t instead.
|
||||
typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
|
||||
/**
|
||||
* @mainpage Getting started
|
||||
*
|
||||
@@ -391,7 +393,8 @@ 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.
|
||||
* adds Auto-Submitted header to outgoing messages
|
||||
* and accepts contact requests automatically (calling dc_accept_chat() is not needed for bots).
|
||||
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
||||
* 0=do not fetch existing messages on configure.
|
||||
* In both cases, existing recipients are added to the contact database.
|
||||
@@ -1385,6 +1388,9 @@ void dc_block_chat (dc_context_t* context, uint32_t ch
|
||||
*
|
||||
* Use it to accept "contact request" chats as indicated by dc_chat_is_contact_request().
|
||||
*
|
||||
* If the dc_set_config()-option `bot` is set,
|
||||
* all chats are accepted automatically and calling this function has no effect.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param chat_id The ID of the chat to accept.
|
||||
@@ -2510,9 +2516,9 @@ int dc_set_location (dc_context_t* context, double latit
|
||||
* Must be given in number of seconds since 00:00 hours, Jan 1, 1970 UTC.
|
||||
* 0 for "all up to now".
|
||||
* @return An array of locations, NULL is never returned.
|
||||
* The array is sorted decending;
|
||||
* The array is sorted descending;
|
||||
* the first entry in the array is the location with the newest timestamp.
|
||||
* Note that this is only realated to the recent postion of the user
|
||||
* Note that this is only related to the recent position of the user
|
||||
* if dc_array_is_independent() returns 0.
|
||||
* The returned array must be freed using dc_array_unref().
|
||||
*
|
||||
@@ -2836,7 +2842,7 @@ void dc_accounts_maybe_network_lost (dc_accounts_t* accounts);
|
||||
*
|
||||
* The library will emit various @ref DC_EVENT events as "new message", "message read" etc.
|
||||
* To get these events, you have to create an event emitter using this function
|
||||
* and call dc_accounts_get_next_event() on the emitter.
|
||||
* and call dc_get_next_event() on the emitter.
|
||||
*
|
||||
* This is similar to dc_get_event_emitter(), which, however,
|
||||
* must not be called for accounts handled by the account manager.
|
||||
@@ -2844,13 +2850,13 @@ void dc_accounts_maybe_network_lost (dc_accounts_t* accounts);
|
||||
* @memberof dc_accounts_t
|
||||
* @param accounts The account manager as created by dc_accounts_new().
|
||||
* @return Returns the event emitter, NULL on errors.
|
||||
* Must be freed using dc_accounts_event_emitter_unref() after usage.
|
||||
* Must be freed using dc_event_emitter_unref() after usage.
|
||||
*
|
||||
* Note: Use only one event emitter per account manager.
|
||||
* Having more than one event emitter running at the same time on the same account manager
|
||||
* will result in events randomly delivered to the one or to the other.
|
||||
*/
|
||||
dc_accounts_event_emitter_t* dc_accounts_get_event_emitter (dc_accounts_t* accounts);
|
||||
dc_event_emitter_t* dc_accounts_get_event_emitter (dc_accounts_t* accounts);
|
||||
|
||||
|
||||
/**
|
||||
@@ -3757,6 +3763,11 @@ char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char*
|
||||
* URL where the source code of the Webxdc and other information can be found;
|
||||
* defaults to an empty string.
|
||||
* Implementations may offer an menu or a button to open this URL.
|
||||
* - internet_access:
|
||||
* true if the Webxdc should get full internet access, including Webrtc.
|
||||
* currently, this is only true for encrypted Webxdc's in the self chat
|
||||
* that have requested internet access in the manifest.
|
||||
* this is useful for development and maybe for internal integrations at some point.
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The webxdc instance.
|
||||
@@ -4638,6 +4649,22 @@ char* dc_contact_get_status (const dc_contact_t* contact);
|
||||
*/
|
||||
int64_t dc_contact_get_last_seen (const dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* Check if the contact was seen recently.
|
||||
*
|
||||
* The UI may highlight these contacts,
|
||||
* eg. draw a little green dot on the avatars of the users recently seen.
|
||||
* DC_CONTACT_ID_SELF and other special contact IDs are defined as never seen recently (they should not get a dot).
|
||||
* To get the time a contact was seen, use dc_contact_get_last_seen().
|
||||
*
|
||||
* @memberof dc_contact_t
|
||||
* @param contact The contact object.
|
||||
* @return 1=contact seen recently, 0=contact not seen recently.
|
||||
*/
|
||||
int dc_contact_was_seen_recently (const dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a contact is blocked.
|
||||
*
|
||||
@@ -5245,9 +5272,8 @@ char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||
* @class dc_event_emitter_t
|
||||
*
|
||||
* Opaque object that is used to get events from a single context.
|
||||
* You can get an event emitter from a context using dc_get_event_emitter().
|
||||
* If you are using the dc_accounts_t account manager,
|
||||
* dc_accounts_event_emitter_t must be used instead.
|
||||
* You can get an event emitter from a context using dc_get_event_emitter()
|
||||
* or dc_accounts_get_event_emitter().
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -5263,6 +5289,8 @@ char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||
*/
|
||||
dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter);
|
||||
|
||||
// Alias for backwards compatibility, use dc_get_next_event instead.
|
||||
#define dc_accounts_get_next_event dc_get_next_event
|
||||
|
||||
/**
|
||||
* Free a context event emitter object.
|
||||
@@ -5273,39 +5301,8 @@ dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter);
|
||||
*/
|
||||
void dc_event_emitter_unref(dc_event_emitter_t* emitter);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_accounts_event_emitter_t
|
||||
*
|
||||
* Opaque object that is used to get events from the dc_accounts_t account manager.
|
||||
* You get an event emitter from the account manager using dc_accounts_get_event_emitter().
|
||||
* If you are not using the dc_accounts_t account manager but just a single dc_context_t object,
|
||||
* dc_event_emitter_t must be used instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the next event from an accounts event emitter object.
|
||||
*
|
||||
* @memberof dc_accounts_event_emitter_t
|
||||
* @param emitter Event emitter object as returned from dc_accounts_get_event_emitter().
|
||||
* @return An event as an dc_event_t object.
|
||||
* You can query the event for information using dc_event_get_id(), dc_event_get_data1_int() and so on;
|
||||
* if you are done with the event, you have to free the event using dc_event_unref().
|
||||
* If NULL is returned, the contexts belonging to the event emitter are unref'd and no more events will come;
|
||||
* in this case, free the event emitter using dc_accounts_event_emitter_unref().
|
||||
*/
|
||||
dc_event_t* dc_accounts_get_next_event (dc_accounts_event_emitter_t* emitter);
|
||||
|
||||
|
||||
/**
|
||||
* Free an accounts event emitter object.
|
||||
*
|
||||
* @memberof dc_accounts_event_emitter_t
|
||||
* @param emitter Event emitter object as returned from dc_accounts_get_event_emitter().
|
||||
* If NULL is given, nothing is done and an error is logged.
|
||||
*/
|
||||
void dc_accounts_event_emitter_unref(dc_accounts_event_emitter_t* emitter);
|
||||
|
||||
// Alias for backwards compatibility, use dc_event_emtitter_unref instead.
|
||||
#define dc_accounts_event_emitter_unref dc_event_emitter_unref
|
||||
|
||||
/**
|
||||
* @class dc_event_t
|
||||
@@ -5377,7 +5374,7 @@ char* dc_event_get_data2_str(dc_event_t* event);
|
||||
* To get the context object belonging to the event, use dc_accounts_get_account().
|
||||
*
|
||||
* @memberof dc_event_t
|
||||
* @param event The event object as returned from dc_accounts_get_next_event().
|
||||
* @param event The event object as returned from dc_get_next_event().
|
||||
* @return The account ID belonging to the event, 0 for account manager errors.
|
||||
*/
|
||||
uint32_t dc_event_get_account_id(dc_event_t* event);
|
||||
@@ -5727,8 +5724,30 @@ void dc_event_unref(dc_event_t* event);
|
||||
* @param data1 (int) msg_id
|
||||
* @param data2 (int) status_update_serial - must not be used by UI implementations.
|
||||
*/
|
||||
#define DC_EVENT_WEBXDC_STATUS_UPDATE 2120
|
||||
#define DC_EVENT_WEBXDC_STATUS_UPDATE 2120
|
||||
|
||||
/**
|
||||
* Message deleted which contained a webxdc instance.
|
||||
*
|
||||
* @param data1 (int) msg_id
|
||||
*/
|
||||
#define DC_EVENT_WEBXDC_INSTANCE_DELETED 2121
|
||||
|
||||
|
||||
/**
|
||||
* Webxdc has some updates that need to be sent
|
||||
*
|
||||
* @param data1 (int) msg_id
|
||||
*/
|
||||
#define DC_EVENT_WEBXDC_BUSY_UPDATING 2122
|
||||
|
||||
|
||||
/**
|
||||
* Webxdc has finished sending updates
|
||||
*
|
||||
* @param data1 (int) msg_id
|
||||
*/
|
||||
#define DC_EVENT_WEBXDC_UP_TO_DATE 2123
|
||||
|
||||
/**
|
||||
* @}
|
||||
|
||||
@@ -504,6 +504,9 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
||||
EventType::ConnectivityChanged => 2100,
|
||||
EventType::SelfavatarChanged => 2110,
|
||||
EventType::WebxdcStatusUpdate { .. } => 2120,
|
||||
EventType::WebxdcInstanceDeleted { .. } => 2121,
|
||||
EventType::WebxdcBusyUpdating { .. } => 2022,
|
||||
EventType::WebxdcUpToDate { .. } => 2023,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +553,9 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
||||
contact_id.to_u32() as libc::c_int
|
||||
}
|
||||
EventType::WebxdcStatusUpdate { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
||||
EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
||||
EventType::WebxdcBusyUpdating { msg_id } => msg_id.to_u32() as libc::c_int,
|
||||
EventType::WebxdcUpToDate { msg_id } => msg_id.to_u32() as libc::c_int,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,6 +587,9 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
||||
| EventType::ImexFileWritten(_)
|
||||
| EventType::MsgsNoticed(_)
|
||||
| EventType::ConnectivityChanged
|
||||
| EventType::WebxdcInstanceDeleted { .. }
|
||||
| EventType::WebxdcBusyUpdating { .. }
|
||||
| EventType::WebxdcUpToDate { .. }
|
||||
| EventType::SelfavatarChanged => 0,
|
||||
EventType::ChatModified(_) => 0,
|
||||
EventType::MsgsChanged { msg_id, .. }
|
||||
@@ -637,6 +646,9 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
||||
| EventType::ConnectivityChanged
|
||||
| EventType::SelfavatarChanged
|
||||
| EventType::WebxdcStatusUpdate { .. }
|
||||
| EventType::WebxdcInstanceDeleted { .. }
|
||||
| EventType::WebxdcBusyUpdating { .. }
|
||||
| EventType::WebxdcUpToDate { .. }
|
||||
| EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(),
|
||||
EventType::ConfigureProgress { comment, .. } => {
|
||||
if let Some(comment) = comment {
|
||||
@@ -3859,6 +3871,16 @@ pub unsafe extern "C" fn dc_contact_get_last_seen(contact: *mut dc_contact_t) ->
|
||||
ffi_contact.contact.last_seen()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_contact_was_seen_recently(contact: *mut dc_contact_t) -> libc::c_int {
|
||||
if contact.is_null() {
|
||||
eprintln!("ignoring careless call to dc_contact_was_seen_recently()");
|
||||
return 0;
|
||||
}
|
||||
let ffi_contact = &*contact;
|
||||
ffi_contact.contact.was_seen_recently() as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_contact_is_blocked(contact: *mut dc_contact_t) -> libc::c_int {
|
||||
if contact.is_null() {
|
||||
@@ -4406,12 +4428,10 @@ pub unsafe extern "C" fn dc_accounts_maybe_network_lost(accounts: *mut dc_accoun
|
||||
block_on(async move { accounts.write().await.maybe_network_lost().await });
|
||||
}
|
||||
|
||||
pub type dc_accounts_event_emitter_t = EventEmitter;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_accounts_get_event_emitter(
|
||||
accounts: *mut dc_accounts_t,
|
||||
) -> *mut dc_accounts_event_emitter_t {
|
||||
) -> *mut dc_event_emitter_t {
|
||||
if accounts.is_null() {
|
||||
eprintln!("ignoring careless call to dc_accounts_get_event_emitter()");
|
||||
return ptr::null_mut();
|
||||
@@ -4423,31 +4443,6 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
|
||||
Box::into_raw(Box::new(emitter))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_accounts_event_emitter_unref(
|
||||
emitter: *mut dc_accounts_event_emitter_t,
|
||||
) {
|
||||
if emitter.is_null() {
|
||||
eprintln!("ignoring careless call to dc_accounts_event_emitter_unref()");
|
||||
return;
|
||||
}
|
||||
let _ = Box::from_raw(emitter);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_accounts_get_next_event(
|
||||
emitter: *mut dc_accounts_event_emitter_t,
|
||||
) -> *mut dc_event_t {
|
||||
if emitter.is_null() {
|
||||
eprintln!("ignoring careless call to dc_accounts_get_next_event()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let emitter = &mut *emitter;
|
||||
block_on(emitter.recv())
|
||||
.map(|ev| Box::into_raw(Box::new(ev)))
|
||||
.unwrap_or_else(ptr::null_mut)
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonrpc")]
|
||||
mod jsonrpc {
|
||||
use super::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.86.0"
|
||||
version = "1.93.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2021"
|
||||
@@ -20,8 +20,8 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.3.0"
|
||||
log = "0.4"
|
||||
async-channel = { version = "1.6.1" }
|
||||
futures = { version = "0.3.19" }
|
||||
serde_json = "1.0.75"
|
||||
futures = { version = "0.3.24" }
|
||||
serde_json = "1.0.85"
|
||||
yerpc = { version = "^0.3.1", features = ["anyhow_expose"] }
|
||||
typescript-type-def = { version = "0.5.3", features = ["json_value"] }
|
||||
tokio = { version = "1.19.2" }
|
||||
|
||||
@@ -60,6 +60,9 @@ pub fn event_to_json_rpc_notification(event: Event) -> Value {
|
||||
msg_id,
|
||||
status_update_serial,
|
||||
} => (json!(msg_id), json!(status_update_serial)),
|
||||
EventType::WebxdcInstanceDeleted { msg_id } => (json!(msg_id), Value::Null),
|
||||
EventType::WebxdcBusyUpdating { msg_id } => (json!(msg_id), Value::Null),
|
||||
EventType::WebxdcUpToDate { msg_id } => (json!(msg_id), Value::Null),
|
||||
};
|
||||
|
||||
let id: EventTypeName = event.typ.into();
|
||||
@@ -102,6 +105,9 @@ pub enum EventTypeName {
|
||||
ConnectivityChanged,
|
||||
SelfavatarChanged,
|
||||
WebxdcStatusUpdate,
|
||||
WebxdcInstanceDeleted,
|
||||
WebxdcBusyUpdating,
|
||||
WebxdcUpToDate,
|
||||
}
|
||||
|
||||
impl From<EventType> for EventTypeName {
|
||||
@@ -137,6 +143,9 @@ impl From<EventType> for EventTypeName {
|
||||
EventType::ConnectivityChanged => ConnectivityChanged,
|
||||
EventType::SelfavatarChanged => SelfavatarChanged,
|
||||
EventType::WebxdcStatusUpdate { .. } => WebxdcStatusUpdate,
|
||||
EventType::WebxdcInstanceDeleted { .. } => WebxdcInstanceDeleted,
|
||||
EventType::WebxdcBusyUpdating { .. } => WebxdcBusyUpdating,
|
||||
EventType::WebxdcUpToDate { .. } => WebxdcUpToDate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use deltachat::{
|
||||
chat::{get_chat_media, get_chat_msgs, ChatId},
|
||||
chat::{add_contact_to_chat, get_chat_media, get_chat_msgs, remove_contact_from_chat, ChatId},
|
||||
chatlist::Chatlist,
|
||||
config::Config,
|
||||
contact::{may_be_valid_addr, Contact, ContactId},
|
||||
context::get_info,
|
||||
message::{Message, MsgId, Viewtype},
|
||||
message::{delete_msgs, get_msg_info, Message, MsgId, Viewtype},
|
||||
provider::get_provider_info,
|
||||
qr,
|
||||
qr_code_generator::get_securejoin_qr_svg,
|
||||
securejoin,
|
||||
webxdc::StatusUpdateSerial,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
@@ -192,12 +194,10 @@ impl CommandApi {
|
||||
}
|
||||
|
||||
/// Set configuration values from a QR code. (technically from the URI that is stored in the qrcode)
|
||||
/// Before this function is called, dc_check_qr() should confirm the type of the
|
||||
/// QR code is DC_QR_ACCOUNT or DC_QR_WEBRTC_INSTANCE.
|
||||
/// Before this function is called, `checkQr()` should confirm the type of the
|
||||
/// QR code is `account` or `webrtcInstance`.
|
||||
///
|
||||
/// Internally, the function will call dc_set_config() with the appropriate keys,
|
||||
/// e.g. `addr` and `mail_pw` for DC_QR_ACCOUNT
|
||||
/// or `webrtc_instance` for DC_QR_WEBRTC_INSTANCE.
|
||||
async fn set_config_from_qr(&self, account_id: u32, qr_content: String) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
qr::set_config_from_qr(&ctx, &qr_content).await
|
||||
@@ -378,6 +378,108 @@ impl CommandApi {
|
||||
ChatId::new(chat_id).block(&ctx).await
|
||||
}
|
||||
|
||||
/// Delete a chat.
|
||||
///
|
||||
/// Messages are deleted from the device and the chat database entry is deleted.
|
||||
/// After that, the event #DC_EVENT_MSGS_CHANGED is posted.
|
||||
///
|
||||
/// Things that are _not done_ implicitly:
|
||||
///
|
||||
/// - Messages are **not deleted from the server**.
|
||||
/// - The chat or the contact is **not blocked**, so new messages from the user/the group may appear as a contact request
|
||||
/// and the user may create the chat again.
|
||||
/// - **Groups are not left** - this would
|
||||
/// be unexpected as (1) deleting a normal chat also does not prevent new mails
|
||||
/// from arriving, (2) leaving a group requires sending a message to
|
||||
/// all group members - especially for groups not used for a longer time, this is
|
||||
/// really unexpected when deletion results in contacting all members again,
|
||||
/// (3) only leaving groups is also a valid usecase.
|
||||
///
|
||||
/// To leave a chat explicitly, use dc_remove_contact_from_chat() with
|
||||
/// chat_id=DC_CONTACT_ID_SELF)
|
||||
// TODO fix doc comment after adding dc_remove_contact_from_chat
|
||||
async fn delete_chat(&self, account_id: u32, chat_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ChatId::new(chat_id).delete(&ctx).await
|
||||
}
|
||||
|
||||
/// Get encryption info for a chat.
|
||||
/// Get a multi-line encryption info, containing encryption preferences of all members.
|
||||
/// Can be used to find out why messages sent to group are not encrypted.
|
||||
///
|
||||
/// returns Multi-line text
|
||||
async fn get_chat_encryption_info(&self, account_id: u32, chat_id: u32) -> Result<String> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ChatId::new(chat_id).get_encryption_info(&ctx).await
|
||||
}
|
||||
|
||||
/// Get QR code (text and SVG) that will offer an Setup-Contact or Verified-Group invitation.
|
||||
/// The QR code is compatible to the OPENPGP4FPR format
|
||||
/// so that a basic fingerprint comparison also works e.g. with OpenKeychain.
|
||||
///
|
||||
/// The scanning device will pass the scanned content to `checkQr()` then;
|
||||
/// if `checkQr()` returns `askVerifyContact` or `askVerifyGroup`
|
||||
/// an out-of-band-verification can be joined using dc_join_securejoin()
|
||||
///
|
||||
/// chat_id: If set to a group-chat-id,
|
||||
/// the Verified-Group-Invite protocol is offered in the QR code;
|
||||
/// works for protected groups as well as for normal groups.
|
||||
/// If not set, the Setup-Contact protocol is offered in the QR code.
|
||||
/// See https://countermitm.readthedocs.io/en/latest/new.html
|
||||
/// for details about both protocols.
|
||||
// TODO fix doc comment after adding dc_join_securejoin
|
||||
async fn get_chat_securejoin_qr_code_svg(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: Option<u32>,
|
||||
) -> Result<(String, String)> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let chat = chat_id.map(ChatId::new);
|
||||
Ok((
|
||||
securejoin::get_securejoin_qr(&ctx, chat).await?,
|
||||
get_securejoin_qr_svg(&ctx, chat).await?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn leave_group(&self, account_id: u32, chat_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
remove_contact_from_chat(&ctx, ChatId::new(chat_id), ContactId::SELF).await
|
||||
}
|
||||
|
||||
/// Remove a member from a group.
|
||||
///
|
||||
/// If the group is already _promoted_ (any message was sent to the group),
|
||||
/// all group members are informed by a special status message that is sent automatically by this function.
|
||||
///
|
||||
/// Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
async fn remove_contact_from_chat(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
remove_contact_from_chat(&ctx, ChatId::new(chat_id), ContactId::new(contact_id)).await
|
||||
}
|
||||
|
||||
/// Add a member to a group.
|
||||
///
|
||||
/// If the group is already _promoted_ (any message was sent to the group),
|
||||
/// all group members are informed by a special status message that is sent automatically by this function.
|
||||
///
|
||||
/// If the group has group protection enabled, only verified contacts can be added to the group.
|
||||
///
|
||||
/// Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
async fn add_contact_to_chat(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
add_contact_to_chat(&ctx, ChatId::new(chat_id), ContactId::new(contact_id)).await
|
||||
}
|
||||
|
||||
// for now only text messages, because we only used text messages in desktop thusfar
|
||||
async fn add_device_message(
|
||||
&self,
|
||||
@@ -435,6 +537,24 @@ impl CommandApi {
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
/// Delete messages. The messages are deleted on the current device and
|
||||
/// on the IMAP server.
|
||||
async fn delete_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msgs: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
||||
delete_msgs(&ctx, &msgs).await
|
||||
}
|
||||
|
||||
/// Get an informational text for a single message. The text is multiline and may
|
||||
/// contain e.g. the raw text of the message.
|
||||
///
|
||||
/// The max. text returned is typically longer (about 100000 characters) than the
|
||||
/// 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
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// contact
|
||||
// ---------------------------------------------
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use deltachat::chat::get_chat_contacts;
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use deltachat::context::Context;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
@@ -33,6 +34,7 @@ pub struct FullChat {
|
||||
is_muted: bool,
|
||||
ephemeral_timer: u32, //TODO look if there are more important properties in newer core versions
|
||||
can_send: bool,
|
||||
was_seen_recently: bool,
|
||||
}
|
||||
|
||||
impl FullChat {
|
||||
@@ -65,6 +67,17 @@ impl FullChat {
|
||||
|
||||
let can_send = chat.can_send(context).await?;
|
||||
|
||||
let was_seen_recently = if chat.get_type() == Chattype::Single {
|
||||
match contact_ids.get(0) {
|
||||
Some(contact) => Contact::load_from_db(context, *contact)
|
||||
.await?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(FullChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
@@ -87,6 +100,7 @@ impl FullChat {
|
||||
is_muted: chat.is_muted(),
|
||||
ephemeral_timer,
|
||||
can_send,
|
||||
was_seen_recently,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::ContactId;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use deltachat::{
|
||||
chat::{get_chat_contacts, ChatVisibility},
|
||||
chatlist::Chatlist,
|
||||
@@ -42,8 +42,11 @@ pub enum ChatListItemFetchResult {
|
||||
is_pinned: bool,
|
||||
is_muted: bool,
|
||||
is_contact_request: bool,
|
||||
/// true when chat is a broadcastlist
|
||||
is_broadcast: bool,
|
||||
/// contact id if this is a dm chat (for view profile entry in context menu)
|
||||
dm_chat_contact: Option<u32>,
|
||||
was_seen_recently: bool,
|
||||
},
|
||||
ArchiveLink,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -92,10 +95,20 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
|
||||
let self_in_group = chat_contacts.contains(&ContactId::SELF);
|
||||
|
||||
let dm_chat_contact = if chat.get_type() == Chattype::Single {
|
||||
chat_contacts.get(0).map(|contact_id| contact_id.to_u32())
|
||||
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)
|
||||
.await?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
};
|
||||
(
|
||||
contact.map(|contact_id| contact_id.to_u32()),
|
||||
was_seen_recently,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
(None, false)
|
||||
};
|
||||
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
@@ -121,6 +134,8 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
is_pinned: visibility == ChatVisibility::Pinned,
|
||||
is_muted: chat.is_muted(),
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_broadcast: chat.get_type() == Chattype::Broadcast,
|
||||
dm_chat_contact,
|
||||
was_seen_recently,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct ContactObject {
|
||||
name_and_addr: String,
|
||||
is_blocked: bool,
|
||||
is_verified: bool,
|
||||
was_seen_recently: bool,
|
||||
}
|
||||
|
||||
impl ContactObject {
|
||||
@@ -45,6 +46,7 @@ impl ContactObject {
|
||||
name_and_addr: contact.get_name_n_addr(),
|
||||
is_blocked: contact.is_blocked(),
|
||||
is_verified,
|
||||
was_seen_recently: contact.was_seen_recently(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::contact::ContactObject;
|
||||
use super::webxdc::WebxdcMessageInfo;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Message", rename_all = "camelCase")]
|
||||
@@ -53,6 +54,8 @@ pub struct MessageObject {
|
||||
file_mime: Option<String>,
|
||||
file_bytes: u64,
|
||||
file_name: Option<String>,
|
||||
|
||||
webxdc_info: Option<WebxdcMessageInfo>,
|
||||
}
|
||||
|
||||
impl MessageObject {
|
||||
@@ -70,6 +73,12 @@ impl MessageObject {
|
||||
let file_bytes = message.get_filebytes(context).await;
|
||||
let override_sender_name = message.get_override_sender_name();
|
||||
|
||||
let webxdc_info = if message.get_viewtype() == Viewtype::Webxdc {
|
||||
Some(WebxdcMessageInfo::get_for_message(context, msg_id).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(MessageObject {
|
||||
id: message_id,
|
||||
chat_id: message.get_chat_id().to_u32(),
|
||||
@@ -121,6 +130,7 @@ impl MessageObject {
|
||||
file_mime: message.get_filemime(),
|
||||
file_bytes,
|
||||
file_name: message.get_filename(),
|
||||
webxdc_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ pub struct WebxdcMessageInfo {
|
||||
/// defaults to an empty string.
|
||||
/// Implementations may offer an menu or a button to open this URL.
|
||||
source_code_url: Option<String>,
|
||||
/// True if full internet access should be granted to the app.
|
||||
internet_access: bool,
|
||||
}
|
||||
|
||||
impl WebxdcMessageInfo {
|
||||
@@ -47,6 +49,7 @@ impl WebxdcMessageInfo {
|
||||
document,
|
||||
summary,
|
||||
source_code_url,
|
||||
internet_access,
|
||||
} = message.get_webxdc_info(context).await?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -55,6 +58,7 @@ impl WebxdcMessageInfo {
|
||||
document: maybe_empty_string_to_option(document),
|
||||
summary: maybe_empty_string_to_option(summary),
|
||||
source_code_url: maybe_empty_string_to_option(source_code_url),
|
||||
internet_access,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,12 +111,10 @@ export class RawClient {
|
||||
|
||||
/**
|
||||
* Set configuration values from a QR code. (technically from the URI that is stored in the qrcode)
|
||||
* Before this function is called, dc_check_qr() should confirm the type of the
|
||||
* QR code is DC_QR_ACCOUNT or DC_QR_WEBRTC_INSTANCE.
|
||||
* Before this function is called, `checkQr()` should confirm the type of the
|
||||
* QR code is `account` or `webrtcInstance`.
|
||||
*
|
||||
* Internally, the function will call dc_set_config() with the appropriate keys,
|
||||
* e.g. `addr` and `mail_pw` for DC_QR_ACCOUNT
|
||||
* or `webrtc_instance` for DC_QR_WEBRTC_INSTANCE.
|
||||
*/
|
||||
public setConfigFromQr(accountId: T.U32, qrContent: string): Promise<null> {
|
||||
return (this._transport.request('set_config_from_qr', [accountId, qrContent] as RPC.Params)) as Promise<null>;
|
||||
@@ -216,6 +214,93 @@ export class RawClient {
|
||||
return (this._transport.request('block_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a chat.
|
||||
*
|
||||
* Messages are deleted from the device and the chat database entry is deleted.
|
||||
* After that, the event #DC_EVENT_MSGS_CHANGED is posted.
|
||||
*
|
||||
* Things that are _not done_ implicitly:
|
||||
*
|
||||
* - Messages are **not deleted from the server**.
|
||||
* - The chat or the contact is **not blocked**, so new messages from the user/the group may appear as a contact request
|
||||
* and the user may create the chat again.
|
||||
* - **Groups are not left** - this would
|
||||
* be unexpected as (1) deleting a normal chat also does not prevent new mails
|
||||
* from arriving, (2) leaving a group requires sending a message to
|
||||
* all group members - especially for groups not used for a longer time, this is
|
||||
* really unexpected when deletion results in contacting all members again,
|
||||
* (3) only leaving groups is also a valid usecase.
|
||||
*
|
||||
* To leave a chat explicitly, use dc_remove_contact_from_chat() with
|
||||
* chat_id=DC_CONTACT_ID_SELF)
|
||||
*/
|
||||
public deleteChat(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('delete_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encryption info for a chat.
|
||||
* Get a multi-line encryption info, containing encryption preferences of all members.
|
||||
* Can be used to find out why messages sent to group are not encrypted.
|
||||
*
|
||||
* returns Multi-line text
|
||||
*/
|
||||
public getChatEncryptionInfo(accountId: T.U32, chatId: T.U32): Promise<string> {
|
||||
return (this._transport.request('get_chat_encryption_info', [accountId, chatId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get QR code (text and SVG) that will offer an Setup-Contact or Verified-Group invitation.
|
||||
* The QR code is compatible to the OPENPGP4FPR format
|
||||
* so that a basic fingerprint comparison also works e.g. with OpenKeychain.
|
||||
*
|
||||
* The scanning device will pass the scanned content to `checkQr()` then;
|
||||
* if `checkQr()` returns `askVerifyContact` or `askVerifyGroup`
|
||||
* an out-of-band-verification can be joined using dc_join_securejoin()
|
||||
*
|
||||
* chat_id: If set to a group-chat-id,
|
||||
* the Verified-Group-Invite protocol is offered in the QR code;
|
||||
* works for protected groups as well as for normal groups.
|
||||
* If not set, the Setup-Contact protocol is offered in the QR code.
|
||||
* See https://countermitm.readthedocs.io/en/latest/new.html
|
||||
* for details about both protocols.
|
||||
*/
|
||||
public getChatSecurejoinQrCodeSvg(accountId: T.U32, chatId: (T.U32|null)): Promise<[string,string]> {
|
||||
return (this._transport.request('get_chat_securejoin_qr_code_svg', [accountId, chatId] as RPC.Params)) as Promise<[string,string]>;
|
||||
}
|
||||
|
||||
|
||||
public leaveGroup(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('leave_group', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a member from a group.
|
||||
*
|
||||
* If the group is already _promoted_ (any message was sent to the group),
|
||||
* all group members are informed by a special status message that is sent automatically by this function.
|
||||
*
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
*/
|
||||
public removeContactFromChat(accountId: T.U32, chatId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('remove_contact_from_chat', [accountId, chatId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a member to a group.
|
||||
*
|
||||
* If the group is already _promoted_ (any message was sent to the group),
|
||||
* all group members are informed by a special status message that is sent automatically by this function.
|
||||
*
|
||||
* If the group has group protection enabled, only verified contacts can be added to the group.
|
||||
*
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
*/
|
||||
public addContactToChat(accountId: T.U32, chatId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('add_contact_to_chat', [accountId, chatId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public addDeviceMessage(accountId: T.U32, label: string, text: string): Promise<T.U32> {
|
||||
return (this._transport.request('add_device_message', [accountId, label, text] as RPC.Params)) as Promise<T.U32>;
|
||||
@@ -236,6 +321,25 @@ export class RawClient {
|
||||
return (this._transport.request('message_get_messages', [accountId, messageIds] as RPC.Params)) as Promise<Record<T.U32,T.Message>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete messages. The messages are deleted on the current device and
|
||||
* on the IMAP server.
|
||||
*/
|
||||
public deleteMessages(accountId: T.U32, messageIds: (T.U32)[]): Promise<null> {
|
||||
return (this._transport.request('delete_messages', [accountId, messageIds] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an informational text for a single message. The text is multiline and may
|
||||
* contain e.g. the raw text of the message.
|
||||
*
|
||||
* The max. text returned is typically longer (about 100000 characters) than the
|
||||
* max. text returned by dc_msg_get_text() (about 30000 characters).
|
||||
*/
|
||||
public getMessageInfo(accountId: T.U32, messageId: T.U32): Promise<string> {
|
||||
return (this._transport.request('get_message_info', [accountId, messageId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single contact options by ID.
|
||||
*/
|
||||
@@ -299,7 +403,7 @@ export class RawClient {
|
||||
* The list is already sorted and starts with the oldest message.
|
||||
* Clients should not try to re-sort the list as this would be an expensive action
|
||||
* and would result in inconsistencies between clients.
|
||||
*
|
||||
*
|
||||
* Setting `chat_id` to `None` (`null` in typescript) means get messages with media
|
||||
* from any chat of the currently used account.
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// AUTO-GENERATED by typescript-type-def
|
||||
|
||||
export type EventTypeName=("Info"|"SmtpConnected"|"ImapConnected"|"SmtpMessageSent"|"ImapMessageDeleted"|"ImapMessageMoved"|"NewBlobFile"|"DeletedBlobFile"|"Warning"|"Error"|"ErrorSelfNotInGroup"|"MsgsChanged"|"IncomingMsg"|"MsgsNoticed"|"MsgDelivered"|"MsgFailed"|"MsgRead"|"ChatModified"|"ChatEphemeralTimerModified"|"ContactsChanged"|"LocationChanged"|"ConfigureProgress"|"ImexProgress"|"ImexFileWritten"|"SecurejoinInviterProgress"|"SecurejoinJoinerProgress"|"ConnectivityChanged"|"SelfavatarChanged"|"WebxdcStatusUpdate");
|
||||
export type EventTypeName=("Info"|"SmtpConnected"|"ImapConnected"|"SmtpMessageSent"|"ImapMessageDeleted"|"ImapMessageMoved"|"NewBlobFile"|"DeletedBlobFile"|"Warning"|"Error"|"ErrorSelfNotInGroup"|"MsgsChanged"|"IncomingMsg"|"MsgsNoticed"|"MsgDelivered"|"MsgFailed"|"MsgRead"|"ChatModified"|"ChatEphemeralTimerModified"|"ContactsChanged"|"LocationChanged"|"ConfigureProgress"|"ImexProgress"|"ImexFileWritten"|"SecurejoinInviterProgress"|"SecurejoinJoinerProgress"|"ConnectivityChanged"|"SelfavatarChanged"|"WebxdcStatusUpdate"|"WebXdInstanceDeleted");
|
||||
|
||||
@@ -8,12 +8,16 @@ export type Usize=number;
|
||||
export type ChatListEntry=[U32,U32];
|
||||
export type I64=number;
|
||||
export type ChatListItemFetchResult=(({"type":"ChatListItem";}&{"id":U32;"name":string;"avatarPath":(string|null);"color":string;"lastUpdated":(I64|null);"summaryText1":string;"summaryText2":string;"summaryStatus":U32;"isProtected":boolean;"isGroup":boolean;"freshMessageCounter":Usize;"isSelfTalk":boolean;"isDeviceTalk":boolean;"isSendingLocation":boolean;"isSelfInGroup":boolean;"isArchived":boolean;"isPinned":boolean;"isMuted":boolean;"isContactRequest":boolean;
|
||||
/**
|
||||
* true when chat is a broadcastlist
|
||||
*/
|
||||
"isBroadcast":boolean;
|
||||
/**
|
||||
* contact id if this is a dm chat (for view profile entry in context menu)
|
||||
*/
|
||||
"dmChatContact":(U32|null);})|{"type":"ArchiveLink";}|({"type":"Error";}&{"id":U32;"error":string;}));
|
||||
export type Contact={"address":string;"color":string;"authName":string;"status":string;"displayName":string;"id":U32;"name":string;"profileImage":(string|null);"nameAndAddr":string;"isBlocked":boolean;"isVerified":boolean;};
|
||||
export type FullChat={"id":U32;"name":string;"isProtected":boolean;"profileImage":(string|null);"archived":boolean;"chatType":U32;"isUnpromoted":boolean;"isSelfTalk":boolean;"contacts":(Contact)[];"contactIds":(U32)[];"color":string;"freshMessageCounter":Usize;"isContactRequest":boolean;"isDeviceChat":boolean;"selfInGroup":boolean;"isMuted":boolean;"ephemeralTimer":U32;"canSend":boolean;};
|
||||
"dmChatContact":(U32|null);"wasSeenRecently":boolean;})|{"type":"ArchiveLink";}|({"type":"Error";}&{"id":U32;"error":string;}));
|
||||
export type Contact={"address":string;"color":string;"authName":string;"status":string;"displayName":string;"id":U32;"name":string;"profileImage":(string|null);"nameAndAddr":string;"isBlocked":boolean;"isVerified":boolean;"wasSeenRecently":boolean;};
|
||||
export type FullChat={"id":U32;"name":string;"isProtected":boolean;"profileImage":(string|null);"archived":boolean;"chatType":U32;"isUnpromoted":boolean;"isSelfTalk":boolean;"contacts":(Contact)[];"contactIds":(U32)[];"color":string;"freshMessageCounter":Usize;"isContactRequest":boolean;"isDeviceChat":boolean;"selfInGroup":boolean;"isMuted":boolean;"ephemeralTimer":U32;"canSend":boolean;"wasSeenRecently":boolean;};
|
||||
export type Viewtype=("Unknown"|
|
||||
/**
|
||||
* Text message.
|
||||
@@ -61,7 +65,6 @@ export type Viewtype=("Unknown"|
|
||||
"Webxdc");
|
||||
export type I32=number;
|
||||
export type U64=number;
|
||||
export type Message={"id":U32;"chatId":U32;"fromId":U32;"quotedText":(string|null);"quotedMessageId":(U32|null);"text":(string|null);"hasLocation":boolean;"hasHtml":boolean;"viewType":Viewtype;"state":U32;"timestamp":I64;"sortTimestamp":I64;"receivedTimestamp":I64;"hasDeviatingTimestamp":boolean;"subject":string;"showPadlock":boolean;"isSetupmessage":boolean;"isInfo":boolean;"isForwarded":boolean;"duration":I32;"dimensionsHeight":I32;"dimensionsWidth":I32;"videochatType":(U32|null);"videochatUrl":(string|null);"overrideSenderName":(string|null);"sender":Contact;"setupCodeBegin":(string|null);"file":(string|null);"fileMime":(string|null);"fileBytes":U64;"fileName":(string|null);};
|
||||
export type WebxdcMessageInfo={
|
||||
/**
|
||||
* The name of the app.
|
||||
@@ -94,5 +97,10 @@ export type WebxdcMessageInfo={
|
||||
* defaults to an empty string.
|
||||
* Implementations may offer an menu or a button to open this URL.
|
||||
*/
|
||||
"sourceCodeUrl":(string|null);};
|
||||
export type __AllTyps=[string,boolean,Record<string,string>,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],U32,Account,U32,string,(ProviderInfo|null),U32,boolean,U32,Record<string,string>,U32,string,(string|null),null,U32,Record<string,(string|null)>,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record<string,(string|null)>,U32,null,U32,null,U32,(U32)[],U32,U32,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record<U32,ChatListItemFetchResult>,U32,U32,FullChat,U32,U32,null,U32,U32,null,U32,string,string,U32,U32,U32,U32,(U32)[],U32,U32,Message,U32,(U32)[],Record<U32,Message>,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record<U32,Contact>,U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,string,U32,U32];
|
||||
"sourceCodeUrl":(string|null);
|
||||
/**
|
||||
* True if full internet access should be granted to the app.
|
||||
*/
|
||||
"internetAccess":boolean;};
|
||||
export type Message={"id":U32;"chatId":U32;"fromId":U32;"quotedText":(string|null);"quotedMessageId":(U32|null);"text":(string|null);"hasLocation":boolean;"hasHtml":boolean;"viewType":Viewtype;"state":U32;"timestamp":I64;"sortTimestamp":I64;"receivedTimestamp":I64;"hasDeviatingTimestamp":boolean;"subject":string;"showPadlock":boolean;"isSetupmessage":boolean;"isInfo":boolean;"isForwarded":boolean;"duration":I32;"dimensionsHeight":I32;"dimensionsWidth":I32;"videochatType":(U32|null);"videochatUrl":(string|null);"overrideSenderName":(string|null);"sender":Contact;"setupCodeBegin":(string|null);"file":(string|null);"fileMime":(string|null);"fileBytes":U64;"fileName":(string|null);"webxdcInfo":(WebxdcMessageInfo|null);};
|
||||
export type __AllTyps=[string,boolean,Record<string,string>,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],U32,Account,U32,string,(ProviderInfo|null),U32,boolean,U32,Record<string,string>,U32,string,(string|null),null,U32,Record<string,(string|null)>,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record<string,(string|null)>,U32,null,U32,null,U32,(U32)[],U32,U32,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record<U32,ChatListItemFetchResult>,U32,U32,FullChat,U32,U32,null,U32,U32,null,U32,U32,null,U32,U32,string,U32,(U32|null),[string,string],U32,U32,null,U32,U32,U32,null,U32,U32,U32,null,U32,string,string,U32,U32,U32,U32,(U32)[],U32,U32,Message,U32,(U32)[],Record<U32,Message>,U32,(U32)[],null,U32,U32,string,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record<U32,Contact>,U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,string,U32,U32];
|
||||
|
||||
@@ -1,29 +1,5 @@
|
||||
{
|
||||
"name": "deltachat-jsonrpc-client",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/deltachat.js",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"type": "module",
|
||||
"author": "Delta Chat Developers (ML) <delta@codespeak.net>",
|
||||
"license": "MPL-2.0",
|
||||
"scripts": {
|
||||
"prettier:check": "prettier --check **.ts",
|
||||
"prettier:fix": "prettier --write **.ts",
|
||||
"generate-bindings": "cargo test",
|
||||
"build": "run-s generate-bindings build:tsc build:bundle",
|
||||
"build:tsc": "tsc",
|
||||
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
|
||||
"example": "run-s build example:build example:start",
|
||||
"example:build": "esbuild --bundle dist/example/example.js --outfile=dist/example.bundle.js",
|
||||
"example:start": "http-server .",
|
||||
"example:dev": "esbuild example/example.ts --bundle --outfile=dist/example.bundle.js --servedir=.",
|
||||
"test": "run-s test:prepare test:run-coverage test:report-coverage",
|
||||
"test:prepare": "cargo build --features webserver --bin deltachat-jsonrpc-server",
|
||||
"test:run": "mocha dist/test",
|
||||
"test:run-coverage": "COVERAGE=1 NODE_OPTIONS=--enable-source-maps c8 --include 'dist/*' -r text -r html -r json mocha dist/test",
|
||||
"test:report-coverage": "node report_api_coverage.mjs",
|
||||
"docs": "typedoc --out docs deltachat.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"isomorphic-ws": "^4.0.1",
|
||||
"tiny-emitter": "git+https://github.com/Simon-Laux/tiny-emitter.git",
|
||||
@@ -47,5 +23,29 @@
|
||||
"typedoc": "^0.23.2",
|
||||
"typescript": "^4.5.5",
|
||||
"ws": "^8.5.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"main": "dist/deltachat.js",
|
||||
"name": "deltachat-jsonrpc-client",
|
||||
"scripts": {
|
||||
"build": "run-s generate-bindings build:tsc build:bundle",
|
||||
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
|
||||
"build:tsc": "tsc",
|
||||
"docs": "typedoc --out docs deltachat.ts",
|
||||
"example": "run-s build example:build example:start",
|
||||
"example:build": "esbuild --bundle dist/example/example.js --outfile=dist/example.bundle.js",
|
||||
"example:dev": "esbuild example/example.ts --bundle --outfile=dist/example.bundle.js --servedir=.",
|
||||
"example:start": "http-server .",
|
||||
"generate-bindings": "cargo test",
|
||||
"prettier:check": "prettier --check **.ts",
|
||||
"prettier:fix": "prettier --write **.ts",
|
||||
"test": "run-s test:prepare test:run-coverage test:report-coverage",
|
||||
"test:prepare": "cargo build --features webserver --bin deltachat-jsonrpc-server",
|
||||
"test:report-coverage": "node report_api_coverage.mjs",
|
||||
"test:run": "mocha dist/test",
|
||||
"test:run-coverage": "COVERAGE=1 NODE_OPTIONS=--enable-source-maps c8 --include 'dist/*' -r text -r html -r json mocha dist/test"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.93.0"
|
||||
}
|
||||
@@ -72,6 +72,10 @@ export class Contact {
|
||||
return binding.dcn_contact_get_last_seen(this.dc_contact)
|
||||
}
|
||||
|
||||
wasSeenRecently() {
|
||||
return Boolean(binding.dcn_contact_was_seen_recently(this.dc_contact))
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return binding.dcn_contact_get_name(this.dc_contact)
|
||||
}
|
||||
|
||||
@@ -1930,6 +1930,13 @@ NAPI_METHOD(dcn_contact_get_last_seen) {
|
||||
NAPI_RETURN_INT64(timestamp);
|
||||
}
|
||||
|
||||
NAPI_METHOD(dcn_contact_was_seen_recently) {
|
||||
NAPI_ARGV(1);
|
||||
NAPI_DC_CONTACT();
|
||||
int seen_recently = dc_contact_was_seen_recently(dc_contact);
|
||||
NAPI_RETURN_UINT32(seen_recently);
|
||||
}
|
||||
|
||||
NAPI_METHOD(dcn_contact_is_blocked) {
|
||||
NAPI_ARGV(1);
|
||||
NAPI_DC_CONTACT();
|
||||
@@ -3105,14 +3112,14 @@ static void accounts_event_handler_thread_func(void* arg)
|
||||
|
||||
TRACE("event_handler_thread_func starting");
|
||||
|
||||
dc_accounts_event_emitter_t * dc_accounts_event_emitter = dc_accounts_get_event_emitter(dcn_accounts->dc_accounts);
|
||||
dc_event_emitter_t * dc_event_emitter = dc_accounts_get_event_emitter(dcn_accounts->dc_accounts);
|
||||
dc_event_t* event;
|
||||
while (true) {
|
||||
if (dc_accounts_event_emitter == NULL) {
|
||||
if (dc_event_emitter == NULL) {
|
||||
TRACE("event emitter is null, bailing");
|
||||
break;
|
||||
}
|
||||
event = dc_accounts_get_next_event(dc_accounts_event_emitter);
|
||||
event = dc_get_next_event(dc_event_emitter);
|
||||
if (event == NULL) {
|
||||
TRACE("no more events");
|
||||
break;
|
||||
@@ -3138,7 +3145,7 @@ static void accounts_event_handler_thread_func(void* arg)
|
||||
}
|
||||
}
|
||||
|
||||
dc_accounts_event_emitter_unref(dc_accounts_event_emitter);
|
||||
dc_event_emitter_unref(dc_event_emitter);
|
||||
|
||||
TRACE("event_handler_thread_func ended");
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[devpi:upload]
|
||||
formats = sdist.tgz
|
||||
no-vcs = 1
|
||||
|
||||
@@ -8,7 +8,7 @@ set -e -x
|
||||
|
||||
# compile core lib
|
||||
|
||||
cargo build --release -p deltachat_ffi
|
||||
cargo build --release -p deltachat_ffi --features jsonrpc
|
||||
|
||||
# Statically link against libdeltachat.a.
|
||||
export DCC_RS_DEV="$PWD"
|
||||
|
||||
@@ -42,15 +42,15 @@ def replace_toml_version(relpath, newversion):
|
||||
|
||||
|
||||
def read_json_version(relpath):
|
||||
p = pathlib.Path("package.json")
|
||||
p = pathlib.Path(relpath)
|
||||
assert p.exists()
|
||||
with open(p, "r") as f:
|
||||
json_data = json.loads(f.read())
|
||||
return json_data["version"]
|
||||
|
||||
|
||||
def update_package_json(newversion):
|
||||
p = pathlib.Path("package.json")
|
||||
def update_package_json(relpath, newversion):
|
||||
p = pathlib.Path(relpath)
|
||||
assert p.exists()
|
||||
with open(p, "r") as f:
|
||||
json_data = json.loads(f.read())
|
||||
@@ -64,13 +64,15 @@ def main():
|
||||
parser.add_argument("newversion")
|
||||
|
||||
toml_list = ["Cargo.toml", "deltachat-ffi/Cargo.toml", "deltachat-jsonrpc/Cargo.toml"]
|
||||
json_list = ["package.json", "deltachat-jsonrpc/typescript/package.json"]
|
||||
try:
|
||||
opts = parser.parse_args()
|
||||
except SystemExit:
|
||||
print()
|
||||
for x in toml_list:
|
||||
print("{}: {}".format(x, read_toml_version(x)))
|
||||
print("package.json:", str(read_json_version("package.json")))
|
||||
for x in json_list:
|
||||
print("{}: {}".format(x, read_json_version(x)))
|
||||
print()
|
||||
raise SystemExit("need argument: new version, example: 1.25.0")
|
||||
|
||||
@@ -91,9 +93,12 @@ def main():
|
||||
else:
|
||||
raise SystemExit("CHANGELOG.md contains no entry for version: {}".format(newversion))
|
||||
|
||||
replace_toml_version("Cargo.toml", newversion)
|
||||
replace_toml_version("deltachat-ffi/Cargo.toml", newversion)
|
||||
update_package_json(newversion)
|
||||
for toml_filename in toml_list:
|
||||
replace_toml_version(toml_filename, newversion)
|
||||
|
||||
for json_filename in json_list:
|
||||
update_package_json(json_filename, newversion)
|
||||
|
||||
|
||||
print("running cargo check")
|
||||
subprocess.call(["cargo", "check"])
|
||||
|
||||
@@ -2525,7 +2525,7 @@ pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec
|
||||
LEFT JOIN contacts c
|
||||
ON c.id=cc.contact_id
|
||||
WHERE cc.chat_id=?
|
||||
ORDER BY c.id=1, LOWER(c.name||c.addr), c.id;",
|
||||
ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
|
||||
paramsv![chat_id],
|
||||
|row| row.get::<_, ContactId>(0),
|
||||
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
@@ -2863,7 +2863,7 @@ pub async fn remove_contact_from_chat(
|
||||
let mut success = false;
|
||||
|
||||
/* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */
|
||||
/* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */
|
||||
/* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */
|
||||
if let Ok(chat) = Chat::load_from_db(context, chat_id).await {
|
||||
if chat.typ == Chattype::Group || chat.typ == Chattype::Broadcast {
|
||||
if !chat.is_self_in_chat(context).await? {
|
||||
@@ -5456,8 +5456,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
chat_id.get_encryption_info(&alice).await?,
|
||||
"No encryption:\n\
|
||||
bob@example.net\n\
|
||||
fiona@example.net"
|
||||
fiona@example.net\n\
|
||||
bob@example.net"
|
||||
);
|
||||
|
||||
let direct_chat = bob.create_chat(&alice).await;
|
||||
|
||||
@@ -24,9 +24,12 @@ use crate::mimeparser::AvatarAction;
|
||||
use crate::param::{Param, Params};
|
||||
use crate::peerstate::{Peerstate, PeerstateVerifiedStatus};
|
||||
use crate::sql::{self, params_iter};
|
||||
use crate::tools::{get_abs_path, improve_single_line_input, EmailAddress};
|
||||
use crate::tools::{get_abs_path, improve_single_line_input, time, EmailAddress};
|
||||
use crate::{chat, stock_str};
|
||||
|
||||
/// Time during which a contact is considered as seen recently.
|
||||
const SEEN_RECENTLY_SECONDS: i64 = 600;
|
||||
|
||||
/// Contact ID, including reserved IDs.
|
||||
///
|
||||
/// Some contact IDs are reserved to identify special contacts. This
|
||||
@@ -323,6 +326,11 @@ impl Contact {
|
||||
self.last_seen
|
||||
}
|
||||
|
||||
/// Returns `true` if this contact was seen recently.
|
||||
pub fn was_seen_recently(&self) -> bool {
|
||||
time() - self.last_seen <= SEEN_RECENTLY_SECONDS
|
||||
}
|
||||
|
||||
/// Check if a contact is blocked.
|
||||
pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
|
||||
let blocked = Self::load_from_db(context, id).await?.blocked;
|
||||
@@ -708,7 +716,7 @@ impl Contact {
|
||||
AND c.blocked=0 \
|
||||
AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
|
||||
AND (1=? OR LENGTH(ps.verified_key_fingerprint)!=0) \
|
||||
ORDER BY LOWER(iif(c.name='',c.authname,c.name)||c.addr),c.id;",
|
||||
ORDER BY c.last_seen DESC, c.id DESC;",
|
||||
sql::repeat_vars(self_addrs.len())
|
||||
),
|
||||
rusqlite::params_from_iter(params_iter(&self_addrs).chain(params_iterv![
|
||||
@@ -760,7 +768,7 @@ impl Contact {
|
||||
AND id>?
|
||||
AND origin>=?
|
||||
AND blocked=0
|
||||
ORDER BY LOWER(iif(name='',authname,name)||addr),id;",
|
||||
ORDER BY last_seen DESC, id DESC;",
|
||||
sql::repeat_vars(self_addrs.len())
|
||||
),
|
||||
rusqlite::params_from_iter(params_iter(&self_addrs).chain(params_iterv![
|
||||
@@ -849,7 +857,7 @@ impl Contact {
|
||||
let list = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(iif(name='',authname,name)||addr),id;",
|
||||
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
|
||||
paramsv![ContactId::LAST_SPECIAL],
|
||||
|row| row.get::<_, ContactId>(0),
|
||||
|ids| {
|
||||
@@ -1444,7 +1452,7 @@ mod tests {
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::message::Message;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{self, TestContext};
|
||||
use crate::test_utils::{self, TestContext, TestContextManager};
|
||||
|
||||
#[test]
|
||||
fn test_contact_id_values() {
|
||||
@@ -2270,4 +2278,28 @@ Hi."#;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_was_seen_recently() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
let chat = alice.create_chat(&bob).await;
|
||||
let sent_msg = alice.send_text(chat.id, "moin").await;
|
||||
|
||||
let chat = bob.create_chat(&alice).await;
|
||||
let contacts = chat::get_chat_contacts(&bob, chat.id).await?;
|
||||
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
|
||||
assert!(!contact.was_seen_recently());
|
||||
|
||||
bob.recv_msg(&sent_msg).await;
|
||||
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
|
||||
assert!(contact.was_seen_recently());
|
||||
|
||||
let self_contact = Contact::get_by_id(&bob, ContactId::SELF).await?;
|
||||
assert!(!self_contact.was_seen_recently());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,4 +302,17 @@ pub enum EventType {
|
||||
msg_id: MsgId,
|
||||
status_update_serial: StatusUpdateSerial,
|
||||
},
|
||||
|
||||
/// Inform that a message containing a webxdc instance has been deleted
|
||||
WebxdcInstanceDeleted {
|
||||
msg_id: MsgId,
|
||||
},
|
||||
|
||||
WebxdcBusyUpdating {
|
||||
msg_id: MsgId,
|
||||
},
|
||||
|
||||
WebxdcUpToDate {
|
||||
msg_id: MsgId,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1236,6 +1236,11 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
|
||||
.trash(context)
|
||||
.await
|
||||
.with_context(|| format!("Unable to trash message {}", msg_id))?;
|
||||
|
||||
if msg.viewtype == Viewtype::Webxdc {
|
||||
context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: *msg_id });
|
||||
}
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
|
||||
@@ -445,6 +445,8 @@ impl<'a> MimeFactory<'a> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Consumes a `MimeFactory` and renders it into a message which is then stored in
|
||||
/// `smtp`-table to be used by the SMTP loop
|
||||
pub async fn render(mut self, context: &Context) -> Result<RenderedEmail> {
|
||||
let mut headers: MessageHeaders = Default::default();
|
||||
|
||||
|
||||
@@ -551,7 +551,7 @@ impl MimeMessage {
|
||||
|
||||
// For mailing lists, always add the subject because sometimes there are different topics
|
||||
// and otherwise it might be hard to keep track:
|
||||
if self.is_mailinglist_message() {
|
||||
if self.is_mailinglist_message() && !self.has_chat_version() {
|
||||
prepend_subject = true;
|
||||
}
|
||||
|
||||
@@ -746,7 +746,7 @@ impl MimeMessage {
|
||||
MimeS::Single
|
||||
}
|
||||
} else if mimetype.starts_with("message") {
|
||||
if mimetype == "message/rfc822" {
|
||||
if mimetype == "message/rfc822" && !is_attachment_disposition(mail) {
|
||||
MimeS::Message
|
||||
} else {
|
||||
MimeS::Single
|
||||
@@ -1674,14 +1674,18 @@ fn get_mime_type(mail: &mailparse::ParsedMail<'_>) -> Result<(Mime, Viewtype)> {
|
||||
mime::VIDEO => Viewtype::Video,
|
||||
mime::MULTIPART => Viewtype::Unknown,
|
||||
mime::MESSAGE => {
|
||||
// Enacapsulated messages, see <https://www.w3.org/Protocols/rfc1341/7_3_Message.html>
|
||||
// Also used as part "message/disposition-notification" of "multipart/report", which, however, will
|
||||
// be handled separatedly.
|
||||
// I've not seen any messages using this, so we do not attach these parts (maybe they're used to attach replies,
|
||||
// which are unwanted at all).
|
||||
// For now, we skip these parts at all; if desired, we could return DcMimeType::File/DC_MSG_File
|
||||
// for selected and known subparts.
|
||||
Viewtype::Unknown
|
||||
if is_attachment_disposition(mail) {
|
||||
Viewtype::File
|
||||
} else {
|
||||
// Enacapsulated messages, see <https://www.w3.org/Protocols/rfc1341/7_3_Message.html>
|
||||
// Also used as part "message/disposition-notification" of "multipart/report", which, however, will
|
||||
// be handled separatedly.
|
||||
// I've not seen any messages using this, so we do not attach these parts (maybe they're used to attach replies,
|
||||
// which are unwanted at all).
|
||||
// For now, we skip these parts at all; if desired, we could return DcMimeType::File/DC_MSG_File
|
||||
// for selected and known subparts.
|
||||
Viewtype::Unknown
|
||||
}
|
||||
}
|
||||
mime::APPLICATION => Viewtype::File,
|
||||
_ => Viewtype::Unknown,
|
||||
@@ -3299,4 +3303,36 @@ Message.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_receive_eml() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
|
||||
let mime_message = MimeMessage::from_bytes(
|
||||
&alice,
|
||||
include_bytes!("../test-data/message/attached-eml.eml"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_eq!(mime_message.parts.len(), 1);
|
||||
assert_eq!(mime_message.parts[0].typ, Viewtype::File);
|
||||
assert_eq!(
|
||||
mime_message.parts[0].mimetype,
|
||||
Some("message/rfc822".parse().unwrap(),)
|
||||
);
|
||||
assert_eq!(
|
||||
mime_message.parts[0].msg,
|
||||
"this is a classic email – I attached the .EML file".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
mime_message.parts[0].param.get(Param::File),
|
||||
Some("$BLOBDIR/.eml")
|
||||
);
|
||||
|
||||
assert_eq!(mime_message.parts[0].org_filename, Some(".eml".to_string()));
|
||||
|
||||
dbg!(mime_message);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ pub enum Param {
|
||||
/// For Chats: timestamp of group name update.
|
||||
GroupNameTimestamp = b'g',
|
||||
|
||||
/// For Chats: timestamp of group name update.
|
||||
/// For Chats: timestamp of member list update.
|
||||
MemberListTimestamp = b'k',
|
||||
|
||||
/// For Chats: timestamp of protection settings update.
|
||||
|
||||
@@ -388,6 +388,9 @@ pub async fn from_field_to_contact_id(
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `ReceivedMsg` from given parts which might consist of
|
||||
/// mulitple messages (if there are multiple attachments).
|
||||
/// Every entry in `mime_parser.parts` produces a new row in the `msgs` table.
|
||||
#[allow(clippy::too_many_arguments, clippy::cognitive_complexity)]
|
||||
async fn add_parts(
|
||||
context: &Context,
|
||||
@@ -492,7 +495,7 @@ async fn add_parts(
|
||||
}
|
||||
|
||||
let test_normal_chat = if from_id == ContactId::UNDEFINED {
|
||||
Default::default()
|
||||
None
|
||||
} else {
|
||||
ChatIdBlocked::lookup_by_contact(context, from_id).await?
|
||||
};
|
||||
@@ -513,6 +516,9 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
// signals wether the current user is a bot
|
||||
let is_bot = context.get_config(Config::Bot).await?.is_some();
|
||||
|
||||
if chat_id.is_none() {
|
||||
// try to create a group
|
||||
|
||||
@@ -521,6 +527,7 @@ async fn add_parts(
|
||||
id: _,
|
||||
blocked: Blocked::Not,
|
||||
}) => Blocked::Not,
|
||||
_ if is_bot => Blocked::Not,
|
||||
_ => Blocked::Request,
|
||||
};
|
||||
|
||||
@@ -540,6 +547,9 @@ async fn add_parts(
|
||||
{
|
||||
chat_id = Some(new_chat_id);
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
|
||||
// if the chat is somehow blocked but we want to create a non-blocked chat,
|
||||
// unblock the chat
|
||||
if chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not {
|
||||
new_chat_id.unblock(context).await?;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
@@ -637,10 +647,10 @@ async fn add_parts(
|
||||
Blocked::Not
|
||||
} else {
|
||||
let contact = Contact::load_from_db(context, from_id).await?;
|
||||
if contact.is_blocked() {
|
||||
Blocked::Yes
|
||||
} else {
|
||||
Blocked::Request
|
||||
match contact.is_blocked() {
|
||||
true => Blocked::Yes,
|
||||
false if is_bot => Blocked::Not,
|
||||
false => Blocked::Request,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -676,12 +686,17 @@ async fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
state =
|
||||
if seen || fetching_existing_messages || is_mdn || location_kml_is || securejoin_seen {
|
||||
MessageState::InSeen
|
||||
} else {
|
||||
MessageState::InFresh
|
||||
};
|
||||
state = if seen
|
||||
|| fetching_existing_messages
|
||||
|| is_mdn
|
||||
|| location_kml_is
|
||||
|| securejoin_seen
|
||||
|| chat_id_blocked == Blocked::Yes
|
||||
{
|
||||
MessageState::InSeen
|
||||
} else {
|
||||
MessageState::InFresh
|
||||
};
|
||||
} else {
|
||||
// Outgoing
|
||||
|
||||
@@ -3106,8 +3121,14 @@ Hello mailinglist!\r\n"
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0); // Test that the message disappeared
|
||||
|
||||
t.evtracker.consume_events().await;
|
||||
receive_imf(&t.ctx, DC_MAILINGLIST2, false).await.unwrap();
|
||||
|
||||
// Check that no notification is displayed for blocked mailing list message.
|
||||
while let Ok(event) = t.evtracker.try_recv() {
|
||||
assert!(!matches!(event.typ, EventType::IncomingMsg { .. }));
|
||||
}
|
||||
|
||||
// Test that the mailing list stays disappeared
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0); // Test that the message is not shown
|
||||
@@ -3551,6 +3572,27 @@ Hello mailinglist!\r\n"
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_mailing_list_chat_message() {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
receive_imf(
|
||||
&t,
|
||||
include_bytes!("../test-data/message/mailinglist_chat_message.eml"),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let msg = t.get_last_msg().await;
|
||||
assert_eq!(msg.text, Some("hello, this is a test 👋\n\n_______________________________________________\nTest1 mailing list -- test1@example.net\nTo unsubscribe send an email to test1-leave@example.net".to_string()));
|
||||
assert!(!msg.has_html());
|
||||
let chat = Chat::load_from_db(&t, msg.chat_id).await.unwrap();
|
||||
assert_eq!(chat.typ, Chattype::Mailinglist);
|
||||
assert_eq!(chat.blocked, Blocked::Request);
|
||||
assert_eq!(chat.grpid, "test1.example.net");
|
||||
assert_eq!(chat.name, "Test1");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_dont_show_tokens_in_contacts_list() {
|
||||
check_dont_show_in_contacts_list(
|
||||
@@ -5079,6 +5121,17 @@ Reply from different address
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_auto_accept_for_bots() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
t.set_config(Config::Bot, Some("1")).await.unwrap();
|
||||
receive_imf(&t, MSGRMSG, false).await?;
|
||||
let msg = t.get_last_msg().await;
|
||||
let chat = chat::Chat::load_from_db(&t, msg.chat_id).await?;
|
||||
assert!(!chat.is_contact_request());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_private_reply_to_blocked_account() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new().await;
|
||||
|
||||
@@ -332,7 +332,7 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
|
||||
if !duration_until_can_send.is_zero() {
|
||||
info!(
|
||||
ctx,
|
||||
"smtp got rate limited, waiting for {} until can send again",
|
||||
"smtp got rate limited, delaying next try by {}",
|
||||
duration_to_str(duration_until_can_send)
|
||||
);
|
||||
tokio::time::timeout(duration_until_can_send, async {
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::mimefactory::MimeFactory;
|
||||
use crate::oauth2::get_oauth2_access_token;
|
||||
use crate::provider::Socket;
|
||||
use crate::sql;
|
||||
use crate::webxdc::get_busy_webxdc_instances;
|
||||
use crate::{context::Context, scheduler::connectivity::ConnectivityStore};
|
||||
|
||||
/// SMTP write and read timeout in seconds.
|
||||
@@ -499,7 +500,15 @@ async fn send_mdns(context: &Context, connection: &mut Smtp) -> Result<()> {
|
||||
pub(crate) async fn send_smtp_messages(context: &Context, connection: &mut Smtp) -> Result<()> {
|
||||
let ratelimited = if context.ratelimit.read().await.can_send() {
|
||||
// add status updates and sync messages to end of sending queue
|
||||
|
||||
let update_needed = get_busy_webxdc_instances(&context.sql).await?;
|
||||
context.flush_status_updates().await?;
|
||||
let update_needed_after_sending = get_busy_webxdc_instances(&context.sql).await?;
|
||||
|
||||
for msg_id in update_needed.difference(&update_needed_after_sending) {
|
||||
context.emit_event(EventType::WebxdcUpToDate { msg_id: *msg_id })
|
||||
}
|
||||
|
||||
context.send_sync_msg().await?;
|
||||
false
|
||||
} else {
|
||||
|
||||
20
src/sql.rs
20
src/sql.rs
@@ -7,6 +7,7 @@ use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use rusqlite::types::FromSql;
|
||||
use rusqlite::{config::DbConfig, Connection, OpenFlags};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -363,6 +364,25 @@ impl Sql {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns unique values of a `column` in `table`
|
||||
pub async fn distinct<T: FromSql + Default>(
|
||||
&self,
|
||||
table: &str,
|
||||
column: &str,
|
||||
) -> Result<Vec<T>> {
|
||||
let conn = self.get_conn().await?;
|
||||
let rows: Result<Vec<T>> = tokio::task::block_in_place(move || {
|
||||
let mut stmt = conn.prepare(&format!("SELECT DISTINCT {column} FROM {table}"))?;
|
||||
let rows = stmt
|
||||
.query([])?
|
||||
.mapped(|r| r.get(0))
|
||||
.map(|a| a.unwrap_or_default())
|
||||
.collect();
|
||||
Ok(rows)
|
||||
});
|
||||
rows
|
||||
}
|
||||
|
||||
/// Prepares and executes the statement and maps a function over the resulting rows.
|
||||
/// Then executes the second function over the returned iterator and returns the
|
||||
/// result of that function.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! This private module is only compiled for test runs.
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::panic;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -816,6 +816,20 @@ pub fn fiona_keypair() -> key::KeyPair {
|
||||
#[derive(Debug)]
|
||||
pub struct EventTracker(Receiver<Event>);
|
||||
|
||||
impl Deref for EventTracker {
|
||||
type Target = Receiver<Event>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for EventTracker {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl EventTracker {
|
||||
/// Consumes emitted events returning the first matching one.
|
||||
///
|
||||
@@ -824,7 +838,7 @@ impl EventTracker {
|
||||
pub async fn get_matching<F: Fn(&EventType) -> bool>(&self, event_matcher: F) -> EventType {
|
||||
tokio::time::timeout(Duration::from_secs(10), async move {
|
||||
loop {
|
||||
let event = self.0.recv().await.unwrap();
|
||||
let event = self.recv().await.unwrap();
|
||||
if event_matcher(&event.typ) {
|
||||
return event.typ;
|
||||
}
|
||||
@@ -842,6 +856,11 @@ impl EventTracker {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Consumes all pending events.
|
||||
pub async fn consume_events(&self) {
|
||||
while self.try_recv().is_ok() {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a specific message from a chat and asserts that the chat has a specific length.
|
||||
|
||||
174
src/webxdc.rs
174
src/webxdc.rs
@@ -1,5 +1,6 @@
|
||||
//! # Handle webxdc messages.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -20,6 +21,7 @@ use crate::mimeparser::SystemMessage;
|
||||
use crate::param::Param;
|
||||
use crate::param::Params;
|
||||
use crate::scheduler::InterruptInfo;
|
||||
use crate::sql::Sql;
|
||||
use crate::tools::{create_smeared_timestamp, get_abs_path};
|
||||
use crate::{chat, EventType};
|
||||
|
||||
@@ -56,6 +58,7 @@ struct WebxdcManifest {
|
||||
name: Option<String>,
|
||||
min_api: Option<u32>,
|
||||
source_code_url: Option<String>,
|
||||
request_internet_access: Option<bool>,
|
||||
}
|
||||
|
||||
/// Parsed information from WebxdcManifest and fallbacks.
|
||||
@@ -66,6 +69,7 @@ pub struct WebxdcInfo {
|
||||
pub document: String,
|
||||
pub summary: String,
|
||||
pub source_code_url: String,
|
||||
pub internet_access: bool,
|
||||
}
|
||||
|
||||
/// Status Update ID.
|
||||
@@ -375,6 +379,10 @@ impl Context {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.emit_event(EventType::WebxdcBusyUpdating {
|
||||
msg_id: instance.id,
|
||||
});
|
||||
|
||||
if send_now {
|
||||
self.sql.insert(
|
||||
"INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) VALUES(?, ?, ?, ?)
|
||||
@@ -388,7 +396,6 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Pops one record of queued webxdc status updates.
|
||||
/// This function exists to make the sqlite statement testable.
|
||||
async fn pop_smtp_status_update(
|
||||
&self,
|
||||
) -> Result<Option<(MsgId, StatusUpdateSerial, StatusUpdateSerial, String)>> {
|
||||
@@ -487,6 +494,12 @@ impl Context {
|
||||
bail!("receive_status_update: status message has no parent.")
|
||||
};
|
||||
|
||||
if from_id != ContactId::SELF
|
||||
&& !chat::is_contact_in_chat(self, instance.chat_id, from_id).await?
|
||||
{
|
||||
bail!("receive_status_update: status sender not chat member.")
|
||||
}
|
||||
|
||||
let updates: StatusUpdates = serde_json::from_str(json)?;
|
||||
for update_item in updates.updates {
|
||||
self.create_status_update_record(
|
||||
@@ -676,6 +689,7 @@ impl Message {
|
||||
name: None,
|
||||
min_api: None,
|
||||
source_code_url: None,
|
||||
request_internet_access: None,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -683,6 +697,7 @@ impl Message {
|
||||
name: None,
|
||||
min_api: None,
|
||||
source_code_url: None,
|
||||
request_internet_access: None,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -694,6 +709,10 @@ impl Message {
|
||||
}
|
||||
}
|
||||
|
||||
let internet_access = manifest.request_internet_access.unwrap_or_default()
|
||||
&& self.chat_id.is_self_talk(context).await.unwrap_or_default()
|
||||
&& self.get_showpadlock();
|
||||
|
||||
Ok(WebxdcInfo {
|
||||
name: if let Some(name) = manifest.name {
|
||||
name
|
||||
@@ -712,29 +731,43 @@ impl Message {
|
||||
.get(Param::WebxdcDocument)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
summary: self
|
||||
.param
|
||||
.get(Param::WebxdcSummary)
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
summary: if internet_access {
|
||||
"Dev Mode: Do not enter sensitive data!".to_string()
|
||||
} else {
|
||||
self.param
|
||||
.get(Param::WebxdcSummary)
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
},
|
||||
source_code_url: if let Some(url) = manifest.source_code_url {
|
||||
url
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
internet_access,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a hashset of all webxdc instaces which still have updates to send
|
||||
pub(crate) async fn get_busy_webxdc_instances(sql: &Sql) -> Result<HashSet<MsgId>> {
|
||||
Ok(sql
|
||||
.distinct("smtp_status_updates", "msg_id")
|
||||
.await?
|
||||
.into_iter()
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::chat::{
|
||||
add_contact_to_chat, create_group_chat, forward_msgs, resend_msgs, send_msg, send_text_msg,
|
||||
ChatId, ProtectionStatus,
|
||||
add_contact_to_chat, create_broadcast_list, create_group_chat, forward_msgs,
|
||||
remove_contact_from_chat, resend_msgs, send_msg, send_text_msg, ChatId, ProtectionStatus,
|
||||
};
|
||||
use crate::chatlist::Chatlist;
|
||||
use crate::config::Config;
|
||||
use crate::contact::Contact;
|
||||
use crate::message;
|
||||
use crate::receive_imf::{receive_imf, receive_imf_inner};
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
@@ -1781,6 +1814,19 @@ sth_for_the = "future""#
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_parse_webxdc_manifest_request_internet_access() -> Result<()> {
|
||||
let result = parse_webxdc_manifest(r#"request_internet_access = 3"#.as_bytes());
|
||||
assert!(result.is_err());
|
||||
let manifest = parse_webxdc_manifest(r#" source_code_url="https://foo.org""#.as_bytes())?;
|
||||
assert_eq!(manifest.request_internet_access, None);
|
||||
let manifest = parse_webxdc_manifest(r#" request_internet_access=false"#.as_bytes())?;
|
||||
assert_eq!(manifest.request_internet_access, Some(false));
|
||||
let manifest = parse_webxdc_manifest(r#"request_internet_access = true"#.as_bytes())?;
|
||||
assert_eq!(manifest.request_internet_access, Some(true));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_min_api_too_large() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -2191,6 +2237,51 @@ sth_for_the = "future""#
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_internet_access() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
let self_id = t.get_self_chat().await.id;
|
||||
let single_id = t.create_chat_with_contact("bob", "bob@e.com").await.id;
|
||||
let group_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
|
||||
let broadcast_id = create_broadcast_list(&t).await?;
|
||||
|
||||
let mut first_test = true; // only the first test has all conditions for internet access
|
||||
|
||||
for e2ee in ["1", "0"] {
|
||||
t.set_config(Config::E2eeEnabled, Some(e2ee)).await?;
|
||||
for chat_id in [self_id, single_id, group_id, broadcast_id] {
|
||||
for internet_xdc in [true, false] {
|
||||
let mut instance = create_webxdc_instance(
|
||||
&t,
|
||||
"foo.xdc",
|
||||
if internet_xdc {
|
||||
include_bytes!("../test-data/webxdc/request-internet-access.xdc")
|
||||
} else {
|
||||
include_bytes!("../test-data/webxdc/minimal.xdc")
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let instance_id = send_msg(&t, chat_id, &mut instance).await?;
|
||||
t.send_webxdc_status_update(
|
||||
instance_id,
|
||||
r#"{"summary":"real summary", "payload": 42}"#,
|
||||
"descr",
|
||||
)
|
||||
.await?;
|
||||
let instance = Message::load_from_db(&t, instance_id).await?;
|
||||
let info = instance.get_webxdc_info(&t).await?;
|
||||
assert_eq!(info.internet_access, first_test);
|
||||
assert_eq!(info.summary.contains("Do not enter sensitive"), first_test);
|
||||
assert_eq!(info.summary.contains("real summary"), !first_test);
|
||||
|
||||
first_test = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_chatlist_summary() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
@@ -2247,4 +2338,71 @@ sth_for_the = "future""#
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn helper_send_receive_status_update(
|
||||
bob: &TestContext,
|
||||
alice: &TestContext,
|
||||
bob_instance: &Message,
|
||||
alice_instance: &Message,
|
||||
) -> Result<String> {
|
||||
bob.send_webxdc_status_update(
|
||||
bob_instance.id,
|
||||
r#"{"payload":7,"info": "i","summary":"s"}"#,
|
||||
"",
|
||||
)
|
||||
.await?;
|
||||
bob.flush_status_updates().await?;
|
||||
let msg = bob.pop_sent_msg().await;
|
||||
alice.recv_msg(&msg).await;
|
||||
alice
|
||||
.get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial(0))
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_reject_updates_from_non_groupmembers() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let bob = TestContext::new_bob().await;
|
||||
let contact_bob = Contact::create(&alice, "Bob", "bob@example.net").await?;
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "Group").await?;
|
||||
add_contact_to_chat(&alice, chat_id, contact_bob).await?;
|
||||
let instance = send_webxdc_instance(&alice, chat_id).await?;
|
||||
bob.recv_msg(&alice.pop_sent_msg().await).await;
|
||||
let bob_instance = bob.get_last_msg().await;
|
||||
Chat::load_from_db(&bob, bob_instance.chat_id)
|
||||
.await?
|
||||
.id
|
||||
.accept(&bob)
|
||||
.await?;
|
||||
|
||||
let status =
|
||||
helper_send_receive_status_update(&bob, &alice, &bob_instance, &instance).await?;
|
||||
assert_eq!(
|
||||
status,
|
||||
r#"[{"payload":7,"info":"i","summary":"s","serial":1,"max_serial":1}]"#
|
||||
);
|
||||
|
||||
remove_contact_from_chat(&alice, chat_id, contact_bob).await?;
|
||||
let status =
|
||||
helper_send_receive_status_update(&bob, &alice, &bob_instance, &instance).await?;
|
||||
|
||||
assert_eq!(
|
||||
status,
|
||||
r#"[{"payload":7,"info":"i","summary":"s","serial":1,"max_serial":1}]"#
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_webxdc_delete_event() -> Result<()> {
|
||||
let alice = TestContext::new_alice().await;
|
||||
let chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
|
||||
let instance = send_webxdc_instance(&alice, chat_id).await?;
|
||||
message::delete_msgs(&alice, &[instance.id]).await?;
|
||||
alice
|
||||
.evtracker
|
||||
.get_matching(|evt| matches!(evt, EventType::WebxdcInstanceDeleted { .. }))
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
138
test-data/message/attached-eml.eml
Normal file
138
test-data/message/attached-eml.eml
Normal file
@@ -0,0 +1,138 @@
|
||||
Return-Path: <bob@nauta.cu>
|
||||
Authentication-Results: gmx.net; dkim=pass header.i=@nauta.cu
|
||||
Received: from relay22.etecsa.net ([181.225.231.92]) by mx-ha.gmx.net
|
||||
(mxgmx002 [212.227.15.9]) with ESMTPS (Nemesis) id 1MfpjH-1p2tvu2MDh-00gIrC
|
||||
for <alice@gmx.de>; Fri, 26 Aug 2022 20:48:28 +0200
|
||||
Received: from smtp-gw02.enet.cu (unknown [172.29.8.32])
|
||||
by relay01.etecsa.net (Postfix) with ESMTP id 5FDB0E0136
|
||||
for <alice@gmx.de>; Fri, 26 Aug 2022 14:48:18 -0400 (CDT)
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nauta.cu; s=nauta;
|
||||
t=1661539698; bh=pp7iY44vKTrdWyBjLR1PLAHM8rgsP+R0LngZ+hpp+Rc=;
|
||||
h=Date:From:To:Subject:In-Reply-To:References;
|
||||
b=H9gt32amS79HP/93yWsIeeMN28cpD/ly9yfTUkkLa+N7oKhUmTgvYd6AS4ztyoMRl
|
||||
U18Gl9OsFlxKiUtJDA9xbXiJShJ1ixWZjsPWrRnsdlRC6p1NurZcZVC5LlxzMeqHaf
|
||||
S+T9O6mIqqpMJ0UuEdNPQzFM22vCo7NAXuYICva4=
|
||||
Received: from amavis2.etecsa.net (unknown [172.29.8.231])
|
||||
by smtp-gw02.enet.cu (Postfix) with ESMTP id 74CBF180004C
|
||||
for <alice@gmx.de>; Fri, 26 Aug 2022 14:48:15 -0400 (CDT)
|
||||
Received: from smtp.nauta.cu ([172.29.8.248])
|
||||
by amavis2.etecsa.net with ESMTP id 27QImIS3008021-27QImIS4008021;
|
||||
Fri, 26 Aug 2022 14:48:18 -0400
|
||||
Date: Fri, 26 Aug 2022 14:48:17 -0400
|
||||
From: adb <bob@nauta.cu>
|
||||
To: alice@gmx.de
|
||||
Subject: this is a classic email
|
||||
User-Agent: K-9 Mail for Android
|
||||
In-Reply-To: <Mr.qEqZTYn44yJ.1aNPpfpFAA-@disrooot.org>
|
||||
References: <Mr.qEqZTYn44yJ.1aNPpfpFAA-@disrooot.org>
|
||||
Message-ID: <2DFA2B07-37E8-4E91-84D5-F1C9D81FF9E2@nauta.cu>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/mixed;
|
||||
boundary=----X9MFOPC8VAUATAMM54N60RYO457N0T
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Envelope-To: <alice@gmx.de>
|
||||
X-GMX-Antispam: 0 (Mail was not recognized as spam); Detail=V3;
|
||||
X-Spam-Flag: NO
|
||||
|
||||
------X9MFOPC8VAUATAMM54N60RYO457N0T
|
||||
Content-Type: multipart/alternative;
|
||||
boundary=----S5IK4AODBC4A8E4GQE13T7ZD9M5SU9
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
------S5IK4AODBC4A8E4GQE13T7ZD9M5SU9
|
||||
Content-Type: text/plain;
|
||||
charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
I attached the =2EEML file
|
||||
------S5IK4AODBC4A8E4GQE13T7ZD9M5SU9
|
||||
Content-Type: text/html;
|
||||
charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<!DOCTYPE html><html><body>I attached the =2EEML file it probably will look=
|
||||
ugly in delta chat, use classic MUA</body></html>
|
||||
------S5IK4AODBC4A8E4GQE13T7ZD9M5SU9--
|
||||
|
||||
------X9MFOPC8VAUATAMM54N60RYO457N0T
|
||||
Content-Type: message/rfc822;
|
||||
name=.eml
|
||||
Content-Disposition: attachment;
|
||||
filename=.eml;
|
||||
size=5349
|
||||
|
||||
Return-Path: <charlie@hisp.net>
|
||||
Delivered-To: <bob@nauta.cu>
|
||||
Received: from smtp-gw02.enet.cu ([172.29.8.32])
|
||||
by mail-backend21.enet.cu (Dovecot) with LMTP id 0fibJk4TCWP2QAAAhKHYxA
|
||||
for <bob@nauta.cu>; Fri, 26 Aug 2022 14:42:34 -0400
|
||||
Received: from amavis.etecsa.net (unknown [172.29.8.230])
|
||||
by smtp-gw02.enet.cu (Postfix) with ESMTP id 83A41180004D
|
||||
for <bob@nauta.cu>; Fri, 26 Aug 2022 14:42:31 -0400 (CDT)
|
||||
Received: from emx01.etecsa.net (emx01.etecsa.net [181.225.231.41])
|
||||
by amavis.etecsa.net with ESMTP id 27QIgTFM024250-27QIgTFN024250
|
||||
for <bob@nauta.cu>; Fri, 26 Aug 2022 14:42:29 -0400
|
||||
Received-SPF: pass (hisp.net: 51.15.127.36 is authorized to use 'charlie@hisp.net' in 'mfrom' identity (mechanism 'mx' matched)) receiver=unknown; identity=mailfrom; envelope-from="charlie@hisp.net"; helo=box.hisp.net; client-ip=51.15.127.36
|
||||
Received: from box.hisp.net (box.hisp.net [51.15.127.36])
|
||||
by emx01.etecsa.net (Postfix) with ESMTP id 9A8969F15B
|
||||
for <bob@nauta.cu>; Fri, 26 Aug 2022 14:42:28 -0400 (CDT)
|
||||
Received: from box.hisp.net (localhost [127.0.0.1])
|
||||
by box.hisp.net (Postfix) with ESMTP id 9621D7E44F
|
||||
for <bob@nauta.cu>; Fri, 26 Aug 2022 20:42:27 +0200 (CEST)
|
||||
Authentication-Results: box.hisp.net;
|
||||
dkim=fail reason="signature verification failed" (2048-bit key; secure) header.d=disrooot.org header.i=@disrooot.org header.b="kqh3WUKq";
|
||||
dkim-atps=neutral
|
||||
Authentication-Results: box.hisp.net; dmarc=pass (p=quarantine dis=none) header.from=disrooot.org
|
||||
Authentication-Results: box.hisp.net; spf=pass smtp.mailfrom=bob@disrooot.org
|
||||
Received: from localhost (localhost [127.0.0.1])
|
||||
by disrooot.org (Postfix) with UTF8SMTP id CB886400EA
|
||||
for <test2@hisp.net>; Fri, 26 Aug 2022 20:42:25 +0200 (CEST)
|
||||
X-Virus-Scanned: SPAM Filter at disrooot.org
|
||||
Received: from knopi.disrooot.org ([127.0.0.1])
|
||||
by localhost (disrooot.org [127.0.0.1]) (amavisd-new, port 10024)
|
||||
with UTF8SMTP id 8rOR0oFsvsSB for <test2@hisp.net>;
|
||||
Fri, 26 Aug 2022 20:42:24 +0200 (CEST)
|
||||
Received: from [127.0.0.1] (unknown [152.207.59.223])
|
||||
by disrooot.org (Postfix) with UTF8SMTPSA id 97C3440140
|
||||
for <test2@hisp.net>; Fri, 26 Aug 2022 20:42:21 +0200 (CEST)
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disrooot.org; s=mail;
|
||||
t=1661539342; bh=PDQuwT72ixzAL9uft0/IufLsqJSAuFu4gcNsEA84hd0=;
|
||||
h=From:To:Date:In-Reply-To:References;
|
||||
b=kqh3WUKqi5QMv4DT02hqyHcXZvcGu91aHu8bbSuZ2VCUnef5Ggwv0/WE5LBB/Xte2
|
||||
on7yxLG4eqXRkWCmWStlqJmPhwuGcDupyGy2pFNyvRl8eERPb/uSlTsoSNCIEf5fNT
|
||||
I2X+bEtyzp7jch3ZIxJgBZ/B3U/ByOO26NsqMP/Gy6ph21TL3YqWhR4VXi60KQXa6G
|
||||
6A1X1M6pppNGYXofI6qJtjyb+wfQuzeTMRO4hqNqPEnOjETmbFpHYqrBDzz7pL4bZj
|
||||
+Sdwj7cU+RpFJZPwbRs86wUV2lBIEyP4Pizyr/pgYcyXIt3pzSRHvBsukDQbyQabme
|
||||
LoyApyOJnzbSA==
|
||||
From: adb <bob@disrooot.org>
|
||||
To: <test2@hisp.net>
|
||||
Date: Fri, 26 Aug 2022 18:42:18 +0000
|
||||
Message-ID: <Mr.qEqZTYn44yJ.1aNPpfpFAA-@disrooot.org>
|
||||
In-Reply-To: <Mr.CmBUFS4tI6D.LHAPUDmW-x0@nauta.cu>
|
||||
References: <Mr.CmBUFS4tI6D.LHAPUDmW-x0@nauta.cu>
|
||||
<Mr.CmBUFS4tI6D.LHAPUDmW-x0@nauta.cu>
|
||||
Chat-Version: 1.0
|
||||
Mime-Version: 1.0
|
||||
Message-ID-Hash: KJBUH4PALETYB65FC5SOMXP5HRWUIQEH
|
||||
X-Message-ID-Hash: KJBUH4PALETYB65FC5SOMXP5HRWUIQEH
|
||||
X-MailFrom: bob@disrooot.org
|
||||
X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; deltachat; implicit-dest; max-recipients; max-size; news-moderation; digests; suspicious-header
|
||||
X-Mailman-Version: 3.3.6b1
|
||||
Precedence: list
|
||||
List-Id: =?utf-8?b?4pqg77iPIE5PVCBBTk9OWU1PVVMgR1JPVVA=?= <test2.hisp.net>
|
||||
List-Help: <mailto:test2-request@hisp.net?subject=help>
|
||||
List-Owner: <mailto:test2-owner@hisp.net>
|
||||
List-Post: <mailto:test2@hisp.net>
|
||||
List-Subscribe: <mailto:test2-join@hisp.net>
|
||||
List-Unsubscribe: <mailto:test2-leave@hisp.net>
|
||||
Content-Type: text/plain; charset="utf-8"; format="flowed"; delsp="no"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
aGVsbG8gZnJvbSBkaXNyb290IOKYuu+4jw0KDQotLSAKX19fX19fX19fX19fX19fX19fX19fX19f
|
||||
X19fX19fX19fX19fX19fX19fX19fX18K4pqg77iPIE5PVCBBTk9OWU1PVVMgR1JPVVAgbWFpbGlu
|
||||
ZyBsaXN0IC0tIHRlc3QyQGhpc3BhbmlsYW5kaWEubmV0ClRvIHVuc3Vic2NyaWJlIHNlbmQgYW4g
|
||||
ZW1haWwgdG8gdGVzdDItbGVhdmVAaGlzcGFuaWxhbmRpYS5uZXQK
|
||||
|
||||
|
||||
------X9MFOPC8VAUATAMM54N60RYO457N0T--
|
||||
|
||||
29
test-data/message/mailinglist_chat_message.eml
Normal file
29
test-data/message/mailinglist_chat_message.eml
Normal file
@@ -0,0 +1,29 @@
|
||||
From: <tmp.jvg4y@testrun.org>
|
||||
To: <test1@example.net>
|
||||
Date: Sat, 23 Apr 2022 00:40:47 +0000
|
||||
Message-ID: <Mr.rcN94EGkUqw.mz9DVEAlh1p@testrun.org>
|
||||
In-Reply-To: <Mr.olg8zDlL3HE.rImDv3GWJBk@testrun.org>
|
||||
References: <Mr.a3MAke9MKLq.NNRzMZnyTr6@example.org>
|
||||
<Mr.olg8zDlL3HE.rImDv3GWJBk@testrun.org>
|
||||
Chat-Version: 1.0
|
||||
MIME-Version: 1.0
|
||||
Message-ID-Hash: NTNAY2662T7LMIXRSA2VZMFR52VKP35F
|
||||
X-Message-ID-Hash: NTNAY2662T7LMIXRSA2VZMFR52VKP35F
|
||||
X-MailFrom: tmp.jvg4y@testrun.org
|
||||
X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header
|
||||
X-Mailman-Version: 3.3.6b1
|
||||
Precedence: list
|
||||
Subject: [Test1] Re: Message from alice@example.org
|
||||
List-Id: <test1.example.net>
|
||||
List-Help: <mailto:test1-request@example.net?subject=help>
|
||||
List-Owner: <mailto:test1-owner@example.net>
|
||||
List-Post: <mailto:test1@example.net>
|
||||
List-Subscribe: <mailto:test1-join@example.net>
|
||||
List-Unsubscribe: <mailto:test1-leave@example.net>
|
||||
Content-Type: text/plain; charset="utf-8"; format="flowed"; delsp="no"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
aGVsbG8sIHRoaXMgaXMgYSB0ZXN0IPCfkYsNCg0KX19fX19fX19fX19fX19fX19fX19fX19fX19f
|
||||
X19fX19fX19fX19fX19fX19fX18KVGVzdDEgbWFpbGluZyBsaXN0IC0tIHRlc3QxQGV4YW1wbGUu
|
||||
bmV0ClRvIHVuc3Vic2NyaWJlIHNlbmQgYW4gZW1haWwgdG8gdGVzdDEtbGVhdmVAZXhhbXBsZS5u
|
||||
ZXQK
|
||||
BIN
test-data/webxdc/request-internet-access.xdc
Normal file
BIN
test-data/webxdc/request-internet-access.xdc
Normal file
Binary file not shown.
Reference in New Issue
Block a user