-
-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add async python client for Delta Chat core JSON-RPC
- Loading branch information
Showing
14 changed files
with
546 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Delta Chat RPC python client | ||
|
||
RPC client connects to standalone Delta Chat RPC server `deltachat-rpc-server` | ||
and provides asynchronous interface to it. | ||
|
||
## Getting started | ||
|
||
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`. | ||
Install it anywhere in your `PATH`. | ||
|
||
## Testing | ||
|
||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`. | ||
2. Run `tox`. | ||
|
||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output. | ||
|
||
## Using in REPL | ||
|
||
It is recommended to use IPython, because it supports using `await` directly | ||
from the REPL. | ||
|
||
``` | ||
PATH="../target/debug:$PATH" ipython | ||
... | ||
In [1]: from deltachat_rpc_client import * | ||
In [2]: dc = Deltachat(await start_rpc_server()) | ||
In [3]: await dc.get_all_accounts() | ||
Out [3]: [] | ||
In [4]: alice = await dc.add_account() | ||
In [5]: (await alice.get_info())["journal_mode"] | ||
Out [5]: 'wal' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#!/usr/bin/env python3 | ||
import asyncio | ||
import logging | ||
import sys | ||
|
||
import deltachat_rpc_client as dc | ||
|
||
|
||
async def main(): | ||
rpc = await dc.start_rpc_server() | ||
deltachat = dc.Deltachat(rpc) | ||
system_info = await deltachat.get_system_info() | ||
logging.info("Running deltachat core %s", system_info["deltachat_core_version"]) | ||
|
||
accounts = await deltachat.get_all_accounts() | ||
account = accounts[0] if accounts else await deltachat.add_account() | ||
|
||
await account.set_config("bot", "1") | ||
if not await account.is_configured(): | ||
logging.info("Account is not configured, configuring") | ||
await account.set_config("addr", sys.argv[1]) | ||
await account.set_config("mail_pw", sys.argv[2]) | ||
await account.configure() | ||
logging.info("Configured") | ||
else: | ||
logging.info("Account is already configured") | ||
await deltachat.start_io() | ||
|
||
async def process_messages(): | ||
fresh_messages = await account.get_fresh_messages() | ||
fresh_message_snapshot_tasks = [ | ||
message.get_snapshot() for message in fresh_messages | ||
] | ||
fresh_message_snapshots = await asyncio.gather(*fresh_message_snapshot_tasks) | ||
for snapshot in reversed(fresh_message_snapshots): | ||
if not snapshot.is_info: | ||
await snapshot.chat.send_text(snapshot.text) | ||
await snapshot.message.mark_seen() | ||
|
||
# Process old messages. | ||
await process_messages() | ||
|
||
while True: | ||
event = await account.wait_for_event() | ||
if event["type"] == "Info": | ||
logging.info("%s", event["msg"]) | ||
elif event["type"] == "Warning": | ||
logging.warning("%s", event["msg"]) | ||
elif event["type"] == "Error": | ||
logging.error("%s", event["msg"]) | ||
elif event["type"] == "IncomingMsg": | ||
logging.info("Got an incoming message") | ||
await process_messages() | ||
|
||
|
||
if __name__ == "__main__": | ||
logging.basicConfig(level=logging.INFO) | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[build-system] | ||
requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"] | ||
build-backend = "setuptools.build_meta" | ||
|
||
[project] | ||
name = "deltachat-rpc-client" | ||
description = "Python client for Delta Chat core JSON-RPC interface" | ||
dependencies = [ | ||
"aiohttp", | ||
"aiodns" | ||
] | ||
dynamic = [ | ||
"version" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .account import Account | ||
from .contact import Contact | ||
from .deltachat import Deltachat | ||
from .message import Message | ||
from .rpc import Rpc, new_online_account, start_rpc_server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from typing import Optional | ||
|
||
from .chat import Chat | ||
from .contact import Contact | ||
from .message import Message | ||
|
||
|
||
class Account: | ||
def __init__(self, rpc, account_id): | ||
self.rpc = rpc | ||
self.account_id = account_id | ||
|
||
def __repr__(self): | ||
return "<Account id={}>".format(self.account_id) | ||
|
||
async def wait_for_event(self): | ||
"""Wait until the next event and return it.""" | ||
return await self.rpc.get_next_event(self.account_id) | ||
|
||
async def remove(self) -> None: | ||
"""Remove the account.""" | ||
await self.rpc.remove_account(self.account_id) | ||
|
||
async def start_io(self) -> None: | ||
"""Start the account I/O.""" | ||
await self.rpc.start_io(self.account_id) | ||
|
||
async def stop_io(self) -> None: | ||
"""Stop the account I/O.""" | ||
await self.rpc.stop_io(self.account_id) | ||
|
||
async def get_info(self): | ||
return await self.rpc.get_info(self.account_id) | ||
|
||
async def get_file_size(self): | ||
return await self.rpc.get_account_file_size(self.account_id) | ||
|
||
async def is_configured(self) -> bool: | ||
"""Return True for configured accounts.""" | ||
return await self.rpc.is_configured(self.account_id) | ||
|
||
async def set_config(self, key: str, value: Optional[str]): | ||
"""Set the configuration value key pair.""" | ||
await self.rpc.set_config(self.account_id, key, value) | ||
|
||
async def get_config(self, key: str) -> Optional[str]: | ||
"""Get the configuration value.""" | ||
return await self.rpc.get_config(self.account_id, key) | ||
|
||
async def configure(self): | ||
"""Configure an account.""" | ||
await self.rpc.configure(self.account_id) | ||
|
||
async def create_contact(self, address: str, name: Optional[str]) -> Contact: | ||
"""Create a contact with the given address and, optionally, a name.""" | ||
return Contact( | ||
self.rpc, | ||
self.account_id, | ||
await self.rpc.create_contact(self.account_id, address, name), | ||
) | ||
|
||
async def secure_join(self, qr: str) -> Chat: | ||
chat_id = await self.rpc.secure_join(self.account_id, qr) | ||
return Chat(self.rpc, self.account_id, self.chat_id) | ||
|
||
async def get_fresh_messages(self): | ||
fresh_msg_ids = await self.rpc.get_fresh_msgs(self.account_id) | ||
return [Message(self.rpc, self.account_id, msg_id) for msg_id in fresh_msg_ids] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
class Chat: | ||
def __init__(self, rpc, account_id, chat_id): | ||
self.rpc = rpc | ||
self.account_id = account_id | ||
self.chat_id = chat_id | ||
|
||
async def block(self): | ||
"""Block the chat.""" | ||
await self.rpc.block_chat(self.account_id, self.chat_id) | ||
|
||
async def accept(self): | ||
"""Accept the contact request.""" | ||
await self.rpc.accept_chat(self.account_id, self.chat_id) | ||
|
||
async def delete(self): | ||
await self.rpc.delete_chat(self.account_id, self.chat_id) | ||
|
||
async def get_encryption_info(self): | ||
await self.rpc.get_chat_encryption_info(self.account_id, self.chat_id) | ||
|
||
async def send_text(self, text: str): | ||
from .message import Message | ||
|
||
msg_id = await self.rpc.misc_send_text_message( | ||
self.account_id, self.chat_id, text | ||
) | ||
return Message(self.rpc, self.account_id, msg_id) | ||
|
||
async def leave(self): | ||
await self.rpc.leave_group(self.account_id, self.chat_id) | ||
|
||
async def get_fresh_message_count() -> int: | ||
await get_fresh_msg_cnt(self.account_id, self.chat_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
class Contact: | ||
""" | ||
Contact API. | ||
Essentially a wrapper for RPC, account ID and a contact ID. | ||
""" | ||
|
||
def __init__(self, rpc, account_id, contact_id): | ||
self.rpc = rpc | ||
self.account_id = account_id | ||
self.contact_id = contact_id | ||
|
||
async def block(self): | ||
"""Block contact.""" | ||
await self.rpc.block_contact(self.account_id, self.contact_id) | ||
|
||
async def unblock(self): | ||
"""Unblock contact.""" | ||
await self.rpc.unblock_contact(self.account_id, self.contact_id) | ||
|
||
async def delete(self): | ||
"""Delete contact.""" | ||
await self.rpc.delete_contact(self.account_id, self.contact_id) | ||
|
||
async def change_name(self, name: str): | ||
await self.rpc.change_contact_name(self.account_id, self.contact_id, name) | ||
|
||
async def get_encryption_info(self) -> str: | ||
return await self.rpc.get_contact_encryption_info( | ||
self.account_id, self.contact_id | ||
) | ||
|
||
async def get_dictionary(self): | ||
"""Returns a dictionary with a snapshot of all contact properties.""" | ||
return await self.rpc.get_contact(self.account_id, self.contact_id) | ||
|
||
async def create_chat(self): | ||
from .chat import Chat | ||
|
||
return Chat( | ||
self.rpc, | ||
self.account_id, | ||
await self.rpc.create_chat_by_contact_id(self.account_id, self.contact_id), | ||
) |
31 changes: 31 additions & 0 deletions
31
deltachat-rpc-client/src/deltachat_rpc_client/deltachat.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from .account import Account | ||
|
||
|
||
class Deltachat: | ||
""" | ||
Delta Chat account manager. | ||
This is the root of the object oriented API. | ||
""" | ||
|
||
def __init__(self, rpc): | ||
self.rpc = rpc | ||
|
||
async def add_account(self): | ||
account_id = await self.rpc.add_account() | ||
return Account(self.rpc, account_id) | ||
|
||
async def get_all_accounts(self): | ||
account_ids = await self.rpc.get_all_account_ids() | ||
return [Account(self.rpc, account_id) for account_id in account_ids] | ||
|
||
async def start_io(self) -> None: | ||
await self.rpc.start_io_for_all_accounts() | ||
|
||
async def stop_io(self) -> None: | ||
await self.rpc.stop_io_for_all_accounts() | ||
|
||
async def maybe_network(self) -> None: | ||
await self.rpc.maybe_network() | ||
|
||
async def get_system_info(self): | ||
return await self.rpc.get_system_info() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from dataclasses import dataclass | ||
from typing import Optional | ||
|
||
from .chat import Chat | ||
from .contact import Contact | ||
|
||
|
||
class Message: | ||
def __init__(self, rpc, account_id, msg_id): | ||
self.rpc = rpc | ||
self.account_id = account_id | ||
self.msg_id = msg_id | ||
|
||
async def send_reaction(self, reactions): | ||
msg_id = await self.rpc.send_reaction(self.account_id, self.msg_id, reactions) | ||
return Message(self.rpc, self.account_id, msg_id) | ||
|
||
async def get_snapshot(self): | ||
message_object = await self.rpc.get_message(self.account_id, self.msg_id) | ||
return MessageSnapshot( | ||
message=self, | ||
chat=Chat(self.rpc, self.account_id, message_object["chatId"]), | ||
sender=Contact(self.rpc, self.account_id, message_object["fromId"]), | ||
text=message_object["text"], | ||
error=message_object.get("error"), | ||
is_info=message_object["isInfo"], | ||
) | ||
|
||
async def mark_seen(self) -> None: | ||
"""Mark the message as seen.""" | ||
await self.rpc.markseen_msgs(self.account_id, [self.msg_id]) | ||
|
||
|
||
@dataclass | ||
class MessageSnapshot: | ||
message: Message | ||
chat: Chat | ||
sender: Contact | ||
text: str | ||
error: Optional[str] | ||
is_info: bool |
Oops, something went wrong.