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())
}