mirror of
https://github.com/chatmail/core.git
synced 2026-05-05 14:26:30 +03:00
Add async python client for Delta Chat core JSON-RPC
This commit is contained in:
@@ -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
|
||||
68
deltachat-rpc-client/src/deltachat_rpc_client/account.py
Normal file
68
deltachat-rpc-client/src/deltachat_rpc_client/account.py
Normal file
@@ -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.wait_for_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]
|
||||
33
deltachat-rpc-client/src/deltachat_rpc_client/chat.py
Normal file
33
deltachat-rpc-client/src/deltachat_rpc_client/chat.py
Normal file
@@ -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)
|
||||
44
deltachat-rpc-client/src/deltachat_rpc_client/contact.py
Normal file
44
deltachat-rpc-client/src/deltachat_rpc_client/contact.py
Normal file
@@ -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
deltachat-rpc-client/src/deltachat_rpc_client/deltachat.py
Normal file
31
deltachat-rpc-client/src/deltachat_rpc_client/deltachat.py
Normal file
@@ -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()
|
||||
41
deltachat-rpc-client/src/deltachat_rpc_client/message.py
Normal file
41
deltachat-rpc-client/src/deltachat_rpc_client/message.py
Normal file
@@ -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
|
||||
92
deltachat-rpc-client/src/deltachat_rpc_client/rpc.py
Normal file
92
deltachat-rpc-client/src/deltachat_rpc_client/rpc.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
class JsonRpcError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Rpc:
|
||||
def __init__(self, process):
|
||||
self.process = process
|
||||
self.event_queues = {}
|
||||
self.id = 0
|
||||
self.reader_task = asyncio.create_task(self.reader_loop())
|
||||
|
||||
# Map from request ID to `asyncio.Future` returning the response.
|
||||
self.request_events = {}
|
||||
|
||||
async def reader_loop(self):
|
||||
while True:
|
||||
line = await self.process.stdout.readline()
|
||||
response = json.loads(line)
|
||||
if "id" in response:
|
||||
fut = self.request_events.pop(response["id"])
|
||||
fut.set_result(response)
|
||||
elif response["method"] == "event":
|
||||
# An event notification.
|
||||
params = response["params"]
|
||||
account_id = params["contextId"]
|
||||
if account_id not in self.event_queues:
|
||||
self.event_queues[account_id] = asyncio.Queue()
|
||||
await self.event_queues[account_id].put(params["event"])
|
||||
else:
|
||||
print(response)
|
||||
|
||||
async def wait_for_event(self, account_id):
|
||||
"""Waits for the next event from the given account and returns it."""
|
||||
if account_id in self.event_queues:
|
||||
return await self.event_queues[account_id].get()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
async def method(*args, **kwargs):
|
||||
self.id += 1
|
||||
request_id = self.id
|
||||
|
||||
params = args
|
||||
if kwargs:
|
||||
assert not args
|
||||
params = kwargs
|
||||
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": attr,
|
||||
"params": params,
|
||||
"id": self.id,
|
||||
}
|
||||
data = (json.dumps(request) + "\n").encode()
|
||||
self.process.stdin.write(data)
|
||||
event = asyncio.Event()
|
||||
loop = asyncio.get_running_loop()
|
||||
fut = loop.create_future()
|
||||
self.request_events[request_id] = fut
|
||||
response = await fut
|
||||
if "error" in response:
|
||||
raise JsonRpcError(response["error"])
|
||||
if "result" in response:
|
||||
return response["result"]
|
||||
|
||||
return method
|
||||
|
||||
|
||||
async def start_rpc_server(*args, **kwargs):
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"deltachat-rpc-server",
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
rpc = Rpc(proc)
|
||||
return rpc
|
||||
|
||||
|
||||
async def new_online_account():
|
||||
url = os.getenv("DCC_NEW_TMP_EMAIL")
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url) as response:
|
||||
return json.loads(await response.text())
|
||||
Reference in New Issue
Block a user