]> nmode's Git Repositories - signal-cli/blobdiff - client/src/main.rs
Add support for sending view once messages
[signal-cli] / client / src / main.rs
index 2951295314ff79a3cf5ff224495c8f0bb3f58caa..ac12331df672891a7f62ce06348207080848679f 100644 (file)
@@ -1,66 +1,85 @@
-use clap::Parser;
-use jsonrpc_client_transports::{RpcError, TypedSubscriptionStream};
-use jsonrpc_core::{futures_util::StreamExt, Value};
 use std::{path::PathBuf, time::Duration};
+
+use clap::Parser;
+use jsonrpsee::core::client::{Error as RpcError, Subscription, SubscriptionClientT};
+use serde_json::{Error, Value};
 use tokio::{select, time::sleep};
 
-use crate::cli::{GroupPermission, LinkState};
+use cli::Cli;
+
+use crate::cli::{CliCommands, GroupPermission, LinkState};
+use crate::jsonrpc::RpcClient;
 
 mod cli;
-#[allow(clippy::too_many_arguments)]
+#[allow(non_snake_case, clippy::too_many_arguments)]
 mod jsonrpc;
-mod tcp;
+mod transports;
 
 const DEFAULT_TCP: &str = "127.0.0.1:7583";
 const DEFAULT_SOCKET_SUFFIX: &str = "signal-cli/socket";
