From 1ba448fe199751e0854fccccde8c768229a5f44e Mon Sep 17 00:00:00 2001 From: link2xt Date: Thu, 25 Sep 2025 22:38:50 +0000 Subject: [PATCH] api(jsonrpc): add `state` attribute to call info Existing boolean attributes removed. --- deltachat-jsonrpc/src/api/types/calls.rs | 92 ++++++++++++++++-------- deltachat-rpc-client/tests/test_calls.py | 8 ++- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/deltachat-jsonrpc/src/api/types/calls.rs b/deltachat-jsonrpc/src/api/types/calls.rs index a06e4c444..7f477da65 100644 --- a/deltachat-jsonrpc/src/api/types/calls.rs +++ b/deltachat-jsonrpc/src/api/types/calls.rs @@ -1,5 +1,6 @@ use anyhow::Result; +use deltachat::calls::{call_state, CallState}; use deltachat::context::Context; use deltachat::message::MsgId; use serde::Serialize; @@ -8,46 +9,79 @@ use typescript_type_def::TypeDef; #[derive(Serialize, TypeDef, schemars::JsonSchema)] #[serde(rename = "CallInfo", rename_all = "camelCase")] pub struct JsonrpcCallInfo { - /// True if the call is an incoming call. - pub is_incoming: bool, - - /// True if the call should not ring anymore. - pub is_stale: bool, - - /// True if the call is accepted. - pub is_accepted: bool, - - /// True if the call has been ended. - pub is_ended: bool, - - /// Call duration in seconds. - pub duration: i64, - /// SDP offer. /// /// Can be used to manually answer the call /// even if incoming call event was missed. pub sdp_offer: String, + + /// Call state. + /// + /// For example, if the call is accepted, active, cancelled, declined etc. + pub state: JsonrpcCallState, } impl JsonrpcCallInfo { pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result { let call_info = context.load_call_by_id(msg_id).await?; - - let is_incoming = call_info.is_incoming(); - let is_stale = call_info.is_stale(); - let is_accepted = call_info.is_accepted(); - let is_ended = call_info.is_ended(); - let duration = call_info.duration_seconds(); let sdp_offer = call_info.place_call_info.clone(); + let state = JsonrpcCallState::from_msg_id(context, msg_id).await?; - Ok(JsonrpcCallInfo { - is_incoming, - is_stale, - is_accepted, - is_ended, - duration, - sdp_offer, - }) + Ok(JsonrpcCallInfo { sdp_offer, state }) + } +} + +#[derive(Serialize, TypeDef, schemars::JsonSchema)] +#[serde(rename = "CallState", tag = "kind")] +pub enum JsonrpcCallState { + /// Fresh incoming or outgoing call that is still ringing. + /// + /// There is no separate state for outgoing call + /// that has been dialled but not ringing on the other side yet + /// as we don't know whether the other side received our call. + Alerting, + + /// Active call. + Active, + + /// Completed call that was once active + /// and then was terminated for any reason. + Completed { + /// Call duration in seconds. + duration: i64, + }, + + /// Incoming call that was not picked up within a timeout + /// or was explicitly ended by the caller before we picked up. + Missed, + + /// Incoming call that was explicitly ended on our side + /// before picking up or outgoing call + /// that was declined before the timeout. + Declined, + + /// Outgoing call that has been cancelled on our side + /// before receiving a response. + /// + /// Incoming calls cannot be cancelled, + /// on the receiver side cancelled calls + /// usually result in missed calls. + Cancelled, +} + +impl JsonrpcCallState { + pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result { + let call_state = call_state(context, msg_id).await?; + + let jsonrpc_call_state = match call_state { + CallState::Alerting => JsonrpcCallState::Alerting, + CallState::Active => JsonrpcCallState::Active, + CallState::Completed { duration } => JsonrpcCallState::Completed { duration }, + CallState::Missed => JsonrpcCallState::Missed, + CallState::Declined => JsonrpcCallState::Declined, + CallState::Cancelled => JsonrpcCallState::Cancelled, + }; + + Ok(jsonrpc_call_state) } } diff --git a/deltachat-rpc-client/tests/test_calls.py b/deltachat-rpc-client/tests/test_calls.py index e6cb5457a..25641b8c9 100644 --- a/deltachat-rpc-client/tests/test_calls.py +++ b/deltachat-rpc-client/tests/test_calls.py @@ -10,23 +10,27 @@ def test_calls(acfactory) -> None: alice_contact_bob = alice.create_contact(bob, "Bob") alice_chat_bob = alice_contact_bob.create_chat() outgoing_call_message = alice_chat_bob.place_outgoing_call(place_call_info) + assert outgoing_call_message.get_call_info().state.kind == "Alerting" incoming_call_event = bob.wait_for_event(EventType.INCOMING_CALL) assert incoming_call_event.place_call_info == place_call_info assert not incoming_call_event.has_video # Cannot be parsed as SDP, so false by default incoming_call_message = Message(bob, incoming_call_event.msg_id) - assert not incoming_call_message.get_call_info().is_accepted + assert incoming_call_message.get_call_info().state.kind == "Alerting" incoming_call_message.accept_incoming_call(accept_call_info) - assert incoming_call_message.get_call_info().is_accepted assert incoming_call_message.get_call_info().sdp_offer == place_call_info + assert incoming_call_message.get_call_info().state.kind == "Active" outgoing_call_accepted_event = alice.wait_for_event(EventType.OUTGOING_CALL_ACCEPTED) assert outgoing_call_accepted_event.accept_call_info == accept_call_info + assert outgoing_call_message.get_call_info().state.kind == "Active" outgoing_call_message.end_call() + assert outgoing_call_message.get_call_info().state.kind == "Completed" end_call_event = bob.wait_for_event(EventType.CALL_ENDED) assert end_call_event.msg_id == outgoing_call_message.id + assert incoming_call_message.get_call_info().state.kind == "Completed" def test_video_call(acfactory) -> None: