Skip to main content

NativeClient

Struct NativeClient 

Source
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

Source

pub fn group_mutation_raw( &self, state: &mut AccountState, request: &GroupRpc, ) -> Result<Vec<u8>>

Source§

impl NativeClient

Source

pub fn group_detail_raw( &self, state: &mut AccountState, group: &str, ) -> Result<Vec<u8>>

Source§

impl NativeClient

Source

pub fn group_inventory( &self, state: &mut AccountState, cursor: &[u8], ) -> Result<GroupMetadataPage>

Source

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.

Source

pub fn lookup_contacts_receipt( &self, state: &mut AccountState, ids: &[String], ) -> Result<MetadataReply<ContactLookup>>

Source

pub fn lookup_room_receipt( &self, state: &mut AccountState, id: &str, version: u32, ) -> Result<MetadataReply<RoomRoster>>

Source

pub fn lookup_room_raw( &self, state: &mut AccountState, id: &str, version: u32, ) -> Result<Vec<u8>>

Source

pub fn init_contacts_receipt( &self, state: &mut AccountState, a: u32, b: u32, ) -> Result<MetadataReply<InitContactPage>>

Source§

impl NativeClient

Source

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

Source

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

Source

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

Source

pub fn new(options: NativeOptions) -> Result<Self>

Validate transport options. Construction itself makes no network request.

Examples found in repository?
examples/support/mod.rs (line 14)
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}
Source

pub fn push_connection(&self, state: &AccountState) -> Result<Connection>

Source

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?
examples/support/mod.rs (line 18)
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}
Source

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.

Source

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.

Source

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.

Source

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.

Source

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?
examples/support/mod.rs (line 31)
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}
Source

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?
examples/support/mod.rs (line 33)
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}
Source

pub fn refresh_qr_login(&self, attempt: &LoginAttempt) -> Result<LoginAttempt>

Replace an expired, unscanned QR while preserving installation key-info.

Source

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.

Source

pub fn experimental_call_rpc( &self, state: &mut AccountState, route: CallHostRoute, request: &[u8], ) -> Result<Vec<u8>>

Perform one experimental call host RPC using its fixed CGI/path pair. The owner must first retain its intent, hold account mutation authority and fence account/logout generations. This method does not retry uncertain operations. An active Ax policy is unsupported here; there is no ordinary packing fallback. Returned CommonResponse/application results still need their own checks. Accepted cookie updates follow normal native transport.

Source

pub fn experimental_call_rpc_guarded( &self, state: &mut AccountState, route: CallHostRoute, request: &[u8], before_submit: impl FnOnce() -> Result<()>, ) -> Result<Vec<u8>>

As Self::experimental_call_rpc, with one final owner check after handshake/preparation and before handing business bytes to transport. A guard cannot retract bytes after transport handoff. Handshake traffic can already have occurred when this guard stops the business request.

Source

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?
examples/sync_once.rs (line 10)
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}
Source

pub fn sync_scene( &self, state: &mut AccountState, cursor: &[u8], scene: u32, ) -> Result<SyncBatch>

Source

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.

Source

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?
examples/send_text.rs (line 23)
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}
Source

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.

Source

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.

Source

pub fn lookup_room( &self, state: &mut AccountState, room_id: &str, version: u32, ) -> Result<RoomRoster>

Source

pub fn init_contacts( &self, state: &mut AccountState, contact_sequence: u32, room_sequence: u32, ) -> Result<InitContactPage>

Source

pub fn download_avatars( &self, state: &mut AccountState, usernames: &[String], ) -> Result<Vec<AvatarBytes>>

Source

pub fn download_voice_chunk( &self, state: &mut AccountState, server_message_id: u64, total_length: u32, offset: u32, ) -> Result<VoiceChunk>

Source

pub fn cdn_config(&self, state: &mut AccountState) -> Result<CdnConfig>

Source

pub fn large_cdn_config( &self, state: &mut AccountState, route: LargeCdnRoute, ) -> Result<CdnConfig>

Source

pub fn send_share( &self, state: &mut AccountState, destination: &str, client_id: &str, created_at: u32, content: &str, ) -> Result<MediaSendResult>

Submit a URL card once through the app-message carrier. The caller owns the durable intent and must inspect an uncertain submission outcome.

Source

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.

Source

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>

Source

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.

Source

pub fn send_file( &self, state: &mut AccountState, destination: &str, client_id: &str, created_at: u32, upload: &UploadedMedia, ) -> Result<MediaSendResult>

Source

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>

Source

pub fn send_image( &self, state: &mut AccountState, destination: &str, client_id: &str, upload: &UploadedImage, ) -> Result<MediaSendResult>

Source

pub fn send_video( &self, state: &mut AccountState, destination: &str, client_id: &str, upload: &UploadedVideo, play_seconds: u32, ) -> Result<MediaSendResult>

Source

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.

Source§

impl NativeClient

Source

pub fn contact_sync( &self, state: &mut AccountState, cursor: &[u8], message_cursor: &[u8], continuation: bool, ) -> Result<ContactSyncBatch>

Trait Implementations§

Source§

impl PersonalTransport for NativeClient

Source§

fn call_rpc( &self, state: &mut AccountState, route: CallHostRoute, request: &[u8], ) -> Result<Vec<u8>>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more