+const DEFAULT_HTTP: &str = "http://localhost:8080/api/v1/rpc";
 
 #[tokio::main]
 async fn main() -> Result<(), anyhow::Error> {
     let cli = cli::Cli::parse();
 
-    let client = connect(&cli)
-        .await
-        .map_err(|e| anyhow::anyhow!("Failed to connect to socket: {e}"))?;
+    let result = connect(cli).await;
 
-    let result = match cli.command {
-        cli::CliCommands::Receive { timeout } => {
-            let mut stream = client
-                .subscribe_receive(cli.account)
-                .map_err(|e| anyhow::anyhow!("JSON-RPC command failed: {:?}", e))?;
+    match result {
+        Ok(Value::Null) => {}
+        Ok(v) => println!("{v}"),
+        Err(e) => return Err(anyhow::anyhow!("JSON-RPC command failed: {e:?}")),
+    }
+    Ok(())
+}
+
+async fn handle_command(
+    cli: Cli,
+    client: impl SubscriptionClientT + Sync,
+) -> Result<Value, RpcError> {
+    match cli.command {
+        CliCommands::Receive { timeout } => {
+            let mut stream = client.subscribe_receive(cli.account).await?;
 
             {
                 while let Some(v) = stream_next(timeout, &mut stream).await {
-                    let v = v.map_err(|e| anyhow::anyhow!("JSON-RPC command failed: {:?}", e))?;
+                    let v = v?;
                     println!("{v}");
                 }
             }
-            return Ok(());
+            stream.unsubscribe().await?;
+            Ok(Value::Null)
         }
-        cli::CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await,
-        cli::CliCommands::Block {
+        CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await,
+        CliCommands::Block {
             recipient,
             group_id,
         } => client.block(cli.account, recipient, group_id).await,
-        cli::CliCommands::DeleteLocalAccountData { ignore_registered } => {
+        CliCommands::DeleteLocalAccountData { ignore_registered } => {
             client
                 .delete_local_account_data(cli.account, ignore_registered)
                 .await
         }
-        cli::CliCommands::GetUserStatus { recipient } => {
-            client.get_user_status(cli.account, recipient).await
+        CliCommands::GetUserStatus {
+            recipient,
+            username,
+        } => {
+            client
+                .get_user_status(cli.account, recipient, username)
+                .await
         }
-        cli::CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
-        cli::CliCommands::Link { name } => {
+        CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
+        CliCommands::Link { name } => {
             let url = client
                 .start_link(cli.account)
                 .await
-                .map_err(|e| anyhow::anyhow!("JSON-RPC command startLink failed: {e:?}",))?
+                .map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))?
                 .device_link_uri;
-            println!("{}", url);
+            println!("{url}");
             client.finish_link(url, name).await
         }
-        cli::CliCommands::ListAccounts => client.list_accounts().await,
-        cli::CliCommands::ListContacts {
+        CliCommands::ListAccounts => client.list_accounts().await,
+        CliCommands::ListContacts {
             recipient,
             all_recipients,
             blocked,
@@ -70,16 +89,14 @@ async fn main() -> Result<(), anyhow::Error> {
                 .list_contacts(cli.account, recipient, all_recipients, blocked, name)
                 .await
         }
-        cli::CliCommands::ListDevices => client.list_devices(cli.account).await,
-        cli::CliCommands::ListGroups {
+        CliCommands::ListDevices => client.list_devices(cli.account).await,
+        CliCommands::ListGroups {
             detailed: _,
             group_id,
         } => client.list_groups(cli.account, group_id).await,
-        cli::CliCommands::ListIdentities { number } => {
-            client.list_identities(cli.account, number).await
-        }
-        cli::CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await,
-        cli::CliCommands::QuitGroup {
+        CliCommands::ListIdentities { number } => client.list_identities(cli.account, number).await,
+        CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await,
+        CliCommands::QuitGroup {
             group_id,
             delete,
             admin,
@@ -88,17 +105,23 @@ async fn main() -> Result<(), anyhow::Error> {
                 .quit_group(cli.account, group_id, delete, admin)
                 .await
         }
-        cli::CliCommands::Register { voice, captcha } => {
+        CliCommands::Register { voice, captcha } => {
             client.register(cli.account, voice, captcha).await
         }
-        cli::CliCommands::RemoveContact { recipient, forget } => {
-            client.remove_contact(cli.account, recipient, forget).await
+        CliCommands::RemoveContact {
+            recipient,
+            forget,
+            hide,
+        } => {
+            client
+                .remove_contact(cli.account, recipient, forget, hide)
+                .await
         }
-        cli::CliCommands::RemoveDevice { device_id } => {
+        CliCommands::RemoveDevice { device_id } => {
             client.remove_device(cli.account, device_id).await
         }
-        cli::CliCommands::RemovePin => client.remove_pin(cli.account).await,
-        cli::CliCommands::RemoteDelete {
+        CliCommands::RemovePin => client.remove_pin(cli.account).await,
+        CliCommands::RemoteDelete {
             target_timestamp,
             recipient,
             group_id,
@@ -114,19 +137,30 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::Send {
+        CliCommands::Send {
             recipient,
             group_id,
             note_to_self,
             end_session,
             message,
             attachment,
+            view_once,
             mention,
+            text_style,
             quote_timestamp,
             quote_author,
             quote_message,
             quote_mention,
+            quote_text_style,
+            quote_attachment,
+            preview_url,
+            preview_title,
+            preview_description,
+            preview_image,
             sticker,
+            story_timestamp,
+            story_author,
+            edit_timestamp,
         } => {
             client
                 .send(
@@ -137,17 +171,28 @@ async fn main() -> Result<(), anyhow::Error> {
                     end_session,
                     message.unwrap_or_default(),
                     attachment,
+                    view_once,
                     mention,
+                    text_style,
                     quote_timestamp,
                     quote_author,
                     quote_message,
                     quote_mention,
+                    quote_text_style,
+                    quote_attachment,
+                    preview_url,
+                    preview_title,
+                    preview_description,
+                    preview_image,
                     sticker,
+                    story_timestamp,
+                    story_author,
+                    edit_timestamp,
                 )
                 .await
         }
-        cli::CliCommands::SendContacts => client.send_contacts(cli.account).await,
-        cli::CliCommands::SendPaymentNotification {
+        CliCommands::SendContacts => client.send_contacts(cli.account).await,
+        CliCommands::SendPaymentNotification {
             recipient,
             receipt,
             note,
@@ -156,7 +201,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 .send_payment_notification(cli.account, recipient, receipt, note)
                 .await
         }
-        cli::CliCommands::SendReaction {
+        CliCommands::SendReaction {
             recipient,
             group_id,
             note_to_self,
@@ -180,7 +225,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::SendReceipt {
+        CliCommands::SendReceipt {
             recipient,
             target_timestamp,
             r#type,
@@ -197,8 +242,8 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
-        cli::CliCommands::SendTyping {
+        CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
+        CliCommands::SendTyping {
             recipient,
             group_id,
             stop,
@@ -207,13 +252,13 @@ async fn main() -> Result<(), anyhow::Error> {
                 .send_typing(cli.account, recipient, group_id, stop)
                 .await
         }
-        cli::CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
-        cli::CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
+        CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
+        CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
             client
                 .submit_rate_limit_challenge(cli.account, challenge, captcha)
                 .await
         }
-        cli::CliCommands::Trust {
+        CliCommands::Trust {
             recipient,
             trust_all_known_keys,
             verified_safety_number,
@@ -227,17 +272,30 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::Unblock {
+        CliCommands::Unblock {
             recipient,
             group_id,
         } => client.unblock(cli.account, recipient, group_id).await,
-        cli::CliCommands::Unregister { delete_account } => {
+        CliCommands::Unregister { delete_account } => {
             client.unregister(cli.account, delete_account).await
         }
-        cli::CliCommands::UpdateAccount { device_name } => {
-            client.update_account(cli.account, device_name).await
+        CliCommands::UpdateAccount {
+            device_name,
+            unrestricted_unidentified_sender,
+            discoverable_by_number,
+            number_sharing,
+        } => {
+            client
+                .update_account(
+                    cli.account,
+                    device_name,
+                    unrestricted_unidentified_sender,
+                    discoverable_by_number,
+                    number_sharing,
+                )
+                .await
         }
-        cli::CliCommands::UpdateConfiguration {
+        CliCommands::UpdateConfiguration {
             read_receipts,
             unidentified_delivery_indicators,
             typing_indicators,
@@ -253,7 +311,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::UpdateContact {
+        CliCommands::UpdateContact {
             recipient,
             expiration,
             name,
@@ -262,7 +320,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 .update_contact(cli.account, recipient, name, expiration)
                 .await
         }
-        cli::CliCommands::UpdateGroup {
+        CliCommands::UpdateGroup {
             group_id,
             name,
             description,
@@ -315,7 +373,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::UpdateProfile {
+        CliCommands::UpdateProfile {
             given_name,
             family_name,
             about,
@@ -337,47 +395,126 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::UploadStickerPack { path } => {
+        CliCommands::UploadStickerPack { path } => {
             client.upload_sticker_pack(cli.account, path).await
         }
-        cli::CliCommands::Verify {
+        CliCommands::Verify {
             verification_code,
             pin,
         } => client.verify(cli.account, verification_code, pin).await,
-        cli::CliCommands::Version => client.version().await,
-    };
-
-    result
-        .map(|v| println!("{v}"))
-        .map_err(|e| anyhow::anyhow!("JSON-RPC command failed: {e:?}",))?;
-    Ok(())
+        CliCommands::Version => client.version().await,
+        CliCommands::AddStickerPack { uri } => client.add_sticker_pack(cli.account, uri).await,
+        CliCommands::FinishChangeNumber {
+            number,
+            verification_code,
+            pin,
+        } => {
+            client
+                .finish_change_number(cli.account, number, verification_code, pin)
+                .await
+        }
+        CliCommands::GetAttachment {
+            id,
+            recipient,
+            group_id,
+        } => {
+            client
+                .get_attachment(cli.account, id, recipient, group_id)
+                .await
+        }
+        CliCommands::GetAvatar {
+            contact,
+            profile,
+            group_id,
+        } => {
+            client
+                .get_avatar(cli.account, contact, profile, group_id)
+                .await
+        }
+        CliCommands::GetSticker {
+            pack_id,
+            sticker_id,
+        } => client.get_sticker(cli.account, pack_id, sticker_id).await,
+        CliCommands::StartChangeNumber {
+            number,
+            voice,
+            captcha,
+        } => {
+            client
+                .start_change_number(cli.account, number, voice, captcha)
+                .await
+        }
+        CliCommands::SendMessageRequestResponse {
+            recipient,
+            group_id,
+            r#type,
+        } => {
+            client
+                .send_message_request_response(
+                    cli.account,
+                    recipient,
+                    group_id,
+                    match r#type {
+                        cli::MessageRequestResponseType::Accept => "accept".to_owned(),
+                        cli::MessageRequestResponseType::Delete => "delete".to_owned(),
+                    },
+                )
+                .await
+        }
+    }
 }
 
-async fn connect(cli: &cli::Cli) -> Result<jsonrpc::SignalCliClient, RpcError> {
-    if let Some(tcp) = cli.json_rpc_tcp {
+async fn connect(cli: Cli) -> Result<Value, RpcError> {
+    if let Some(http) = &cli.json_rpc_http {
+        let uri = if let Some(uri) = http {
+            uri
+        } else {
+            DEFAULT_HTTP
+        };
+        let client = jsonrpc::connect_http(uri)
+            .await
+            .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
+
+        handle_command(cli, client).await
+    } else if let Some(tcp) = cli.json_rpc_tcp {
         let socket_addr = tcp.unwrap_or_else(|| DEFAULT_TCP.parse().unwrap());
-        jsonrpc::connect_tcp(socket_addr).await
+        let client = jsonrpc::connect_tcp(socket_addr)
+            .await
+            .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
+
+        handle_command(cli, client).await
     } else {
-        let socket_path = cli
-            .json_rpc_socket
-            .clone()
-            .unwrap_or(None)
-            .or_else(|| {
-                std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| {
-                    PathBuf::from(runtime_dir)
-                        .join(DEFAULT_SOCKET_SUFFIX)
-                        .into()
+        #[cfg(windows)]
+        {
+            Err(RpcError::Custom("Invalid socket".into()))
+        }
+        #[cfg(unix)]
+        {
+            let socket_path = cli
+                .json_rpc_socket
+                .clone()
+                .unwrap_or(None)
+                .or_else(|| {
+                    std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| {
+                        PathBuf::from(runtime_dir)
+                            .join(DEFAULT_SOCKET_SUFFIX)
+                            .into()
+                    })
                 })
-            })
-            .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into());
-        jsonrpc::connect_unix(socket_path).await
+                .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into());
+            let client = jsonrpc::connect_unix(socket_path)
+                .await
+                .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
+
+            handle_command(cli, client).await
+        }
     }
 }
 
 async fn stream_next(
     timeout: f64,
-    stream: &mut TypedSubscriptionStream<Value>,
-) -> Option<Result<Value, RpcError>> {
+    stream: &mut Subscription<Value>,
+) -> Option<Result<Value, Error>> {
     if timeout < 0.0 {
         stream.next().await
     } else {