whatsapp_rust/features/
blocking.rs

1use crate::client::Client;
2use crate::jid_utils::server_jid;
3use crate::request::{InfoQuery, IqError};
4use anyhow::Result;
5use log::debug;
6use wacore_binary::builder::NodeBuilder;
7use wacore_binary::jid::Jid;
8use wacore_binary::node::NodeContent;
9
10#[derive(Debug, Clone)]
11pub struct BlocklistEntry {
12    pub jid: Jid,
13    pub timestamp: Option<u64>,
14}
15
16pub struct Blocking<'a> {
17    client: &'a Client,
18}
19
20impl<'a> Blocking<'a> {
21    pub(crate) fn new(client: &'a Client) -> Self {
22        Self { client }
23    }
24
25    pub async fn block(&self, jid: &Jid) -> Result<(), IqError> {
26        debug!(target: "Blocking", "Blocking contact: {}", jid);
27        self.update_blocklist(jid, "block").await
28    }
29
30    pub async fn unblock(&self, jid: &Jid) -> Result<(), IqError> {
31        debug!(target: "Blocking", "Unblocking contact: {}", jid);
32        self.update_blocklist(jid, "unblock").await
33    }
34
35    pub async fn get_blocklist(&self) -> Result<Vec<BlocklistEntry>> {
36        debug!(target: "Blocking", "Fetching blocklist...");
37
38        let iq = InfoQuery::get("blocklist", server_jid(), None);
39
40        let response = self.client.send_iq(iq).await?;
41        self.parse_blocklist_response(&response)
42    }
43
44    async fn update_blocklist(&self, jid: &Jid, action: &str) -> Result<(), IqError> {
45        let item_node = NodeBuilder::new("item")
46            .attr("action", action)
47            .attr("jid", jid.to_string())
48            .build();
49
50        let iq = InfoQuery::set(
51            "blocklist",
52            server_jid(),
53            Some(NodeContent::Nodes(vec![item_node])),
54        );
55
56        self.client.send_iq(iq).await?;
57        debug!(target: "Blocking", "Successfully {}ed contact: {}", action, jid);
58        Ok(())
59    }
60
61    fn parse_blocklist_response(
62        &self,
63        node: &wacore_binary::node::Node,
64    ) -> Result<Vec<BlocklistEntry>> {
65        let mut entries = Vec::new();
66
67        let items = if let Some(list) = node.get_optional_child("list") {
68            list.get_children_by_tag("item")
69        } else {
70            node.get_children_by_tag("item")
71        };
72
73        for item in items {
74            if let Some(jid_str) = item.attrs().optional_string("jid")
75                && let Ok(jid) = jid_str.parse::<Jid>()
76            {
77                let timestamp = item.attrs().optional_u64("t");
78                entries.push(BlocklistEntry { jid, timestamp });
79            }
80        }
81
82        debug!(target: "Blocking", "Parsed {} blocked contacts", entries.len());
83        Ok(entries)
84    }
85
86    pub async fn is_blocked(&self, jid: &Jid) -> Result<bool> {
87        let blocklist = self.get_blocklist().await?;
88        Ok(blocklist.iter().any(|e| e.jid.user == jid.user))
89    }
90}
91
92impl Client {
93    pub fn blocking(&self) -> Blocking<'_> {
94        Blocking::new(self)
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_blocklist_entry() {
104        let jid: Jid = "1234567890@s.whatsapp.net"
105            .parse()
106            .expect("test JID should be valid");
107        let entry = BlocklistEntry {
108            jid: jid.clone(),
109            timestamp: Some(1234567890),
110        };
111
112        assert_eq!(entry.jid.user, "1234567890");
113        assert_eq!(entry.timestamp, Some(1234567890));
114    }
115}