poll_thread/
poll_thread.rs1use std::collections::HashSet;
9use std::env;
10use std::time::Duration;
11
12use chan::{Client, Post};
13
14#[chan::tokio::main]
15async fn main() -> chan::Result<()> {
16 let mut args = env::args().skip(1);
17 let board = args.next().unwrap_or_else(|| "po".to_string());
18 let thread_no: u64 = args
19 .next()
20 .and_then(|s| s.parse().ok())
21 .expect("usage: poll_thread <board> <thread_no>");
22
23 let client = Client::new();
24 let mut seen: HashSet<u64> = HashSet::new();
25 let mut since: Option<String> = None;
26
27 loop {
28 match client
29 .get_full_thread_if_modified(&board, thread_no, since.as_deref())
30 .await
31 {
32 Ok(None) => {
33 eprintln!("304 Not Modified");
34 }
35 Ok(Some(cond)) => {
36 since = cond.last_modified;
37 for post in &cond.value.posts {
38 if seen.insert(post.no) {
39 print_post(&board, post);
40 }
41 }
42 if cond.value.op().archived || cond.value.op().closed {
43 eprintln!("Thread archived or closed. Exiting.");
44 return Ok(());
45 }
46 }
47 Err(chan::Error::NotFound(_)) => {
48 eprintln!("Thread 404'd. Exiting.");
49 return Ok(());
50 }
51 Err(e) => return Err(e),
52 }
53 tokio::time::sleep(Duration::from_secs(30)).await;
54 }
55}
56
57fn print_post(board: &str, p: &Post) {
58 let who = match (&p.trip, &p.id) {
59 (Some(t), Some(id)) => format!("{} {} ({})", p.name, t, id),
60 (Some(t), None) => format!("{} {}", p.name, t),
61 (None, Some(id)) => format!("{} ({})", p.name, id),
62 (None, None) => p.name.clone(),
63 };
64 println!("--- >>{} {} [{}] ---", p.no, who, p.now);
65 if let Some(sub) = &p.sub {
66 println!("Subject: {}", sub);
67 }
68 if let Some(att) = &p.attachment {
69 println!("File: {} ({} bytes)", att.url(board), att.size);
70 }
71 if let Some(com) = &p.com {
72 println!("{}", com);
73 }
74 println!();
75}