rustybook 0.2.1

An ergonomic Facebook client in Rust
Documentation
use std::error::Error;
use std::fs::{
    self,
    OpenOptions,
};
use std::path::Path;
use std::sync::Arc;

use async_trait::async_trait;
use rustybook::{
    Context,
    EventHandler,
    Message,
    Rustybook,
    Typing,
    User,
};
use tracing::{
    info,
    level_filters::LevelFilter,
    warn,
};
use tracing_subscriber::{
    EnvFilter,
    fmt::writer::MakeWriterExt,
};

struct Handler;

#[async_trait]
impl EventHandler for Handler {
    async fn ready(&self, _ctx: Context, user: User) {
        info!("ready: id={} name={:?}", user.id, user.name);
    }

    async fn message(&self, ctx: Context, msg: Message) {
        info!(
            "message: thread={} sender={} text={:?}",
            msg.thread_id, msg.sender_id, msg.text
        );

        match msg.text.as_deref() {
            Some("hi") => {
                if let Err(error) = ctx.client().send_text(&msg.thread_id, "hello").await {
                    warn!("failed to auto-reply: {error}");
                }
            }
            Some(_) => {}
            None => {}
        }
    }

    async fn typing(&self, _ctx: Context, typing: Typing) {
        info!(
            "typing: user_id={} thread_id={:?} is_typing={}",
            typing.user_id, typing.thread_id, typing.is_typing
        );
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    init_logging();

    let mut args = std::env::args();
    let _bin = args.next();

    let Some(cookies_path) = args.next() else {
        eprintln!("usage: cargo run --example messenger --features messenger -- <cookies.json>");
        return Ok(());
    };

    let client = Rustybook::builder()
        .cookies_file_path(cookies_path)
        .handler(Arc::new(Handler))
        .build()?;

    client.start().await?;
    info!("messenger client started (Ctrl+C to stop)");

    tokio::signal::ctrl_c().await?;
    Ok(())
}

fn init_logging() {
    if let Err(error) = install_file_tracing("storage/messenger.log", "info") {
        eprintln!("failed to install tracing file subscriber: {error}");
    }
}

fn install_file_tracing(path: &str, filter: &str) -> Result<(), String> {
    let path = Path::new(path);
    if let Some(parent) = path.parent()
        && !parent.as_os_str().is_empty()
    {
        fs::create_dir_all(parent).map_err(|error| error.to_string())?;
    }

    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(path)
        .map_err(|error| error.to_string())?;
    let writer = std::io::stdout.and(file);

    let subscriber = tracing_subscriber::fmt()
        .with_writer(writer)
        .with_max_level(LevelFilter::DEBUG)
        .with_env_filter(EnvFilter::builder().parse_lossy(filter))
        .finish();

    tracing::subscriber::set_global_default(subscriber).map_err(|error| error.to_string())
}