pub struct NativeClient { /* private fields */ }Expand description
Blocking client for supported WeChat server operations.
Construct with Self::new, obtain an AccountState through approved login,
then use methods such as Self::send_text or Self::sync. The state is
passed mutably because authenticated responses can update credentials/routing.
This client does not own a daemon, profile, send queue or recovery journal.
Implementations§
Source§impl NativeClient
impl NativeClient
pub fn group_mutation_raw( &self, state: &mut AccountState, request: &GroupRpc, ) -> Result<Vec<u8>>
Source§impl NativeClient
impl NativeClient
pub fn group_detail_raw( &self, state: &mut AccountState, group: &str, ) -> Result<Vec<u8>>
Source§impl NativeClient
impl NativeClient
pub fn group_inventory( &self, state: &mut AccountState, cursor: &[u8], ) -> Result<GroupMetadataPage>
Sourcepub fn lookup_contacts_raw(
&self,
state: &mut AccountState,
ids: &[String],
) -> Result<Vec<u8>>
pub fn lookup_contacts_raw( &self, state: &mut AccountState, ids: &[String], ) -> Result<Vec<u8>>
Verified native business payload, retained before strict contact decoding. Callers must protect this account data at rest. The transport’s global message bound applies; no identities or opaque fields are exposed in logs.
pub fn lookup_contacts_receipt( &self, state: &mut AccountState, ids: &[String], ) -> Result<MetadataReply<ContactLookup>>
pub fn lookup_room_receipt( &self, state: &mut AccountState, id: &str, version: u32, ) -> Result<MetadataReply<RoomRoster>>
pub fn lookup_room_raw( &self, state: &mut AccountState, id: &str, version: u32, ) -> Result<Vec<u8>>
pub fn init_contacts_receipt( &self, state: &mut AccountState, a: u32, b: u32, ) -> Result<MetadataReply<InitContactPage>>
Source§impl NativeClient
impl NativeClient
Sourcepub fn logout(&self, state: &mut AccountState) -> Result<LogoutReply>
pub fn logout(&self, state: &mut AccountState) -> Result<LogoutReply>
Submit one authenticated logout without renewal, retry or login fallback. An error can follow submission and must be treated as an unknown outcome.
Source§impl NativeClient
impl NativeClient
Sourcepub fn recall_message(
&self,
state: &mut AccountState,
recall: &MessageRecall,
) -> Result<MessageRecallReply>
pub fn recall_message( &self, state: &mut AccountState, recall: &MessageRecall, ) -> Result<MessageRecallReply>
Submit once without credential renewal, login fallback or automatic retry.
Source§impl NativeClient
impl NativeClient
Sourcepub fn notify_read_session(
&self,
state: &mut AccountState,
notification: &SessionReadNotification,
) -> Result<SessionReadReply>
pub fn notify_read_session( &self, state: &mut AccountState, notification: &SessionReadNotification, ) -> Result<SessionReadReply>
Submit once, with no renewal, retry, or login fallback. A caller must retain the boundary and request identity before invoking this method.
Source§impl NativeClient
impl NativeClient
Sourcepub fn new(options: NativeOptions) -> Result<Self>
pub fn new(options: NativeOptions) -> Result<Self>
Validate transport options. Construction itself makes no network request.
Examples found in repository?
13pub fn login() -> Result<(NativeClient, AccountState)> {
14 let client = NativeClient::new(NativeOptions::default())?;
15 let device = DeviceProfile::generate("UnifiedPCLinux x86_64".into())?;
16 let qr_directory = wechat_platform::private_tempdir("wechat-protocol-example-")?;
17 let qr_path = qr_directory.path().join("login.png");
18 let mut attempt = client.start_qr_login(device, "Rust protocol example")?;
19 let mut qr = wechat_platform::OpenOptions::new()
20 .write(true)
21 .create_new(true)
22 .mode(0o600)
23 .open(&qr_path)?;
24 qr.write_all(&attempt.qr_png)?;
25 drop(qr);
26 eprintln!("Scan the private QR image: {}", qr_path.display());
27 eprintln!("Approve this example on the test account's phone.");
28 let deadline = Instant::now() + Duration::from_secs(600);
29 loop {
30 ensure!(Instant::now() < deadline, "Login example timed out");
31 match client.poll_login_guarded(&mut attempt, deadline, || false) {
32 Ok(LoginStatus::Approved) => {
33 let state = client.complete_login(&mut attempt)?;
34 eprintln!("Login complete; credentials remain in memory.");
35 return Ok((client, state));
36 }
37 Ok(LoginStatus::Pending | LoginStatus::Scanned) => {}
38 Ok(status) => bail!("Login stopped: {status:?}"),
39 Err(error) if is_retryable_login_poll_error(&error) => {}
40 Err(error) => return Err(error),
41 }
42 // Only polling an already issued challenge is repeated. QR creation,
43 // credential exchange and sends are never automatically resubmitted.
44 thread::sleep(Duration::from_secs(2));
45 }
46}pub fn push_connection(&self, state: &AccountState) -> Result<Connection>
Sourcepub fn start_qr_login(
&self,
device: DeviceProfile,
computer_name: &str,
) -> Result<LoginAttempt>
pub fn start_qr_login( &self, device: DeviceProfile, computer_name: &str, ) -> Result<LoginAttempt>
Request one QR challenge for the supplied device. Display
LoginAttempt::qr_png privately, poll for phone approval, then call
Self::complete_login. This does not grant authorization by itself.
Examples found in repository?
13pub fn login() -> Result<(NativeClient, AccountState)> {
14 let client = NativeClient::new(NativeOptions::default())?;
15 let device = DeviceProfile::generate("UnifiedPCLinux x86_64".into())?;
16 let qr_directory = wechat_platform::private_tempdir("wechat-protocol-example-")?;
17 let qr_path = qr_directory.path().join("login.png");
18 let mut attempt = client.start_qr_login(device, "Rust protocol example")?;
19 let mut qr = wechat_platform::OpenOptions::new()
20 .write(true)
21 .create_new(true)
22 .mode(0o600)
23 .open(&qr_path)?;
24 qr.write_all(&attempt.qr_png)?;
25 drop(qr);
26 eprintln!("Scan the private QR image: {}", qr_path.display());
27 eprintln!("Approve this example on the test account's phone.");
28 let deadline = Instant::now() + Duration::from_secs(600);
29 loop {
30 ensure!(Instant::now() < deadline, "Login example timed out");
31 match client.poll_login_guarded(&mut attempt, deadline, || false) {
32 Ok(LoginStatus::Approved) => {
33 let state = client.complete_login(&mut attempt)?;
34 eprintln!("Login complete; credentials remain in memory.");
35 return Ok((client, state));
36 }
37 Ok(LoginStatus::Pending | LoginStatus::Scanned) => {}
38 Ok(status) => bail!("Login stopped: {status:?}"),
39 Err(error) if is_retryable_login_poll_error(&error) => {}
40 Err(error) => return Err(error),
41 }
42 // Only polling an already issued challenge is repeated. QR creation,
43 // credential exchange and sends are never automatically resubmitted.
44 thread::sleep(Duration::from_secs(2));
45 }
46}Sourcepub fn start_profile_qr_login(
&self,
device: DeviceProfile,
computer_name: &str,
key_info: &[u8],
) -> Result<LoginAttempt>
pub fn start_profile_qr_login( &self, device: DeviceProfile, computer_name: &str, key_info: &[u8], ) -> Result<LoginAttempt>
QR approval for an existing installation retains its selected key-info. This neither bypasses phone approval nor imports active authorization.
Sourcepub fn start_saved_device_login(
&self,
state: &AccountState,
computer_name: &str,
) -> Result<LoginAttempt>
pub fn start_saved_device_login( &self, state: &AccountState, computer_name: &str, ) -> Result<LoginAttempt>
Initiates phone approval for an existing remembered device without a QR. The caller chooses this fallback explicitly; no automatic auth retries occur.
Sourcepub fn start_profile_saved_device_login(
&self,
device: &DeviceProfile,
username: &str,
auto_auth_key: &[u8],
cookie: &[u8],
key_info: &[u8],
computer_name: &str,
) -> Result<LoginAttempt>
pub fn start_profile_saved_device_login( &self, device: &DeviceProfile, username: &str, auto_auth_key: &[u8], cookie: &[u8], key_info: &[u8], computer_name: &str, ) -> Result<LoginAttempt>
Use remembered official-profile trust without inventing active session keys.
Sourcepub fn poll_login(&self, attempt: &mut LoginAttempt) -> Result<LoginStatus>
pub fn poll_login(&self, attempt: &mut LoginAttempt) -> Result<LoginStatus>
Poll an existing challenge. Only LoginStatus::Approved permits
Self::complete_login; expired/rejected/cancelled attempts must stop.
Sourcepub fn poll_login_guarded(
&self,
attempt: &mut LoginAttempt,
deadline: Instant,
cancelled: impl Fn() -> bool + Send + Sync + 'static,
) -> Result<LoginStatus>
pub fn poll_login_guarded( &self, attempt: &mut LoginAttempt, deadline: Instant, cancelled: impl Fn() -> bool + Send + Sync + 'static, ) -> Result<LoginStatus>
The guard bounds the temporary LAN listener even while a blocking poll or upload is in flight. It must not perform blocking work itself.
Examples found in repository?
13pub fn login() -> Result<(NativeClient, AccountState)> {
14 let client = NativeClient::new(NativeOptions::default())?;
15 let device = DeviceProfile::generate("UnifiedPCLinux x86_64".into())?;
16 let qr_directory = wechat_platform::private_tempdir("wechat-protocol-example-")?;
17 let qr_path = qr_directory.path().join("login.png");
18 let mut attempt = client.start_qr_login(device, "Rust protocol example")?;
19 let mut qr = wechat_platform::OpenOptions::new()
20 .write(true)
21 .create_new(true)
22 .mode(0o600)
23 .open(&qr_path)?;
24 qr.write_all(&attempt.qr_png)?;
25 drop(qr);
26 eprintln!("Scan the private QR image: {}", qr_path.display());
27 eprintln!("Approve this example on the test account's phone.");
28 let deadline = Instant::now() + Duration::from_secs(600);
29 loop {
30 ensure!(Instant::now() < deadline, "Login example timed out");
31 match client.poll_login_guarded(&mut attempt, deadline, || false) {
32 Ok(LoginStatus::Approved) => {
33 let state = client.complete_login(&mut attempt)?;
34 eprintln!("Login complete; credentials remain in memory.");
35 return Ok((client, state));
36 }
37 Ok(LoginStatus::Pending | LoginStatus::Scanned) => {}
38 Ok(status) => bail!("Login stopped: {status:?}"),
39 Err(error) if is_retryable_login_poll_error(&error) => {}
40 Err(error) => return Err(error),
41 }
42 // Only polling an already issued challenge is repeated. QR creation,
43 // credential exchange and sends are never automatically resubmitted.
44 thread::sleep(Duration::from_secs(2));
45 }
46}Sourcepub fn complete_login(&self, attempt: &mut LoginAttempt) -> Result<AccountState>
pub fn complete_login(&self, attempt: &mut LoginAttempt) -> Result<AccountState>
Exchange an approved challenge for secret account credentials once. Keep the returned state in memory or an encrypted application vault.
Examples found in repository?
13pub fn login() -> Result<(NativeClient, AccountState)> {
14 let client = NativeClient::new(NativeOptions::default())?;
15 let device = DeviceProfile::generate("UnifiedPCLinux x86_64".into())?;
16 let qr_directory = wechat_platform::private_tempdir("wechat-protocol-example-")?;
17 let qr_path = qr_directory.path().join("login.png");
18 let mut attempt = client.start_qr_login(device, "Rust protocol example")?;
19 let mut qr = wechat_platform::OpenOptions::new()
20 .write(true)
21 .create_new(true)
22 .mode(0o600)
23 .open(&qr_path)?;
24 qr.write_all(&attempt.qr_png)?;
25 drop(qr);
26 eprintln!("Scan the private QR image: {}", qr_path.display());
27 eprintln!("Approve this example on the test account's phone.");
28 let deadline = Instant::now() + Duration::from_secs(600);
29 loop {
30 ensure!(Instant::now() < deadline, "Login example timed out");
31 match client.poll_login_guarded(&mut attempt, deadline, || false) {
32 Ok(LoginStatus::Approved) => {
33 let state = client.complete_login(&mut attempt)?;
34 eprintln!("Login complete; credentials remain in memory.");
35 return Ok((client, state));
36 }
37 Ok(LoginStatus::Pending | LoginStatus::Scanned) => {}
38 Ok(status) => bail!("Login stopped: {status:?}"),
39 Err(error) if is_retryable_login_poll_error(&error) => {}
40 Err(error) => return Err(error),
41 }
42 // Only polling an already issued challenge is repeated. QR creation,
43 // credential exchange and sends are never automatically resubmitted.
44 thread::sleep(Duration::from_secs(2));
45 }
46}Sourcepub fn refresh_qr_login(&self, attempt: &LoginAttempt) -> Result<LoginAttempt>
pub fn refresh_qr_login(&self, attempt: &LoginAttempt) -> Result<LoginAttempt>
Replace an expired, unscanned QR while preserving installation key-info.
Sourcepub fn renew_session(
&self,
previous: &AccountState,
computer_name: &str,
) -> Result<AccountState>
pub fn renew_session( &self, previous: &AccountState, computer_name: &str, ) -> Result<AccountState>
One trusted AutoAuth exchange, retaining identity and replacing selected key sections atomically in the returned value. Does not silently fall back to QR.
Sourcepub fn sync(&self, state: &mut AccountState, cursor: &[u8]) -> Result<SyncBatch>
pub fn sync(&self, state: &mut AccountState, cursor: &[u8]) -> Result<SyncBatch>
Fetch one scene-6 sync page. An empty cursor starts an initial sync. Returned commands include their original payloads. Persist and handle all required commands before committing the returned cursor; this call does not write history or manage a checkpoint for the application.
Examples found in repository?
4fn main() -> anyhow::Result<()> {
5 if std::env::args().skip(1).any(|arg| arg == "--help") {
6 println!("sync_once: approve a test-account QR login and summarize one initial sync page.");
7 return Ok(());
8 }
9 let (client, mut state) = support::login()?;
10 let batch = client.sync(&mut state, &[])?;
11 let mut messages = 0;
12 for command in &batch.commands {
13 if command.id == 5 {
14 let _message =
15 wechat_protocol::wire::decode_add_message(&command.payload, &state.own_username)?;
16 messages += 1;
17 }
18 }
19 println!(
20 "commands={} add_messages={} other_commands={} continuation={}",
21 batch.commands.len(),
22 messages,
23 batch.commands.len() - messages,
24 batch.continuation
25 );
26 // This is a preview, not a sync consumer: all raw commands remain in `batch`,
27 // and we never save its returned cursor. A persistent client must retain and
28 // handle every required command before committing that cursor.
29 println!("Preview only. No history or sync cursor was saved.");
30 Ok(())
31}pub fn sync_scene( &self, state: &mut AccountState, cursor: &[u8], scene: u32, ) -> Result<SyncBatch>
Sourcepub fn heartbeat(
&self,
state: &mut AccountState,
cursor: &[u8],
) -> Result<Heartbeat>
pub fn heartbeat( &self, state: &mut AccountState, cursor: &[u8], ) -> Result<Heartbeat>
Request heartbeat hints for a committed sync cursor. Hints are not message contents; the application still needs to fetch and handle sync commands.
Sourcepub fn send_text(
&self,
state: &mut AccountState,
destination: &str,
content: &str,
created_at: u32,
client_message_id: u32,
) -> Result<TextSendResult>
pub fn send_text( &self, state: &mut AccountState, destination: &str, content: &str, created_at: u32, client_message_id: u32, ) -> Result<TextSendResult>
Submit exactly once. An I/O error after submission is an uncertain outcome; services must persist the intent and must not automatically retry this call.
destination is an exact WeChat username, not a display name or daemon
account ID. created_at is Unix seconds; client_message_id is a nonzero
protocol ID derived with wire::client_message_id from the caller’s task
ID. Check acceptance and both returned-identity matches in the receipt.
No official-profile history is written by this method.
Examples found in repository?
7fn main() -> anyhow::Result<()> {
8 let args: Vec<_> = std::env::args().skip(1).collect();
9 if args.iter().any(|arg| arg == "--help") {
10 println!("send_text RECIPIENT TASK_ID TEXT\nUses a fresh test-account QR login and submits once.");
11 return Ok(());
12 }
13 ensure!(args.len() == 3, "Usage: send_text RECIPIENT TASK_ID TEXT");
14 let (recipient, task_id, text) = (&args[0], &args[1], &args[2]);
15 let client_id = wire::client_message_id(task_id.as_bytes());
16 ensure!(client_id != 0, "Choose another nonempty task ID");
17 // Validate arguments before requesting any login. A real application records
18 // this operation and timestamp before submitting it, and retains its result.
19 wire::text_send_request(recipient, text, wire::unix_time()?, client_id)?;
20 let (client, mut state) = support::login()?;
21 let created_at = wire::unix_time()?;
22 let receipt = client
23 .send_text(&mut state, recipient, text, created_at, client_id)
24 .context(
25 "Send outcome may be uncertain. Investigate before submitting this operation again",
26 )?;
27 println!("{}", serde_json::to_string_pretty(&receipt)?);
28 ensure!(receipt.accepted, "The server rejected this text send");
29 ensure!(
30 receipt.returned_destination_matches && receipt.returned_client_id_matches,
31 "Receipt identity mismatch; investigate before submitting again"
32 );
33 Ok(())
34}Sourcepub fn send_text_with_source(
&self,
state: &mut AccountState,
destination: &str,
content: &str,
created_at: u32,
client_message_id: u32,
source: Option<&str>,
) -> Result<TextSendResult>
pub fn send_text_with_source( &self, state: &mut AccountState, destination: &str, content: &str, created_at: u32, client_message_id: u32, source: Option<&str>, ) -> Result<TextSendResult>
Submit text with optional native message-source XML. For a simple send use
Self::send_text. This has the same single-submission/uncertainty contract.
Sourcepub fn lookup_contacts(
&self,
state: &mut AccountState,
usernames: &[String],
) -> Result<ContactLookup>
pub fn lookup_contacts( &self, state: &mut AccountState, usernames: &[String], ) -> Result<ContactLookup>
Look up exact usernames on the server. The returned records are not automatically written into a local profile.
pub fn lookup_room( &self, state: &mut AccountState, room_id: &str, version: u32, ) -> Result<RoomRoster>
pub fn init_contacts( &self, state: &mut AccountState, contact_sequence: u32, room_sequence: u32, ) -> Result<InitContactPage>
pub fn download_avatars( &self, state: &mut AccountState, usernames: &[String], ) -> Result<Vec<AvatarBytes>>
pub fn download_voice_chunk( &self, state: &mut AccountState, server_message_id: u64, total_length: u32, offset: u32, ) -> Result<VoiceChunk>
pub fn cdn_config(&self, state: &mut AccountState) -> Result<CdnConfig>
pub fn large_cdn_config( &self, state: &mut AccountState, route: LargeCdnRoute, ) -> Result<CdnConfig>
Submit a URL card once through the app-message carrier. The caller owns the durable intent and must inspect an uncertain submission outcome.
Sourcepub fn send_quote(
&self,
state: &mut AccountState,
destination: &str,
client_id: &str,
created_at: u32,
content: &str,
) -> Result<MediaSendResult>
pub fn send_quote( &self, state: &mut AccountState, destination: &str, client_id: &str, created_at: u32, content: &str, ) -> Result<MediaSendResult>
Submit a quoted reply once. The caller owns the persisted XML, intent and uncertain-submission handling, as for other app-message sends.
pub fn send_quote_with_source( &self, state: &mut AccountState, destination: &str, client_id: &str, created_at: u32, content: &str, source: Option<&str>, ) -> Result<MediaSendResult>
Sourcepub fn reserve_file_upload(
&self,
state: &mut AccountState,
destination: &str,
progress_client_id: &str,
file_name: &str,
plaintext_bytes: usize,
md5: &str,
) -> Result<FileUploadReservation>
pub fn reserve_file_upload( &self, state: &mut AccountState, destination: &str, progress_client_id: &str, file_name: &str, plaintext_bytes: usize, md5: &str, ) -> Result<FileUploadReservation>
Reserve one file-upload message. An uncertain response must not be retried automatically; retain the returned receipt with the send intent.
pub fn send_file( &self, state: &mut AccountState, destination: &str, client_id: &str, created_at: u32, upload: &UploadedMedia, ) -> Result<MediaSendResult>
pub fn send_file_with_reservation( &self, state: &mut AccountState, destination: &str, client_id: &str, created_at: u32, upload: &UploadedMedia, reservation: Option<&FileUploadReservation>, ) -> Result<MediaSendResult>
pub fn send_image( &self, state: &mut AccountState, destination: &str, client_id: &str, upload: &UploadedImage, ) -> Result<MediaSendResult>
pub fn send_video( &self, state: &mut AccountState, destination: &str, client_id: &str, upload: &UploadedVideo, play_seconds: u32, ) -> Result<MediaSendResult>
Sourcepub fn send_voice(
&self,
state: &mut AccountState,
destination: &str,
client_id: &str,
created_at: u32,
duration_ms: u32,
encoded: &[u8],
) -> Result<MediaSendResult>
pub fn send_voice( &self, state: &mut AccountState, destination: &str, client_id: &str, created_at: u32, duration_ms: u32, encoded: &[u8], ) -> Result<MediaSendResult>
Submit one voice note, as consecutive byte ranges when it exceeds one request. Each range is sent once; an error after the first submission is an uncertain outcome that the caller must not retry automatically.