Skip to main content

wechat_profile/
lib.rs

1//! Readers and narrow opt-in writers for selected official Linux profiles.
2//!
3//! Offline readers use a private temporary copy, including its WAL, without
4//! changing source SHM. An owned daemon can instead reuse read-only connections
5//! while serializing profile operations. This crate neither launches WeChat nor connects
6//! to the network or acquires keys from a process. Checked write functions require
7//! explicit ownership and a durable caller intent.
8//!
9//! # API guide
10//!
11//! Start with [`ProfileReader`] for history, contacts, groups and local media.
12//! [`ReadOptions`] requires a stopped writer or a consistent copy. The library
13//! understands supported official file layouts; it supplies no account daemon,
14//! network requests or alternative settled-state database.
15//!
16//! Advanced writers use the following naming conventions. These are names for
17//! this adapter's operations, not official-client API names.
18//!
19//! | Prefix | Effect | Example |
20//! | --- | --- | --- |
21//! | `stage_*` | Prepare validated bytes in memory without changing files | [`stage_mmkv_values`] |
22//! | `apply_*` | Usually a logical update to supported profile records | [`apply_message_projection`] |
23//! | `publish_*` | Usually installation of prepared bytes at local paths | [`publish_mmkv_append`], [`publish_cache_file`] |
24//!
25//! Both `apply_*` and `publish_*` perform writes. Their names are descriptive,
26//! not separate transaction levels: [`publish_saved_key_info`] can change both a
27//! database and a file, and media apply operations can also install cache files.
28//! New profile mutations use `apply_*`; existing `publish_*` names are retained
29//! for compatibility. `stage_*` remains the convention for preparing a change
30//! without writing to the profile.
31//! "Prepared" means bytes already constructed and checked, such as the encrypted
32//! data and CRC metadata returned by [`stage_mmkv_values`]. It does not mean an
33//! additional profile or a network publication.
34//!
35//! A SQLite transaction or an atomic file replacement does not make a change
36//! across several databases/files atomic. The caller owns exclusive access and
37//! recovery ordering. [`WriteOptions::durable_intent_id`] references a recovery
38//! record the caller has already saved; the crate neither creates that record nor
39//! proves its existence. Unrecognized layouts remain unsupported for writes.
40// API naming policy for future changes is recorded in this crate's AGENTS.md.
41mod dat;
42mod images;
43mod initialize;
44mod keys;
45mod login_storage;
46mod media;
47mod mmkv;
48mod model;
49mod owned_reader;
50mod peer_metadata;
51mod projection;
52mod reader;
53mod recall_search;
54mod recalls;
55mod schema;
56mod snapshot;
57mod thread_read;
58mod writer;
59
60pub use dat::*;
61pub use initialize::*;
62pub use keys::*;
63pub use login_storage::*;
64pub use media::*;
65pub use mmkv::*;
66pub use model::*;
67pub use owned_reader::OwnedReaderCache;
68pub use peer_metadata::*;
69pub use projection::*;
70pub use reader::*;
71pub use recall_search::*;
72pub use recalls::*;
73pub use schema::*;
74pub use snapshot::{keys_from_agent_cache, keys_from_password, ProfileLocation, ReadOptions};
75pub use thread_read::*;
76pub use writer::*;
77
78#[derive(Debug, thiserror::Error)]
79pub enum ProfileError {
80    #[error("profile source is unavailable or not an account root")]
81    InvalidProfile,
82    #[error("a stopped profile or consistent snapshot must be explicitly confirmed")]
83    SnapshotRequired,
84    #[error("parallel snapshots are unavailable for this profile layout")]
85    ParallelSnapshotUnavailable,
86    #[error("profile changed while the read snapshot was being copied")]
87    SourceChanged,
88    #[error("source contains an unsupported link or special file")]
89    UnsupportedFile,
90    #[error("profile resource exceeds a configured bound")]
91    ResourceLimit,
92    #[error("protected keys file is missing")]
93    KeysMissing,
94    #[error("keys file must be a private regular file")]
95    InsecureKeysFile,
96    #[error("invalid or unsupported keys file")]
97    InvalidKeysFile,
98    #[error("keys are bound to a different account directory")]
99    AccountMismatch,
100    #[error("no authorized key is available for database {0}")]
101    KeyUnavailable(String),
102    #[error("supplied key or database generation did not validate for {0}")]
103    KeyMismatch(String),
104    #[error("validated database could not be opened for {0}")]
105    DatabaseOpen(String),
106    #[error("the database engine does not provide SQLCipher support")]
107    SqlcipherUnavailable,
108    #[error("unsupported database schema for {0}")]
109    UnsupportedSchema(String),
110    #[error("official-profile writes are not supported by this adapter")]
111    WritesUnsupported,
112    #[error("invalid or query-mismatched continuation cursor")]
113    InvalidCursor,
114    #[error("continuation source generation changed; start a new query")]
115    CursorExpired,
116    #[error("invalid query or identifier")]
117    InvalidQuery,
118    #[error("requested bytes are not cached")]
119    NotCached,
120    #[error("requested local image quality is not established by this adapter")]
121    ImageQualityUnavailable,
122    #[error("local image key is unavailable")]
123    ImageKeyUnavailable,
124    #[error("invalid or unsupported local V2 DAT payload")]
125    InvalidDat,
126    #[error("corrupt or unsupported encoded metadata")]
127    InvalidMetadata,
128    #[error("profile filesystem operation failed")]
129    Io(#[from] std::io::Error),
130    // Do not expose SQLCipher error SQL/parameters or row content to callers.
131    #[error("profile database operation failed")]
132    Database,
133}
134
135impl From<rusqlite::Error> for ProfileError {
136    fn from(_: rusqlite::Error) -> Self {
137        Self::Database
138    }
139}
140
141pub type Result<T> = std::result::Result<T, ProfileError>;