mod structs; use std::env; use std::io::{stdin, BufRead}; use std::process::{exit, Command}; use std::os::unix::process::CommandExt; use rss::Channel; use reqwest::Client; extern crate tokio; fn main() { let view_image_command = ["xdg-open", "{}"]; let nitter_instance = "https://nitter.nixnet.services"; let twitter_accounts = [ structs::TwitterAccount { display_name: "calli", twitter_username: "moricalliope", schedule_keyword: "schedule" }, structs::TwitterAccount { display_name: "kiara", twitter_username: "takanashikiara", schedule_keyword: "schedule" }, ]; let mut args = env::args(); args.next(); let mut preselect = String::new(); for arg in args { preselect.push_str(&format!("{} ", arg)); } let mut preselect = preselect.trim().to_lowercase(); if preselect.is_empty() { let mut index = 0; for account in &twitter_accounts { eprintln!("{}. {}", index, &account.display_name); index += 1; } eprint!("> "); // https://www.programming-idioms.org/idiom/120/read-integer-from-stdin/1906/rust preselect = stdin() .lock() .lines() .next() .expect("stdin is not avaliable") .expect("can't read from stdin") .trim() .to_lowercase(); } let mut account_index = 0; match preselect.parse::() { Ok(index) => { if twitter_accounts.len() > index { account_index = index; } else { eprintln!("Invalid index"); exit(1); } }, Err(_) => { let mut exhausted = true; for account in &twitter_accounts { if &account.display_name.to_lowercase() == &preselect { exhausted = false; break; } account_index += 1; } if exhausted { eprintln!("Cannot find account"); exit(1); } } }; let account = &twitter_accounts[account_index]; let text = tokio::runtime::Runtime::new() .unwrap() .block_on(get_feed_text(nitter_instance, &account)); let channel = Channel::read_from(&text.as_bytes()[..]).unwrap(); if channel.items().is_empty() { eprintln!("Cannot find tweet"); exit(1); } let src_attrib = &channel .items()[0] .description() .unwrap() .rsplitn(2, "\n") .nth(0) .unwrap() .splitn(3, " ") .nth(1) .unwrap(); println!("{}", &channel.items()[0].title().unwrap()); let mut args = Vec::with_capacity(view_image_command.len() - 1); for arg in &view_image_command[1..] { if arg == &"{}" { args.push(&src_attrib[5..src_attrib.len() - 1]); } else { args.push(&arg); } } Command::new(&view_image_command[0]) .args(args) .exec(); } async fn get_feed_text(nitter_instance: &str, account: &structs::TwitterAccount) -> String { let mut url = nitter_instance.to_string(); url.push_str("/search/rss"); match Client::new().get(&url) .query(&[ ("f", "tweets"), ("f-images", "on"), ("q", &format!("{} from:{}", &account.schedule_keyword, &account.twitter_username)) ]) .send() .await { Ok(resp) => { match resp.text().await { Ok(text) => text, Err(err) => { eprintln!("Failed to get text due to {}", err); exit(1); } } }, Err(err) => { eprintln!("Failed to connect to Nitter due to {}", err); exit(1); } } }