]> nmode's Git Repositories - signal-cli/blob - client/src/main.rs
Fix send parameters to be all camel case
[signal-cli] / client / src / main.rs
1 use std::{path::PathBuf, time::Duration};
2
3 use clap::Parser;
4 use jsonrpsee::core::client::{Error as RpcError, Subscription, SubscriptionClientT};
5 use serde_json::{Error, Value};
6 use tokio::{select, time::sleep};
7
8 use cli::Cli;
9
10 use crate::cli::{CliCommands, 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 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 stream.unsubscribe().await?;
51 Ok(Value::Null)
52 }
53 CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await,
54 CliCommands::Block {
55 recipient,
56 group_id,
57 } => client.block(cli.account, recipient, group_id).await,
58 CliCommands::DeleteLocalAccountData { ignore_registered } => {
59 client
60 .delete_local_account_data(cli.account, ignore_registered)
61 .await
62 }
63 CliCommands::GetUserStatus {
64 recipient,
65 username,
66 } => {
67 client
68 .get_user_status(cli.account, recipient, username)
69 .await
70 }
71 CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
72 CliCommands::Link { name } => {
73 let url = client
74 .start_link(cli.account)
75 .await
76 .map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))?
77 .device_link_uri;
78 println!("{url}");
79 client.finish_link(url, name).await
80 }
81 CliCommands::ListAccounts => client.list_accounts().await,
82 CliCommands::ListContacts {
83 recipient,
84 all_recipients,
85 blocked,
86 name,
87 } => {
88 client
89 .list_contacts(cli.account, recipient, all_recipients, blocked, name)
90 .await
91 }
92 CliCommands::ListDevices => client.list_devices(cli.account).await,
93 CliCommands::ListGroups {
94 detailed: _,
95 group_id,
96 } => client.list_groups(cli.account, group_id).await,
97 CliCommands::ListIdentities { number } => client.list_identities(cli.account, number).await,
98 CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await,
99 CliCommands::QuitGroup {
100 group_id,
101 delete,
102 admin,
103 } => {
104 client
105 .quit_group(cli.account, group_id, delete, admin)
106 .await
107 }
108 CliCommands::Register { voice, captcha } => {
109 client.register(cli.account, voice, captcha).await
110 }
111 CliCommands::RemoveContact {
112 recipient,
113 forget,
114 hide,
115 } => {
116 client
117 .remove_contact(cli.account, recipient, forget, hide)
118 .await
119 }
120 CliCommands::RemoveDevice { device_id } => {
121 client.remove_device(cli.account, device_id).await
122 }
123 CliCommands::RemovePin => client.remove_pin(cli.account).await,
124 CliCommands::RemoteDelete {
125 target_timestamp,
126 recipient,
127 group_id,
128 note_to_self,
129 } => {
130 client
131 .remote_delete(
132 cli.account,
133 target_timestamp,
134 recipient,
135 group_id,
136 note_to_self,
137 )
138 .await
139 }
140 CliCommands::Send {
141 recipient,
142 group_id,
143 note_to_self,
144 end_session,
145 message,
146 attachment,
147 view_once,
148 mention,
149 text_style,
150 quote_timestamp,
151 quote_author,
152 quote_message,
153 quote_mention,
154 quote_text_style,
155 quote_attachment,
156 preview_url,
157 preview_title,
158 preview_description,
159 preview_image,
160 sticker,
161 story_timestamp,
162 story_author,
163 edit_timestamp,
164 } => {
165 client
166 .send(
167 cli.account,
168 recipient,
169 group_id,
170 note_to_self,
171 end_session,
172 message.unwrap_or_default(),
173 attachment,
174 view_once,
175 mention,
176 text_style,
177 quote_timestamp,
178 quote_author,
179 quote_message,
180 quote_mention,
181 quote_text_style,
182 quote_attachment,
183 preview_url,
184 preview_title,
185 preview_description,
186 preview_image,
187 sticker,
188 story_timestamp,
189 story_author,
190 edit_timestamp,
191 )
192 .await
193 }
194 CliCommands::SendContacts => client.send_contacts(cli.account).await,
195 CliCommands::SendPaymentNotification {
196 recipient,
197 receipt,
198 note,
199 } => {
200 client
201 .send_payment_notification(cli.account, recipient, receipt, note)
202 .await
203 }
204 CliCommands::SendReaction {
205 recipient,
206 group_id,
207 note_to_self,
208 emoji,
209 target_author,
210 target_timestamp,
211 remove,
212 story,
213 } => {
214 client
215 .send_reaction(
216 cli.account,
217 recipient,
218 group_id,
219 note_to_self,
220 emoji,
221 target_author,
222 target_timestamp,
223 remove,
224 story,
225 )
226 .await
227 }
228 CliCommands::SendReceipt {
229 recipient,
230 target_timestamp,
231 r#type,
232 } => {
233 client
234 .send_receipt(
235 cli.account,
236 recipient,
237 target_timestamp,
238 match r#type {
239 cli::ReceiptType::Read => "read".to_owned(),
240 cli::ReceiptType::Viewed => "viewed".to_owned(),
241 },
242 )
243 .await
244 }
245 CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
246 CliCommands::SendTyping {
247 recipient,
248 group_id,
249 stop,
250 } => {
251 client
252 .send_typing(cli.account, recipient, group_id, stop)
253 .await
254 }
255 CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
256 CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
257 client
258 .submit_rate_limit_challenge(cli.account, challenge, captcha)
259 .await
260 }
261 CliCommands::Trust {
262 recipient,
263 trust_all_known_keys,
264 verified_safety_number,
265 } => {
266 client
267 .trust(
268 cli.account,
269 recipient,
270 trust_all_known_keys,
271 verified_safety_number,
272 )
273 .await
274 }
275 CliCommands::Unblock {
276 recipient,
277 group_id,
278 } => client.unblock(cli.account, recipient, group_id).await,
279 CliCommands::Unregister { delete_account } => {
280 client.unregister(cli.account, delete_account).await
281 }
282 CliCommands::UpdateAccount {
283 device_name,
284 unrestricted_unidentified_sender,
285 discoverable_by_number,
286 number_sharing,
287 } => {
288 client
289 .update_account(
290 cli.account,
291 device_name,
292 unrestricted_unidentified_sender,
293 discoverable_by_number,
294 number_sharing,
295 )
296 .await
297 }
298 CliCommands::UpdateConfiguration {
299 read_receipts,
300 unidentified_delivery_indicators,
301 typing_indicators,
302 link_previews,
303 } => {
304 client
305 .update_configuration(
306 cli.account,
307 read_receipts,
308 unidentified_delivery_indicators,
309 typing_indicators,
310 link_previews,
311 )
312 .await
313 }
314 CliCommands::UpdateContact {
315 recipient,
316 expiration,
317 name,
318 } => {
319 client
320 .update_contact(cli.account, recipient, name, expiration)
321 .await
322 }
323 CliCommands::UpdateGroup {
324 group_id,
325 name,
326 description,
327 avatar,
328 member,
329 remove_member,
330 admin,
331 remove_admin,
332 ban,
333 unban,
334 reset_link,
335 link,
336 set_permission_add_member,
337 set_permission_edit_details,
338 set_permission_send_messages,
339 expiration,
340 } => {
341 client
342 .update_group(
343 cli.account,
344 group_id,
345 name,
346 description,
347 avatar,
348 member,
349 remove_member,
350 admin,
351 remove_admin,
352 ban,
353 unban,
354 reset_link,
355 link.map(|link| match link {
356 LinkState::Enabled => "enabled".to_owned(),
357 LinkState::EnabledWithApproval => "enabledWithApproval".to_owned(),
358 LinkState::Disabled => "disabled".to_owned(),
359 }),
360 set_permission_add_member.map(|p| match p {
361 GroupPermission::EveryMember => "everyMember".to_owned(),
362 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
363 }),
364 set_permission_edit_details.map(|p| match p {
365 GroupPermission::EveryMember => "everyMember".to_owned(),
366 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
367 }),
368 set_permission_send_messages.map(|p| match p {
369 GroupPermission::EveryMember => "everyMember".to_owned(),
370 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
371 }),
372 expiration,
373 )
374 .await
375 }
376 CliCommands::UpdateProfile {
377 given_name,
378 family_name,
379 about,
380 about_emoji,
381 mobile_coin_address,
382 avatar,
383 remove_avatar,
384 } => {
385 client
386 .update_profile(
387 cli.account,
388 given_name,
389 family_name,
390 about,
391 about_emoji,
392 mobile_coin_address,
393 avatar,
394 remove_avatar,
395 )
396 .await
397 }
398 CliCommands::UploadStickerPack { path } => {
399 client.upload_sticker_pack(cli.account, path).await
400 }
401 CliCommands::Verify {
402 verification_code,
403 pin,
404 } => client.verify(cli.account, verification_code, pin).await,
405 CliCommands::Version => client.version().await,
406 CliCommands::AddStickerPack { uri } => client.add_sticker_pack(cli.account, uri).await,
407 CliCommands::FinishChangeNumber {
408 number,
409 verification_code,
410 pin,
411 } => {
412 client
413 .finish_change_number(cli.account, number, verification_code, pin)
414 .await
415 }
416 CliCommands::GetAttachment {
417 id,
418 recipient,
419 group_id,
420 } => {
421 client
422 .get_attachment(cli.account, id, recipient, group_id)
423 .await
424 }
425 CliCommands::GetAvatar {
426 contact,
427 profile,
428 group_id,
429 } => {
430 client
431 .get_avatar(cli.account, contact, profile, group_id)
432 .await
433 }
434 CliCommands::GetSticker {
435 pack_id,
436 sticker_id,
437 } => client.get_sticker(cli.account, pack_id, sticker_id).await,
438 CliCommands::StartChangeNumber {
439 number,
440 voice,
441 captcha,
442 } => {
443 client
444 .start_change_number(cli.account, number, voice, captcha)
445 .await
446 }
447 CliCommands::SendMessageRequestResponse {
448 recipient,
449 group_id,
450 r#type,
451 } => {
452 client
453 .send_message_request_response(
454 cli.account,
455 recipient,
456 group_id,
457 match r#type {
458 cli::MessageRequestResponseType::Accept => "accept".to_owned(),
459 cli::MessageRequestResponseType::Delete => "delete".to_owned(),
460 },
461 )
462 .await
463 }
464 }
465 }
466
467 async fn connect(cli: Cli) -> Result<Value, RpcError> {
468 if let Some(http) = &cli.json_rpc_http {
469 let uri = if let Some(uri) = http {
470 uri
471 } else {
472 DEFAULT_HTTP
473 };
474 let client = jsonrpc::connect_http(uri)
475 .await
476 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
477
478 handle_command(cli, client).await
479 } else if let Some(tcp) = cli.json_rpc_tcp {
480 let socket_addr = tcp.unwrap_or_else(|| DEFAULT_TCP.parse().unwrap());
481 let client = jsonrpc::connect_tcp(socket_addr)
482 .await
483 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
484
485 handle_command(cli, client).await
486 } else {
487 #[cfg(windows)]
488 {
489 Err(RpcError::Custom("Invalid socket".into()))
490 }
491 #[cfg(unix)]
492 {
493 let socket_path = cli
494 .json_rpc_socket
495 .clone()
496 .unwrap_or(None)
497 .or_else(|| {
498 std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| {
499 PathBuf::from(runtime_dir)
500 .join(DEFAULT_SOCKET_SUFFIX)
501 .into()
502 })
503 })
504 .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into());
505 let client = jsonrpc::connect_unix(socket_path)
506 .await
507 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
508
509 handle_command(cli, client).await
510 }
511 }
512 }
513
514 async fn stream_next(
515 timeout: f64,
516 stream: &mut Subscription<Value>,
517 ) -> Option<Result<Value, Error>> {
518 if timeout < 0.0 {
519 stream.next().await
520 } else {
521 select! {
522 v = stream.next() => v,
523 _= sleep(Duration::from_millis((timeout * 1000.0) as u64)) => None,
524 }
525 }
526 }