mirror of
https://github.com/chatmail/core.git
synced 2026-04-05 23:22:11 +03:00
Compare commits
28 Commits
simon/feat
...
debug-iroh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
866fa57234 | ||
|
|
0def0e070d | ||
|
|
1b184af875 | ||
|
|
5a26a84fb0 | ||
|
|
f7a1ab627c | ||
|
|
4bed4b32f5 | ||
|
|
013eaba47f | ||
|
|
7aad78e894 | ||
|
|
41f39117af | ||
|
|
b9425577b4 | ||
|
|
90d30c4a35 | ||
|
|
97695d7e19 | ||
|
|
6bcb347426 | ||
|
|
24aa657984 | ||
|
|
f0bfa5869f | ||
|
|
df17d9b1da | ||
|
|
66fec82daf | ||
|
|
b501ab1532 | ||
|
|
e6087db69c | ||
|
|
9e8ee7b1c7 | ||
|
|
397e71a66a | ||
|
|
4bcc3d22aa | ||
|
|
ba3bc01e1b | ||
|
|
a1649a8258 | ||
|
|
96d43b6084 | ||
|
|
b95a593211 | ||
|
|
7b046692ae | ||
|
|
9fb003563b |
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@@ -7,10 +7,3 @@ updates:
|
|||||||
commit-message:
|
commit-message:
|
||||||
prefix: "chore(cargo)"
|
prefix: "chore(cargo)"
|
||||||
open-pull-requests-limit: 50
|
open-pull-requests-limit: 50
|
||||||
|
|
||||||
# Keep GitHub Actions up to date.
|
|
||||||
# <https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot>
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
|
|||||||
69
.github/workflows/ci.yml
vendored
69
.github/workflows/ci.yml
vendored
@@ -16,8 +16,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: -Dwarnings
|
RUSTFLAGS: -Dwarnings
|
||||||
|
|
||||||
@@ -26,12 +24,11 @@ jobs:
|
|||||||
name: Lint Rust
|
name: Lint Rust
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
RUSTUP_TOOLCHAIN: 1.84.1
|
RUSTUP_TOOLCHAIN: 1.78.0
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- name: Install rustfmt and clippy
|
- name: Install rustfmt and clippy
|
||||||
run: rustup toolchain install $RUSTUP_TOOLCHAIN --profile minimal --component rustfmt --component clippy
|
run: rustup toolchain install $RUSTUP_TOOLCHAIN --profile minimal --component rustfmt --component clippy
|
||||||
- name: Cache rust cargo artifacts
|
- name: Cache rust cargo artifacts
|
||||||
@@ -40,10 +37,20 @@ jobs:
|
|||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: scripts/clippy.sh
|
run: scripts/clippy.sh
|
||||||
- name: Check with all features
|
- name: Check
|
||||||
run: cargo check --workspace --all-targets --all-features
|
run: cargo check --workspace --all-targets --all-features
|
||||||
- name: Check with only default features
|
|
||||||
run: cargo check --all-targets
|
npm_constants:
|
||||||
|
name: Check if node constants are up to date
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
- name: Rebuild constants
|
||||||
|
run: npm run build:core:constants
|
||||||
|
- name: Check that constants are not changed
|
||||||
|
run: git diff --exit-code
|
||||||
|
|
||||||
cargo_deny:
|
cargo_deny:
|
||||||
name: cargo deny
|
name: cargo deny
|
||||||
@@ -52,8 +59,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
|
||||||
with:
|
with:
|
||||||
arguments: --all-features --workspace
|
arguments: --all-features --workspace
|
||||||
command: check
|
command: check
|
||||||
@@ -66,7 +72,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- name: Check provider database
|
- name: Check provider database
|
||||||
run: scripts/update-provider-database.sh
|
run: scripts/update-provider-database.sh
|
||||||
|
|
||||||
@@ -79,7 +84,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- name: Cache rust cargo artifacts
|
- name: Cache rust cargo artifacts
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
- name: Rustdoc
|
- name: Rustdoc
|
||||||
@@ -91,21 +95,20 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
rust: 1.84.1
|
rust: 1.78.0
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
rust: 1.84.1
|
rust: 1.78.0
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
rust: 1.84.1
|
rust: 1.78.0
|
||||||
|
|
||||||
# Minimum Supported Rust Version = 1.81.0
|
# Minimum Supported Rust Version = 1.77.0
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
rust: 1.81.0
|
rust: 1.77.0
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Rust ${{ matrix.rust }}
|
- name: Install Rust ${{ matrix.rust }}
|
||||||
run: rustup toolchain install --profile minimal ${{ matrix.rust }}
|
run: rustup toolchain install --profile minimal ${{ matrix.rust }}
|
||||||
@@ -122,6 +125,9 @@ jobs:
|
|||||||
- name: Tests
|
- name: Tests
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
|
# Workaround for <https://github.com/nextest-rs/nextest/issues/1493>.
|
||||||
|
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
|
||||||
run: cargo nextest run --workspace
|
run: cargo nextest run --workspace
|
||||||
|
|
||||||
- name: Doc-Tests
|
- name: Doc-Tests
|
||||||
@@ -142,13 +148,12 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Cache rust cargo artifacts
|
- name: Cache rust cargo artifacts
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Build C library
|
- name: Build C library
|
||||||
run: cargo build -p deltachat_ffi
|
run: cargo build -p deltachat_ffi --features jsonrpc
|
||||||
|
|
||||||
- name: Upload C library
|
- name: Upload C library
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -167,7 +172,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Cache rust cargo artifacts
|
- name: Cache rust cargo artifacts
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
@@ -189,7 +193,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install tox
|
- name: Install tox
|
||||||
run: pip install tox
|
run: pip install tox
|
||||||
@@ -211,9 +214,9 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
# Currently used Rust version.
|
# Currently used Rust version.
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.13
|
python: 3.12
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python: 3.13
|
python: 3.12
|
||||||
|
|
||||||
# PyPy tests
|
# PyPy tests
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
@@ -221,18 +224,17 @@ jobs:
|
|||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python: pypy3.10
|
python: pypy3.10
|
||||||
|
|
||||||
# Minimum Supported Python Version = 3.8
|
# Minimum Supported Python Version = 3.7
|
||||||
# This is the minimum version for which manylinux Python wheels are
|
# This is the minimum version for which manylinux Python wheels are
|
||||||
# built. Test it with minimum supported Rust version.
|
# built. Test it with minimum supported Rust version.
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.8
|
python: 3.7
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Download libdeltachat.a
|
- name: Download libdeltachat.a
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -250,7 +252,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Run python tests
|
- name: Run python tests
|
||||||
env:
|
env:
|
||||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||||
DCC_RS_TARGET: debug
|
DCC_RS_TARGET: debug
|
||||||
DCC_RS_DEV: ${{ github.workspace }}
|
DCC_RS_DEV: ${{ github.workspace }}
|
||||||
working-directory: python
|
working-directory: python
|
||||||
@@ -264,11 +266,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.13
|
python: 3.12
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python: 3.13
|
python: 3.12
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python: 3.13
|
python: 3.12
|
||||||
|
|
||||||
# PyPy tests
|
# PyPy tests
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
@@ -276,16 +278,15 @@ jobs:
|
|||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python: pypy3.10
|
python: pypy3.10
|
||||||
|
|
||||||
# Minimum Supported Python Version = 3.8
|
# Minimum Supported Python Version = 3.7
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.8
|
python: 3.7
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install python
|
- name: Install python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -316,6 +317,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Run deltachat-rpc-client tests
|
- name: Run deltachat-rpc-client tests
|
||||||
env:
|
env:
|
||||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||||
working-directory: deltachat-rpc-client
|
working-directory: deltachat-rpc-client
|
||||||
run: tox -e py
|
run: tox -e py
|
||||||
|
|||||||
24
.github/workflows/deltachat-rpc-server.yml
vendored
24
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -17,8 +17,6 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Build a version statically linked against musl libc
|
# Build a version statically linked against musl libc
|
||||||
# to avoid problems with glibc version incompatibility.
|
# to avoid problems with glibc version incompatibility.
|
||||||
@@ -33,8 +31,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server binaries
|
- name: Build deltachat-rpc-server binaries
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||||
@@ -57,8 +55,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server binaries
|
- name: Build deltachat-rpc-server binaries
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
||||||
@@ -82,7 +80,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Setup rust target
|
- name: Setup rust target
|
||||||
run: rustup target add ${{ matrix.arch }}-apple-darwin
|
run: rustup target add ${{ matrix.arch }}-apple-darwin
|
||||||
@@ -108,8 +105,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server binaries
|
- name: Build deltachat-rpc-server binaries
|
||||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
||||||
@@ -135,8 +132,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
- name: Download Linux aarch64 binary
|
- name: Download Linux aarch64 binary
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -248,10 +245,6 @@ jobs:
|
|||||||
cp result/*.whl dist/
|
cp result/*.whl dist/
|
||||||
nix build .#deltachat-rpc-server-win32-wheel
|
nix build .#deltachat-rpc-server-win32-wheel
|
||||||
cp result/*.whl dist/
|
cp result/*.whl dist/
|
||||||
nix build .#deltachat-rpc-server-arm64-v8a-android-wheel
|
|
||||||
cp result/*.whl dist/
|
|
||||||
nix build .#deltachat-rpc-server-armeabi-v7a-android-wheel
|
|
||||||
cp result/*.whl dist/
|
|
||||||
nix build .#deltachat-rpc-server-source
|
nix build .#deltachat-rpc-server-source
|
||||||
cp result/*.tar.gz dist/
|
cp result/*.tar.gz dist/
|
||||||
python3 scripts/wheel-rpc-server.py x86_64-darwin bin/deltachat-rpc-server-x86_64-macos
|
python3 scripts/wheel-rpc-server.py x86_64-darwin bin/deltachat-rpc-server-x86_64-macos
|
||||||
@@ -265,9 +258,8 @@ jobs:
|
|||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
REF_NAME: ${{ github.ref_name }}
|
|
||||||
run: |
|
run: |
|
||||||
gh release upload "$REF_NAME" \
|
gh release upload ${{ github.ref_name }} \
|
||||||
--repo ${{ github.repository }} \
|
--repo ${{ github.repository }} \
|
||||||
bin/* dist/*
|
bin/* dist/*
|
||||||
|
|
||||||
@@ -288,7 +280,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
@@ -394,9 +385,8 @@ jobs:
|
|||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
REF_NAME: ${{ github.ref_name }}
|
|
||||||
run: |
|
run: |
|
||||||
gh release upload "$REF_NAME" \
|
gh release upload ${{ github.ref_name }} \
|
||||||
--repo ${{ github.repository }} \
|
--repo ${{ github.repository }} \
|
||||||
deltachat-rpc-server/npm-package/*.tgz
|
deltachat-rpc-server/npm-package/*.tgz
|
||||||
|
|
||||||
@@ -411,6 +401,6 @@ jobs:
|
|||||||
working-directory: deltachat-rpc-server/npm-package
|
working-directory: deltachat-rpc-server/npm-package
|
||||||
run: |
|
run: |
|
||||||
ls -lah platform_package
|
ls -lah platform_package
|
||||||
for platform in *.tgz; do npm publish --provenance "$platform" --access public; done
|
for platform in *.tgz; do npm publish --provenance "$platform"; done
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/dependabot.yml
vendored
2
.github/workflows/dependabot.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Dependabot metadata
|
- name: Dependabot metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: dependabot/fetch-metadata@v2.3.0
|
uses: dependabot/fetch-metadata@v1.1.1
|
||||||
with:
|
with:
|
||||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
- name: Approve a PR
|
- name: Approve a PR
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pack-module:
|
pack-module:
|
||||||
name: "Publish @deltachat/jsonrpc-client"
|
name: "Publish @deltachat/jsonrpc-client"
|
||||||
@@ -17,7 +15,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -36,6 +33,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
working-directory: deltachat-jsonrpc/typescript
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
run: npm publish --provenance deltachat-jsonrpc-client-* --access public
|
run: npm publish --provenance deltachat-jsonrpc-client-*
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
8
.github/workflows/jsonrpc.yml
vendored
8
.github/workflows/jsonrpc.yml
vendored
@@ -6,8 +6,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
RUST_MIN_STACK: "8388608"
|
RUST_MIN_STACK: "8388608"
|
||||||
@@ -19,7 +17,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -36,7 +33,10 @@ jobs:
|
|||||||
working-directory: deltachat-jsonrpc/typescript
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
run: npm run test
|
run: npm run test
|
||||||
env:
|
env:
|
||||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||||
|
- name: make sure websocket server version still builds
|
||||||
|
working-directory: deltachat-jsonrpc
|
||||||
|
run: cargo build --bin deltachat-jsonrpc-server --features webserver
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
working-directory: deltachat-jsonrpc/typescript
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
run: npm run prettier:check
|
run: npm run prettier:check
|
||||||
|
|||||||
108
.github/workflows/nix.yml
vendored
108
.github/workflows/nix.yml
vendored
@@ -1,108 +0,0 @@
|
|||||||
name: Test Nix flake
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- flake.nix
|
|
||||||
- flake.lock
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- flake.nix
|
|
||||||
- flake.lock
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
format:
|
|
||||||
name: check flake formatting
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
show-progress: false
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
- run: nix fmt
|
|
||||||
|
|
||||||
# Check that formatting does not change anything.
|
|
||||||
- run: git diff --exit-code
|
|
||||||
|
|
||||||
build:
|
|
||||||
name: nix build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
installable:
|
|
||||||
# Ensure `nix develop` will work.
|
|
||||||
- devShells.x86_64-linux.default
|
|
||||||
|
|
||||||
- deltachat-python
|
|
||||||
- deltachat-repl
|
|
||||||
- deltachat-repl-aarch64-linux
|
|
||||||
- deltachat-repl-arm64-v8a-android
|
|
||||||
- deltachat-repl-armeabi-v7a-android
|
|
||||||
- deltachat-repl-armv6l-linux
|
|
||||||
- deltachat-repl-armv7l-linux
|
|
||||||
- deltachat-repl-i686-linux
|
|
||||||
- deltachat-repl-win32
|
|
||||||
- deltachat-repl-win64
|
|
||||||
- deltachat-repl-x86_64-linux
|
|
||||||
- deltachat-rpc-client
|
|
||||||
- deltachat-rpc-server
|
|
||||||
- deltachat-rpc-server-aarch64-linux
|
|
||||||
- deltachat-rpc-server-aarch64-linux-wheel
|
|
||||||
- deltachat-rpc-server-arm64-v8a-android
|
|
||||||
- deltachat-rpc-server-arm64-v8a-android-wheel
|
|
||||||
- deltachat-rpc-server-armeabi-v7a-android
|
|
||||||
- deltachat-rpc-server-armeabi-v7a-android-wheel
|
|
||||||
- deltachat-rpc-server-armv6l-linux
|
|
||||||
- deltachat-rpc-server-armv6l-linux-wheel
|
|
||||||
- deltachat-rpc-server-armv7l-linux
|
|
||||||
- deltachat-rpc-server-armv7l-linux-wheel
|
|
||||||
- deltachat-rpc-server-i686-linux
|
|
||||||
- deltachat-rpc-server-i686-linux-wheel
|
|
||||||
- deltachat-rpc-server-source
|
|
||||||
- deltachat-rpc-server-win32
|
|
||||||
- deltachat-rpc-server-win32-wheel
|
|
||||||
- deltachat-rpc-server-win64
|
|
||||||
- deltachat-rpc-server-win64-wheel
|
|
||||||
- deltachat-rpc-server-x86_64-linux
|
|
||||||
- deltachat-rpc-server-x86_64-linux-wheel
|
|
||||||
- docs
|
|
||||||
- libdeltachat
|
|
||||||
- python-docs
|
|
||||||
|
|
||||||
# Fails to build
|
|
||||||
#- deltachat-repl-x86_64-android
|
|
||||||
#- deltachat-repl-x86-android
|
|
||||||
#- deltachat-rpc-server-x86_64-android
|
|
||||||
#- deltachat-rpc-server-x86-android
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
show-progress: false
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
- run: nix build .#${{ matrix.installable }}
|
|
||||||
|
|
||||||
build-macos:
|
|
||||||
name: nix build on macOS
|
|
||||||
runs-on: macos-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
installable:
|
|
||||||
- deltachat-rpc-server-aarch64-darwin
|
|
||||||
|
|
||||||
# Fails to bulid
|
|
||||||
# - deltachat-rpc-server-x86_64-darwin
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
show-progress: false
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
- run: nix build .#${{ matrix.installable }}
|
|
||||||
41
.github/workflows/node-docs.yml
vendored
Normal file
41
.github/workflows/node-docs.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# GitHub Actions workflow to build
|
||||||
|
# Node.js bindings documentation
|
||||||
|
# and upload it to the web server.
|
||||||
|
# Built documentation is available at <https://js.delta.chat/>
|
||||||
|
|
||||||
|
name: Generate & upload node.js documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
|
- name: Use Node.js 18.x
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
|
||||||
|
- name: npm install and generate documentation
|
||||||
|
working-directory: node
|
||||||
|
run: |
|
||||||
|
npm i --ignore-scripts
|
||||||
|
npx typedoc
|
||||||
|
mv docs js
|
||||||
|
|
||||||
|
- name: Upload
|
||||||
|
uses: horochx/deploy-via-scp@v1.0.1
|
||||||
|
with:
|
||||||
|
user: ${{ secrets.USERNAME }}
|
||||||
|
key: ${{ secrets.KEY }}
|
||||||
|
host: "delta.chat"
|
||||||
|
port: 22
|
||||||
|
local: "node/js"
|
||||||
|
remote: "/var/www/html/"
|
||||||
235
.github/workflows/node-package.yml
vendored
Normal file
235
.github/workflows/node-package.yml
vendored
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
name: "node.js build"
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
- "!py-*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prebuild:
|
||||||
|
name: Prebuild
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, windows-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
- name: System info
|
||||||
|
run: |
|
||||||
|
rustc -vV
|
||||||
|
rustup -vV
|
||||||
|
cargo -vV
|
||||||
|
npm --version
|
||||||
|
node --version
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.APPDATA }}/npm-cache
|
||||||
|
~/.npm
|
||||||
|
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||||
|
|
||||||
|
- name: Cache cargo index
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
|
||||||
|
|
||||||
|
- name: Install dependencies & build
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
working-directory: node
|
||||||
|
run: npm install --verbose
|
||||||
|
|
||||||
|
- name: Build Prebuild
|
||||||
|
working-directory: node
|
||||||
|
run: |
|
||||||
|
npm run prebuildify
|
||||||
|
tar -zcvf "${{ matrix.os }}.tar.gz" -C prebuilds .
|
||||||
|
|
||||||
|
- name: Upload Prebuild
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.os }}
|
||||||
|
path: node/${{ matrix.os }}.tar.gz
|
||||||
|
|
||||||
|
prebuild-linux:
|
||||||
|
name: Prebuild Linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Build Linux prebuilds inside a container with old glibc for backwards compatibility.
|
||||||
|
# Debian 10 contained glibc 2.28: https://packages.debian.org/buster/libc6
|
||||||
|
container: debian:10
|
||||||
|
steps:
|
||||||
|
# Working directory is owned by 1001:1001 by default.
|
||||||
|
# Change it to our user.
|
||||||
|
- name: Change working directory owner
|
||||||
|
run: chown root:root .
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
- run: apt-get update
|
||||||
|
|
||||||
|
# Python is needed for node-gyp
|
||||||
|
- name: Install curl, python and compilers
|
||||||
|
run: apt-get install -y curl build-essential python3
|
||||||
|
- name: Install Rust
|
||||||
|
run: |
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
- name: System info
|
||||||
|
run: |
|
||||||
|
rustc -vV
|
||||||
|
rustup -vV
|
||||||
|
cargo -vV
|
||||||
|
npm --version
|
||||||
|
node --version
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.APPDATA }}/npm-cache
|
||||||
|
~/.npm
|
||||||
|
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||||
|
|
||||||
|
- name: Cache cargo index
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
|
||||||
|
|
||||||
|
- name: Install dependencies & build
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
working-directory: node
|
||||||
|
run: npm install --verbose
|
||||||
|
|
||||||
|
- name: Build Prebuild
|
||||||
|
working-directory: node
|
||||||
|
run: |
|
||||||
|
npm run prebuildify
|
||||||
|
tar -zcvf "linux.tar.gz" -C prebuilds .
|
||||||
|
|
||||||
|
- name: Upload Prebuild
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
path: node/linux.tar.gz
|
||||||
|
|
||||||
|
pack-module:
|
||||||
|
needs: [prebuild, prebuild-linux]
|
||||||
|
name: Package deltachat-node and upload to download.delta.chat
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install tree
|
||||||
|
run: sudo apt install tree
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
- name: Get tag
|
||||||
|
id: tag
|
||||||
|
uses: dawidd6/action-get-tag@v1
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Get Pull Request ID
|
||||||
|
id: prepare
|
||||||
|
run: |
|
||||||
|
tag=${{ steps.tag.outputs.tag }}
|
||||||
|
if [ -z "$tag" ]; then
|
||||||
|
node -e "console.log('DELTACHAT_NODE_TAR_GZ=deltachat-node-' + '${{ github.ref }}'.split('/')[2] + '.tar.gz')" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "DELTACHAT_NODE_TAR_GZ=deltachat-node-${{ steps.tag.outputs.tag }}.tar.gz" >> $GITHUB_ENV
|
||||||
|
echo "No preview will be uploaded this time, but the $tag release"
|
||||||
|
fi
|
||||||
|
- name: System info
|
||||||
|
run: |
|
||||||
|
rustc -vV
|
||||||
|
rustup -vV
|
||||||
|
cargo -vV
|
||||||
|
npm --version
|
||||||
|
node --version
|
||||||
|
echo $DELTACHAT_NODE_TAR_GZ
|
||||||
|
- name: Download Linux prebuild
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
- name: Download macOS prebuild
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos-latest
|
||||||
|
- name: Download Windows prebuild
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows-latest
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir node/prebuilds
|
||||||
|
tar -xvzf linux.tar.gz -C node/prebuilds
|
||||||
|
tar -xvzf macos-latest.tar.gz -C node/prebuilds
|
||||||
|
tar -xvzf windows-latest.tar.gz -C node/prebuilds
|
||||||
|
tree node/prebuilds
|
||||||
|
rm -f linux.tar.gz macos-latest.tar.gz windows-latest.tar.gz
|
||||||
|
- name: Install dependencies without running scripts
|
||||||
|
run: |
|
||||||
|
npm install --ignore-scripts
|
||||||
|
- name: Build constants
|
||||||
|
run: |
|
||||||
|
npm run build:core:constants
|
||||||
|
- name: Build TypeScript part
|
||||||
|
run: |
|
||||||
|
npm run build:bindings:ts
|
||||||
|
- name: Package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mv node/README.md README.md
|
||||||
|
npm pack .
|
||||||
|
ls -lah
|
||||||
|
mv $(find deltachat-node-*) $DELTACHAT_NODE_TAR_GZ
|
||||||
|
- name: Upload prebuild
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: deltachat-node.tgz
|
||||||
|
path: ${{ env.DELTACHAT_NODE_TAR_GZ }}
|
||||||
|
# Upload to download.delta.chat/node/preview/
|
||||||
|
- name: Upload deltachat-node preview to download.delta.chat/node/preview/
|
||||||
|
if: ${{ ! steps.tag.outputs.tag }}
|
||||||
|
id: upload-preview
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
|
||||||
|
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||||
|
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Post links to details
|
||||||
|
if: steps.upload-preview.outcome == 'success'
|
||||||
|
run: node ./node/scripts/postLinksToDetails.js
|
||||||
|
env:
|
||||||
|
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# Upload to download.delta.chat/node/
|
||||||
|
- name: Upload deltachat-node build to download.delta.chat/node/
|
||||||
|
if: ${{ steps.tag.outputs.tag }}
|
||||||
|
id: upload
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
|
||||||
|
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||||
|
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/"
|
||||||
68
.github/workflows/node-tests.yml
vendored
Normal file
68
.github/workflows/node-tests.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# GitHub Actions workflow
|
||||||
|
# to test Node.js bindings.
|
||||||
|
|
||||||
|
name: "node.js tests"
|
||||||
|
|
||||||
|
# Cancel previously started workflow runs
|
||||||
|
# when the branch is updated.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
- name: System info
|
||||||
|
run: |
|
||||||
|
rustc -vV
|
||||||
|
rustup -vV
|
||||||
|
cargo -vV
|
||||||
|
npm --version
|
||||||
|
node --version
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.APPDATA }}/npm-cache
|
||||||
|
~/.npm
|
||||||
|
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||||
|
|
||||||
|
- name: Cache cargo index
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
|
||||||
|
|
||||||
|
- name: Install dependencies & build
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
working-directory: node
|
||||||
|
run: npm install --verbose
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
timeout-minutes: 10
|
||||||
|
working-directory: node
|
||||||
|
run: npm run test
|
||||||
|
env:
|
||||||
|
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||||
|
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
||||||
@@ -5,8 +5,6 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build distribution
|
name: Build distribution
|
||||||
@@ -16,7 +14,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- name: Install pypa/build
|
- name: Install pypa/build
|
||||||
run: python3 -m pip install build
|
run: python3 -m pip install build
|
||||||
- name: Build a binary wheel and a source tarball
|
- name: Build a binary wheel and a source tarball
|
||||||
|
|||||||
4
.github/workflows/repl.yml
vendored
4
.github/workflows/repl.yml
vendored
@@ -7,8 +7,6 @@ name: Build Windows REPL .exe
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_repl:
|
build_repl:
|
||||||
name: Build REPL example
|
name: Build REPL example
|
||||||
@@ -17,8 +15,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
- name: Build
|
- name: Build
|
||||||
run: nix build .#deltachat-repl-win64
|
run: nix build .#deltachat-repl-win64
|
||||||
- name: Upload binary
|
- name: Upload binary
|
||||||
|
|||||||
10
.github/workflows/upload-docs.yml
vendored
10
.github/workflows/upload-docs.yml
vendored
@@ -6,8 +6,6 @@ on:
|
|||||||
- main
|
- main
|
||||||
- build_jsonrpc_docs_ci
|
- build_jsonrpc_docs_ci
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-rs:
|
build-rs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -16,7 +14,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- name: Build the documentation with cargo
|
- name: Build the documentation with cargo
|
||||||
run: |
|
run: |
|
||||||
cargo doc --package deltachat --no-deps --document-private-items
|
cargo doc --package deltachat --no-deps --document-private-items
|
||||||
@@ -34,9 +31,9 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
- name: Build Python documentation
|
- name: Build Python documentation
|
||||||
run: nix build .#python-docs
|
run: nix build .#python-docs
|
||||||
- name: Upload to py.delta.chat
|
- name: Upload to py.delta.chat
|
||||||
@@ -53,9 +50,9 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
- name: Build C documentation
|
- name: Build C documentation
|
||||||
run: nix build .#docs
|
run: nix build .#docs
|
||||||
- name: Upload to c.delta.chat
|
- name: Upload to c.delta.chat
|
||||||
@@ -75,10 +72,9 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
- name: npm install
|
- name: npm install
|
||||||
|
|||||||
3
.github/workflows/upload-ffi-docs.yml
vendored
3
.github/workflows/upload-ffi-docs.yml
vendored
@@ -9,8 +9,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -19,7 +17,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
show-progress: false
|
show-progress: false
|
||||||
persist-credentials: false
|
|
||||||
- name: Build the documentation with cargo
|
- name: Build the documentation with cargo
|
||||||
run: |
|
run: |
|
||||||
cargo doc --package deltachat_ffi --no-deps
|
cargo doc --package deltachat_ffi --no-deps
|
||||||
|
|||||||
31
.github/workflows/zizmor-scan.yml
vendored
31
.github/workflows/zizmor-scan.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
name: GitHub Actions Security Analysis with zizmor
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["**"]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
zizmor:
|
|
||||||
name: zizmor latest via PyPI
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
security-events: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
|
||||||
uses: astral-sh/setup-uv@v5
|
|
||||||
|
|
||||||
- name: Run zizmor
|
|
||||||
run: uvx zizmor --format sarif . > results.sarif
|
|
||||||
|
|
||||||
- name: Upload SARIF file
|
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
|
||||||
with:
|
|
||||||
sarif_file: results.sarif
|
|
||||||
category: zizmor
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,9 +1,7 @@
|
|||||||
target/
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
/build
|
/build
|
||||||
/dist
|
/dist
|
||||||
/fuzz/fuzz_targets/corpus/
|
|
||||||
/fuzz/fuzz_targets/crashes/
|
|
||||||
|
|
||||||
# ignore vi temporaries
|
# ignore vi temporaries
|
||||||
*~
|
*~
|
||||||
|
|||||||
2366
CHANGELOG.md
2366
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ add_custom_command(
|
|||||||
PREFIX=${CMAKE_INSTALL_PREFIX}
|
PREFIX=${CMAKE_INSTALL_PREFIX}
|
||||||
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
|
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
|
||||||
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
|
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
|
||||||
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release
|
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release --no-default-features --features jsonrpc
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
110
CONTRIBUTING.md
110
CONTRIBUTING.md
@@ -1,35 +1,21 @@
|
|||||||
# Contributing to Delta Chat
|
# Contributing guidelines
|
||||||
|
|
||||||
## Bug reports
|
## Reporting bugs
|
||||||
|
|
||||||
If you found a bug, [report it on GitHub](https://github.com/chatmail/core/issues).
|
If you found a bug, [report it on GitHub](https://github.com/deltachat/deltachat-core-rust/issues).
|
||||||
If the bug you found is specific to
|
If the bug you found is specific to
|
||||||
[Android](https://github.com/deltachat/deltachat-android/issues),
|
[Android](https://github.com/deltachat/deltachat-android/issues),
|
||||||
[iOS](https://github.com/deltachat/deltachat-ios/issues) or
|
[iOS](https://github.com/deltachat/deltachat-ios/issues) or
|
||||||
[Desktop](https://github.com/deltachat/deltachat-desktop/issues),
|
[Desktop](https://github.com/deltachat/deltachat-desktop/issues),
|
||||||
report it to the corresponding repository.
|
report it to the corresponding repository.
|
||||||
|
|
||||||
## Feature proposals
|
## Proposing features
|
||||||
|
|
||||||
If you have a feature request, create a new topic on the [forum](https://support.delta.chat/).
|
If you have a feature request, create a new topic on the [forum](https://support.delta.chat/).
|
||||||
|
|
||||||
## Code contributions
|
## Contributing code
|
||||||
|
|
||||||
If you want to contribute a code, follow this guide.
|
If you want to contribute a code, [open a Pull Request](https://github.com/deltachat/deltachat-core-rust/pulls).
|
||||||
|
|
||||||
1. **Select an issue to work on.**
|
|
||||||
|
|
||||||
If you have an write access to the repository, assign the issue to yourself.
|
|
||||||
Otherwise state in the comment that you are going to work on the issue
|
|
||||||
to avoid duplicate work.
|
|
||||||
|
|
||||||
If the issue does not exist yet, create it first.
|
|
||||||
|
|
||||||
2. **Write the code.**
|
|
||||||
|
|
||||||
Follow the [coding conventions](STYLE.md) when writing the code.
|
|
||||||
|
|
||||||
3. **Commit the code.**
|
|
||||||
|
|
||||||
If you have write access to the repository,
|
If you have write access to the repository,
|
||||||
push a branch named `<username>/<feature>`
|
push a branch named `<username>/<feature>`
|
||||||
@@ -37,6 +23,15 @@ If you want to contribute a code, follow this guide.
|
|||||||
and open a PR proposing to merge the change.
|
and open a PR proposing to merge the change.
|
||||||
Otherwise fork the repository and create a branch in your fork.
|
Otherwise fork the repository and create a branch in your fork.
|
||||||
|
|
||||||
|
You can find the list of good first issues
|
||||||
|
and a link to this guide
|
||||||
|
on the contributing page: <https://github.com/deltachat/deltachat-core-rust/contribute>
|
||||||
|
|
||||||
|
### Coding conventions
|
||||||
|
|
||||||
|
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
|
||||||
|
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
|
||||||
|
|
||||||
Commit messages follow the [Conventional Commits] notation.
|
Commit messages follow the [Conventional Commits] notation.
|
||||||
We use [git-cliff] to generate the changelog from commit messages before the release.
|
We use [git-cliff] to generate the changelog from commit messages before the release.
|
||||||
|
|
||||||
@@ -54,8 +49,14 @@ If you want to contribute a code, follow this guide.
|
|||||||
- `docs`: Documentation changes, e.g. "docs: add contributing guidelines"
|
- `docs`: Documentation changes, e.g. "docs: add contributing guidelines"
|
||||||
- `chore`: miscellaneous tasks, e.g. "chore: add `.DS_Store` to `.gitignore`"
|
- `chore`: miscellaneous tasks, e.g. "chore: add `.DS_Store` to `.gitignore`"
|
||||||
|
|
||||||
Release preparation commits are marked as "chore(release): prepare for X.Y.Z"
|
Release preparation commits are marked as "chore(release): prepare for vX.Y.Z".
|
||||||
as described in [releasing guide](RELEASE.md).
|
|
||||||
|
If you intend to squash merge the PR from the web interface,
|
||||||
|
make sure the PR title follows the conventional commits notation
|
||||||
|
as it will end up being a commit title.
|
||||||
|
Otherwise make sure each commit title follows the conventional commit notation.
|
||||||
|
|
||||||
|
#### Breaking Changes
|
||||||
|
|
||||||
Use a `!` to mark breaking changes, e.g. "api!: Remove `dc_chat_can_send`".
|
Use a `!` to mark breaking changes, e.g. "api!: Remove `dc_chat_can_send`".
|
||||||
|
|
||||||
@@ -67,56 +68,59 @@ If you want to contribute a code, follow this guide.
|
|||||||
BREAKING CHANGE: You have to call `dc_stop_io()`/`dc_start_io()` before/after `dc_imex(DC_IMEX_EXPORT_BACKUP)`
|
BREAKING CHANGE: You have to call `dc_stop_io()`/`dc_start_io()` before/after `dc_imex(DC_IMEX_EXPORT_BACKUP)`
|
||||||
```
|
```
|
||||||
|
|
||||||
4. [**Open a Pull Request**](https://github.com/chatmail/core/pulls).
|
#### Multiple Changes in one PR
|
||||||
|
|
||||||
Refer to the corresponding issue.
|
If you have multiple changes in one PR, create multiple conventional commits, and then do a rebase merge. Otherwise, you should usually do a squash merge.
|
||||||
|
|
||||||
If you intend to squash merge the PR from the web interface,
|
[Clippy]: https://doc.rust-lang.org/clippy/
|
||||||
make sure the PR title follows the conventional commits notation
|
[Conventional Commits]: https://www.conventionalcommits.org/
|
||||||
as it will end up being a commit title.
|
[git-cliff]: https://git-cliff.org/
|
||||||
Otherwise make sure each commit title follows the conventional commit notation.
|
|
||||||
|
|
||||||
5. **Make sure all CI checks succeed.**
|
### Errors
|
||||||
|
|
||||||
CI runs the tests and checks code formatting.
|
Delta Chat core mostly uses [`anyhow`](https://docs.rs/anyhow/) errors.
|
||||||
|
When using [`Context`](https://docs.rs/anyhow/latest/anyhow/trait.Context.html),
|
||||||
|
capitalize it but do not add a full stop as the contexts will be separated by `:`.
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
.with_context(|| format!("Unable to trash message {msg_id}"))
|
||||||
|
```
|
||||||
|
|
||||||
While it is running, self-review your PR to make sure all the changes you expect are there
|
All errors should be handled in one of these ways:
|
||||||
and there are no accidentally committed unrelated changes and files.
|
- With `if let Err() =` (incl. logging them into `warn!()`/`err!()`).
|
||||||
|
- With `.log_err().ok()`.
|
||||||
|
- Bubbled up with `?`.
|
||||||
|
|
||||||
Push the necessary fixup commits or force-push to your branch if needed.
|
`backtrace` feature is enabled for `anyhow` crate
|
||||||
|
and `debug = 1` option is set in the test profile.
|
||||||
|
This allows to run `RUST_BACKTRACE=1 cargo test`
|
||||||
|
and get a backtrace with line numbers in resultified tests
|
||||||
|
which return `anyhow::Result`.
|
||||||
|
|
||||||
6. **Ask for review.**
|
### Logging
|
||||||
|
|
||||||
Use built-in GitHub feature to request a review from suggested reviewers.
|
For logging, use `info!`, `warn!` and `error!` macros.
|
||||||
|
Log messages should be capitalized and have a full stop in the end. For example:
|
||||||
|
```
|
||||||
|
info!(context, "Ignoring addition of {added_addr:?} to {chat_id}.");
|
||||||
|
```
|
||||||
|
|
||||||
If you do not have write access to the repository, ask for review in the comments.
|
Format anyhow errors with `{:#}` to print all the contexts like this:
|
||||||
|
```
|
||||||
|
error!(context, "Failed to set selfavatar timestamp: {err:#}.");
|
||||||
|
```
|
||||||
|
|
||||||
7. **Merge the PR.**
|
### Reviewing
|
||||||
|
|
||||||
Once a PR has an approval and passes CI, it can be merged.
|
Once a PR has an approval and passes CI, it can be merged.
|
||||||
|
|
||||||
PRs from a branch created in the main repository,
|
PRs from a branch created in the main repository, i.e. authored by those who have write access, are merged by their authors.
|
||||||
i.e. authored by those who have write access, are merged by their authors.
|
|
||||||
|
|
||||||
This is to ensure that PRs are merged as intended by the author,
|
This is to ensure that PRs are merged as intended by the author,
|
||||||
e.g. as a squash merge, by rebasing from the web interface or manually from the command line.
|
e.g. as a squash merge, by rebasing from the web interface or manually from the command line.
|
||||||
|
|
||||||
If you have multiple changes in one PR, do a rebase merge.
|
|
||||||
Otherwise, you should usually do a squash merge.
|
|
||||||
|
|
||||||
If PR author does not have write access to the repository,
|
|
||||||
maintainers who reviewed the PR can merge it.
|
|
||||||
|
|
||||||
If you do not have access to the repository and created a PR from a fork,
|
If you do not have access to the repository and created a PR from a fork,
|
||||||
ask the maintainers to merge the PR and say how it should be merged.
|
ask the maintainers to merge the PR and say how it should be merged.
|
||||||
|
|
||||||
## Other ways to contribute
|
## Other ways to contribute
|
||||||
|
|
||||||
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).
|
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).
|
||||||
|
|
||||||
You can find the list of good first issues
|
|
||||||
and a link to this guide
|
|
||||||
on the contributing page: <https://github.com/chatmail/core/contribute>
|
|
||||||
|
|
||||||
[Conventional Commits]: https://www.conventionalcommits.org/
|
|
||||||
[git-cliff]: https://git-cliff.org/
|
|
||||||
|
|||||||
5327
Cargo.lock
generated
5327
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
165
Cargo.toml
165
Cargo.toml
@@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "1.157.3"
|
version = "1.138.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
rust-version = "1.81"
|
rust-version = "1.77"
|
||||||
repository = "https://github.com/chatmail/core"
|
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
debug = 0
|
debug = 0
|
||||||
@@ -34,94 +34,100 @@ strip = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
deltachat_derive = { path = "./deltachat_derive" }
|
deltachat_derive = { path = "./deltachat_derive" }
|
||||||
deltachat-time = { path = "./deltachat-time" }
|
deltachat-time = { path = "./deltachat-time" }
|
||||||
deltachat-contact-tools = { workspace = true }
|
deltachat-contact-tools = { path = "./deltachat-contact-tools" }
|
||||||
format-flowed = { path = "./format-flowed" }
|
format-flowed = { path = "./format-flowed" }
|
||||||
ratelimit = { path = "./deltachat-ratelimit" }
|
ratelimit = { path = "./deltachat-ratelimit" }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
async-broadcast = "0.7.2"
|
async-broadcast = "0.7.0"
|
||||||
async-channel = { workspace = true }
|
async-channel = "2.2.1"
|
||||||
async-imap = { version = "0.10.3", default-features = false, features = ["runtime-tokio", "compress"] }
|
async-imap = { version = "0.9.7", default-features = false, features = ["runtime-tokio"] }
|
||||||
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
||||||
async-smtp = { version = "0.10", default-features = false, features = ["runtime-tokio"] }
|
async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] }
|
||||||
async_zip = { version = "0.0.17", default-features = false, features = ["deflate", "tokio-fs"] }
|
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
|
||||||
base64 = { workspace = true }
|
backtrace = "0.3"
|
||||||
brotli = { version = "7", default-features=false, features = ["std"] }
|
base64 = "0.22"
|
||||||
bytes = "1"
|
brotli = { version = "6", default-features=false, features = ["std"] }
|
||||||
chrono = { workspace = true, features = ["alloc", "clock", "std"] }
|
chrono = { workspace = true }
|
||||||
data-encoding = "2.7.0"
|
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||||
|
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
||||||
escaper = "0.1"
|
escaper = "0.1"
|
||||||
fast-socks5 = "0.10"
|
fast-socks5 = "0.9"
|
||||||
fd-lock = "4"
|
fd-lock = "4"
|
||||||
futures-lite = { workspace = true }
|
futures = "0.3"
|
||||||
futures = { workspace = true }
|
futures-lite = "2.3.0"
|
||||||
hex = "0.4.0"
|
hex = "0.4.0"
|
||||||
hickory-resolver = "=0.25.0-alpha.5"
|
hickory-resolver = "0.24"
|
||||||
http-body-util = "0.1.2"
|
|
||||||
humansize = "2"
|
humansize = "2"
|
||||||
hyper = "1"
|
image = { version = "0.25.1", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||||
hyper-util = "0.1.10"
|
iroh_old = { version = "0.4.2", default-features = false, package = "iroh"}
|
||||||
image = { version = "0.25.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
iroh-net = { git = "https://github.com/link2xt/iroh", branch="link2xt/keep-connection" }
|
||||||
iroh-gossip = { version = "0.33", default-features = false, features = ["net"] }
|
iroh-gossip = { git = "https://github.com/link2xt/iroh", branch="link2xt/keep-connection", features = ["net"] }
|
||||||
iroh = { version = "0.33", default-features = false }
|
quinn = "0.10.0"
|
||||||
kamadak-exif = "0.6.1"
|
kamadak-exif = "0.5.3"
|
||||||
libc = { workspace = true }
|
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||||
mail-builder = { version = "0.4.2", default-features = false }
|
libc = "0.2"
|
||||||
mailparse = { workspace = true }
|
mailparse = "0.15"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
num_cpus = "1.16"
|
num_cpus = "1.16"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
num-traits = { workspace = true }
|
num-traits = "0.2"
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
parking_lot = "0.12"
|
|
||||||
percent-encoding = "2.3"
|
percent-encoding = "2.3"
|
||||||
pgp = { version = "0.15.0", default-features = false }
|
parking_lot = "0.12"
|
||||||
pin-project = "1"
|
pgp = { version = "0.11", default-features = false }
|
||||||
|
pretty_env_logger = { version = "0.5", optional = true }
|
||||||
qrcodegen = "1.7.0"
|
qrcodegen = "1.7.0"
|
||||||
quick-xml = "0.37"
|
quick-xml = "0.31"
|
||||||
quoted_printable = "0.5"
|
quoted_printable = "0.5"
|
||||||
rand = { workspace = true }
|
rand = "0.8"
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
|
reqwest = { version = "0.11.27", features = ["json"] }
|
||||||
rusqlite = { workspace = true, features = ["sqlcipher"] }
|
rusqlite = { workspace = true, features = ["sqlcipher"] }
|
||||||
rust-hsluv = "0.1"
|
rust-hsluv = "0.1"
|
||||||
rustls-pki-types = "1.11.0"
|
sanitize-filename = "0.5"
|
||||||
rustls = { version = "0.23.22", default-features = false }
|
serde_json = "1"
|
||||||
sanitize-filename = { workspace = true }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
|
||||||
serde_urlencoded = "0.7.1"
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
sha-1 = "0.10"
|
sha-1 = "0.10"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
shadowsocks = { version = "1.22.0", default-features = false, features = ["aead-cipher", "aead-cipher-2022"] }
|
smallvec = "1.13.2"
|
||||||
smallvec = "1.14.0"
|
strum = "0.26"
|
||||||
strum = "0.27"
|
strum_macros = "0.26"
|
||||||
strum_macros = "0.27"
|
|
||||||
tagger = "4.3.4"
|
tagger = "4.3.4"
|
||||||
textwrap = "0.16.1"
|
textwrap = "0.16.1"
|
||||||
thiserror = { workspace = true }
|
thiserror = "1"
|
||||||
|
tokio = { version = "1.37.0", features = ["fs", "rt-multi-thread", "macros"] }
|
||||||
tokio-io-timeout = "1.2.0"
|
tokio-io-timeout = "1.2.0"
|
||||||
tokio-rustls = { version = "0.26.2", default-features = false }
|
tokio-stream = { version = "0.1.15", features = ["fs"] }
|
||||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
|
||||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||||
tokio-util = { workspace = true }
|
tokio-util = "0.7.9"
|
||||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
url = "2"
|
url = "2"
|
||||||
uuid = { version = "1", features = ["serde", "v4"] }
|
uuid = { version = "1", features = ["serde", "v4"] }
|
||||||
webpki-roots = "0.26.8"
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
blake3 = "1.6.1"
|
|
||||||
|
# Pin OpenSSL to 3.1 releases.
|
||||||
|
# OpenSSL 3.2 has a regression tracked at <https://github.com/openssl/openssl/issues/23376>
|
||||||
|
# which results in broken `deltachat-rpc-server` binaries when cross-compiled using Zig toolchain.
|
||||||
|
# See <https://github.com/deltachat/deltachat-core-rust/issues/5206> for Delta Chat issue.
|
||||||
|
# According to <https://www.openssl.org/policies/releasestrat.html>
|
||||||
|
# 3.1 branch will be supported until 2025-03-14.
|
||||||
|
openssl-src = "~300.1"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
ansi_term = "0.12.0"
|
||||||
|
anyhow = { version = "1", features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
||||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||||
futures-lite = { workspace = true }
|
futures-lite = "2.3.0"
|
||||||
log = { workspace = true }
|
log = "0.4"
|
||||||
nu-ansi-term = { workspace = true }
|
pretty_env_logger = "0.5"
|
||||||
pretty_assertions = "1.4.1"
|
|
||||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||||
tempfile = { workspace = true }
|
tempfile = "3"
|
||||||
testdir = "0.9.3"
|
testdir = "0.9.0"
|
||||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
tokio = { version = "1.37.0", features = ["parking_lot", "rt-multi-thread", "macros"] }
|
||||||
|
pretty_assertions = "1.3.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
@@ -134,7 +140,6 @@ members = [
|
|||||||
"deltachat-time",
|
"deltachat-time",
|
||||||
"format-flowed",
|
"format-flowed",
|
||||||
"deltachat-contact-tools",
|
"deltachat-contact-tools",
|
||||||
"fuzz",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
@@ -151,17 +156,12 @@ harness = false
|
|||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "receive_emails"
|
name = "receive_emails"
|
||||||
required-features = ["internals"]
|
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "get_chat_msgs"
|
name = "get_chat_msgs"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "marknoticed_chat"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "get_chatlist"
|
name = "get_chatlist"
|
||||||
harness = false
|
harness = false
|
||||||
@@ -172,39 +172,16 @@ harness = false
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
async-channel = "2.3.1"
|
once_cell = "1.18.0"
|
||||||
base64 = "0.22"
|
|
||||||
chrono = { version = "0.4.40", default-features = false }
|
|
||||||
deltachat-contact-tools = { path = "deltachat-contact-tools" }
|
|
||||||
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
|
|
||||||
deltachat = { path = ".", default-features = false }
|
|
||||||
futures = "0.3.31"
|
|
||||||
futures-lite = "2.6.0"
|
|
||||||
libc = "0.2"
|
|
||||||
log = "0.4"
|
|
||||||
mailparse = "0.16.1"
|
|
||||||
nu-ansi-term = "0.46"
|
|
||||||
num-traits = "0.2"
|
|
||||||
once_cell = "1.20.2"
|
|
||||||
rand = "0.8"
|
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
rusqlite = "0.32"
|
rusqlite = "0.31"
|
||||||
sanitize-filename = "0.5"
|
chrono = { version = "0.4.38", default-features=false, features = ["alloc", "clock", "std"] }
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1"
|
|
||||||
tempfile = "3.14.0"
|
|
||||||
thiserror = "2"
|
|
||||||
tokio = "1"
|
|
||||||
tokio-util = "0.7.13"
|
|
||||||
tracing-subscriber = "0.3"
|
|
||||||
yerpc = "0.6.2"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored"]
|
default = ["vendored"]
|
||||||
internals = []
|
internals = []
|
||||||
vendored = [
|
vendored = [
|
||||||
"rusqlite/bundled-sqlcipher-vendored-openssl"
|
"async-native-tls/vendored",
|
||||||
|
"rusqlite/bundled-sqlcipher-vendored-openssl",
|
||||||
|
"reqwest/native-tls-vendored"
|
||||||
]
|
]
|
||||||
|
|
||||||
[lints.rust]
|
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
|
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -3,11 +3,11 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/chatmail/core/actions/workflows/ci.yml">
|
<a href="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml">
|
||||||
<img alt="Rust CI" src="https://github.com/chatmail/core/actions/workflows/ci.yml/badge.svg">
|
<img alt="Rust CI" src="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml/badge.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://deps.rs/repo/github/chatmail/core">
|
<a href="https://deps.rs/repo/github/deltachat/deltachat-core-rust">
|
||||||
<img alt="dependency status" src="https://deps.rs/repo/github/chatmail/core/status.svg">
|
<img alt="dependency status" src="https://deps.rs/repo/github/deltachat/deltachat-core-rust/status.svg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -30,13 +30,13 @@ $ curl https://sh.rustup.rs -sSf | sh
|
|||||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
Compile and run Delta Chat Core command line utility, using `cargo`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cargo run --locked -p deltachat-repl -- ~/deltachat-db
|
$ cargo run -p deltachat-repl -- ~/deltachat-db
|
||||||
```
|
```
|
||||||
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.
|
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.
|
||||||
|
|
||||||
Optionally, install `deltachat-repl` binary with
|
Optionally, install `deltachat-repl` binary with
|
||||||
```
|
```
|
||||||
$ cargo install --locked --path deltachat-repl/
|
$ cargo install --path deltachat-repl/
|
||||||
```
|
```
|
||||||
and run as
|
and run as
|
||||||
```
|
```
|
||||||
@@ -104,7 +104,7 @@ For more commands type:
|
|||||||
## Installing libdeltachat system wide
|
## Installing libdeltachat system wide
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/chatmail/core.git
|
$ git clone https://github.com/deltachat/deltachat-core-rust.git
|
||||||
$ cd deltachat-core-rust
|
$ cd deltachat-core-rust
|
||||||
$ cmake -B build . -DCMAKE_INSTALL_PREFIX=/usr
|
$ cmake -B build . -DCMAKE_INSTALL_PREFIX=/usr
|
||||||
$ cmake --build build
|
$ cmake --build build
|
||||||
@@ -139,7 +139,7 @@ $ cargo test -- --ignored
|
|||||||
|
|
||||||
Install [`cargo-bolero`](https://github.com/camshaft/bolero) with
|
Install [`cargo-bolero`](https://github.com/camshaft/bolero) with
|
||||||
```sh
|
```sh
|
||||||
$ cargo install cargo-bolero@0.8.0
|
$ cargo install cargo-bolero
|
||||||
```
|
```
|
||||||
|
|
||||||
Run fuzzing tests with
|
Run fuzzing tests with
|
||||||
@@ -161,6 +161,7 @@ $ cargo bolero test fuzz_format_flowed --release=false -e afl -s NONE
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
||||||
|
- `nightly`: Enable nightly only performance and security related features.
|
||||||
|
|
||||||
## Update Provider Data
|
## Update Provider Data
|
||||||
|
|
||||||
@@ -176,7 +177,9 @@ To add the updates from the
|
|||||||
Language bindings are available for:
|
Language bindings are available for:
|
||||||
|
|
||||||
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
|
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
|
||||||
- **JS**: \[[📂 source](./deltachat-rpc-client) | [📦 npm](https://www.npmjs.com/package/@deltachat/jsonrpc-client) | [📚 docs](https://js.jsonrpc.delta.chat/)\]
|
- **Node.js**
|
||||||
|
- over cffi: \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
|
||||||
|
- over jsonrpc built with napi.rs (experimental): \[[📂 source](https://github.com/deltachat/napi-jsonrpc) | [📦 npm](https://www.npmjs.com/package/@deltachat/napi-jsonrpc)\]
|
||||||
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
|
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
|
||||||
- **Go**
|
- **Go**
|
||||||
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
For example, to release version 1.116.0 of the core, do the following steps.
|
For example, to release version 1.116.0 of the core, do the following steps.
|
||||||
|
|
||||||
1. Resolve all [blocker issues](https://github.com/chatmail/core/labels/blocker).
|
1. Resolve all [blocker issues](https://github.com/deltachat/deltachat-core-rust/labels/blocker).
|
||||||
|
|
||||||
2. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
|
2. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
|
||||||
|
|
||||||
3. add a link to compare previous with current version to the end of CHANGELOG.md:
|
3. add a link to compare previous with current version to the end of CHANGELOG.md:
|
||||||
`[1.116.0]: https://github.com/chatmail/core/compare/v1.115.2...v1.116.0`
|
`[1.116.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.115.2...v1.116.0`
|
||||||
|
|
||||||
4. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
4. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||||
|
|
||||||
5. Commit the changes as `chore(release): prepare for 1.116.0`.
|
5. Commit the changes as `chore(release): prepare for 1.116.0`.
|
||||||
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
|
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
|
||||||
|
|
||||||
6. Tag the release: `git tag --annotate v1.116.0`.
|
6. Tag the release: `git tag -a v1.116.0`.
|
||||||
|
|
||||||
7. Push the release tag: `git push origin v1.116.0`.
|
7. Push the release tag: `git push origin v1.116.0`.
|
||||||
|
|
||||||
8. Create a GitHub release: `gh release create v1.116.0 --notes ''`.
|
8. Create a GitHub release: `gh release create v1.116.0 -n ''`.
|
||||||
|
|||||||
98
STYLE.md
98
STYLE.md
@@ -1,98 +0,0 @@
|
|||||||
# Coding conventions
|
|
||||||
|
|
||||||
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
|
|
||||||
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
|
|
||||||
|
|
||||||
[Clippy]: https://doc.rust-lang.org/clippy/
|
|
||||||
|
|
||||||
## SQL
|
|
||||||
|
|
||||||
Multi-line SQL statements should be formatted using string literals,
|
|
||||||
for example
|
|
||||||
```
|
|
||||||
sql.execute(
|
|
||||||
"CREATE TABLE messages (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
text TEXT DEFAULT '' NOT NULL -- message text
|
|
||||||
) STRICT",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not use macros like [`concat!`](https://doc.rust-lang.org/std/macro.concat.html)
|
|
||||||
or [`indoc!](https://docs.rs/indoc).
|
|
||||||
Do not escape newlines like this:
|
|
||||||
```
|
|
||||||
sql.execute(
|
|
||||||
"CREATE TABLE messages ( \
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT, \
|
|
||||||
text TEXT DEFAULT '' NOT NULL \
|
|
||||||
) STRICT",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
```
|
|
||||||
Escaping newlines
|
|
||||||
is prone to errors like this if space before backslash is missing:
|
|
||||||
```
|
|
||||||
"SELECT foo\
|
|
||||||
FROM bar"
|
|
||||||
```
|
|
||||||
Literal above results in `SELECT fooFROM bar` string.
|
|
||||||
This style also does not allow using `--` comments.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Declare new SQL tables with [`STRICT`](https://sqlite.org/stricttables.html) keyword
|
|
||||||
to make SQLite check column types.
|
|
||||||
|
|
||||||
Declare primary keys with [`AUTOINCREMENT`](https://www.sqlite.org/autoinc.html) keyword.
|
|
||||||
This avoids reuse of the row IDs and can avoid dangerous bugs
|
|
||||||
like forwarding wrong message because the message was deleted
|
|
||||||
and another message took its row ID.
|
|
||||||
|
|
||||||
Declare all new columns as `NOT NULL`
|
|
||||||
and set the `DEFAULT` value if it is optional so the column can be skipped in `INSERT` statements.
|
|
||||||
Dealing with `NULL` values both in SQL and in Rust is tricky and we try to avoid it.
|
|
||||||
If column is already declared without `NOT NULL`, use `IFNULL` function to provide default value when selecting it.
|
|
||||||
Use `HAVING COUNT(*) > 0` clause
|
|
||||||
to [prevent aggregate functions such as `MIN` and `MAX` from returning `NULL`](https://stackoverflow.com/questions/66527856/aggregate-functions-max-etc-return-null-instead-of-no-rows).
|
|
||||||
|
|
||||||
Don't delete unused columns too early, but maybe after several months/releases, unused columns are
|
|
||||||
still used by older versions, so deleting them breaks downgrading the core or importing a backup in
|
|
||||||
an older version. Also don't change the column type, consider adding a new column with another name
|
|
||||||
instead. Finally, never change column semantics, this is especially dangerous because the `STRICT`
|
|
||||||
keyword doesn't help here.
|
|
||||||
|
|
||||||
## Errors
|
|
||||||
|
|
||||||
Delta Chat core mostly uses [`anyhow`](https://docs.rs/anyhow/) errors.
|
|
||||||
When using [`Context`](https://docs.rs/anyhow/latest/anyhow/trait.Context.html),
|
|
||||||
capitalize it but do not add a full stop as the contexts will be separated by `:`.
|
|
||||||
For example:
|
|
||||||
```
|
|
||||||
.with_context(|| format!("Unable to trash message {msg_id}"))
|
|
||||||
```
|
|
||||||
|
|
||||||
All errors should be handled in one of these ways:
|
|
||||||
- With `if let Err() =` (incl. logging them into `warn!()`/`err!()`).
|
|
||||||
- With `.log_err().ok()`.
|
|
||||||
- Bubbled up with `?`.
|
|
||||||
|
|
||||||
`backtrace` feature is enabled for `anyhow` crate
|
|
||||||
and `debug = 1` option is set in the test profile.
|
|
||||||
This allows to run `RUST_BACKTRACE=1 cargo test`
|
|
||||||
and get a backtrace with line numbers in resultified tests
|
|
||||||
which return `anyhow::Result`.
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
For logging, use `info!`, `warn!` and `error!` macros.
|
|
||||||
Log messages should be capitalized and have a full stop in the end. For example:
|
|
||||||
```
|
|
||||||
info!(context, "Ignoring addition of {added_addr:?} to {chat_id}.");
|
|
||||||
```
|
|
||||||
|
|
||||||
Format anyhow errors with `{:#}` to print all the contexts like this:
|
|
||||||
```
|
|
||||||
error!(context, "Failed to set selfavatar timestamp: {err:#}.");
|
|
||||||
```
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<path
|
|
||||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
|
||||||
d="m 24.015419,1.2870249 c -12.549421,0 -22.7283936,10.1789711 -22.7283936,22.7283931 0,12.549422 10.1789726,22.728395 22.7283936,22.728395 14.337742,-0.342877 9.614352,-4.702705 23.697556,0.969161 -7.545453,-13.001555 -1.082973,-13.32964 -0.969161,-23.697556 0,-12.549422 -10.178973,-22.7283931 -22.728395,-22.7283931 z" />
|
|
||||||
<path
|
|
||||||
style="fill:#000000;fill-opacity:1;stroke:none"
|
|
||||||
d="M 23.982249,5.3106163 C 13.645822,5.4364005 5.2618355,13.92999 5.2618355,24.275753 c 0,10.345764 8.3839865,18.635301 18.7204135,18.509516 9.827724,-0.03951 7.516769,-5.489695 18.380082,-0.443187 -5.950849,-9.296115 0.201753,-10.533667 0.340336,-18.521947 0,-10.345766 -8.383989,-18.6353031 -18.720418,-18.5095187 z" />
|
|
||||||
<g
|
|
||||||
style="fill:#ffffff"
|
|
||||||
transform="scale(1.1342891,0.88160947)">
|
|
||||||
<path
|
|
||||||
d="m 21.360141,23.513382 q -1.218487,-1.364705 -3.387392,-3.265543 -2.388233,-2.095797 -3.216804,-3.289913 -0.828571,-1.218486 -0.828571,-2.6563 0,-2.144536 1.998318,-3.363022 1.998317,-1.2428565 5.215121,-1.2428565 3.216804,0 5.605037,1.0966375 2.412603,1.096638 2.412603,3.021846 0,0.92605 -0.584873,1.535293 -0.584874,0.609243 -1.364705,0.609243 -1.121008,0 -2.631931,-1.681511 -1.535292,-1.705881 -2.60756,-2.388233 -1.047898,-0.706722 -2.461343,-0.706722 -1.803359,0 -2.973106,0.804201 -1.145377,0.804201 -1.145377,2.047057 0,1.169747 0.950419,2.193275 0.950419,1.023529 4.898315,3.728568 4.215963,2.899998 5.946213,4.532769 1.75462,1.632772 2.851258,3.972265 1.096638,2.339494 1.096638,4.947055 0,4.581508 -3.241174,8.090749 -3.216804,3.484871 -7.530245,3.484871 -3.923526,0 -6.628566,-2.802519 -2.705039,-2.802518 -2.705039,-7.481506 0,-4.508399 2.973106,-7.530245 2.997477,-3.021846 7.359658,-3.655459 z m 1.072268,1.121008 q -6.994112,1.145377 -6.994112,9.601672 0,4.36218 1.730251,6.774783 1.75462,2.412603 4.069744,2.412603 2.412603,0 3.972265,-2.315124 1.559663,-2.339493 1.559663,-6.311759 0,-5.751255 -4.337811,-10.162175 z" />
|
|
||||||
</g>
|
|
||||||
BIN
assets/root-certificates/letsencrypt/isrgrootx1.der
Normal file
BIN
assets/root-certificates/letsencrypt/isrgrootx1.der
Normal file
Binary file not shown.
@@ -1,94 +0,0 @@
|
|||||||
#![recursion_limit = "256"]
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
|
|
||||||
use deltachat::chat::{self, ChatId};
|
|
||||||
use deltachat::chatlist::Chatlist;
|
|
||||||
use deltachat::context::Context;
|
|
||||||
use deltachat::stock_str::StockStrings;
|
|
||||||
use deltachat::Events;
|
|
||||||
use futures_lite::future::block_on;
|
|
||||||
use tempfile::tempdir;
|
|
||||||
|
|
||||||
async fn marknoticed_chat_benchmark(context: &Context, chats: &[ChatId]) {
|
|
||||||
for c in chats.iter().take(20) {
|
|
||||||
chat::marknoticed_chat(context, *c).await.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn criterion_benchmark(c: &mut Criterion) {
|
|
||||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
|
||||||
// messages, such as your primary account.
|
|
||||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
||||||
|
|
||||||
let chats: Vec<_> = rt.block_on(async {
|
|
||||||
let context = Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let chatlist = Chatlist::try_load(&context, 0, None, None).await.unwrap();
|
|
||||||
let len = chatlist.len();
|
|
||||||
(1..len).map(|i| chatlist.get_chat_id(i).unwrap()).collect()
|
|
||||||
});
|
|
||||||
|
|
||||||
// This mainly tests the performance of marknoticed_chat()
|
|
||||||
// when nothing has to be done
|
|
||||||
c.bench_function(
|
|
||||||
"chat::marknoticed_chat (mark 20 chats as noticed repeatedly)",
|
|
||||||
|b| {
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
let dir = dir.path();
|
|
||||||
let new_db = dir.join("dc.db");
|
|
||||||
std::fs::copy(&path, &new_db).unwrap();
|
|
||||||
|
|
||||||
let context = block_on(async {
|
|
||||||
Context::new(Path::new(&new_db), 100, Events::new(), StockStrings::new())
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
b.to_async(&rt)
|
|
||||||
.iter(|| marknoticed_chat_benchmark(&context, black_box(&chats)))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the first 20 chats contain fresh messages or reactions,
|
|
||||||
// this tests the performance of marking them as noticed.
|
|
||||||
c.bench_function(
|
|
||||||
"chat::marknoticed_chat (mark 20 chats as noticed, resetting after every iteration)",
|
|
||||||
|b| {
|
|
||||||
b.to_async(&rt).iter_batched(
|
|
||||||
|| {
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
let new_db = dir.path().join("dc.db");
|
|
||||||
std::fs::copy(&path, &new_db).unwrap();
|
|
||||||
|
|
||||||
let context = block_on(async {
|
|
||||||
Context::new(
|
|
||||||
Path::new(&new_db),
|
|
||||||
100,
|
|
||||||
Events::new(),
|
|
||||||
StockStrings::new(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
(dir, context)
|
|
||||||
},
|
|
||||||
|(_dir, context)| {
|
|
||||||
let chats = &chats;
|
|
||||||
async move {
|
|
||||||
marknoticed_chat_benchmark(black_box(&context), black_box(chats)).await
|
|
||||||
}
|
|
||||||
},
|
|
||||||
BatchSize::PerIteration,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, criterion_benchmark);
|
|
||||||
criterion_main!(benches);
|
|
||||||
@@ -12,18 +12,18 @@ use deltachat::{
|
|||||||
};
|
};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
async fn recv_all_emails(context: Context, iteration: u32) -> Context {
|
async fn recv_all_emails(context: Context) -> Context {
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
let imf_raw = format!(
|
let imf_raw = format!(
|
||||||
"Subject: Benchmark
|
"Subject: Benchmark
|
||||||
Message-ID: Mr.{iteration}.{i}@testrun.org
|
Message-ID: Mr.OssSYnOFkhR.{i}@testrun.org
|
||||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||||
To: alice@example.com
|
To: alice@example.com
|
||||||
From: sender@testrun.org
|
From: sender@testrun.org
|
||||||
Chat-Version: 1.0
|
Chat-Version: 1.0
|
||||||
Chat-Disposition-Notification-To: sender@testrun.org
|
Chat-Disposition-Notification-To: sender@testrun.org
|
||||||
Chat-User-Avatar: 0
|
Chat-User-Avatar: 0
|
||||||
In-Reply-To: Mr.{iteration}.{i_dec}@testrun.org
|
In-Reply-To: Mr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
|
|
||||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||||
@@ -41,11 +41,11 @@ Hello {i}",
|
|||||||
|
|
||||||
/// Receive 100 emails that remove charlie@example.com and add
|
/// Receive 100 emails that remove charlie@example.com and add
|
||||||
/// him back
|
/// him back
|
||||||
async fn recv_groupmembership_emails(context: Context, iteration: u32) -> Context {
|
async fn recv_groupmembership_emails(context: Context) -> Context {
|
||||||
for i in 0..50 {
|
for i in 0..50 {
|
||||||
let imf_raw = format!(
|
let imf_raw = format!(
|
||||||
"Subject: Benchmark
|
"Subject: Benchmark
|
||||||
Message-ID: Gr.{iteration}.ADD.{i}@testrun.org
|
Message-ID: Gr.OssSYnOFkhR.{i}@testrun.org
|
||||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||||
From: sender@testrun.org
|
From: sender@testrun.org
|
||||||
@@ -53,12 +53,13 @@ Chat-Version: 1.0
|
|||||||
Chat-Disposition-Notification-To: sender@testrun.org
|
Chat-Disposition-Notification-To: sender@testrun.org
|
||||||
Chat-User-Avatar: 0
|
Chat-User-Avatar: 0
|
||||||
Chat-Group-Member-Added: charlie@example.com
|
Chat-Group-Member-Added: charlie@example.com
|
||||||
In-Reply-To: Gr.{iteration}.REMOVE.{i_dec}@testrun.org
|
In-Reply-To: Gr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
|
|
||||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||||
|
|
||||||
Hello {i}",
|
Hello {i}",
|
||||||
|
i = i,
|
||||||
i_dec = i - 1,
|
i_dec = i - 1,
|
||||||
);
|
);
|
||||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||||
@@ -67,7 +68,7 @@ Hello {i}",
|
|||||||
|
|
||||||
let imf_raw = format!(
|
let imf_raw = format!(
|
||||||
"Subject: Benchmark
|
"Subject: Benchmark
|
||||||
Message-ID: Gr.{iteration}.REMOVE.{i}@testrun.org
|
Message-ID: Gr.OssSYnOFkhR.{i}@testrun.org
|
||||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||||
From: sender@testrun.org
|
From: sender@testrun.org
|
||||||
@@ -75,12 +76,14 @@ Chat-Version: 1.0
|
|||||||
Chat-Disposition-Notification-To: sender@testrun.org
|
Chat-Disposition-Notification-To: sender@testrun.org
|
||||||
Chat-User-Avatar: 0
|
Chat-User-Avatar: 0
|
||||||
Chat-Group-Member-Removed: charlie@example.com
|
Chat-Group-Member-Removed: charlie@example.com
|
||||||
In-Reply-To: Gr.{iteration}.ADD.{i}@testrun.org
|
In-Reply-To: Gr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
|
|
||||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||||
|
|
||||||
Hello {i}"
|
Hello {i}",
|
||||||
|
i = i,
|
||||||
|
i_dec = i - 1,
|
||||||
);
|
);
|
||||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||||
.await
|
.await
|
||||||
@@ -126,13 +129,11 @@ fn criterion_benchmark(c: &mut Criterion) {
|
|||||||
group.bench_function("Receive 100 simple text msgs", |b| {
|
group.bench_function("Receive 100 simple text msgs", |b| {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
let context = rt.block_on(create_context());
|
let context = rt.block_on(create_context());
|
||||||
let mut i = 0;
|
|
||||||
|
|
||||||
b.to_async(&rt).iter(|| {
|
b.to_async(&rt).iter(|| {
|
||||||
let ctx = context.clone();
|
let ctx = context.clone();
|
||||||
i += 1;
|
|
||||||
async move {
|
async move {
|
||||||
recv_all_emails(black_box(ctx), i).await;
|
recv_all_emails(black_box(ctx)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -141,13 +142,11 @@ fn criterion_benchmark(c: &mut Criterion) {
|
|||||||
|b| {
|
|b| {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
let context = rt.block_on(create_context());
|
let context = rt.block_on(create_context());
|
||||||
let mut i = 0;
|
|
||||||
|
|
||||||
b.to_async(&rt).iter(|| {
|
b.to_async(&rt).iter(|| {
|
||||||
let ctx = context.clone();
|
let ctx = context.clone();
|
||||||
i += 1;
|
|
||||||
async move {
|
async move {
|
||||||
recv_groupmembership_emails(black_box(ctx), i).await;
|
recv_groupmembership_emails(black_box(ctx)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ filter_unconventional = false
|
|||||||
split_commits = false
|
split_commits = false
|
||||||
# regex for preprocessing the commit messages
|
# regex for preprocessing the commit messages
|
||||||
commit_preprocessors = [
|
commit_preprocessors = [
|
||||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/chatmail/core/pull/${2}))"}, # replace pull request / issue numbers
|
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/deltachat/deltachat-core-rust/pull/${2}))"}, # replace pull request / issue numbers
|
||||||
]
|
]
|
||||||
# regex for parsing and grouping commits
|
# regex for parsing and grouping commits
|
||||||
commit_parsers = [
|
commit_parsers = [
|
||||||
@@ -82,11 +82,11 @@ footer = """
|
|||||||
{% if release.version -%}
|
{% if release.version -%}
|
||||||
{% if release.previous.version -%}
|
{% if release.previous.version -%}
|
||||||
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
||||||
https://github.com/chatmail/core\
|
https://github.com/deltachat/deltachat-core-rust\
|
||||||
/compare/{{ release.previous.version }}..{{ release.version }}
|
/compare/{{ release.previous.version }}..{{ release.version }}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{% else -%}
|
{% else -%}
|
||||||
[unreleased]: https://github.com/chatmail/core\
|
[unreleased]: https://github.com/deltachat/deltachat-core-rust\
|
||||||
/compare/{{ release.previous.version }}..HEAD
|
/compare/{{ release.previous.version }}..HEAD
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
80
contrib/proxy.py
Normal file
80
contrib/proxy.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# Original server that doesn't use SSL:
|
||||||
|
# ./proxy.py 8080 imap.nauta.cu 143
|
||||||
|
# ./proxy.py 8081 smtp.nauta.cu 25
|
||||||
|
#
|
||||||
|
# Original server that uses SSL:
|
||||||
|
# ./proxy.py 8080 testrun.org 993 --ssl
|
||||||
|
# ./proxy.py 8081 testrun.org 465 --ssl
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import argparse
|
||||||
|
import selectors
|
||||||
|
import ssl
|
||||||
|
import socket
|
||||||
|
import socketserver
|
||||||
|
|
||||||
|
|
||||||
|
class Proxy(socketserver.ThreadingTCPServer):
|
||||||
|
allow_reuse_address = True
|
||||||
|
|
||||||
|
def __init__(self, proxy_host, proxy_port, real_host, real_port, use_ssl):
|
||||||
|
self.real_host = real_host
|
||||||
|
self.real_port = real_port
|
||||||
|
self.use_ssl = use_ssl
|
||||||
|
super().__init__((proxy_host, proxy_port), RequestHandler)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestHandler(socketserver.BaseRequestHandler):
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
print('{} - {} CONNECTED.'.format(datetime.now(), self.client_address))
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
real_server = (self.server.real_host, self.server.real_port)
|
||||||
|
with socket.create_connection(real_server) as sock:
|
||||||
|
if self.server.use_ssl:
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
sock = context.wrap_socket(
|
||||||
|
sock, server_hostname=real_server[0])
|
||||||
|
|
||||||
|
forward = {self.request: sock, sock: self.request}
|
||||||
|
|
||||||
|
sel = selectors.DefaultSelector()
|
||||||
|
sel.register(self.request, selectors.EVENT_READ,
|
||||||
|
self.client_address)
|
||||||
|
sel.register(sock, selectors.EVENT_READ, real_server)
|
||||||
|
|
||||||
|
active = True
|
||||||
|
while active:
|
||||||
|
events = sel.select()
|
||||||
|
for key, mask in events:
|
||||||
|
print('\n{} - {} wrote:'.format(datetime.now(), key.data))
|
||||||
|
data = key.fileobj.recv(1024)
|
||||||
|
received = len(data)
|
||||||
|
total += received
|
||||||
|
print(data)
|
||||||
|
print('{} Bytes\nTotal: {} Bytes'.format(received, total))
|
||||||
|
if data:
|
||||||
|
forward[key.fileobj].sendall(data)
|
||||||
|
else:
|
||||||
|
print('\nCLOSING CONNECTION.\n\n')
|
||||||
|
forward[key.fileobj].close()
|
||||||
|
key.fileobj.close()
|
||||||
|
active = False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
p = argparse.ArgumentParser(description='Simple Python Proxy')
|
||||||
|
p.add_argument(
|
||||||
|
"proxy_port", help="the port where the proxy will listen", type=int)
|
||||||
|
p.add_argument('host', help="the real host")
|
||||||
|
p.add_argument('port', help="the port of the real host", type=int)
|
||||||
|
p.add_argument("--ssl", help="use ssl to connect to the real host",
|
||||||
|
action="store_true")
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
with Proxy('', args.proxy_port, args.host, args.port, args.ssl) as proxy:
|
||||||
|
proxy.serve_forever()
|
||||||
@@ -12,7 +12,7 @@ anyhow = { workspace = true }
|
|||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
rusqlite = { workspace = true } # Needed in order to `impl rusqlite::types::ToSql for EmailAddress`. Could easily be put behind a feature.
|
rusqlite = { workspace = true } # Needed in order to `impl rusqlite::types::ToSql for EmailAddress`. Could easily be put behind a feature.
|
||||||
chrono = { workspace = true, features = ["alloc", "clock", "std"] }
|
chrono = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
anyhow = { workspace = true, features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
||||||
|
|||||||
@@ -15,16 +15,14 @@
|
|||||||
clippy::explicit_into_iter_loop,
|
clippy::explicit_into_iter_loop,
|
||||||
clippy::cloned_instead_of_copied
|
clippy::cloned_instead_of_copied
|
||||||
)]
|
)]
|
||||||
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
|
#![cfg_attr(not(test), warn(clippy::indexing_slicing))]
|
||||||
#![cfg_attr(not(test), forbid(clippy::string_slice))]
|
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::match_bool,
|
clippy::match_bool,
|
||||||
clippy::mixed_read_write_in_expression,
|
clippy::mixed_read_write_in_expression,
|
||||||
clippy::bool_assert_comparison,
|
clippy::bool_assert_comparison,
|
||||||
clippy::manual_split_once,
|
clippy::manual_split_once,
|
||||||
clippy::format_push_string,
|
clippy::format_push_string,
|
||||||
clippy::bool_to_int_with_if,
|
clippy::bool_to_int_with_if
|
||||||
clippy::manual_range_contains
|
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -37,6 +35,10 @@ use chrono::{DateTime, NaiveDateTime};
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
// TODOs to clean up:
|
||||||
|
// - Check if sanitizing is done correctly everywhere
|
||||||
|
// - Apply lints everywhere (https://doc.rust-lang.org/cargo/reference/workspaces.html#the-lints-table)
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// A Contact, as represented in a VCard.
|
/// A Contact, as represented in a VCard.
|
||||||
pub struct VcardContact {
|
pub struct VcardContact {
|
||||||
@@ -78,21 +80,21 @@ pub fn make_vcard(contacts: &[VcardContact]) -> String {
|
|||||||
let addr = &c.addr;
|
let addr = &c.addr;
|
||||||
let display_name = c.display_name();
|
let display_name = c.display_name();
|
||||||
res += &format!(
|
res += &format!(
|
||||||
"BEGIN:VCARD\r\n\
|
"BEGIN:VCARD\n\
|
||||||
VERSION:4.0\r\n\
|
VERSION:4.0\n\
|
||||||
EMAIL:{addr}\r\n\
|
EMAIL:{addr}\n\
|
||||||
FN:{display_name}\r\n"
|
FN:{display_name}\n"
|
||||||
);
|
);
|
||||||
if let Some(key) = &c.key {
|
if let Some(key) = &c.key {
|
||||||
res += &format!("KEY:data:application/pgp-keys;base64,{key}\r\n");
|
res += &format!("KEY:data:application/pgp-keys;base64,{key}\n");
|
||||||
}
|
}
|
||||||
if let Some(profile_image) = &c.profile_image {
|
if let Some(profile_image) = &c.profile_image {
|
||||||
res += &format!("PHOTO:data:image/jpeg;base64,{profile_image}\r\n");
|
res += &format!("PHOTO:data:image/jpeg;base64,{profile_image}\n");
|
||||||
}
|
}
|
||||||
if let Some(timestamp) = format_timestamp(c) {
|
if let Some(timestamp) = format_timestamp(c) {
|
||||||
res += &format!("REV:{timestamp}\r\n");
|
res += &format!("REV:{timestamp}\n");
|
||||||
}
|
}
|
||||||
res += "END:VCARD\r\n";
|
res += "END:VCARD\n";
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
@@ -113,9 +115,7 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
|||||||
// If `s` is `EMAIL;TYPE=work:alice@example.com` and `property` is `EMAIL`,
|
// If `s` is `EMAIL;TYPE=work:alice@example.com` and `property` is `EMAIL`,
|
||||||
// then `remainder` is now `;TYPE=work:alice@example.com`
|
// then `remainder` is now `;TYPE=work:alice@example.com`
|
||||||
|
|
||||||
// Note: This doesn't handle the case where there are quotes around a colon,
|
// TODO this doesn't handle the case where there are quotes around a colon
|
||||||
// like `NAME;Foo="Some quoted text: that contains a colon":value`.
|
|
||||||
// This could be improved in the future, but for now, the parsing is good enough.
|
|
||||||
let (params, value) = remainder.split_once(':')?;
|
let (params, value) = remainder.split_once(':')?;
|
||||||
// In the example from above, `params` is now `;TYPE=work`
|
// In the example from above, `params` is now `;TYPE=work`
|
||||||
// and `value` is now `alice@example.com`
|
// and `value` is now `alice@example.com`
|
||||||
@@ -155,7 +155,7 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove line folding, see https://datatracker.ietf.org/doc/html/rfc6350#section-3.2
|
// Remove line folding, see https://datatracker.ietf.org/doc/html/rfc6350#section-3.2
|
||||||
static NEWLINE_AND_SPACE_OR_TAB: Lazy<Regex> = Lazy::new(|| Regex::new("\r?\n[\t ]").unwrap());
|
static NEWLINE_AND_SPACE_OR_TAB: Lazy<Regex> = Lazy::new(|| Regex::new("\n[\t ]").unwrap());
|
||||||
let unfolded_lines = NEWLINE_AND_SPACE_OR_TAB.replace_all(vcard, "");
|
let unfolded_lines = NEWLINE_AND_SPACE_OR_TAB.replace_all(vcard, "");
|
||||||
|
|
||||||
let mut lines = unfolded_lines.lines().peekable();
|
let mut lines = unfolded_lines.lines().peekable();
|
||||||
@@ -175,15 +175,7 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
|||||||
let mut photo = None;
|
let mut photo = None;
|
||||||
let mut datetime = None;
|
let mut datetime = None;
|
||||||
|
|
||||||
for mut line in lines.by_ref() {
|
for line in lines.by_ref() {
|
||||||
if let Some(remainder) = remove_prefix(line, "item1.") {
|
|
||||||
// Remove the group name, if the group is called "item1".
|
|
||||||
// If necessary, we can improve this to also remove groups that are called something different that "item1".
|
|
||||||
//
|
|
||||||
// Search "group name" at https://datatracker.ietf.org/doc/html/rfc6350 for more infos.
|
|
||||||
line = remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(email) = vcard_property(line, "email") {
|
if let Some(email) = vcard_property(line, "email") {
|
||||||
addr.get_or_insert(email);
|
addr.get_or_insert(email);
|
||||||
} else if let Some(name) = vcard_property(line, "fn") {
|
} else if let Some(name) = vcard_property(line, "fn") {
|
||||||
@@ -191,7 +183,6 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
|||||||
} else if let Some(k) = remove_prefix(line, "KEY;PGP;ENCODING=BASE64:")
|
} else if let Some(k) = remove_prefix(line, "KEY;PGP;ENCODING=BASE64:")
|
||||||
.or_else(|| remove_prefix(line, "KEY;TYPE=PGP;ENCODING=b:"))
|
.or_else(|| remove_prefix(line, "KEY;TYPE=PGP;ENCODING=b:"))
|
||||||
.or_else(|| remove_prefix(line, "KEY:data:application/pgp-keys;base64,"))
|
.or_else(|| remove_prefix(line, "KEY:data:application/pgp-keys;base64,"))
|
||||||
.or_else(|| remove_prefix(line, "KEY;PREF=1:data:application/pgp-keys;base64,"))
|
|
||||||
{
|
{
|
||||||
key.get_or_insert(k);
|
key.get_or_insert(k);
|
||||||
} else if let Some(p) = remove_prefix(line, "PHOTO;JPEG;ENCODING=BASE64:")
|
} else if let Some(p) = remove_prefix(line, "PHOTO;JPEG;ENCODING=BASE64:")
|
||||||
@@ -206,6 +197,10 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
|||||||
} else if let Some(rev) = vcard_property(line, "rev") {
|
} else if let Some(rev) = vcard_property(line, "rev") {
|
||||||
datetime.get_or_insert(rev);
|
datetime.get_or_insert(rev);
|
||||||
} else if line.eq_ignore_ascii_case("END:VCARD") {
|
} else if line.eq_ignore_ascii_case("END:VCARD") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (authname, addr) =
|
let (authname, addr) =
|
||||||
sanitize_name_and_addr(display_name.unwrap_or(""), addr.unwrap_or(""));
|
sanitize_name_and_addr(display_name.unwrap_or(""), addr.unwrap_or(""));
|
||||||
|
|
||||||
@@ -218,9 +213,6 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
|||||||
.context("No timestamp in vcard")
|
.context("No timestamp in vcard")
|
||||||
.and_then(parse_datetime),
|
.and_then(parse_datetime),
|
||||||
});
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contacts
|
contacts
|
||||||
@@ -271,27 +263,27 @@ impl rusqlite::types::ToSql for ContactAddress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a name and an address and sanitizes them:
|
/// Make the name and address
|
||||||
/// - Extracts a name from the addr if the addr is in form "Alice <alice@example.org>"
|
|
||||||
/// - Removes special characters from the name, see [`sanitize_name()`]
|
|
||||||
/// - Removes the name if it is equal to the address by setting it to ""
|
|
||||||
pub fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) {
|
pub fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) {
|
||||||
static ADDR_WITH_NAME_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("(.*)<(.*)>").unwrap());
|
static ADDR_WITH_NAME_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("(.*)<(.*)>").unwrap());
|
||||||
let (name, addr) = if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
|
let (name, addr) = if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
|
||||||
(
|
(
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
captures.get(1).map_or("", |m| m.as_str())
|
strip_rtlo_characters(captures.get(1).map_or("", |m| m.as_str()))
|
||||||
} else {
|
} else {
|
||||||
name
|
strip_rtlo_characters(name)
|
||||||
},
|
},
|
||||||
captures
|
captures
|
||||||
.get(2)
|
.get(2)
|
||||||
.map_or("".to_string(), |m| m.as_str().to_string()),
|
.map_or("".to_string(), |m| m.as_str().to_string()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(name, addr.to_string())
|
(
|
||||||
|
strip_rtlo_characters(&normalize_name(name)),
|
||||||
|
addr.to_string(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let mut name = sanitize_name(name);
|
let mut name = normalize_name(&name);
|
||||||
|
|
||||||
// If the 'display name' is just the address, remove it:
|
// If the 'display name' is just the address, remove it:
|
||||||
// Otherwise, the contact would sometimes be shown as "alice@example.com (alice@example.com)" (see `get_name_n_addr()`).
|
// Otherwise, the contact would sometimes be shown as "alice@example.com (alice@example.com)" (see `get_name_n_addr()`).
|
||||||
@@ -303,77 +295,31 @@ pub fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) {
|
|||||||
(name, addr)
|
(name, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sanitizes a name.
|
/// Normalize a name.
|
||||||
///
|
///
|
||||||
/// - Removes newlines and trims the string
|
/// - Remove quotes (come from some bad MUA implementations)
|
||||||
/// - Removes quotes (come from some bad MUA implementations)
|
/// - Trims the resulting string
|
||||||
/// - Removes potentially-malicious bidi characters
|
///
|
||||||
pub fn sanitize_name(name: &str) -> String {
|
/// Typically, this function is not needed as it is called implicitly by `Contact::add_address_book`.
|
||||||
let name = sanitize_single_line(name);
|
pub fn normalize_name(full_name: &str) -> String {
|
||||||
|
let full_name = full_name.trim();
|
||||||
|
if full_name.is_empty() {
|
||||||
|
return full_name.into();
|
||||||
|
}
|
||||||
|
|
||||||
match name.as_bytes() {
|
match full_name.as_bytes() {
|
||||||
[b'\'', .., b'\''] | [b'\"', .., b'\"'] | [b'<', .., b'>'] => name
|
[b'\'', .., b'\''] | [b'\"', .., b'\"'] | [b'<', .., b'>'] => full_name
|
||||||
.get(1..name.len() - 1)
|
.get(1..full_name.len() - 1)
|
||||||
.map_or("".to_string(), |s| s.trim().to_string()),
|
.map_or("".to_string(), |s| s.trim().to_string()),
|
||||||
_ => name.to_string(),
|
_ => full_name.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sanitizes user input
|
|
||||||
///
|
|
||||||
/// - Removes newlines and trims the string
|
|
||||||
/// - Removes potentially-malicious bidi characters
|
|
||||||
pub fn sanitize_single_line(input: &str) -> String {
|
|
||||||
sanitize_bidi_characters(input.replace(['\n', '\r'], " ").trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
const RTLO_CHARACTERS: [char; 5] = ['\u{202A}', '\u{202B}', '\u{202C}', '\u{202D}', '\u{202E}'];
|
const RTLO_CHARACTERS: [char; 5] = ['\u{202A}', '\u{202B}', '\u{202C}', '\u{202D}', '\u{202E}'];
|
||||||
const ISOLATE_CHARACTERS: [char; 3] = ['\u{2066}', '\u{2067}', '\u{2068}'];
|
/// This method strips all occurrences of the RTLO Unicode character.
|
||||||
const POP_ISOLATE_CHARACTER: char = '\u{2069}';
|
/// [Why is this needed](https://github.com/deltachat/deltachat-core-rust/issues/3479)?
|
||||||
/// Some control unicode characters can influence whether adjacent text is shown from
|
pub fn strip_rtlo_characters(input_str: &str) -> String {
|
||||||
/// left to right or from right to left.
|
input_str.replace(|char| RTLO_CHARACTERS.contains(&char), "")
|
||||||
///
|
|
||||||
/// Since user input is not supposed to influence how adjacent text looks,
|
|
||||||
/// this function removes some of these characters.
|
|
||||||
///
|
|
||||||
/// Also see https://github.com/deltachat/deltachat-core-rust/issues/3479.
|
|
||||||
pub fn sanitize_bidi_characters(input_str: &str) -> String {
|
|
||||||
// RTLO_CHARACTERS are apparently rarely used in practice.
|
|
||||||
// They can impact all following text, so, better remove them all:
|
|
||||||
let input_str = input_str.replace(|char| RTLO_CHARACTERS.contains(&char), "");
|
|
||||||
|
|
||||||
// If the ISOLATE characters are not ended with a POP DIRECTIONAL ISOLATE character,
|
|
||||||
// we regard the input as potentially malicious and simply remove all ISOLATE characters.
|
|
||||||
// See https://en.wikipedia.org/wiki/Bidirectional_text#Unicode_bidi_support
|
|
||||||
// and https://www.w3.org/International/questions/qa-bidi-unicode-controls.en
|
|
||||||
// for an explanation about ISOLATE characters.
|
|
||||||
fn isolate_characters_are_valid(input_str: &str) -> bool {
|
|
||||||
let mut isolate_character_nesting: i32 = 0;
|
|
||||||
for char in input_str.chars() {
|
|
||||||
if ISOLATE_CHARACTERS.contains(&char) {
|
|
||||||
isolate_character_nesting += 1;
|
|
||||||
} else if char == POP_ISOLATE_CHARACTER {
|
|
||||||
isolate_character_nesting -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// According to Wikipedia, 125 levels are allowed:
|
|
||||||
// https://en.wikipedia.org/wiki/Unicode_control_characters
|
|
||||||
// (although, in practice, we could also significantly lower this number)
|
|
||||||
if isolate_character_nesting < 0 || isolate_character_nesting > 125 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isolate_character_nesting == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if isolate_characters_are_valid(&input_str) {
|
|
||||||
input_str
|
|
||||||
} else {
|
|
||||||
input_str.replace(
|
|
||||||
|char| ISOLATE_CHARACTERS.contains(&char) || POP_ISOLATE_CHARACTER == char,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns false if addr is an invalid address, otherwise true.
|
/// Returns false if addr is an invalid address, otherwise true.
|
||||||
@@ -539,30 +485,6 @@ END:VCARD",
|
|||||||
assert_eq!(contacts.len(), 1);
|
assert_eq!(contacts.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_vcard_with_trailing_newline() {
|
|
||||||
let contacts = parse_vcard(
|
|
||||||
"BEGIN:VCARD\r
|
|
||||||
VERSION:4.0\r
|
|
||||||
FN:Alice Wonderland\r
|
|
||||||
N:Wonderland;Alice;;;Ms.\r
|
|
||||||
GENDER:W\r
|
|
||||||
EMAIL;TYPE=work:alice@example.com\r
|
|
||||||
KEY;TYPE=PGP;ENCODING=b:[base64-data]\r
|
|
||||||
REV:20240418T184242Z\r
|
|
||||||
END:VCARD\r
|
|
||||||
\r",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(contacts[0].addr, "alice@example.com".to_string());
|
|
||||||
assert_eq!(contacts[0].authname, "Alice Wonderland".to_string());
|
|
||||||
assert_eq!(contacts[0].key, Some("[base64-data]".to_string()));
|
|
||||||
assert_eq!(contacts[0].profile_image, None);
|
|
||||||
assert_eq!(*contacts[0].timestamp.as_ref().unwrap(), 1713465762);
|
|
||||||
|
|
||||||
assert_eq!(contacts.len(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_make_and_parse_vcard() {
|
fn test_make_and_parse_vcard() {
|
||||||
let contacts = [
|
let contacts = [
|
||||||
@@ -582,20 +504,20 @@ END:VCARD\r
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
let items = [
|
let items = [
|
||||||
"BEGIN:VCARD\r\n\
|
"BEGIN:VCARD\n\
|
||||||
VERSION:4.0\r\n\
|
VERSION:4.0\n\
|
||||||
EMAIL:alice@example.org\r\n\
|
EMAIL:alice@example.org\n\
|
||||||
FN:Alice Wonderland\r\n\
|
FN:Alice Wonderland\n\
|
||||||
KEY:data:application/pgp-keys;base64,[base64-data]\r\n\
|
KEY:data:application/pgp-keys;base64,[base64-data]\n\
|
||||||
PHOTO:data:image/jpeg;base64,image in Base64\r\n\
|
PHOTO:data:image/jpeg;base64,image in Base64\n\
|
||||||
REV:20240418T184242Z\r\n\
|
REV:20240418T184242Z\n\
|
||||||
END:VCARD\r\n",
|
END:VCARD\n",
|
||||||
"BEGIN:VCARD\r\n\
|
"BEGIN:VCARD\n\
|
||||||
VERSION:4.0\r\n\
|
VERSION:4.0\n\
|
||||||
EMAIL:bob@example.com\r\n\
|
EMAIL:bob@example.com\n\
|
||||||
FN:bob@example.com\r\n\
|
FN:bob@example.com\n\
|
||||||
REV:19700101T000000Z\r\n\
|
REV:19700101T000000Z\n\
|
||||||
END:VCARD\r\n",
|
END:VCARD\n",
|
||||||
];
|
];
|
||||||
let mut expected = "".to_string();
|
let mut expected = "".to_string();
|
||||||
for len in 0..=contacts.len() {
|
for len in 0..=contacts.len() {
|
||||||
@@ -721,10 +643,10 @@ END:VCARD
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vcard_with_base64_avatar() {
|
fn test_android_vcard_with_base64_avatar() {
|
||||||
// This is not an actual base64-encoded avatar, it's just to test the parsing.
|
// This is not an actual base64-encoded avatar, it's just to test the parsing
|
||||||
// This one is Android-like.
|
let contacts = parse_vcard(
|
||||||
let vcard0 = "BEGIN:VCARD
|
"BEGIN:VCARD
|
||||||
VERSION:2.1
|
VERSION:2.1
|
||||||
N:;Bob;;;
|
N:;Bob;;;
|
||||||
FN:Bob
|
FN:Bob
|
||||||
@@ -734,11 +656,9 @@ PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEU
|
|||||||
L8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==
|
L8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==
|
||||||
|
|
||||||
END:VCARD
|
END:VCARD
|
||||||
";
|
",
|
||||||
// This one is DOS-like.
|
);
|
||||||
let vcard1 = vcard0.replace('\n', "\r\n");
|
|
||||||
for vcard in [vcard0, vcard1.as_str()] {
|
|
||||||
let contacts = parse_vcard(vcard);
|
|
||||||
assert_eq!(contacts.len(), 1);
|
assert_eq!(contacts.len(), 1);
|
||||||
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
||||||
assert_eq!(contacts[0].authname, "Bob".to_string());
|
assert_eq!(contacts[0].authname, "Bob".to_string());
|
||||||
@@ -746,89 +666,3 @@ END:VCARD
|
|||||||
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
|
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_protonmail_vcard() {
|
|
||||||
let contacts = parse_vcard(
|
|
||||||
"BEGIN:VCARD
|
|
||||||
VERSION:4.0
|
|
||||||
FN;PREF=1:Alice Wonderland
|
|
||||||
UID:proton-web-03747582-328d-38dc-5ddd-000000000000
|
|
||||||
ITEM1.EMAIL;PREF=1:alice@example.org
|
|
||||||
ITEM1.KEY;PREF=1:data:application/pgp-keys;base64,aaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
ITEM1.KEY;PREF=2:data:application/pgp-keys;base64,bbbbbbbbbbbbbbbbbbbbbbbbb
|
|
||||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
||||||
ITEM1.X-PM-ENCRYPT:true
|
|
||||||
ITEM1.X-PM-SIGN:true
|
|
||||||
END:VCARD",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(contacts.len(), 1);
|
|
||||||
assert_eq!(&contacts[0].addr, "alice@example.org");
|
|
||||||
assert_eq!(&contacts[0].authname, "Alice Wonderland");
|
|
||||||
assert_eq!(contacts[0].key.as_ref().unwrap(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
assert!(contacts[0].timestamp.is_err());
|
|
||||||
assert_eq!(contacts[0].profile_image, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sanitize_name() {
|
|
||||||
assert_eq!(&sanitize_name(" hello world "), "hello world");
|
|
||||||
assert_eq!(&sanitize_name("<"), "<");
|
|
||||||
assert_eq!(&sanitize_name(">"), ">");
|
|
||||||
assert_eq!(&sanitize_name("'"), "'");
|
|
||||||
assert_eq!(&sanitize_name("\""), "\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sanitize_single_line() {
|
|
||||||
assert_eq!(sanitize_single_line("Hi\naiae "), "Hi aiae");
|
|
||||||
assert_eq!(sanitize_single_line("\r\nahte\n\r"), "ahte");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sanitize_bidi_characters() {
|
|
||||||
// Legit inputs:
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Tes\u{2067}ting Delta Chat\u{2069}"),
|
|
||||||
"Tes\u{2067}ting Delta Chat\u{2069}"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Tes\u{2067}ting \u{2068} Delta Chat\u{2069}\u{2069}"),
|
|
||||||
"Tes\u{2067}ting \u{2068} Delta Chat\u{2069}\u{2069}"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Tes\u{2067}ting\u{2069} Delta Chat\u{2067}\u{2069}"),
|
|
||||||
"Tes\u{2067}ting\u{2069} Delta Chat\u{2067}\u{2069}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Potentially-malicious inputs:
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Tes\u{202C}ting Delta Chat"),
|
|
||||||
"Testing Delta Chat"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Testing Delta Chat\u{2069}"),
|
|
||||||
"Testing Delta Chat"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Tes\u{2067}ting Delta Chat"),
|
|
||||||
"Testing Delta Chat"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Tes\u{2069}ting Delta Chat\u{2067}"),
|
|
||||||
"Testing Delta Chat"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
&sanitize_bidi_characters("Tes\u{2068}ting Delta Chat"),
|
|
||||||
"Testing Delta Chat"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "1.157.3"
|
version = "1.138.5"
|
||||||
description = "Deltachat FFI"
|
description = "Deltachat FFI"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -14,20 +14,21 @@ name = "deltachat"
|
|||||||
crate-type = ["cdylib", "staticlib"]
|
crate-type = ["cdylib", "staticlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deltachat = { workspace = true, default-features = false }
|
deltachat = { path = "../", default-features = false }
|
||||||
deltachat-jsonrpc = { workspace = true }
|
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", optional = true }
|
||||||
libc = { workspace = true }
|
libc = "0.2"
|
||||||
human-panic = { version = "2", default-features = false }
|
human-panic = { version = "2", default-features = false }
|
||||||
num-traits = { workspace = true }
|
num-traits = "0.2"
|
||||||
serde_json = { workspace = true }
|
serde_json = "1.0"
|
||||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
tokio = { version = "1.37.0", features = ["rt-multi-thread"] }
|
||||||
anyhow = { workspace = true }
|
anyhow = "1"
|
||||||
thiserror = { workspace = true }
|
thiserror = "1"
|
||||||
rand = { workspace = true }
|
rand = "0.8"
|
||||||
once_cell = { workspace = true }
|
once_cell = "1.18.0"
|
||||||
yerpc = { workspace = true, features = ["anyhow_expose"] }
|
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored"]
|
default = ["vendored"]
|
||||||
vendored = ["deltachat/vendored", "deltachat-jsonrpc/vendored"]
|
vendored = ["deltachat/vendored"]
|
||||||
|
jsonrpc = ["dep:deltachat-jsonrpc"]
|
||||||
|
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
|||||||
* - Strings in function arguments or return values are usually UTF-8 encoded.
|
* - Strings in function arguments or return values are usually UTF-8 encoded.
|
||||||
*
|
*
|
||||||
* - The issue-tracker for the core library is here:
|
* - The issue-tracker for the core library is here:
|
||||||
* <https://github.com/chatmail/core/issues>
|
* <https://github.com/deltachat/deltachat-core-rust/issues>
|
||||||
*
|
*
|
||||||
* If you need further assistance,
|
* If you need further assistance,
|
||||||
* please do not hesitate to contact us
|
* please do not hesitate to contact us
|
||||||
@@ -403,10 +403,13 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* - `send_port` = SMTP-port, guessed if left out
|
* - `send_port` = SMTP-port, guessed if left out
|
||||||
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
||||||
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
|
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
|
||||||
* - `proxy_enabled` = Proxy enabled. Disabled by default.
|
* - `socks5_enabled` = SOCKS5 enabled
|
||||||
* - `proxy_url` = Proxy URL. May contain multiple URLs separated by newline, but only the first one is used.
|
* - `socks5_host` = SOCKS5 proxy server host
|
||||||
|
* - `socks5_port` = SOCKS5 proxy server port
|
||||||
|
* - `socks5_user` = SOCKS5 proxy username
|
||||||
|
* - `socks5_password` = SOCKS5 proxy password
|
||||||
* - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
* - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||||
* - `smtp_certificate_checks` = deprecated option, should be set to the same value as `imap_certificate_checks` but ignored by the new core
|
* - `smtp_certificate_checks` = how to check SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||||
* - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way e.g. using CC, defaults to empty
|
* - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way e.g. using CC, defaults to empty
|
||||||
* - `selfstatus` = Own status to display, e.g. in e-mail footers, defaults to empty
|
* - `selfstatus` = Own status to display, e.g. in e-mail footers, defaults to empty
|
||||||
* - `selfavatar` = File containing avatar. Will immediately be copied to the
|
* - `selfavatar` = File containing avatar. Will immediately be copied to the
|
||||||
@@ -417,10 +420,9 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* and also recoded to a reasonable size.
|
* and also recoded to a reasonable size.
|
||||||
* - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
|
* - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
|
||||||
* - `mdns_enabled` = 0=do not send or request read receipts,
|
* - `mdns_enabled` = 0=do not send or request read receipts,
|
||||||
* 1=send and request read receipts
|
* 1=send and request read receipts (default)
|
||||||
* default=send and request read receipts, only send but not request if `bot` is set
|
* - `bcc_self` = 0=do not send a copy of outgoing messages to self (default),
|
||||||
* - `bcc_self` = 0=do not send a copy of outgoing messages to self,
|
* 1=send a copy of outgoing messages to self.
|
||||||
* 1=send a copy of outgoing messages to self (default).
|
|
||||||
* Sending messages to self is needed for a proper multi-account setup,
|
* Sending messages to self is needed for a proper multi-account setup,
|
||||||
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
|
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
|
||||||
* - `sentbox_watch`= 1=watch `Sent`-folder for changes,
|
* - `sentbox_watch`= 1=watch `Sent`-folder for changes,
|
||||||
@@ -440,6 +442,17 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* also show all mails of confirmed contacts,
|
* also show all mails of confirmed contacts,
|
||||||
* DC_SHOW_EMAILS_ALL (2)=
|
* DC_SHOW_EMAILS_ALL (2)=
|
||||||
* also show mails of unconfirmed contacts (default).
|
* also show mails of unconfirmed contacts (default).
|
||||||
|
* - `key_gen_type` = DC_KEY_GEN_DEFAULT (0)=
|
||||||
|
* generate recommended key type (default),
|
||||||
|
* DC_KEY_GEN_RSA2048 (1)=
|
||||||
|
* generate RSA 2048 keypair
|
||||||
|
* DC_KEY_GEN_ED25519 (2)=
|
||||||
|
* generate Curve25519 keypair
|
||||||
|
* DC_KEY_GEN_RSA4096 (3)=
|
||||||
|
* generate RSA 4096 keypair
|
||||||
|
* - `save_mime_headers` = 1=save mime headers
|
||||||
|
* and make dc_get_mime_headers() work for subsequent calls,
|
||||||
|
* 0=do not save mime headers (default)
|
||||||
* - `delete_device_after` = 0=do not delete messages from device automatically (default),
|
* - `delete_device_after` = 0=do not delete messages from device automatically (default),
|
||||||
* >=1=seconds, after which messages are deleted automatically from the device.
|
* >=1=seconds, after which messages are deleted automatically from the device.
|
||||||
* Messages in the "saved messages" chat (see dc_chat_is_self_talk()) are skipped.
|
* Messages in the "saved messages" chat (see dc_chat_is_self_talk()) are skipped.
|
||||||
@@ -468,9 +481,8 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* - `bot` = Set to "1" if this is a bot.
|
* - `bot` = Set to "1" if this is a bot.
|
||||||
* Prevents adding the "Device messages" and "Saved messages" chats,
|
* Prevents adding the "Device messages" and "Saved messages" chats,
|
||||||
* adds Auto-Submitted header to outgoing messages,
|
* adds Auto-Submitted header to outgoing messages,
|
||||||
* accepts contact requests automatically (calling dc_accept_chat() is not needed),
|
* accepts contact requests automatically (calling dc_accept_chat() is not needed for bots)
|
||||||
* does not cut large incoming text messages,
|
* and does not cut large incoming text messages.
|
||||||
* handles existing messages the same way as new ones if `fetch_existing_msgs=1`.
|
|
||||||
* - `last_msg_id` = database ID of the last message processed by the bot.
|
* - `last_msg_id` = database ID of the last message processed by the bot.
|
||||||
* This ID and IDs below it are guaranteed not to be returned
|
* This ID and IDs below it are guaranteed not to be returned
|
||||||
* by dc_get_next_msgs() and dc_wait_next_msgs().
|
* by dc_get_next_msgs() and dc_wait_next_msgs().
|
||||||
@@ -481,8 +493,8 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* For most bots calling `dc_markseen_msgs()` is the
|
* For most bots calling `dc_markseen_msgs()` is the
|
||||||
* recommended way to update this value
|
* recommended way to update this value
|
||||||
* even for self-sent messages.
|
* even for self-sent messages.
|
||||||
* - `fetch_existing_msgs` = 0=do not fetch existing messages on configure (default),
|
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
||||||
* 1=fetch most recent existing messages on configure.
|
* 0=do not fetch existing messages on configure.
|
||||||
* In both cases, existing recipients are added to the contact database.
|
* In both cases, existing recipients are added to the contact database.
|
||||||
* - `disable_idle` = 1=disable IMAP IDLE even if the server supports it,
|
* - `disable_idle` = 1=disable IMAP IDLE even if the server supports it,
|
||||||
* 0=use IMAP IDLE if the server supports it.
|
* 0=use IMAP IDLE if the server supports it.
|
||||||
@@ -495,11 +507,6 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* to not mess up with non-delivery-reports or read-receipts.
|
* to not mess up with non-delivery-reports or read-receipts.
|
||||||
* 0=no limit (default).
|
* 0=no limit (default).
|
||||||
* Changes affect future messages only.
|
* Changes affect future messages only.
|
||||||
* - `protect_autocrypt` = Enable Header Protection for Autocrypt header.
|
|
||||||
* This is an experimental option not compatible to other MUAs
|
|
||||||
* and older Delta Chat versions.
|
|
||||||
* 1 = enable.
|
|
||||||
* 0 = disable (default).
|
|
||||||
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
|
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
|
||||||
* seconds. 2 days by default.
|
* seconds. 2 days by default.
|
||||||
* This is not supposed to be changed by UIs and only used for testing.
|
* This is not supposed to be changed by UIs and only used for testing.
|
||||||
@@ -511,21 +518,11 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* 1=After the key changed, `dc_chat_can_send()` returns false and `dc_chat_is_protection_broken()` returns true
|
* 1=After the key changed, `dc_chat_can_send()` returns false and `dc_chat_is_protection_broken()` returns true
|
||||||
* until `dc_accept_chat()` is called.
|
* until `dc_accept_chat()` is called.
|
||||||
* - `is_chatmail` = 1 if the the server is a chatmail server, 0 otherwise.
|
* - `is_chatmail` = 1 if the the server is a chatmail server, 0 otherwise.
|
||||||
* - `is_muted` = Whether a context is muted by the user.
|
|
||||||
* Muted contexts should not sound, vibrate or show notifications.
|
|
||||||
* In contrast to `dc_set_chat_mute_duration()`,
|
|
||||||
* fresh message and badge counters are not changed by this setting,
|
|
||||||
* but should be tuned down where appropriate.
|
|
||||||
* - `private_tag` = Optional tag as "Work", "Family".
|
|
||||||
* Meant to help profile owner to differ between profiles with similar names.
|
|
||||||
* - `ui.*` = All keys prefixed by `ui.` can be used by the user-interfaces for system-specific purposes.
|
* - `ui.*` = All keys prefixed by `ui.` can be used by the user-interfaces for system-specific purposes.
|
||||||
* The prefix should be followed by the system and maybe subsystem,
|
* The prefix should be followed by the system and maybe subsystem,
|
||||||
* e.g. `ui.desktop.foo`, `ui.desktop.linux.bar`, `ui.android.foo`, `ui.dc40.bar`, `ui.bot.simplebot.baz`.
|
* e.g. `ui.desktop.foo`, `ui.desktop.linux.bar`, `ui.android.foo`, `ui.dc40.bar`, `ui.bot.simplebot.baz`.
|
||||||
* These keys go to backups and allow easy per-account settings when using @ref dc_accounts_t,
|
* These keys go to backups and allow easy per-account settings when using @ref dc_accounts_t,
|
||||||
* however, are not handled by the core otherwise.
|
* however, are not handled by the core otherwise.
|
||||||
* - `webxdc_realtime_enabled` = Whether the realtime APIs should be enabled.
|
|
||||||
* 0 = WebXDC realtime API is disabled and behaves as noop.
|
|
||||||
* 1 = WebXDC realtime API is enabled (default).
|
|
||||||
*
|
*
|
||||||
* If you want to retrieve a value, use dc_get_config().
|
* If you want to retrieve a value, use dc_get_config().
|
||||||
*
|
*
|
||||||
@@ -711,6 +708,12 @@ char* dc_get_connectivity_html (dc_context_t* context);
|
|||||||
int dc_get_push_state (dc_context_t* context);
|
int dc_get_push_state (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only used by the python tests.
|
||||||
|
*/
|
||||||
|
int dc_all_work_done (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
// connect
|
// connect
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -854,10 +857,13 @@ void dc_maybe_network (dc_context_t* context);
|
|||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context as created by dc_context_new().
|
* @param context The context as created by dc_context_new().
|
||||||
|
* @param addr The e-mail address of the user. This must match the
|
||||||
|
* configured_addr setting of the context as well as the UID of the key.
|
||||||
|
* @param public_data Ignored, actual public key is extracted from secret_data.
|
||||||
* @param secret_data ASCII armored secret key.
|
* @param secret_data ASCII armored secret key.
|
||||||
* @return 1 on success, 0 on failure.
|
* @return 1 on success, 0 on failure.
|
||||||
*/
|
*/
|
||||||
int dc_preconfigure_keypair (dc_context_t* context, const char *secret_data);
|
int dc_preconfigure_keypair (dc_context_t* context, const char *addr, const char *public_data, const char *secret_data);
|
||||||
|
|
||||||
|
|
||||||
// handle chatlists
|
// handle chatlists
|
||||||
@@ -952,6 +958,54 @@ uint32_t dc_create_chat_by_contact_id (dc_context_t* context, uint32_t co
|
|||||||
uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t contact_id);
|
uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t contact_id);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a message for sending.
|
||||||
|
*
|
||||||
|
* Call this function if the file to be sent is still in creation.
|
||||||
|
* Once you're done with creating the file, call dc_send_msg() as usual
|
||||||
|
* and the message will really be sent.
|
||||||
|
*
|
||||||
|
* This is useful as the user can already send the next messages while
|
||||||
|
* e.g. the recoding of a video is not yet finished. Or the user can even forward
|
||||||
|
* the message with the file being still in creation to other groups.
|
||||||
|
*
|
||||||
|
* Files being sent with the increation-method must be placed in the
|
||||||
|
* blob directory, see dc_get_blobdir().
|
||||||
|
* If the increation-method is not used - which is probably the normal case -
|
||||||
|
* dc_send_msg() copies the file to the blob directory if it is not yet there.
|
||||||
|
* To distinguish the two cases, msg->state must be set properly. The easiest
|
||||||
|
* way to ensure this is to re-use the same object for both calls.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ~~~
|
||||||
|
* char* blobdir = dc_get_blobdir(context);
|
||||||
|
* char* file_to_send = mprintf("%s/%s", blobdir, "send.mp4")
|
||||||
|
*
|
||||||
|
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_VIDEO);
|
||||||
|
* dc_msg_set_file(msg, file_to_send, NULL);
|
||||||
|
* dc_prepare_msg(context, chat_id, msg);
|
||||||
|
*
|
||||||
|
* // ... create the file ...
|
||||||
|
*
|
||||||
|
* dc_send_msg(context, chat_id, msg);
|
||||||
|
*
|
||||||
|
* dc_msg_unref(msg);
|
||||||
|
* free(file_to_send);
|
||||||
|
* dc_str_unref(file_to_send);
|
||||||
|
* ~~~
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object as returned from dc_context_new().
|
||||||
|
* @param chat_id The chat ID to send the message to.
|
||||||
|
* @param msg The message object to send to the chat defined by the chat ID.
|
||||||
|
* On success, msg_id and state of the object are set up,
|
||||||
|
* The function does not take ownership of the object,
|
||||||
|
* so you have to free it using dc_msg_unref() as usual.
|
||||||
|
* @return The ID of the message that is being prepared.
|
||||||
|
*/
|
||||||
|
uint32_t dc_prepare_msg (dc_context_t* context, uint32_t chat_id, dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message defined by a dc_msg_t object to a chat.
|
* Send a message defined by a dc_msg_t object to a chat.
|
||||||
*
|
*
|
||||||
@@ -964,7 +1018,7 @@ uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t co
|
|||||||
* ~~~
|
* ~~~
|
||||||
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_IMAGE);
|
* dc_msg_t* msg = dc_msg_new(context, DC_MSG_IMAGE);
|
||||||
*
|
*
|
||||||
* dc_msg_set_file_and_deduplicate(msg, "/file/to/send.jpg", NULL, NULL);
|
* dc_msg_set_file(msg, "/file/to/send.jpg", NULL);
|
||||||
* dc_send_msg(context, chat_id, msg);
|
* dc_send_msg(context, chat_id, msg);
|
||||||
*
|
*
|
||||||
* dc_msg_unref(msg);
|
* dc_msg_unref(msg);
|
||||||
@@ -976,11 +1030,13 @@ uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t co
|
|||||||
* If that fails, is not possible, or the image is already small enough, the image is sent as original.
|
* If that fails, is not possible, or the image is already small enough, the image is sent as original.
|
||||||
* If you want images to be always sent as the original file, use the #DC_MSG_FILE type.
|
* If you want images to be always sent as the original file, use the #DC_MSG_FILE type.
|
||||||
*
|
*
|
||||||
* Videos and other file types are currently not recoded by the library.
|
* Videos and other file types are currently not recoded by the library,
|
||||||
|
* with dc_prepare_msg(), however, you can do that from the UI.
|
||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object as returned from dc_context_new().
|
* @param context The context object as returned from dc_context_new().
|
||||||
* @param chat_id The chat ID to send the message to.
|
* @param chat_id The chat ID to send the message to.
|
||||||
|
* If dc_prepare_msg() was called before, this parameter can be 0.
|
||||||
* @param msg The message object to send to the chat defined by the chat ID.
|
* @param msg The message object to send to the chat defined by the chat ID.
|
||||||
* On success, msg_id of the object is set up,
|
* On success, msg_id of the object is set up,
|
||||||
* The function does not take ownership of the object,
|
* The function does not take ownership of the object,
|
||||||
@@ -997,6 +1053,7 @@ uint32_t dc_send_msg (dc_context_t* context, uint32_t ch
|
|||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object as returned from dc_context_new().
|
* @param context The context object as returned from dc_context_new().
|
||||||
* @param chat_id The chat ID to send the message to.
|
* @param chat_id The chat ID to send the message to.
|
||||||
|
* If dc_prepare_msg() was called before, this parameter can be 0.
|
||||||
* @param msg The message object to send to the chat defined by the chat ID.
|
* @param msg The message object to send to the chat defined by the chat ID.
|
||||||
* On success, msg_id of the object is set up,
|
* On success, msg_id of the object is set up,
|
||||||
* The function does not take ownership of the object,
|
* The function does not take ownership of the object,
|
||||||
@@ -1028,38 +1085,6 @@ uint32_t dc_send_msg_sync (dc_context_t* context, uint32
|
|||||||
uint32_t dc_send_text_msg (dc_context_t* context, uint32_t chat_id, const char* text_to_send);
|
uint32_t dc_send_text_msg (dc_context_t* context, uint32_t chat_id, const char* text_to_send);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send chat members a request to edit the given message's text.
|
|
||||||
*
|
|
||||||
* Only outgoing messages sent by self can be edited.
|
|
||||||
* Edited messages should be flagged as such in the UI, see dc_msg_is_edited().
|
|
||||||
* UI is informed about changes using the event #DC_EVENT_MSGS_CHANGED.
|
|
||||||
* If the text is not changed, no event and no edit request message are sent.
|
|
||||||
*
|
|
||||||
* @memberof dc_context_t
|
|
||||||
* @param context The context object as returned from dc_context_new().
|
|
||||||
* @param msg_id The message ID of the message to edit.
|
|
||||||
* @param new_text The new text.
|
|
||||||
* This must not be NULL nor empty.
|
|
||||||
*/
|
|
||||||
void dc_send_edit_request (dc_context_t* context, uint32_t msg_id, const char* new_text);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send chat members a request to delete the given messages.
|
|
||||||
*
|
|
||||||
* Only outgoing messages can be deleted this way
|
|
||||||
* and all messages must be in the same chat.
|
|
||||||
* No tombstone or sth. like that is left.
|
|
||||||
*
|
|
||||||
* @memberof dc_context_t
|
|
||||||
* @param context The context object as returned from dc_context_new().
|
|
||||||
* @param msg_ids An array of uint32_t containing all message IDs to delete.
|
|
||||||
* @param msg_cnt The number of messages IDs in the msg_ids array.
|
|
||||||
*/
|
|
||||||
void dc_send_delete_request (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send invitation to a videochat.
|
* Send invitation to a videochat.
|
||||||
*
|
*
|
||||||
@@ -1118,14 +1143,9 @@ uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id);
|
|||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object.
|
* @param context The context object.
|
||||||
* @param msg_id The ID of the message with the webxdc instance.
|
* @param msg_id The ID of the message with the webxdc instance.
|
||||||
* @param json program-readable data, this is created in JS land as:
|
* @param json program-readable data, the actual payload
|
||||||
* - `payload`: any JS object or primitive.
|
* @param descr The user-visible description of JSON data,
|
||||||
* - `info`: optional informational message. Will be shown in chat and may be added as system notification.
|
* in case of a chess game, e.g. the move.
|
||||||
* note that also users that are not notified explicitly get the `info` or `summary` update shown in the chat.
|
|
||||||
* - `document`: optional document name. shown eg. in title bar.
|
|
||||||
* - `summary`: optional summary. shown beside app icon.
|
|
||||||
* - `notify`: optional array of other users `selfAddr` to be notified e.g. by a sound about `info` or `summary`.
|
|
||||||
* @param descr Deprecated, set to NULL
|
|
||||||
* @return 1=success, 0=error
|
* @return 1=success, 0=error
|
||||||
*/
|
*/
|
||||||
int dc_send_webxdc_status_update (dc_context_t* context, uint32_t msg_id, const char* json, const char* descr);
|
int dc_send_webxdc_status_update (dc_context_t* context, uint32_t msg_id, const char* json, const char* descr);
|
||||||
@@ -1523,6 +1543,30 @@ void dc_marknoticed_chat (dc_context_t* context, uint32_t ch
|
|||||||
dc_array_t* dc_get_chat_media (dc_context_t* context, uint32_t chat_id, int msg_type, int msg_type2, int msg_type3);
|
dc_array_t* dc_get_chat_media (dc_context_t* context, uint32_t chat_id, int msg_type, int msg_type2, int msg_type3);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search next/previous message based on a given message and a list of types.
|
||||||
|
* Typically used to implement the "next" and "previous" buttons
|
||||||
|
* in a gallery or in a media player.
|
||||||
|
*
|
||||||
|
* @deprecated Deprecated 2023-10-03, use dc_get_chat_media() and navigate the returned array instead.
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object as returned from dc_context_new().
|
||||||
|
* @param msg_id The ID of the current message from which the next or previous message should be searched.
|
||||||
|
* @param dir 1=get the next message, -1=get the previous one.
|
||||||
|
* @param msg_type The message type to search for.
|
||||||
|
* If 0, the message type from curr_msg_id is used.
|
||||||
|
* @param msg_type2 Alternative message type to search for. 0 to skip.
|
||||||
|
* @param msg_type3 Alternative message type to search for. 0 to skip.
|
||||||
|
* @return Returns the message ID that should be played next.
|
||||||
|
* The returned message is in the same chat as the given one
|
||||||
|
* and has one of the given types.
|
||||||
|
* Typically, this result is passed again to dc_get_next_media()
|
||||||
|
* later on the next swipe.
|
||||||
|
* If there is not next/previous message, the function returns 0.
|
||||||
|
*/
|
||||||
|
uint32_t dc_get_next_media (dc_context_t* context, uint32_t msg_id, int dir, int msg_type, int msg_type2, int msg_type3);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set chat visibility to pinned, archived or normal.
|
* Set chat visibility to pinned, archived or normal.
|
||||||
*
|
*
|
||||||
@@ -1950,7 +1994,24 @@ void dc_download_full_msg (dc_context_t* context, int msg_id);
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete messages. The messages are deleted on all devices and
|
* Get the raw mime-headers of the given message.
|
||||||
|
* Raw headers are saved for incoming messages
|
||||||
|
* only if `dc_set_config(context, "save_mime_headers", "1")`
|
||||||
|
* was called before.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object.
|
||||||
|
* @param msg_id The message ID, must be the ID of an incoming message.
|
||||||
|
* @return Raw headers as a multi-line string, must be released using dc_str_unref() after usage.
|
||||||
|
* Returns NULL if there are no headers saved for the given message,
|
||||||
|
* e.g. because of save_mime_headers is not set
|
||||||
|
* or the message is not incoming.
|
||||||
|
*/
|
||||||
|
char* dc_get_mime_headers (dc_context_t* context, uint32_t msg_id);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete messages. The messages are deleted on the current device and
|
||||||
* on the IMAP server.
|
* on the IMAP server.
|
||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
@@ -1978,36 +2039,6 @@ void dc_delete_msgs (dc_context_t* context, const uint3
|
|||||||
void dc_forward_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt, uint32_t chat_id);
|
void dc_forward_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt, uint32_t chat_id);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save a copy of messages in "Saved Messages".
|
|
||||||
*
|
|
||||||
* In contrast to forwarding messages,
|
|
||||||
* information as author, date and origin are preserved.
|
|
||||||
* The action completes locally, so "Saved Messages" do not show sending errors in case one is offline.
|
|
||||||
* Still, a sync message is emitted, so that other devices will save the same message,
|
|
||||||
* as long as not deleted before.
|
|
||||||
*
|
|
||||||
* To check if a message was saved, use dc_msg_get_saved_msg_id(),
|
|
||||||
* UI may show an indicator and offer an "Unsave" instead of a "Save" button then.
|
|
||||||
*
|
|
||||||
* The other way round, from inside the "Saved Messages" chat,
|
|
||||||
* UI may show the indicator and "Unsave" button checking dc_msg_get_original_msg_id()
|
|
||||||
* and offer a button to go the original message.
|
|
||||||
*
|
|
||||||
* "Unsave" is done by deleting the saved message.
|
|
||||||
* Webxdc updates are not copied on purpose.
|
|
||||||
*
|
|
||||||
* For performance reasons, esp. when saving lots of messages,
|
|
||||||
* UI should call this function from a background thread.
|
|
||||||
*
|
|
||||||
* @memberof dc_context_t
|
|
||||||
* @param context The context object.
|
|
||||||
* @param msg_ids An array of uint32_t containing all message IDs that should be saved.
|
|
||||||
* @param msg_cnt The number of messages IDs in the msg_ids array.
|
|
||||||
*/
|
|
||||||
void dc_save_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resend messages and make information available for newly added chat members.
|
* Resend messages and make information available for newly added chat members.
|
||||||
* Resending sends out the original message, however, recipients and webxdc-status may differ.
|
* Resending sends out the original message, however, recipients and webxdc-status may differ.
|
||||||
@@ -2463,11 +2494,8 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
|||||||
#define DC_QR_FPR_MISMATCH 220 // id=contact
|
#define DC_QR_FPR_MISMATCH 220 // id=contact
|
||||||
#define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint
|
#define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint
|
||||||
#define DC_QR_ACCOUNT 250 // text1=domain
|
#define DC_QR_ACCOUNT 250 // text1=domain
|
||||||
#define DC_QR_BACKUP 251 // deprecated
|
#define DC_QR_BACKUP 251
|
||||||
#define DC_QR_BACKUP2 252
|
|
||||||
#define DC_QR_BACKUP_TOO_NEW 255
|
|
||||||
#define DC_QR_WEBRTC_INSTANCE 260 // text1=domain, text2=instance pattern
|
#define DC_QR_WEBRTC_INSTANCE 260 // text1=domain, text2=instance pattern
|
||||||
#define DC_QR_PROXY 271 // text1=address (e.g. "127.0.0.1:9050")
|
|
||||||
#define DC_QR_ADDR 320 // id=contact
|
#define DC_QR_ADDR 320 // id=contact
|
||||||
#define DC_QR_TEXT 330 // text1=text
|
#define DC_QR_TEXT 330 // text1=text
|
||||||
#define DC_QR_URL 332 // text1=URL
|
#define DC_QR_URL 332 // text1=URL
|
||||||
@@ -2512,24 +2540,14 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
|||||||
* ask the user if they want to create an account on the given domain,
|
* ask the user if they want to create an account on the given domain,
|
||||||
* if so, call dc_set_config_from_qr() and then dc_configure().
|
* if so, call dc_set_config_from_qr() and then dc_configure().
|
||||||
*
|
*
|
||||||
* - DC_QR_BACKUP2:
|
* - DC_QR_BACKUP:
|
||||||
* ask the user if they want to set up a new device.
|
* ask the user if they want to set up a new device.
|
||||||
* If so, pass the qr-code to dc_receive_backup().
|
* If so, pass the qr-code to dc_receive_backup().
|
||||||
*
|
*
|
||||||
* - DC_QR_BACKUP_TOO_NEW:
|
|
||||||
* show a hint to the user that this backup comes from a newer Delta Chat version
|
|
||||||
* and this device needs an update
|
|
||||||
*
|
|
||||||
* - DC_QR_WEBRTC_INSTANCE with dc_lot_t::text1=domain:
|
* - DC_QR_WEBRTC_INSTANCE with dc_lot_t::text1=domain:
|
||||||
* ask the user if they want to use the given service for video chats;
|
* ask the user if they want to use the given service for video chats;
|
||||||
* if so, call dc_set_config_from_qr().
|
* if so, call dc_set_config_from_qr().
|
||||||
*
|
*
|
||||||
* - DC_QR_PROXY with dc_lot_t::text1=address:
|
|
||||||
* ask the user if they want to use the given proxy.
|
|
||||||
* if so, call dc_set_config_from_qr() and restart I/O.
|
|
||||||
* On success, dc_get_config(context, "proxy_url")
|
|
||||||
* will contain the new proxy in normalized form as the first element.
|
|
||||||
*
|
|
||||||
* - DC_QR_ADDR with dc_lot_t::id=Contact ID:
|
* - DC_QR_ADDR with dc_lot_t::id=Contact ID:
|
||||||
* e-mail address scanned, optionally, a draft message could be set in
|
* e-mail address scanned, optionally, a draft message could be set in
|
||||||
* dc_lot_t::text1 in which case dc_lot_t::text1_meaning will be DC_TEXT1_DRAFT;
|
* dc_lot_t::text1 in which case dc_lot_t::text1_meaning will be DC_TEXT1_DRAFT;
|
||||||
@@ -2578,15 +2596,13 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get QR code text that will offer an Setup-Contact or Verified-Group invitation.
|
* Get QR code text 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 dc_check_qr() then;
|
* The scanning device will pass the scanned content to dc_check_qr() then;
|
||||||
* if dc_check_qr() returns DC_QR_ASK_VERIFYCONTACT or DC_QR_ASK_VERIFYGROUP
|
* if dc_check_qr() returns DC_QR_ASK_VERIFYCONTACT or DC_QR_ASK_VERIFYGROUP
|
||||||
* an out-of-band-verification can be joined using dc_join_securejoin()
|
* an out-of-band-verification can be joined using dc_join_securejoin()
|
||||||
*
|
*
|
||||||
* The returned text will also work as a normal https:-link,
|
|
||||||
* so that the QR code is useful also without Delta Chat being installed
|
|
||||||
* or can be passed to contacts through other channels.
|
|
||||||
*
|
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object.
|
* @param context The context object.
|
||||||
* @param chat_id If set to a group-chat-id,
|
* @param chat_id If set to a group-chat-id,
|
||||||
@@ -2606,7 +2622,6 @@ char* dc_get_securejoin_qr (dc_context_t* context, uint32_t ch
|
|||||||
* Get QR code image from the QR code text generated by dc_get_securejoin_qr().
|
* Get QR code image from the QR code text generated by dc_get_securejoin_qr().
|
||||||
* See dc_get_securejoin_qr() for details about the contained QR code.
|
* See dc_get_securejoin_qr() for details about the contained QR code.
|
||||||
*
|
*
|
||||||
* @deprecated 2024-10 use dc_create_qr_svg(dc_get_securejoin_qr()) instead.
|
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object.
|
* @param context The context object.
|
||||||
* @param chat_id group-chat-id for secure-join or 0 for setup-contact,
|
* @param chat_id group-chat-id for secure-join or 0 for setup-contact,
|
||||||
@@ -2787,22 +2802,6 @@ dc_array_t* dc_get_locations (dc_context_t* context, uint32_t cha
|
|||||||
void dc_delete_all_locations (dc_context_t* context);
|
void dc_delete_all_locations (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
// misc
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a QR code from any input data.
|
|
||||||
*
|
|
||||||
* The QR code is returned as a square SVG image.
|
|
||||||
*
|
|
||||||
* @memberof dc_context_t
|
|
||||||
* @param payload The content for the QR code.
|
|
||||||
* @return SVG image with the QR code.
|
|
||||||
* On errors, an empty string is returned.
|
|
||||||
* The returned string must be released using dc_str_unref() after usage.
|
|
||||||
*/
|
|
||||||
char* dc_create_qr_svg (const char* payload);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get last error string.
|
* Get last error string.
|
||||||
*
|
*
|
||||||
@@ -2891,7 +2890,6 @@ char* dc_backup_provider_get_qr (const dc_backup_provider_t* backup_provider);
|
|||||||
* This works like dc_backup_provider_qr() but returns the text of a rendered
|
* This works like dc_backup_provider_qr() but returns the text of a rendered
|
||||||
* SVG image containing the QR code.
|
* SVG image containing the QR code.
|
||||||
*
|
*
|
||||||
* @deprecated 2024-10 use dc_create_qr_svg(dc_backup_provider_get_qr()) instead.
|
|
||||||
* @memberof dc_backup_provider_t
|
* @memberof dc_backup_provider_t
|
||||||
* @param backup_provider The backup provider object as created by
|
* @param backup_provider The backup provider object as created by
|
||||||
* dc_backup_provider_new().
|
* dc_backup_provider_new().
|
||||||
@@ -2931,7 +2929,7 @@ void dc_backup_provider_unref (dc_backup_provider_t* backup_provider);
|
|||||||
* Gets a backup offered by a dc_backup_provider_t object on another device.
|
* Gets a backup offered by a dc_backup_provider_t object on another device.
|
||||||
*
|
*
|
||||||
* This function is called on a device that scanned the QR code offered by
|
* This function is called on a device that scanned the QR code offered by
|
||||||
* dc_backup_provider_get_qr(). Typically this is a
|
* dc_backup_sender_qr() or dc_backup_sender_qr_svg(). Typically this is a
|
||||||
* different device than that which provides the backup.
|
* different device than that which provides the backup.
|
||||||
*
|
*
|
||||||
* This call will block while the backup is being transferred and only
|
* This call will block while the backup is being transferred and only
|
||||||
@@ -3972,7 +3970,7 @@ int dc_msg_get_viewtype (const dc_msg_t* msg);
|
|||||||
*
|
*
|
||||||
* Outgoing message states:
|
* Outgoing message states:
|
||||||
* - @ref DC_STATE_OUT_PREPARING - For files which need time to be prepared before they can be sent,
|
* - @ref DC_STATE_OUT_PREPARING - For files which need time to be prepared before they can be sent,
|
||||||
* the message enters this state before @ref DC_STATE_OUT_PENDING. Deprecated.
|
* the message enters this state before @ref DC_STATE_OUT_PENDING.
|
||||||
* - @ref DC_STATE_OUT_DRAFT - Message saved as draft using dc_set_draft()
|
* - @ref DC_STATE_OUT_DRAFT - Message saved as draft using dc_set_draft()
|
||||||
* - @ref DC_STATE_OUT_PENDING - The user has pressed the "send" button but the
|
* - @ref DC_STATE_OUT_PENDING - The user has pressed the "send" button but the
|
||||||
* message is not yet sent and is pending in some way. Maybe we're offline (no checkmark).
|
* message is not yet sent and is pending in some way. Maybe we're offline (no checkmark).
|
||||||
@@ -4182,13 +4180,9 @@ char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char*
|
|||||||
* defaults to an empty string.
|
* defaults to an empty string.
|
||||||
* Implementations may offer an menu or a button to open this URL.
|
* Implementations may offer an menu or a button to open this URL.
|
||||||
* - internet_access:
|
* - internet_access:
|
||||||
* true if the Webxdc should get internet access;
|
* true if the Webxdc should get full internet access, including Webrtc.
|
||||||
* this is the case i.e. for experimental maps integration.
|
* currently, this is only true for encrypted Webxdc's in the self chat
|
||||||
* - self_addr: address to be used for `window.webxdc.selfAddr` in JS land.
|
* that have requested internet access in the manifest.
|
||||||
* - send_update_interval: Milliseconds to wait before calling `sendUpdate()` again since the last call.
|
|
||||||
* Should be exposed to `webxdc.sendUpdateInterval` in JS land.
|
|
||||||
* - send_update_max_size: Maximum number of bytes accepted for a serialized update object.
|
|
||||||
* Should be exposed to `webxdc.sendUpdateMaxSize` in JS land.
|
|
||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The webxdc instance.
|
* @param msg The webxdc instance.
|
||||||
@@ -4440,20 +4434,6 @@ int dc_msg_is_sent (const dc_msg_t* msg);
|
|||||||
int dc_msg_is_forwarded (const dc_msg_t* msg);
|
int dc_msg_is_forwarded (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the message was edited.
|
|
||||||
*
|
|
||||||
* Edited messages should be marked by the UI as such,
|
|
||||||
* e.g. by the text "Edited" beside the time.
|
|
||||||
* To edit messages, use dc_send_edit_request().
|
|
||||||
*
|
|
||||||
* @memberof dc_msg_t
|
|
||||||
* @param msg The message object.
|
|
||||||
* @return 1=message is edited, 0=message not edited.
|
|
||||||
*/
|
|
||||||
int dc_msg_is_edited (const dc_msg_t* msg);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the message is an informational message, created by the
|
* Check if the message is an informational message, created by the
|
||||||
* device or by another users. Such messages are not "typed" by the user but
|
* device or by another users. Such messages are not "typed" by the user but
|
||||||
@@ -4488,7 +4468,6 @@ int dc_msg_is_info (const dc_msg_t* msg);
|
|||||||
* - DC_INFO_INVALID_UNENCRYPTED_MAIL (13) - Info-message for "Provider requires end-to-end encryption which is not setup yet",
|
* - DC_INFO_INVALID_UNENCRYPTED_MAIL (13) - Info-message for "Provider requires end-to-end encryption which is not setup yet",
|
||||||
* the UI should change the corresponding string using #DC_STR_INVALID_UNENCRYPTED_MAIL
|
* the UI should change the corresponding string using #DC_STR_INVALID_UNENCRYPTED_MAIL
|
||||||
* and also offer a way to fix the encryption, eg. by a button offering a QR scan
|
* and also offer a way to fix the encryption, eg. by a button offering a QR scan
|
||||||
* - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info`
|
|
||||||
*
|
*
|
||||||
* Even when you display an icon,
|
* Even when you display an icon,
|
||||||
* you should still display the text of the informational message using dc_msg_get_text()
|
* you should still display the text of the informational message using dc_msg_get_text()
|
||||||
@@ -4518,23 +4497,19 @@ int dc_msg_get_info_type (const dc_msg_t* msg);
|
|||||||
#define DC_INFO_INVALID_UNENCRYPTED_MAIL 13
|
#define DC_INFO_INVALID_UNENCRYPTED_MAIL 13
|
||||||
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
|
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get link attached to an webxdc info message.
|
* Check if a message is still in creation. A message is in creation between
|
||||||
* The info message needs to be of type DC_INFO_WEBXDC_INFO_MESSAGE.
|
* the calls to dc_prepare_msg() and dc_send_msg().
|
||||||
*
|
*
|
||||||
* Typically, this is used to set `document.location.href` in JS land.
|
* Typically, this is used for videos that are recoded by the UI before
|
||||||
*
|
* they can be sent.
|
||||||
* Webxdc apps can define the link by setting `update.href` when sending and update,
|
|
||||||
* see dc_send_webxdc_status_update().
|
|
||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The info message object.
|
* @param msg The message object.
|
||||||
* Not: the webxdc instance.
|
* @return 1=message is still in creation (dc_send_msg() was not called yet),
|
||||||
* @return The link to be set to `document.location.href` in JS land.
|
* 0=message no longer in creation.
|
||||||
* Returns NULL if there is no link attached to the info message and on errors.
|
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_webxdc_href (const dc_msg_t* msg);
|
int dc_msg_is_increation (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -4687,7 +4662,7 @@ int dc_msg_has_html (dc_msg_t* msg);
|
|||||||
* If the download fails or succeeds,
|
* If the download fails or succeeds,
|
||||||
* the event @ref DC_EVENT_MSGS_CHANGED is emitted.
|
* the event @ref DC_EVENT_MSGS_CHANGED is emitted.
|
||||||
*
|
*
|
||||||
* - @ref DC_DOWNLOAD_UNDECIPHERABLE - The message does not need any further download action.
|
* - @ref DC_DOWNLOAD_UNDECIPHERABLE - The message does not need any futher download action.
|
||||||
* It was fully downloaded, but we failed to decrypt it.
|
* It was fully downloaded, but we failed to decrypt it.
|
||||||
* - @ref DC_DOWNLOAD_FAILURE - Download error, the user may start over calling dc_download_full_msg() again.
|
* - @ref DC_DOWNLOAD_FAILURE - Download error, the user may start over calling dc_download_full_msg() again.
|
||||||
*
|
*
|
||||||
@@ -4764,33 +4739,23 @@ void dc_msg_set_override_sender_name(dc_msg_t* msg, const char* name)
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the file associated with a message.
|
* Set the file associated with a message object.
|
||||||
*
|
* This does not alter any information in the database
|
||||||
* If `name` is non-null, it is used as the file name
|
* nor copy or move the file or checks if the file exist.
|
||||||
* and the actual current name of the file is ignored.
|
* All this can be done with dc_send_msg() later.
|
||||||
*
|
|
||||||
* If the source file is already in the blobdir, it will be renamed,
|
|
||||||
* otherwise it will be copied to the blobdir first.
|
|
||||||
*
|
|
||||||
* In order to deduplicate files that contain the same data,
|
|
||||||
* the file will be named `<hash>.<extension>`, e.g. `ce940175885d7b78f7b7e9f1396611f.jpg`.
|
|
||||||
*
|
|
||||||
* NOTE:
|
|
||||||
* - This function will rename the file. To get the new file path, call `get_file()`.
|
|
||||||
* - The file must not be modified after this function was called.
|
|
||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object. Must not be NULL.
|
* @param msg The message object.
|
||||||
* @param file The path of the file to attach. Must not be NULL.
|
* @param file If the message object is used in dc_send_msg() later,
|
||||||
* @param name The original filename of the attachment. If NULL, the current name of `file` will be used instead.
|
* this must be the full path of the image file to send.
|
||||||
* @param filemime The MIME type of the file. NULL if you don't know or don't care.
|
* @param filemime The MIME type of the file. NULL if you don't know or don't care.
|
||||||
*/
|
*/
|
||||||
void dc_msg_set_file_and_deduplicate(dc_msg_t* msg, const char* file, const char* name, const char* filemime);
|
void dc_msg_set_file (dc_msg_t* msg, const char* file, const char* filemime);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the dimensions associated with message object.
|
* Set the dimensions associated with message object.
|
||||||
* Typically this is the width and the height of an image or video associated using dc_msg_set_file_and_deduplicate().
|
* Typically this is the width and the height of an image or video associated using dc_msg_set_file().
|
||||||
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
@@ -4803,7 +4768,7 @@ void dc_msg_set_dimension (dc_msg_t* msg, int width, int hei
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the duration associated with message object.
|
* Set the duration associated with message object.
|
||||||
* Typically this is the duration of an audio or video associated using dc_msg_set_file_and_deduplicate().
|
* Typically this is the duration of an audio or video associated using dc_msg_set_file().
|
||||||
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
@@ -4930,35 +4895,6 @@ dc_msg_t* dc_msg_get_quoted_msg (const dc_msg_t* msg);
|
|||||||
dc_msg_t* dc_msg_get_parent (const dc_msg_t* msg);
|
dc_msg_t* dc_msg_get_parent (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get original message ID for a saved message from the "Saved Messages" chat.
|
|
||||||
*
|
|
||||||
* Can be used by UI to show a button to go the original message
|
|
||||||
* and an option to "Unsave" the message.
|
|
||||||
*
|
|
||||||
* @memberof dc_msg_t
|
|
||||||
* @param msg The message object. Usually, this refers to a a message inside "Saved Messages".
|
|
||||||
* @return The message ID of the original message.
|
|
||||||
* 0 if the given message object is not a "Saved Message"
|
|
||||||
* or if the original message does no longer exist.
|
|
||||||
*/
|
|
||||||
uint32_t dc_msg_get_original_msg_id (const dc_msg_t* msg);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a message was saved and return its ID inside "Saved Messages".
|
|
||||||
*
|
|
||||||
* Deleting the returned message will un-save the message.
|
|
||||||
* The state "is saved" can be used to show some icon to indicate that a message was saved.
|
|
||||||
*
|
|
||||||
* @memberof dc_msg_t
|
|
||||||
* @param msg The message object. Usually, this refers to a a message outside "Saved Messages".
|
|
||||||
* @return The message ID inside "Saved Messages", if any.
|
|
||||||
* 0 if the given message object is not saved.
|
|
||||||
*/
|
|
||||||
uint32_t dc_msg_get_saved_msg_id (const dc_msg_t* msg);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force the message to be sent in plain text.
|
* Force the message to be sent in plain text.
|
||||||
*
|
*
|
||||||
@@ -5442,7 +5378,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
* If you want to define the type of a dc_msg_t object for sending,
|
* If you want to define the type of a dc_msg_t object for sending,
|
||||||
* use dc_msg_new().
|
* use dc_msg_new().
|
||||||
* Depending on the type, you will set more properties using e.g.
|
* Depending on the type, you will set more properties using e.g.
|
||||||
* dc_msg_set_text() or dc_msg_set_file_and_deduplicate().
|
* dc_msg_set_text() or dc_msg_set_file().
|
||||||
* To finally send the message, use dc_send_msg().
|
* To finally send the message, use dc_send_msg().
|
||||||
*
|
*
|
||||||
* To get the types of dc_msg_t objects received, use dc_msg_get_viewtype().
|
* To get the types of dc_msg_t objects received, use dc_msg_get_viewtype().
|
||||||
@@ -5463,7 +5399,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
/**
|
/**
|
||||||
* Image message.
|
* Image message.
|
||||||
* If the image is an animated GIF, the type #DC_MSG_GIF should be used.
|
* If the image is an animated GIF, the type #DC_MSG_GIF should be used.
|
||||||
* File, width, and height are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_dimension()
|
* File, width, and height are set via dc_msg_set_file(), dc_msg_set_dimension()
|
||||||
* and retrieved via dc_msg_get_file(), dc_msg_get_width(), and dc_msg_get_height().
|
* and retrieved via dc_msg_get_file(), dc_msg_get_width(), and dc_msg_get_height().
|
||||||
*
|
*
|
||||||
* Before sending, the image is recoded to an reasonable size,
|
* Before sending, the image is recoded to an reasonable size,
|
||||||
@@ -5476,7 +5412,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Animated GIF message.
|
* Animated GIF message.
|
||||||
* File, width, and height are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_dimension()
|
* File, width, and height are set via dc_msg_set_file(), dc_msg_set_dimension()
|
||||||
* and retrieved via dc_msg_get_file(), dc_msg_get_width(), and dc_msg_get_height().
|
* and retrieved via dc_msg_get_file(), dc_msg_get_width(), and dc_msg_get_height().
|
||||||
*/
|
*/
|
||||||
#define DC_MSG_GIF 21
|
#define DC_MSG_GIF 21
|
||||||
@@ -5484,8 +5420,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Message containing a sticker, similar to image.
|
* Message containing a sticker, similar to image.
|
||||||
* NB: When sending, the message viewtype may be changed to `Image` by some heuristics like checking
|
|
||||||
* for transparent pixels.
|
|
||||||
* If possible, the UI should display the image without borders in a transparent way.
|
* If possible, the UI should display the image without borders in a transparent way.
|
||||||
* A click on a sticker will offer to install the sticker set in some future.
|
* A click on a sticker will offer to install the sticker set in some future.
|
||||||
*/
|
*/
|
||||||
@@ -5494,7 +5428,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Message containing an audio file.
|
* Message containing an audio file.
|
||||||
* File and duration are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_duration()
|
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||||
* and retrieved via dc_msg_get_file(), and dc_msg_get_duration().
|
* and retrieved via dc_msg_get_file(), and dc_msg_get_duration().
|
||||||
*/
|
*/
|
||||||
#define DC_MSG_AUDIO 40
|
#define DC_MSG_AUDIO 40
|
||||||
@@ -5503,7 +5437,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
/**
|
/**
|
||||||
* A voice message that was directly recorded by the user.
|
* A voice message that was directly recorded by the user.
|
||||||
* For all other audio messages, the type #DC_MSG_AUDIO should be used.
|
* For all other audio messages, the type #DC_MSG_AUDIO should be used.
|
||||||
* File and duration are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_duration()
|
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
|
||||||
* and retrieved via dc_msg_get_file(), and dc_msg_get_duration().
|
* and retrieved via dc_msg_get_file(), and dc_msg_get_duration().
|
||||||
*/
|
*/
|
||||||
#define DC_MSG_VOICE 41
|
#define DC_MSG_VOICE 41
|
||||||
@@ -5512,7 +5446,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
/**
|
/**
|
||||||
* Video messages.
|
* Video messages.
|
||||||
* File, width, height, and duration
|
* File, width, height, and duration
|
||||||
* are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_dimension(), dc_msg_set_duration()
|
* are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
|
||||||
* and retrieved via
|
* and retrieved via
|
||||||
* dc_msg_get_file(), dc_msg_get_width(),
|
* dc_msg_get_file(), dc_msg_get_width(),
|
||||||
* dc_msg_get_height(), and dc_msg_get_duration().
|
* dc_msg_get_height(), and dc_msg_get_duration().
|
||||||
@@ -5522,7 +5456,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Message containing any file, e.g. a PDF.
|
* Message containing any file, e.g. a PDF.
|
||||||
* The file is set via dc_msg_set_file_and_deduplicate()
|
* The file is set via dc_msg_set_file()
|
||||||
* and retrieved via dc_msg_get_file().
|
* and retrieved via dc_msg_get_file().
|
||||||
*/
|
*/
|
||||||
#define DC_MSG_FILE 60
|
#define DC_MSG_FILE 60
|
||||||
@@ -5590,8 +5524,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Outgoing message being prepared. See dc_msg_get_state() for details.
|
* Outgoing message being prepared. See dc_msg_get_state() for details.
|
||||||
*
|
|
||||||
* @deprecated 2024-12-07
|
|
||||||
*/
|
*/
|
||||||
#define DC_STATE_OUT_PREPARING 18
|
#define DC_STATE_OUT_PREPARING 18
|
||||||
|
|
||||||
@@ -5765,14 +5697,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
#define DC_CERTCK_STRICT 1
|
#define DC_CERTCK_STRICT 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accept certificates that are expired, self-signed
|
* Accept invalid certificates, including self-signed ones
|
||||||
* or not valid for the server hostname.
|
* or having incorrect hostname.
|
||||||
*/
|
|
||||||
#define DC_CERTCK_ACCEPT_INVALID 2
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For API compatibility only: Treat this as DC_CERTCK_ACCEPT_INVALID on reading.
|
|
||||||
* Must not be written.
|
|
||||||
*/
|
*/
|
||||||
#define DC_CERTCK_ACCEPT_INVALID_CERTIFICATES 3
|
#define DC_CERTCK_ACCEPT_INVALID_CERTIFICATES 3
|
||||||
|
|
||||||
@@ -5812,23 +5738,6 @@ void dc_jsonrpc_unref(dc_jsonrpc_instance_t* jsonrpc_instance);
|
|||||||
* returns immediately and once the result is ready it can be retrieved via dc_jsonrpc_next_response()
|
* returns immediately and once the result is ready it can be retrieved via dc_jsonrpc_next_response()
|
||||||
* the jsonrpc specification defines an invocation id that can then be used to match request and response.
|
* the jsonrpc specification defines an invocation id that can then be used to match request and response.
|
||||||
*
|
*
|
||||||
* An overview of JSON-RPC calls is available at
|
|
||||||
* <https://js.jsonrpc.delta.chat/classes/RawClient.html>.
|
|
||||||
* Note that the page describes only the rough methods.
|
|
||||||
* Calling convention, casing etc. does vary, this is a known flaw,
|
|
||||||
* and at some point we will get to improve that :)
|
|
||||||
*
|
|
||||||
* Also, note that most calls are more high-level than this CFFI, require more database calls and are slower.
|
|
||||||
* They're more suitable for an environment that is totally async and/or cannot use CFFI, which might not be true for native apps.
|
|
||||||
*
|
|
||||||
* Notable exceptions that exist only as JSON-RPC and probably never get a CFFI counterpart:
|
|
||||||
* - getMessageReactions(), sendReaction()
|
|
||||||
* - getHttpResponse()
|
|
||||||
* - draftSelfReport()
|
|
||||||
* - getAccountFileSize()
|
|
||||||
* - importVcard(), parseVcard(), makeVcard()
|
|
||||||
* - sendWebxdcRealtimeData, sendWebxdcRealtimeAdvertisement(), leaveWebxdcRealtime()
|
|
||||||
*
|
|
||||||
* @memberof dc_jsonrpc_instance_t
|
* @memberof dc_jsonrpc_instance_t
|
||||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||||
* @param request JSON-RPC request as string
|
* @param request JSON-RPC request as string
|
||||||
@@ -5849,8 +5758,6 @@ char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
|||||||
/**
|
/**
|
||||||
* Make a JSON-RPC call and return a response.
|
* Make a JSON-RPC call and return a response.
|
||||||
*
|
*
|
||||||
* See dc_jsonrpc_request() for an overview of possible calls and for more information.
|
|
||||||
*
|
|
||||||
* @memberof dc_jsonrpc_instance_t
|
* @memberof dc_jsonrpc_instance_t
|
||||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||||
* @param input JSON-RPC request.
|
* @param input JSON-RPC request.
|
||||||
@@ -5946,26 +5853,15 @@ int dc_event_get_data2_int(dc_event_t* event);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get data associated with an event object.
|
* Get data associated with an event object.
|
||||||
* The meaning of the data depends on the event ID returned as @ref DC_EVENT constants.
|
* The meaning of the data depends on the event ID
|
||||||
|
* returned as @ref DC_EVENT constants by dc_event_get_id().
|
||||||
|
* See also dc_event_get_data1_int() and dc_event_get_data2_int().
|
||||||
*
|
*
|
||||||
* @memberof dc_event_t
|
* @memberof dc_event_t
|
||||||
* @param event Event object as returned from dc_get_next_event().
|
* @param event Event object as returned from dc_get_next_event().
|
||||||
* @return "data1" string or NULL.
|
* @return "data2" as a string or NULL.
|
||||||
* The meaning depends on the event type associated with this event.
|
* the meaning depends on the event type associated with this event.
|
||||||
* Must be freed using dc_str_unref().
|
* Once you're done with the string, you have to unref it using dc_unref_str().
|
||||||
*/
|
|
||||||
char* dc_event_get_data1_str(dc_event_t* event);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get data associated with an event object.
|
|
||||||
* The meaning of the data depends on the event ID returned as @ref DC_EVENT constants.
|
|
||||||
*
|
|
||||||
* @memberof dc_event_t
|
|
||||||
* @param event Event object as returned from dc_get_next_event().
|
|
||||||
* @return "data2" string or NULL.
|
|
||||||
* The meaning depends on the event type associated with this event.
|
|
||||||
* Must be freed using dc_str_unref().
|
|
||||||
*/
|
*/
|
||||||
char* dc_event_get_data2_str(dc_event_t* event);
|
char* dc_event_get_data2_str(dc_event_t* event);
|
||||||
|
|
||||||
@@ -6148,50 +6044,12 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
#define DC_EVENT_REACTIONS_CHANGED 2001
|
#define DC_EVENT_REACTIONS_CHANGED 2001
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A reaction to one's own sent message received.
|
|
||||||
* Typically, the UI will show a notification for that.
|
|
||||||
*
|
|
||||||
* In addition to this event, DC_EVENT_REACTIONS_CHANGED is emitted.
|
|
||||||
*
|
|
||||||
* @param data1 (int) contact_id ID of the contact sending this reaction.
|
|
||||||
* @param data2 (int) msg_id + (char*) reaction.
|
|
||||||
* ID of the message for which a reaction was received in dc_event_get_data2_int(),
|
|
||||||
* and the reaction as dc_event_get_data2_str().
|
|
||||||
* string must be passed to dc_str_unref() afterwards.
|
|
||||||
*/
|
|
||||||
#define DC_EVENT_INCOMING_REACTION 2002
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A webxdc wants an info message or a changed summary to be notified.
|
|
||||||
*
|
|
||||||
* @param data1 (int) contact_id ID _and_ (char*) href.
|
|
||||||
* - dc_event_get_data1_int() returns contact_id of the sending contact.
|
|
||||||
* - dc_event_get_data1_str() returns the href as set to `update.href`.
|
|
||||||
* @param data2 (int) msg_id _and_ (char*) text_to_notify.
|
|
||||||
* - dc_event_get_data2_int() returns the msg_id,
|
|
||||||
* referring to the webxdc-info-message, if there is any.
|
|
||||||
* Sometimes no webxdc-info-message is added to the chat
|
|
||||||
* and yet a notification is sent; in this case the msg_id
|
|
||||||
* of the webxdc instance is returned.
|
|
||||||
* - dc_event_get_data2_str() returns text_to_notify,
|
|
||||||
* the text that shall be shown in the notification.
|
|
||||||
* string must be passed to dc_str_unref() afterwards.
|
|
||||||
*/
|
|
||||||
#define DC_EVENT_INCOMING_WEBXDC_NOTIFY 2003
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There is a fresh message. Typically, the user will show an notification
|
* There is a fresh message. Typically, the user will show an notification
|
||||||
* when receiving this message.
|
* when receiving this message.
|
||||||
*
|
*
|
||||||
* There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
* There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||||
*
|
*
|
||||||
* If the message is a webxdc info message,
|
|
||||||
* dc_msg_get_parent() returns the webxdc instance the notification belongs to.
|
|
||||||
*
|
|
||||||
* @param data1 (int) chat_id
|
* @param data1 (int) chat_id
|
||||||
* @param data2 (int) msg_id
|
* @param data2 (int) msg_id
|
||||||
*/
|
*/
|
||||||
@@ -6286,18 +6144,6 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
#define DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED 2021
|
#define DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED 2021
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chat was deleted.
|
|
||||||
* This event is emitted in response to dc_delete_chat()
|
|
||||||
* called on this or another device.
|
|
||||||
* The event is a good place to remove notifications or homescreen shortcuts.
|
|
||||||
*
|
|
||||||
* @param data1 (int) chat_id
|
|
||||||
* @param data2 (int) 0
|
|
||||||
*/
|
|
||||||
#define DC_EVENT_CHAT_DELETED 2023
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contact(s) created, renamed, verified, blocked or deleted.
|
* Contact(s) created, renamed, verified, blocked or deleted.
|
||||||
*
|
*
|
||||||
@@ -6415,7 +6261,7 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Webxdc status update received.
|
* webxdc status update received.
|
||||||
* To get the received status update, use dc_get_webxdc_status_updates() with
|
* To get the received status update, use dc_get_webxdc_status_updates() with
|
||||||
* `serial` set to the last known update
|
* `serial` set to the last known update
|
||||||
* (in case of special bots, `status_update_serial` from `data2`
|
* (in case of special bots, `status_update_serial` from `data2`
|
||||||
@@ -6438,27 +6284,6 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
|
|
||||||
#define DC_EVENT_WEBXDC_INSTANCE_DELETED 2121
|
#define DC_EVENT_WEBXDC_INSTANCE_DELETED 2121
|
||||||
|
|
||||||
/**
|
|
||||||
* Data received over an ephemeral peer channel.
|
|
||||||
*
|
|
||||||
* @param data1 (int) msg_id
|
|
||||||
* @param data2 (int) + (char*) binary data.
|
|
||||||
* length is returned as integer with dc_event_get_data2_int()
|
|
||||||
* and binary data is returned as dc_event_get_data2_str().
|
|
||||||
* Binary data must be passed to dc_str_unref() afterwards.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DC_EVENT_WEBXDC_REALTIME_DATA 2150
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advertisement for ephemeral peer channel communication received.
|
|
||||||
* This can be used by bots to initiate peer-to-peer communication from their side.
|
|
||||||
* @param data1 (int) msg_id
|
|
||||||
* @param data2 0
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DC_EVENT_WEBXDC_REALTIME_ADVERTISEMENT 2151
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells that the Background fetch was completed (or timed out).
|
* Tells that the Background fetch was completed (or timed out).
|
||||||
*
|
*
|
||||||
@@ -6487,33 +6312,6 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
|
|
||||||
#define DC_EVENT_CHATLIST_ITEM_CHANGED 2301
|
#define DC_EVENT_CHATLIST_ITEM_CHANGED 2301
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
|
|
||||||
*
|
|
||||||
* This event is only emitted by the account manager.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DC_EVENT_ACCOUNTS_CHANGED 2302
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform that an account property that might be shown in the account list changed, namely:
|
|
||||||
* - is_configured (see dc_is_configured())
|
|
||||||
* - displayname
|
|
||||||
* - selfavatar
|
|
||||||
* - private_tag
|
|
||||||
*
|
|
||||||
* This event is emitted from the account whose property changed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DC_EVENT_ACCOUNTS_ITEM_CHANGED 2303
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform that some events have been skipped due to event channel overflow.
|
|
||||||
*
|
|
||||||
* @param data1 (int) number of events that have been skipped
|
|
||||||
*/
|
|
||||||
#define DC_EVENT_CHANNEL_OVERFLOW 2400
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
@@ -6538,6 +6336,15 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
#define DC_MEDIA_QUALITY_WORSE 1
|
#define DC_MEDIA_QUALITY_WORSE 1
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Values for dc_get|set_config("key_gen_type")
|
||||||
|
*/
|
||||||
|
#define DC_KEY_GEN_DEFAULT 0
|
||||||
|
#define DC_KEY_GEN_RSA2048 1
|
||||||
|
#define DC_KEY_GEN_ED25519 2
|
||||||
|
#define DC_KEY_GEN_RSA4096 3
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
|
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
|
||||||
*
|
*
|
||||||
@@ -6812,16 +6619,12 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
/// "Message opened"
|
/// "Message opened"
|
||||||
///
|
///
|
||||||
/// Used in subjects of outgoing read receipts.
|
/// Used in subjects of outgoing read receipts.
|
||||||
///
|
|
||||||
/// @deprecated Deprecated 2024-07-26
|
|
||||||
#define DC_STR_READRCPT 31
|
#define DC_STR_READRCPT 31
|
||||||
|
|
||||||
/// "The message '%1$s' you sent was displayed on the screen of the recipient."
|
/// "The message '%1$s' you sent was displayed on the screen of the recipient."
|
||||||
///
|
///
|
||||||
/// Used as message text of outgoing read receipts.
|
/// Used as message text of outgoing read receipts.
|
||||||
/// - %1$s will be replaced by the subject of the displayed message
|
/// - %1$s will be replaced by the subject of the displayed message
|
||||||
///
|
|
||||||
/// @deprecated Deprecated 2024-06-23
|
|
||||||
#define DC_STR_READRCPT_MAILBODY 32
|
#define DC_STR_READRCPT_MAILBODY 32
|
||||||
|
|
||||||
/// @deprecated Deprecated, this string is no longer needed.
|
/// @deprecated Deprecated, this string is no longer needed.
|
||||||
@@ -6944,7 +6747,7 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
|
|
||||||
/// "Failed to send message to %1$s."
|
/// "Failed to send message to %1$s."
|
||||||
///
|
///
|
||||||
/// Unused. Was used in group chat status messages.
|
/// Used in status messages.
|
||||||
/// - %1$s will be replaced by the name of the contact the message cannot be sent to
|
/// - %1$s will be replaced by the name of the contact the message cannot be sent to
|
||||||
#define DC_STR_FAILED_SENDING_TO 74
|
#define DC_STR_FAILED_SENDING_TO 74
|
||||||
|
|
||||||
@@ -7540,7 +7343,7 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
/// Used as info message.
|
/// Used as info message.
|
||||||
#define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191
|
#define DC_STR_SECUREJOIN_WAIT_TIMEOUT 191
|
||||||
|
|
||||||
/// "Contact". Deprecated, currently unused.
|
/// "Contact"
|
||||||
#define DC_STR_CONTACT 200
|
#define DC_STR_CONTACT 200
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,13 +30,11 @@ use deltachat::ephemeral::Timer as EphemeralTimer;
|
|||||||
use deltachat::imex::BackupProvider;
|
use deltachat::imex::BackupProvider;
|
||||||
use deltachat::key::preconfigure_keypair;
|
use deltachat::key::preconfigure_keypair;
|
||||||
use deltachat::message::MsgId;
|
use deltachat::message::MsgId;
|
||||||
use deltachat::qr_code_generator::{create_qr_svg, generate_backup_qr, get_securejoin_qr_svg};
|
use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
|
||||||
use deltachat::stock_str::StockMessage;
|
use deltachat::stock_str::StockMessage;
|
||||||
use deltachat::webxdc::StatusUpdateSerial;
|
use deltachat::webxdc::StatusUpdateSerial;
|
||||||
use deltachat::*;
|
use deltachat::*;
|
||||||
use deltachat::{accounts::Accounts, log::LogExt};
|
use deltachat::{accounts::Accounts, log::LogExt};
|
||||||
use deltachat_jsonrpc::api::CommandApi;
|
|
||||||
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcSession};
|
|
||||||
use num_traits::{FromPrimitive, ToPrimitive};
|
use num_traits::{FromPrimitive, ToPrimitive};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@@ -415,6 +413,16 @@ pub unsafe extern "C" fn dc_get_push_state(context: *const dc_context_t) -> libc
|
|||||||
block_on(ctx.push_state()) as libc::c_int
|
block_on(ctx.push_state()) as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_all_work_done(context: *mut dc_context_t) -> libc::c_int {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_all_work_done()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let ctx = &*context;
|
||||||
|
block_on(async move { ctx.all_work_done().await as libc::c_int })
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_get_oauth2_url(
|
pub unsafe extern "C" fn dc_get_oauth2_url(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -533,8 +541,6 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
|||||||
EventType::ErrorSelfNotInGroup(_) => 410,
|
EventType::ErrorSelfNotInGroup(_) => 410,
|
||||||
EventType::MsgsChanged { .. } => 2000,
|
EventType::MsgsChanged { .. } => 2000,
|
||||||
EventType::ReactionsChanged { .. } => 2001,
|
EventType::ReactionsChanged { .. } => 2001,
|
||||||
EventType::IncomingReaction { .. } => 2002,
|
|
||||||
EventType::IncomingWebxdcNotify { .. } => 2003,
|
|
||||||
EventType::IncomingMsg { .. } => 2005,
|
EventType::IncomingMsg { .. } => 2005,
|
||||||
EventType::IncomingMsgBunch { .. } => 2006,
|
EventType::IncomingMsgBunch { .. } => 2006,
|
||||||
EventType::MsgsNoticed { .. } => 2008,
|
EventType::MsgsNoticed { .. } => 2008,
|
||||||
@@ -544,7 +550,6 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
|||||||
EventType::MsgDeleted { .. } => 2016,
|
EventType::MsgDeleted { .. } => 2016,
|
||||||
EventType::ChatModified(_) => 2020,
|
EventType::ChatModified(_) => 2020,
|
||||||
EventType::ChatEphemeralTimerModified { .. } => 2021,
|
EventType::ChatEphemeralTimerModified { .. } => 2021,
|
||||||
EventType::ChatDeleted { .. } => 2023,
|
|
||||||
EventType::ContactsChanged(_) => 2030,
|
EventType::ContactsChanged(_) => 2030,
|
||||||
EventType::LocationChanged(_) => 2035,
|
EventType::LocationChanged(_) => 2035,
|
||||||
EventType::ConfigureProgress { .. } => 2041,
|
EventType::ConfigureProgress { .. } => 2041,
|
||||||
@@ -558,16 +563,9 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
|||||||
EventType::WebxdcStatusUpdate { .. } => 2120,
|
EventType::WebxdcStatusUpdate { .. } => 2120,
|
||||||
EventType::WebxdcInstanceDeleted { .. } => 2121,
|
EventType::WebxdcInstanceDeleted { .. } => 2121,
|
||||||
EventType::WebxdcRealtimeData { .. } => 2150,
|
EventType::WebxdcRealtimeData { .. } => 2150,
|
||||||
EventType::WebxdcRealtimeAdvertisementReceived { .. } => 2151,
|
|
||||||
EventType::AccountsBackgroundFetchDone => 2200,
|
EventType::AccountsBackgroundFetchDone => 2200,
|
||||||
EventType::ChatlistChanged => 2300,
|
EventType::ChatlistChanged => 2300,
|
||||||
EventType::ChatlistItemChanged { .. } => 2301,
|
EventType::ChatlistItemChanged { .. } => 2301,
|
||||||
EventType::AccountsChanged => 2302,
|
|
||||||
EventType::AccountsItemChanged => 2303,
|
|
||||||
EventType::EventChannelOverflow { .. } => 2400,
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
#[cfg(test)]
|
|
||||||
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,12 +594,8 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
|||||||
| EventType::ConfigSynced { .. }
|
| EventType::ConfigSynced { .. }
|
||||||
| EventType::IncomingMsgBunch { .. }
|
| EventType::IncomingMsgBunch { .. }
|
||||||
| EventType::ErrorSelfNotInGroup(_)
|
| EventType::ErrorSelfNotInGroup(_)
|
||||||
| EventType::AccountsBackgroundFetchDone
|
| EventType::AccountsBackgroundFetchDone => 0,
|
||||||
| EventType::ChatlistChanged
|
EventType::ChatlistChanged => 0,
|
||||||
| EventType::AccountsChanged
|
|
||||||
| EventType::AccountsItemChanged => 0,
|
|
||||||
EventType::IncomingReaction { contact_id, .. }
|
|
||||||
| EventType::IncomingWebxdcNotify { contact_id, .. } => contact_id.to_u32() as libc::c_int,
|
|
||||||
EventType::MsgsChanged { chat_id, .. }
|
EventType::MsgsChanged { chat_id, .. }
|
||||||
| EventType::ReactionsChanged { chat_id, .. }
|
| EventType::ReactionsChanged { chat_id, .. }
|
||||||
| EventType::IncomingMsg { chat_id, .. }
|
| EventType::IncomingMsg { chat_id, .. }
|
||||||
@@ -611,8 +605,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
|||||||
| EventType::MsgRead { chat_id, .. }
|
| EventType::MsgRead { chat_id, .. }
|
||||||
| EventType::MsgDeleted { chat_id, .. }
|
| EventType::MsgDeleted { chat_id, .. }
|
||||||
| EventType::ChatModified(chat_id)
|
| EventType::ChatModified(chat_id)
|
||||||
| EventType::ChatEphemeralTimerModified { chat_id, .. }
|
| EventType::ChatEphemeralTimerModified { chat_id, .. } => chat_id.to_u32() as libc::c_int,
|
||||||
| EventType::ChatDeleted { chat_id } => chat_id.to_u32() as libc::c_int,
|
|
||||||
EventType::ContactsChanged(id) | EventType::LocationChanged(id) => {
|
EventType::ContactsChanged(id) | EventType::LocationChanged(id) => {
|
||||||
let id = id.unwrap_or_default();
|
let id = id.unwrap_or_default();
|
||||||
id.to_u32() as libc::c_int
|
id.to_u32() as libc::c_int
|
||||||
@@ -627,15 +620,10 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
|||||||
}
|
}
|
||||||
EventType::WebxdcRealtimeData { msg_id, .. }
|
EventType::WebxdcRealtimeData { msg_id, .. }
|
||||||
| EventType::WebxdcStatusUpdate { msg_id, .. }
|
| EventType::WebxdcStatusUpdate { msg_id, .. }
|
||||||
| EventType::WebxdcRealtimeAdvertisementReceived { msg_id }
|
|
||||||
| EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
| EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
||||||
EventType::ChatlistItemChanged { chat_id } => {
|
EventType::ChatlistItemChanged { chat_id } => {
|
||||||
chat_id.unwrap_or_default().to_u32() as libc::c_int
|
chat_id.unwrap_or_default().to_u32() as libc::c_int
|
||||||
}
|
}
|
||||||
EventType::EventChannelOverflow { n } => *n as libc::c_int,
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
#[cfg(test)]
|
|
||||||
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,21 +658,15 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
|||||||
| EventType::ConnectivityChanged
|
| EventType::ConnectivityChanged
|
||||||
| EventType::WebxdcInstanceDeleted { .. }
|
| EventType::WebxdcInstanceDeleted { .. }
|
||||||
| EventType::IncomingMsgBunch { .. }
|
| EventType::IncomingMsgBunch { .. }
|
||||||
|
| EventType::WebxdcRealtimeData { .. }
|
||||||
| EventType::SelfavatarChanged
|
| EventType::SelfavatarChanged
|
||||||
| EventType::AccountsBackgroundFetchDone
|
| EventType::AccountsBackgroundFetchDone
|
||||||
| EventType::ChatlistChanged
|
| EventType::ChatlistChanged
|
||||||
| EventType::ChatlistItemChanged { .. }
|
| EventType::ChatlistItemChanged { .. }
|
||||||
| EventType::AccountsChanged
|
| EventType::ConfigSynced { .. } => 0,
|
||||||
| EventType::AccountsItemChanged
|
EventType::ChatModified(_) => 0,
|
||||||
| EventType::ConfigSynced { .. }
|
|
||||||
| EventType::ChatModified(_)
|
|
||||||
| EventType::ChatDeleted { .. }
|
|
||||||
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
|
|
||||||
| EventType::EventChannelOverflow { .. } => 0,
|
|
||||||
EventType::MsgsChanged { msg_id, .. }
|
EventType::MsgsChanged { msg_id, .. }
|
||||||
| EventType::ReactionsChanged { msg_id, .. }
|
| EventType::ReactionsChanged { msg_id, .. }
|
||||||
| EventType::IncomingReaction { msg_id, .. }
|
|
||||||
| EventType::IncomingWebxdcNotify { msg_id, .. }
|
|
||||||
| EventType::IncomingMsg { msg_id, .. }
|
| EventType::IncomingMsg { msg_id, .. }
|
||||||
| EventType::MsgDelivered { msg_id, .. }
|
| EventType::MsgDelivered { msg_id, .. }
|
||||||
| EventType::MsgFailed { msg_id, .. }
|
| EventType::MsgFailed { msg_id, .. }
|
||||||
@@ -697,31 +679,6 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
|||||||
status_update_serial,
|
status_update_serial,
|
||||||
..
|
..
|
||||||
} => status_update_serial.to_u32() as libc::c_int,
|
} => status_update_serial.to_u32() as libc::c_int,
|
||||||
EventType::WebxdcRealtimeData { data, .. } => data.len() as libc::c_int,
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
#[cfg(test)]
|
|
||||||
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_event_get_data1_str(event: *mut dc_event_t) -> *mut libc::c_char {
|
|
||||||
if event.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_event_get_data1_str()");
|
|
||||||
return ptr::null_mut();
|
|
||||||
}
|
|
||||||
|
|
||||||
let event = &(*event).typ;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
EventType::IncomingWebxdcNotify { href, .. } => {
|
|
||||||
if let Some(href) = href {
|
|
||||||
href.to_c_string().unwrap_or_default().into_raw()
|
|
||||||
} else {
|
|
||||||
ptr::null_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => ptr::null_mut(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,16 +725,12 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
|||||||
| EventType::SelfavatarChanged
|
| EventType::SelfavatarChanged
|
||||||
| EventType::WebxdcStatusUpdate { .. }
|
| EventType::WebxdcStatusUpdate { .. }
|
||||||
| EventType::WebxdcInstanceDeleted { .. }
|
| EventType::WebxdcInstanceDeleted { .. }
|
||||||
|
| EventType::WebxdcRealtimeData { .. }
|
||||||
| EventType::AccountsBackgroundFetchDone
|
| EventType::AccountsBackgroundFetchDone
|
||||||
| EventType::ChatEphemeralTimerModified { .. }
|
| EventType::ChatEphemeralTimerModified { .. }
|
||||||
| EventType::ChatDeleted { .. }
|
|
||||||
| EventType::IncomingMsgBunch { .. }
|
| EventType::IncomingMsgBunch { .. }
|
||||||
| EventType::ChatlistItemChanged { .. }
|
| EventType::ChatlistItemChanged { .. }
|
||||||
| EventType::ChatlistChanged
|
| EventType::ChatlistChanged => ptr::null_mut(),
|
||||||
| EventType::AccountsChanged
|
|
||||||
| EventType::AccountsItemChanged
|
|
||||||
| EventType::WebxdcRealtimeAdvertisementReceived { .. }
|
|
||||||
| EventType::EventChannelOverflow { .. } => ptr::null_mut(),
|
|
||||||
EventType::ConfigureProgress { comment, .. } => {
|
EventType::ConfigureProgress { comment, .. } => {
|
||||||
if let Some(comment) = comment {
|
if let Some(comment) = comment {
|
||||||
comment.to_c_string().unwrap_or_default().into_raw()
|
comment.to_c_string().unwrap_or_default().into_raw()
|
||||||
@@ -793,22 +746,6 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
|||||||
let data2 = key.to_string().to_c_string().unwrap_or_default();
|
let data2 = key.to_string().to_c_string().unwrap_or_default();
|
||||||
data2.into_raw()
|
data2.into_raw()
|
||||||
}
|
}
|
||||||
EventType::WebxdcRealtimeData { data, .. } => {
|
|
||||||
let ptr = libc::malloc(data.len());
|
|
||||||
libc::memcpy(ptr, data.as_ptr() as *mut libc::c_void, data.len());
|
|
||||||
ptr as *mut libc::c_char
|
|
||||||
}
|
|
||||||
EventType::IncomingReaction { reaction, .. } => reaction
|
|
||||||
.as_str()
|
|
||||||
.to_c_string()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into_raw(),
|
|
||||||
EventType::IncomingWebxdcNotify { text, .. } => {
|
|
||||||
text.to_c_string().unwrap_or_default().into_raw()
|
|
||||||
}
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
#[cfg(test)]
|
|
||||||
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,6 +827,8 @@ pub unsafe extern "C" fn dc_maybe_network(context: *mut dc_context_t) {
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_preconfigure_keypair(
|
pub unsafe extern "C" fn dc_preconfigure_keypair(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
|
addr: *const libc::c_char,
|
||||||
|
_public_data: *const libc::c_char,
|
||||||
secret_data: *const libc::c_char,
|
secret_data: *const libc::c_char,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
@@ -897,8 +836,9 @@ pub unsafe extern "C" fn dc_preconfigure_keypair(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
|
let addr = to_string_lossy(addr);
|
||||||
let secret_data = to_string_lossy(secret_data);
|
let secret_data = to_string_lossy(secret_data);
|
||||||
block_on(preconfigure_keypair(ctx, &secret_data))
|
block_on(preconfigure_keypair(ctx, &addr, &secret_data))
|
||||||
.context("Failed to save keypair")
|
.context("Failed to save keypair")
|
||||||
.log_err(ctx)
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int
|
.is_ok() as libc::c_int
|
||||||
@@ -982,6 +922,27 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_prepare_msg(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
chat_id: u32,
|
||||||
|
msg: *mut dc_msg_t,
|
||||||
|
) -> u32 {
|
||||||
|
if context.is_null() || chat_id == 0 || msg.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_prepare_msg()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let ctx = &mut *context;
|
||||||
|
let ffi_msg: &mut MessageWrapper = &mut *msg;
|
||||||
|
|
||||||
|
block_on(async move {
|
||||||
|
chat::prepare_msg(ctx, ChatId::new(chat_id), &mut ffi_msg.message)
|
||||||
|
.await
|
||||||
|
.unwrap_or_log_default(ctx, "Failed to prepare message")
|
||||||
|
})
|
||||||
|
.to_u32()
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_send_msg(
|
pub unsafe extern "C" fn dc_send_msg(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1045,42 +1006,6 @@ pub unsafe extern "C" fn dc_send_text_msg(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_send_edit_request(
|
|
||||||
context: *mut dc_context_t,
|
|
||||||
msg_id: u32,
|
|
||||||
new_text: *const libc::c_char,
|
|
||||||
) {
|
|
||||||
if context.is_null() || new_text.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_send_edit_request()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let ctx = &*context;
|
|
||||||
let new_text = to_string_lossy(new_text);
|
|
||||||
|
|
||||||
block_on(chat::send_edit_request(ctx, MsgId::new(msg_id), new_text))
|
|
||||||
.unwrap_or_log_default(ctx, "Failed to send text edit")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_send_delete_request(
|
|
||||||
context: *mut dc_context_t,
|
|
||||||
msg_ids: *const u32,
|
|
||||||
msg_cnt: libc::c_int,
|
|
||||||
) {
|
|
||||||
if context.is_null() || msg_ids.is_null() || msg_cnt <= 0 {
|
|
||||||
eprintln!("ignoring careless call to dc_send_delete_request()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let ctx = &*context;
|
|
||||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
|
||||||
|
|
||||||
block_on(message::delete_msgs_ex(ctx, &msg_ids, true))
|
|
||||||
.context("failed dc_send_delete_request() call")
|
|
||||||
.log_err(ctx)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_send_videochat_invitation(
|
pub unsafe extern "C" fn dc_send_videochat_invitation(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1105,7 +1030,7 @@ pub unsafe extern "C" fn dc_send_webxdc_status_update(
|
|||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
msg_id: u32,
|
msg_id: u32,
|
||||||
json: *const libc::c_char,
|
json: *const libc::c_char,
|
||||||
_descr: *const libc::c_char,
|
descr: *const libc::c_char,
|
||||||
) -> libc::c_int {
|
) -> libc::c_int {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_send_webxdc_status_update()");
|
eprintln!("ignoring careless call to dc_send_webxdc_status_update()");
|
||||||
@@ -1113,7 +1038,11 @@ pub unsafe extern "C" fn dc_send_webxdc_status_update(
|
|||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
|
|
||||||
block_on(ctx.send_webxdc_status_update(MsgId::new(msg_id), &to_string_lossy(json)))
|
block_on(ctx.send_webxdc_status_update(
|
||||||
|
MsgId::new(msg_id),
|
||||||
|
&to_string_lossy(json),
|
||||||
|
&to_string_lossy(descr),
|
||||||
|
))
|
||||||
.context("Failed to send webxdc update")
|
.context("Failed to send webxdc update")
|
||||||
.log_err(ctx)
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int
|
.is_ok() as libc::c_int
|
||||||
@@ -1509,6 +1438,48 @@ pub unsafe extern "C" fn dc_get_chat_media(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
#[allow(deprecated)]
|
||||||
|
pub unsafe extern "C" fn dc_get_next_media(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
msg_id: u32,
|
||||||
|
dir: libc::c_int,
|
||||||
|
msg_type: libc::c_int,
|
||||||
|
or_msg_type2: libc::c_int,
|
||||||
|
or_msg_type3: libc::c_int,
|
||||||
|
) -> u32 {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_get_next_media()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let direction = if dir < 0 {
|
||||||
|
chat::Direction::Backward
|
||||||
|
} else {
|
||||||
|
chat::Direction::Forward
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctx = &*context;
|
||||||
|
let msg_type = from_prim(msg_type).expect(&format!("invalid msg_type = {msg_type}"));
|
||||||
|
let or_msg_type2 =
|
||||||
|
from_prim(or_msg_type2).expect(&format!("incorrect or_msg_type2 = {or_msg_type2}"));
|
||||||
|
let or_msg_type3 =
|
||||||
|
from_prim(or_msg_type3).expect(&format!("incorrect or_msg_type3 = {or_msg_type3}"));
|
||||||
|
|
||||||
|
block_on(async move {
|
||||||
|
chat::get_next_media(
|
||||||
|
ctx,
|
||||||
|
MsgId::new(msg_id),
|
||||||
|
direction,
|
||||||
|
msg_type,
|
||||||
|
or_msg_type2,
|
||||||
|
or_msg_type3,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|msg_id| msg_id.map(|id| id.to_u32()).unwrap_or_default())
|
||||||
|
.unwrap_or(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_set_chat_visibility(
|
pub unsafe extern "C" fn dc_set_chat_visibility(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1658,7 +1629,6 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
|
|||||||
return ptr::null_mut();
|
return ptr::null_mut();
|
||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
let context: Context = ctx.clone();
|
|
||||||
|
|
||||||
block_on(async move {
|
block_on(async move {
|
||||||
match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
|
match chat::Chat::load_from_db(ctx, ChatId::new(chat_id)).await {
|
||||||
@@ -1954,6 +1924,28 @@ pub unsafe extern "C" fn dc_get_msg_html(
|
|||||||
.strdup()
|
.strdup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_get_mime_headers(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
msg_id: u32,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_get_mime_headers()");
|
||||||
|
return ptr::null_mut(); // NULL explicitly defined as "no mime headers"
|
||||||
|
}
|
||||||
|
let ctx = &*context;
|
||||||
|
|
||||||
|
block_on(async move {
|
||||||
|
let mime = message::get_mime_headers(ctx, MsgId::new(msg_id))
|
||||||
|
.await
|
||||||
|
.unwrap_or_log_default(ctx, "failed to get mime headers");
|
||||||
|
if mime.is_empty() {
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
mime.strdup()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_delete_msgs(
|
pub unsafe extern "C" fn dc_delete_msgs(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1998,26 +1990,6 @@ pub unsafe extern "C" fn dc_forward_msgs(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_save_msgs(
|
|
||||||
context: *mut dc_context_t,
|
|
||||||
msg_ids: *const u32,
|
|
||||||
msg_cnt: libc::c_int,
|
|
||||||
) {
|
|
||||||
if context.is_null() || msg_ids.is_null() || msg_cnt <= 0 {
|
|
||||||
eprintln!("ignoring careless call to dc_save_msgs()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
|
||||||
let ctx = &*context;
|
|
||||||
|
|
||||||
block_on(async move {
|
|
||||||
chat::save_msgs(ctx, &msg_ids[..])
|
|
||||||
.await
|
|
||||||
.unwrap_or_log_default(ctx, "Failed to save message")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_resend_msgs(
|
pub unsafe extern "C" fn dc_resend_msgs(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -2635,18 +2607,6 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_create_qr_svg(payload: *const libc::c_char) -> *mut libc::c_char {
|
|
||||||
if payload.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_create_qr_svg()");
|
|
||||||
return "".strdup();
|
|
||||||
}
|
|
||||||
|
|
||||||
create_qr_svg(&to_string_lossy(payload))
|
|
||||||
.unwrap_or_else(|_| "".to_string())
|
|
||||||
.strdup()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_get_last_error(context: *mut dc_context_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_get_last_error(context: *mut dc_context_t) -> *mut libc::c_char {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
@@ -2983,7 +2943,7 @@ pub unsafe extern "C" fn dc_chatlist_get_context(
|
|||||||
/// context, but the Rust API does not, so the FFI layer needs to glue
|
/// context, but the Rust API does not, so the FFI layer needs to glue
|
||||||
/// these together.
|
/// these together.
|
||||||
pub struct ChatWrapper {
|
pub struct ChatWrapper {
|
||||||
context: Context,
|
context: *const dc_context_t,
|
||||||
chat: chat::Chat,
|
chat: chat::Chat,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3050,13 +3010,14 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
|
|||||||
return ptr::null_mut(); // NULL explicitly defined as "no image"
|
return ptr::null_mut(); // NULL explicitly defined as "no image"
|
||||||
}
|
}
|
||||||
let ffi_chat = &*chat;
|
let ffi_chat = &*chat;
|
||||||
|
let ctx = &*ffi_chat.context;
|
||||||
|
|
||||||
block_on(async move {
|
block_on(async move {
|
||||||
match ffi_chat.chat.get_profile_image(&ffi_chat.context).await {
|
match ffi_chat.chat.get_profile_image(ctx).await {
|
||||||
Ok(Some(p)) => p.to_string_lossy().strdup(),
|
Ok(Some(p)) => p.to_string_lossy().strdup(),
|
||||||
Ok(None) => ptr::null_mut(),
|
Ok(None) => ptr::null_mut(),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(ffi_chat.context, "failed to get profile image: {err:#}");
|
error!(ctx, "failed to get profile image: {err:#}");
|
||||||
ptr::null_mut()
|
ptr::null_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3070,9 +3031,9 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let ffi_chat = &*chat;
|
let ffi_chat = &*chat;
|
||||||
|
let ctx = &*ffi_chat.context;
|
||||||
|
|
||||||
block_on(ffi_chat.chat.get_color(&ffi_chat.context))
|
block_on(ffi_chat.chat.get_color(ctx)).unwrap_or_log_default(ctx, "Failed get_color")
|
||||||
.unwrap_or_log_default(&ffi_chat.context, "Failed get_color")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -3136,9 +3097,10 @@ pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let ffi_chat = &*chat;
|
let ffi_chat = &*chat;
|
||||||
block_on(ffi_chat.chat.can_send(&ffi_chat.context))
|
let ctx = &*ffi_chat.context;
|
||||||
|
block_on(ffi_chat.chat.can_send(ctx))
|
||||||
.context("can_send failed")
|
.context("can_send failed")
|
||||||
.log_err(&ffi_chat.context)
|
.log_err(ctx)
|
||||||
.unwrap_or_default() as libc::c_int
|
.unwrap_or_default() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3700,16 +3662,6 @@ pub unsafe extern "C" fn dc_msg_is_forwarded(msg: *mut dc_msg_t) -> libc::c_int
|
|||||||
ffi_msg.message.is_forwarded().into()
|
ffi_msg.message.is_forwarded().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_msg_is_edited(msg: *mut dc_msg_t) -> libc::c_int {
|
|
||||||
if msg.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_msg_is_edited()");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let ffi_msg = &*msg;
|
|
||||||
ffi_msg.message.is_edited().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_is_info(msg: *mut dc_msg_t) -> libc::c_int {
|
pub unsafe extern "C" fn dc_msg_is_info(msg: *mut dc_msg_t) -> libc::c_int {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
@@ -3731,14 +3683,13 @@ pub unsafe extern "C" fn dc_msg_get_info_type(msg: *mut dc_msg_t) -> libc::c_int
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_get_webxdc_href(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_msg_is_increation(msg: *mut dc_msg_t) -> libc::c_int {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_msg_get_webxdc_href()");
|
eprintln!("ignoring careless call to dc_msg_is_increation()");
|
||||||
return "".strdup();
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ffi_msg = &*msg;
|
let ffi_msg = &*msg;
|
||||||
ffi_msg.message.get_webxdc_href().strdup()
|
ffi_msg.message.is_increation().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -3846,30 +3797,20 @@ pub unsafe extern "C" fn dc_msg_set_override_sender_name(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_set_file_and_deduplicate(
|
pub unsafe extern "C" fn dc_msg_set_file(
|
||||||
msg: *mut dc_msg_t,
|
msg: *mut dc_msg_t,
|
||||||
file: *const libc::c_char,
|
file: *const libc::c_char,
|
||||||
name: *const libc::c_char,
|
|
||||||
filemime: *const libc::c_char,
|
filemime: *const libc::c_char,
|
||||||
) {
|
) {
|
||||||
if msg.is_null() || file.is_null() {
|
if msg.is_null() || file.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_msg_set_file_and_deduplicate()");
|
eprintln!("ignoring careless call to dc_msg_set_file()");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ffi_msg = &mut *msg;
|
let ffi_msg = &mut *msg;
|
||||||
let ctx = &*ffi_msg.context;
|
ffi_msg.message.set_file(
|
||||||
|
to_string_lossy(file),
|
||||||
ffi_msg
|
|
||||||
.message
|
|
||||||
.set_file_and_deduplicate(
|
|
||||||
ctx,
|
|
||||||
as_path(file),
|
|
||||||
to_opt_string_lossy(name).as_deref(),
|
|
||||||
to_opt_string_lossy(filemime).as_deref(),
|
to_opt_string_lossy(filemime).as_deref(),
|
||||||
)
|
)
|
||||||
.context("Failed to set file")
|
|
||||||
.log_err(&*ffi_msg.context)
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -4037,48 +3978,6 @@ pub unsafe extern "C" fn dc_msg_get_parent(msg: *const dc_msg_t) -> *mut dc_msg_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_msg_get_original_msg_id(msg: *const dc_msg_t) -> u32 {
|
|
||||||
if msg.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_msg_get_original_msg_id()");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let ffi_msg: &MessageWrapper = &*msg;
|
|
||||||
let context = &*ffi_msg.context;
|
|
||||||
block_on(async move {
|
|
||||||
ffi_msg
|
|
||||||
.message
|
|
||||||
.get_original_msg_id(context)
|
|
||||||
.await
|
|
||||||
.context("failed to get original message")
|
|
||||||
.log_err(context)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.map(|id| id.to_u32())
|
|
||||||
.unwrap_or(0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn dc_msg_get_saved_msg_id(msg: *const dc_msg_t) -> u32 {
|
|
||||||
if msg.is_null() {
|
|
||||||
eprintln!("ignoring careless call to dc_msg_get_saved_msg_id()");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let ffi_msg: &MessageWrapper = &*msg;
|
|
||||||
let context = &*ffi_msg.context;
|
|
||||||
block_on(async move {
|
|
||||||
ffi_msg
|
|
||||||
.message
|
|
||||||
.get_saved_msg_id(context)
|
|
||||||
.await
|
|
||||||
.context("failed to get original message")
|
|
||||||
.log_err(context)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.map(|id| id.to_u32())
|
|
||||||
.unwrap_or(0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_force_plaintext(msg: *mut dc_msg_t) {
|
pub unsafe extern "C" fn dc_msg_force_plaintext(msg: *mut dc_msg_t) {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
@@ -4457,7 +4356,7 @@ pub unsafe extern "C" fn dc_backup_provider_wait(provider: *mut dc_backup_provid
|
|||||||
let ctx = &*ffi_provider.context;
|
let ctx = &*ffi_provider.context;
|
||||||
let provider = &mut ffi_provider.provider;
|
let provider = &mut ffi_provider.provider;
|
||||||
block_on(provider)
|
block_on(provider)
|
||||||
.context("Failed to await backup provider")
|
.context("Failed to await BackupProvider")
|
||||||
.log_err(ctx)
|
.log_err(ctx)
|
||||||
.set_last_error(ctx)
|
.set_last_error(ctx)
|
||||||
.ok();
|
.ok();
|
||||||
@@ -4630,16 +4529,19 @@ pub unsafe extern "C" fn dc_provider_new_from_email_with_dns(
|
|||||||
let addr = to_string_lossy(addr);
|
let addr = to_string_lossy(addr);
|
||||||
|
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
let proxy_enabled = block_on(ctx.get_config_bool(config::Config::ProxyEnabled))
|
let socks5_enabled = block_on(async move {
|
||||||
|
ctx.get_config_bool(config::Config::Socks5Enabled)
|
||||||
|
.await
|
||||||
.context("Can't get config")
|
.context("Can't get config")
|
||||||
.log_err(ctx);
|
.log_err(ctx)
|
||||||
|
});
|
||||||
|
|
||||||
match proxy_enabled {
|
match socks5_enabled {
|
||||||
Ok(proxy_enabled) => {
|
Ok(socks5_enabled) => {
|
||||||
match block_on(provider::get_provider_info_by_addr(
|
match block_on(provider::get_provider_info_by_addr(
|
||||||
ctx,
|
ctx,
|
||||||
addr.as_str(),
|
addr.as_str(),
|
||||||
proxy_enabled,
|
socks5_enabled,
|
||||||
))
|
))
|
||||||
.log_err(ctx)
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@@ -4970,7 +4872,7 @@ pub unsafe extern "C" fn dc_accounts_maybe_network_lost(accounts: *mut dc_accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
let accounts = &*accounts;
|
let accounts = &*accounts;
|
||||||
block_on(async move { accounts.read().await.maybe_network_lost().await });
|
block_on(async move { accounts.write().await.maybe_network_lost().await });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -4984,12 +4886,12 @@ pub unsafe extern "C" fn dc_accounts_background_fetch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let accounts = &*accounts;
|
let accounts = &*accounts;
|
||||||
let background_fetch_future = {
|
block_on(async move {
|
||||||
let lock = block_on(accounts.read());
|
let accounts = accounts.read().await;
|
||||||
lock.background_fetch(Duration::from_secs(timeout_in_seconds))
|
accounts
|
||||||
};
|
.background_fetch(Duration::from_secs(timeout_in_seconds))
|
||||||
// At this point account manager is not locked anymore.
|
.await;
|
||||||
block_on(background_fetch_future);
|
});
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5007,7 +4909,7 @@ pub unsafe extern "C" fn dc_accounts_set_push_device_token(
|
|||||||
let token = to_string_lossy(token);
|
let token = to_string_lossy(token);
|
||||||
|
|
||||||
block_on(async move {
|
block_on(async move {
|
||||||
let accounts = accounts.read().await;
|
let mut accounts = accounts.write().await;
|
||||||
if let Err(err) = accounts.set_push_device_token(&token).await {
|
if let Err(err) = accounts.set_push_device_token(&token).await {
|
||||||
accounts.emit_event(EventType::Error(format!(
|
accounts.emit_event(EventType::Error(format!(
|
||||||
"Failed to set notify token: {err:#}."
|
"Failed to set notify token: {err:#}."
|
||||||
@@ -5031,6 +4933,13 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
|
|||||||
Box::into_raw(Box::new(emitter))
|
Box::into_raw(Box::new(emitter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "jsonrpc")]
|
||||||
|
mod jsonrpc {
|
||||||
|
use deltachat_jsonrpc::api::CommandApi;
|
||||||
|
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcSession};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
pub struct dc_jsonrpc_instance_t {
|
pub struct dc_jsonrpc_instance_t {
|
||||||
receiver: OutReceiver,
|
receiver: OutReceiver,
|
||||||
handle: RpcSession<CommandApi>,
|
handle: RpcSession<CommandApi>,
|
||||||
@@ -5125,3 +5034,4 @@ pub unsafe extern "C" fn dc_jsonrpc_blocking_call(
|
|||||||
None => ptr::null_mut(),
|
None => ptr::null_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,35 +34,33 @@ pub enum Meaning {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Lot {
|
impl Lot {
|
||||||
pub fn get_text1(&self) -> Option<Cow<str>> {
|
pub fn get_text1(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Summary(summary) => match &summary.prefix {
|
Self::Summary(summary) => match &summary.prefix {
|
||||||
None => None,
|
None => None,
|
||||||
Some(SummaryPrefix::Draft(text)) => Some(Cow::Borrowed(text)),
|
Some(SummaryPrefix::Draft(text)) => Some(text),
|
||||||
Some(SummaryPrefix::Username(username)) => Some(Cow::Borrowed(username)),
|
Some(SummaryPrefix::Username(username)) => Some(username),
|
||||||
Some(SummaryPrefix::Me(text)) => Some(Cow::Borrowed(text)),
|
Some(SummaryPrefix::Me(text)) => Some(text),
|
||||||
},
|
},
|
||||||
Self::Qr(qr) => match qr {
|
Self::Qr(qr) => match qr {
|
||||||
Qr::AskVerifyContact { .. } => None,
|
Qr::AskVerifyContact { .. } => None,
|
||||||
Qr::AskVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
Qr::AskVerifyGroup { grpname, .. } => Some(grpname),
|
||||||
Qr::FprOk { .. } => None,
|
Qr::FprOk { .. } => None,
|
||||||
Qr::FprMismatch { .. } => None,
|
Qr::FprMismatch { .. } => None,
|
||||||
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
|
Qr::FprWithoutAddr { fingerprint, .. } => Some(fingerprint),
|
||||||
Qr::Account { domain } => Some(Cow::Borrowed(domain)),
|
Qr::Account { domain } => Some(domain),
|
||||||
Qr::Backup2 { .. } => None,
|
Qr::Backup { .. } => None,
|
||||||
Qr::BackupTooNew { .. } => None,
|
Qr::WebrtcInstance { domain, .. } => Some(domain),
|
||||||
Qr::WebrtcInstance { domain, .. } => Some(Cow::Borrowed(domain)),
|
Qr::Addr { draft, .. } => draft.as_deref(),
|
||||||
Qr::Proxy { host, port, .. } => Some(Cow::Owned(format!("{host}:{port}"))),
|
Qr::Url { url } => Some(url),
|
||||||
Qr::Addr { draft, .. } => draft.as_deref().map(Cow::Borrowed),
|
Qr::Text { text } => Some(text),
|
||||||
Qr::Url { url } => Some(Cow::Borrowed(url)),
|
|
||||||
Qr::Text { text } => Some(Cow::Borrowed(text)),
|
|
||||||
Qr::WithdrawVerifyContact { .. } => None,
|
Qr::WithdrawVerifyContact { .. } => None,
|
||||||
Qr::WithdrawVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
Qr::WithdrawVerifyGroup { grpname, .. } => Some(grpname),
|
||||||
Qr::ReviveVerifyContact { .. } => None,
|
Qr::ReviveVerifyContact { .. } => None,
|
||||||
Qr::ReviveVerifyGroup { grpname, .. } => Some(Cow::Borrowed(grpname)),
|
Qr::ReviveVerifyGroup { grpname, .. } => Some(grpname),
|
||||||
Qr::Login { address, .. } => Some(Cow::Borrowed(address)),
|
Qr::Login { address, .. } => Some(address),
|
||||||
},
|
},
|
||||||
Self::Error(err) => Some(Cow::Borrowed(err)),
|
Self::Error(err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +101,8 @@ impl Lot {
|
|||||||
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
|
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
|
||||||
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
||||||
Qr::Account { .. } => LotState::QrAccount,
|
Qr::Account { .. } => LotState::QrAccount,
|
||||||
Qr::Backup2 { .. } => LotState::QrBackup2,
|
Qr::Backup { .. } => LotState::QrBackup,
|
||||||
Qr::BackupTooNew { .. } => LotState::QrBackupTooNew,
|
|
||||||
Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance,
|
Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance,
|
||||||
Qr::Proxy { .. } => LotState::QrProxy,
|
|
||||||
Qr::Addr { .. } => LotState::QrAddr,
|
Qr::Addr { .. } => LotState::QrAddr,
|
||||||
Qr::Url { .. } => LotState::QrUrl,
|
Qr::Url { .. } => LotState::QrUrl,
|
||||||
Qr::Text { .. } => LotState::QrText,
|
Qr::Text { .. } => LotState::QrText,
|
||||||
@@ -130,10 +126,8 @@ impl Lot {
|
|||||||
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
|
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
|
||||||
Qr::FprWithoutAddr { .. } => Default::default(),
|
Qr::FprWithoutAddr { .. } => Default::default(),
|
||||||
Qr::Account { .. } => Default::default(),
|
Qr::Account { .. } => Default::default(),
|
||||||
Qr::Backup2 { .. } => Default::default(),
|
Qr::Backup { .. } => Default::default(),
|
||||||
Qr::BackupTooNew { .. } => Default::default(),
|
|
||||||
Qr::WebrtcInstance { .. } => Default::default(),
|
Qr::WebrtcInstance { .. } => Default::default(),
|
||||||
Qr::Proxy { .. } => Default::default(),
|
|
||||||
Qr::Addr { contact_id, .. } => contact_id.to_u32(),
|
Qr::Addr { contact_id, .. } => contact_id.to_u32(),
|
||||||
Qr::Url { .. } => Default::default(),
|
Qr::Url { .. } => Default::default(),
|
||||||
Qr::Text { .. } => Default::default(),
|
Qr::Text { .. } => Default::default(),
|
||||||
@@ -181,16 +175,11 @@ pub enum LotState {
|
|||||||
/// text1=domain
|
/// text1=domain
|
||||||
QrAccount = 250,
|
QrAccount = 250,
|
||||||
|
|
||||||
QrBackup2 = 252,
|
QrBackup = 251,
|
||||||
|
|
||||||
QrBackupTooNew = 255,
|
|
||||||
|
|
||||||
/// text1=domain, text2=instance pattern
|
/// text1=domain, text2=instance pattern
|
||||||
QrWebrtcInstance = 260,
|
QrWebrtcInstance = 260,
|
||||||
|
|
||||||
/// text1=address, text2=protocol
|
|
||||||
QrProxy = 271,
|
|
||||||
|
|
||||||
/// id=contact
|
/// id=contact
|
||||||
QrAddr = 320,
|
QrAddr = 320,
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,45 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-jsonrpc"
|
name = "deltachat-jsonrpc"
|
||||||
version = "1.157.3"
|
version = "1.138.5"
|
||||||
description = "DeltaChat JSON-RPC API"
|
description = "DeltaChat JSON-RPC API"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "deltachat-jsonrpc-server"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/chatmail/core"
|
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "deltachat-jsonrpc-server"
|
||||||
|
path = "src/webserver.rs"
|
||||||
|
required-features = ["webserver"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = "1"
|
||||||
deltachat = { workspace = true }
|
deltachat = { path = ".." }
|
||||||
deltachat-contact-tools = { workspace = true }
|
deltachat-contact-tools = { path = "../deltachat-contact-tools" }
|
||||||
num-traits = { workspace = true }
|
num-traits = "0.2"
|
||||||
schemars = "0.8.22"
|
schemars = "0.8.19"
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tempfile = { workspace = true }
|
tempfile = "3.10.1"
|
||||||
log = { workspace = true }
|
log = "0.4"
|
||||||
async-channel = { workspace = true }
|
async-channel = { version = "2.2.1" }
|
||||||
futures = { workspace = true }
|
futures = { version = "0.3.30" }
|
||||||
serde_json = { workspace = true }
|
serde_json = "1"
|
||||||
yerpc = { workspace = true, features = ["anyhow_expose", "openrpc"] }
|
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
|
||||||
typescript-type-def = { version = "0.5.13", features = ["json_value"] }
|
typescript-type-def = { version = "0.5.8", features = ["json_value"] }
|
||||||
tokio = { workspace = true }
|
tokio = { version = "1.37.0" }
|
||||||
sanitize-filename = { workspace = true }
|
sanitize-filename = "0.5"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
base64 = { workspace = true }
|
base64 = "0.22"
|
||||||
|
|
||||||
|
# optional dependencies
|
||||||
|
axum = { version = "0.7", optional = true, features = ["ws"] }
|
||||||
|
env_logger = { version = "0.11.3", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["full", "rt-multi-thread"] }
|
tokio = { version = "1.37.0", features = ["full", "rt-multi-thread"] }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored"]
|
default = ["vendored"]
|
||||||
|
webserver = ["dep:env_logger", "dep:axum", "tokio/full", "yerpc/support-axum"]
|
||||||
vendored = ["deltachat/vendored"]
|
vendored = ["deltachat/vendored"]
|
||||||
|
|||||||
@@ -4,16 +4,46 @@ This crate provides a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) inte
|
|||||||
|
|
||||||
The JSON-RPC API is exposed in two fashions:
|
The JSON-RPC API is exposed in two fashions:
|
||||||
|
|
||||||
* A executable `deltachat-rpc-server` that exposes the JSON-RPC API through stdio.
|
* A executable that exposes the JSON-RPC API through a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) server running on localhost.
|
||||||
* The JSON-RPC API can also be called through the [C FFI](../deltachat-ffi). It exposes the functions `dc_jsonrpc_init`, `dc_jsonrpc_request`, `dc_jsonrpc_next_response` and `dc_jsonrpc_unref`. See the docs in the [header file](../deltachat-ffi/deltachat.h) for details.
|
* The JSON-RPC API can also be called through the [C FFI](../deltachat-ffi). The C FFI needs to be built with the `jsonrpc` feature. It will then expose the functions `dc_jsonrpc_init`, `dc_jsonrpc_request`, `dc_jsonrpc_next_response` and `dc_jsonrpc_unref`. See the docs in the [header file](../deltachat-ffi/deltachat.h) for details.
|
||||||
|
|
||||||
We also include a JavaScript and TypeScript client for the JSON-RPC API. The source for this is in the [`typescript`](typescript) folder.
|
We also include a JavaScript and TypeScript client for the JSON-RPC API. The source for this is in the [`typescript`](typescript) folder. The client can easily be used with the WebSocket server to build DeltaChat apps for web browsers or Node.js. See the [examples](typescript/example) for details.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
#### Running the WebSocket server
|
||||||
|
|
||||||
|
From within this folder, you can start the WebSocket server with the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --features webserver
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to use the server in a production setup, first build it in release mode:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --features webserver --release
|
||||||
|
```
|
||||||
|
You will then find the `deltachat-jsonrpc-server` executable in your `target/release` folder.
|
||||||
|
|
||||||
|
The executable currently does not support any command-line arguments. By default, once started it will accept WebSocket connections on `ws://localhost:20808/ws`. It will store the persistent configuration and databases in a `./accounts` folder relative to the directory from where it is started.
|
||||||
|
|
||||||
|
The server can be configured with environment variables:
|
||||||
|
|
||||||
|
|variable|default|description|
|
||||||
|
|-|-|-|
|
||||||
|
|`DC_PORT`|`20808`|port to listen on|
|
||||||
|
|`DC_ACCOUNTS_PATH`|`./accounts`|path to storage directory|
|
||||||
|
|
||||||
|
If you are targeting other architectures (like KaiOS or Android), the webserver binary can be cross-compiled easily with [rust-cross](https://github.com/cross-rs/cross):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cross build --features=webserver --target armv7-linux-androideabi --release
|
||||||
|
```
|
||||||
|
|
||||||
#### Using the TypeScript/JavaScript client
|
#### Using the TypeScript/JavaScript client
|
||||||
|
|
||||||
The package includes a JavaScript/TypeScript client which is partially auto-generated through the JSON-RPC library used by this crate ([yerpc](https://github.com/chatmail/yerpc)). Find the source in the [`typescript`](typescript) folder.
|
The package includes a JavaScript/TypeScript client which is partially auto-generated through the JSON-RPC library used by this crate ([yerpc](https://github.com/Frando/yerpc/)). Find the source in the [`typescript`](typescript) folder.
|
||||||
|
|
||||||
To use it locally, first install the dependencies and compile the TypeScript code to JavaScript:
|
To use it locally, first install the dependencies and compile the TypeScript code to JavaScript:
|
||||||
```sh
|
```sh
|
||||||
@@ -22,7 +52,15 @@ npm install
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
The JavaScript client is [published on NPM](https://www.npmjs.com/package/@deltachat/jsonrpc-client).
|
The JavaScript client is not yet published on NPM (but will likely be soon). Currently, it is recommended to vendor the bundled build. After running `npm run build` as documented above, there will be a file `dist/deltachat.bundle.js`. This is an ESM module containing all dependencies. Copy this file to your project and import the DeltaChat class.
|
||||||
|
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { DeltaChat } from './deltachat.bundle.js'
|
||||||
|
const dc = new DeltaChat('ws://localhost:20808/ws')
|
||||||
|
const accounts = await dc.rpc.getAllAccounts()
|
||||||
|
console.log('accounts', accounts)
|
||||||
|
```
|
||||||
|
|
||||||
A script is included to build autogenerated documentation, which includes all RPC methods:
|
A script is included to build autogenerated documentation, which includes all RPC methods:
|
||||||
```sh
|
```sh
|
||||||
@@ -35,6 +73,18 @@ Then open the [`typescript/docs`](typescript/docs) folder in a web browser.
|
|||||||
|
|
||||||
#### Running the example app
|
#### Running the example app
|
||||||
|
|
||||||
|
We include a small demo web application that talks to the WebSocket server. It can be used for testing. Feel invited to expand this.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd typescript
|
||||||
|
npm run build
|
||||||
|
npm run example:build
|
||||||
|
npm run example:start
|
||||||
|
```
|
||||||
|
Then, open [`http://localhost:8080/example.html`](http://localhost:8080/example.html) in a web browser.
|
||||||
|
|
||||||
|
Run `npm run example:dev` to live-rebuild the example app when files changes.
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
The crate includes both a basic Rust smoke test and more featureful integration tests that use the TypeScript client.
|
The crate includes both a basic Rust smoke test and more featureful integration tests that use the TypeScript client.
|
||||||
@@ -54,12 +104,14 @@ cd typescript
|
|||||||
npm run test
|
npm run test
|
||||||
```
|
```
|
||||||
|
|
||||||
This will build the `deltachat-jsonrpc-server` binary and then run a test suite.
|
This will build the `deltachat-jsonrpc-server` binary and then run a test suite against the WebSocket server.
|
||||||
|
|
||||||
The test suite includes some tests that need online connectivity and a way to create test email accounts. To run these tests, set the `CHATMAIL_DOMAIN` environment variable to your testing email server domain.
|
The test suite includes some tests that need online connectivity and a way to create test email accounts. To run these tests, talk to DeltaChat developers to get a token for the `testrun.org` service, or use a local instance of [`mailadm`](https://github.com/deltachat/docker-mailadm).
|
||||||
|
|
||||||
|
Then, set the `CHATMAIL_DOMAIN` environment variable to your testing email server domain.
|
||||||
|
|
||||||
```
|
```
|
||||||
CHATMAIL_DOMAIN=ci-chatmail.testrun.org npm run test
|
CHATMAIL_DOMAIN=chat.example.org npm run test
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Test Coverage
|
#### Test Coverage
|
||||||
|
|||||||
28
deltachat-jsonrpc/TODO.md
Normal file
28
deltachat-jsonrpc/TODO.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
- [ ] different test type to simulate two devices: to test autocrypt_initiate_key_transfer & autocrypt_continue_key_transfer
|
||||||
|
|
||||||
|
## MVP - Websocket server&client
|
||||||
|
|
||||||
|
For kaiOS and other experiments, like a deltachat "web" over network from an android phone.
|
||||||
|
|
||||||
|
- [ ] coverage for a majority of the API
|
||||||
|
- [ ] Blobs served
|
||||||
|
- [ ] Blob upload (for attachments, setting profile-picture, importing backup and so on)
|
||||||
|
- [ ] other way blobs can be addressed when using websocket vs. jsonrpc over dc-node
|
||||||
|
- [ ] Web push API? At least some kind of notification hook closure this lib can accept.
|
||||||
|
|
||||||
|
### Other Ideas for the Websocket server
|
||||||
|
|
||||||
|
- [ ] make sure there can only be one connection at a time to the ws
|
||||||
|
- why? , it could give problems if its commanded from multiple connections
|
||||||
|
- [ ] encrypted connection?
|
||||||
|
- [ ] authenticated connection?
|
||||||
|
- [ ] Look into unit-testing for the proc macros?
|
||||||
|
- [ ] proc macro taking over doc comments to generated typescript file
|
||||||
|
|
||||||
|
## Desktop Apis
|
||||||
|
|
||||||
|
Incomplete todo for desktop api porting, just some remainders for points that might need more work:
|
||||||
|
|
||||||
|
- [ ] manual start/stop io functions in the api for context and accounts, so "not syncing all accounts" can still be done in desktop -> webserver should then not do start io on all accounts by default
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -7,7 +7,6 @@ use std::{collections::HashMap, str::FromStr};
|
|||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
pub use deltachat::accounts::Accounts;
|
pub use deltachat::accounts::Accounts;
|
||||||
use deltachat::blob::BlobObject;
|
|
||||||
use deltachat::chat::{
|
use deltachat::chat::{
|
||||||
self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
|
self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
|
||||||
marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
|
marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
|
||||||
@@ -22,7 +21,7 @@ use deltachat::ephemeral::Timer;
|
|||||||
use deltachat::location;
|
use deltachat::location;
|
||||||
use deltachat::message::get_msg_read_receipts;
|
use deltachat::message::get_msg_read_receipts;
|
||||||
use deltachat::message::{
|
use deltachat::message::{
|
||||||
self, delete_msgs_ex, markseen_msgs, Message, MessageState, MsgId, Viewtype,
|
self, delete_msgs, markseen_msgs, Message, MessageState, MsgId, Viewtype,
|
||||||
};
|
};
|
||||||
use deltachat::peer_channels::{
|
use deltachat::peer_channels::{
|
||||||
leave_webxdc_realtime, send_webxdc_realtime_advertisement, send_webxdc_realtime_data,
|
leave_webxdc_realtime, send_webxdc_realtime_advertisement, send_webxdc_realtime_data,
|
||||||
@@ -39,8 +38,6 @@ use deltachat::{imex, info};
|
|||||||
use sanitize_filename::is_sanitized;
|
use sanitize_filename::is_sanitized;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::sync::{watch, Mutex, RwLock};
|
use tokio::sync::{watch, Mutex, RwLock};
|
||||||
use types::basic_message::{BasicMessageLoadResult, BasicMessageObject};
|
|
||||||
use types::login_param::EnteredLoginParam;
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use yerpc::rpc;
|
use yerpc::rpc;
|
||||||
|
|
||||||
@@ -215,12 +212,14 @@ impl CommandApi {
|
|||||||
self.accounts.read().await.get_all()
|
self.accounts.read().await.get_all()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select account in account manager, this saves the last used account to accounts.toml
|
/// Select account id for internally selected state.
|
||||||
|
/// TODO: Likely this is deprecated as all methods take an account id now.
|
||||||
async fn select_account(&self, id: u32) -> Result<()> {
|
async fn select_account(&self, id: u32) -> Result<()> {
|
||||||
self.accounts.write().await.select_account(id).await
|
self.accounts.write().await.select_account(id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the selected account from the account manager (on startup it is read from accounts.toml)
|
/// Get the selected account id of the internal state..
|
||||||
|
/// TODO: Likely this is deprecated as all methods take an account id now.
|
||||||
async fn get_selected_account_id(&self) -> Option<u32> {
|
async fn get_selected_account_id(&self) -> Option<u32> {
|
||||||
self.accounts.read().await.get_selected_account_id()
|
self.accounts.read().await.get_selected_account_id()
|
||||||
}
|
}
|
||||||
@@ -255,12 +254,11 @@ impl CommandApi {
|
|||||||
/// Process all events until you get this one and you can safely return to the background
|
/// Process all events until you get this one and you can safely return to the background
|
||||||
/// without forgetting to create notifications caused by timing race conditions.
|
/// without forgetting to create notifications caused by timing race conditions.
|
||||||
async fn accounts_background_fetch(&self, timeout_in_seconds: f64) -> Result<()> {
|
async fn accounts_background_fetch(&self, timeout_in_seconds: f64) -> Result<()> {
|
||||||
let future = {
|
self.accounts
|
||||||
let lock = self.accounts.read().await;
|
.write()
|
||||||
lock.background_fetch(std::time::Duration::from_secs_f64(timeout_in_seconds))
|
.await
|
||||||
};
|
.background_fetch(std::time::Duration::from_secs_f64(timeout_in_seconds))
|
||||||
// At this point account manager is not locked anymore.
|
.await;
|
||||||
future.await;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,12 +321,12 @@ impl CommandApi {
|
|||||||
) -> Result<Option<ProviderInfo>> {
|
) -> Result<Option<ProviderInfo>> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
|
||||||
let proxy_enabled = ctx
|
let socks5_enabled = ctx
|
||||||
.get_config_bool(deltachat::config::Config::ProxyEnabled)
|
.get_config_bool(deltachat::config::Config::Socks5Enabled)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let provider_info =
|
let provider_info =
|
||||||
get_provider_info(&ctx, email.split('@').last().unwrap_or(""), proxy_enabled).await;
|
get_provider_info(&ctx, email.split('@').last().unwrap_or(""), socks5_enabled).await;
|
||||||
Ok(ProviderInfo::from_dc_type(provider_info))
|
Ok(ProviderInfo::from_dc_type(provider_info))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,19 +342,11 @@ impl CommandApi {
|
|||||||
ctx.get_info().await
|
ctx.get_info().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the blob dir.
|
|
||||||
async fn get_blob_dir(&self, account_id: u32) -> Result<Option<String>> {
|
async fn get_blob_dir(&self, account_id: u32) -> Result<Option<String>> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
Ok(ctx.get_blobdir().to_str().map(|s| s.to_owned()))
|
Ok(ctx.get_blobdir().to_str().map(|s| s.to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy file to blob dir.
|
|
||||||
async fn copy_to_blob_dir(&self, account_id: u32, path: String) -> Result<PathBuf> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let file = Path::new(&path);
|
|
||||||
Ok(BlobObject::create_and_deduplicate(&ctx, file, file)?.to_abs_path())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn draft_self_report(&self, account_id: u32) -> Result<u32> {
|
async fn draft_self_report(&self, account_id: u32) -> Result<u32> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
Ok(ctx.draft_self_report().await?.to_u32())
|
Ok(ctx.draft_self_report().await?.to_u32())
|
||||||
@@ -433,9 +423,6 @@ impl CommandApi {
|
|||||||
|
|
||||||
/// Configures this account with the currently set parameters.
|
/// Configures this account with the currently set parameters.
|
||||||
/// Setup the credential config before calling this.
|
/// Setup the credential config before calling this.
|
||||||
///
|
|
||||||
/// Deprecated as of 2025-02; use `add_transport_from_qr()`
|
|
||||||
/// or `add_transport()` instead.
|
|
||||||
async fn configure(&self, account_id: u32) -> Result<()> {
|
async fn configure(&self, account_id: u32) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
ctx.stop_io().await;
|
ctx.stop_io().await;
|
||||||
@@ -450,69 +437,6 @@ impl CommandApi {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures a new email account using the provided parameters
|
|
||||||
/// and adds it as a transport.
|
|
||||||
///
|
|
||||||
/// If the email address is the same as an existing transport,
|
|
||||||
/// then this existing account will be reconfigured instead of a new one being added.
|
|
||||||
///
|
|
||||||
/// This function stops and starts IO as needed.
|
|
||||||
///
|
|
||||||
/// Usually it will be enough to only set `addr` and `imap.password`,
|
|
||||||
/// and all the other settings will be autoconfigured.
|
|
||||||
///
|
|
||||||
/// During configuration, ConfigureProgress events are emitted;
|
|
||||||
/// they indicate a successful configuration as well as errors
|
|
||||||
/// and may be used to create a progress bar.
|
|
||||||
/// This function will return after configuration is finished.
|
|
||||||
///
|
|
||||||
/// If configuration is successful,
|
|
||||||
/// the working server parameters will be saved
|
|
||||||
/// and used for connecting to the server.
|
|
||||||
/// The parameters entered by the user will be saved separately
|
|
||||||
/// so that they can be prefilled when the user opens the server-configuration screen again.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
/// - [Self::is_configured()] to check whether there is
|
|
||||||
/// at least one working transport.
|
|
||||||
/// - [Self::add_transport_from_qr()] to add a transport
|
|
||||||
/// from a server encoded in a QR code.
|
|
||||||
/// - [Self::list_transports()] to get a list of all configured transports.
|
|
||||||
/// - [Self::delete_transport()] to remove a transport.
|
|
||||||
async fn add_transport(&self, account_id: u32, param: EnteredLoginParam) -> Result<()> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
ctx.add_transport(¶m.try_into()?).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new email account as a transport
|
|
||||||
/// using the server encoded in the QR code.
|
|
||||||
/// See [Self::add_transport].
|
|
||||||
async fn add_transport_from_qr(&self, account_id: u32, qr: String) -> Result<()> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
ctx.add_transport_from_qr(&qr).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
|
||||||
/// Use [Self::add_transport()] to add or change a transport
|
|
||||||
/// and [Self::delete_transport()] to delete a transport.
|
|
||||||
async fn list_transports(&self, account_id: u32) -> Result<Vec<EnteredLoginParam>> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let res = ctx
|
|
||||||
.list_transports()
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.into())
|
|
||||||
.collect();
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the transport with the specified email address
|
|
||||||
/// (i.e. [EnteredLoginParam::addr]).
|
|
||||||
async fn delete_transport(&self, account_id: u32, addr: String) -> Result<()> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
ctx.delete_transport(&addr).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signal an ongoing process to stop.
|
/// Signal an ongoing process to stop.
|
||||||
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
|
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
@@ -783,22 +707,7 @@ impl CommandApi {
|
|||||||
ChatId::new(chat_id).get_encryption_info(&ctx).await
|
ChatId::new(chat_id).get_encryption_info(&ctx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get QR code text that will offer a [SecureJoin](https://securejoin.delta.chat/) invitation.
|
/// Get QR code (text and SVG) that will offer an Setup-Contact or Verified-Group invitation.
|
||||||
///
|
|
||||||
/// If `chat_id` is a group chat ID, SecureJoin QR code for the group is returned.
|
|
||||||
/// If `chat_id` is unset, setup contact QR code is returned.
|
|
||||||
async fn get_chat_securejoin_qr_code(
|
|
||||||
&self,
|
|
||||||
account_id: u32,
|
|
||||||
chat_id: Option<u32>,
|
|
||||||
) -> Result<String> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let chat = chat_id.map(ChatId::new);
|
|
||||||
let qr = securejoin::get_securejoin_qr(&ctx, chat).await?;
|
|
||||||
Ok(qr)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get QR code (text and SVG) that will offer a Setup-Contact or Verified-Group invitation.
|
|
||||||
/// The QR code is compatible to the OPENPGP4FPR format
|
/// The QR code is compatible to the OPENPGP4FPR format
|
||||||
/// so that a basic fingerprint comparison also works e.g. with OpenKeychain.
|
/// so that a basic fingerprint comparison also works e.g. with OpenKeychain.
|
||||||
///
|
///
|
||||||
@@ -820,9 +729,10 @@ impl CommandApi {
|
|||||||
) -> Result<(String, String)> {
|
) -> Result<(String, String)> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let chat = chat_id.map(ChatId::new);
|
let chat = chat_id.map(ChatId::new);
|
||||||
let qr = securejoin::get_securejoin_qr(&ctx, chat).await?;
|
Ok((
|
||||||
let svg = get_securejoin_qr_svg(&ctx, chat).await?;
|
securejoin::get_securejoin_qr(&ctx, chat).await?,
|
||||||
Ok((qr, svg))
|
get_securejoin_qr_svg(&ctx, chat).await?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Continue a Setup-Contact or Verified-Group-Invite protocol
|
/// Continue a Setup-Contact or Verified-Group-Invite protocol
|
||||||
@@ -911,13 +821,6 @@ impl CommandApi {
|
|||||||
Ok(contacts.iter().map(|id| id.to_u32()).collect::<Vec<u32>>())
|
Ok(contacts.iter().map(|id| id.to_u32()).collect::<Vec<u32>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns contact IDs of the past chat members.
|
|
||||||
async fn get_past_chat_contacts(&self, account_id: u32, chat_id: u32) -> Result<Vec<u32>> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let contacts = chat::get_past_chat_contacts(&ctx, ChatId::new(chat_id)).await?;
|
|
||||||
Ok(contacts.iter().map(|id| id.to_u32()).collect::<Vec<u32>>())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new group chat.
|
/// Create a new group chat.
|
||||||
///
|
///
|
||||||
/// After creation,
|
/// After creation,
|
||||||
@@ -1075,12 +978,6 @@ impl CommandApi {
|
|||||||
marknoticed_chat(&ctx, ChatId::new(chat_id)).await
|
marknoticed_chat(&ctx, ChatId::new(chat_id)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the message that is immediately followed by the last seen
|
|
||||||
/// message.
|
|
||||||
/// From the point of view of the user this is effectively
|
|
||||||
/// "first unread", but in reality in the database a seen message
|
|
||||||
/// _can_ be followed by a fresh (unseen) message
|
|
||||||
/// if that message has not been individually marked as seen.
|
|
||||||
async fn get_first_unread_message_of_chat(
|
async fn get_first_unread_message_of_chat(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
@@ -1169,9 +1066,6 @@ impl CommandApi {
|
|||||||
markseen_msgs(&ctx, msg_ids.into_iter().map(MsgId::new).collect()).await
|
markseen_msgs(&ctx, msg_ids.into_iter().map(MsgId::new).collect()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all messages of a particular chat.
|
|
||||||
/// If `add_daymarker` is `true`, it will return them as
|
|
||||||
/// `DC_MSG_ID_DAYMARKER`, e.g. [1234, 1237, 9, 1239].
|
|
||||||
async fn get_message_ids(
|
async fn get_message_ids(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
@@ -1226,11 +1120,9 @@ impl CommandApi {
|
|||||||
async fn get_message(&self, account_id: u32, msg_id: u32) -> Result<MessageObject> {
|
async fn get_message(&self, account_id: u32, msg_id: u32) -> Result<MessageObject> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let msg_id = MsgId::new(msg_id);
|
let msg_id = MsgId::new(msg_id);
|
||||||
let message_object = MessageObject::from_msg_id(&ctx, msg_id)
|
MessageObject::from_msg_id(&ctx, msg_id)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to load message {msg_id} for account {account_id}"))?
|
.with_context(|| format!("Failed to load message {msg_id} for account {account_id}"))
|
||||||
.with_context(|| format!("Message {msg_id} does not exist for account {account_id}"))?;
|
|
||||||
Ok(message_object)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_message_html(&self, account_id: u32, message_id: u32) -> Result<Option<String>> {
|
async fn get_message_html(&self, account_id: u32, message_id: u32) -> Result<Option<String>> {
|
||||||
@@ -1254,10 +1146,7 @@ impl CommandApi {
|
|||||||
messages.insert(
|
messages.insert(
|
||||||
message_id,
|
message_id,
|
||||||
match message_result {
|
match message_result {
|
||||||
Ok(Some(message)) => MessageLoadResult::Message(message),
|
Ok(message) => MessageLoadResult::Message(message),
|
||||||
Ok(None) => MessageLoadResult::LoadingError {
|
|
||||||
error: "Message does not exist".to_string(),
|
|
||||||
},
|
|
||||||
Err(error) => MessageLoadResult::LoadingError {
|
Err(error) => MessageLoadResult::LoadingError {
|
||||||
error: format!("{error:#}"),
|
error: format!("{error:#}"),
|
||||||
},
|
},
|
||||||
@@ -1267,48 +1156,6 @@ impl CommandApi {
|
|||||||
Ok(messages)
|
Ok(messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn basic_get_message(&self, account_id: u32, msg_id: u32) -> Result<BasicMessageObject> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let msg_id = MsgId::new(msg_id);
|
|
||||||
let message_object = BasicMessageObject::from_msg_id(&ctx, msg_id)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("Failed to load message {msg_id} for account {account_id}"))?
|
|
||||||
.with_context(|| format!("Message {msg_id} does not exist for account {account_id}"))?;
|
|
||||||
Ok(message_object)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get multiple messages in one call (but only basic properties)
|
|
||||||
///
|
|
||||||
/// This is for optimized performance, the result of [get_messages] is more complete, but is more expensive.
|
|
||||||
///
|
|
||||||
/// if loading one message fails the error is stored in the result object in it's place.
|
|
||||||
///
|
|
||||||
/// this is the batch variant of [basic_get_message]
|
|
||||||
async fn basic_get_messages(
|
|
||||||
&self,
|
|
||||||
account_id: u32,
|
|
||||||
message_ids: Vec<u32>,
|
|
||||||
) -> Result<HashMap<u32, BasicMessageLoadResult>> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let mut messages: HashMap<u32, BasicMessageLoadResult> = HashMap::new();
|
|
||||||
for message_id in message_ids {
|
|
||||||
let message_result = BasicMessageObject::from_msg_id(&ctx, MsgId::new(message_id)).await;
|
|
||||||
messages.insert(
|
|
||||||
message_id,
|
|
||||||
match message_result {
|
|
||||||
Ok(Some(message)) => BasicMessageLoadResult::Message(message),
|
|
||||||
Ok(None) => BasicMessageLoadResult::LoadingError {
|
|
||||||
error: "Message does not exist".to_string(),
|
|
||||||
},
|
|
||||||
Err(error) => BasicMessageLoadResult::LoadingError {
|
|
||||||
error: format!("{error:#}"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(messages)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch info desktop needs for creating a notification for a message
|
/// Fetch info desktop needs for creating a notification for a message
|
||||||
async fn get_message_notification_info(
|
async fn get_message_notification_info(
|
||||||
&self,
|
&self,
|
||||||
@@ -1324,15 +1171,7 @@ impl CommandApi {
|
|||||||
async fn delete_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
async fn delete_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let msgs: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
let msgs: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
||||||
delete_msgs_ex(&ctx, &msgs, false).await
|
delete_msgs(&ctx, &msgs).await
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete messages. The messages are deleted on the current device,
|
|
||||||
/// on the IMAP server and also for all chat members
|
|
||||||
async fn delete_messages_for_all(&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_ex(&ctx, &msgs, true).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an informational text for a single message. The text is multiline and may
|
/// Get an informational text for a single message. The text is multiline and may
|
||||||
@@ -1432,12 +1271,6 @@ impl CommandApi {
|
|||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_msgs(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let message_ids: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
|
||||||
chat::save_msgs(&ctx, &message_ids).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
// contact
|
// contact
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
@@ -1571,15 +1404,6 @@ impl CommandApi {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resets contact encryption.
|
|
||||||
async fn reset_contact_encryption(&self, account_id: u32, contact_id: u32) -> Result<()> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let contact_id = ContactId::new(contact_id);
|
|
||||||
|
|
||||||
contact_id.reset_encryption(&ctx).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn change_contact_name(
|
async fn change_contact_name(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
@@ -1623,7 +1447,7 @@ impl CommandApi {
|
|||||||
|
|
||||||
/// Parses a vCard file located at the given path. Returns contacts in their original order.
|
/// Parses a vCard file located at the given path. Returns contacts in their original order.
|
||||||
async fn parse_vcard(&self, path: String) -> Result<Vec<VcardContact>> {
|
async fn parse_vcard(&self, path: String) -> Result<Vec<VcardContact>> {
|
||||||
let vcard = fs::read(Path::new(&path)).await?;
|
let vcard = tokio::fs::read(Path::new(&path)).await?;
|
||||||
let vcard = str::from_utf8(&vcard)?;
|
let vcard = str::from_utf8(&vcard)?;
|
||||||
Ok(deltachat_contact_tools::parse_vcard(vcard)
|
Ok(deltachat_contact_tools::parse_vcard(vcard)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -1631,32 +1455,6 @@ impl CommandApi {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Imports contacts from a vCard file located at the given path.
|
|
||||||
///
|
|
||||||
/// Returns the ids of created/modified contacts in the order they appear in the vCard.
|
|
||||||
async fn import_vcard(&self, account_id: u32, path: String) -> Result<Vec<u32>> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let vcard = tokio::fs::read(Path::new(&path)).await?;
|
|
||||||
let vcard = str::from_utf8(&vcard)?;
|
|
||||||
Ok(deltachat::contact::import_vcard(&ctx, vcard)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|c| c.to_u32())
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Imports contacts from a vCard.
|
|
||||||
///
|
|
||||||
/// Returns the ids of created/modified contacts in the order they appear in the vCard.
|
|
||||||
async fn import_vcard_contents(&self, account_id: u32, vcard: String) -> Result<Vec<u32>> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
Ok(deltachat::contact::import_vcard(&ctx, &vcard)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|c| c.to_u32())
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a vCard containing contacts with the given ids.
|
/// Returns a vCard containing contacts with the given ids.
|
||||||
async fn make_vcard(&self, account_id: u32, contacts: Vec<u32>) -> Result<String> {
|
async fn make_vcard(&self, account_id: u32, contacts: Vec<u32>) -> Result<String> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
@@ -1664,20 +1462,6 @@ impl CommandApi {
|
|||||||
deltachat::contact::make_vcard(&ctx, &contacts).await
|
deltachat::contact::make_vcard(&ctx, &contacts).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets vCard containing the given contacts to the message draft.
|
|
||||||
async fn set_draft_vcard(
|
|
||||||
&self,
|
|
||||||
account_id: u32,
|
|
||||||
msg_id: u32,
|
|
||||||
contacts: Vec<u32>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let contacts: Vec<_> = contacts.iter().map(|&c| ContactId::new(c)).collect();
|
|
||||||
let mut msg = Message::load_from_db(&ctx, MsgId::new(msg_id)).await?;
|
|
||||||
msg.make_vcard(&ctx, &contacts).await?;
|
|
||||||
msg.get_chat_id().set_draft(&ctx, Some(&mut msg)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
// chat
|
// chat
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
@@ -1726,6 +1510,55 @@ impl CommandApi {
|
|||||||
Ok(media.iter().map(|msg_id| msg_id.to_u32()).collect())
|
Ok(media.iter().map(|msg_id| msg_id.to_u32()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Search next/previous message based on a given message and a list of types.
|
||||||
|
/// Typically used to implement the "next" and "previous" buttons
|
||||||
|
/// in a gallery or in a media player.
|
||||||
|
///
|
||||||
|
/// one combined call for getting chat::get_next_media for both directions
|
||||||
|
/// the manual chat::get_next_media in only one direction is not exposed by the jsonrpc yet
|
||||||
|
///
|
||||||
|
/// Deprecated 2023-10-03, use `get_chat_media` method
|
||||||
|
/// and navigate the returned array instead.
|
||||||
|
#[allow(deprecated)]
|
||||||
|
async fn get_neighboring_chat_media(
|
||||||
|
&self,
|
||||||
|
account_id: u32,
|
||||||
|
msg_id: u32,
|
||||||
|
message_type: MessageViewtype,
|
||||||
|
or_message_type2: Option<MessageViewtype>,
|
||||||
|
or_message_type3: Option<MessageViewtype>,
|
||||||
|
) -> Result<(Option<u32>, Option<u32>)> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
|
||||||
|
let msg_type: Viewtype = message_type.into();
|
||||||
|
let msg_type2: Viewtype = or_message_type2.map(|v| v.into()).unwrap_or_default();
|
||||||
|
let msg_type3: Viewtype = or_message_type3.map(|v| v.into()).unwrap_or_default();
|
||||||
|
|
||||||
|
let prev = chat::get_next_media(
|
||||||
|
&ctx,
|
||||||
|
MsgId::new(msg_id),
|
||||||
|
chat::Direction::Backward,
|
||||||
|
msg_type,
|
||||||
|
msg_type2,
|
||||||
|
msg_type3,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.map(|id| id.to_u32());
|
||||||
|
|
||||||
|
let next = chat::get_next_media(
|
||||||
|
&ctx,
|
||||||
|
MsgId::new(msg_id),
|
||||||
|
chat::Direction::Forward,
|
||||||
|
msg_type,
|
||||||
|
msg_type2,
|
||||||
|
msg_type3,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.map(|id| id.to_u32());
|
||||||
|
|
||||||
|
Ok((prev, next))
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
// backup
|
// backup
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
@@ -1797,10 +1630,10 @@ impl CommandApi {
|
|||||||
///
|
///
|
||||||
/// This call will block until the QR code is ready,
|
/// This call will block until the QR code is ready,
|
||||||
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
|
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
|
||||||
/// but will fail after 60 seconds to avoid deadlocks.
|
/// but will fail after 10 seconds to avoid deadlocks.
|
||||||
async fn get_backup_qr(&self, account_id: u32) -> Result<String> {
|
async fn get_backup_qr(&self, account_id: u32) -> Result<String> {
|
||||||
let qr = tokio::time::timeout(
|
let qr = tokio::time::timeout(
|
||||||
Duration::from_secs(60),
|
Duration::from_secs(10),
|
||||||
self.inner_get_backup_qr(account_id),
|
self.inner_get_backup_qr(account_id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1816,13 +1649,13 @@ impl CommandApi {
|
|||||||
///
|
///
|
||||||
/// This call will block until the QR code is ready,
|
/// This call will block until the QR code is ready,
|
||||||
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
|
/// even if there is no concurrent call to [`CommandApi::provide_backup`],
|
||||||
/// but will fail after 60 seconds to avoid deadlocks.
|
/// but will fail after 10 seconds to avoid deadlocks.
|
||||||
///
|
///
|
||||||
/// Returns the QR code rendered as an SVG image.
|
/// Returns the QR code rendered as an SVG image.
|
||||||
async fn get_backup_qr_svg(&self, account_id: u32) -> Result<String> {
|
async fn get_backup_qr_svg(&self, account_id: u32) -> Result<String> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let qr = tokio::time::timeout(
|
let qr = tokio::time::timeout(
|
||||||
Duration::from_secs(60),
|
Duration::from_secs(10),
|
||||||
self.inner_get_backup_qr(account_id),
|
self.inner_get_backup_qr(account_id),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -1926,10 +1759,10 @@ impl CommandApi {
|
|||||||
account_id: u32,
|
account_id: u32,
|
||||||
instance_msg_id: u32,
|
instance_msg_id: u32,
|
||||||
update_str: String,
|
update_str: String,
|
||||||
_descr: Option<String>,
|
description: String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
ctx.send_webxdc_status_update(MsgId::new(instance_msg_id), &update_str)
|
ctx.send_webxdc_status_update(MsgId::new(instance_msg_id), &update_str, &description)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1988,18 +1821,6 @@ impl CommandApi {
|
|||||||
WebxdcMessageInfo::get_for_message(&ctx, MsgId::new(instance_msg_id)).await
|
WebxdcMessageInfo::get_for_message(&ctx, MsgId::new(instance_msg_id)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get href from a WebxdcInfoMessage which might include a hash holding
|
|
||||||
/// information about a specific position or state in a webxdc app (optional)
|
|
||||||
async fn get_webxdc_href(
|
|
||||||
&self,
|
|
||||||
account_id: u32,
|
|
||||||
instance_msg_id: u32,
|
|
||||||
) -> Result<Option<String>> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
let message = Message::load_from_db(&ctx, MsgId::new(instance_msg_id)).await?;
|
|
||||||
Ok(message.get_webxdc_href())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get blob encoded as base64 from a webxdc message
|
/// Get blob encoded as base64 from a webxdc message
|
||||||
///
|
///
|
||||||
/// path is the path of the file within webxdc archive
|
/// path is the path of the file within webxdc archive
|
||||||
@@ -2089,7 +1910,7 @@ impl CommandApi {
|
|||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
|
||||||
let mut msg = Message::new(Viewtype::Sticker);
|
let mut msg = Message::new(Viewtype::Sticker);
|
||||||
msg.set_file_and_deduplicate(&ctx, Path::new(&sticker_path), None, None)?;
|
msg.set_file(&sticker_path, None);
|
||||||
|
|
||||||
// JSON-rpc does not need heuristics to turn [Viewtype::Sticker] into [Viewtype::Image]
|
// JSON-rpc does not need heuristics to turn [Viewtype::Sticker] into [Viewtype::Image]
|
||||||
msg.force_sticker();
|
msg.force_sticker();
|
||||||
@@ -2132,27 +1953,13 @@ impl CommandApi {
|
|||||||
|
|
||||||
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
|
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let mut message = data
|
let mut message = data.create_message(&ctx).await?;
|
||||||
.create_message(&ctx)
|
|
||||||
.await
|
|
||||||
.context("Failed to create message")?;
|
|
||||||
let msg_id = chat::send_msg(&ctx, ChatId::new(chat_id), &mut message)
|
let msg_id = chat::send_msg(&ctx, ChatId::new(chat_id), &mut message)
|
||||||
.await
|
.await?
|
||||||
.context("Failed to send created message")?
|
|
||||||
.to_u32();
|
.to_u32();
|
||||||
Ok(msg_id)
|
Ok(msg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_edit_request(
|
|
||||||
&self,
|
|
||||||
account_id: u32,
|
|
||||||
msg_id: u32,
|
|
||||||
new_text: String,
|
|
||||||
) -> Result<()> {
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
|
||||||
chat::send_edit_request(&ctx, MsgId::new(msg_id), new_text).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if messages can be sent to a given chat.
|
/// Checks if messages can be sent to a given chat.
|
||||||
async fn can_send(&self, account_id: u32, chat_id: u32) -> Result<bool> {
|
async fn can_send(&self, account_id: u32, chat_id: u32) -> Result<bool> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
@@ -2185,7 +1992,9 @@ impl CommandApi {
|
|||||||
async fn get_draft(&self, account_id: u32, chat_id: u32) -> Result<Option<MessageObject>> {
|
async fn get_draft(&self, account_id: u32, chat_id: u32) -> Result<Option<MessageObject>> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
if let Some(draft) = ChatId::new(chat_id).get_draft(&ctx).await? {
|
if let Some(draft) = ChatId::new(chat_id).get_draft(&ctx).await? {
|
||||||
Ok(MessageObject::from_msg_id(&ctx, draft.get_id()).await?)
|
Ok(Some(
|
||||||
|
MessageObject::from_msg_id(&ctx, draft.get_id()).await?,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -2311,7 +2120,8 @@ impl CommandApi {
|
|||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
|
||||||
let mut msg = Message::new_text(text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
|
msg.set_text(text);
|
||||||
|
|
||||||
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
|
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
|
||||||
Ok(message_id.to_u32())
|
Ok(message_id.to_u32())
|
||||||
@@ -2319,14 +2129,12 @@ impl CommandApi {
|
|||||||
|
|
||||||
// mimics the old desktop call, will get replaced with something better in the composer rewrite,
|
// mimics the old desktop call, will get replaced with something better in the composer rewrite,
|
||||||
// the better version will just be sending the current draft, though there will be probably something similar with more options to this for the corner cases like setting a marker on the map
|
// the better version will just be sending the current draft, though there will be probably something similar with more options to this for the corner cases like setting a marker on the map
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
async fn misc_send_msg(
|
async fn misc_send_msg(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
chat_id: u32,
|
chat_id: u32,
|
||||||
text: Option<String>,
|
text: Option<String>,
|
||||||
file: Option<String>,
|
file: Option<String>,
|
||||||
filename: Option<String>,
|
|
||||||
location: Option<(f64, f64)>,
|
location: Option<(f64, f64)>,
|
||||||
quoted_message_id: Option<u32>,
|
quoted_message_id: Option<u32>,
|
||||||
) -> Result<(u32, MessageObject)> {
|
) -> Result<(u32, MessageObject)> {
|
||||||
@@ -2338,7 +2146,7 @@ impl CommandApi {
|
|||||||
});
|
});
|
||||||
message.set_text(text.unwrap_or_default());
|
message.set_text(text.unwrap_or_default());
|
||||||
if let Some(file) = file {
|
if let Some(file) = file {
|
||||||
message.set_file_and_deduplicate(&ctx, Path::new(&file), filename.as_deref(), None)?;
|
message.set_file(file, None);
|
||||||
}
|
}
|
||||||
if let Some((latitude, longitude)) = location {
|
if let Some((latitude, longitude)) = location {
|
||||||
message.set_location(latitude, longitude);
|
message.set_location(latitude, longitude);
|
||||||
@@ -2356,9 +2164,7 @@ impl CommandApi {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
let msg_id = chat::send_msg(&ctx, ChatId::new(chat_id), &mut message).await?;
|
let msg_id = chat::send_msg(&ctx, ChatId::new(chat_id), &mut message).await?;
|
||||||
let message = MessageObject::from_msg_id(&ctx, msg_id)
|
let message = MessageObject::from_msg_id(&ctx, msg_id).await?;
|
||||||
.await?
|
|
||||||
.context("Just sent message does not exist")?;
|
|
||||||
Ok((msg_id.to_u32(), message))
|
Ok((msg_id.to_u32(), message))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2366,14 +2172,12 @@ impl CommandApi {
|
|||||||
// the better version should support:
|
// the better version should support:
|
||||||
// - changing viewtype to enable/disable compression
|
// - changing viewtype to enable/disable compression
|
||||||
// - keeping same message id as long as attachment does not change for webxdc messages
|
// - keeping same message id as long as attachment does not change for webxdc messages
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
async fn misc_set_draft(
|
async fn misc_set_draft(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
chat_id: u32,
|
chat_id: u32,
|
||||||
text: Option<String>,
|
text: Option<String>,
|
||||||
file: Option<String>,
|
file: Option<String>,
|
||||||
filename: Option<String>,
|
|
||||||
quoted_message_id: Option<u32>,
|
quoted_message_id: Option<u32>,
|
||||||
view_type: Option<MessageViewtype>,
|
view_type: Option<MessageViewtype>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -2390,7 +2194,7 @@ impl CommandApi {
|
|||||||
));
|
));
|
||||||
draft.set_text(text.unwrap_or_default());
|
draft.set_text(text.unwrap_or_default());
|
||||||
if let Some(file) = file {
|
if let Some(file) = file {
|
||||||
draft.set_file_and_deduplicate(&ctx, Path::new(&file), filename.as_deref(), None)?;
|
draft.set_file(file, None);
|
||||||
}
|
}
|
||||||
if let Some(id) = quoted_message_id {
|
if let Some(id) = quoted_message_id {
|
||||||
draft
|
draft
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ pub enum Account {
|
|||||||
// size: u32,
|
// size: u32,
|
||||||
profile_image: Option<String>, // TODO: This needs to be converted to work with blob http server.
|
profile_image: Option<String>, // TODO: This needs to be converted to work with blob http server.
|
||||||
color: String,
|
color: String,
|
||||||
/// Optional tag as "Work", "Family".
|
|
||||||
/// Meant to help profile owner to differ between profiles with similar names.
|
|
||||||
private_tag: Option<String>,
|
|
||||||
},
|
},
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
Unconfigured { id: u32 },
|
Unconfigured { id: u32 },
|
||||||
@@ -34,14 +31,12 @@ impl Account {
|
|||||||
let color = color_int_to_hex_string(
|
let color = color_int_to_hex_string(
|
||||||
Contact::get_by_id(ctx, ContactId::SELF).await?.get_color(),
|
Contact::get_by_id(ctx, ContactId::SELF).await?.get_color(),
|
||||||
);
|
);
|
||||||
let private_tag = ctx.get_config(Config::PrivateTag).await?;
|
|
||||||
Ok(Account::Configured {
|
Ok(Account::Configured {
|
||||||
id,
|
id,
|
||||||
display_name,
|
display_name,
|
||||||
addr,
|
addr,
|
||||||
profile_image,
|
profile_image,
|
||||||
color,
|
color,
|
||||||
private_tag,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(Account::Unconfigured { id })
|
Ok(Account::Unconfigured { id })
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
use anyhow::{Context as _, Result};
|
|
||||||
|
|
||||||
use deltachat::context::Context;
|
|
||||||
use deltachat::message::Message;
|
|
||||||
use deltachat::message::MsgId;
|
|
||||||
use num_traits::cast::ToPrimitive;
|
|
||||||
use serde::Serialize;
|
|
||||||
use typescript_type_def::TypeDef;
|
|
||||||
|
|
||||||
use super::message::DownloadState;
|
|
||||||
use super::message::MessageViewtype;
|
|
||||||
use super::message::SystemMessageType;
|
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase", tag = "kind")]
|
|
||||||
pub enum BasicMessageLoadResult {
|
|
||||||
Message(BasicMessageObject),
|
|
||||||
LoadingError { error: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message that only has basic properties that doen't require additional db calls
|
|
||||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
|
||||||
#[serde(rename = "BasicMessage", rename_all = "camelCase")]
|
|
||||||
pub struct BasicMessageObject {
|
|
||||||
id: u32,
|
|
||||||
chat_id: u32,
|
|
||||||
from_id: u32,
|
|
||||||
|
|
||||||
text: String,
|
|
||||||
|
|
||||||
is_edited: bool,
|
|
||||||
|
|
||||||
/// Check if a message has a POI location bound to it.
|
|
||||||
/// These locations are also returned by `get_locations` method.
|
|
||||||
/// The UI may decide to display a special icon beside such messages.
|
|
||||||
has_location: bool,
|
|
||||||
has_html: bool,
|
|
||||||
view_type: MessageViewtype,
|
|
||||||
state: u32,
|
|
||||||
|
|
||||||
/// An error text, if there is one.
|
|
||||||
error: Option<String>,
|
|
||||||
|
|
||||||
timestamp: i64,
|
|
||||||
sort_timestamp: i64,
|
|
||||||
received_timestamp: i64,
|
|
||||||
has_deviating_timestamp: bool,
|
|
||||||
|
|
||||||
// summary - use/create another function if you need it
|
|
||||||
subject: String,
|
|
||||||
show_padlock: bool,
|
|
||||||
is_setupmessage: bool,
|
|
||||||
is_info: bool,
|
|
||||||
is_forwarded: bool,
|
|
||||||
|
|
||||||
/// True if the message was sent by a bot.
|
|
||||||
is_bot: bool,
|
|
||||||
|
|
||||||
/// when is_info is true this describes what type of system message it is
|
|
||||||
system_message_type: SystemMessageType,
|
|
||||||
|
|
||||||
duration: i32,
|
|
||||||
dimensions_height: i32,
|
|
||||||
dimensions_width: i32,
|
|
||||||
|
|
||||||
videochat_type: Option<u32>,
|
|
||||||
videochat_url: Option<String>,
|
|
||||||
|
|
||||||
override_sender_name: Option<String>,
|
|
||||||
|
|
||||||
setup_code_begin: Option<String>,
|
|
||||||
|
|
||||||
file: Option<String>,
|
|
||||||
file_mime: Option<String>,
|
|
||||||
file_name: Option<String>,
|
|
||||||
|
|
||||||
webxdc_href: Option<String>,
|
|
||||||
|
|
||||||
download_state: DownloadState,
|
|
||||||
|
|
||||||
original_msg_id: Option<u32>,
|
|
||||||
|
|
||||||
saved_message_id: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BasicMessageObject {
|
|
||||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Option<Self>> {
|
|
||||||
let Some(message) = Message::load_from_db_optional(context, msg_id).await? else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let override_sender_name = message.get_override_sender_name();
|
|
||||||
|
|
||||||
let download_state = message.download_state().into();
|
|
||||||
|
|
||||||
let message_object = Self {
|
|
||||||
id: msg_id.to_u32(),
|
|
||||||
chat_id: message.get_chat_id().to_u32(),
|
|
||||||
from_id: message.get_from_id().to_u32(),
|
|
||||||
text: message.get_text(),
|
|
||||||
is_edited: message.is_edited(),
|
|
||||||
has_location: message.has_location(),
|
|
||||||
has_html: message.has_html(),
|
|
||||||
view_type: message.get_viewtype().into(),
|
|
||||||
state: message
|
|
||||||
.get_state()
|
|
||||||
.to_u32()
|
|
||||||
.context("state conversion to number failed")?,
|
|
||||||
error: message.error(),
|
|
||||||
|
|
||||||
timestamp: message.get_timestamp(),
|
|
||||||
sort_timestamp: message.get_sort_timestamp(),
|
|
||||||
received_timestamp: message.get_received_timestamp(),
|
|
||||||
has_deviating_timestamp: message.has_deviating_timestamp(),
|
|
||||||
|
|
||||||
subject: message.get_subject().to_owned(),
|
|
||||||
show_padlock: message.get_showpadlock(),
|
|
||||||
is_setupmessage: message.is_setupmessage(),
|
|
||||||
is_info: message.is_info(),
|
|
||||||
is_forwarded: message.is_forwarded(),
|
|
||||||
is_bot: message.is_bot(),
|
|
||||||
system_message_type: message.get_info_type().into(),
|
|
||||||
|
|
||||||
duration: message.get_duration(),
|
|
||||||
dimensions_height: message.get_height(),
|
|
||||||
dimensions_width: message.get_width(),
|
|
||||||
|
|
||||||
videochat_type: match message.get_videochat_type() {
|
|
||||||
Some(vct) => Some(
|
|
||||||
vct.to_u32()
|
|
||||||
.context("videochat type conversion to number failed")?,
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
videochat_url: message.get_videochat_url(),
|
|
||||||
|
|
||||||
override_sender_name,
|
|
||||||
|
|
||||||
setup_code_begin: message.get_setupcodebegin(context).await,
|
|
||||||
|
|
||||||
file: match message.get_file(context) {
|
|
||||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
|
||||||
None => None,
|
|
||||||
}, //BLOBS
|
|
||||||
file_mime: message.get_filemime(),
|
|
||||||
file_name: message.get_filename(),
|
|
||||||
|
|
||||||
// On a WebxdcInfoMessage this might include a hash holding
|
|
||||||
// information about a specific position or state in a webxdc app
|
|
||||||
webxdc_href: message.get_webxdc_href(),
|
|
||||||
|
|
||||||
download_state,
|
|
||||||
|
|
||||||
original_msg_id: message
|
|
||||||
.get_original_msg_id(context)
|
|
||||||
.await?
|
|
||||||
.map(|id| id.to_u32()),
|
|
||||||
|
|
||||||
saved_message_id: message
|
|
||||||
.get_saved_msg_id(context)
|
|
||||||
.await?
|
|
||||||
.map(|id| id.to_u32()),
|
|
||||||
};
|
|
||||||
Ok(Some(message_object))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use deltachat::chat::{self, get_chat_contacts, get_past_chat_contacts, ChatVisibility};
|
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
|
||||||
use deltachat::chat::{Chat, ChatId};
|
use deltachat::chat::{Chat, ChatId};
|
||||||
use deltachat::constants::Chattype;
|
use deltachat::constants::Chattype;
|
||||||
use deltachat::contact::{Contact, ContactId};
|
use deltachat::contact::{Contact, ContactId};
|
||||||
@@ -32,17 +32,12 @@ pub struct FullChat {
|
|||||||
is_protected: bool,
|
is_protected: bool,
|
||||||
profile_image: Option<String>, //BLOBS ?
|
profile_image: Option<String>, //BLOBS ?
|
||||||
archived: bool,
|
archived: bool,
|
||||||
pinned: bool,
|
|
||||||
// subtitle - will be moved to frontend because it uses translation functions
|
// subtitle - will be moved to frontend because it uses translation functions
|
||||||
chat_type: u32,
|
chat_type: u32,
|
||||||
is_unpromoted: bool,
|
is_unpromoted: bool,
|
||||||
is_self_talk: bool,
|
is_self_talk: bool,
|
||||||
contacts: Vec<ContactObject>,
|
contacts: Vec<ContactObject>,
|
||||||
contact_ids: Vec<u32>,
|
contact_ids: Vec<u32>,
|
||||||
|
|
||||||
/// Contact IDs of the past chat members.
|
|
||||||
past_contact_ids: Vec<u32>,
|
|
||||||
|
|
||||||
color: String,
|
color: String,
|
||||||
fresh_message_counter: usize,
|
fresh_message_counter: usize,
|
||||||
// is_group - please check over chat.type in frontend instead
|
// is_group - please check over chat.type in frontend instead
|
||||||
@@ -63,7 +58,6 @@ impl FullChat {
|
|||||||
let chat = Chat::load_from_db(context, rust_chat_id).await?;
|
let chat = Chat::load_from_db(context, rust_chat_id).await?;
|
||||||
|
|
||||||
let contact_ids = get_chat_contacts(context, rust_chat_id).await?;
|
let contact_ids = get_chat_contacts(context, rust_chat_id).await?;
|
||||||
let past_contact_ids = get_past_chat_contacts(context, rust_chat_id).await?;
|
|
||||||
|
|
||||||
let mut contacts = Vec::with_capacity(contact_ids.len());
|
let mut contacts = Vec::with_capacity(contact_ids.len());
|
||||||
|
|
||||||
@@ -110,13 +104,11 @@ impl FullChat {
|
|||||||
is_protected: chat.is_protected(),
|
is_protected: chat.is_protected(),
|
||||||
profile_image, //BLOBS ?
|
profile_image, //BLOBS ?
|
||||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||||
pinned: chat.get_visibility() == chat::ChatVisibility::Pinned,
|
|
||||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||||
is_unpromoted: chat.is_unpromoted(),
|
is_unpromoted: chat.is_unpromoted(),
|
||||||
is_self_talk: chat.is_self_talk(),
|
is_self_talk: chat.is_self_talk(),
|
||||||
contacts,
|
contacts,
|
||||||
contact_ids: contact_ids.iter().map(|id| id.to_u32()).collect(),
|
contact_ids: contact_ids.iter().map(|id| id.to_u32()).collect(),
|
||||||
past_contact_ids: past_contact_ids.iter().map(|id| id.to_u32()).collect(),
|
|
||||||
color,
|
color,
|
||||||
fresh_message_counter,
|
fresh_message_counter,
|
||||||
is_contact_request: chat.is_contact_request(),
|
is_contact_request: chat.is_contact_request(),
|
||||||
@@ -161,7 +153,6 @@ pub struct BasicChat {
|
|||||||
is_protected: bool,
|
is_protected: bool,
|
||||||
profile_image: Option<String>, //BLOBS ?
|
profile_image: Option<String>, //BLOBS ?
|
||||||
archived: bool,
|
archived: bool,
|
||||||
pinned: bool,
|
|
||||||
chat_type: u32,
|
chat_type: u32,
|
||||||
is_unpromoted: bool,
|
is_unpromoted: bool,
|
||||||
is_self_talk: bool,
|
is_self_talk: bool,
|
||||||
@@ -189,7 +180,6 @@ impl BasicChat {
|
|||||||
is_protected: chat.is_protected(),
|
is_protected: chat.is_protected(),
|
||||||
profile_image, //BLOBS ?
|
profile_image, //BLOBS ?
|
||||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||||
pinned: chat.get_visibility() == chat::ChatVisibility::Pinned,
|
|
||||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||||
is_unpromoted: chat.is_unpromoted(),
|
is_unpromoted: chat.is_unpromoted(),
|
||||||
is_self_talk: chat.is_self_talk(),
|
is_self_talk: chat.is_self_talk(),
|
||||||
|
|||||||
@@ -88,17 +88,11 @@ pub(crate) async fn get_chat_list_item_by_id(
|
|||||||
|
|
||||||
let (last_updated, message_type) = match last_msgid {
|
let (last_updated, message_type) = match last_msgid {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
if let Some(last_message) =
|
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
|
||||||
deltachat::message::Message::load_from_db_optional(ctx, id).await?
|
|
||||||
{
|
|
||||||
(
|
(
|
||||||
Some(last_message.get_timestamp() * 1000),
|
Some(last_message.get_timestamp() * 1000),
|
||||||
Some(last_message.get_viewtype().into()),
|
Some(last_message.get_viewtype().into()),
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
// Message may be deleted by the time we try to load it.
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => (None, None),
|
None => (None, None),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ pub struct ContactObject {
|
|||||||
profile_image: Option<String>, // BLOBS
|
profile_image: Option<String>, // BLOBS
|
||||||
name_and_addr: String,
|
name_and_addr: String,
|
||||||
is_blocked: bool,
|
is_blocked: bool,
|
||||||
e2ee_avail: bool,
|
|
||||||
|
|
||||||
/// True if the contact can be added to verified groups.
|
/// True if the contact can be added to verified groups.
|
||||||
///
|
///
|
||||||
@@ -80,7 +79,6 @@ impl ContactObject {
|
|||||||
profile_image, //BLOBS
|
profile_image, //BLOBS
|
||||||
name_and_addr: contact.get_name_n_addr(),
|
name_and_addr: contact.get_name_n_addr(),
|
||||||
is_blocked: contact.is_blocked(),
|
is_blocked: contact.is_blocked(),
|
||||||
e2ee_avail: contact.e2ee_avail(context).await?,
|
|
||||||
is_verified,
|
is_verified,
|
||||||
is_profile_verified,
|
is_profile_verified,
|
||||||
verifier_id,
|
verifier_id,
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ pub enum EventType {
|
|||||||
/// or for functions that are expected to fail (eg. autocryptContinueKeyTransfer())
|
/// or for functions that are expected to fail (eg. autocryptContinueKeyTransfer())
|
||||||
/// it might be better to delay showing these events until the function has really
|
/// it might be better to delay showing these events until the function has really
|
||||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
/// failed (returned false). It should be sufficient to report only the *last* error
|
||||||
/// in a message box then.
|
/// in a messasge box then.
|
||||||
Error { msg: String },
|
Error { msg: String },
|
||||||
|
|
||||||
/// An action cannot be performed because the user is not in the group.
|
/// An action cannot be performed because the user is not in the group.
|
||||||
@@ -84,78 +84,26 @@ pub enum EventType {
|
|||||||
/// - Messages sent, received or removed
|
/// - Messages sent, received or removed
|
||||||
/// - Chats created, deleted or archived
|
/// - Chats created, deleted or archived
|
||||||
/// - A draft has been set
|
/// - A draft has been set
|
||||||
|
///
|
||||||
|
/// `chatId` is set if only a single chat is affected by the changes, otherwise 0.
|
||||||
|
/// `msgId` is set if only a single message is affected by the changes, otherwise 0.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
MsgsChanged {
|
MsgsChanged { chat_id: u32, msg_id: u32 },
|
||||||
/// Set if only a single chat is affected by the changes, otherwise 0.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// Set if only a single message is affected by the changes, otherwise 0.
|
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Reactions for the message changed.
|
/// Reactions for the message changed.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ReactionsChanged {
|
ReactionsChanged {
|
||||||
/// ID of the chat which the message belongs to.
|
|
||||||
chat_id: u32,
|
chat_id: u32,
|
||||||
|
|
||||||
/// ID of the message for which reactions were changed.
|
|
||||||
msg_id: u32,
|
msg_id: u32,
|
||||||
|
|
||||||
/// ID of the contact whose reaction set is changed.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A reaction to one's own sent message received.
|
/// There is a fresh message. Typically, the user will show an notification
|
||||||
/// Typically, the UI will show a notification for that.
|
|
||||||
///
|
|
||||||
/// In addition to this event, ReactionsChanged is emitted.
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
IncomingReaction {
|
|
||||||
/// ID of the chat which the message belongs to.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// ID of the contact whose reaction set is changed.
|
|
||||||
contact_id: u32,
|
|
||||||
|
|
||||||
/// ID of the message for which reactions were changed.
|
|
||||||
msg_id: u32,
|
|
||||||
|
|
||||||
/// The reaction.
|
|
||||||
reaction: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Incoming webxdc info or summary update, should be notified.
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
IncomingWebxdcNotify {
|
|
||||||
/// ID of the chat.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// ID of the contact sending.
|
|
||||||
contact_id: u32,
|
|
||||||
|
|
||||||
/// ID of the added info message or webxdc instance in case of summary change.
|
|
||||||
msg_id: u32,
|
|
||||||
|
|
||||||
/// Text to notify.
|
|
||||||
text: String,
|
|
||||||
|
|
||||||
/// Link assigned to this notification, if any.
|
|
||||||
href: Option<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// There is a fresh message. Typically, the user will show a notification
|
|
||||||
/// when receiving this message.
|
/// when receiving this message.
|
||||||
///
|
///
|
||||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event sent together with this event.
|
/// There is no extra #DC_EVENT_MSGS_CHANGED event sent together with this event.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
IncomingMsg {
|
IncomingMsg { chat_id: u32, msg_id: u32 },
|
||||||
/// ID of the chat where the message is assigned.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// ID of the message.
|
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Downloading a bunch of messages just finished. This is an
|
/// Downloading a bunch of messages just finished. This is an
|
||||||
/// event to allow the UI to only show one notification per message bunch,
|
/// event to allow the UI to only show one notification per message bunch,
|
||||||
@@ -171,57 +119,21 @@ pub enum EventType {
|
|||||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||||
/// DC_STATE_OUT_DELIVERED, see `Message.state`.
|
/// DC_STATE_OUT_DELIVERED, see `Message.state`.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
MsgDelivered {
|
MsgDelivered { chat_id: u32, msg_id: u32 },
|
||||||
/// ID of the chat which the message belongs to.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// ID of the message that was successfully sent.
|
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||||
/// DC_STATE_OUT_FAILED, see `Message.state`.
|
/// DC_STATE_OUT_FAILED, see `Message.state`.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
MsgFailed {
|
MsgFailed { chat_id: u32, msg_id: u32 },
|
||||||
/// ID of the chat which the message belongs to.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// ID of the message that could not be sent.
|
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||||
/// DC_STATE_OUT_MDN_RCVD, see `Message.state`.
|
/// DC_STATE_OUT_MDN_RCVD, see `Message.state`.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
MsgRead {
|
MsgRead { chat_id: u32, msg_id: u32 },
|
||||||
/// ID of the chat which the message belongs to.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// ID of the message that was read.
|
/// A single message is deleted.
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A single message was deleted.
|
|
||||||
///
|
|
||||||
/// This event means that the message will no longer appear in the messagelist.
|
|
||||||
/// UI should remove the message from the messagelist
|
|
||||||
/// in response to this event if the message is currently displayed.
|
|
||||||
///
|
|
||||||
/// The message may have been explicitly deleted by the user or expired.
|
|
||||||
/// Internally the message may have been removed from the database,
|
|
||||||
/// moved to the trash chat or hidden.
|
|
||||||
///
|
|
||||||
/// This event does not indicate the message
|
|
||||||
/// deletion from the server.
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
MsgDeleted {
|
MsgDeleted { chat_id: u32, msg_id: u32 },
|
||||||
/// ID of the chat where the message was prior to deletion.
|
|
||||||
/// Never 0.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// ID of the deleted message. Never 0.
|
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||||
/// Or the verify state of a chat has changed.
|
/// Or the verify state of a chat has changed.
|
||||||
@@ -235,35 +147,21 @@ pub enum EventType {
|
|||||||
|
|
||||||
/// Chat ephemeral timer changed.
|
/// Chat ephemeral timer changed.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ChatEphemeralTimerModified {
|
ChatEphemeralTimerModified { chat_id: u32, timer: u32 },
|
||||||
/// Chat ID.
|
|
||||||
chat_id: u32,
|
|
||||||
|
|
||||||
/// New ephemeral timer value.
|
|
||||||
timer: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Chat deleted.
|
|
||||||
ChatDeleted {
|
|
||||||
/// Chat ID.
|
|
||||||
chat_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Contact(s) created, renamed, blocked or deleted.
|
/// Contact(s) created, renamed, blocked or deleted.
|
||||||
|
///
|
||||||
|
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ContactsChanged {
|
ContactsChanged { contact_id: Option<u32> },
|
||||||
/// If set, this is the contact_id of an added contact that should be selected.
|
|
||||||
contact_id: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Location of one or more contact has changed.
|
/// Location of one or more contact has changed.
|
||||||
#[serde(rename_all = "camelCase")]
|
///
|
||||||
LocationChanged {
|
/// @param data1 (u32) contact_id of the contact for which the location has changed.
|
||||||
/// contact_id of the contact for which the location has changed.
|
|
||||||
/// If the locations of several contacts have been changed,
|
/// If the locations of several contacts have been changed,
|
||||||
/// this parameter is set to `None`.
|
/// this parameter is set to `None`.
|
||||||
contact_id: Option<u32>,
|
#[serde(rename_all = "camelCase")]
|
||||||
},
|
LocationChanged { contact_id: Option<u32> },
|
||||||
|
|
||||||
/// Inform about the configuration progress started by configure().
|
/// Inform about the configuration progress started by configure().
|
||||||
ConfigureProgress {
|
ConfigureProgress {
|
||||||
@@ -278,11 +176,10 @@ pub enum EventType {
|
|||||||
|
|
||||||
/// Inform about the import/export progress started by imex().
|
/// Inform about the import/export progress started by imex().
|
||||||
///
|
///
|
||||||
|
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||||
|
/// @param data2 0
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ImexProgress {
|
ImexProgress { progress: usize },
|
||||||
/// 0=error, 1-999=progress in permille, 1000=success and done
|
|
||||||
progress: usize,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A file has been exported. A file has been written by imex().
|
/// A file has been exported. A file has been written by imex().
|
||||||
/// This event may be sent multiple times by a single call to imex().
|
/// This event may be sent multiple times by a single call to imex().
|
||||||
@@ -299,34 +196,26 @@ pub enum EventType {
|
|||||||
///
|
///
|
||||||
/// These events are typically sent after a joiner has scanned the QR code
|
/// These events are typically sent after a joiner has scanned the QR code
|
||||||
/// generated by getChatSecurejoinQrCodeSvg().
|
/// generated by getChatSecurejoinQrCodeSvg().
|
||||||
#[serde(rename_all = "camelCase")]
|
///
|
||||||
SecurejoinInviterProgress {
|
/// @param data1 (int) ID of the contact that wants to join.
|
||||||
/// ID of the contact that wants to join.
|
/// @param data2 (int) Progress as:
|
||||||
contact_id: u32,
|
|
||||||
|
|
||||||
/// Progress as:
|
|
||||||
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||||
/// 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol.
|
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||||
/// 1000=Protocol finished for this contact.
|
/// 1000=Protocol finished for this contact.
|
||||||
progress: usize,
|
#[serde(rename_all = "camelCase")]
|
||||||
},
|
SecurejoinInviterProgress { contact_id: u32, progress: usize },
|
||||||
|
|
||||||
/// Progress information of a secure-join handshake from the view of the joiner
|
/// Progress information of a secure-join handshake from the view of the joiner
|
||||||
/// (Bob, the person who scans the QR code).
|
/// (Bob, the person who scans the QR code).
|
||||||
/// The events are typically sent while secureJoin(), which
|
/// The events are typically sent while secureJoin(), which
|
||||||
/// may take some time, is executed.
|
/// may take some time, is executed.
|
||||||
#[serde(rename_all = "camelCase")]
|
/// @param data1 (int) ID of the inviting contact.
|
||||||
SecurejoinJoinerProgress {
|
/// @param data2 (int) Progress as:
|
||||||
/// ID of the inviting contact.
|
|
||||||
contact_id: u32,
|
|
||||||
|
|
||||||
/// Progress as:
|
|
||||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||||
/// 1000=vg-member-added/vc-contact-confirm received
|
#[serde(rename_all = "camelCase")]
|
||||||
progress: usize,
|
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||||
},
|
|
||||||
|
|
||||||
/// The connectivity to the server changed.
|
/// The connectivity to the server changed.
|
||||||
/// This means that you should refresh the connectivity view
|
/// This means that you should refresh the connectivity view
|
||||||
@@ -347,37 +236,17 @@ pub enum EventType {
|
|||||||
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
WebxdcStatusUpdate {
|
WebxdcStatusUpdate {
|
||||||
/// Message ID.
|
|
||||||
msg_id: u32,
|
msg_id: u32,
|
||||||
|
|
||||||
/// Status update ID.
|
|
||||||
status_update_serial: u32,
|
status_update_serial: u32,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Data received over an ephemeral peer channel.
|
/// Data received over an ephemeral peer channel.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
WebxdcRealtimeData {
|
WebxdcRealtimeData { msg_id: u32, data: Vec<u8> },
|
||||||
/// Message ID.
|
|
||||||
msg_id: u32,
|
|
||||||
|
|
||||||
/// Realtime data.
|
|
||||||
data: Vec<u8>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Advertisement received over an ephemeral peer channel.
|
|
||||||
/// This can be used by bots to initiate peer-to-peer communication from their side.
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
WebxdcRealtimeAdvertisementReceived {
|
|
||||||
/// Message ID of the webxdc instance.
|
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Inform that a message containing a webxdc instance has been deleted
|
/// Inform that a message containing a webxdc instance has been deleted
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
WebxdcInstanceDeleted {
|
WebxdcInstanceDeleted { msg_id: u32 },
|
||||||
/// ID of the deleted message.
|
|
||||||
msg_id: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Tells that the Background fetch was completed (or timed out).
|
/// Tells that the Background fetch was completed (or timed out).
|
||||||
/// This event acts as a marker, when you reach this event you can be sure
|
/// This event acts as a marker, when you reach this event you can be sure
|
||||||
@@ -393,30 +262,7 @@ pub enum EventType {
|
|||||||
/// Inform that a single chat list item changed and needs to be rerendered.
|
/// Inform that a single chat list item changed and needs to be rerendered.
|
||||||
/// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
|
/// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ChatlistItemChanged {
|
ChatlistItemChanged { chat_id: Option<u32> },
|
||||||
/// ID of the changed chat
|
|
||||||
chat_id: Option<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Inform that the list of accounts has changed (an account removed or added or (not yet implemented) the account order changes)
|
|
||||||
///
|
|
||||||
/// This event is only emitted by the account manager
|
|
||||||
AccountsChanged,
|
|
||||||
|
|
||||||
/// Inform that an account property that might be shown in the account list changed, namely:
|
|
||||||
/// - is_configured (see is_configured())
|
|
||||||
/// - displayname
|
|
||||||
/// - selfavatar
|
|
||||||
/// - private_tag
|
|
||||||
///
|
|
||||||
/// This event is emitted from the account whose property changed.
|
|
||||||
AccountsItemChanged,
|
|
||||||
|
|
||||||
/// Inform than some events have been skipped due to event channel overflow.
|
|
||||||
EventChannelOverflow {
|
|
||||||
/// Number of events skipped.
|
|
||||||
n: u64,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CoreEventType> for EventType {
|
impl From<CoreEventType> for EventType {
|
||||||
@@ -448,30 +294,6 @@ impl From<CoreEventType> for EventType {
|
|||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
contact_id: contact_id.to_u32(),
|
contact_id: contact_id.to_u32(),
|
||||||
},
|
},
|
||||||
CoreEventType::IncomingReaction {
|
|
||||||
chat_id,
|
|
||||||
contact_id,
|
|
||||||
msg_id,
|
|
||||||
reaction,
|
|
||||||
} => IncomingReaction {
|
|
||||||
chat_id: chat_id.to_u32(),
|
|
||||||
contact_id: contact_id.to_u32(),
|
|
||||||
msg_id: msg_id.to_u32(),
|
|
||||||
reaction: reaction.as_str().to_string(),
|
|
||||||
},
|
|
||||||
CoreEventType::IncomingWebxdcNotify {
|
|
||||||
chat_id,
|
|
||||||
contact_id,
|
|
||||||
msg_id,
|
|
||||||
text,
|
|
||||||
href,
|
|
||||||
} => IncomingWebxdcNotify {
|
|
||||||
chat_id: chat_id.to_u32(),
|
|
||||||
contact_id: contact_id.to_u32(),
|
|
||||||
msg_id: msg_id.to_u32(),
|
|
||||||
text,
|
|
||||||
href,
|
|
||||||
},
|
|
||||||
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
@@ -505,9 +327,6 @@ impl From<CoreEventType> for EventType {
|
|||||||
timer: timer.to_u32(),
|
timer: timer.to_u32(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CoreEventType::ChatDeleted { chat_id } => ChatDeleted {
|
|
||||||
chat_id: chat_id.to_u32(),
|
|
||||||
},
|
|
||||||
CoreEventType::ContactsChanged(contact) => ContactsChanged {
|
CoreEventType::ContactsChanged(contact) => ContactsChanged {
|
||||||
contact_id: contact.map(|c| c.to_u32()),
|
contact_id: contact.map(|c| c.to_u32()),
|
||||||
},
|
},
|
||||||
@@ -551,11 +370,6 @@ impl From<CoreEventType> for EventType {
|
|||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
data,
|
data,
|
||||||
},
|
},
|
||||||
CoreEventType::WebxdcRealtimeAdvertisementReceived { msg_id } => {
|
|
||||||
WebxdcRealtimeAdvertisementReceived {
|
|
||||||
msg_id: msg_id.to_u32(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
@@ -564,12 +378,6 @@ impl From<CoreEventType> for EventType {
|
|||||||
chat_id: chat_id.map(|id| id.to_u32()),
|
chat_id: chat_id.map(|id| id.to_u32()),
|
||||||
},
|
},
|
||||||
CoreEventType::ChatlistChanged => ChatlistChanged,
|
CoreEventType::ChatlistChanged => ChatlistChanged,
|
||||||
CoreEventType::EventChannelOverflow { n } => EventChannelOverflow { n },
|
|
||||||
CoreEventType::AccountsChanged => AccountsChanged,
|
|
||||||
CoreEventType::AccountsItemChanged => AccountsItemChanged,
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
#[cfg(test)]
|
|
||||||
_ => unreachable!("This is just to silence a rust_analyzer false-positive"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use deltachat::login_param as dc;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use yerpc::TypeDef;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EnteredServerLoginParam {
|
|
||||||
/// Server hostname or IP address.
|
|
||||||
pub server: String,
|
|
||||||
|
|
||||||
/// Server port.
|
|
||||||
///
|
|
||||||
/// 0 if not specified.
|
|
||||||
pub port: u16,
|
|
||||||
|
|
||||||
/// Socket security.
|
|
||||||
pub security: Socket,
|
|
||||||
|
|
||||||
/// Username.
|
|
||||||
///
|
|
||||||
/// Empty string if not specified.
|
|
||||||
pub user: String,
|
|
||||||
|
|
||||||
/// Password.
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<dc::EnteredServerLoginParam> for EnteredServerLoginParam {
|
|
||||||
fn from(param: dc::EnteredServerLoginParam) -> Self {
|
|
||||||
Self {
|
|
||||||
server: param.server,
|
|
||||||
port: param.port,
|
|
||||||
security: param.security.into(),
|
|
||||||
user: param.user,
|
|
||||||
password: param.password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EnteredServerLoginParam> for dc::EnteredServerLoginParam {
|
|
||||||
fn from(param: EnteredServerLoginParam) -> Self {
|
|
||||||
Self {
|
|
||||||
server: param.server,
|
|
||||||
port: param.port,
|
|
||||||
security: param.security.into(),
|
|
||||||
user: param.user,
|
|
||||||
password: param.password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Login parameters entered by the user.
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct EnteredLoginParam {
|
|
||||||
/// Email address.
|
|
||||||
pub addr: String,
|
|
||||||
|
|
||||||
/// IMAP settings.
|
|
||||||
pub imap: EnteredServerLoginParam,
|
|
||||||
|
|
||||||
/// SMTP settings.
|
|
||||||
pub smtp: EnteredServerLoginParam,
|
|
||||||
|
|
||||||
/// TLS options: whether to allow invalid certificates and/or
|
|
||||||
/// invalid hostnames
|
|
||||||
pub certificate_checks: EnteredCertificateChecks,
|
|
||||||
|
|
||||||
/// If true, login via OAUTH2 (not recommended anymore)
|
|
||||||
pub oauth2: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<dc::EnteredLoginParam> for EnteredLoginParam {
|
|
||||||
fn from(param: dc::EnteredLoginParam) -> Self {
|
|
||||||
Self {
|
|
||||||
addr: param.addr,
|
|
||||||
imap: param.imap.into(),
|
|
||||||
smtp: param.smtp.into(),
|
|
||||||
certificate_checks: param.certificate_checks.into(),
|
|
||||||
oauth2: param.oauth2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(param: EnteredLoginParam) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
addr: param.addr,
|
|
||||||
imap: param.imap.into(),
|
|
||||||
smtp: param.smtp.into(),
|
|
||||||
certificate_checks: param.certificate_checks.into(),
|
|
||||||
oauth2: param.oauth2,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum Socket {
|
|
||||||
/// Unspecified socket security, select automatically.
|
|
||||||
Automatic,
|
|
||||||
|
|
||||||
/// TLS connection.
|
|
||||||
Ssl,
|
|
||||||
|
|
||||||
/// STARTTLS connection.
|
|
||||||
Starttls,
|
|
||||||
|
|
||||||
/// No TLS, plaintext connection.
|
|
||||||
Plain,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<dc::Socket> for Socket {
|
|
||||||
fn from(value: dc::Socket) -> Self {
|
|
||||||
match value {
|
|
||||||
dc::Socket::Automatic => Self::Automatic,
|
|
||||||
dc::Socket::Ssl => Self::Ssl,
|
|
||||||
dc::Socket::Starttls => Self::Starttls,
|
|
||||||
dc::Socket::Plain => Self::Plain,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Socket> for dc::Socket {
|
|
||||||
fn from(value: Socket) -> Self {
|
|
||||||
match value {
|
|
||||||
Socket::Automatic => Self::Automatic,
|
|
||||||
Socket::Ssl => Self::Ssl,
|
|
||||||
Socket::Starttls => Self::Starttls,
|
|
||||||
Socket::Plain => Self::Plain,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum EnteredCertificateChecks {
|
|
||||||
/// `Automatic` means that provider database setting should be taken.
|
|
||||||
/// If there is no provider database setting for certificate checks,
|
|
||||||
/// check certificates strictly.
|
|
||||||
Automatic,
|
|
||||||
|
|
||||||
/// Ensure that TLS certificate is valid for the server hostname.
|
|
||||||
Strict,
|
|
||||||
|
|
||||||
/// Accept certificates that are expired, self-signed
|
|
||||||
/// or otherwise not valid for the server hostname.
|
|
||||||
AcceptInvalidCertificates,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<dc::EnteredCertificateChecks> for EnteredCertificateChecks {
|
|
||||||
fn from(value: dc::EnteredCertificateChecks) -> Self {
|
|
||||||
match value {
|
|
||||||
dc::EnteredCertificateChecks::Automatic => Self::Automatic,
|
|
||||||
dc::EnteredCertificateChecks::Strict => Self::Strict,
|
|
||||||
dc::EnteredCertificateChecks::AcceptInvalidCertificates => {
|
|
||||||
Self::AcceptInvalidCertificates
|
|
||||||
}
|
|
||||||
dc::EnteredCertificateChecks::AcceptInvalidCertificates2 => {
|
|
||||||
Self::AcceptInvalidCertificates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EnteredCertificateChecks> for dc::EnteredCertificateChecks {
|
|
||||||
fn from(value: EnteredCertificateChecks) -> Self {
|
|
||||||
match value {
|
|
||||||
EnteredCertificateChecks::Automatic => Self::Automatic,
|
|
||||||
EnteredCertificateChecks::Strict => Self::Strict,
|
|
||||||
EnteredCertificateChecks::AcceptInvalidCertificates => Self::AcceptInvalidCertificates,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::api::VcardContact;
|
use crate::api::VcardContact;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use deltachat::chat::Chat;
|
use deltachat::chat::Chat;
|
||||||
@@ -39,8 +37,6 @@ pub struct MessageObject {
|
|||||||
|
|
||||||
text: String,
|
text: String,
|
||||||
|
|
||||||
is_edited: bool,
|
|
||||||
|
|
||||||
/// Check if a message has a POI location bound to it.
|
/// Check if a message has a POI location bound to it.
|
||||||
/// These locations are also returned by `get_locations` method.
|
/// These locations are also returned by `get_locations` method.
|
||||||
/// The UI may decide to display a special icon beside such messages.
|
/// The UI may decide to display a special icon beside such messages.
|
||||||
@@ -89,14 +85,8 @@ pub struct MessageObject {
|
|||||||
|
|
||||||
webxdc_info: Option<WebxdcMessageInfo>,
|
webxdc_info: Option<WebxdcMessageInfo>,
|
||||||
|
|
||||||
webxdc_href: Option<String>,
|
|
||||||
|
|
||||||
download_state: DownloadState,
|
download_state: DownloadState,
|
||||||
|
|
||||||
original_msg_id: Option<u32>,
|
|
||||||
|
|
||||||
saved_message_id: Option<u32>,
|
|
||||||
|
|
||||||
reactions: Option<JSONRPCReactions>,
|
reactions: Option<JSONRPCReactions>,
|
||||||
|
|
||||||
vcard_contact: Option<VcardContact>,
|
vcard_contact: Option<VcardContact>,
|
||||||
@@ -112,9 +102,6 @@ enum MessageQuote {
|
|||||||
WithMessage {
|
WithMessage {
|
||||||
text: String,
|
text: String,
|
||||||
message_id: u32,
|
message_id: u32,
|
||||||
/// The quoted message does not always belong
|
|
||||||
/// to the same chat, e.g. when "Reply Privately" is used.
|
|
||||||
chat_id: u32,
|
|
||||||
author_display_name: String,
|
author_display_name: String,
|
||||||
author_display_color: String,
|
author_display_color: String,
|
||||||
override_sender_name: Option<String>,
|
override_sender_name: Option<String>,
|
||||||
@@ -125,10 +112,8 @@ enum MessageQuote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageObject {
|
impl MessageObject {
|
||||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Option<Self>> {
|
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
||||||
let Some(message) = Message::load_from_db_optional(context, msg_id).await? else {
|
let message = Message::load_from_db(context, msg_id).await?;
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let sender_contact = Contact::get_by_id(context, message.get_from_id())
|
let sender_contact = Contact::get_by_id(context, message.get_from_id())
|
||||||
.await
|
.await
|
||||||
@@ -158,7 +143,6 @@ impl MessageObject {
|
|||||||
Some(MessageQuote::WithMessage {
|
Some(MessageQuote::WithMessage {
|
||||||
text: quoted_text,
|
text: quoted_text,
|
||||||
message_id: quote.get_id().to_u32(),
|
message_id: quote.get_id().to_u32(),
|
||||||
chat_id: quote.get_chat_id().to_u32(),
|
|
||||||
author_display_name: quote_author.get_display_name().to_owned(),
|
author_display_name: quote_author.get_display_name().to_owned(),
|
||||||
author_display_color: color_int_to_hex_string(quote_author.get_color()),
|
author_display_color: color_int_to_hex_string(quote_author.get_color()),
|
||||||
override_sender_name: quote.get_override_sender_name(),
|
override_sender_name: quote.get_override_sender_name(),
|
||||||
@@ -199,14 +183,13 @@ impl MessageObject {
|
|||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let message_object = MessageObject {
|
Ok(MessageObject {
|
||||||
id: msg_id.to_u32(),
|
id: msg_id.to_u32(),
|
||||||
chat_id: message.get_chat_id().to_u32(),
|
chat_id: message.get_chat_id().to_u32(),
|
||||||
from_id: message.get_from_id().to_u32(),
|
from_id: message.get_from_id().to_u32(),
|
||||||
quote,
|
quote,
|
||||||
parent_id,
|
parent_id,
|
||||||
text: message.get_text(),
|
text: message.get_text(),
|
||||||
is_edited: message.is_edited(),
|
|
||||||
has_location: message.has_location(),
|
has_location: message.has_location(),
|
||||||
has_html: message.has_html(),
|
has_html: message.has_html(),
|
||||||
view_type: message.get_viewtype().into(),
|
view_type: message.get_viewtype().into(),
|
||||||
@@ -256,27 +239,12 @@ impl MessageObject {
|
|||||||
file_name: message.get_filename(),
|
file_name: message.get_filename(),
|
||||||
webxdc_info,
|
webxdc_info,
|
||||||
|
|
||||||
// On a WebxdcInfoMessage this might include a hash holding
|
|
||||||
// information about a specific position or state in a webxdc app
|
|
||||||
webxdc_href: message.get_webxdc_href(),
|
|
||||||
|
|
||||||
download_state,
|
download_state,
|
||||||
|
|
||||||
original_msg_id: message
|
|
||||||
.get_original_msg_id(context)
|
|
||||||
.await?
|
|
||||||
.map(|id| id.to_u32()),
|
|
||||||
|
|
||||||
saved_message_id: message
|
|
||||||
.get_saved_msg_id(context)
|
|
||||||
.await?
|
|
||||||
.map(|id| id.to_u32()),
|
|
||||||
|
|
||||||
reactions,
|
reactions,
|
||||||
|
|
||||||
vcard_contact: vcard_contacts.first().cloned(),
|
vcard_contact: vcard_contacts.first().cloned(),
|
||||||
};
|
})
|
||||||
Ok(Some(message_object))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,9 +264,6 @@ pub enum MessageViewtype {
|
|||||||
Gif,
|
Gif,
|
||||||
|
|
||||||
/// Message containing a sticker, similar to image.
|
/// Message containing a sticker, similar to image.
|
||||||
/// NB: When sending, the message viewtype may be changed to `Image` by some heuristics like
|
|
||||||
/// checking for transparent pixels. Use `Message::force_sticker()` to disable them.
|
|
||||||
///
|
|
||||||
/// If possible, the ui should display the image without borders in a transparent way.
|
/// If possible, the ui should display the image without borders in a transparent way.
|
||||||
/// A click on a sticker will offer to install the sticker set in some future.
|
/// A click on a sticker will offer to install the sticker set in some future.
|
||||||
Sticker,
|
Sticker,
|
||||||
@@ -525,7 +490,6 @@ pub struct MessageSearchResult {
|
|||||||
author_name: String,
|
author_name: String,
|
||||||
author_color: String,
|
author_color: String,
|
||||||
author_id: u32,
|
author_id: u32,
|
||||||
chat_id: u32,
|
|
||||||
chat_profile_image: Option<String>,
|
chat_profile_image: Option<String>,
|
||||||
chat_color: String,
|
chat_color: String,
|
||||||
chat_name: String,
|
chat_name: String,
|
||||||
@@ -565,7 +529,6 @@ impl MessageSearchResult {
|
|||||||
author_name,
|
author_name,
|
||||||
author_color: color_int_to_hex_string(sender.get_color()),
|
author_color: color_int_to_hex_string(sender.get_color()),
|
||||||
author_id: sender.id.to_u32(),
|
author_id: sender.id.to_u32(),
|
||||||
chat_id: chat.id.to_u32(),
|
|
||||||
chat_name: chat.get_name().to_owned(),
|
chat_name: chat.get_name().to_owned(),
|
||||||
chat_color,
|
chat_color,
|
||||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||||
@@ -612,12 +575,9 @@ pub struct MessageData {
|
|||||||
pub html: Option<String>,
|
pub html: Option<String>,
|
||||||
pub viewtype: Option<MessageViewtype>,
|
pub viewtype: Option<MessageViewtype>,
|
||||||
pub file: Option<String>,
|
pub file: Option<String>,
|
||||||
pub filename: Option<String>,
|
|
||||||
pub location: Option<(f64, f64)>,
|
pub location: Option<(f64, f64)>,
|
||||||
pub override_sender_name: Option<String>,
|
pub override_sender_name: Option<String>,
|
||||||
/// Quoted message id. Takes preference over `quoted_text` (see below).
|
|
||||||
pub quoted_message_id: Option<u32>,
|
pub quoted_message_id: Option<u32>,
|
||||||
pub quoted_text: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageData {
|
impl MessageData {
|
||||||
@@ -637,27 +597,22 @@ impl MessageData {
|
|||||||
message.set_override_sender_name(self.override_sender_name);
|
message.set_override_sender_name(self.override_sender_name);
|
||||||
}
|
}
|
||||||
if let Some(file) = self.file {
|
if let Some(file) = self.file {
|
||||||
message.set_file_and_deduplicate(
|
message.set_file(file, None);
|
||||||
context,
|
|
||||||
Path::new(&file),
|
|
||||||
self.filename.as_deref(),
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
if let Some((latitude, longitude)) = self.location {
|
if let Some((latitude, longitude)) = self.location {
|
||||||
message.set_location(latitude, longitude);
|
message.set_location(latitude, longitude);
|
||||||
}
|
}
|
||||||
if let Some(id) = self.quoted_message_id {
|
if let Some(id) = self.quoted_message_id {
|
||||||
let quoted_message = Message::load_from_db(context, MsgId::new(id))
|
|
||||||
.await
|
|
||||||
.context("Failed to load quoted message")?;
|
|
||||||
message
|
message
|
||||||
.set_quote(context, Some("ed_message))
|
.set_quote(
|
||||||
|
context,
|
||||||
|
Some(
|
||||||
|
&Message::load_from_db(context, MsgId::new(id))
|
||||||
.await
|
.await
|
||||||
.context("Failed to set quote")?;
|
.context("message to quote could not be loaded")?,
|
||||||
} else if let Some(text) = self.quoted_text {
|
),
|
||||||
let protect = false;
|
)
|
||||||
message.set_quote_text(Some((text, protect)));
|
.await?;
|
||||||
}
|
}
|
||||||
Ok(message)
|
Ok(message)
|
||||||
}
|
}
|
||||||
@@ -680,7 +635,7 @@ pub struct MessageInfo {
|
|||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
rfc724_mid: String,
|
rfc724_mid: String,
|
||||||
server_urls: Vec<String>,
|
server_urls: Vec<String>,
|
||||||
hop_info: String,
|
hop_info: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageInfo {
|
impl MessageInfo {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod basic_message;
|
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
pub mod chat_list;
|
pub mod chat_list;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod location;
|
pub mod location;
|
||||||
pub mod login_param;
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod provider_info;
|
pub mod provider_info;
|
||||||
pub mod qr;
|
pub mod qr;
|
||||||
|
|||||||
@@ -6,165 +6,77 @@ use typescript_type_def::TypeDef;
|
|||||||
#[serde(rename = "Qr", rename_all = "camelCase")]
|
#[serde(rename = "Qr", rename_all = "camelCase")]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
pub enum QrObject {
|
pub enum QrObject {
|
||||||
/// Ask the user whether to verify the contact.
|
|
||||||
///
|
|
||||||
/// If the user agrees, pass this QR code to [`crate::securejoin::join_securejoin`].
|
|
||||||
AskVerifyContact {
|
AskVerifyContact {
|
||||||
/// ID of the contact.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
/// Invite number.
|
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
/// Authentication code.
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
/// Ask the user whether to join the group.
|
|
||||||
AskVerifyGroup {
|
AskVerifyGroup {
|
||||||
/// Group name.
|
|
||||||
grpname: String,
|
grpname: String,
|
||||||
/// Group ID.
|
|
||||||
grpid: String,
|
grpid: String,
|
||||||
/// ID of the contact.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
/// Invite number.
|
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
/// Authentication code.
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
/// Contact fingerprint is verified.
|
|
||||||
///
|
|
||||||
/// Ask the user if they want to start chatting.
|
|
||||||
FprOk {
|
FprOk {
|
||||||
/// Contact ID.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
},
|
},
|
||||||
/// Scanned fingerprint does not match the last seen fingerprint.
|
|
||||||
FprMismatch {
|
FprMismatch {
|
||||||
/// Contact ID.
|
|
||||||
contact_id: Option<u32>,
|
contact_id: Option<u32>,
|
||||||
},
|
},
|
||||||
/// The scanned QR code contains a fingerprint but no e-mail address.
|
|
||||||
FprWithoutAddr {
|
FprWithoutAddr {
|
||||||
/// Key fingerprint.
|
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
},
|
},
|
||||||
/// Ask the user if they want to create an account on the given domain.
|
|
||||||
Account {
|
Account {
|
||||||
/// Server domain name.
|
|
||||||
domain: String,
|
domain: String,
|
||||||
},
|
},
|
||||||
/// Provides a backup that can be retrieved using iroh-net based backup transfer protocol.
|
Backup {
|
||||||
Backup2 {
|
ticket: String,
|
||||||
/// Authentication token.
|
|
||||||
auth_token: String,
|
|
||||||
/// Iroh node address.
|
|
||||||
node_addr: String,
|
|
||||||
},
|
},
|
||||||
BackupTooNew {},
|
|
||||||
/// Ask the user if they want to use the given service for video chats.
|
|
||||||
WebrtcInstance {
|
WebrtcInstance {
|
||||||
domain: String,
|
domain: String,
|
||||||
instance_pattern: String,
|
instance_pattern: String,
|
||||||
},
|
},
|
||||||
/// Ask the user if they want to use the given proxy.
|
|
||||||
///
|
|
||||||
/// Note that HTTP(S) URLs without a path
|
|
||||||
/// and query parameters are treated as HTTP(S) proxy URL.
|
|
||||||
/// UI may want to still offer to open the URL
|
|
||||||
/// in the browser if QR code contents
|
|
||||||
/// starts with `http://` or `https://`
|
|
||||||
/// and the QR code was not scanned from
|
|
||||||
/// the proxy configuration screen.
|
|
||||||
Proxy {
|
|
||||||
/// Proxy URL.
|
|
||||||
///
|
|
||||||
/// This is the URL that is going to be added.
|
|
||||||
url: String,
|
|
||||||
/// Host extracted from the URL to display in the UI.
|
|
||||||
host: String,
|
|
||||||
/// Port extracted from the URL to display in the UI.
|
|
||||||
port: u16,
|
|
||||||
},
|
|
||||||
/// Contact address is scanned.
|
|
||||||
///
|
|
||||||
/// Optionally, a draft message could be provided.
|
|
||||||
/// Ask the user if they want to start chatting.
|
|
||||||
Addr {
|
Addr {
|
||||||
/// Contact ID.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
/// Draft message.
|
|
||||||
draft: Option<String>,
|
draft: Option<String>,
|
||||||
},
|
},
|
||||||
/// URL scanned.
|
|
||||||
///
|
|
||||||
/// Ask the user if they want to open a browser or copy the URL to clipboard.
|
|
||||||
Url {
|
Url {
|
||||||
url: String,
|
url: String,
|
||||||
},
|
},
|
||||||
/// Text scanned.
|
|
||||||
///
|
|
||||||
/// Ask the user if they want to copy the text to clipboard.
|
|
||||||
Text {
|
Text {
|
||||||
text: String,
|
text: String,
|
||||||
},
|
},
|
||||||
/// Ask the user if they want to withdraw their own QR code.
|
|
||||||
WithdrawVerifyContact {
|
WithdrawVerifyContact {
|
||||||
/// Contact ID.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
/// Invite number.
|
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
/// Authentication code.
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
/// Ask the user if they want to withdraw their own group invite QR code.
|
|
||||||
WithdrawVerifyGroup {
|
WithdrawVerifyGroup {
|
||||||
/// Group name.
|
|
||||||
grpname: String,
|
grpname: String,
|
||||||
/// Group ID.
|
|
||||||
grpid: String,
|
grpid: String,
|
||||||
/// Contact ID.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
/// Invite number.
|
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
/// Authentication code.
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
/// Ask the user if they want to revive their own QR code.
|
|
||||||
ReviveVerifyContact {
|
ReviveVerifyContact {
|
||||||
/// Contact ID.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
/// Invite number.
|
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
/// Authentication code.
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
/// Ask the user if they want to revive their own group invite QR code.
|
|
||||||
ReviveVerifyGroup {
|
ReviveVerifyGroup {
|
||||||
/// Contact ID.
|
|
||||||
grpname: String,
|
grpname: String,
|
||||||
/// Group ID.
|
|
||||||
grpid: String,
|
grpid: String,
|
||||||
/// Contact ID.
|
|
||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
/// Fingerprint of the contact key as scanned from the QR code.
|
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
/// Invite number.
|
|
||||||
invitenumber: String,
|
invitenumber: String,
|
||||||
/// Authentication code.
|
|
||||||
authcode: String,
|
authcode: String,
|
||||||
},
|
},
|
||||||
/// `dclogin:` scheme parameters.
|
|
||||||
///
|
|
||||||
/// Ask the user if they want to login with the email address.
|
|
||||||
Login {
|
Login {
|
||||||
address: String,
|
address: String,
|
||||||
},
|
},
|
||||||
@@ -217,14 +129,9 @@ impl From<Qr> for QrObject {
|
|||||||
}
|
}
|
||||||
Qr::FprWithoutAddr { fingerprint } => QrObject::FprWithoutAddr { fingerprint },
|
Qr::FprWithoutAddr { fingerprint } => QrObject::FprWithoutAddr { fingerprint },
|
||||||
Qr::Account { domain } => QrObject::Account { domain },
|
Qr::Account { domain } => QrObject::Account { domain },
|
||||||
Qr::Backup2 {
|
Qr::Backup { ticket } => QrObject::Backup {
|
||||||
ref node_addr,
|
ticket: ticket.to_string(),
|
||||||
auth_token,
|
|
||||||
} => QrObject::Backup2 {
|
|
||||||
node_addr: serde_json::to_string(node_addr).unwrap_or_default(),
|
|
||||||
auth_token,
|
|
||||||
},
|
},
|
||||||
Qr::BackupTooNew {} => QrObject::BackupTooNew {},
|
|
||||||
Qr::WebrtcInstance {
|
Qr::WebrtcInstance {
|
||||||
domain,
|
domain,
|
||||||
instance_pattern,
|
instance_pattern,
|
||||||
@@ -232,7 +139,6 @@ impl From<Qr> for QrObject {
|
|||||||
domain,
|
domain,
|
||||||
instance_pattern,
|
instance_pattern,
|
||||||
},
|
},
|
||||||
Qr::Proxy { url, host, port } => QrObject::Proxy { url, host, port },
|
|
||||||
Qr::Addr { contact_id, draft } => {
|
Qr::Addr { contact_id, draft } => {
|
||||||
let contact_id = contact_id.to_u32();
|
let contact_id = contact_id.to_u32();
|
||||||
QrObject::Addr { contact_id, draft }
|
QrObject::Addr { contact_id, draft }
|
||||||
|
|||||||
@@ -35,14 +35,6 @@ pub struct WebxdcMessageInfo {
|
|||||||
source_code_url: Option<String>,
|
source_code_url: Option<String>,
|
||||||
/// True if full internet access should be granted to the app.
|
/// True if full internet access should be granted to the app.
|
||||||
internet_access: bool,
|
internet_access: bool,
|
||||||
/// Address to be used for `window.webxdc.selfAddr` in JS land.
|
|
||||||
self_addr: String,
|
|
||||||
/// Milliseconds to wait before calling `sendUpdate()` again since the last call.
|
|
||||||
/// Should be exposed to `window.sendUpdateInterval` in JS land.
|
|
||||||
send_update_interval: usize,
|
|
||||||
/// Maximum number of bytes accepted for a serialized update object.
|
|
||||||
/// Should be exposed to `window.sendUpdateMaxSize` in JS land.
|
|
||||||
send_update_max_size: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebxdcMessageInfo {
|
impl WebxdcMessageInfo {
|
||||||
@@ -57,11 +49,7 @@ impl WebxdcMessageInfo {
|
|||||||
document,
|
document,
|
||||||
summary,
|
summary,
|
||||||
source_code_url,
|
source_code_url,
|
||||||
request_integration: _,
|
|
||||||
internet_access,
|
internet_access,
|
||||||
self_addr,
|
|
||||||
send_update_interval,
|
|
||||||
send_update_max_size,
|
|
||||||
} = message.get_webxdc_info(context).await?;
|
} = message.get_webxdc_info(context).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -71,9 +59,6 @@ impl WebxdcMessageInfo {
|
|||||||
summary: maybe_empty_string_to_option(summary),
|
summary: maybe_empty_string_to_option(summary),
|
||||||
source_code_url: maybe_empty_string_to_option(source_code_url),
|
source_code_url: maybe_empty_string_to_option(source_code_url),
|
||||||
internet_access,
|
internet_access,
|
||||||
self_addr,
|
|
||||||
send_update_interval,
|
|
||||||
send_update_max_size,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
|
|
||||||
#![cfg_attr(not(test), forbid(clippy::string_slice))]
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub use yerpc;
|
pub use yerpc;
|
||||||
|
|
||||||
@@ -85,7 +83,7 @@ mod tests {
|
|||||||
assert_eq!(result, response.to_owned());
|
assert_eq!(result, response.to_owned());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let request = r#"{"jsonrpc":"2.0","method":"batch_set_config","id":2,"params":[1,{"addr":"","mail_user":"","mail_pw":"","mail_server":"","mail_port":"","mail_security":"","imap_certificate_checks":"","send_user":"","send_pw":"","send_server":"","send_port":"","send_security":"","smtp_certificate_checks":""}]}"#;
|
let request = r#"{"jsonrpc":"2.0","method":"batch_set_config","id":2,"params":[1,{"addr":"","mail_user":"","mail_pw":"","mail_server":"","mail_port":"","mail_security":"","imap_certificate_checks":"","send_user":"","send_pw":"","send_server":"","send_port":"","send_security":"","smtp_certificate_checks":"","socks5_enabled":"0","socks5_host":"","socks5_port":"","socks5_user":"","socks5_password":""}]}"#;
|
||||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":null}"#;
|
let response = r#"{"jsonrpc":"2.0","id":2,"result":null}"#;
|
||||||
session.handle_incoming(request).await;
|
session.handle_incoming(request).await;
|
||||||
let result = receiver.recv().await?;
|
let result = receiver.recv().await?;
|
||||||
|
|||||||
47
deltachat-jsonrpc/src/webserver.rs
Normal file
47
deltachat-jsonrpc/src/webserver.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#![recursion_limit = "256"]
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use axum::{extract::ws::WebSocketUpgrade, response::Response, routing::get, Extension, Router};
|
||||||
|
use yerpc::axum::handle_ws_rpc;
|
||||||
|
use yerpc::{RpcClient, RpcSession};
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
use api::{Accounts, CommandApi};
|
||||||
|
|
||||||
|
const DEFAULT_PORT: u16 = 20808;
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
|
async fn main() -> Result<(), std::io::Error> {
|
||||||
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||||
|
|
||||||
|
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "./accounts".to_string());
|
||||||
|
let port = std::env::var("DC_PORT")
|
||||||
|
.map(|port| port.parse::<u16>().expect("DC_PORT must be a number"))
|
||||||
|
.unwrap_or(DEFAULT_PORT);
|
||||||
|
log::info!("Starting with accounts directory `{path}`.");
|
||||||
|
let writable = true;
|
||||||
|
let accounts = Accounts::new(PathBuf::from(&path), writable).await.unwrap();
|
||||||
|
let state = CommandApi::new(accounts);
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/ws", get(handler))
|
||||||
|
.layer(Extension(state.clone()));
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
state.accounts.write().await.start_io().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||||
|
log::info!("JSON-RPC WebSocket server listening on {}", addr);
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handler(ws: WebSocketUpgrade, Extension(api): Extension<CommandApi>) -> Response {
|
||||||
|
let (client, out_receiver) = RpcClient::new();
|
||||||
|
let session = RpcSession::new(client.clone(), api.clone());
|
||||||
|
handle_ws_rpc(ws, out_receiver, session).await
|
||||||
|
}
|
||||||
56
deltachat-jsonrpc/typescript/example.html
Normal file
56
deltachat-jsonrpc/typescript/example.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>DeltaChat JSON-RPC example</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: monospace;
|
||||||
|
background: black;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3fr 1fr;
|
||||||
|
grid-template-areas: "a a" "b c";
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
grid-area: a;
|
||||||
|
color: white;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
#header a {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#main {
|
||||||
|
grid-area: b;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
#main h2,
|
||||||
|
#main h3 {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
#side {
|
||||||
|
grid-area: c;
|
||||||
|
color: #777;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="module" src="dist/example.bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>DeltaChat JSON-RPC example</h1>
|
||||||
|
<div class="grid">
|
||||||
|
<div id="header"></div>
|
||||||
|
<div id="main"></div>
|
||||||
|
<div id="side"><h2>log</h2></div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Tip: open the dev console and use the client with
|
||||||
|
<code>window.client</code>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
109
deltachat-jsonrpc/typescript/example/example.ts
Normal file
109
deltachat-jsonrpc/typescript/example/example.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { DcEvent, DeltaChat } from "../deltachat.js";
|
||||||
|
|
||||||
|
var SELECTED_ACCOUNT = 0;
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", (_event) => {
|
||||||
|
(window as any).selectDeltaAccount = (id: string) => {
|
||||||
|
SELECTED_ACCOUNT = Number(id);
|
||||||
|
window.dispatchEvent(new Event("account-changed"));
|
||||||
|
};
|
||||||
|
console.log("launch run script...");
|
||||||
|
run().catch((err) => console.error("run failed", err));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const $main = document.getElementById("main")!;
|
||||||
|
const $side = document.getElementById("side")!;
|
||||||
|
const $head = document.getElementById("header")!;
|
||||||
|
|
||||||
|
const client = new DeltaChat("ws://localhost:20808/ws");
|
||||||
|
|
||||||
|
(window as any).client = client.rpc;
|
||||||
|
|
||||||
|
client.on("ALL", (accountId, event) => {
|
||||||
|
onIncomingEvent(accountId, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("account-changed", async (_event: Event) => {
|
||||||
|
listChatsForSelectedAccount();
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([loadAccountsInHeader(), listChatsForSelectedAccount()]);
|
||||||
|
|
||||||
|
async function loadAccountsInHeader() {
|
||||||
|
console.log("load accounts");
|
||||||
|
const accounts = await client.rpc.getAllAccounts();
|
||||||
|
console.log("accounts loaded", accounts);
|
||||||
|
for (const account of accounts) {
|
||||||
|
if (account.kind === "Configured") {
|
||||||
|
write(
|
||||||
|
$head,
|
||||||
|
`<a href="#" onclick="selectDeltaAccount(${account.id})">
|
||||||
|
${account.id}: ${account.addr!}
|
||||||
|
</a> `
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
write(
|
||||||
|
$head,
|
||||||
|
`<a href="#">
|
||||||
|
${account.id}: (unconfigured)
|
||||||
|
</a> `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listChatsForSelectedAccount() {
|
||||||
|
clear($main);
|
||||||
|
const selectedAccount = SELECTED_ACCOUNT;
|
||||||
|
const info = await client.rpc.getAccountInfo(selectedAccount);
|
||||||
|
if (info.kind !== "Configured") {
|
||||||
|
return write($main, "Account is not configured");
|
||||||
|
}
|
||||||
|
write($main, `<h2>${info.addr!}</h2>`);
|
||||||
|
const chats = await client.rpc.getChatlistEntries(
|
||||||
|
selectedAccount,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
for (const chatId of chats) {
|
||||||
|
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
|
||||||
|
write($main, `<h3>${chat.name}</h3>`);
|
||||||
|
const messageIds = await client.rpc.getMessageIds(
|
||||||
|
selectedAccount,
|
||||||
|
chatId,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const messages = await client.rpc.getMessages(
|
||||||
|
selectedAccount,
|
||||||
|
messageIds
|
||||||
|
);
|
||||||
|
for (const [_messageId, message] of Object.entries(messages)) {
|
||||||
|
if (message.kind === "message") write($main, `<p>${message.text}</p>`);
|
||||||
|
else write($main, `<p>loading error: ${message.error}</p>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onIncomingEvent(accountId: number, event: DcEvent) {
|
||||||
|
write(
|
||||||
|
$side,
|
||||||
|
`
|
||||||
|
<p class="message">
|
||||||
|
[<strong>${event.kind}</strong> on account ${accountId}]<br>
|
||||||
|
<em>f1:</em> ${JSON.stringify(
|
||||||
|
Object.assign({}, event, { kind: undefined })
|
||||||
|
)}
|
||||||
|
</p>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(el: HTMLElement, html: string) {
|
||||||
|
el.innerHTML += html;
|
||||||
|
}
|
||||||
|
function clear(el: HTMLElement) {
|
||||||
|
el.innerHTML = "";
|
||||||
|
}
|
||||||
29
deltachat-jsonrpc/typescript/example/node-add-account.js
Normal file
29
deltachat-jsonrpc/typescript/example/node-add-account.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { DeltaChat } from "../dist/deltachat.js";
|
||||||
|
|
||||||
|
run().catch(console.error);
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const delta = new DeltaChat("ws://localhost:20808/ws");
|
||||||
|
delta.on("event", (event) => {
|
||||||
|
console.log("event", event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = process.argv[2];
|
||||||
|
const password = process.argv[3];
|
||||||
|
if (!email || !password)
|
||||||
|
throw new Error(
|
||||||
|
"USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>"
|
||||||
|
);
|
||||||
|
console.log(`creating account for ${email}`);
|
||||||
|
const id = await delta.rpc.addAccount();
|
||||||
|
console.log(`created account id ${id}`);
|
||||||
|
await delta.rpc.setConfig(id, "addr", email);
|
||||||
|
await delta.rpc.setConfig(id, "mail_pw", password);
|
||||||
|
console.log("configuration updated");
|
||||||
|
await delta.rpc.configure(id);
|
||||||
|
console.log("account configured!");
|
||||||
|
|
||||||
|
const accounts = await delta.rpc.getAllAccounts();
|
||||||
|
console.log("accounts", accounts);
|
||||||
|
console.log("waiting for events...");
|
||||||
|
}
|
||||||
14
deltachat-jsonrpc/typescript/example/node-demo.js
Normal file
14
deltachat-jsonrpc/typescript/example/node-demo.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { DeltaChat } from "../dist/deltachat.js";
|
||||||
|
|
||||||
|
run().catch(console.error);
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const delta = new DeltaChat();
|
||||||
|
delta.on("event", (event) => {
|
||||||
|
console.log("event", event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const accounts = await delta.rpc.getAllAccounts();
|
||||||
|
console.log("accounts", accounts);
|
||||||
|
console.log("waiting for events...");
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@deltachat/tiny-emitter": "3.0.0",
|
"@deltachat/tiny-emitter": "3.0.0",
|
||||||
"isomorphic-ws": "^4.0.1",
|
"isomorphic-ws": "^4.0.1",
|
||||||
"yerpc": "^0.6.2"
|
"yerpc": "^0.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.2.21",
|
"@types/chai": "^4.2.21",
|
||||||
@@ -32,16 +32,16 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"main": "dist/deltachat.js",
|
"main": "dist/deltachat.js",
|
||||||
"name": "@deltachat/jsonrpc-client",
|
"name": "@deltachat/jsonrpc-client",
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/chatmail/core.git"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "run-s generate-bindings extract-constants build:tsc build:bundle build:cjs",
|
"build": "run-s generate-bindings extract-constants build:tsc build:bundle build:cjs",
|
||||||
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
|
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
|
||||||
"build:cjs": "esbuild --format=cjs --bundle --packages=external dist/deltachat.js --outfile=dist/deltachat.cjs",
|
"build:cjs": "esbuild --format=cjs --bundle --packages=external dist/deltachat.js --outfile=dist/deltachat.cjs",
|
||||||
"build:tsc": "tsc",
|
"build:tsc": "tsc",
|
||||||
"docs": "typedoc --out docs deltachat.ts",
|
"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 .",
|
||||||
"extract-constants": "node ./scripts/generate-constants.js",
|
"extract-constants": "node ./scripts/generate-constants.js",
|
||||||
"generate-bindings": "cargo test",
|
"generate-bindings": "cargo test",
|
||||||
"prettier:check": "prettier --check .",
|
"prettier:check": "prettier --check .",
|
||||||
@@ -54,5 +54,5 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "dist/deltachat.d.ts",
|
"types": "dist/deltachat.d.ts",
|
||||||
"version": "1.157.3"
|
"version": "1.138.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as T from "../generated/types.js";
|
|||||||
import { EventType } from "../generated/types.js";
|
import { EventType } from "../generated/types.js";
|
||||||
import * as RPC from "../generated/jsonrpc.js";
|
import * as RPC from "../generated/jsonrpc.js";
|
||||||
import { RawClient } from "../generated/client.js";
|
import { RawClient } from "../generated/client.js";
|
||||||
import { BaseTransport, Request } from "yerpc";
|
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
||||||
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
||||||
|
|
||||||
type Events = { ALL: (accountId: number, event: EventType) => void } & {
|
type Events = { ALL: (accountId: number, event: EventType) => void } & {
|
||||||
@@ -74,6 +74,34 @@ export class BaseDeltaChat<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Opts = {
|
||||||
|
url: string;
|
||||||
|
startEventLoop: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_OPTS: Opts = {
|
||||||
|
url: "ws://localhost:20808/ws",
|
||||||
|
startEventLoop: true,
|
||||||
|
};
|
||||||
|
export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
||||||
|
opts: Opts;
|
||||||
|
close() {
|
||||||
|
this.transport.close();
|
||||||
|
}
|
||||||
|
constructor(opts?: Opts | string) {
|
||||||
|
if (typeof opts === "string") {
|
||||||
|
opts = { ...DEFAULT_OPTS, url: opts };
|
||||||
|
} else if (opts) {
|
||||||
|
opts = { ...DEFAULT_OPTS, ...opts };
|
||||||
|
} else {
|
||||||
|
opts = { ...DEFAULT_OPTS };
|
||||||
|
}
|
||||||
|
const transport = new WebsocketTransport(opts.url);
|
||||||
|
super(transport, opts.startEventLoop);
|
||||||
|
this.opts = opts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
||||||
close() {}
|
close() {}
|
||||||
constructor(input: any, output: any, startEventLoop: boolean) {
|
constructor(input: any, output: any, startEventLoop: boolean) {
|
||||||
|
|||||||
@@ -86,7 +86,10 @@ describe("online tests", function () {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
||||||
const eventPromise = waitForEvent(dc, "IncomingMsg", accountId2);
|
const eventPromise = Promise.race([
|
||||||
|
waitForEvent(dc, "MsgsChanged", accountId2),
|
||||||
|
waitForEvent(dc, "IncomingMsg", accountId2),
|
||||||
|
]);
|
||||||
|
|
||||||
await dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello");
|
await dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello");
|
||||||
const { chatId: chatIdOnAccountB } = await eventPromise;
|
const { chatId: chatIdOnAccountB } = await eventPromise;
|
||||||
@@ -116,7 +119,10 @@ describe("online tests", function () {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
||||||
const eventPromise = waitForEvent(dc, "IncomingMsg", accountId2);
|
const eventPromise = Promise.race([
|
||||||
|
waitForEvent(dc, "MsgsChanged", accountId2),
|
||||||
|
waitForEvent(dc, "IncomingMsg", accountId2),
|
||||||
|
]);
|
||||||
dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello2");
|
dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello2");
|
||||||
// wait for message from A
|
// wait for message from A
|
||||||
console.log("wait for message from A");
|
console.log("wait for message from A");
|
||||||
@@ -137,7 +143,10 @@ describe("online tests", function () {
|
|||||||
);
|
);
|
||||||
expect(message.text).equal("Hello2");
|
expect(message.text).equal("Hello2");
|
||||||
// Send message back from B to A
|
// Send message back from B to A
|
||||||
const eventPromise2 = waitForEvent(dc, "IncomingMsg", accountId1);
|
const eventPromise2 = Promise.race([
|
||||||
|
waitForEvent(dc, "MsgsChanged", accountId1),
|
||||||
|
waitForEvent(dc, "IncomingMsg", accountId1),
|
||||||
|
]);
|
||||||
dc.rpc.miscSendTextMessage(accountId2, chatId, "super secret message");
|
dc.rpc.miscSendTextMessage(accountId2, chatId, "super secret message");
|
||||||
// Check if answer arrives at A and if it is encrypted
|
// Check if answer arrives at A and if it is encrypted
|
||||||
await eventPromise2;
|
await eventPromise2;
|
||||||
|
|||||||
@@ -15,6 +15,6 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"isolatedModules": true
|
"isolatedModules": true
|
||||||
},
|
},
|
||||||
"include": ["*.ts", "test/*.ts"],
|
"include": ["*.ts", "example/*.ts", "test/*.ts"],
|
||||||
"compileOnSave": false
|
"compileOnSave": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,6 @@ impl Ratelimit {
|
|||||||
pub fn until_can_send(&self) -> Duration {
|
pub fn until_can_send(&self) -> Duration {
|
||||||
self.until_can_send_at(SystemTime::now())
|
self.until_can_send_at(SystemTime::now())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns minimum possible update interval in milliseconds.
|
|
||||||
pub fn update_interval(&self) -> usize {
|
|
||||||
(self.window.as_millis() as f64 / self.quota) as usize
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -107,7 +102,6 @@ mod tests {
|
|||||||
|
|
||||||
let mut ratelimit = Ratelimit::new_at(Duration::new(60, 0), 3.0, now);
|
let mut ratelimit = Ratelimit::new_at(Duration::new(60, 0), 3.0, now);
|
||||||
assert!(ratelimit.can_send_at(now));
|
assert!(ratelimit.can_send_at(now));
|
||||||
assert_eq!(ratelimit.update_interval(), 20_000);
|
|
||||||
|
|
||||||
// Send burst of 3 messages.
|
// Send burst of 3 messages.
|
||||||
ratelimit.send_at(now);
|
ratelimit.send_at(now);
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-repl"
|
name = "deltachat-repl"
|
||||||
version = "1.157.3"
|
version = "1.138.5"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/chatmail/core"
|
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
ansi_term = "0.12.1"
|
||||||
deltachat = { workspace = true, features = ["internals"]}
|
anyhow = "1"
|
||||||
dirs = "6"
|
deltachat = { path = "..", features = ["internals"]}
|
||||||
log = { workspace = true }
|
dirs = "5"
|
||||||
nu-ansi-term = { workspace = true }
|
log = "0.4.21"
|
||||||
qr2term = "0.3.3"
|
pretty_env_logger = "0.5"
|
||||||
rusqlite = { workspace = true }
|
rusqlite = "0.31"
|
||||||
rustyline = "15"
|
rustyline = "14"
|
||||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
tokio = { version = "1.37.0", features = ["fs", "rt-multi-thread", "macros"] }
|
||||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored"]
|
default = ["vendored"]
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ use deltachat::mimeparser::SystemMessage;
|
|||||||
use deltachat::peer_channels::{send_webxdc_realtime_advertisement, send_webxdc_realtime_data};
|
use deltachat::peer_channels::{send_webxdc_realtime_advertisement, send_webxdc_realtime_data};
|
||||||
use deltachat::peerstate::*;
|
use deltachat::peerstate::*;
|
||||||
use deltachat::qr::*;
|
use deltachat::qr::*;
|
||||||
use deltachat::qr_code_generator::create_qr_svg;
|
|
||||||
use deltachat::reaction::send_reaction;
|
use deltachat::reaction::send_reaction;
|
||||||
use deltachat::receive_imf::*;
|
use deltachat::receive_imf::*;
|
||||||
use deltachat::sql;
|
use deltachat::sql;
|
||||||
@@ -92,7 +91,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
|||||||
context.emit_msgs_changed_without_ids();
|
context.emit_msgs_changed_without_ids();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn poke_eml_file(context: &Context, filename: &Path) -> Result<()> {
|
async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<()> {
|
||||||
let data = read_file(context, filename).await?;
|
let data = read_file(context, filename).await?;
|
||||||
|
|
||||||
if let Err(err) = receive_imf(context, &data, false).await {
|
if let Err(err) = receive_imf(context, &data, false).await {
|
||||||
@@ -126,7 +125,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
|||||||
real_spec = rs.unwrap();
|
real_spec = rs.unwrap();
|
||||||
}
|
}
|
||||||
if let Some(suffix) = get_filesuffix_lc(&real_spec) {
|
if let Some(suffix) = get_filesuffix_lc(&real_spec) {
|
||||||
if suffix == "eml" && poke_eml_file(context, Path::new(&real_spec)).await.is_ok() {
|
if suffix == "eml" && poke_eml_file(context, &real_spec).await.is_ok() {
|
||||||
read_cnt += 1
|
read_cnt += 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -140,10 +139,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
|||||||
if name.ends_with(".eml") {
|
if name.ends_with(".eml") {
|
||||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||||
println!("Import: {path_plus_name}");
|
println!("Import: {path_plus_name}");
|
||||||
if poke_eml_file(context, Path::new(&path_plus_name))
|
if poke_eml_file(context, path_plus_name).await.is_ok() {
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
read_cnt += 1
|
read_cnt += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,6 +339,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
receive-backup <qr>\n\
|
receive-backup <qr>\n\
|
||||||
export-keys\n\
|
export-keys\n\
|
||||||
import-keys\n\
|
import-keys\n\
|
||||||
|
export-setup\n\
|
||||||
poke [<eml-file>|<folder>|<addr> <key-file>]\n\
|
poke [<eml-file>|<folder>|<addr> <key-file>]\n\
|
||||||
reset <flags>\n\
|
reset <flags>\n\
|
||||||
stop\n\
|
stop\n\
|
||||||
@@ -359,7 +356,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
configure\n\
|
configure\n\
|
||||||
connect\n\
|
connect\n\
|
||||||
disconnect\n\
|
disconnect\n\
|
||||||
fetch\n\
|
|
||||||
connectivity\n\
|
connectivity\n\
|
||||||
maybenetwork\n\
|
maybenetwork\n\
|
||||||
housekeeping\n\
|
housekeeping\n\
|
||||||
@@ -429,7 +425,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
checkqr <qr-content>\n\
|
checkqr <qr-content>\n\
|
||||||
joinqr <qr-content>\n\
|
joinqr <qr-content>\n\
|
||||||
setqr <qr-content>\n\
|
setqr <qr-content>\n\
|
||||||
createqrsvg <qr-content>\n\
|
|
||||||
providerinfo <addr>\n\
|
providerinfo <addr>\n\
|
||||||
fileinfo <file>\n\
|
fileinfo <file>\n\
|
||||||
estimatedeletion <seconds>\n\
|
estimatedeletion <seconds>\n\
|
||||||
@@ -492,9 +487,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
}
|
}
|
||||||
"send-backup" => {
|
"send-backup" => {
|
||||||
let provider = BackupProvider::prepare(&context).await?;
|
let provider = BackupProvider::prepare(&context).await?;
|
||||||
let qr = format_backup(&provider.qr())?;
|
let qr = provider.qr();
|
||||||
println!("QR code: {}", qr);
|
println!("QR code: {}", format_backup(&qr)?);
|
||||||
qr2term::print_qr(qr.as_str())?;
|
|
||||||
provider.await?;
|
provider.await?;
|
||||||
}
|
}
|
||||||
"receive-backup" => {
|
"receive-backup" => {
|
||||||
@@ -510,6 +504,17 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
"import-keys" => {
|
"import-keys" => {
|
||||||
imex(&context, ImexMode::ImportSelfKeys, arg1.as_ref(), None).await?;
|
imex(&context, ImexMode::ImportSelfKeys, arg1.as_ref(), None).await?;
|
||||||
}
|
}
|
||||||
|
"export-setup" => {
|
||||||
|
let setup_code = create_setup_code(&context);
|
||||||
|
let file_name = blobdir.join("autocrypt-setup-message.html");
|
||||||
|
let file_content = render_setup_file(&context, &setup_code).await?;
|
||||||
|
fs::write(&file_name, file_content).await?;
|
||||||
|
println!(
|
||||||
|
"Setup message written to: {}\nSetup code: {}",
|
||||||
|
file_name.display(),
|
||||||
|
&setup_code,
|
||||||
|
);
|
||||||
|
}
|
||||||
"poke" => {
|
"poke" => {
|
||||||
ensure!(poke_spec(&context, Some(arg1)).await, "Poke failed");
|
ensure!(poke_spec(&context, Some(arg1)).await, "Poke failed");
|
||||||
}
|
}
|
||||||
@@ -942,7 +947,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
} else {
|
} else {
|
||||||
Viewtype::File
|
Viewtype::File
|
||||||
});
|
});
|
||||||
msg.set_file_and_deduplicate(&context, Path::new(arg1), None, None)?;
|
msg.set_file(arg1, None);
|
||||||
msg.set_text(arg2.to_string());
|
msg.set_text(arg2.to_string());
|
||||||
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
||||||
}
|
}
|
||||||
@@ -972,7 +977,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
"Arguments <msg-id> <json status update> expected"
|
"Arguments <msg-id> <json status update> expected"
|
||||||
);
|
);
|
||||||
let msg_id = MsgId::new(arg1.parse()?);
|
let msg_id = MsgId::new(arg1.parse()?);
|
||||||
context.send_webxdc_status_update(msg_id, arg2).await?;
|
context
|
||||||
|
.send_webxdc_status_update(msg_id, arg2, "this is a webxdc status update")
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
"videochat" => {
|
"videochat" => {
|
||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
@@ -1005,7 +1012,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
|
|
||||||
if !arg1.is_empty() {
|
if !arg1.is_empty() {
|
||||||
let mut draft = Message::new_text(arg1.to_string());
|
let mut draft = Message::new(Viewtype::Text);
|
||||||
|
draft.set_text(arg1.to_string());
|
||||||
sel_chat
|
sel_chat
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1028,7 +1036,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
!arg1.is_empty(),
|
!arg1.is_empty(),
|
||||||
"Please specify text to add as device message."
|
"Please specify text to add as device message."
|
||||||
);
|
);
|
||||||
let mut msg = Message::new_text(arg1.to_string());
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
|
msg.set_text(arg1.to_string());
|
||||||
chat::add_device_msg(&context, None, Some(&mut msg)).await?;
|
chat::add_device_msg(&context, None, Some(&mut msg)).await?;
|
||||||
}
|
}
|
||||||
"listmedia" => {
|
"listmedia" => {
|
||||||
@@ -1250,19 +1259,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
Err(err) => println!("Cannot set config from QR code: {err:?}"),
|
Err(err) => println!("Cannot set config from QR code: {err:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"createqrsvg" => {
|
|
||||||
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
|
|
||||||
let svg = create_qr_svg(arg1)?;
|
|
||||||
let file = dirs::home_dir().unwrap_or_default().join("qr.svg");
|
|
||||||
fs::write(&file, svg).await?;
|
|
||||||
println!("{file:#?} written.");
|
|
||||||
}
|
|
||||||
"providerinfo" => {
|
"providerinfo" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <addr> missing.");
|
ensure!(!arg1.is_empty(), "Argument <addr> missing.");
|
||||||
let proxy_enabled = context
|
let socks5_enabled = context
|
||||||
.get_config_bool(config::Config::ProxyEnabled)
|
.get_config_bool(config::Config::Socks5Enabled)
|
||||||
.await?;
|
.await?;
|
||||||
match provider::get_provider_info(&context, arg1, proxy_enabled).await {
|
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
|
||||||
Some(info) => {
|
Some(info) => {
|
||||||
println!("Information for provider belonging to {arg1}:");
|
println!("Information for provider belonging to {arg1}:");
|
||||||
println!("status: {}", info.status as u32);
|
println!("status: {}", info.status as u32);
|
||||||
@@ -1281,7 +1283,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
"fileinfo" => {
|
"fileinfo" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||||
|
|
||||||
if let Ok(buf) = read_file(&context, Path::new(arg1)).await {
|
if let Ok(buf) = read_file(&context, &arg1).await {
|
||||||
let (width, height) = get_filemeta(&buf)?;
|
let (width, height) = get_filemeta(&buf)?;
|
||||||
println!("width={width}, height={height}");
|
println!("width={width}, height={height}");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
extern crate deltachat;
|
extern crate deltachat;
|
||||||
|
|
||||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use ansi_term::Color;
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use deltachat::chat::ChatId;
|
use deltachat::chat::ChatId;
|
||||||
use deltachat::config;
|
use deltachat::config;
|
||||||
@@ -19,10 +22,9 @@ use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
|||||||
use deltachat::securejoin::*;
|
use deltachat::securejoin::*;
|
||||||
use deltachat::EventType;
|
use deltachat::EventType;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use nu_ansi_term::Color;
|
|
||||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::highlight::{CmdKind as HighlightCmdKind, Highlighter, MatchingBracketHighlighter};
|
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
|
||||||
use rustyline::hint::{Hinter, HistoryHinter};
|
use rustyline::hint::{Hinter, HistoryHinter};
|
||||||
use rustyline::validate::Validator;
|
use rustyline::validate::Validator;
|
||||||
use rustyline::{
|
use rustyline::{
|
||||||
@@ -30,7 +32,6 @@ use rustyline::{
|
|||||||
};
|
};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
use tracing_subscriber::EnvFilter;
|
|
||||||
|
|
||||||
mod cmdline;
|
mod cmdline;
|
||||||
use self::cmdline::*;
|
use self::cmdline::*;
|
||||||
@@ -150,7 +151,7 @@ impl Completer for DcHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const IMEX_COMMANDS: [&str; 13] = [
|
const IMEX_COMMANDS: [&str; 14] = [
|
||||||
"initiate-key-transfer",
|
"initiate-key-transfer",
|
||||||
"get-setupcodebegin",
|
"get-setupcodebegin",
|
||||||
"continue-key-transfer",
|
"continue-key-transfer",
|
||||||
@@ -161,12 +162,13 @@ const IMEX_COMMANDS: [&str; 13] = [
|
|||||||
"receive-backup",
|
"receive-backup",
|
||||||
"export-keys",
|
"export-keys",
|
||||||
"import-keys",
|
"import-keys",
|
||||||
|
"export-setup",
|
||||||
"poke",
|
"poke",
|
||||||
"reset",
|
"reset",
|
||||||
"stop",
|
"stop",
|
||||||
];
|
];
|
||||||
|
|
||||||
const DB_COMMANDS: [&str; 11] = [
|
const DB_COMMANDS: [&str; 10] = [
|
||||||
"info",
|
"info",
|
||||||
"set",
|
"set",
|
||||||
"get",
|
"get",
|
||||||
@@ -174,7 +176,6 @@ const DB_COMMANDS: [&str; 11] = [
|
|||||||
"configure",
|
"configure",
|
||||||
"connect",
|
"connect",
|
||||||
"disconnect",
|
"disconnect",
|
||||||
"fetch",
|
|
||||||
"connectivity",
|
"connectivity",
|
||||||
"maybenetwork",
|
"maybenetwork",
|
||||||
"housekeeping",
|
"housekeeping",
|
||||||
@@ -240,13 +241,12 @@ const CONTACT_COMMANDS: [&str; 9] = [
|
|||||||
"unblock",
|
"unblock",
|
||||||
"listblocked",
|
"listblocked",
|
||||||
];
|
];
|
||||||
const MISC_COMMANDS: [&str; 12] = [
|
const MISC_COMMANDS: [&str; 11] = [
|
||||||
"getqr",
|
"getqr",
|
||||||
"getqrsvg",
|
"getqrsvg",
|
||||||
"getbadqr",
|
"getbadqr",
|
||||||
"checkqr",
|
"checkqr",
|
||||||
"joinqr",
|
"joinqr",
|
||||||
"createqrsvg",
|
|
||||||
"fileinfo",
|
"fileinfo",
|
||||||
"clear",
|
"clear",
|
||||||
"exit",
|
"exit",
|
||||||
@@ -298,8 +298,8 @@ impl Highlighter for DcHelper {
|
|||||||
self.highlighter.highlight(line, pos)
|
self.highlighter.highlight(line, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highlight_char(&self, line: &str, pos: usize, kind: HighlightCmdKind) -> bool {
|
fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
|
||||||
self.highlighter.highlight_char(line, pos, kind)
|
self.highlighter.highlight_char(line, pos, forced)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,9 +417,6 @@ async fn handle_cmd(
|
|||||||
"disconnect" => {
|
"disconnect" => {
|
||||||
ctx.stop_io().await;
|
ctx.stop_io().await;
|
||||||
}
|
}
|
||||||
"fetch" => {
|
|
||||||
ctx.background_fetch().await?;
|
|
||||||
}
|
|
||||||
"configure" => {
|
"configure" => {
|
||||||
ctx.configure().await?;
|
ctx.configure().await?;
|
||||||
}
|
}
|
||||||
@@ -449,7 +446,12 @@ async fn handle_cmd(
|
|||||||
qr.replace_range(12..22, "0000000000")
|
qr.replace_range(12..22, "0000000000")
|
||||||
}
|
}
|
||||||
println!("{qr}");
|
println!("{qr}");
|
||||||
qr2term::print_qr(qr.as_str())?;
|
let output = Command::new("qrencode")
|
||||||
|
.args(["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
io::stdout().write_all(&output.stdout).unwrap();
|
||||||
|
io::stderr().write_all(&output.stderr).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"getqrsvg" => {
|
"getqrsvg" => {
|
||||||
@@ -481,10 +483,9 @@ async fn handle_cmd(
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Error> {
|
async fn main() -> Result<(), Error> {
|
||||||
tracing_subscriber::fmt()
|
pretty_env_logger::formatted_timed_builder()
|
||||||
.with_env_filter(
|
.parse_default_env()
|
||||||
EnvFilter::from_default_env().add_directive("deltachat_repl=info".parse()?),
|
.filter_module("deltachat_repl", log::LevelFilter::Info)
|
||||||
)
|
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let args = std::env::args().collect();
|
let args = std::env::args().collect();
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ $ pip install .
|
|||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||||
2. Install tox `pip install -U tox`
|
2. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||||
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
|
||||||
|
|
||||||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "deltachat-rpc-client"
|
name = "deltachat-rpc-client"
|
||||||
version = "1.157.3"
|
version = "1.138.5"
|
||||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
@@ -17,13 +17,13 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
|
||||||
"Programming Language :: Python :: 3.13",
|
|
||||||
"Topic :: Communications :: Chat",
|
"Topic :: Communications :: Chat",
|
||||||
"Topic :: Communications :: Email"
|
"Topic :: Communications :: Email"
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.8"
|
dependencies = [
|
||||||
|
"imap-tools",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
deltachat_rpc_client = [
|
deltachat_rpc_client = [
|
||||||
|
|||||||
@@ -26,12 +26,9 @@ class Account:
|
|||||||
def _rpc(self) -> "Rpc":
|
def _rpc(self) -> "Rpc":
|
||||||
return self.manager.rpc
|
return self.manager.rpc
|
||||||
|
|
||||||
def wait_for_event(self, event_type=None) -> AttrDict:
|
def wait_for_event(self) -> AttrDict:
|
||||||
"""Wait until the next event and return it."""
|
"""Wait until the next event and return it."""
|
||||||
while True:
|
return AttrDict(self._rpc.wait_for_event(self.id))
|
||||||
next_event = AttrDict(self._rpc.wait_for_event(self.id))
|
|
||||||
if event_type is None or next_event.kind == event_type:
|
|
||||||
return next_event
|
|
||||||
|
|
||||||
def clear_all_events(self):
|
def clear_all_events(self):
|
||||||
"""Removes all queued-up events for a given account. Useful for tests."""
|
"""Removes all queued-up events for a given account. Useful for tests."""
|
||||||
@@ -41,16 +38,6 @@ class Account:
|
|||||||
"""Remove the account."""
|
"""Remove the account."""
|
||||||
self._rpc.remove_account(self.id)
|
self._rpc.remove_account(self.id)
|
||||||
|
|
||||||
def clone(self) -> "Account":
|
|
||||||
"""Clone given account.
|
|
||||||
This uses backup-transfer via iroh, i.e. the 'Add second device' feature."""
|
|
||||||
future = self._rpc.provide_backup.future(self.id)
|
|
||||||
qr = self._rpc.get_backup_qr(self.id)
|
|
||||||
new_account = self.manager.add_account()
|
|
||||||
new_account._rpc.get_backup(new_account.id, qr)
|
|
||||||
future()
|
|
||||||
return new_account
|
|
||||||
|
|
||||||
def start_io(self) -> None:
|
def start_io(self) -> None:
|
||||||
"""Start the account I/O."""
|
"""Start the account I/O."""
|
||||||
self._rpc.start_io(self.id)
|
self._rpc.start_io(self.id)
|
||||||
@@ -96,10 +83,6 @@ class Account:
|
|||||||
return self.get_config("selfavatar")
|
return self.get_config("selfavatar")
|
||||||
|
|
||||||
def check_qr(self, qr):
|
def check_qr(self, qr):
|
||||||
"""Parse QR code contents.
|
|
||||||
|
|
||||||
This function takes the raw text scanned
|
|
||||||
and checks what can be done with it."""
|
|
||||||
return self._rpc.check_qr(self.id, qr)
|
return self._rpc.check_qr(self.id, qr)
|
||||||
|
|
||||||
def set_config_from_qr(self, qr: str):
|
def set_config_from_qr(self, qr: str):
|
||||||
@@ -113,7 +96,10 @@ class Account:
|
|||||||
def bring_online(self):
|
def bring_online(self):
|
||||||
"""Start I/O and wait until IMAP becomes IDLE."""
|
"""Start I/O and wait until IMAP becomes IDLE."""
|
||||||
self.start_io()
|
self.start_io()
|
||||||
self.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
while True:
|
||||||
|
event = self.wait_for_event()
|
||||||
|
if event.kind == EventType.IMAP_INBOX_IDLE:
|
||||||
|
break
|
||||||
|
|
||||||
def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
|
def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
|
||||||
"""Create a new Contact or return an existing one.
|
"""Create a new Contact or return an existing one.
|
||||||
@@ -132,28 +118,11 @@ class Account:
|
|||||||
obj = obj.get_snapshot().address
|
obj = obj.get_snapshot().address
|
||||||
return Contact(self, self._rpc.create_contact(self.id, obj, name))
|
return Contact(self, self._rpc.create_contact(self.id, obj, name))
|
||||||
|
|
||||||
def make_vcard(self, contacts: list[Contact]) -> str:
|
|
||||||
"""Create vCard with the given contacts."""
|
|
||||||
assert all(contact.account == self for contact in contacts)
|
|
||||||
contact_ids = [contact.id for contact in contacts]
|
|
||||||
return self._rpc.make_vcard(self.id, contact_ids)
|
|
||||||
|
|
||||||
def import_vcard(self, vcard: str) -> list[Contact]:
|
|
||||||
"""Import vCard.
|
|
||||||
|
|
||||||
Return created or modified contacts in the order they appear in vCard."""
|
|
||||||
contact_ids = self._rpc.import_vcard_contents(self.id, vcard)
|
|
||||||
return [Contact(self, contact_id) for contact_id in contact_ids]
|
|
||||||
|
|
||||||
def create_chat(self, account: "Account") -> Chat:
|
def create_chat(self, account: "Account") -> Chat:
|
||||||
vcard = account.self_contact.make_vcard()
|
addr = account.get_config("addr")
|
||||||
[contact] = self.import_vcard(vcard)
|
contact = self.create_contact(addr)
|
||||||
return contact.create_chat()
|
return contact.create_chat()
|
||||||
|
|
||||||
def get_device_chat(self) -> Chat:
|
|
||||||
"""Return device chat."""
|
|
||||||
return self.device_contact.create_chat()
|
|
||||||
|
|
||||||
def get_contact_by_id(self, contact_id: int) -> Contact:
|
def get_contact_by_id(self, contact_id: int) -> Contact:
|
||||||
"""Return Contact instance for the given contact ID."""
|
"""Return Contact instance for the given contact ID."""
|
||||||
return Contact(self, contact_id)
|
return Contact(self, contact_id)
|
||||||
@@ -214,11 +183,6 @@ class Account:
|
|||||||
"""This account's identity as a Contact."""
|
"""This account's identity as a Contact."""
|
||||||
return Contact(self, SpecialContactId.SELF)
|
return Contact(self, SpecialContactId.SELF)
|
||||||
|
|
||||||
@property
|
|
||||||
def device_contact(self) -> Chat:
|
|
||||||
"""This account's device contact."""
|
|
||||||
return Contact(self, SpecialContactId.DEVICE)
|
|
||||||
|
|
||||||
def get_chatlist(
|
def get_chatlist(
|
||||||
self,
|
self,
|
||||||
query: Optional[str] = None,
|
query: Optional[str] = None,
|
||||||
@@ -286,16 +250,12 @@ class Account:
|
|||||||
"""
|
"""
|
||||||
return Chat(self, self._rpc.secure_join(self.id, qrdata))
|
return Chat(self, self._rpc.secure_join(self.id, qrdata))
|
||||||
|
|
||||||
def get_qr_code(self) -> str:
|
def get_qr_code(self) -> tuple[str, str]:
|
||||||
"""Get Setup-Contact QR Code text.
|
"""Get Setup-Contact QR Code text and SVG data.
|
||||||
|
|
||||||
This data needs to be transferred to another Delta Chat account
|
this data needs to be transferred to another Delta Chat account
|
||||||
in a second channel, typically used by mobiles with QRcode-show + scan UX.
|
in a second channel, typically used by mobiles with QRcode-show + scan UX.
|
||||||
"""
|
"""
|
||||||
return self._rpc.get_chat_securejoin_qr_code(self.id, None)
|
|
||||||
|
|
||||||
def get_qr_code_svg(self) -> tuple[str, str]:
|
|
||||||
"""Get Setup-Contact QR code text and SVG."""
|
|
||||||
return self._rpc.get_chat_securejoin_qr_code_svg(self.id, None)
|
return self._rpc.get_chat_securejoin_qr_code_svg(self.id, None)
|
||||||
|
|
||||||
def get_message_by_id(self, msg_id: int) -> Message:
|
def get_message_by_id(self, msg_id: int) -> Message:
|
||||||
@@ -332,21 +292,10 @@ class Account:
|
|||||||
|
|
||||||
def wait_for_incoming_msg_event(self):
|
def wait_for_incoming_msg_event(self):
|
||||||
"""Wait for incoming message event and return it."""
|
"""Wait for incoming message event and return it."""
|
||||||
return self.wait_for_event(EventType.INCOMING_MSG)
|
while True:
|
||||||
|
event = self.wait_for_event()
|
||||||
def wait_for_msgs_changed_event(self):
|
if event.kind == EventType.INCOMING_MSG:
|
||||||
"""Wait for messages changed event and return it."""
|
return event
|
||||||
return self.wait_for_event(EventType.MSGS_CHANGED)
|
|
||||||
|
|
||||||
def wait_for_msgs_noticed_event(self):
|
|
||||||
"""Wait for messages noticed event and return it."""
|
|
||||||
return self.wait_for_event(EventType.MSGS_NOTICED)
|
|
||||||
|
|
||||||
def wait_for_incoming_msg(self):
|
|
||||||
"""Wait for incoming message and return it.
|
|
||||||
|
|
||||||
Consumes all events before the next incoming message event."""
|
|
||||||
return self.get_message_by_id(self.wait_for_incoming_msg_event().msg_id)
|
|
||||||
|
|
||||||
def wait_for_securejoin_inviter_success(self):
|
def wait_for_securejoin_inviter_success(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -361,7 +310,10 @@ class Account:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def wait_for_reactions_changed(self):
|
def wait_for_reactions_changed(self):
|
||||||
return self.wait_for_event(EventType.REACTIONS_CHANGED)
|
while True:
|
||||||
|
event = self.wait_for_event()
|
||||||
|
if event.kind == EventType.REACTIONS_CHANGED:
|
||||||
|
return event
|
||||||
|
|
||||||
def get_fresh_messages_in_arrival_order(self) -> list[Message]:
|
def get_fresh_messages_in_arrival_order(self) -> list[Message]:
|
||||||
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
||||||
@@ -390,7 +342,3 @@ class Account:
|
|||||||
"""Import keys."""
|
"""Import keys."""
|
||||||
passphrase = "" # Importing passphrase-protected keys is currently not supported.
|
passphrase = "" # Importing passphrase-protected keys is currently not supported.
|
||||||
self._rpc.import_self_keys(self.id, str(path), passphrase)
|
self._rpc.import_self_keys(self.id, str(path), passphrase)
|
||||||
|
|
||||||
def initiate_autocrypt_key_transfer(self) -> None:
|
|
||||||
"""Send Autocrypt Setup Message."""
|
|
||||||
return self._rpc.initiate_autocrypt_key_transfer(self.id)
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from tempfile import NamedTemporaryFile
|
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
|
|
||||||
from ._utils import AttrDict
|
from ._utils import AttrDict
|
||||||
@@ -96,11 +95,7 @@ class Chat:
|
|||||||
"""Return encryption info for this chat."""
|
"""Return encryption info for this chat."""
|
||||||
return self._rpc.get_chat_encryption_info(self.account.id, self.id)
|
return self._rpc.get_chat_encryption_info(self.account.id, self.id)
|
||||||
|
|
||||||
def get_qr_code(self) -> str:
|
def get_qr_code(self) -> tuple[str, str]:
|
||||||
"""Get Join-Group QR code text."""
|
|
||||||
return self._rpc.get_chat_securejoin_qr_code(self.account.id, self.id)
|
|
||||||
|
|
||||||
def get_qr_code_svg(self) -> tuple[str, str]:
|
|
||||||
"""Get Join-Group QR code text and SVG data."""
|
"""Get Join-Group QR code text and SVG data."""
|
||||||
return self._rpc.get_chat_securejoin_qr_code_svg(self.account.id, self.id)
|
return self._rpc.get_chat_securejoin_qr_code_svg(self.account.id, self.id)
|
||||||
|
|
||||||
@@ -124,7 +119,6 @@ class Chat:
|
|||||||
html: Optional[str] = None,
|
html: Optional[str] = None,
|
||||||
viewtype: Optional[ViewType] = None,
|
viewtype: Optional[ViewType] = None,
|
||||||
file: Optional[str] = None,
|
file: Optional[str] = None,
|
||||||
filename: Optional[str] = None,
|
|
||||||
location: Optional[tuple[float, float]] = None,
|
location: Optional[tuple[float, float]] = None,
|
||||||
override_sender_name: Optional[str] = None,
|
override_sender_name: Optional[str] = None,
|
||||||
quoted_msg: Optional[Union[int, Message]] = None,
|
quoted_msg: Optional[Union[int, Message]] = None,
|
||||||
@@ -138,7 +132,6 @@ class Chat:
|
|||||||
"html": html,
|
"html": html,
|
||||||
"viewtype": viewtype,
|
"viewtype": viewtype,
|
||||||
"file": file,
|
"file": file,
|
||||||
"filename": filename,
|
|
||||||
"location": location,
|
"location": location,
|
||||||
"overrideSenderName": override_sender_name,
|
"overrideSenderName": override_sender_name,
|
||||||
"quotedMessageId": quoted_msg,
|
"quotedMessageId": quoted_msg,
|
||||||
@@ -174,14 +167,13 @@ class Chat:
|
|||||||
self,
|
self,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
file: Optional[str] = None,
|
file: Optional[str] = None,
|
||||||
filename: Optional[str] = None,
|
|
||||||
quoted_msg: Optional[int] = None,
|
quoted_msg: Optional[int] = None,
|
||||||
viewtype: Optional[str] = None,
|
viewtype: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set draft message."""
|
"""Set draft message."""
|
||||||
if isinstance(quoted_msg, Message):
|
if isinstance(quoted_msg, Message):
|
||||||
quoted_msg = quoted_msg.id
|
quoted_msg = quoted_msg.id
|
||||||
self._rpc.misc_set_draft(self.account.id, self.id, text, file, filename, quoted_msg, viewtype)
|
self._rpc.misc_set_draft(self.account.id, self.id, text, file, quoted_msg, viewtype)
|
||||||
|
|
||||||
def remove_draft(self) -> None:
|
def remove_draft(self) -> None:
|
||||||
"""Remove draft message."""
|
"""Remove draft message."""
|
||||||
@@ -241,11 +233,6 @@ class Chat:
|
|||||||
contacts = self._rpc.get_chat_contacts(self.account.id, self.id)
|
contacts = self._rpc.get_chat_contacts(self.account.id, self.id)
|
||||||
return [Contact(self.account, contact_id) for contact_id in contacts]
|
return [Contact(self.account, contact_id) for contact_id in contacts]
|
||||||
|
|
||||||
def get_past_contacts(self) -> list[Contact]:
|
|
||||||
"""Get past contacts for this chat."""
|
|
||||||
past_contacts = self._rpc.get_past_chat_contacts(self.account.id, self.id)
|
|
||||||
return [Contact(self.account, contact_id) for contact_id in past_contacts]
|
|
||||||
|
|
||||||
def set_image(self, path: str) -> None:
|
def set_image(self, path: str) -> None:
|
||||||
"""Set profile image of this chat.
|
"""Set profile image of this chat.
|
||||||
|
|
||||||
@@ -278,11 +265,3 @@ class Chat:
|
|||||||
location["message"] = Message(self.account, location.msg_id)
|
location["message"] = Message(self.account, location.msg_id)
|
||||||
locations.append(location)
|
locations.append(location)
|
||||||
return locations
|
return locations
|
||||||
|
|
||||||
def send_contact(self, contact: Contact):
|
|
||||||
"""Send contact to the chat."""
|
|
||||||
vcard = contact.make_vcard()
|
|
||||||
with NamedTemporaryFile(suffix=".vcard") as f:
|
|
||||||
f.write(vcard.encode())
|
|
||||||
f.flush()
|
|
||||||
self._rpc.send_msg(self.account.id, self.id, {"viewtype": ViewType.VCARD, "file": f.name})
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ class EventType(str, Enum):
|
|||||||
REACTIONS_CHANGED = "ReactionsChanged"
|
REACTIONS_CHANGED = "ReactionsChanged"
|
||||||
INCOMING_MSG = "IncomingMsg"
|
INCOMING_MSG = "IncomingMsg"
|
||||||
INCOMING_MSG_BUNCH = "IncomingMsgBunch"
|
INCOMING_MSG_BUNCH = "IncomingMsgBunch"
|
||||||
INCOMING_REACTION = "IncomingReaction"
|
|
||||||
MSGS_NOTICED = "MsgsNoticed"
|
MSGS_NOTICED = "MsgsNoticed"
|
||||||
MSG_DELIVERED = "MsgDelivered"
|
MSG_DELIVERED = "MsgDelivered"
|
||||||
MSG_FAILED = "MsgFailed"
|
MSG_FAILED = "MsgFailed"
|
||||||
@@ -62,11 +61,8 @@ class EventType(str, Enum):
|
|||||||
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
|
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
|
||||||
CHATLIST_CHANGED = "ChatlistChanged"
|
CHATLIST_CHANGED = "ChatlistChanged"
|
||||||
CHATLIST_ITEM_CHANGED = "ChatlistItemChanged"
|
CHATLIST_ITEM_CHANGED = "ChatlistItemChanged"
|
||||||
ACCOUNTS_CHANGED = "AccountsChanged"
|
|
||||||
ACCOUNTS_ITEM_CHANGED = "AccountsItemChanged"
|
|
||||||
CONFIG_SYNCED = "ConfigSynced"
|
CONFIG_SYNCED = "ConfigSynced"
|
||||||
WEBXDC_REALTIME_DATA = "WebxdcRealtimeData"
|
WEBXDC_REALTIME_DATA = "WebxdcRealtimeData"
|
||||||
WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED = "WebxdcRealtimeAdvertisementReceived"
|
|
||||||
|
|
||||||
|
|
||||||
class ChatId(IntEnum):
|
class ChatId(IntEnum):
|
||||||
@@ -119,7 +115,6 @@ class ViewType(str, Enum):
|
|||||||
FILE = "File"
|
FILE = "File"
|
||||||
VIDEOCHAT_INVITATION = "VideochatInvitation"
|
VIDEOCHAT_INVITATION = "VideochatInvitation"
|
||||||
WEBXDC = "Webxdc"
|
WEBXDC = "Webxdc"
|
||||||
VCARD = "Vcard"
|
|
||||||
|
|
||||||
|
|
||||||
class SystemMessageType(str, Enum):
|
class SystemMessageType(str, Enum):
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ class Contact:
|
|||||||
"""Delete contact."""
|
"""Delete contact."""
|
||||||
self._rpc.delete_contact(self.account.id, self.id)
|
self._rpc.delete_contact(self.account.id, self.id)
|
||||||
|
|
||||||
def reset_encryption(self) -> None:
|
|
||||||
"""Reset contact encryption."""
|
|
||||||
self._rpc.reset_contact_encryption(self.account.id, self.id)
|
|
||||||
|
|
||||||
def set_name(self, name: str) -> None:
|
def set_name(self, name: str) -> None:
|
||||||
"""Change the name of this contact."""
|
"""Change the name of this contact."""
|
||||||
self._rpc.change_contact_name(self.account.id, self.id, name)
|
self._rpc.change_contact_name(self.account.id, self.id, name)
|
||||||
@@ -64,6 +60,3 @@ class Contact:
|
|||||||
self.account,
|
self.account,
|
||||||
self._rpc.create_chat_by_contact_id(self.account.id, self.id),
|
self._rpc.create_chat_by_contact_id(self.account.id, self.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_vcard(self) -> str:
|
|
||||||
return self.account.make_vcard([self])
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Internal Python-level IMAP handling used by the tests.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import imaplib
|
import imaplib
|
||||||
@@ -5,13 +9,18 @@ import io
|
|||||||
import pathlib
|
import pathlib
|
||||||
import ssl
|
import ssl
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
import pytest
|
from imap_tools import (
|
||||||
from imap_tools import AND, Header, MailBox, MailMessage, MailMessageFlags, errors
|
AND,
|
||||||
|
Header,
|
||||||
|
MailBox,
|
||||||
|
MailBoxTls,
|
||||||
|
MailMessage,
|
||||||
|
MailMessageFlags,
|
||||||
|
errors,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
from . import Account, const
|
||||||
from deltachat_rpc_client import Account
|
|
||||||
|
|
||||||
FLAGS = b"FLAGS"
|
FLAGS = b"FLAGS"
|
||||||
FETCH = b"FETCH"
|
FETCH = b"FETCH"
|
||||||
@@ -19,8 +28,6 @@ ALL = "1:*"
|
|||||||
|
|
||||||
|
|
||||||
class DirectImap:
|
class DirectImap:
|
||||||
"""Internal Python-level IMAP handling."""
|
|
||||||
|
|
||||||
def __init__(self, account: Account) -> None:
|
def __init__(self, account: Account) -> None:
|
||||||
self.account = account
|
self.account = account
|
||||||
self.logid = account.get_config("displayname") or id(account)
|
self.logid = account.get_config("displayname") or id(account)
|
||||||
@@ -28,15 +35,28 @@ class DirectImap:
|
|||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
# Assume the testing server supports TLS on port 993.
|
|
||||||
host = self.account.get_config("configured_mail_server")
|
host = self.account.get_config("configured_mail_server")
|
||||||
port = 993
|
port = int(self.account.get_config("configured_mail_port"))
|
||||||
|
security = int(self.account.get_config("configured_mail_security"))
|
||||||
|
|
||||||
user = self.account.get_config("addr")
|
user = self.account.get_config("addr")
|
||||||
host = user.rsplit("@")[-1]
|
|
||||||
pw = self.account.get_config("mail_pw")
|
pw = self.account.get_config("mail_pw")
|
||||||
|
|
||||||
self.conn = MailBox(host, port, ssl_context=ssl.create_default_context())
|
if security == const.SocketSecurity.PLAIN:
|
||||||
|
ssl_context = None
|
||||||
|
else:
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
|
||||||
|
# don't check if certificate hostname doesn't match target hostname
|
||||||
|
ssl_context.check_hostname = False
|
||||||
|
|
||||||
|
# don't check if the certificate is trusted by a certificate authority
|
||||||
|
ssl_context.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
if security == const.SocketSecurity.STARTTLS:
|
||||||
|
self.conn = MailBoxTls(host, port, ssl_context=ssl_context)
|
||||||
|
elif security == const.SocketSecurity.PLAIN or security == const.SocketSecurity.SSL:
|
||||||
|
self.conn = MailBox(host, port, ssl_context=ssl_context)
|
||||||
self.conn.login(user, pw)
|
self.conn.login(user, pw)
|
||||||
|
|
||||||
self.select_folder("INBOX")
|
self.select_folder("INBOX")
|
||||||
@@ -204,8 +224,3 @@ class IdleManager:
|
|||||||
def done(self):
|
def done(self):
|
||||||
"""send idle-done to server if we are currently in idle mode."""
|
"""send idle-done to server if we are currently in idle mode."""
|
||||||
return self.direct_imap.conn.idle.stop()
|
return self.direct_imap.conn.idle.stop()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def direct_imap():
|
|
||||||
return DirectImap
|
|
||||||
@@ -2,7 +2,7 @@ import json
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
|
|
||||||
from ._utils import AttrDict, futuremethod
|
from ._utils import AttrDict
|
||||||
from .const import EventType
|
from .const import EventType
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
|
|
||||||
@@ -52,9 +52,6 @@ class Message:
|
|||||||
"""Mark the message as seen."""
|
"""Mark the message as seen."""
|
||||||
self._rpc.markseen_msgs(self.account.id, [self.id])
|
self._rpc.markseen_msgs(self.account.id, [self.id])
|
||||||
|
|
||||||
def continue_autocrypt_key_transfer(self, setup_code: str) -> None:
|
|
||||||
self._rpc.continue_autocrypt_key_transfer(self.account.id, self.id, setup_code)
|
|
||||||
|
|
||||||
def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None:
|
def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None:
|
||||||
"""Send a webxdc status update. This message must be a webxdc."""
|
"""Send a webxdc status update. This message must be a webxdc."""
|
||||||
if not isinstance(update, str):
|
if not isinstance(update, str):
|
||||||
@@ -73,11 +70,3 @@ class Message:
|
|||||||
event = self.account.wait_for_event()
|
event = self.account.wait_for_event()
|
||||||
if event.kind == EventType.MSG_DELIVERED and event.msg_id == self.id:
|
if event.kind == EventType.MSG_DELIVERED and event.msg_id == self.id:
|
||||||
break
|
break
|
||||||
|
|
||||||
@futuremethod
|
|
||||||
def send_webxdc_realtime_advertisement(self):
|
|
||||||
yield self._rpc.send_webxdc_realtime_advertisement.future(self.account.id, self.id)
|
|
||||||
|
|
||||||
@futuremethod
|
|
||||||
def send_webxdc_realtime_data(self, data) -> None:
|
|
||||||
yield self._rpc.send_webxdc_realtime_data.future(self.account.id, self.id, list(data))
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import os
|
|||||||
import random
|
import random
|
||||||
from typing import AsyncGenerator, Optional
|
from typing import AsyncGenerator, Optional
|
||||||
|
|
||||||
import py
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from . import Account, AttrDict, Bot, Chat, Client, DeltaChat, EventType, Message
|
from . import Account, AttrDict, Bot, Chat, Client, DeltaChat, EventType, Message
|
||||||
@@ -115,60 +114,13 @@ class ACFactory:
|
|||||||
return to_client.run_until(lambda e: e.kind == EventType.INCOMING_MSG)
|
return to_client.run_until(lambda e: e.kind == EventType.INCOMING_MSG)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def rpc(tmp_path) -> AsyncGenerator:
|
def rpc(tmp_path) -> AsyncGenerator:
|
||||||
rpc_server = Rpc(accounts_dir=str(tmp_path / "accounts"))
|
rpc_server = Rpc(accounts_dir=str(tmp_path / "accounts"))
|
||||||
with rpc_server:
|
with rpc_server:
|
||||||
yield rpc_server
|
yield rpc_server
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def acfactory(rpc) -> AsyncGenerator:
|
def acfactory(rpc) -> AsyncGenerator:
|
||||||
return ACFactory(DeltaChat(rpc))
|
return ACFactory(DeltaChat(rpc))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def data():
|
|
||||||
"""Test data."""
|
|
||||||
|
|
||||||
class Data:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
for path in reversed(py.path.local(__file__).parts()):
|
|
||||||
datadir = path.join("test-data")
|
|
||||||
if datadir.isdir():
|
|
||||||
self.path = datadir
|
|
||||||
return
|
|
||||||
raise Exception("Data path cannot be found")
|
|
||||||
|
|
||||||
def get_path(self, bn):
|
|
||||||
"""return path of file or None if it doesn't exist."""
|
|
||||||
fn = os.path.join(self.path, *bn.split("/"))
|
|
||||||
assert os.path.exists(fn)
|
|
||||||
return fn
|
|
||||||
|
|
||||||
def read_path(self, bn, mode="r"):
|
|
||||||
fn = self.get_path(bn)
|
|
||||||
if fn is not None:
|
|
||||||
with open(fn, mode) as f:
|
|
||||||
return f.read()
|
|
||||||
return None
|
|
||||||
|
|
||||||
return Data()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def log():
|
|
||||||
"""Log printer fixture."""
|
|
||||||
|
|
||||||
class Printer:
|
|
||||||
def section(self, msg: str) -> None:
|
|
||||||
print()
|
|
||||||
print("=" * 10, msg, "=" * 10)
|
|
||||||
|
|
||||||
def step(self, msg: str) -> None:
|
|
||||||
print("-" * 5, "step " + msg, "-" * 5)
|
|
||||||
|
|
||||||
def indent(self, msg: str) -> None:
|
|
||||||
print(" " + msg)
|
|
||||||
|
|
||||||
return Printer()
|
|
||||||
|
|||||||
@@ -131,7 +131,10 @@ class Rpc:
|
|||||||
|
|
||||||
def reader_loop(self) -> None:
|
def reader_loop(self) -> None:
|
||||||
try:
|
try:
|
||||||
while line := self.process.stdout.readline():
|
while True:
|
||||||
|
line = self.process.stdout.readline()
|
||||||
|
if not line: # EOF
|
||||||
|
break
|
||||||
response = json.loads(line)
|
response = json.loads(line)
|
||||||
if "id" in response:
|
if "id" in response:
|
||||||
response_id = response["id"]
|
response_id = response["id"]
|
||||||
@@ -147,7 +150,10 @@ class Rpc:
|
|||||||
def writer_loop(self) -> None:
|
def writer_loop(self) -> None:
|
||||||
"""Writer loop ensuring only a single thread writes requests."""
|
"""Writer loop ensuring only a single thread writes requests."""
|
||||||
try:
|
try:
|
||||||
while request := self.request_queue.get():
|
while True:
|
||||||
|
request = self.request_queue.get()
|
||||||
|
if not request:
|
||||||
|
break
|
||||||
data = (json.dumps(request) + "\n").encode()
|
data = (json.dumps(request) + "\n").encode()
|
||||||
self.process.stdin.write(data)
|
self.process.stdin.write(data)
|
||||||
self.process.stdin.flush()
|
self.process.stdin.flush()
|
||||||
@@ -171,7 +177,7 @@ class Rpc:
|
|||||||
account_id = event["contextId"]
|
account_id = event["contextId"]
|
||||||
queue = self.get_queue(account_id)
|
queue = self.get_queue(account_id)
|
||||||
event = event["event"]
|
event = event["event"]
|
||||||
logging.debug("account_id=%d got an event %s", account_id, event)
|
print("account_id=%d got an event %s" % (account_id, event), file=sys.stderr)
|
||||||
queue.put(event)
|
queue.put(event)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Log an exception if the event loop dies.
|
# Log an exception if the event loop dies.
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from deltachat_rpc_client import EventType
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from deltachat_rpc_client.pytestplugin import ACFactory
|
|
||||||
|
|
||||||
|
|
||||||
def test_event_on_configuration(acfactory: ACFactory) -> None:
|
|
||||||
"""
|
|
||||||
Test if ACCOUNTS_ITEM_CHANGED event is emitted on configure
|
|
||||||
"""
|
|
||||||
|
|
||||||
account = acfactory.new_preconfigured_account()
|
|
||||||
account.clear_all_events()
|
|
||||||
assert not account.is_configured()
|
|
||||||
future = account.configure.future()
|
|
||||||
while True:
|
|
||||||
event = account.wait_for_event()
|
|
||||||
if event.kind == EventType.ACCOUNTS_ITEM_CHANGED:
|
|
||||||
break
|
|
||||||
assert account.is_configured()
|
|
||||||
|
|
||||||
future()
|
|
||||||
|
|
||||||
|
|
||||||
# other tests are written in rust: src/tests/account_events.rs
|
|
||||||
@@ -126,7 +126,8 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
|
|||||||
|
|
||||||
alice.set_config("download_limit", "1")
|
alice.set_config("download_limit", "1")
|
||||||
|
|
||||||
msg = bob.wait_for_incoming_msg()
|
event = bob.wait_for_incoming_msg_event()
|
||||||
|
msg = bob.get_message_by_id(event.msg_id)
|
||||||
chat_id = msg.get_snapshot().chat_id
|
chat_id = msg.get_snapshot().chat_id
|
||||||
msg.get_snapshot().chat.accept()
|
msg.get_snapshot().chat.accept()
|
||||||
bob.get_chat_by_id(chat_id).send_message(
|
bob.get_chat_by_id(chat_id).send_message(
|
||||||
@@ -134,15 +135,13 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
|
|||||||
html=base64.b64encode(os.urandom(300000)).decode("utf-8"),
|
html=base64.b64encode(os.urandom(300000)).decode("utf-8"),
|
||||||
)
|
)
|
||||||
|
|
||||||
message = alice.wait_for_incoming_msg()
|
msg_id = alice.wait_for_incoming_msg_event().msg_id
|
||||||
snapshot = message.get_snapshot()
|
|
||||||
assert snapshot.download_state == const.DownloadState.AVAILABLE
|
assert alice.get_message_by_id(msg_id).get_snapshot().download_state == const.DownloadState.AVAILABLE
|
||||||
|
|
||||||
alice.clear_all_events()
|
alice.clear_all_events()
|
||||||
|
chat_id = alice.get_message_by_id(msg_id).get_snapshot().chat_id
|
||||||
snapshot = message.get_snapshot()
|
alice._rpc.download_full_message(alice.id, msg_id)
|
||||||
chat_id = snapshot.chat_id
|
|
||||||
alice._rpc.download_full_message(alice.id, message.id)
|
|
||||||
|
|
||||||
wait_for_chatlist_specific_item(alice, chat_id)
|
wait_for_chatlist_specific_item(alice, chat_id)
|
||||||
|
|
||||||
@@ -157,7 +156,11 @@ def get_multi_account_test_setup(acfactory: ACFactory) -> [Account, Account, Acc
|
|||||||
|
|
||||||
bob.wait_for_incoming_msg_event()
|
bob.wait_for_incoming_msg_event()
|
||||||
|
|
||||||
alice_second_device = alice.clone()
|
alice_second_device: Account = acfactory.get_unconfigured_account()
|
||||||
|
|
||||||
|
alice._rpc.provide_backup.future(alice.id)
|
||||||
|
backup_code = alice._rpc.get_backup_qr(alice.id)
|
||||||
|
alice_second_device._rpc.get_backup(alice_second_device.id, backup_code)
|
||||||
alice_second_device.start_io()
|
alice_second_device.start_io()
|
||||||
alice.clear_all_events()
|
alice.clear_all_events()
|
||||||
alice_second_device.clear_all_events()
|
alice_second_device.clear_all_events()
|
||||||
@@ -174,7 +177,8 @@ def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
|
|||||||
|
|
||||||
alice_chat_bob.send_text("hello")
|
alice_chat_bob.send_text("hello")
|
||||||
|
|
||||||
msg = bob.wait_for_incoming_msg()
|
event = bob.wait_for_incoming_msg_event()
|
||||||
|
msg = bob.get_message_by_id(event.msg_id)
|
||||||
bob_chat_id = msg.get_snapshot().chat_id
|
bob_chat_id = msg.get_snapshot().chat_id
|
||||||
msg.get_snapshot().chat.accept()
|
msg.get_snapshot().chat.accept()
|
||||||
|
|
||||||
@@ -185,7 +189,8 @@ def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
|
|||||||
# make sure alice_second_device already received the message
|
# make sure alice_second_device already received the message
|
||||||
alice_second_device.wait_for_incoming_msg_event()
|
alice_second_device.wait_for_incoming_msg_event()
|
||||||
|
|
||||||
msg = alice.wait_for_incoming_msg()
|
event = alice.wait_for_incoming_msg_event()
|
||||||
|
msg = alice.get_message_by_id(event.msg_id)
|
||||||
alice_second_device.clear_all_events()
|
alice_second_device.clear_all_events()
|
||||||
msg.mark_seen()
|
msg.mark_seen()
|
||||||
|
|
||||||
@@ -206,7 +211,6 @@ def test_multidevice_sync_chat(acfactory: ACFactory) -> None:
|
|||||||
alice_second_device.clear_all_events()
|
alice_second_device.clear_all_events()
|
||||||
alice_chat_bob.pin()
|
alice_chat_bob.pin()
|
||||||
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
|
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
|
||||||
assert alice_second_device.get_chat_by_id(alice_chat_bob.id).get_basic_snapshot().pinned
|
|
||||||
|
|
||||||
alice_second_device.clear_all_events()
|
alice_second_device.clear_all_events()
|
||||||
alice_chat_bob.mute()
|
alice_chat_bob.mute()
|
||||||
|
|||||||
@@ -1,227 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Testing webxdc iroh connectivity
|
|
||||||
|
|
||||||
If you want to debug iroh at rust-trace/log level set
|
|
||||||
|
|
||||||
RUST_LOG=iroh_net=trace,iroh_gossip=trace
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from deltachat_rpc_client import EventType
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def path_to_webxdc(request):
|
|
||||||
p = request.path.parent.parent.parent.joinpath("test-data/webxdc/chess.xdc")
|
|
||||||
assert p.exists()
|
|
||||||
return str(p)
|
|
||||||
|
|
||||||
|
|
||||||
def log(msg):
|
|
||||||
logging.info(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_realtime_webxdc(ac1, ac2, path_to_webxdc):
|
|
||||||
assert ac1.get_config("webxdc_realtime_enabled") == "1"
|
|
||||||
assert ac2.get_config("webxdc_realtime_enabled") == "1"
|
|
||||||
ac1_ac2_chat = ac1.create_chat(ac2)
|
|
||||||
ac2.create_chat(ac1)
|
|
||||||
|
|
||||||
# share a webxdc app between ac1 and ac2
|
|
||||||
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="play", file=path_to_webxdc)
|
|
||||||
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
|
|
||||||
assert ac2_webxdc_msg.get_snapshot().text == "play"
|
|
||||||
|
|
||||||
# send iroh announcements simultaneously
|
|
||||||
log("sending ac1 -> ac2 realtime advertisement and additional message")
|
|
||||||
ac1_webxdc_msg.send_webxdc_realtime_advertisement()
|
|
||||||
|
|
||||||
log("sending ac2 -> ac1 realtime advertisement and additional message")
|
|
||||||
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
|
|
||||||
|
|
||||||
return ac1_webxdc_msg, ac2_webxdc_msg
|
|
||||||
|
|
||||||
|
|
||||||
def setup_thread_send_realtime_data(msg, data):
|
|
||||||
def thread_run():
|
|
||||||
for _i in range(10):
|
|
||||||
msg.send_webxdc_realtime_data(data)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
threading.Thread(target=thread_run, daemon=True).start()
|
|
||||||
|
|
||||||
|
|
||||||
def wait_receive_realtime_data(msg_data_list):
|
|
||||||
account = msg_data_list[0][0].account
|
|
||||||
msg_data_list = msg_data_list[:]
|
|
||||||
|
|
||||||
log(f"account {account.id}: waiting for realtime data {msg_data_list}")
|
|
||||||
while msg_data_list:
|
|
||||||
event = account.wait_for_event()
|
|
||||||
if event.kind == EventType.WEBXDC_REALTIME_DATA:
|
|
||||||
for i, (msg, data) in enumerate(msg_data_list):
|
|
||||||
if msg.id == event.msg_id:
|
|
||||||
assert list(data) == event.data
|
|
||||||
log(f"msg {msg.id}: got correct realtime data {data}")
|
|
||||||
del msg_data_list[i]
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def test_realtime_sequentially(acfactory, path_to_webxdc):
|
|
||||||
"""Test two peers trying to establish connection sequentially."""
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
ac1.create_chat(ac2)
|
|
||||||
ac2.create_chat(ac1)
|
|
||||||
|
|
||||||
# share a webxdc app between ac1 and ac2
|
|
||||||
ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
|
|
||||||
ac2_webxdc_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
|
|
||||||
snapshot = ac2_webxdc_msg.get_snapshot()
|
|
||||||
assert snapshot.text == "play"
|
|
||||||
|
|
||||||
# send iroh announcements sequentially
|
|
||||||
log("sending ac1 -> ac2 realtime advertisement and additional message")
|
|
||||||
ac1_webxdc_msg.send_webxdc_realtime_advertisement()
|
|
||||||
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
|
|
||||||
|
|
||||||
log("waiting for incoming message on ac2")
|
|
||||||
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
|
||||||
assert snapshot.text == "ping1"
|
|
||||||
|
|
||||||
log("sending ac2 -> ac1 realtime advertisement and additional message")
|
|
||||||
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
|
|
||||||
acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
|
|
||||||
|
|
||||||
log("waiting for incoming message on ac1")
|
|
||||||
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
|
||||||
assert snapshot.text == "ping2"
|
|
||||||
|
|
||||||
log("sending realtime data ac1 -> ac2")
|
|
||||||
# Test that 128 KB of data can be sent in a single message.
|
|
||||||
data = os.urandom(128000)
|
|
||||||
ac1_webxdc_msg.send_webxdc_realtime_data(data)
|
|
||||||
|
|
||||||
log("ac2: waiting for realtime data")
|
|
||||||
while 1:
|
|
||||||
event = ac2.wait_for_event()
|
|
||||||
if event.kind == EventType.WEBXDC_REALTIME_DATA:
|
|
||||||
assert event.data == list(data)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def test_realtime_simultaneously(acfactory, path_to_webxdc):
|
|
||||||
"""Test two peers trying to establish connection simultaneously."""
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
|
|
||||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
|
||||||
|
|
||||||
setup_thread_send_realtime_data(ac1_webxdc_msg, [10])
|
|
||||||
wait_receive_realtime_data([(ac2_webxdc_msg, [10])])
|
|
||||||
|
|
||||||
|
|
||||||
def test_two_parallel_realtime_simultaneously(acfactory, path_to_webxdc):
|
|
||||||
"""Test two peers trying to establish connection simultaneously."""
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
|
|
||||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
|
||||||
ac1_webxdc_msg2, ac2_webxdc_msg2 = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
|
||||||
|
|
||||||
setup_thread_send_realtime_data(ac1_webxdc_msg, [10])
|
|
||||||
setup_thread_send_realtime_data(ac1_webxdc_msg2, [20])
|
|
||||||
setup_thread_send_realtime_data(ac2_webxdc_msg, [30])
|
|
||||||
setup_thread_send_realtime_data(ac2_webxdc_msg2, [40])
|
|
||||||
|
|
||||||
wait_receive_realtime_data([(ac1_webxdc_msg, [30]), (ac1_webxdc_msg2, [40])])
|
|
||||||
wait_receive_realtime_data([(ac2_webxdc_msg, [10]), (ac2_webxdc_msg2, [20])])
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_duplicate_messages(acfactory, path_to_webxdc):
|
|
||||||
"""Test that messages are received only once."""
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
|
|
||||||
ac1_ac2_chat = ac1.create_chat(ac2)
|
|
||||||
|
|
||||||
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="webxdc", file=path_to_webxdc)
|
|
||||||
|
|
||||||
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
|
|
||||||
ac2_webxdc_msg.get_snapshot().chat.accept()
|
|
||||||
assert ac2_webxdc_msg.get_snapshot().text == "webxdc"
|
|
||||||
|
|
||||||
# Issue a "send" call in parallel with sending advertisement.
|
|
||||||
# Previously due to a bug this caused subscribing to the channel twice.
|
|
||||||
ac2_webxdc_msg.send_webxdc_realtime_data.future(b"foobar")
|
|
||||||
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
|
|
||||||
|
|
||||||
def thread_run():
|
|
||||||
for i in range(10):
|
|
||||||
data = str(i).encode()
|
|
||||||
ac1_webxdc_msg.send_webxdc_realtime_data(data)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
threading.Thread(target=thread_run, daemon=True).start()
|
|
||||||
|
|
||||||
event = ac2.wait_for_event(EventType.WEBXDC_REALTIME_DATA)
|
|
||||||
n = int(bytes(event.data).decode())
|
|
||||||
|
|
||||||
event = ac2.wait_for_event(EventType.WEBXDC_REALTIME_DATA)
|
|
||||||
assert int(bytes(event.data).decode()) > n
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_reordering(acfactory, path_to_webxdc):
|
|
||||||
"""Test that sending a lot of realtime messages does not result in reordering."""
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
|
|
||||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
|
||||||
|
|
||||||
setup_thread_send_realtime_data(ac1_webxdc_msg, b"hello")
|
|
||||||
wait_receive_realtime_data([(ac2_webxdc_msg, b"hello")])
|
|
||||||
|
|
||||||
for i in range(200):
|
|
||||||
ac1_webxdc_msg.send_webxdc_realtime_data([i])
|
|
||||||
|
|
||||||
for i in range(200):
|
|
||||||
while 1:
|
|
||||||
event = ac2.wait_for_event()
|
|
||||||
if event.kind == EventType.WEBXDC_REALTIME_DATA and bytes(event.data) != b"hello":
|
|
||||||
if event.data[0] == i:
|
|
||||||
break
|
|
||||||
pytest.fail("Reordering detected")
|
|
||||||
|
|
||||||
|
|
||||||
def test_advertisement_after_chatting(acfactory, path_to_webxdc):
|
|
||||||
"""Test that realtime advertisement is assigned to the correct message after chatting."""
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
|
||||||
|
|
||||||
ac1_ac2_chat = ac1.create_chat(ac2)
|
|
||||||
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="WebXDC", file=path_to_webxdc)
|
|
||||||
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
|
|
||||||
assert ac2_webxdc_msg.get_snapshot().text == "WebXDC"
|
|
||||||
|
|
||||||
ac1_ac2_chat.send_text("Hello!")
|
|
||||||
ac2_hello_msg = ac2.wait_for_incoming_msg()
|
|
||||||
ac2_hello_msg_snapshot = ac2_hello_msg.get_snapshot()
|
|
||||||
assert ac2_hello_msg_snapshot.text == "Hello!"
|
|
||||||
ac2_hello_msg_snapshot.chat.accept()
|
|
||||||
|
|
||||||
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
|
|
||||||
event = ac1.wait_for_event(EventType.WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED)
|
|
||||||
assert event.msg_id == ac1_webxdc_msg.id
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from deltachat_rpc_client import EventType
|
|
||||||
from deltachat_rpc_client.rpc import JsonRpcError
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_autocrypt_setup_message(account):
|
|
||||||
while True:
|
|
||||||
event = account.wait_for_event()
|
|
||||||
if event.kind == EventType.MSGS_CHANGED and event.msg_id != 0:
|
|
||||||
msg_id = event.msg_id
|
|
||||||
msg = account.get_message_by_id(msg_id)
|
|
||||||
if msg.get_snapshot().is_setupmessage:
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
def test_autocrypt_setup_message_key_transfer(acfactory):
|
|
||||||
alice1 = acfactory.get_online_account()
|
|
||||||
|
|
||||||
alice2 = acfactory.get_unconfigured_account()
|
|
||||||
alice2.set_config("addr", alice1.get_config("addr"))
|
|
||||||
alice2.set_config("mail_pw", alice1.get_config("mail_pw"))
|
|
||||||
alice2.configure()
|
|
||||||
alice2.bring_online()
|
|
||||||
|
|
||||||
setup_code = alice1.initiate_autocrypt_key_transfer()
|
|
||||||
msg = wait_for_autocrypt_setup_message(alice2)
|
|
||||||
|
|
||||||
# Test that entering wrong code returns an error.
|
|
||||||
with pytest.raises(JsonRpcError):
|
|
||||||
msg.continue_autocrypt_key_transfer("7037-0673-6287-3013-4095-7956-5617-6806-6756")
|
|
||||||
|
|
||||||
msg.continue_autocrypt_key_transfer(setup_code)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ac_setup_message_twice(acfactory):
|
|
||||||
alice1 = acfactory.get_online_account()
|
|
||||||
|
|
||||||
alice2 = acfactory.get_unconfigured_account()
|
|
||||||
alice2.set_config("addr", alice1.get_config("addr"))
|
|
||||||
alice2.set_config("mail_pw", alice1.get_config("mail_pw"))
|
|
||||||
alice2.configure()
|
|
||||||
alice2.bring_online()
|
|
||||||
|
|
||||||
# Send the first Autocrypt Setup Message and ignore it.
|
|
||||||
_setup_code = alice1.initiate_autocrypt_key_transfer()
|
|
||||||
wait_for_autocrypt_setup_message(alice2)
|
|
||||||
|
|
||||||
# Send the second Autocrypt Setup Message and import it.
|
|
||||||
setup_code = alice1.initiate_autocrypt_key_transfer()
|
|
||||||
msg = wait_for_autocrypt_setup_message(alice2)
|
|
||||||
|
|
||||||
msg.continue_autocrypt_key_transfer(setup_code)
|
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from deltachat_rpc_client import Chat, EventType, SpecialContactId
|
from deltachat_rpc_client import Chat, EventType, SpecialContactId
|
||||||
from deltachat_rpc_client.rpc import JsonRpcError
|
|
||||||
|
|
||||||
|
|
||||||
def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
qr_code = alice.get_qr_code()
|
qr_code, _svg = alice.get_qr_code()
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
|
|
||||||
alice.wait_for_securejoin_inviter_success()
|
alice.wait_for_securejoin_inviter_success()
|
||||||
@@ -27,67 +24,47 @@ def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
|||||||
bob_contact_alice_snapshot = bob_contact_alice.get_snapshot()
|
bob_contact_alice_snapshot = bob_contact_alice.get_snapshot()
|
||||||
assert bob_contact_alice_snapshot.is_verified
|
assert bob_contact_alice_snapshot.is_verified
|
||||||
|
|
||||||
# Test that if Bob imports a key,
|
# Test that if Bob changes the key, backwards verification is lost.
|
||||||
# backwards verification is not lost
|
|
||||||
# because default key is not changed.
|
|
||||||
logging.info("Bob 2 is created")
|
logging.info("Bob 2 is created")
|
||||||
bob2 = acfactory.new_configured_account()
|
bob2 = acfactory.new_configured_account()
|
||||||
bob2.export_self_keys(tmp_path)
|
bob2.export_self_keys(tmp_path)
|
||||||
|
|
||||||
logging.info("Bob tries to import a key")
|
logging.info("Bob imports a key")
|
||||||
# Importing a second key is not allowed.
|
bob.import_self_keys(tmp_path / "private-key-default.asc")
|
||||||
with pytest.raises(JsonRpcError):
|
|
||||||
bob.import_self_keys(tmp_path)
|
|
||||||
|
|
||||||
assert bob.get_config("key_id") == "1"
|
assert bob.get_config("key_id") == "2"
|
||||||
bob_contact_alice_snapshot = bob_contact_alice.get_snapshot()
|
bob_contact_alice_snapshot = bob_contact_alice.get_snapshot()
|
||||||
assert bob_contact_alice_snapshot.is_verified
|
assert not bob_contact_alice_snapshot.is_verified
|
||||||
|
|
||||||
|
|
||||||
def test_qr_setup_contact_svg(acfactory) -> None:
|
|
||||||
alice = acfactory.new_configured_account()
|
|
||||||
_, _, domain = alice.get_config("addr").rpartition("@")
|
|
||||||
|
|
||||||
_qr_code, svg = alice.get_qr_code_svg()
|
|
||||||
|
|
||||||
alice.set_config("displayname", "Alice")
|
|
||||||
|
|
||||||
# Test that display name is used
|
|
||||||
# in SVG and no address is visible.
|
|
||||||
_qr_code, svg = alice.get_qr_code_svg()
|
|
||||||
assert domain not in svg
|
|
||||||
assert "Alice" in svg
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("protect", [True, False])
|
@pytest.mark.parametrize("protect", [True, False])
|
||||||
def test_qr_securejoin(acfactory, protect):
|
def test_qr_securejoin(acfactory, protect):
|
||||||
alice, bob, fiona = acfactory.get_online_accounts(3)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
# Setup second device for Alice
|
logging.info("Alice creates a verified group")
|
||||||
# to test observing securejoin protocol.
|
alice_chat = alice.create_group("Verified group", protect=protect)
|
||||||
alice2 = alice.clone()
|
|
||||||
|
|
||||||
logging.info("Alice creates a group")
|
|
||||||
alice_chat = alice.create_group("Group", protect=protect)
|
|
||||||
assert alice_chat.get_basic_snapshot().is_protected == protect
|
assert alice_chat.get_basic_snapshot().is_protected == protect
|
||||||
|
|
||||||
logging.info("Bob joins the group")
|
logging.info("Bob joins verified group")
|
||||||
qr_code = alice_chat.get_qr_code()
|
qr_code, _svg = alice_chat.get_qr_code()
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
|
|
||||||
# Alice deletes "vg-request".
|
# Check that at least some of the handshake messages are deleted.
|
||||||
alice.wait_for_event(EventType.IMAP_MESSAGE_DELETED)
|
|
||||||
alice.wait_for_securejoin_inviter_success()
|
|
||||||
# Bob deletes "vg-auth-required", Alice deletes "vg-request-with-auth".
|
|
||||||
for ac in [alice, bob]:
|
for ac in [alice, bob]:
|
||||||
ac.wait_for_event(EventType.IMAP_MESSAGE_DELETED)
|
while True:
|
||||||
bob.wait_for_securejoin_joiner_success()
|
event = ac.wait_for_event()
|
||||||
|
if event["kind"] == "ImapMessageDeleted":
|
||||||
|
break
|
||||||
|
|
||||||
|
alice.wait_for_securejoin_inviter_success()
|
||||||
|
|
||||||
# Test that Alice verified Bob's profile.
|
# Test that Alice verified Bob's profile.
|
||||||
alice_contact_bob = alice.get_contact_by_addr(bob.get_config("addr"))
|
alice_contact_bob = alice.get_contact_by_addr(bob.get_config("addr"))
|
||||||
alice_contact_bob_snapshot = alice_contact_bob.get_snapshot()
|
alice_contact_bob_snapshot = alice_contact_bob.get_snapshot()
|
||||||
assert alice_contact_bob_snapshot.is_verified
|
assert alice_contact_bob_snapshot.is_verified
|
||||||
|
|
||||||
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
|
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
|
||||||
assert snapshot.chat.get_basic_snapshot().is_protected == protect
|
assert snapshot.chat.get_basic_snapshot().is_protected == protect
|
||||||
@@ -97,21 +74,6 @@ def test_qr_securejoin(acfactory, protect):
|
|||||||
bob_contact_alice_snapshot = bob_contact_alice.get_snapshot()
|
bob_contact_alice_snapshot = bob_contact_alice.get_snapshot()
|
||||||
assert bob_contact_alice_snapshot.is_verified
|
assert bob_contact_alice_snapshot.is_verified
|
||||||
|
|
||||||
# Start second Alice device.
|
|
||||||
# Alice observes securejoin protocol and verifies Bob on second device.
|
|
||||||
alice2.start_io()
|
|
||||||
alice2.wait_for_securejoin_inviter_success()
|
|
||||||
alice2_contact_bob = alice2.get_contact_by_addr(bob.get_config("addr"))
|
|
||||||
alice2_contact_bob_snapshot = alice2_contact_bob.get_snapshot()
|
|
||||||
assert alice2_contact_bob_snapshot.is_verified
|
|
||||||
|
|
||||||
# The QR code token is synced, so alice2 must be able to handle join requests.
|
|
||||||
logging.info("Fiona joins the group via alice2")
|
|
||||||
alice.stop_io()
|
|
||||||
fiona.secure_join(qr_code)
|
|
||||||
alice2.wait_for_securejoin_inviter_success()
|
|
||||||
fiona.wait_for_securejoin_joiner_success()
|
|
||||||
|
|
||||||
|
|
||||||
def test_qr_securejoin_contact_request(acfactory) -> None:
|
def test_qr_securejoin_contact_request(acfactory) -> None:
|
||||||
"""Alice invites Bob to a group when Bob's chat with Alice is in a contact request mode."""
|
"""Alice invites Bob to a group when Bob's chat with Alice is in a contact request mode."""
|
||||||
@@ -129,7 +91,7 @@ def test_qr_securejoin_contact_request(acfactory) -> None:
|
|||||||
|
|
||||||
alice_chat = alice.create_group("Verified group", protect=True)
|
alice_chat = alice.create_group("Verified group", protect=True)
|
||||||
logging.info("Bob joins verified group")
|
logging.info("Bob joins verified group")
|
||||||
qr_code = alice_chat.get_qr_code()
|
qr_code, _svg = alice_chat.get_qr_code()
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
while True:
|
while True:
|
||||||
event = bob.wait_for_event()
|
event = bob.wait_for_event()
|
||||||
@@ -144,7 +106,7 @@ def test_qr_readreceipt(acfactory) -> None:
|
|||||||
alice, bob, charlie = acfactory.get_online_accounts(3)
|
alice, bob, charlie = acfactory.get_online_accounts(3)
|
||||||
|
|
||||||
logging.info("Bob and Charlie setup contact with Alice")
|
logging.info("Bob and Charlie setup contact with Alice")
|
||||||
qr_code = alice.get_qr_code()
|
qr_code, _svg = alice.get_qr_code()
|
||||||
|
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
charlie.secure_join(qr_code)
|
charlie.secure_join(qr_code)
|
||||||
@@ -206,13 +168,13 @@ def test_setup_contact_resetup(acfactory) -> None:
|
|||||||
"""Tests that setup contact works after Alice resets the device and changes the key."""
|
"""Tests that setup contact works after Alice resets the device and changes the key."""
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
qr_code = alice.get_qr_code()
|
qr_code, _svg = alice.get_qr_code()
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
bob.wait_for_securejoin_joiner_success()
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
alice = acfactory.resetup_account(alice)
|
alice = acfactory.resetup_account(alice)
|
||||||
|
|
||||||
qr_code = alice.get_qr_code()
|
qr_code, _svg = alice.get_qr_code()
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
bob.wait_for_securejoin_joiner_success()
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
@@ -226,7 +188,7 @@ def test_verified_group_recovery(acfactory) -> None:
|
|||||||
assert chat.get_basic_snapshot().is_protected
|
assert chat.get_basic_snapshot().is_protected
|
||||||
|
|
||||||
logging.info("ac2 joins verified group")
|
logging.info("ac2 joins verified group")
|
||||||
qr_code = chat.get_qr_code()
|
qr_code, _svg = chat.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac2.wait_for_securejoin_joiner_success()
|
ac2.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
@@ -243,7 +205,7 @@ def test_verified_group_recovery(acfactory) -> None:
|
|||||||
ac2 = acfactory.resetup_account(ac2)
|
ac2 = acfactory.resetup_account(ac2)
|
||||||
|
|
||||||
logging.info("ac2 reverifies with ac3")
|
logging.info("ac2 reverifies with ac3")
|
||||||
qr_code = ac3.get_qr_code()
|
qr_code, _svg = ac3.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac2.wait_for_securejoin_joiner_success()
|
ac2.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
@@ -290,7 +252,7 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
|
|||||||
assert chat.get_basic_snapshot().is_protected
|
assert chat.get_basic_snapshot().is_protected
|
||||||
|
|
||||||
logging.info("ac2 joins verified group")
|
logging.info("ac2 joins verified group")
|
||||||
qr_code = chat.get_qr_code()
|
qr_code, _svg = chat.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac2.wait_for_securejoin_joiner_success()
|
ac2.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
@@ -307,7 +269,7 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
|
|||||||
ac2 = acfactory.resetup_account(ac2)
|
ac2 = acfactory.resetup_account(ac2)
|
||||||
|
|
||||||
logging.info("ac2 reverifies with ac3")
|
logging.info("ac2 reverifies with ac3")
|
||||||
qr_code = ac3.get_qr_code()
|
qr_code, _svg = ac3.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac2.wait_for_securejoin_joiner_success()
|
ac2.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
@@ -325,6 +287,7 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
|
|||||||
|
|
||||||
ac3_contact_ac2 = ac3.get_contact_by_addr(ac2.get_config("addr"))
|
ac3_contact_ac2 = ac3.get_contact_by_addr(ac2.get_config("addr"))
|
||||||
ac3_chat.remove_contact(ac3_contact_ac2)
|
ac3_chat.remove_contact(ac3_contact_ac2)
|
||||||
|
ac3_chat.add_contact(ac3_contact_ac2)
|
||||||
|
|
||||||
msg_id = ac2.wait_for_incoming_msg_event().msg_id
|
msg_id = ac2.wait_for_incoming_msg_event().msg_id
|
||||||
message = ac2.get_message_by_id(msg_id)
|
message = ac2.get_message_by_id(msg_id)
|
||||||
@@ -334,8 +297,6 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
|
|||||||
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
assert "removed" in snapshot.text
|
assert "removed" in snapshot.text
|
||||||
|
|
||||||
ac3_chat.add_contact(ac3_contact_ac2)
|
|
||||||
|
|
||||||
event = ac2.wait_for_incoming_msg_event()
|
event = ac2.wait_for_incoming_msg_event()
|
||||||
msg_id = event.msg_id
|
msg_id = event.msg_id
|
||||||
chat_id = event.chat_id
|
chat_id = event.chat_id
|
||||||
@@ -375,7 +336,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
|||||||
ac1, ac2, ac3, ac4 = acfactory.get_online_accounts(4)
|
ac1, ac2, ac3, ac4 = acfactory.get_online_accounts(4)
|
||||||
|
|
||||||
logging.info("ac3: verify with ac2")
|
logging.info("ac3: verify with ac2")
|
||||||
qr_code = ac2.get_qr_code()
|
qr_code, _svg = ac2.get_qr_code()
|
||||||
ac3.secure_join(qr_code)
|
ac3.secure_join(qr_code)
|
||||||
ac2.wait_for_securejoin_inviter_success()
|
ac2.wait_for_securejoin_inviter_success()
|
||||||
|
|
||||||
@@ -385,7 +346,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
|||||||
|
|
||||||
logging.info("ac1: create verified group that ac2 fully joins")
|
logging.info("ac1: create verified group that ac2 fully joins")
|
||||||
ch1 = ac1.create_group("Group", protect=True)
|
ch1 = ac1.create_group("Group", protect=True)
|
||||||
qr_code = ch1.get_qr_code()
|
qr_code, _svg = ch1.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac1.wait_for_securejoin_inviter_success()
|
ac1.wait_for_securejoin_inviter_success()
|
||||||
|
|
||||||
@@ -398,7 +359,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
|||||||
break
|
break
|
||||||
|
|
||||||
logging.info("ac1: let ac2 join again but shutoff ac1 in the middle of securejoin")
|
logging.info("ac1: let ac2 join again but shutoff ac1 in the middle of securejoin")
|
||||||
qr_code = ch1.get_qr_code()
|
qr_code, _svg = ch1.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac1.remove()
|
ac1.remove()
|
||||||
logging.info("ac2 now has pending bobstate but ac1 is shutoff")
|
logging.info("ac2 now has pending bobstate but ac1 is shutoff")
|
||||||
@@ -420,7 +381,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
|||||||
break
|
break
|
||||||
|
|
||||||
logging.info("ac3: create a join-code for group VG and let ac4 join, check that ac2 got it")
|
logging.info("ac3: create a join-code for group VG and let ac4 join, check that ac2 got it")
|
||||||
qr_code = vg.get_qr_code()
|
qr_code, _svg = vg.get_qr_code()
|
||||||
ac4.secure_join(qr_code)
|
ac4.secure_join(qr_code)
|
||||||
ac3.wait_for_securejoin_inviter_success()
|
ac3.wait_for_securejoin_inviter_success()
|
||||||
while 1:
|
while 1:
|
||||||
@@ -441,7 +402,7 @@ def test_qr_new_group_unblocked(acfactory):
|
|||||||
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
ac1_chat = ac1.create_group("Group for joining", protect=True)
|
ac1_chat = ac1.create_group("Group for joining", protect=True)
|
||||||
qr_code = ac1_chat.get_qr_code()
|
qr_code, _svg = ac1_chat.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
|
|
||||||
ac1.wait_for_securejoin_inviter_success()
|
ac1.wait_for_securejoin_inviter_success()
|
||||||
@@ -457,22 +418,17 @@ def test_qr_new_group_unblocked(acfactory):
|
|||||||
assert ac2_msg.chat.get_basic_snapshot().is_contact_request
|
assert ac2_msg.chat.get_basic_snapshot().is_contact_request
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="AEAP is disabled for now")
|
|
||||||
def test_aeap_flow_verified(acfactory):
|
def test_aeap_flow_verified(acfactory):
|
||||||
"""Test that a new address is added to a contact when it changes its address."""
|
"""Test that a new address is added to a contact when it changes its address."""
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2, ac1new = acfactory.get_online_accounts(3)
|
||||||
|
|
||||||
# ac1new is only used to get a new address.
|
|
||||||
ac1new = acfactory.new_preconfigured_account()
|
|
||||||
|
|
||||||
logging.info("ac1: create verified-group QR, ac2 scans and joins")
|
logging.info("ac1: create verified-group QR, ac2 scans and joins")
|
||||||
chat = ac1.create_group("hello", protect=True)
|
chat = ac1.create_group("hello", protect=True)
|
||||||
assert chat.get_basic_snapshot().is_protected
|
assert chat.get_basic_snapshot().is_protected
|
||||||
qr_code = chat.get_qr_code()
|
qr_code, _svg = chat.get_qr_code()
|
||||||
logging.info("ac2: start QR-code based join-group protocol")
|
logging.info("ac2: start QR-code based join-group protocol")
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac1.wait_for_securejoin_inviter_success()
|
ac1.wait_for_securejoin_inviter_success()
|
||||||
ac2.wait_for_securejoin_joiner_success()
|
|
||||||
|
|
||||||
logging.info("sending first message")
|
logging.info("sending first message")
|
||||||
msg_out = chat.send_text("old address").get_snapshot()
|
msg_out = chat.send_text("old address").get_snapshot()
|
||||||
@@ -508,12 +464,12 @@ def test_gossip_verification(acfactory) -> None:
|
|||||||
alice, bob, carol = acfactory.get_online_accounts(3)
|
alice, bob, carol = acfactory.get_online_accounts(3)
|
||||||
|
|
||||||
# Bob verifies Alice.
|
# Bob verifies Alice.
|
||||||
qr_code = alice.get_qr_code()
|
qr_code, _svg = alice.get_qr_code()
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
bob.wait_for_securejoin_joiner_success()
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
# Bob verifies Carol.
|
# Bob verifies Carol.
|
||||||
qr_code = carol.get_qr_code()
|
qr_code, _svg = carol.get_qr_code()
|
||||||
bob.secure_join(qr_code)
|
bob.secure_join(qr_code)
|
||||||
bob.wait_for_securejoin_joiner_success()
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
@@ -564,17 +520,16 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
|||||||
ac3_chat = ac3.create_group("Verified group", protect=True)
|
ac3_chat = ac3.create_group("Verified group", protect=True)
|
||||||
|
|
||||||
# ac1 joins ac3 group.
|
# ac1 joins ac3 group.
|
||||||
ac3_qr_code = ac3_chat.get_qr_code()
|
ac3_qr_code, _svg = ac3_chat.get_qr_code()
|
||||||
ac1.secure_join(ac3_qr_code)
|
ac1.secure_join(ac3_qr_code)
|
||||||
ac1.wait_for_securejoin_joiner_success()
|
ac1.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
# ac1 waits for member added message and creates a QR code.
|
# ac1 waits for member added message and creates a QR code.
|
||||||
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
assert snapshot.text == "Member Me ({}) added by {}.".format(ac1.get_config("addr"), ac3.get_config("addr"))
|
ac1_qr_code, _svg = snapshot.chat.get_qr_code()
|
||||||
ac1_qr_code = snapshot.chat.get_qr_code()
|
|
||||||
|
|
||||||
# ac2 verifies ac1
|
# ac2 verifies ac1
|
||||||
qr_code = ac1.get_qr_code()
|
qr_code, _svg = ac1.get_qr_code()
|
||||||
ac2.secure_join(qr_code)
|
ac2.secure_join(qr_code)
|
||||||
ac2.wait_for_securejoin_joiner_success()
|
ac2.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
@@ -585,14 +540,6 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
|||||||
# ac1 resetups the account.
|
# ac1 resetups the account.
|
||||||
ac1 = acfactory.resetup_account(ac1)
|
ac1 = acfactory.resetup_account(ac1)
|
||||||
|
|
||||||
# Loop sending message from ac1 to ac2
|
|
||||||
# until ac2 accepts new ac1 key.
|
|
||||||
#
|
|
||||||
# This may not happen immediately because resetup of ac1
|
|
||||||
# rewinds "smeared timestamp" so Date: header for messages
|
|
||||||
# sent by new ac1 are in the past compared to the last Date:
|
|
||||||
# header sent by old ac1.
|
|
||||||
while True:
|
|
||||||
# ac1 sends a message to ac2.
|
# ac1 sends a message to ac2.
|
||||||
ac1_contact_ac2 = ac1.create_contact(ac2.get_config("addr"), "")
|
ac1_contact_ac2 = ac1.create_contact(ac2.get_config("addr"), "")
|
||||||
ac1_chat_ac2 = ac1_contact_ac2.create_chat()
|
ac1_chat_ac2 = ac1_contact_ac2.create_chat()
|
||||||
@@ -601,13 +548,9 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
|||||||
# ac2 receives a message.
|
# ac2 receives a message.
|
||||||
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
assert snapshot.text == "Hello!"
|
assert snapshot.text == "Hello!"
|
||||||
logging.info("ac2 received Hello!")
|
|
||||||
|
|
||||||
# ac1 is no longer verified for ac2 as new Autocrypt key is not the same as old verified key.
|
# ac1 is no longer verified for ac2 as new Autocrypt key is not the same as old verified key.
|
||||||
logging.info("ac2 addr={}, ac1 addr={}".format(ac2.get_config("addr"), ac1.get_config("addr")))
|
assert not ac2_contact_ac1.get_snapshot().is_verified
|
||||||
if not ac2_contact_ac1.get_snapshot().is_verified:
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# ac1 goes offline.
|
# ac1 goes offline.
|
||||||
ac1.remove()
|
ac1.remove()
|
||||||
@@ -646,18 +589,16 @@ def test_withdraw_securejoin_qr(acfactory):
|
|||||||
assert alice_chat.get_basic_snapshot().is_protected
|
assert alice_chat.get_basic_snapshot().is_protected
|
||||||
logging.info("Bob joins verified group")
|
logging.info("Bob joins verified group")
|
||||||
|
|
||||||
qr_code = alice_chat.get_qr_code()
|
qr_code, _svg = alice_chat.get_qr_code()
|
||||||
bob_chat = bob.secure_join(qr_code)
|
bob_chat = bob.secure_join(qr_code)
|
||||||
bob.wait_for_securejoin_joiner_success()
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
alice.clear_all_events()
|
|
||||||
|
|
||||||
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
|
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
|
||||||
assert snapshot.chat.get_basic_snapshot().is_protected
|
assert snapshot.chat.get_basic_snapshot().is_protected
|
||||||
bob_chat.leave()
|
bob_chat.leave()
|
||||||
|
|
||||||
snapshot = alice.get_message_by_id(alice.wait_for_msgs_changed_event().msg_id).get_snapshot()
|
snapshot = alice.get_message_by_id(alice.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
assert snapshot.text == "Group left by {}.".format(bob.get_config("addr"))
|
assert snapshot.text == "Group left by {}.".format(bob.get_config("addr"))
|
||||||
|
|
||||||
logging.info("Alice withdraws QR code.")
|
logging.info("Alice withdraws QR code.")
|
||||||
@@ -671,8 +612,7 @@ def test_withdraw_securejoin_qr(acfactory):
|
|||||||
logging.info("Bob scanned withdrawn QR code")
|
logging.info("Bob scanned withdrawn QR code")
|
||||||
while True:
|
while True:
|
||||||
event = alice.wait_for_event()
|
event = alice.wait_for_event()
|
||||||
if (
|
if event.kind == EventType.MSGS_CHANGED and event.chat_id != 0:
|
||||||
event.kind == EventType.WARNING
|
|
||||||
and "Ignoring vg-request-with-auth message because of invalid auth code." in event.msg
|
|
||||||
):
|
|
||||||
break
|
break
|
||||||
|
snapshot = alice.get_message_by_id(event.msg_id).get_snapshot()
|
||||||
|
assert snapshot.text == "Cannot establish guaranteed end-to-end encryption with {}".format(bob.get_config("addr"))
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import base64
|
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from deltachat_rpc_client import Contact, EventType, Message, events
|
from deltachat_rpc_client import Contact, EventType, Message, events
|
||||||
from deltachat_rpc_client.const import DownloadState, MessageState
|
from deltachat_rpc_client.const import DownloadState, MessageState
|
||||||
|
from deltachat_rpc_client.direct_imap import DirectImap
|
||||||
from deltachat_rpc_client.rpc import JsonRpcError
|
from deltachat_rpc_client.rpc import JsonRpcError
|
||||||
|
|
||||||
|
|
||||||
@@ -56,8 +54,8 @@ def test_acfactory(acfactory) -> None:
|
|||||||
if event.progress == 1000: # Success
|
if event.progress == 1000: # Success
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.info(event)
|
print(event)
|
||||||
logging.info("Successful configuration")
|
print("Successful configuration")
|
||||||
|
|
||||||
|
|
||||||
def test_configure_starttls(acfactory) -> None:
|
def test_configure_starttls(acfactory) -> None:
|
||||||
@@ -70,38 +68,6 @@ def test_configure_starttls(acfactory) -> None:
|
|||||||
assert account.is_configured()
|
assert account.is_configured()
|
||||||
|
|
||||||
|
|
||||||
def test_configure_ip(acfactory) -> None:
|
|
||||||
account = acfactory.new_preconfigured_account()
|
|
||||||
|
|
||||||
domain = account.get_config("addr").rsplit("@")[-1]
|
|
||||||
ip_address = socket.gethostbyname(domain)
|
|
||||||
|
|
||||||
# This should fail TLS check.
|
|
||||||
account.set_config("mail_server", ip_address)
|
|
||||||
with pytest.raises(JsonRpcError):
|
|
||||||
account.configure()
|
|
||||||
|
|
||||||
|
|
||||||
def test_configure_alternative_port(acfactory) -> None:
|
|
||||||
"""Test that configuration with alternative port 443 works."""
|
|
||||||
account = acfactory.new_preconfigured_account()
|
|
||||||
|
|
||||||
account.set_config("mail_port", "443")
|
|
||||||
account.set_config("send_port", "443")
|
|
||||||
|
|
||||||
account.configure()
|
|
||||||
|
|
||||||
|
|
||||||
def test_configure_username(acfactory) -> None:
|
|
||||||
account = acfactory.new_preconfigured_account()
|
|
||||||
|
|
||||||
addr = account.get_config("addr")
|
|
||||||
account.set_config("mail_user", addr)
|
|
||||||
account.configure()
|
|
||||||
|
|
||||||
assert account.get_config("configured_mail_user") == addr
|
|
||||||
|
|
||||||
|
|
||||||
def test_account(acfactory) -> None:
|
def test_account(acfactory) -> None:
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
@@ -110,9 +76,12 @@ def test_account(acfactory) -> None:
|
|||||||
alice_chat_bob = alice_contact_bob.create_chat()
|
alice_chat_bob = alice_contact_bob.create_chat()
|
||||||
alice_chat_bob.send_text("Hello!")
|
alice_chat_bob.send_text("Hello!")
|
||||||
|
|
||||||
event = bob.wait_for_incoming_msg_event()
|
while True:
|
||||||
|
event = bob.wait_for_event()
|
||||||
|
if event.kind == EventType.INCOMING_MSG:
|
||||||
chat_id = event.chat_id
|
chat_id = event.chat_id
|
||||||
msg_id = event.msg_id
|
msg_id = event.msg_id
|
||||||
|
break
|
||||||
|
|
||||||
message = bob.get_message_by_id(msg_id)
|
message = bob.get_message_by_id(msg_id)
|
||||||
snapshot = message.get_snapshot()
|
snapshot = message.get_snapshot()
|
||||||
@@ -134,12 +103,12 @@ def test_account(acfactory) -> None:
|
|||||||
assert alice.get_chatlist(snapshot=True)
|
assert alice.get_chatlist(snapshot=True)
|
||||||
assert alice.get_qr_code()
|
assert alice.get_qr_code()
|
||||||
assert alice.get_fresh_messages()
|
assert alice.get_fresh_messages()
|
||||||
|
assert alice.get_next_messages()
|
||||||
|
|
||||||
# Test sending empty message.
|
# Test sending empty message.
|
||||||
assert len(bob.wait_next_messages()) == 0
|
assert len(bob.wait_next_messages()) == 0
|
||||||
alice_chat_bob.send_text("")
|
alice_chat_bob.send_text("")
|
||||||
messages = bob.wait_next_messages()
|
messages = bob.wait_next_messages()
|
||||||
assert bob.get_next_messages() == messages
|
|
||||||
assert len(messages) == 1
|
assert len(messages) == 1
|
||||||
message = messages[0]
|
message = messages[0]
|
||||||
snapshot = message.get_snapshot()
|
snapshot = message.get_snapshot()
|
||||||
@@ -228,9 +197,7 @@ def test_chat(acfactory) -> None:
|
|||||||
group.get_fresh_message_count()
|
group.get_fresh_message_count()
|
||||||
group.mark_noticed()
|
group.mark_noticed()
|
||||||
assert group.get_contacts()
|
assert group.get_contacts()
|
||||||
assert group.get_past_contacts() == []
|
group.remove_contact(alice_chat_bob)
|
||||||
group.remove_contact(alice_contact_bob)
|
|
||||||
assert len(group.get_past_contacts()) == 1
|
|
||||||
group.get_locations()
|
group.get_locations()
|
||||||
|
|
||||||
|
|
||||||
@@ -244,7 +211,6 @@ def test_contact(acfactory) -> None:
|
|||||||
assert repr(alice_contact_bob)
|
assert repr(alice_contact_bob)
|
||||||
alice_contact_bob.block()
|
alice_contact_bob.block()
|
||||||
alice_contact_bob.unblock()
|
alice_contact_bob.unblock()
|
||||||
alice_contact_bob.reset_encryption()
|
|
||||||
alice_contact_bob.set_name("new name")
|
alice_contact_bob.set_name("new name")
|
||||||
alice_contact_bob.get_encryption_info()
|
alice_contact_bob.get_encryption_info()
|
||||||
snapshot = alice_contact_bob.get_snapshot()
|
snapshot = alice_contact_bob.get_snapshot()
|
||||||
@@ -284,59 +250,6 @@ def test_message(acfactory) -> None:
|
|||||||
assert reactions == snapshot.reactions
|
assert reactions == snapshot.reactions
|
||||||
|
|
||||||
|
|
||||||
def test_selfavatar_sync(acfactory, data, log) -> None:
|
|
||||||
alice = acfactory.get_online_account()
|
|
||||||
|
|
||||||
log.section("Alice adds a second device")
|
|
||||||
alice2 = alice.clone()
|
|
||||||
|
|
||||||
log.section("Second device goes online")
|
|
||||||
alice2.start_io()
|
|
||||||
|
|
||||||
log.section("First device changes avatar")
|
|
||||||
image = data.get_path("image/avatar1000x1000.jpg")
|
|
||||||
alice.set_config("selfavatar", image)
|
|
||||||
avatar_config = alice.get_config("selfavatar")
|
|
||||||
avatar_hash = os.path.basename(avatar_config)
|
|
||||||
print("Info: avatar hash is ", avatar_hash)
|
|
||||||
|
|
||||||
log.section("First device receives avatar change")
|
|
||||||
alice2.wait_for_event(EventType.SELFAVATAR_CHANGED)
|
|
||||||
avatar_config2 = alice2.get_config("selfavatar")
|
|
||||||
avatar_hash2 = os.path.basename(avatar_config2)
|
|
||||||
print("Info: avatar hash on second device is ", avatar_hash2)
|
|
||||||
assert avatar_hash == avatar_hash2
|
|
||||||
assert avatar_config != avatar_config2
|
|
||||||
|
|
||||||
|
|
||||||
def test_reaction_seen_on_another_dev(acfactory) -> None:
|
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
|
||||||
alice2 = alice.clone()
|
|
||||||
alice2.start_io()
|
|
||||||
|
|
||||||
bob_addr = bob.get_config("addr")
|
|
||||||
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
|
|
||||||
alice_chat_bob = alice_contact_bob.create_chat()
|
|
||||||
alice_chat_bob.send_text("Hello!")
|
|
||||||
|
|
||||||
event = bob.wait_for_incoming_msg_event()
|
|
||||||
msg_id = event.msg_id
|
|
||||||
|
|
||||||
message = bob.get_message_by_id(msg_id)
|
|
||||||
snapshot = message.get_snapshot()
|
|
||||||
snapshot.chat.accept()
|
|
||||||
message.send_reaction("😎")
|
|
||||||
for a in [alice, alice2]:
|
|
||||||
a.wait_for_event(EventType.INCOMING_REACTION)
|
|
||||||
|
|
||||||
alice2.clear_all_events()
|
|
||||||
alice_chat_bob.mark_noticed()
|
|
||||||
chat_id = alice2.wait_for_event(EventType.MSGS_NOTICED).chat_id
|
|
||||||
alice2_contact_bob = alice2.get_contact_by_addr(bob_addr)
|
|
||||||
alice2_chat_bob = alice2_contact_bob.create_chat()
|
|
||||||
assert chat_id == alice2_chat_bob.id
|
|
||||||
|
|
||||||
|
|
||||||
def test_is_bot(acfactory) -> None:
|
def test_is_bot(acfactory) -> None:
|
||||||
"""Test that we can recognize messages submitted by bots."""
|
"""Test that we can recognize messages submitted by bots."""
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
@@ -349,12 +262,16 @@ def test_is_bot(acfactory) -> None:
|
|||||||
alice.set_config("bot", "1")
|
alice.set_config("bot", "1")
|
||||||
alice_chat_bob.send_text("Hello!")
|
alice_chat_bob.send_text("Hello!")
|
||||||
|
|
||||||
event = bob.wait_for_incoming_msg_event()
|
while True:
|
||||||
message = bob.get_message_by_id(event.msg_id)
|
event = bob.wait_for_event()
|
||||||
|
if event.kind == EventType.INCOMING_MSG:
|
||||||
|
msg_id = event.msg_id
|
||||||
|
message = bob.get_message_by_id(msg_id)
|
||||||
snapshot = message.get_snapshot()
|
snapshot = message.get_snapshot()
|
||||||
assert snapshot.chat_id == event.chat_id
|
assert snapshot.chat_id == event.chat_id
|
||||||
assert snapshot.text == "Hello!"
|
assert snapshot.text == "Hello!"
|
||||||
assert snapshot.is_bot
|
assert snapshot.is_bot
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def test_bot(acfactory) -> None:
|
def test_bot(acfactory) -> None:
|
||||||
@@ -481,7 +398,7 @@ def test_provider_info(rpc) -> None:
|
|||||||
assert provider_info["id"] == "gmail"
|
assert provider_info["id"] == "gmail"
|
||||||
|
|
||||||
# Disable MX record resolution.
|
# Disable MX record resolution.
|
||||||
rpc.set_config(account_id, "proxy_enabled", "1")
|
rpc.set_config(account_id, "socks5_enabled", "1")
|
||||||
provider_info = rpc.get_provider_info(account_id, "github.com")
|
provider_info = rpc.get_provider_info(account_id, "github.com")
|
||||||
assert provider_info is None
|
assert provider_info is None
|
||||||
|
|
||||||
@@ -515,7 +432,10 @@ def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
|
|||||||
|
|
||||||
# Alice reads Bob's message.
|
# Alice reads Bob's message.
|
||||||
message.mark_seen()
|
message.mark_seen()
|
||||||
bob.wait_for_event(EventType.MSG_READ)
|
while True:
|
||||||
|
event = bob.wait_for_event()
|
||||||
|
if event.kind == EventType.MSG_READ:
|
||||||
|
break
|
||||||
|
|
||||||
# Bob sends a message to Alice, it should also be encrypted.
|
# Bob sends a message to Alice, it should also be encrypted.
|
||||||
bob_chat_alice.send_text("Hi Alice!")
|
bob_chat_alice.send_text("Hi Alice!")
|
||||||
@@ -580,7 +500,7 @@ def test_reaction_to_partially_fetched_msg(acfactory, tmp_path):
|
|||||||
assert list(reactions.reactions_by_contact.values())[0] == [react_str]
|
assert list(reactions.reactions_by_contact.values())[0] == [react_str]
|
||||||
|
|
||||||
|
|
||||||
def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
def test_reactions_for_a_reordering_move(acfactory):
|
||||||
"""When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
|
"""When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
|
||||||
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
|
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
|
||||||
processed by receive_imf in the wrong order, and, particularly, reactions were processed before
|
processed by receive_imf in the wrong order, and, particularly, reactions were processed before
|
||||||
@@ -604,7 +524,7 @@ def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
|||||||
msg1.send_reaction(react_str).wait_until_delivered()
|
msg1.send_reaction(react_str).wait_until_delivered()
|
||||||
|
|
||||||
logging.info("moving messages to ac2's DeltaChat folder in the reverse order")
|
logging.info("moving messages to ac2's DeltaChat folder in the reverse order")
|
||||||
ac2_direct_imap = direct_imap(ac2)
|
ac2_direct_imap = DirectImap(ac2)
|
||||||
ac2_direct_imap.connect()
|
ac2_direct_imap.connect()
|
||||||
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
|
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
|
||||||
ac2_direct_imap.conn.move(uid, "DeltaChat")
|
ac2_direct_imap.conn.move(uid, "DeltaChat")
|
||||||
@@ -666,7 +586,7 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
|
|||||||
assert snapshot.chat == bob_chat_alice
|
assert snapshot.chat == bob_chat_alice
|
||||||
|
|
||||||
|
|
||||||
def test_markseen_contact_request(acfactory):
|
def test_markseen_contact_request(acfactory, tmp_path):
|
||||||
"""
|
"""
|
||||||
Test that seen status is synchronized for contact request messages
|
Test that seen status is synchronized for contact request messages
|
||||||
even though read receipt is not sent.
|
even though read receipt is not sent.
|
||||||
@@ -674,7 +594,10 @@ def test_markseen_contact_request(acfactory):
|
|||||||
alice, bob = acfactory.get_online_accounts(2)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
# Bob sets up a second device.
|
# Bob sets up a second device.
|
||||||
bob2 = bob.clone()
|
bob.export_backup(tmp_path)
|
||||||
|
files = list(tmp_path.glob("*.tar"))
|
||||||
|
bob2 = acfactory.get_unconfigured_account()
|
||||||
|
bob2.import_backup(files[0])
|
||||||
bob2.start_io()
|
bob2.start_io()
|
||||||
|
|
||||||
alice_chat_bob = alice.create_chat(bob)
|
alice_chat_bob = alice.create_chat(bob)
|
||||||
@@ -685,60 +608,8 @@ def test_markseen_contact_request(acfactory):
|
|||||||
assert message2.get_snapshot().state == MessageState.IN_FRESH
|
assert message2.get_snapshot().state == MessageState.IN_FRESH
|
||||||
|
|
||||||
message.mark_seen()
|
message.mark_seen()
|
||||||
bob2.wait_for_event(EventType.MSGS_NOTICED)
|
while True:
|
||||||
|
event = bob2.wait_for_event()
|
||||||
|
if event.kind == EventType.MSGS_NOTICED:
|
||||||
|
break
|
||||||
assert message2.get_snapshot().state == MessageState.IN_SEEN
|
assert message2.get_snapshot().state == MessageState.IN_SEEN
|
||||||
|
|
||||||
|
|
||||||
def test_get_http_response(acfactory):
|
|
||||||
alice = acfactory.new_configured_account()
|
|
||||||
http_response = alice._rpc.get_http_response(alice.id, "https://example.org")
|
|
||||||
assert http_response["mimetype"] == "text/html"
|
|
||||||
assert b"<title>Example Domain</title>" in base64.b64decode((http_response["blob"] + "==").encode())
|
|
||||||
|
|
||||||
|
|
||||||
def test_configured_imap_certificate_checks(acfactory):
|
|
||||||
alice = acfactory.new_configured_account()
|
|
||||||
configured_certificate_checks = alice.get_config("configured_imap_certificate_checks")
|
|
||||||
|
|
||||||
# Certificate checks should be configured (not None)
|
|
||||||
assert configured_certificate_checks
|
|
||||||
|
|
||||||
# 0 is the value old Delta Chat core versions used
|
|
||||||
# to mean user entered "imap_certificate_checks=0" (Automatic)
|
|
||||||
# and configuration failed to use strict TLS checks
|
|
||||||
# so it switched strict TLS checks off.
|
|
||||||
#
|
|
||||||
# New versions of Delta Chat are not disabling TLS checks
|
|
||||||
# unless users explicitly disables them
|
|
||||||
# or provider database says provider has invalid certificates.
|
|
||||||
#
|
|
||||||
# Core 1.142.4, 1.142.5 and 1.142.6 saved this value due to bug.
|
|
||||||
# This test is a regression test to prevent this happening again.
|
|
||||||
assert configured_certificate_checks != "0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_old_msg_is_fresh(acfactory):
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac1_clone = ac1.clone()
|
|
||||||
ac1_clone.start_io()
|
|
||||||
|
|
||||||
ac1.create_chat(ac2)
|
|
||||||
ac1_clone_chat = ac1_clone.create_chat(ac2)
|
|
||||||
|
|
||||||
ac1.get_device_chat().mark_noticed()
|
|
||||||
|
|
||||||
logging.info("Send a first message from ac2 to ac1 and check that it's 'fresh'")
|
|
||||||
first_msg = ac2.create_chat(ac1).send_text("Hi")
|
|
||||||
ac1.wait_for_incoming_msg_event()
|
|
||||||
assert ac1.create_chat(ac2).get_fresh_message_count() == 1
|
|
||||||
assert len(list(ac1.get_fresh_messages())) == 1
|
|
||||||
|
|
||||||
ac1.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
|
||||||
|
|
||||||
logging.info("Send a message from ac1_clone to ac2 and check that ac1 marks the first message as 'noticed'")
|
|
||||||
ac1_clone_chat.send_text("Hi back")
|
|
||||||
ev = ac1.wait_for_msgs_noticed_event()
|
|
||||||
|
|
||||||
assert ev.chat_id == first_msg.get_snapshot().chat_id
|
|
||||||
assert ac1.create_chat(ac2).get_fresh_message_count() == 0
|
|
||||||
assert len(list(ac1.get_fresh_messages())) == 0
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
def test_vcard(acfactory) -> None:
|
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
|
||||||
|
|
||||||
bob_addr = bob.get_config("addr")
|
|
||||||
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
|
|
||||||
alice_contact_charlie = alice.create_contact("charlie@example.org", "Charlie")
|
|
||||||
|
|
||||||
alice_chat_bob = alice_contact_bob.create_chat()
|
|
||||||
alice_chat_bob.send_contact(alice_contact_charlie)
|
|
||||||
|
|
||||||
event = bob.wait_for_incoming_msg_event()
|
|
||||||
message = bob.get_message_by_id(event.msg_id)
|
|
||||||
snapshot = message.get_snapshot()
|
|
||||||
assert snapshot.vcard_contact
|
|
||||||
assert snapshot.vcard_contact.addr == "charlie@example.org"
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
from deltachat_rpc_client import EventType
|
||||||
|
|
||||||
|
|
||||||
def test_webxdc(acfactory) -> None:
|
def test_webxdc(acfactory) -> None:
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
@@ -6,9 +9,12 @@ def test_webxdc(acfactory) -> None:
|
|||||||
alice_chat_bob = alice_contact_bob.create_chat()
|
alice_chat_bob = alice_contact_bob.create_chat()
|
||||||
alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
||||||
|
|
||||||
event = bob.wait_for_incoming_msg_event()
|
while True:
|
||||||
|
event = bob.wait_for_event()
|
||||||
|
if event.kind == EventType.INCOMING_MSG:
|
||||||
bob_chat_alice = bob.get_chat_by_id(event.chat_id)
|
bob_chat_alice = bob.get_chat_by_id(event.chat_id)
|
||||||
message = bob.get_message_by_id(event.msg_id)
|
message = bob.get_message_by_id(event.msg_id)
|
||||||
|
break
|
||||||
|
|
||||||
webxdc_info = message.get_webxdc_info()
|
webxdc_info = message.get_webxdc_info()
|
||||||
assert webxdc_info == {
|
assert webxdc_info == {
|
||||||
@@ -18,9 +24,6 @@ def test_webxdc(acfactory) -> None:
|
|||||||
"name": "Chess Board",
|
"name": "Chess Board",
|
||||||
"sourceCodeUrl": None,
|
"sourceCodeUrl": None,
|
||||||
"summary": None,
|
"summary": None,
|
||||||
"selfAddr": webxdc_info["selfAddr"],
|
|
||||||
"sendUpdateInterval": 1000,
|
|
||||||
"sendUpdateMaxSize": 18874368,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
status_updates = message.get_webxdc_status_updates()
|
status_updates = message.get_webxdc_status_updates()
|
||||||
|
|||||||
123
deltachat-rpc-client/tests/test_webxdc_iroh.py
Normal file
123
deltachat-rpc-client/tests/test_webxdc_iroh.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Testing webxdc iroh connectivity
|
||||||
|
|
||||||
|
If you want to debug iroh at rust-trace/log level set
|
||||||
|
|
||||||
|
RUST_LOG=iroh_net=trace,iroh_gossip=trace
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from deltachat_rpc_client import DeltaChat, EventType, SpecialContactId
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def path_to_webxdc():
|
||||||
|
return "../test-data/webxdc/chess.xdc"
|
||||||
|
|
||||||
|
|
||||||
|
def test_realtime_sequentially(acfactory, path_to_webxdc):
|
||||||
|
"""Test two peers trying to establish connection sequentially."""
|
||||||
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
ac1.create_chat(ac2)
|
||||||
|
ac2.create_chat(ac1)
|
||||||
|
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping0")
|
||||||
|
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
|
assert snapshot.text == "ping0"
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print()
|
||||||
|
print("*" * 80 + "\n" + msg + "\n", file=sys.stderr)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# share a webxdc app between ac1 and ac2
|
||||||
|
ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
|
||||||
|
ac2_webxdc_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
|
||||||
|
snapshot = ac2_webxdc_msg.get_snapshot()
|
||||||
|
assert snapshot.text == "play"
|
||||||
|
|
||||||
|
# send iroh announcements sequentially
|
||||||
|
log("sending ac1 -> ac2 realtime advertisement and additional message")
|
||||||
|
ac1._rpc.send_webxdc_realtime_advertisement(ac1.id, ac1_webxdc_msg.id)
|
||||||
|
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
|
||||||
|
|
||||||
|
log("waiting for incoming message on ac2")
|
||||||
|
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
|
assert snapshot.text == "ping1"
|
||||||
|
|
||||||
|
log("sending ac2 -> ac1 realtime advertisement and additional message")
|
||||||
|
ac2._rpc.send_webxdc_realtime_advertisement(ac2.id, ac2_webxdc_msg.id)
|
||||||
|
acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
|
||||||
|
|
||||||
|
log("waiting for incoming message on ac1")
|
||||||
|
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
|
assert snapshot.text == "ping2"
|
||||||
|
|
||||||
|
log("sending realtime data ac1 -> ac2")
|
||||||
|
ac1._rpc.send_webxdc_realtime_data(ac1.id, ac1_webxdc_msg.id, [13, 15, 17])
|
||||||
|
|
||||||
|
log("ac2: waiting for realtime data")
|
||||||
|
while 1:
|
||||||
|
event = ac2.wait_for_event()
|
||||||
|
if event.kind == EventType.WEBXDC_REALTIME_DATA:
|
||||||
|
assert event.data == [13, 15, 17]
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def test_realtime_simultaneously(acfactory, path_to_webxdc):
|
||||||
|
"""Test two peers trying to establish connection simultaneously."""
|
||||||
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
ac1.create_chat(ac2)
|
||||||
|
ac2.create_chat(ac1)
|
||||||
|
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping0")
|
||||||
|
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
|
assert snapshot.text == "ping0"
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print()
|
||||||
|
print("*" * 80 + "\n" + msg + "\n", file=sys.stderr)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# share a webxdc app between ac1 and ac2
|
||||||
|
ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
|
||||||
|
ac2_webxdc_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
|
||||||
|
snapshot = ac2_webxdc_msg.get_snapshot()
|
||||||
|
assert snapshot.text == "play"
|
||||||
|
|
||||||
|
# send iroh announcements simultaneously
|
||||||
|
log("sending ac1 -> ac2 realtime advertisement and additional message")
|
||||||
|
ac1._rpc.send_webxdc_realtime_advertisement(ac1.id, ac1_webxdc_msg.id)
|
||||||
|
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
|
||||||
|
|
||||||
|
log("sending ac2 -> ac1 realtime advertisement and additional message")
|
||||||
|
ac2._rpc.send_webxdc_realtime_advertisement(ac2.id, ac2_webxdc_msg.id)
|
||||||
|
acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
|
||||||
|
|
||||||
|
# Ensure that advertisements have been received.
|
||||||
|
|
||||||
|
log("waiting for incoming message on ac2")
|
||||||
|
snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
|
assert snapshot.text == "ping1"
|
||||||
|
|
||||||
|
log("waiting for incoming message on ac1")
|
||||||
|
snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||||
|
assert snapshot.text == "ping2"
|
||||||
|
|
||||||
|
log("sending realtime data ac1 -> ac2")
|
||||||
|
ac1._rpc.send_webxdc_realtime_data(ac1.id, ac1_webxdc_msg.id, [13, 15, 17])
|
||||||
|
|
||||||
|
log("ac2: waiting for realtime data")
|
||||||
|
while 1:
|
||||||
|
event = ac2.wait_for_event()
|
||||||
|
if event.kind == EventType.WEBXDC_REALTIME_DATA:
|
||||||
|
assert event.data == [13, 15, 17]
|
||||||
|
break
|
||||||
@@ -16,7 +16,6 @@ deps =
|
|||||||
pytest
|
pytest
|
||||||
pytest-timeout
|
pytest-timeout
|
||||||
pytest-xdist
|
pytest-xdist
|
||||||
imap-tools
|
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-rpc-server"
|
name = "deltachat-rpc-server"
|
||||||
version = "1.157.3"
|
version = "1.138.5"
|
||||||
description = "DeltaChat JSON-RPC server"
|
description = "DeltaChat JSON-RPC server"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -10,18 +10,19 @@ keywords = ["deltachat", "chat", "openpgp", "email", "encryption"]
|
|||||||
categories = ["cryptography", "std", "email"]
|
categories = ["cryptography", "std", "email"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deltachat-jsonrpc = { workspace = true }
|
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", default-features = false }
|
||||||
deltachat = { workspace = true }
|
deltachat = { path = "..", default-features = false }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = "1"
|
||||||
futures-lite = { workspace = true }
|
env_logger = { version = "0.11.3" }
|
||||||
log = { workspace = true }
|
futures-lite = "2.3.0"
|
||||||
serde_json = { workspace = true }
|
log = "0.4"
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde_json = "1"
|
||||||
tokio = { workspace = true, features = ["io-std"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio-util = { workspace = true }
|
tokio = { version = "1.37.0", features = ["io-std"] }
|
||||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
tokio-util = "0.7.9"
|
||||||
yerpc = { workspace = true, features = ["anyhow_expose", "openrpc"] }
|
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored"]
|
default = ["vendored"]
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ over standard I/O.
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
To download binary pre-builds check the [releases page](https://github.com/chatmail/core/releases).
|
To download binary pre-builds check the [releases page](https://github.com/deltachat/deltachat-core-rust/releases).
|
||||||
Rename the downloaded binary to `deltachat-rpc-server` and add it to your `PATH`.
|
Rename the downloaded binary to `deltachat-rpc-server` and add it to your `PATH`.
|
||||||
|
|
||||||
To install from source run:
|
To install from source run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install --git https://github.com/chatmail/core/ deltachat-rpc-server
|
cargo install --git https://github.com/deltachat/deltachat-core-rust/ deltachat-rpc-server
|
||||||
```
|
```
|
||||||
|
|
||||||
The `deltachat-rpc-server` executable will be installed into `$HOME/.cargo/bin` that should be available
|
The `deltachat-rpc-server` executable will be installed into `$HOME/.cargo/bin` that should be available
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ This simplifies cross-compilation and even reduces binary size (no CFFI layer an
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
> The **minimum** nodejs version for this package is `16`
|
> The **minimum** nodejs version for this package is `20.11`
|
||||||
|
|
||||||
```
|
```
|
||||||
npm i @deltachat/stdio-rpc-server @deltachat/jsonrpc-client
|
npm i @deltachat/stdio-rpc-server @deltachat/jsonrpc-client
|
||||||
@@ -20,9 +20,7 @@ import { C } from "@deltachat/jsonrpc-client";
|
|||||||
async function main() {
|
async function main() {
|
||||||
const dc = await startDeltaChat("deltachat-data");
|
const dc = await startDeltaChat("deltachat-data");
|
||||||
console.log(await dc.rpc.getSystemInfo());
|
console.log(await dc.rpc.getSystemInfo());
|
||||||
dc.close()
|
|
||||||
}
|
}
|
||||||
main()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For a more complete example refer to https://github.com/deltachat-bot/echo/pull/69/files (TODO change link when pr is merged).
|
For a more complete example refer to https://github.com/deltachat-bot/echo/pull/69/files (TODO change link when pr is merged).
|
||||||
@@ -46,11 +44,12 @@ references:
|
|||||||
When you import this package it searches for the rpc server in the following locations and order:
|
When you import this package it searches for the rpc server in the following locations and order:
|
||||||
|
|
||||||
1. `DELTA_CHAT_RPC_SERVER` environment variable
|
1. `DELTA_CHAT_RPC_SERVER` environment variable
|
||||||
2. use the PATH when `{takeVersionFromPATH: true}` is supplied in the options.
|
2. in PATH
|
||||||
|
- unless `DELTA_CHAT_SKIP_PATH=1` is specified
|
||||||
|
- searches in .cargo/bin directory first
|
||||||
|
- but there an additional version check is performed
|
||||||
3. prebuilds in npm packages
|
3. prebuilds in npm packages
|
||||||
|
|
||||||
so by default it uses the prebuilds.
|
|
||||||
|
|
||||||
## How do you built this package in CI
|
## How do you built this package in CI
|
||||||
|
|
||||||
- To build platform packages, run the `build_platform_package.py` script:
|
- To build platform packages, run the `build_platform_package.py` script:
|
||||||
@@ -65,13 +64,13 @@ so by default it uses the prebuilds.
|
|||||||
- this will run `update_optional_dependencies_and_version.js` (in the `prepack` script),
|
- this will run `update_optional_dependencies_and_version.js` (in the `prepack` script),
|
||||||
which puts all platform packages into `optionalDependencies` and updates the `version` in `package.json`
|
which puts all platform packages into `optionalDependencies` and updates the `version` in `package.json`
|
||||||
|
|
||||||
## How to build a version you can use locally on your host machine for development
|
## How to build a version you can use localy on your host machine for development
|
||||||
|
|
||||||
You can not install the npm packet from the previous section locally, unless you have a local npm registry set up where you upload it too. This is why we have separate scripts for making it work for local installation.
|
You can not install the npm packet from the previous section locally, unless you have a local npm registry set up where you upload it too. This is why we have seperate scripts for making it work for local installation.
|
||||||
|
|
||||||
- If you just need your host platform run `python scripts/make_local_dev_version.py`
|
- If you just need your host platform run `python scripts/make_local_dev_version.py`
|
||||||
- note: this clears the `platform_package` folder
|
- note: this clears the `platform_package` folder
|
||||||
- (advanced) If you need more than one platform for local install you can just run `node scripts/update_optional_dependencies_and_version.js` after building multiple platforms with `build_platform_package.py`
|
- (advanced) If you need more than one platform for local install you can just run `node scripts/update_optional_dependencies_and_version.js` after building multiple plaftorms with `build_platform_package.py`
|
||||||
|
|
||||||
## Thanks to nlnet
|
## Thanks to nlnet
|
||||||
|
|
||||||
|
|||||||
4
deltachat-rpc-server/npm-package/index.d.ts
vendored
4
deltachat-rpc-server/npm-package/index.d.ts
vendored
@@ -1,8 +1,8 @@
|
|||||||
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
|
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
|
||||||
|
|
||||||
export interface SearchOptions {
|
export interface SearchOptions {
|
||||||
/** whether take deltachat-rpc-server inside of $PATH*/
|
/** whether to disable looking for deltachat-rpc-server inside of $PATH */
|
||||||
takeVersionFromPATH: boolean;
|
skipSearchInPath: boolean;
|
||||||
|
|
||||||
/** whether to disable the DELTA_CHAT_RPC_SERVER environment variable */
|
/** whether to disable the DELTA_CHAT_RPC_SERVER environment variable */
|
||||||
disableEnvPath: boolean;
|
disableEnvPath: boolean;
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
//@ts-check
|
//@ts-check
|
||||||
import { spawn } from "node:child_process";
|
import { execFile, spawn } from "node:child_process";
|
||||||
import { stat } from "node:fs/promises";
|
import { stat, readdir } from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
|
import { join, basename } from "node:path";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
import { ENV_VAR_NAME, PATH_EXECUTABLE_NAME } from "./src/const.js";
|
import { promisify } from "node:util";
|
||||||
|
import {
|
||||||
|
ENV_VAR_NAME,
|
||||||
|
PATH_EXECUTABLE_NAME,
|
||||||
|
SKIP_SEARCH_IN_PATH,
|
||||||
|
} from "./src/const.js";
|
||||||
import {
|
import {
|
||||||
ENV_VAR_LOCATION_NOT_FOUND,
|
ENV_VAR_LOCATION_NOT_FOUND,
|
||||||
FAILED_TO_START_SERVER_EXECUTABLE,
|
FAILED_TO_START_SERVER_EXECUTABLE,
|
||||||
@@ -11,6 +17,9 @@ import {
|
|||||||
NPM_NOT_FOUND_UNSUPPORTED_PLATFORM_ERROR,
|
NPM_NOT_FOUND_UNSUPPORTED_PLATFORM_ERROR,
|
||||||
} from "./src/errors.js";
|
} from "./src/errors.js";
|
||||||
|
|
||||||
|
// Because this is not compiled by typescript, esm needs this stuff (` with { type: "json" };`,
|
||||||
|
// nodejs still complains about it being experimental, but deno also uses it, so treefit bets taht it will become standard)
|
||||||
|
import package_json from "./package.json" with { type: "json" };
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
|
|
||||||
function findRPCServerInNodeModules() {
|
function findRPCServerInNodeModules() {
|
||||||
@@ -22,12 +31,7 @@ function findRPCServerInNodeModules() {
|
|||||||
return resolve(package_name);
|
return resolve(package_name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug("findRpcServerInNodeModules", error);
|
console.debug("findRpcServerInNodeModules", error);
|
||||||
const require = createRequire(import.meta.url);
|
if (Object.keys(package_json.optionalDependencies).includes(package_name)) {
|
||||||
if (
|
|
||||||
Object.keys(require("./package.json").optionalDependencies).includes(
|
|
||||||
package_name
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new Error(NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR(package_name));
|
throw new Error(NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR(package_name));
|
||||||
} else {
|
} else {
|
||||||
throw new Error(NPM_NOT_FOUND_UNSUPPORTED_PLATFORM_ERROR());
|
throw new Error(NPM_NOT_FOUND_UNSUPPORTED_PLATFORM_ERROR());
|
||||||
@@ -35,13 +39,38 @@ function findRPCServerInNodeModules() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
async function getLocationInPath() {
|
||||||
|
const exec = promisify(execFile);
|
||||||
|
|
||||||
|
if (os.platform() === "win32") {
|
||||||
|
const { stdout: executable } = await exec("where", [PATH_EXECUTABLE_NAME], {
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
return executable;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout: executable } = await exec(
|
||||||
|
"command",
|
||||||
|
["-v", PATH_EXECUTABLE_NAME],
|
||||||
|
{ shell: true }
|
||||||
|
);
|
||||||
|
return executable;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code > 0) return "";
|
||||||
|
else throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import("./index").FnTypes.getRPCServerPath} */
|
/** @type {import("./index").FnTypes.getRPCServerPath} */
|
||||||
export async function getRPCServerPath(options = {}) {
|
export async function getRPCServerPath(
|
||||||
const { takeVersionFromPATH, disableEnvPath } = {
|
options = { skipSearchInPath: false, disableEnvPath: false }
|
||||||
takeVersionFromPATH: false,
|
) {
|
||||||
disableEnvPath: false,
|
// @TODO: improve confusing naming of these options
|
||||||
...options,
|
const { skipSearchInPath, disableEnvPath } = options;
|
||||||
};
|
|
||||||
// 1. check if it is set as env var
|
// 1. check if it is set as env var
|
||||||
if (process.env[ENV_VAR_NAME] && !disableEnvPath) {
|
if (process.env[ENV_VAR_NAME] && !disableEnvPath) {
|
||||||
try {
|
try {
|
||||||
@@ -56,9 +85,35 @@ export async function getRPCServerPath(options = {}) {
|
|||||||
return process.env[ENV_VAR_NAME];
|
return process.env[ENV_VAR_NAME];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. check if PATH should be used
|
// 2. check if it can be found in PATH
|
||||||
if (takeVersionFromPATH) {
|
if (!process.env[SKIP_SEARCH_IN_PATH] && !skipSearchInPath) {
|
||||||
return PATH_EXECUTABLE_NAME;
|
const executable = await getLocationInPath();
|
||||||
|
|
||||||
|
// by just trying to execute it and then use "command -v deltachat-rpc-server" (unix) or "where deltachat-rpc-server" (windows) to get the path to the executable
|
||||||
|
if (executable.length > 1) {
|
||||||
|
// test if it is the right version
|
||||||
|
try {
|
||||||
|
// for some unknown reason it is in stderr and not in stdout
|
||||||
|
const { stderr } = await promisify(execFile)(
|
||||||
|
executable,
|
||||||
|
["--version"],
|
||||||
|
{ shell: true }
|
||||||
|
);
|
||||||
|
const version = stderr.slice(0, stderr.indexOf("\n"));
|
||||||
|
if (package_json.version !== version) {
|
||||||
|
throw new Error(
|
||||||
|
`version mismatch: (npm package: ${package_json.version}) (installed ${PATH_EXECUTABLE_NAME} version: ${version})`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return executable;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"Found executable in PATH, but there was an error: " + error
|
||||||
|
);
|
||||||
|
console.error("So falling back to using prebuild...");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 3. check for prebuilds
|
// 3. check for prebuilds
|
||||||
|
|
||||||
@@ -68,11 +123,11 @@ export async function getRPCServerPath(options = {}) {
|
|||||||
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
|
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
|
||||||
|
|
||||||
/** @type {import("./index").FnTypes.startDeltaChat} */
|
/** @type {import("./index").FnTypes.startDeltaChat} */
|
||||||
export async function startDeltaChat(directory, options = {}) {
|
export async function startDeltaChat(directory, options) {
|
||||||
const pathToServerBinary = await getRPCServerPath(options);
|
const pathToServerBinary = await getRPCServerPath(options);
|
||||||
const server = spawn(pathToServerBinary, {
|
const server = spawn(pathToServerBinary, {
|
||||||
env: {
|
env: {
|
||||||
RUST_LOG: process.env.RUST_LOG,
|
RUST_LOG: process.env.RUST_LOG || "info",
|
||||||
DC_ACCOUNTS_PATH: directory,
|
DC_ACCOUNTS_PATH: directory,
|
||||||
},
|
},
|
||||||
stdio: ["pipe", "pipe", options.muteStdErr ? "ignore" : "inherit"],
|
stdio: ["pipe", "pipe", options.muteStdErr ? "ignore" : "inherit"],
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/chatmail/core.git"
|
"url": "https://github.com/deltachat/deltachat-core-rust.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepack": "node scripts/update_optional_dependencies_and_version.js"
|
"prepack": "node scripts/update_optional_dependencies_and_version.js"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"version": "1.157.3"
|
"version": "1.138.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def write_package_json(platform_path, rust_target, my_binary_name):
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/chatmail/core.git",
|
"url": "https://github.com/deltachat/deltachat-core-rust.git",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const expected_cwd = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|||||||
|
|
||||||
if (process.cwd() !== expected_cwd) {
|
if (process.cwd() !== expected_cwd) {
|
||||||
console.error(
|
console.error(
|
||||||
"CWD mismatch: this script needs to be run from " + expected_cwd,
|
"CWD missmatch: this script needs to be run from " + expected_cwd,
|
||||||
{ actual: process.cwd(), expected: expected_cwd }
|
{ actual: process.cwd(), expected: expected_cwd }
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -40,7 +40,7 @@ const platform_package_names = await Promise.all(
|
|||||||
"has a different version than the version of the rpc server.",
|
"has a different version than the version of the rpc server.",
|
||||||
{ rpc_server: version, platform_package: p.version }
|
{ rpc_server: version, platform_package: p.version }
|
||||||
);
|
);
|
||||||
throw new Error("version mismatch");
|
throw new Error("version missmatch");
|
||||||
}
|
}
|
||||||
return { folder_name: name, package_name: p.name };
|
return { folder_name: name, package_name: p.name };
|
||||||
})
|
})
|
||||||
@@ -55,10 +55,9 @@ for (const { folder_name, package_name } of platform_package_names) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_local) {
|
if (is_local) {
|
||||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] =
|
package_json.peerDependencies["@deltachat/jsonrpc-client"] = 'file:../../deltachat-jsonrpc/typescript'
|
||||||
`file:${join(expected_cwd, "/../../deltachat-jsonrpc/typescript")}`;
|
|
||||||
} else {
|
} else {
|
||||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] = "*";
|
package_json.peerDependencies["@deltachat/jsonrpc-client"] = "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile("./package.json", JSON.stringify(package_json, null, 4));
|
await fs.writeFile("./package.json", JSON.stringify(package_json, null, 4));
|
||||||
|
|||||||
@@ -3,3 +3,4 @@
|
|||||||
export const PATH_EXECUTABLE_NAME = 'deltachat-rpc-server'
|
export const PATH_EXECUTABLE_NAME = 'deltachat-rpc-server'
|
||||||
|
|
||||||
export const ENV_VAR_NAME = "DELTA_CHAT_RPC_SERVER"
|
export const ENV_VAR_NAME = "DELTA_CHAT_RPC_SERVER"
|
||||||
|
export const SKIP_SEARCH_IN_PATH = "DELTA_CHAT_SKIP_PATH"
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ENV_VAR_NAME } from "./const.js";
|
import { ENV_VAR_NAME } from "./const.js";
|
||||||
|
|
||||||
const cargoInstallCommand =
|
const cargoInstallCommand =
|
||||||
"cargo install --git https://github.com/chatmail/core deltachat-rpc-server";
|
"cargo install --git https://github.com/deltachat/deltachat-core-rust deltachat-rpc-server";
|
||||||
|
|
||||||
export function NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR(package_name) {
|
export function NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR(package_name) {
|
||||||
return `deltachat-rpc-server not found:
|
return `deltachat-rpc-server not found:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use deltachat::constants::DC_VERSION_STR;
|
|||||||
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
||||||
use futures_lite::stream::StreamExt;
|
use futures_lite::stream::StreamExt;
|
||||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||||
use yerpc::RpcServer as _;
|
use yerpc::RpcServer as _;
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
@@ -29,9 +29,6 @@ async fn main() {
|
|||||||
// "For technical reasons, stdin is implemented by using an ordinary blocking read on a separate
|
// "For technical reasons, stdin is implemented by using an ordinary blocking read on a separate
|
||||||
// thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang
|
// thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang
|
||||||
// until the user presses enter."
|
// until the user presses enter."
|
||||||
if let Err(error) = &r {
|
|
||||||
log::error!("Fatal error: {error:#}.")
|
|
||||||
}
|
|
||||||
std::process::exit(if r.is_ok() { 0 } else { 1 });
|
std::process::exit(if r.is_ok() { 0 } else { 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,13 +61,13 @@ async fn main_impl() -> Result<()> {
|
|||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let mut sigterm = signal_unix::signal(signal_unix::SignalKind::terminate())?;
|
let mut sigterm = signal_unix::signal(signal_unix::SignalKind::terminate())?;
|
||||||
|
|
||||||
// Logs from `log` crate and traces from `tracing` crate
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||||
// are configurable with `RUST_LOG` environment variable
|
|
||||||
// and go to stderr to avoid interfering with JSON-RPC using stdout.
|
tracing_subscriber::registry()
|
||||||
tracing_subscriber::fmt()
|
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
|
||||||
.with_env_filter(EnvFilter::from_default_env())
|
.with(EnvFilter::builder().from_env_lossy())
|
||||||
.with_writer(std::io::stderr)
|
.try_init()
|
||||||
.init();
|
.ok();
|
||||||
|
|
||||||
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
|
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
|
||||||
log::info!("Starting with accounts directory `{}`.", path);
|
log::info!("Starting with accounts directory `{}`.", path);
|
||||||
|
|||||||
126
deny.toml
126
deny.toml
@@ -1,5 +1,8 @@
|
|||||||
[advisories]
|
[advisories]
|
||||||
ignore = [
|
ignore = [
|
||||||
|
"RUSTSEC-2020-0071",
|
||||||
|
"RUSTSEC-2022-0093",
|
||||||
|
|
||||||
# Timing attack on RSA.
|
# Timing attack on RSA.
|
||||||
# Delta Chat does not use RSA for new keys
|
# Delta Chat does not use RSA for new keys
|
||||||
# and this requires precise measurement of the decryption time by the attacker.
|
# and this requires precise measurement of the decryption time by the attacker.
|
||||||
@@ -7,14 +10,11 @@ ignore = [
|
|||||||
# <https://rustsec.org/advisories/RUSTSEC-2023-0071>
|
# <https://rustsec.org/advisories/RUSTSEC-2023-0071>
|
||||||
"RUSTSEC-2023-0071",
|
"RUSTSEC-2023-0071",
|
||||||
|
|
||||||
# Unmaintained instant
|
# Unmaintained ansi_term
|
||||||
"RUSTSEC-2024-0384",
|
"RUSTSEC-2021-0139",
|
||||||
|
|
||||||
# Unmaintained backoff
|
# Unmaintained encoding
|
||||||
"RUSTSEC-2025-0012",
|
"RUSTSEC-2021-0153",
|
||||||
|
|
||||||
# Unmaintained paste
|
|
||||||
"RUSTSEC-2024-0436",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[bans]
|
[bans]
|
||||||
@@ -23,53 +23,86 @@ ignore = [
|
|||||||
# when upgrading.
|
# when upgrading.
|
||||||
# Please keep this list alphabetically sorted.
|
# Please keep this list alphabetically sorted.
|
||||||
skip = [
|
skip = [
|
||||||
|
{ name = "asn1-rs-derive", version = "0.4.0" },
|
||||||
|
{ name = "asn1-rs-impl", version = "0.1.0" },
|
||||||
|
{ name = "asn1-rs", version = "0.5.2" },
|
||||||
{ name = "async-channel", version = "1.9.0" },
|
{ name = "async-channel", version = "1.9.0" },
|
||||||
|
{ name = "base16ct", version = "0.1.1" },
|
||||||
{ name = "base64", version = "<0.21" },
|
{ name = "base64", version = "<0.21" },
|
||||||
{ name = "base64", version = "0.21.7" },
|
{ name = "base64", version = "0.21.7" },
|
||||||
{ name = "bitflags", version = "1.3.2" },
|
{ name = "bitflags", version = "1.3.2" },
|
||||||
{ name = "core-foundation", version = "0.9.4" },
|
{ name = "block-buffer", version = "<0.10" },
|
||||||
|
{ name = "convert_case", version = "0.4.0" },
|
||||||
|
{ name = "curve25519-dalek", version = "3.2.0" },
|
||||||
|
{ name = "darling_core", version = "<0.14" },
|
||||||
|
{ name = "darling_macro", version = "<0.14" },
|
||||||
|
{ name = "darling", version = "<0.14" },
|
||||||
|
{ name = "der_derive", version = "0.6.1" },
|
||||||
|
{ name = "derive_more", version = "0.99.17" },
|
||||||
|
{ name = "der-parser", version = "8.2.0" },
|
||||||
|
{ name = "der", version = "0.6.1" },
|
||||||
|
{ name = "digest", version = "<0.10" },
|
||||||
|
{ name = "dlopen2", version = "0.4.1" },
|
||||||
|
{ name = "ed25519-dalek", version = "1.0.1" },
|
||||||
|
{ name = "ed25519", version = "1.5.3" },
|
||||||
|
{ name = "env_logger", version = "0.10.2" },
|
||||||
{ name = "event-listener", version = "2.5.3" },
|
{ name = "event-listener", version = "2.5.3" },
|
||||||
{ name = "generator", version = "0.7.5" },
|
{ name = "event-listener", version = "4.0.3" },
|
||||||
{ name = "getrandom", version = "0.2.12" },
|
{ name = "fastrand", version = "1.9.0" },
|
||||||
{ name = "heck", version = "0.4.1" },
|
{ name = "futures-lite", version = "1.13.0" },
|
||||||
|
{ name = "getrandom", version = "<0.2" },
|
||||||
|
{ name = "http-body", version = "0.4.6" },
|
||||||
{ name = "http", version = "0.2.12" },
|
{ name = "http", version = "0.2.12" },
|
||||||
{ name = "loom", version = "0.5.6" },
|
{ name = "hyper", version = "0.14.28" },
|
||||||
{ name = "netlink-packet-route", version = "0.17.1" },
|
{ name = "idna", version = "0.4.0" },
|
||||||
|
{ name = "netlink-packet-core", version = "0.5.0" },
|
||||||
|
{ name = "netlink-packet-route", version = "0.15.0" },
|
||||||
{ name = "nix", version = "0.26.4" },
|
{ name = "nix", version = "0.26.4" },
|
||||||
{ name = "nix", version = "0.27.1" },
|
{ name = "oid-registry", version = "0.6.1" },
|
||||||
|
{ name = "pem-rfc7468", version = "0.6.0" },
|
||||||
|
{ name = "pem", version = "1.1.1" },
|
||||||
|
{ name = "pkcs8", version = "0.9.0" },
|
||||||
|
{ name = "proc-macro-error-attr", version = "0.4.12" },
|
||||||
|
{ name = "proc-macro-error", version = "0.4.12" },
|
||||||
{ name = "quick-error", version = "<2.0" },
|
{ name = "quick-error", version = "<2.0" },
|
||||||
{ name = "rand_chacha", version = "0.3.1" },
|
{ name = "rand_chacha", version = "<0.3" },
|
||||||
{ name = "rand_core", version = "0.6.4" },
|
{ name = "rand_core", version = "<0.6" },
|
||||||
{ name = "rand", version = "0.8.5" },
|
{ name = "rand", version = "<0.8" },
|
||||||
|
{ name = "rcgen", version = "<0.12.1" },
|
||||||
{ name = "redox_syscall", version = "0.3.5" },
|
{ name = "redox_syscall", version = "0.3.5" },
|
||||||
{ name = "regex-automata", version = "0.1.10" },
|
{ name = "regex-automata", version = "0.1.10" },
|
||||||
{ name = "regex-syntax", version = "0.6.29" },
|
{ name = "regex-syntax", version = "0.6.29" },
|
||||||
{ name = "rtnetlink", version = "0.13.1" },
|
{ name = "ring", version = "0.16.20" },
|
||||||
{ name = "security-framework", version = "2.11.1" },
|
{ name = "sec1", version = "0.3.0" },
|
||||||
{ name = "strum_macros", version = "0.26.2" },
|
{ name = "sha2", version = "<0.10" },
|
||||||
{ name = "strum", version = "0.26.2" },
|
{ name = "signature", version = "1.6.4" },
|
||||||
|
{ name = "spin", version = "<0.9.6" },
|
||||||
|
{ name = "spki", version = "0.6.0" },
|
||||||
|
{ name = "ssh-encoding", version = "0.1.0" },
|
||||||
|
{ name = "ssh-key", version = "0.5.1" },
|
||||||
|
{ name = "sync_wrapper", version = "0.1.2" },
|
||||||
|
{ name = "synstructure", version = "0.12.6" },
|
||||||
{ name = "syn", version = "1.0.109" },
|
{ name = "syn", version = "1.0.109" },
|
||||||
{ name = "thiserror-impl", version = "1.0.69" },
|
{ name = "system-configuration-sys", version = "0.5.0" },
|
||||||
{ name = "thiserror", version = "1.0.69" },
|
{ name = "system-configuration", version = "0.5.1" },
|
||||||
{ name = "unicode-width", version = "0.1.11" },
|
{ name = "time", version = "<0.3" },
|
||||||
{ name = "wasi", version = "0.11.0+wasi-snapshot-preview1" },
|
{ name = "toml_edit", version = "0.21.1" },
|
||||||
{ name = "windows" },
|
{ name = "untrusted", version = "0.7.1" },
|
||||||
{ name = "windows_aarch64_gnullvm" },
|
{ name = "wasi", version = "<0.11" },
|
||||||
{ name = "windows_aarch64_msvc" },
|
{ name = "windows_aarch64_gnullvm", version = "<0.52" },
|
||||||
{ name = "windows-core" },
|
{ name = "windows_aarch64_msvc", version = "<0.52" },
|
||||||
{ name = "windows_i686_gnu" },
|
{ name = "windows-core", version = "<0.54.0" },
|
||||||
{ name = "windows_i686_gnullvm" },
|
{ name = "windows_i686_gnu", version = "<0.52" },
|
||||||
{ name = "windows_i686_msvc" },
|
{ name = "windows_i686_msvc", version = "<0.52" },
|
||||||
{ name = "windows-implement" },
|
{ name = "windows-sys", version = "<0.52" },
|
||||||
{ name = "windows-interface" },
|
{ name = "windows-targets", version = "<0.52" },
|
||||||
{ name = "windows-result" },
|
{ name = "windows", version = "0.32.0" },
|
||||||
{ name = "windows-strings" },
|
{ name = "windows", version = "<0.54.0" },
|
||||||
{ name = "windows-sys" },
|
{ name = "windows_x86_64_gnullvm", version = "<0.52" },
|
||||||
{ name = "windows-targets" },
|
{ name = "windows_x86_64_gnu", version = "<0.52" },
|
||||||
{ name = "windows_x86_64_gnu" },
|
{ name = "windows_x86_64_msvc", version = "<0.52" },
|
||||||
{ name = "windows_x86_64_gnullvm" },
|
{ name = "winnow", version = "0.5.40" },
|
||||||
{ name = "windows_x86_64_msvc" },
|
{ name = "x509-parser", version = "<0.16.0" },
|
||||||
{ name = "zerocopy", version = "0.7.32" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +117,7 @@ allow = [
|
|||||||
"ISC",
|
"ISC",
|
||||||
"MIT",
|
"MIT",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
"Unicode-3.0",
|
"OpenSSL",
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
]
|
]
|
||||||
@@ -95,3 +128,10 @@ expression = "MIT AND ISC AND OpenSSL"
|
|||||||
license-files = [
|
license-files = [
|
||||||
{ path = "LICENSE", hash = 0xbd0eed23 },
|
{ path = "LICENSE", hash = 0xbd0eed23 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[sources.allow-org]
|
||||||
|
# Organisations which we allow git sources from.
|
||||||
|
github = [
|
||||||
|
"async-email",
|
||||||
|
"deltachat",
|
||||||
|
]
|
||||||
|
|||||||
111
flake.lock
generated
111
flake.lock
generated
@@ -3,15 +3,15 @@
|
|||||||
"android": {
|
"android": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"devshell": "devshell",
|
"devshell": "devshell",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils_2",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731356359,
|
"lastModified": 1712088936,
|
||||||
"narHash": "sha256-vYqJnu6jotmWpPT4DgzHVdvNIZcKZCIUqS8QaptsZA0=",
|
"narHash": "sha256-mVjeSWQiR/t4UZ9fUawY9OEPAhY1R3meYG+0oh8DUBs=",
|
||||||
"owner": "tadfisher",
|
"owner": "tadfisher",
|
||||||
"repo": "android-nixpkgs",
|
"repo": "android-nixpkgs",
|
||||||
"rev": "c028ead7e88edb2e94cd7c90ee37593f63ae494a",
|
"rev": "2d8181caef279f19c4a33dc694723f89ffc195d4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -22,17 +22,18 @@
|
|||||||
},
|
},
|
||||||
"devshell": {
|
"devshell": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"android",
|
"android",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1728330715,
|
"lastModified": 1711099426,
|
||||||
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
"narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "devshell",
|
"repo": "devshell",
|
||||||
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
"rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -47,11 +48,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1737527504,
|
"lastModified": 1714112748,
|
||||||
"narHash": "sha256-Z8S5gLPdIYeKwBXDaSxlJ72ZmiilYhu3418h3RSQZA0=",
|
"narHash": "sha256-jq6Cpf/pQH85p+uTwPPrGG8Ky/zUOTwMJ7mcqc5M4So=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "aa13f23e3e91b95377a693ac655bbc6545ebec0d",
|
"rev": "3ae4b908a795b6a3824d401a0702e11a7157d7e1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -65,11 +66,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1726560853,
|
"lastModified": 1701680307,
|
||||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -83,11 +84,29 @@
|
|||||||
"systems": "systems_2"
|
"systems": "systems_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1726560853,
|
"lastModified": 1710146030,
|
||||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_3": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_3"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710146030,
|
||||||
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -101,11 +120,11 @@
|
|||||||
"nixpkgs": "nixpkgs_3"
|
"nixpkgs": "nixpkgs_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1721727458,
|
"lastModified": 1713520724,
|
||||||
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
|
"narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "naersk",
|
"repo": "naersk",
|
||||||
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
|
"rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -116,11 +135,11 @@
|
|||||||
},
|
},
|
||||||
"nix-filter": {
|
"nix-filter": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1730207686,
|
"lastModified": 1710156097,
|
||||||
"narHash": "sha256-SCHiL+1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I=",
|
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "nix-filter",
|
"repo": "nix-filter",
|
||||||
"rev": "776e68c1d014c3adde193a18db9d738458cd2ba4",
|
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -131,11 +150,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731139594,
|
"lastModified": 1711703276,
|
||||||
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
"narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
|
"rev": "d8fe5e6c92d0d190646fb9f1056741a229980089",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -147,11 +166,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1737469691,
|
"lastModified": 1713895582,
|
||||||
"narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=",
|
"narHash": "sha256-cfh1hi+6muQMbi9acOlju3V1gl8BEaZBXBR9jQfQi4U=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab",
|
"rev": "572af610f6151fd41c212f897c71f7056e3fb518",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -163,9 +182,10 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 0,
|
"lastModified": 1711668574,
|
||||||
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
|
"narHash": "sha256-u1dfs0ASQIEr1icTVrsKwg2xToIpn7ZXxW3RHfHxshg=",
|
||||||
"path": "/nix/store/zq2axpgzd5kykk1v446rkffj3bxa2m2h-source",
|
"path": "/nix/store/9fpv0kjq9a80isa1wkkvrdqsh9dpcn05-source",
|
||||||
|
"rev": "219951b495fc2eac67b1456824cc1ec1fd2ee659",
|
||||||
"type": "path"
|
"type": "path"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -175,11 +195,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_4": {
|
"nixpkgs_4": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731139594,
|
"lastModified": 1714076141,
|
||||||
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
"narHash": "sha256-Drmja/f5MRHZCskS6mvzFqxEaZMeciScCTFxWVLqWEY=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
|
"rev": "7bb2ccd8cdc44c91edba16c48d2c8f331fb3d856",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -193,7 +213,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"android": "android",
|
"android": "android",
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-utils": "flake-utils_2",
|
"flake-utils": "flake-utils_3",
|
||||||
"naersk": "naersk",
|
"naersk": "naersk",
|
||||||
"nix-filter": "nix-filter",
|
"nix-filter": "nix-filter",
|
||||||
"nixpkgs": "nixpkgs_4"
|
"nixpkgs": "nixpkgs_4"
|
||||||
@@ -202,11 +222,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1737453499,
|
"lastModified": 1714031783,
|
||||||
"narHash": "sha256-fa5AJI9mjFU2oVXqdCq2oA2pripAXbHzkUkewJRQpxA=",
|
"narHash": "sha256-xS/niQsq1CQPOe4M4jvVPO2cnXS/EIeRG5gIopUbk+Q=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "0b68402d781955d526b80e5d479e9e47addb4075",
|
"rev": "56bee2ddafa6177b19c631eedc88d43366553223",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -245,6 +265,21 @@
|
|||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
68
flake.nix
68
flake.nix
@@ -18,9 +18,9 @@
|
|||||||
manifest = (pkgs.lib.importTOML ./Cargo.toml).package;
|
manifest = (pkgs.lib.importTOML ./Cargo.toml).package;
|
||||||
androidSdk = android.sdk.${system} (sdkPkgs:
|
androidSdk = android.sdk.${system} (sdkPkgs:
|
||||||
builtins.attrValues {
|
builtins.attrValues {
|
||||||
inherit (sdkPkgs) ndk-27-2-12479018 cmdline-tools-latest;
|
inherit (sdkPkgs) ndk-24-0-8215888 cmdline-tools-latest;
|
||||||
});
|
});
|
||||||
androidNdkRoot = "${androidSdk}/share/android-sdk/ndk/27.2.12479018";
|
androidNdkRoot = "${androidSdk}/share/android-sdk/ndk/24.0.8215888";
|
||||||
|
|
||||||
rustSrc = nix-filter.lib {
|
rustSrc = nix-filter.lib {
|
||||||
root = ./.;
|
root = ./.;
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
include = [
|
include = [
|
||||||
./benches
|
./benches
|
||||||
./assets
|
./assets
|
||||||
./fuzz
|
|
||||||
./Cargo.lock
|
./Cargo.lock
|
||||||
./Cargo.toml
|
./Cargo.toml
|
||||||
./CMakeLists.txt
|
./CMakeLists.txt
|
||||||
@@ -88,6 +87,11 @@
|
|||||||
};
|
};
|
||||||
cargoLock = {
|
cargoLock = {
|
||||||
lockFile = ./Cargo.lock;
|
lockFile = ./Cargo.lock;
|
||||||
|
outputHashes = {
|
||||||
|
"email-0.0.20" = "sha256-rV4Uzqt2Qdrfi5Ti1r+Si1c2iW1kKyWLwOgLkQ5JGGw=";
|
||||||
|
"encoded-words-0.2.0" = "sha256-KK9st0hLFh4dsrnLd6D8lC6pRFFs8W+WpZSGMGJcosk=";
|
||||||
|
"lettre-0.9.2" = "sha256-+hU1cFacyyeC9UGVBpS14BWlJjHy90i/3ynMkKAzclk=";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
mkRustPackage = packageName:
|
mkRustPackage = packageName:
|
||||||
naersk'.buildPackage {
|
naersk'.buildPackage {
|
||||||
@@ -253,21 +257,13 @@
|
|||||||
|
|
||||||
androidAttrs = {
|
androidAttrs = {
|
||||||
armeabi-v7a = {
|
armeabi-v7a = {
|
||||||
cc = "armv7a-linux-androideabi21-clang";
|
cc = "armv7a-linux-androideabi19-clang";
|
||||||
rustTarget = "armv7-linux-androideabi";
|
rustTarget = "armv7-linux-androideabi";
|
||||||
};
|
};
|
||||||
arm64-v8a = {
|
arm64-v8a = {
|
||||||
cc = "aarch64-linux-android21-clang";
|
cc = "aarch64-linux-android21-clang";
|
||||||
rustTarget = "aarch64-linux-android";
|
rustTarget = "aarch64-linux-android";
|
||||||
};
|
};
|
||||||
x86 = {
|
|
||||||
cc = "i686-linux-android21-clang";
|
|
||||||
rustTarget = "i686-linux-android";
|
|
||||||
};
|
|
||||||
x86_64 = {
|
|
||||||
cc = "x86_64-linux-android21-clang";
|
|
||||||
rustTarget = "x86_64-linux-android";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mkAndroidRustPackage = arch: packageName:
|
mkAndroidRustPackage = arch: packageName:
|
||||||
@@ -309,40 +305,9 @@
|
|||||||
LD = "${targetCc}";
|
LD = "${targetCc}";
|
||||||
};
|
};
|
||||||
|
|
||||||
mkAndroidPackages = arch:
|
mkAndroidPackages = arch: {
|
||||||
let
|
"deltachat-rpc-server-${arch}-android" = mkAndroidRustPackage arch "deltachat-rpc-server";
|
||||||
rpc-server = mkAndroidRustPackage arch "deltachat-rpc-server";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
"deltachat-rpc-server-${arch}-android" = rpc-server;
|
|
||||||
"deltachat-repl-${arch}-android" = mkAndroidRustPackage arch "deltachat-repl";
|
"deltachat-repl-${arch}-android" = mkAndroidRustPackage arch "deltachat-repl";
|
||||||
"deltachat-rpc-server-${arch}-android-wheel" =
|
|
||||||
pkgs.stdenv.mkDerivation {
|
|
||||||
pname = "deltachat-rpc-server-${arch}-android-wheel";
|
|
||||||
version = manifest.version;
|
|
||||||
src = nix-filter.lib {
|
|
||||||
root = ./.;
|
|
||||||
include = [
|
|
||||||
"scripts/wheel-rpc-server.py"
|
|
||||||
"deltachat-rpc-server/README.md"
|
|
||||||
"LICENSE"
|
|
||||||
"Cargo.toml"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.python3
|
|
||||||
pkgs.python3Packages.wheel
|
|
||||||
];
|
|
||||||
buildInputs = [
|
|
||||||
rpc-server
|
|
||||||
];
|
|
||||||
buildPhase = ''
|
|
||||||
mkdir tmp
|
|
||||||
cp ${rpc-server}/bin/deltachat-rpc-server tmp/deltachat-rpc-server
|
|
||||||
python3 scripts/wheel-rpc-server.py ${arch}-android tmp/deltachat-rpc-server
|
|
||||||
'';
|
|
||||||
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-*.whl $out'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mkRustPackages = arch:
|
mkRustPackages = arch:
|
||||||
@@ -390,8 +355,6 @@
|
|||||||
mkRustPackages "x86_64-linux" //
|
mkRustPackages "x86_64-linux" //
|
||||||
mkRustPackages "armv7l-linux" //
|
mkRustPackages "armv7l-linux" //
|
||||||
mkRustPackages "armv6l-linux" //
|
mkRustPackages "armv6l-linux" //
|
||||||
mkRustPackages "x86_64-darwin" //
|
|
||||||
mkRustPackages "aarch64-darwin" //
|
|
||||||
mkAndroidPackages "armeabi-v7a" //
|
mkAndroidPackages "armeabi-v7a" //
|
||||||
mkAndroidPackages "arm64-v8a" //
|
mkAndroidPackages "arm64-v8a" //
|
||||||
mkAndroidPackages "x86" //
|
mkAndroidPackages "x86" //
|
||||||
@@ -508,8 +471,8 @@
|
|||||||
pkgs.python3
|
pkgs.python3
|
||||||
pkgs.python3Packages.wheel
|
pkgs.python3Packages.wheel
|
||||||
];
|
];
|
||||||
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat_rpc_server-${manifest.version}.tar.gz'';
|
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat-rpc-server-${manifest.version}.tar.gz'';
|
||||||
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-${manifest.version}.tar.gz $out'';
|
installPhase = ''mkdir -p $out; cp -av deltachat-rpc-server-${manifest.version}.tar.gz $out'';
|
||||||
};
|
};
|
||||||
|
|
||||||
deltachat-rpc-client =
|
deltachat-rpc-client =
|
||||||
@@ -562,14 +525,12 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.default =
|
devShells.default = let
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
system = system;
|
system = system;
|
||||||
overlays = [ fenix.overlays.default ];
|
overlays = [ fenix.overlays.default ];
|
||||||
};
|
};
|
||||||
in
|
in pkgs.mkShell {
|
||||||
pkgs.mkShell {
|
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
(fenix.packages.${system}.complete.withComponents [
|
(fenix.packages.${system}.complete.withComponents [
|
||||||
@@ -581,7 +542,6 @@
|
|||||||
])
|
])
|
||||||
cargo-deny
|
cargo-deny
|
||||||
rust-analyzer-nightly
|
rust-analyzer-nightly
|
||||||
cargo-nextest
|
|
||||||
perl # needed to build vendored OpenSSL
|
perl # needed to build vendored OpenSSL
|
||||||
git-cliff
|
git-cliff
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
//! is assumed to be set to "no".
|
//! is assumed to be set to "no".
|
||||||
//!
|
//!
|
||||||
//! For received messages, DelSp parameter is honoured.
|
//! For received messages, DelSp parameter is honoured.
|
||||||
#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
|
|
||||||
#![cfg_attr(not(test), forbid(clippy::string_slice))]
|
|
||||||
|
|
||||||
/// Wraps line to 72 characters using format=flowed soft breaks.
|
/// Wraps line to 72 characters using format=flowed soft breaks.
|
||||||
///
|
///
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user