]> nmode's Git Repositories - signal-cli/blob - client/src/main.rs
Update libsignal-service-java
[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 mention,
148 text_style,
149 quote_timestamp,
150 quote_author,
151 quote_message,
152 quote_mention,
153 quote_text_style,
154 quote_attachment,
155 preview_url,
156 preview_title,
157 preview_description,
158 preview_image,
159 sticker,
160 story_timestamp,
161 story_author,
162 edit_timestamp,
163 } => {
164 client
165 .send(
166 cli.account,
167 recipient,
168 group_id,
169 note_to_self,
170 end_session,
171 message.unwrap_or_default(),
172 attachment,
173 mention,
174 text_style,
175 quote_timestamp,
176 quote_author,
177 quote_message,
178 quote_mention,
179 quote_text_style,
180 quote_attachment,
181 preview_url,
182 preview_title,
183 preview_description,
184 preview_image,
185 sticker,
186 story_timestamp,
187 story_author,
188 edit_timestamp,
189 )
190 .await
191 }
192 CliCommands::SendContacts => client.send_contacts(cli.account).await,
193 CliCommands::SendPaymentNotification {
194 recipient,
195 receipt,
196 note,
197 } => {
198 client
199 .send_payment_notification(cli.account, recipient, receipt, note)
200 .await
201 }
202 CliCommands::SendReaction {
203 recipient,
204 group_id,
205 note_to_self,
206 emoji,
207 target_author,
208 target_timestamp,
209 remove,
210 story,
211 } => {
212 client
213 .send_reaction(
214 cli.account,
215 recipient,
216 group_id,
217 note_to_self,
218 emoji,
219 target_author,
220 target_timestamp,
221 remove,
222 story,
223 )
224 .await
225 }
226 CliCommands::SendReceipt {
227 recipient,
228 target_timestamp,
229 r#type,
230 } => {
231 client
232 .send_receipt(
233 cli.account,
234 recipient,
235 target_timestamp,
236 match r#type {
237 cli::ReceiptType::Read => "read".to_owned(),
238 cli::ReceiptType::Viewed => "viewed".to_owned(),
239 },
240 )
241 .await
242 }
243 CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
244 CliCommands::SendTyping {
245 recipient,
246 group_id,
247 stop,
248 } => {
249 client
250 .send_typing(cli.account, recipient, group_id, stop)
251 .await
252 }
253 CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
254 CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
255 client
256 .submit_rate_limit_challenge(cli.account, challenge, captcha)
257 .await
258 }
259 CliCommands::Trust {
260 recipient,
261 trust_all_known_keys,
262 verified_safety_number,
263 } => {
264 client
265 .trust(
266 cli.account,
267 recipient,
268 trust_all_known_keys,
269 verified_safety_number,
270 )
271 .await
272 }
273 CliCommands::Unblock {
274 recipient,
275 group_id,
276 } => client.unblock(cli.account, recipient, group_id).await,
277 CliCommands::Unregister { delete_account } => {
278 client.unregister(cli.account, delete_account).await
279 }
280 CliCommands::UpdateAccount {
281 device_name,
282 unrestricted_unidentified_sender,
283 discoverable_by_number,
284 number_sharing,
285 } => {
286 client
287 .update_account(
288 cli.account,
289 device_name,
290 unrestricted_unidentified_sender,
291 discoverable_by_number,
292 number_sharing,
293 )
294 .await
295 }
296 CliCommands::UpdateConfiguration {
297 read_receipts,
298 unidentified_delivery_indicators,
299 typing_indicators,
300 link_previews,
301 } => {
302 client
303 .update_configuration(
304 cli.account,
305 read_receipts,
306 unidentified_delivery_indicators,
307 typing_indicators,
308 link_previews,
309 )
310 .await
311 }
312 CliCommands::UpdateContact {
313 recipient,
314 expiration,
315 name,
316 } => {
317 client
318 .update_contact(cli.account, recipient, name, expiration)
319 .await
320 }
321 CliCommands::UpdateGroup {
322 group_id,
323 name,
324 description,
325 avatar,
326 member,
327 remove_member,
328 admin,
329 remove_admin,
330 ban,
331 unban,
332 reset_link,
333 link,
334 set_permission_add_member,
335 set_permission_edit_details,
336 set_permission_send_messages,
337 expiration,
338 } => {
339 client
340 .update_group(
341 cli.account,
342 group_id,
343 name,
344 description,
345 avatar,
346 member,
347 remove_member,
348 admin,
349 remove_admin,
350 ban,
351 unban,
352 reset_link,
353 link.map(|link| match link {
354 LinkState::Enabled => "enabled".to_owned(),
355 LinkState::EnabledWithApproval => "enabledWithApproval".to_owned(),
356 LinkState::Disabled => "disabled".to_owned(),
357 }),
358 set_permission_add_member.map(|p| match p {
359 GroupPermission::EveryMember => "everyMember".to_owned(),
360 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
361 }),
362 set_permission_edit_details.map(|p| match p {
363 GroupPermission::EveryMember => "everyMember".to_owned(),
364 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
365 }),
366 set_permission_send_messages.map(|p| match p {
367 GroupPermission::EveryMember => "everyMember".to_owned(),
368 GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
369 }),
370 expiration,
371 )
372 .await
373 }
374 CliCommands::UpdateProfile {
375 given_name,
376 family_name,
377 about,
378 about_emoji,
379 mobile_coin_address,
380 avatar,
381 remove_avatar,
382 } => {
383 client
384 .update_profile(
385 cli.account,
386 given_name,
387 family_name,
388 about,
389 about_emoji,
390 mobile_coin_address,
391 avatar,
392 remove_avatar,
393 )
394 .await
395 }
396 CliCommands::UploadStickerPack { path } => {
397 client.upload_sticker_pack(cli.account, path).await
398 }
399 CliCommands::Verify {
400 verification_code,
401 pin,
402 } => client.verify(cli.account, verification_code, pin).await,
403 CliCommands::Version => client.version().await,
404 CliCommands::AddStickerPack { uri } => client.add_sticker_pack(cli.account, uri).await,
405 CliCommands::FinishChangeNumber {
406 number,
407 verification_code,
408 pin,
409 } => {
410 client
411 .finish_change_number(cli.account, number, verification_code, pin)
412 .await
413 }
414 CliCommands::GetAttachment {
415 id,
416 recipient,
417 group_id,
418 } => {
419 client
420 .get_attachment(cli.account, id, recipient, group_id)
421 .await
422 }
423 CliCommands::GetAvatar {
424 contact,
425 profile,
426 group_id,
427 } => {
428 client
429 .get_avatar(cli.account, contact, profile, group_id)
430 .await
431 }
432 CliCommands::GetSticker {
433 pack_id,
434 sticker_id,
435 } => client.get_sticker(cli.account, pack_id, sticker_id).await,
436 CliCommands::StartChangeNumber {
437 number,
438 voice,
439 captcha,
440 } => {
441 client
442 .start_change_number(cli.account, number, voice, captcha)
443 .await
444 }
445 CliCommands::SendMessageRequestResponse {
446 recipient,
447 group_id,
448 r#type,
449 } => {
450 client
451 .send_message_request_response(
452 cli.account,
453 recipient,
454 group_id,
455 match r#type {
456 cli::MessageRequestResponseType::Accept => "accept".to_owned(),
457 cli::MessageRequestResponseType::Delete => "delete".to_owned(),
458 },
459 )
460 .await
461 }
462 }
463 }
464
465 async fn connect(cli: Cli) -> Result<Value, RpcError> {
466 if let Some(http) = &cli.json_rpc_http {
467 let uri = if let Some(uri) = http {
468 uri
469 } else {
470 DEFAULT_HTTP
471 };
472 let client = jsonrpc::connect_http(uri)
473 .await
474 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
475
476 handle_command(cli, client).await
477 } else if let Some(tcp) = cli.json_rpc_tcp {
478 let socket_addr = tcp.unwrap_or_else(|| DEFAULT_TCP.parse().unwrap());
479 let client = jsonrpc::connect_tcp(socket_addr)
480 .await
481 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
482
483 handle_command(cli, client).await
484 } else {
485 #[cfg(windows)]
486 {
487 Err(RpcError::Custom("Invalid socket".into()))
488 }
489 #[cfg(unix)]
490 {
491 let socket_path = cli
492 .json_rpc_socket
493 .clone()
494 .unwrap_or(None)
495 .or_else(|| {
496 std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| {
497 PathBuf::from(runtime_dir)
498 .join(DEFAULT_SOCKET_SUFFIX)
499 .into()
500 })
501 })
502 .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into());
503 let client = jsonrpc::connect_unix(socket_path)
504 .await
505 .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
506
507 handle_command(cli, client).await
508 }
509 }
510 }
511
512 async fn stream_next(
513 timeout: f64,
514 stream: &mut Subscription<Value>,
515 ) -> Option<Result<Value, Error>> {
516 if timeout < 0.0 {
517 stream.next().await
518 } else {
519 select! {
520 v = stream.next() => v,
521 _= sleep(Duration::from_millis((timeout * 1000.0) as u64)) => None,
522 }
523 }
524 }