Skip to main content

audit_counts/
audit_counts.rs

1//! Explicit offline validation helper. Prints aggregate counts, never identities,
2//! message bodies, key values, cache URLs or source paths.
3use wechat_profile::*;
4fn run() -> Result<()> {
5    let mut args = std::env::args().skip(1);
6    let path = args.next().ok_or(ProfileError::InvalidQuery)?;
7    let keys = args.next();
8    let location = ProfileLocation::new(path)?;
9    let (keys, plaintext) = if let Some(path) = keys {
10        (KeyFile::load(path)?, false)
11    } else {
12        (KeyFile::empty(location.account_directory()), true)
13    };
14    let r = ProfileReader::open(
15        location,
16        keys,
17        ReadOptions {
18            stopped_or_consistent_snapshot: true,
19            allow_plaintext: plaintext,
20            ..Default::default()
21        },
22    )?;
23    let mut contacts = 0;
24    let mut avatars = 0;
25    let mut q = ListQuery {
26        limit: Some(200),
27        ..Default::default()
28    };
29    loop {
30        let p = r.contacts(&q)?;
31        for c in &p.items {
32            if r.avatar(&c.username).is_ok() {
33                avatars += 1;
34            }
35        }
36        contacts += p.items.len();
37        q.cursor = p.next_cursor;
38        if q.cursor.is_none() {
39            break;
40        }
41    }
42    let mut threads = 0;
43    let mut q = ListQuery {
44        limit: Some(200),
45        ..Default::default()
46    };
47    loop {
48        let p = r.threads(&q)?;
49        threads += p.items.len();
50        q.cursor = p.next_cursor;
51        if q.cursor.is_none() {
52            break;
53        }
54    }
55    let mut groups = 0;
56    let mut members = 0;
57    let mut q = ListQuery {
58        limit: Some(200),
59        ..Default::default()
60    };
61    loop {
62        let p = r.groups(&q)?;
63        for g in &p.items {
64            let mut mq = ListQuery {
65                limit: Some(200),
66                ..Default::default()
67            };
68            loop {
69                let mp = r.group_members(&g.username, &mq)?;
70                members += mp.items.len();
71                mq.cursor = mp.next_cursor;
72                if mq.cursor.is_none() {
73                    break;
74                }
75            }
76        }
77        groups += p.items.len();
78        q.cursor = p.next_cursor;
79        if q.cursor.is_none() {
80            break;
81        }
82    }
83    let mut messages = 0;
84    let mut unresolved = 0;
85    let mut decoded = 0;
86    let mut q = MessageQuery {
87        limit: Some(200),
88        ..Default::default()
89    };
90    loop {
91        let p = r.messages(&q)?;
92        for m in &p.items {
93            unresolved += usize::from(m.peer.is_none());
94            decoded += usize::from(m.content.text.is_some());
95        }
96        messages += p.items.len();
97        q.cursor = p.next_cursor;
98        if q.cursor.is_none() {
99            break;
100        }
101    }
102    println!(
103        "{}",
104        serde_json::json!({"databases":r.inventory().len(),"partial":r.coverage().partial,"contacts":contacts,"threads":threads,"groups":groups,"groupMembers":members,"cachedAvatars":avatars,"messages":messages,"decodedBodies":decoded,"unresolvedMessages":unresolved,"writesPerformed":false})
105    );
106    Ok(())
107}
108fn main() {
109    if let Err(e) = run() {
110        eprintln!("{e}");
111        std::process::exit(1);
112    }
113}