fourchan-rs 0.1.1

Async 4chan JSON API client and type bindings
Documentation
// cargo run --example poll_thread -- po [thread]

// Polls a single thread with `If-Modified-Since` and prints replies as they
// arrive. Demonstrates the `Conditional<T>` flow: feed the previous
// `Last-Modified` back in, get `Ok(None)` on `304 Not Modified`, and only
// re-render when something actually changed.

use std::collections::HashSet;
use std::env;
use std::time::Duration;

use chan::{Client, Post};

#[chan::tokio::main]
async fn main() -> chan::Result<()> {
    let mut args = env::args().skip(1);
    let board = args.next().unwrap_or_else(|| "po".to_string());
    let thread_no: u64 = args
        .next()
        .and_then(|s| s.parse().ok())
        .expect("usage: poll_thread <board> <thread_no>");

    let client = Client::new();
    let mut seen: HashSet<u64> = HashSet::new();
    let mut since: Option<String> = None;

    loop {
        match client
            .get_full_thread_if_modified(&board, thread_no, since.as_deref())
            .await
        {
            Ok(None) => {
                eprintln!("304 Not Modified");
            }
            Ok(Some(cond)) => {
                since = cond.last_modified;
                for post in &cond.value.posts {
                    if seen.insert(post.no) {
                        print_post(&board, post);
                    }
                }
                if cond.value.op().archived || cond.value.op().closed {
                    eprintln!("Thread archived or closed. Exiting.");
                    return Ok(());
                }
            }
            Err(chan::Error::NotFound(_)) => {
                eprintln!("Thread 404'd. Exiting.");
                return Ok(());
            }
            Err(e) => return Err(e),
        }
        tokio::time::sleep(Duration::from_secs(30)).await;
    }
}

fn print_post(board: &str, p: &Post) {
    let who = match (&p.trip, &p.id) {
        (Some(t), Some(id)) => format!("{} {} ({})", p.name, t, id),
        (Some(t), None) => format!("{} {}", p.name, t),
        (None, Some(id)) => format!("{} ({})", p.name, id),
        (None, None) => p.name.clone(),
    };
    println!("--- >>{} {} [{}] ---", p.no, who, p.now);
    if let Some(sub) = &p.sub {
        println!("Subject: {}", sub);
    }
    if let Some(att) = &p.attachment {
        println!("File: {} ({} bytes)", att.url(board), att.size);
    }
    if let Some(com) = &p.com {
        println!("{}", com);
    }
    println!();
}