signal_cli_jsonrpc_client/
rpc.rs

1//! This copied from https://github.com/AsamK/signal-cli/blob/f9a36c6e0404d06bd396b24b5ea699e49ed29b89/client/src/jsonrpc.rs
2#![allow(clippy::too_many_arguments)]
3
4use jsonrpsee::{async_client::ClientBuilder, core::client::SubscriptionClientT, proc_macros::rpc};
5use serde::Deserialize;
6use serde_json::Value;
7use tokio::net::ToSocketAddrs;
8
9pub use jsonrpsee::core::ClientError as RpcClientError;
10
11#[rpc(client)]
12pub trait Rpc {
13    #[method(name = "addDevice", param_kind = map)]
14    async fn add_device(
15        &self,
16        account: Option<String>,
17        uri: String,
18    ) -> Result<Value, ErrorObjectOwned>;
19
20    #[method(name = "addStickerPack", param_kind = map)]
21    async fn add_sticker_pack(
22        &self,
23        account: Option<String>,
24        uri: String,
25    ) -> Result<Value, ErrorObjectOwned>;
26
27    #[method(name = "block", param_kind = map)]
28    fn block(
29        &self,
30        account: Option<String>,
31        recipients: Vec<String>,
32        #[allow(non_snake_case)] groupIds: Vec<String>,
33    ) -> Result<Value, ErrorObjectOwned>;
34
35    #[method(name = "deleteLocalAccountData", param_kind = map)]
36    fn delete_local_account_data(
37        &self,
38        account: Option<String>,
39        #[allow(non_snake_case)] ignoreRegistered: Option<bool>,
40    ) -> Result<Value, ErrorObjectOwned>;
41
42    #[method(name = "getAttachment", param_kind = map)]
43    fn get_attachment(
44        &self,
45        account: Option<String>,
46        id: String,
47        recipient: Option<String>,
48        #[allow(non_snake_case)] groupId: Option<String>,
49    ) -> Result<Value, ErrorObjectOwned>;
50
51    #[method(name = "getAvatar", param_kind = map)]
52    fn get_avatar(
53        &self,
54        account: Option<String>,
55        contact: Option<String>,
56        profile: Option<String>,
57        #[allow(non_snake_case)] groupId: Option<String>,
58    ) -> Result<Value, ErrorObjectOwned>;
59
60    #[method(name = "getSticker", param_kind = map)]
61    fn get_sticker(
62        &self,
63        account: Option<String>,
64        #[allow(non_snake_case)] packId: String,
65        #[allow(non_snake_case)] stickerId: u32,
66    ) -> Result<Value, ErrorObjectOwned>;
67
68    #[method(name = "getUserStatus", param_kind = map)]
69    fn get_user_status(
70        &self,
71        account: Option<String>,
72        recipients: Vec<String>,
73        usernames: Vec<String>,
74    ) -> Result<Value, ErrorObjectOwned>;
75
76    #[method(name = "joinGroup", param_kind = map)]
77    fn join_group(&self, account: Option<String>, uri: String) -> Result<Value, ErrorObjectOwned>;
78
79    #[allow(non_snake_case)]
80    #[method(name = "finishChangeNumber", param_kind = map)]
81    fn finish_change_number(
82        &self,
83        account: Option<String>,
84        number: String,
85        verificationCode: String,
86        pin: Option<String>,
87    ) -> Result<Value, ErrorObjectOwned>;
88
89    #[method(name = "finishLink", param_kind = map)]
90    fn finish_link(
91        &self,
92        #[allow(non_snake_case)] deviceLinkUri: String,
93        #[allow(non_snake_case)] deviceName: String,
94    ) -> Result<Value, ErrorObjectOwned>;
95
96    #[method(name = "listAccounts", param_kind = map)]
97    fn list_accounts(&self) -> Result<Value, ErrorObjectOwned>;
98
99    #[method(name = "listContacts", param_kind = map)]
100    fn list_contacts(
101        &self,
102        account: Option<String>,
103        recipients: Vec<String>,
104        #[allow(non_snake_case)] allRecipients: bool,
105        blocked: Option<bool>,
106        name: Option<String>,
107    ) -> Result<Value, ErrorObjectOwned>;
108
109    #[method(name = "listDevices", param_kind = map)]
110    fn list_devices(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
111
112    #[method(name = "listGroups", param_kind = map)]
113    fn list_groups(
114        &self,
115        account: Option<String>,
116        #[allow(non_snake_case)] groupIds: Vec<String>,
117    ) -> Result<Value, ErrorObjectOwned>;
118
119    #[method(name = "listIdentities", param_kind = map)]
120    fn list_identities(
121        &self,
122        account: Option<String>,
123        number: Option<String>,
124    ) -> Result<Value, ErrorObjectOwned>;
125
126    #[method(name = "listStickerPacks", param_kind = map)]
127    fn list_sticker_packs(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
128
129    #[method(name = "quitGroup", param_kind = map)]
130    fn quit_group(
131        &self,
132        account: Option<String>,
133        #[allow(non_snake_case)] groupId: String,
134        delete: bool,
135        admins: Vec<String>,
136    ) -> Result<Value, ErrorObjectOwned>;
137
138    #[method(name = "register", param_kind = map)]
139    fn register(
140        &self,
141        account: Option<String>,
142        voice: bool,
143        captcha: Option<String>,
144    ) -> Result<Value, ErrorObjectOwned>;
145
146    #[method(name = "removeContact", param_kind = map)]
147    fn remove_contact(
148        &self,
149        account: Option<String>,
150        recipient: String,
151        forget: bool,
152        hide: bool,
153    ) -> Result<Value, ErrorObjectOwned>;
154
155    #[method(name = "removeDevice", param_kind = map)]
156    fn remove_device(
157        &self,
158        account: Option<String>,
159        #[allow(non_snake_case)] deviceId: u32,
160    ) -> Result<Value, ErrorObjectOwned>;
161
162    #[method(name = "removePin", param_kind = map)]
163    fn remove_pin(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
164
165    #[method(name = "remoteDelete", param_kind = map)]
166    fn remote_delete(
167        &self,
168        account: Option<String>,
169        #[allow(non_snake_case)] targetTimestamp: u64,
170        recipients: Vec<String>,
171        #[allow(non_snake_case)] groupIds: Vec<String>,
172        #[allow(non_snake_case)] noteToSelf: bool,
173    ) -> Result<Value, ErrorObjectOwned>;
174
175    #[allow(non_snake_case)]
176    #[method(name = "send", param_kind = map)]
177    fn send(
178        &self,
179        account: Option<String>,
180        recipients: Vec<String>,
181        groupIds: Vec<String>,
182        noteToSelf: bool,
183        endSession: bool,
184        message: String,
185        attachments: Vec<String>,
186        viewOnce: bool,
187        mentions: Vec<String>,
188        textStyle: Vec<String>,
189        quoteTimestamp: Option<u64>,
190        quoteAuthor: Option<String>,
191        quoteMessage: Option<String>,
192        quoteMention: Vec<String>,
193        quoteTextStyle: Vec<String>,
194        quoteAttachment: Vec<String>,
195        previewUrl: Option<String>,
196        previewTitle: Option<String>,
197        previewDescription: Option<String>,
198        previewImage: Option<String>,
199        sticker: Option<String>,
200        storyTimestamp: Option<u64>,
201        storyAuthor: Option<String>,
202        editTimestamp: Option<u64>,
203    ) -> Result<Value, ErrorObjectOwned>;
204
205    #[method(name = "sendContacts", param_kind = map)]
206    fn send_contacts(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
207
208    #[method(name = "sendPaymentNotification", param_kind = map)]
209    fn send_payment_notification(
210        &self,
211        account: Option<String>,
212        recipient: String,
213        receipt: String,
214        note: String,
215    ) -> Result<Value, ErrorObjectOwned>;
216
217    #[method(name = "sendReaction", param_kind = map)]
218    fn send_reaction(
219        &self,
220        account: Option<String>,
221        recipients: Vec<String>,
222        #[allow(non_snake_case)] groupIds: Vec<String>,
223        #[allow(non_snake_case)] noteToSelf: bool,
224        emoji: String,
225        #[allow(non_snake_case)] targetAuthor: String,
226        #[allow(non_snake_case)] targetTimestamp: u64,
227        remove: bool,
228        story: bool,
229    ) -> Result<Value, ErrorObjectOwned>;
230
231    #[method(name = "sendReceipt", param_kind = map)]
232    fn send_receipt(
233        &self,
234        account: Option<String>,
235        recipient: String,
236        #[allow(non_snake_case)] targetTimestamps: Vec<u64>,
237        r#type: String,
238    ) -> Result<Value, ErrorObjectOwned>;
239
240    #[method(name = "sendSyncRequest", param_kind = map)]
241    fn send_sync_request(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
242
243    #[method(name = "sendTyping", param_kind = map)]
244    fn send_typing(
245        &self,
246        account: Option<String>,
247        recipients: Vec<String>,
248        #[allow(non_snake_case)] groupIds: Vec<String>,
249        stop: bool,
250    ) -> Result<Value, ErrorObjectOwned>;
251
252    #[method(name = "sendMessageRequestResponse", param_kind = map)]
253    fn send_message_request_response(
254        &self,
255        account: Option<String>,
256        recipients: Vec<String>,
257        #[allow(non_snake_case)] groupIds: Vec<String>,
258        r#type: String,
259    ) -> Result<Value, ErrorObjectOwned>;
260
261    #[method(name = "setPin", param_kind = map)]
262    fn set_pin(&self, account: Option<String>, pin: String) -> Result<Value, ErrorObjectOwned>;
263
264    #[method(name = "submitRateLimitChallenge", param_kind = map)]
265    fn submit_rate_limit_challenge(
266        &self,
267        account: Option<String>,
268        challenge: String,
269        captcha: String,
270    ) -> Result<Value, ErrorObjectOwned>;
271
272    #[method(name = "startChangeNumber", param_kind = map)]
273    fn start_change_number(
274        &self,
275        account: Option<String>,
276        number: String,
277        voice: bool,
278        captcha: Option<String>,
279    ) -> Result<Value, ErrorObjectOwned>;
280
281    #[method(name = "startLink", param_kind = map)]
282    fn start_link(&self, account: Option<String>) -> Result<JsonLink, ErrorObjectOwned>;
283
284    #[method(name = "trust", param_kind = map)]
285    fn trust(
286        &self,
287        account: Option<String>,
288        recipient: String,
289        #[allow(non_snake_case)] trustAllKnownKeys: bool,
290        #[allow(non_snake_case)] verifiedSafetyNumber: Option<String>,
291    ) -> Result<Value, ErrorObjectOwned>;
292
293    #[method(name = "unblock", param_kind = map)]
294    fn unblock(
295        &self,
296        account: Option<String>,
297        recipients: Vec<String>,
298        #[allow(non_snake_case)] groupIds: Vec<String>,
299    ) -> Result<Value, ErrorObjectOwned>;
300
301    #[method(name = "unregister", param_kind = map)]
302    fn unregister(
303        &self,
304        account: Option<String>,
305        #[allow(non_snake_case)] deleteAccount: bool,
306    ) -> Result<Value, ErrorObjectOwned>;
307
308    #[allow(non_snake_case)]
309    #[method(name = "updateAccount", param_kind = map)]
310    fn update_account(
311        &self,
312        account: Option<String>,
313        deviceName: Option<String>,
314        unrestrictedUnidentifiedSender: Option<bool>,
315        discoverableByNumber: Option<bool>,
316        numberSharing: Option<bool>,
317    ) -> Result<Value, ErrorObjectOwned>;
318
319    #[method(name = "updateConfiguration", param_kind = map)]
320    fn update_configuration(
321        &self,
322        account: Option<String>,
323        #[allow(non_snake_case)] readReceipts: Option<bool>,
324        #[allow(non_snake_case)] unidentifiedDeliveryIndicators: Option<bool>,
325        #[allow(non_snake_case)] typingIndicators: Option<bool>,
326        #[allow(non_snake_case)] linkPreviews: Option<bool>,
327    ) -> Result<Value, ErrorObjectOwned>;
328
329    #[method(name = "updateContact", param_kind = map)]
330    fn update_contact(
331        &self,
332        account: Option<String>,
333        recipient: String,
334        name: Option<String>,
335        expiration: Option<u32>,
336    ) -> Result<Value, ErrorObjectOwned>;
337
338    #[method(name = "updateGroup", param_kind = map)]
339    fn update_group(
340        &self,
341        account: Option<String>,
342        #[allow(non_snake_case)] groupId: Option<String>,
343        name: Option<String>,
344        description: Option<String>,
345        avatar: Option<String>,
346        member: Vec<String>,
347        #[allow(non_snake_case)] removeMember: Vec<String>,
348        admin: Vec<String>,
349        #[allow(non_snake_case)] removeAdmin: Vec<String>,
350        ban: Vec<String>,
351        unban: Vec<String>,
352        #[allow(non_snake_case)] resetLink: bool,
353        #[allow(non_snake_case)] link: Option<String>,
354        #[allow(non_snake_case)] setPermissionAddMember: Option<String>,
355        #[allow(non_snake_case)] setPermissionEditDetails: Option<String>,
356        #[allow(non_snake_case)] setPermissionSendMessages: Option<String>,
357        expiration: Option<u32>,
358    ) -> Result<Value, ErrorObjectOwned>;
359
360    #[method(name = "updateProfile", param_kind = map)]
361    fn update_profile(
362        &self,
363        account: Option<String>,
364        #[allow(non_snake_case)] givenName: Option<String>,
365        #[allow(non_snake_case)] familyName: Option<String>,
366        about: Option<String>,
367        #[allow(non_snake_case)] aboutEmoji: Option<String>,
368        #[allow(non_snake_case)] mobileCoinAddress: Option<String>,
369        avatar: Option<String>,
370        #[allow(non_snake_case)] removeAvatar: bool,
371    ) -> Result<Value, ErrorObjectOwned>;
372
373    #[method(name = "uploadStickerPack", param_kind = map)]
374    fn upload_sticker_pack(
375        &self,
376        account: Option<String>,
377        path: String,
378    ) -> Result<Value, ErrorObjectOwned>;
379
380    #[method(name = "verify", param_kind = map)]
381    fn verify(
382        &self,
383        account: Option<String>,
384        #[allow(non_snake_case)] verificationCode: String,
385        pin: Option<String>,
386    ) -> Result<Value, ErrorObjectOwned>;
387
388    #[subscription(
389        name = "subscribeReceive" => "receive",
390        unsubscribe = "unsubscribeReceive",
391        item = RecvMessage,
392        param_kind = map
393    )]
394    async fn subscribe_receive(&self, account: Option<String>) -> SubscriptionResult;
395
396    #[method(name = "version")]
397    fn version(&self) -> Result<Value, ErrorObjectOwned>;
398}
399
400#[allow(unused)]
401#[derive(Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct JsonLink {
404    pub device_link_uri: String,
405}
406
407#[derive(Debug, Deserialize)]
408#[serde(rename_all = "camelCase")]
409pub struct RecvMessage {
410    pub envelope: Envelope,
411}
412
413#[allow(unused)]
414#[derive(Debug, Deserialize)]
415#[serde(rename_all = "camelCase")]
416pub struct Envelope {
417    pub source: String,
418    pub source_number: String,
419    pub source_uuid: String,
420    pub source_name: String,
421    pub source_device: i64,
422    pub timestamp: u64,
423    pub data_message: Option<DataMessage>,
424}
425
426#[derive(Debug, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct DataMessage {
429    pub timestamp: u64,
430    pub message: String,
431    #[serde(default)]
432    pub group_info: Option<GroupInfo>,
433}
434
435#[derive(Debug, Deserialize)]
436#[serde(rename_all = "camelCase")]
437pub struct GroupInfo {
438    pub group_id: String,
439}
440
441/// Trust level for an identity
442#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
443#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
444pub enum TrustLevel {
445    Untrusted,
446    TrustedUnverified,
447    TrustedVerified,
448}
449
450impl TrustLevel {
451    pub fn is_trusted(self) -> bool {
452        matches!(
453            self,
454            TrustLevel::TrustedUnverified | TrustLevel::TrustedVerified
455        )
456    }
457}
458
459/// Identity information returned by listIdentities
460#[derive(Debug, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct Identity {
463    pub safety_number: String,
464    pub trust_level: TrustLevel,
465}
466
467/// Connect to signal-cli over tcp socket
468pub async fn connect_tcp(
469    tcp: impl ToSocketAddrs,
470) -> Result<impl SubscriptionClientT, std::io::Error> {
471    let (sender, receiver) = crate::transports::tcp::connect(tcp).await?;
472
473    Ok(ClientBuilder::default().build_with_tokio(sender, receiver))
474}
475
476/// Connect to signal-cli over unix domain socket
477#[cfg(unix)]
478pub async fn connect_ipc(
479    path: impl AsRef<std::path::Path>,
480) -> Result<impl SubscriptionClientT, std::io::Error> {
481    let (sender, receiver) = crate::transports::ipc::connect(path).await?;
482
483    Ok(ClientBuilder::default().build_with_tokio(sender, receiver))
484}
485
486impl Envelope {
487    /// Send a read-receipt for an envelope
488    pub async fn send_read_receipt(
489        &self,
490        client: &impl RpcClient,
491        account: impl Into<String>,
492    ) -> Result<(), RpcClientError> {
493        if let Some(dm) = self.data_message.as_ref() {
494            let _ = client
495                .send_receipt(
496                    Some(account.into()),
497                    self.source_uuid.clone(),
498                    vec![dm.timestamp],
499                    "read".into(),
500                )
501                .await?;
502        }
503        Ok(())
504    }
505}
506
507/// Target for a SignalMessage - either individual recipients or a group
508#[derive(Clone, Debug)]
509pub enum MessageTarget {
510    /// Send to individual recipients
511    Recipients(Vec<String>),
512    /// Send to a group
513    Group(String),
514}
515
516/// Helper for invoking send, which has way too many parameters
517pub struct SignalMessage {
518    pub sender: String,
519    pub target: MessageTarget,
520    pub message: String,
521    pub attachments: Vec<String>,
522}
523
524impl SignalMessage {
525    #[allow(non_snake_case)]
526    pub async fn send(self, client: &impl RpcClient) -> Result<(), RpcClientError> {
527        // See note about string indexing here: https://github.com/AsamK/signal-cli/wiki/FAQ#string-indexing-units
528        let message_len_utf16: usize = self.message.chars().map(|c| c.len_utf16()).sum();
529        /*
530            account: Option<String>,
531            recipients: Vec<String>,
532            groupIds: Vec<String>,
533            noteToSelf: bool,
534            endSession: bool,
535            message: String,
536            attachments: Vec<String>,
537            viewOnce: bool,
538            mentions: Vec<String>,
539            textStyle: Vec<String>,
540            quoteTimestamp: Option<u64>,
541            quoteAuthor: Option<String>,
542            quoteMessage: Option<String>,
543            quoteMention: Vec<String>,
544            quoteTextStyle: Vec<String>,
545            quoteAttachment: Vec<String>,
546            previewUrl: Option<String>,
547            previewTitle: Option<String>,
548            previewDescription: Option<String>,
549            previewImage: Option<String>,
550            sticker: Option<String>,
551            storyTimestamp: Option<u64>,
552            storyAuthor: Option<String>,
553            editTimestamp: Option<u64>,
554        */
555        let account = Some(self.sender);
556        let (recipients, groupIds) = match self.target {
557            MessageTarget::Recipients(r) => (r, vec![]),
558            MessageTarget::Group(g) => (vec![], vec![g]),
559        };
560        let noteToSelf = false;
561        let endSession = false;
562        let message = self.message;
563        let attachments = self.attachments;
564        let viewOnce = false;
565        let mentions = vec![];
566        let textStyle = vec![format!("0:{message_len_utf16}:MONOSPACE")];
567        let quoteTimestamp = None;
568        let quoteAuthor = None;
569        let quoteMention = vec![];
570        let quoteMessage = None;
571        let quoteTextStyle = vec![];
572        let quoteAttachment = vec![];
573        let previewUrl = None;
574        let previewTitle = None;
575        let previewDescription = None;
576        let previewImage = None;
577        let sticker = None;
578        let storyTimestamp = None;
579        let storyAuthor = None;
580        let editTimestamp = None;
581
582        let _resp = client
583            .send(
584                account,
585                recipients,
586                groupIds,
587                noteToSelf,
588                endSession,
589                message,
590                attachments,
591                viewOnce,
592                mentions,
593                textStyle,
594                quoteTimestamp,
595                quoteAuthor,
596                quoteMessage,
597                quoteMention,
598                quoteTextStyle,
599                quoteAttachment,
600                previewUrl,
601                previewTitle,
602                previewDescription,
603                previewImage,
604                sticker,
605                storyTimestamp,
606                storyAuthor,
607                editTimestamp,
608            )
609            .await?;
610        Ok(())
611    }
612}