rustybook 0.2.0

An ergonomic Facebook client in Rust
Documentation
use std::sync::Arc;

use crate::{
    Context,
    Error,
    EventHandler,
    Rustybook,
    SendReceipt,
    User,
};

impl Rustybook {
    /// Starts messenger runtime and dispatches incoming events to `handler`.
    ///
    /// Available only with the `messenger` feature.
    pub async fn start<H>(&self, handler: Arc<H>) -> Result<(), Error>
    where
        H: EventHandler,
    {
        let client = self.ensure_messenger_client().await?;
        client.start_listening().await?;
        let mut events = client.subscribe();
        let ctx = Context::new(self.clone());
        let ready_user = self.current_user().await?;
        #[cfg(all(feature = "messenger", feature = "cache"))]
        self.set_user(ready_user.clone()).await;

        let task = tokio::spawn(async move {
            while let Ok(event) = events.recv().await {
                #[cfg(all(feature = "messenger", feature = "cache"))]
                let mut event = event;
                #[cfg(all(feature = "messenger", feature = "cache"))]
                ctx.client().inner.cache.update(&mut event);
                crate::events::dispatch_event(&handler, ctx.clone(), &ready_user, event).await;
            }
        });

        let mut handler_task = self.inner.handler_task.lock().await;
        if let Some(existing) = handler_task.replace(task) {
            existing.abort();
        }

        Ok(())
    }

    /// Stops messenger runtime if it is currently running.
    ///
    /// Available only with the `messenger` feature.
    pub async fn stop(&self) -> Result<(), Error> {
        let client = {
            let guard = self.inner.messenger.lock().await;
            guard.clone()
        };

        if let Some(client) = client {
            client.stop_listening().await?;
        }
        let mut handler_task = self.inner.handler_task.lock().await;
        if let Some(task) = handler_task.take() {
            task.abort();
        }

        Ok(())
    }

    /// Sends a text message to a thread.
    ///
    /// Available only with the `messenger` feature and requires `start()` first.
    pub async fn send_text(&self, thread_id: &str, text: &str) -> Result<SendReceipt, Error> {
        let client = {
            let guard = self.inner.messenger.lock().await;
            guard.clone()
        };

        let Some(client) = client else {
            return Err(Error::Config(
                "messenger is not started; call start() first".to_string(),
            ));
        };

        let receipt = client.send_text(thread_id, text).await?;
        Ok(SendReceipt {
            thread_id: receipt.thread_id,
            message_id: receipt.message_id,
            offline_threading_id: receipt.offline_threading_id,
        })
    }

    async fn ensure_messenger_client(&self) -> Result<rustybook_messenger::MessengerClient, Error> {
        {
            let guard = self.inner.messenger.lock().await;
            if let Some(existing) = guard.as_ref() {
                return Ok(existing.clone());
            }
        }

        let session = self
            .inner
            .session
            .as_ref()
            .ok_or_else(|| {
                Error::Config(
                    "messenger requires cookies_file_path on RustybookBuilder".to_string(),
                )
            })?
            .clone();

        let mut builder = rustybook_messenger::MessengerClient::builder()
            .shared_session(
                session.user_id,
                session.cookie_header,
                self.inner.http.clone(),
            )
            .online(self.inner.messenger_online);

        if let Some(user_agent) = self.inner.user_agent.as_deref() {
            builder = builder.user_agent(user_agent.to_string());
        }

        if let Some(proxy) = self.inner.proxy.as_deref() {
            builder = builder.proxy(proxy.to_string());
        }

        let client = builder.build()?;

        let mut guard = self.inner.messenger.lock().await;
        if guard.is_none() {
            *guard = Some(client.clone());
        }

        Ok(client)
    }

    async fn current_user(&self) -> Result<User, Error> {
        let client = {
            let guard = self.inner.messenger.lock().await;
            guard.clone()
        };

        let Some(client) = client else {
            return Err(Error::Config(
                "messenger is not started; call start() first".to_string(),
            ));
        };

        let id = client
            .uid()
            .await
            .ok_or_else(|| Error::Config("missing user id from messenger state".to_string()))?;

        let name = client.name().await;
        Ok(User { id, name })
    }
}