diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 162b46c1f..aa68b7a1a 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -48,6 +48,7 @@ pub mod types; use num_traits::FromPrimitive; use types::account::Account; +use types::calls::JsonrpcCallInfo; use types::chat::FullChat; use types::contact::{ContactObject, VcardContact}; use types::events::Event; @@ -2117,6 +2118,13 @@ impl CommandApi { Ok(()) } + /// Returns information about the call. + async fn call_info(&self, account_id: u32, msg_id: u32) -> Result { + let ctx = self.get_context(account_id).await?; + let call_info = JsonrpcCallInfo::from_msg_id(&ctx, MsgId::new(msg_id)).await?; + Ok(call_info) + } + /// Returns JSON with ICE servers, to be used for WebRTC video calls. async fn ice_servers(&self, account_id: u32) -> Result { let ctx = self.get_context(account_id).await?; diff --git a/deltachat-jsonrpc/src/api/types/calls.rs b/deltachat-jsonrpc/src/api/types/calls.rs new file mode 100644 index 000000000..a06e4c444 --- /dev/null +++ b/deltachat-jsonrpc/src/api/types/calls.rs @@ -0,0 +1,53 @@ +use anyhow::Result; + +use deltachat::context::Context; +use deltachat::message::MsgId; +use serde::Serialize; +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, +} + +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(); + + Ok(JsonrpcCallInfo { + is_incoming, + is_stale, + is_accepted, + is_ended, + duration, + sdp_offer, + }) + } +} diff --git a/deltachat-jsonrpc/src/api/types/mod.rs b/deltachat-jsonrpc/src/api/types/mod.rs index 995e931bf..0f49fcaa3 100644 --- a/deltachat-jsonrpc/src/api/types/mod.rs +++ b/deltachat-jsonrpc/src/api/types/mod.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod calls; pub mod chat; pub mod chat_list; pub mod contact; diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/message.py b/deltachat-rpc-client/src/deltachat_rpc_client/message.py index 2eda3c854..c7cd378ba 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/message.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/message.py @@ -110,3 +110,7 @@ class Message: def end_call(self): """Ends incoming or outgoing call.""" self._rpc.end_call(self.account.id, self.id) + + def get_call_info(self) -> AttrDict: + """Return information about the call.""" + return AttrDict(self._rpc.call_info(self.account.id, self.id)) diff --git a/deltachat-rpc-client/tests/test_calls.py b/deltachat-rpc-client/tests/test_calls.py index 66ac99729..e6cb5457a 100644 --- a/deltachat-rpc-client/tests/test_calls.py +++ b/deltachat-rpc-client/tests/test_calls.py @@ -15,8 +15,11 @@ def test_calls(acfactory) -> None: 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 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 outgoing_call_accepted_event = alice.wait_for_event(EventType.OUTGOING_CALL_ACCEPTED) assert outgoing_call_accepted_event.accept_call_info == accept_call_info diff --git a/src/calls.rs b/src/calls.rs index 1b8e3ca75..890aa6d18 100644 --- a/src/calls.rs +++ b/src/calls.rs @@ -52,11 +52,13 @@ pub struct CallInfo { } impl CallInfo { - fn is_incoming(&self) -> bool { + /// Returns true if the call is an incoming call. + pub fn is_incoming(&self) -> bool { self.msg.from_id != ContactId::SELF } - fn is_stale(&self) -> bool { + /// Returns true if the call should not ring anymore. + pub fn is_stale(&self) -> bool { self.remaining_ring_seconds() <= 0 } @@ -77,7 +79,7 @@ impl CallInfo { } async fn update_text_duration(&self, context: &Context) -> Result<()> { - let minutes = self.get_duration_seconds() / 60; + let minutes = self.duration_seconds() / 60; let duration = match minutes { 0 => "<1 minute".to_string(), 1 => "1 minute".to_string(), @@ -102,7 +104,8 @@ impl CallInfo { Ok(()) } - fn is_accepted(&self) -> bool { + /// Returns true if the call is accepted. + pub fn is_accepted(&self) -> bool { self.msg.param.exists(CALL_ACCEPTED_TIMESTAMP) } @@ -112,11 +115,13 @@ impl CallInfo { Ok(()) } - fn is_ended(&self) -> bool { + /// Returns true if the call is ended. + pub fn is_ended(&self) -> bool { self.msg.param.exists(CALL_ENDED_TIMESTAMP) } - fn get_duration_seconds(&self) -> i64 { + /// Returns call duration in seconds. + pub fn duration_seconds(&self) -> i64 { if let (Some(start), Some(end)) = ( self.msg.param.get_i64(CALL_ACCEPTED_TIMESTAMP), self.msg.param.get_i64(CALL_ENDED_TIMESTAMP), @@ -358,7 +363,8 @@ impl Context { Ok(()) } - async fn load_call_by_id(&self, call_id: MsgId) -> Result { + /// Loads information about the call given its ID. + pub async fn load_call_by_id(&self, call_id: MsgId) -> Result { let call = Message::load_from_db(self, call_id).await?; self.load_call_by_message(call) }