]> nmode's Git Repositories - signal-cli/blob - client/src/main.rs
Switch to jsonrpsee
[signal-cli] / client / src / main.rs
1 use std::{path::PathBuf, time::Duration};
2
3 use clap::Parser;
4 use cli::Cli;
5 use jsonrpsee::core::client::{Subscription, SubscriptionClientT};
6 use jsonrpsee::core::Error as RpcError;
7 use serde_json::Value;
8 use tokio::{select, time::sleep};
9
10 use crate::cli::{GroupPermission, LinkState};
11 use crate::jsonrpc::RpcClient;
12
13 mod cli;
14 #[allow(non_snake_case, clippy::too_many_arguments)]
15 mod jsonrpc;
16 mod transports;
17
18 const DEFAULT_TCP: &str = "127.0.0.1:7583";
19 const DEFAULT_SOCKET_SUFFIX: &str = "signal-cli/socket";
20 const DEFAULT_HTTP: &str = "http://localhost:8080/api/v1/rpc";
21
22 #[tokio::main]
23 async fn main() -> Result<(), anyhow::Error> {
24 let cli = cli::Cli::parse();
25
26 let result = connect(cli).await;
27
28 match result {
29 Ok(Value::Null) => {}
30 Ok(v) => println!("{v}"),
31 Err(e) => return Err(anyhow::anyhow!("JSON-RPC command failed: {e:?}")),
32 }
33 Ok(())
34 }
35
36 async fn handle_command(
37 cli: Cli,
38 client: impl SubscriptionClientT + Sync,
39 ) -> Result<Value, RpcError> {
40 match cli.command {
41 cli::CliCommands::Receive { timeout } => {
42 let mut stream = client.subscribe_receive(cli.account).await?;
43
44 {
45 while let Some(v) = stream_next(timeout, &mut stream).await {
46 let v = v?;
47 println!("{v}");
48 }
49 }
50 Ok(Value::Null)
51 }
52 cli::CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await,
53 cli::CliCommands::Block {
54 recipient,
55 group_id,
56 } => client.block(cli.account, recipient, group_id).await,
57 cli::CliCommands::DeleteLocalAccountData { ignore_registered } => {
58 client
59 .delete_local_account_data(cli.account, ignore_registered)
60 .await
61 }
62 cli::CliCommands::GetUserStatus { recipient } => {
63 client.get_user_status(cli.account, recipient).await
64 }
65 cli::CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
66 cli::CliCommands::Link { name } => {
67 let url = client
68 .start_link(cli.account)
69 .await
70 .map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))?
71 .device_link_uri;
72 println!("{}", url);
73 client.finish_link(url, name).await
74 }
75 cli::CliCommands::ListAccounts => client.list_accounts().await,
76 cli::CliCommands::ListContacts {
77 recipient,
78 all_recipients,
79 blocked,
80 name,
81 } => {
82 client
83 .list_contacts(cli.account, recipient, all_recipients, blocked, name)
84 .await
85 }
86 cli::CliCommands::ListDevices => client.list_devices(cli.account).await,
87 cli::CliCommands::ListGroups {
88 detailed: _,
89 group_id,
90 } => client.list_groups(cli.account, group_id).await,
91 cli::CliCommands::ListIdentities { number } => {
92 client.list_identities(cli.account, number).await
93 }
94 cli::CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await,
95 cli::CliCommands::QuitGroup {
96 group_id,
97 delete,
98 admin,
99 } => {
100 client
101 .quit_group(cli.account, group_id, delete, admin)
102 .await
103 }
104 cli::CliCommands::Register { voice, captcha } => {
105 client.register(cli.account, voice, captcha).await
106 }
107 cli::CliCommands::RemoveContact { recipient, forget } => {
108 client.remove_contact(cli.account, recipient, forget).await
109 }
110 cli::CliCommands::RemoveDevice { device_id } => {
111 client.remove_device(cli.account, device_id).await
112 }
113 cli::CliCommands::RemovePin => client.remove_pin(cli.account).await,
114 cli::CliCommands::RemoteDelete {
115 target_timestamp,
116 recipient,
117 group_id,
118 note_to_self,
119 } => {
120 client
121 .remote_delete(
122 cli.account,
123 target_timestamp,
124 recipient,
125 group_id,
126 note_to_self,
127 )
128 .await
129 }
130 cli::CliCommands::Send {
131 recipient,
132 group_id,
133 note_to_self,
134 end_session,
135 message,
136 attachment,
137 mention,
138 quote_timestamp,
139 quote_author,
140 quote_message,
141 quote_mention,
142 sticker,
143 story_timestamp,
144 story_author,
145 } => {
146 client
147 .send(
148 cli.account,
149 recipient,
150 group_id,
151 note_to_self,
152 end_session,
153 message.unwrap_or_default(),
154 attachment,
155 mention,
156 quote_timestamp,
157 quote_author,
158 quote_message,
159 quote_mention,
160 sticker,
161 story_timestamp,
162 story_author,
163 )
164 .await
165 }
166 cli::CliCommands::SendContacts => client.send_contacts(cli.account).await,
167 cli::CliCommands::SendPaymentNotification {
168 recipient,
169 receipt,
170 note,
171 } => {
172 client
173 .send_payment_notification(cli.account, recipient, receipt, note)
174 .await
175 }
176 cli::CliCommands::SendReaction {
177 recipient,
178 group_id,
179 note_to_self,
180 emoji,
181 target_author,
182 target_timestamp,
183 remove,
184 story,
185 } => {
186 client
187 .send_reaction(
188 cli.account,
189 recipient,
190 group_id,
191 note_to_self,
192 emoji,
193 target_author,
194 target_timestamp,
195 remove,
196 story,
197 )
198 .await
199 }
200 cli::CliCommands::SendReceipt {
201 recipient,
202 target_timestamp,
203 r#type,
204 } => {
205 client
206 .send_receipt(
207 cli.account,
208 recipient,
209 target_timestamp,
210 match r#type {
211 cli::ReceiptType::Read => "read".to_owned(),
212 cli::ReceiptType::Viewed => "viewed".to_owned(),
213 },
214 )
215 .await
216 }
217 cli::CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
218 cli::CliCommands::SendTyping {
219 recipient,
220 group_id,
221 stop,
222 } => {
223 client
224 .send_typing(cli.account, recipient, group_id, stop)
225 .await
226 }
227 cli::CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
228 cli::CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
229 client
230 .submit_rate_limit_challenge(cli.account, challenge, captcha)
231 .await
232 }
233 cli::CliCommands::Trust {
234 recipient,
235 trust_all_known_keys,
236 verified_safety_number,
237 } => {
238 client
239 .trust(
240 cli.account,
241 recipient,
242 trust_all_known_keys,
243 verified_safety_number,
244 )
245 .await
246 }
247 cli::CliCommands::Unblock {
248 recipient,
249 group_id,
250 } => client.unblock(cli.account, recipient, group_id).await,
251 cli::CliCommands::Unregister { delete_account } => {
252 client.unregister(cli.account, delete_account).await
253 }
254 cli::CliCommands::UpdateAccount { device_name } => {
255 client.update_account(cli.account, device_name).await
256 }
257 cli::CliCommands::UpdateConfiguration {
258 read_receipts,
259 unidentified_delivery_indicators,
260 typing_indicators,
261 link_previews,
262 } => {
263 client
264 .update_configuration(
265 cli.account,
266 read_receipts,
267 unidentified_delivery_indicators,
268 typing_indicators,
269 link_previews,
270 )
271 .await
272 }
273 cli::CliCommands::UpdateContact {
274 recipient,
275 expiration,
276 name,
277 } => {
278 client
279 .update_contact(cli.account, recipient, name, expiration)
280 .await
281 }
282 cli::CliCommands::UpdateGroup {
283 group_id,
284 name,
285 description,
286 avatar,
287 member,
288 remove_member,
289 admin,
290 remove_admin,
291 ban,
292 unban,
293 reset_link,
294 link,
295 set_permission_add_member,
296 set_permission_edit_details,
297 set_permission_send_messages,
298 expiration,
299 } => {
300 client
301 .update_group(
302 cli.account,
303 group_id,
304 name,
305 description,
306 avatar,
307 member,
308 remove_member,
309 admin,
310 remove_admin,
311 ban,
312 unban,
313 reset_link,
314 link.map(|link| match link {
315 LinkState::Enabled => "enabled".to_owned(),
316 LinkState::EnabledWithApproval => "enabledWithApproval".to_owned(),
317 LinkState::Disabled => "disabled".to_owned(),
318 }),
319 set_permission_add_member.map(|p| match p {
320 GroupPermission::EveryMember => "everyMember".to_owned(),
321 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
322 }),
323 set_permission_edit_details.map(|p| match p {
324 GroupPermission::EveryMember => "everyMember".to_owned(),
325 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
326 }),
327 set_permission_send_messages.map(|p| match p {
328 GroupPermission::EveryMember => "everyMember".to_owned(),
329 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
330 }),
331 expiration,
332 )
333 .await
334 }
335 cli::CliCommands::UpdateProfile {
336 given_name,
337 family_name,
338 about,
339 about_emoji,
340 mobile_coin_address,
341 avatar,
342 remove_avatar,
343 } => {
344 client
345 .update_profile(
346 cli.account,
347 given_name,
348 family_name,
349 about,
350 about_emoji,
351 mobile_coin_address,
352 avatar,
353 remove_avatar,
354 )
355 .await
356 }
357 cli::CliCommands::UploadStickerPack { path } => {
358 client.upload_sticker_pack(cli.account, path).await
359 }
360 cli::CliCommands::Verify {
361 verification_code,
362 pin,
363 } => client.verify(cli.account, verification_code, pin).await,
364 cli::CliCommands::Version => client.version().await,
365 }
366 }
367
368 async fn connect(cli: Cli) -> Result<Value, RpcError> {
369 if let Some(http) = &cli.json_rpc_http {
370 let uri = if let Some(uri) = http {
371 uri
372 } else {
373 DEFAULT_HTTP
374 };
375 let client = jsonrpc::connect_http(uri)
376 .await
377 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
378
379 handle_command(cli, client).await
380 } else if let Some(tcp) = cli.json_rpc_tcp {
381 let socket_addr = tcp.unwrap_or_else(|| DEFAULT_TCP.parse().unwrap());
382 let client = jsonrpc::connect_tcp(socket_addr)
383 .await
384 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
385
386 handle_command(cli, client).await
387 } else {
388 let socket_path = cli
389 .json_rpc_socket
390 .clone()
391 .unwrap_or(None)
392 .or_else(|| {
393 std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| {
394 PathBuf::from(runtime_dir)
395 .join(DEFAULT_SOCKET_SUFFIX)
396 .into()
397 })
398 })
399 .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into());
400 let client = jsonrpc::connect_unix(socket_path)
401 .await
402 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
403
404 handle_command(cli, client).await
405 }
406 }
407
408 async fn stream_next(
409 timeout: f64,
410 stream: &mut Subscription<Value>,
411 ) -> Option<Result<Value, RpcError>> {
412 if timeout < 0.0 {
413 stream.next().await
414 } else {
415 select! {
416 v = stream.next() => v,
417 _= sleep(Duration::from_millis((timeout * 1000.0) as u64)) => None,
418 }
419 }
420 }