]> nmode's Git Repositories - signal-cli/blobdiff - client/src/main.rs
Compile UnixStream support only on unix systems
[signal-cli] / client / src / main.rs
index 6266d224da3c642b061d668c110a2749366bfcd4..f82ee237eb3a23f5aca8cf386f66cbc667467b0d 100644 (file)
-use clap::StructOpt;
-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;
+
+    match result {
+        Ok(Value::Null) => {}
+        Ok(v) => println!("{v}"),
+        Err(e) => return Err(anyhow::anyhow!("JSON-RPC command failed: {e:?}")),
+    }
+    Ok(())
+}
 
-    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))?;
+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::GetUserStatus { recipient } => {
-            client.get_user_status(cli.account, recipient).await
+        CliCommands::DeleteLocalAccountData { ignore_registered } => {
+            client
+                .delete_local_account_data(cli.account, ignore_registered)
+                .await
         }
-        cli::CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
-        cli::CliCommands::Link { name } => {
+        CliCommands::GetUserStatus {
+            recipient,
+            username,
+        } => {
+            client
+                .get_user_status(cli.account, recipient, username)
+                .await
+        }
+        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 => client.list_contacts(cli.account).await,
-        cli::CliCommands::ListDevices => client.list_devices(cli.account).await,
-        cli::CliCommands::ListGroups { detailed: _ } => client.list_groups(cli.account).await,
-        cli::CliCommands::ListIdentities { number } => {
-            client.list_identities(cli.account, number).await
+        CliCommands::ListAccounts => client.list_accounts().await,
+        CliCommands::ListContacts {
+            recipient,
+            all_recipients,
+            blocked,
+            name,
+        } => {
+            client
+                .list_contacts(cli.account, recipient, all_recipients, blocked, name)
+                .await
         }
-        cli::CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await,
-        cli::CliCommands::QuitGroup {
+        CliCommands::ListDevices => client.list_devices(cli.account).await,
+        CliCommands::ListGroups {
+            detailed: _,
+            group_id,
+        } => client.list_groups(cli.account, group_id).await,
+        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,
@@ -71,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,
@@ -97,7 +137,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::Send {
+        CliCommands::Send {
             recipient,
             group_id,
             note_to_self,
@@ -105,11 +145,21 @@ async fn main() -> Result<(), anyhow::Error> {
             message,
             attachment,
             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(
@@ -121,16 +171,35 @@ async fn main() -> Result<(), anyhow::Error> {
                     message.unwrap_or_default(),
                     attachment,
                     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::SendReaction {
+        CliCommands::SendContacts => client.send_contacts(cli.account).await,
+        CliCommands::SendPaymentNotification {
+            recipient,
+            receipt,
+            note,
+        } => {
+            client
+                .send_payment_notification(cli.account, recipient, receipt, note)
+                .await
+        }
+        CliCommands::SendReaction {
             recipient,
             group_id,
             note_to_self,
@@ -138,6 +207,7 @@ async fn main() -> Result<(), anyhow::Error> {
             target_author,
             target_timestamp,
             remove,
+            story,
         } => {
             client
                 .send_reaction(
@@ -149,10 +219,11 @@ async fn main() -> Result<(), anyhow::Error> {
                     target_author,
                     target_timestamp,
                     remove,
+                    story,
                 )
                 .await
         }
-        cli::CliCommands::SendReceipt {
+        CliCommands::SendReceipt {
             recipient,
             target_timestamp,
             r#type,
@@ -169,8 +240,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,
@@ -179,13 +250,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,
@@ -199,17 +270,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,
@@ -225,7 +309,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::UpdateContact {
+        CliCommands::UpdateContact {
             recipient,
             expiration,
             name,
@@ -234,7 +318,7 @@ async fn main() -> Result<(), anyhow::Error> {
                 .update_contact(cli.account, recipient, name, expiration)
                 .await
         }
-        cli::CliCommands::UpdateGroup {
+        CliCommands::UpdateGroup {
             group_id,
             name,
             description,
@@ -287,11 +371,12 @@ async fn main() -> Result<(), anyhow::Error> {
                 )
                 .await
         }
-        cli::CliCommands::UpdateProfile {
+        CliCommands::UpdateProfile {
             given_name,
             family_name,
             about,
             about_emoji,
+            mobile_coin_address,
             avatar,
             remove_avatar,
         } => {
@@ -302,52 +387,132 @@ async fn main() -> Result<(), anyhow::Error> {
                     family_name,
                     about,
                     about_emoji,
+                    mobile_coin_address,
                     avatar,
                     remove_avatar,
                 )
                 .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 {