diff options
-rw-r--r-- | src/commands/fav.rs | 14 | ||||
-rw-r--r-- | src/commands/follow.rs | 7 | ||||
-rw-r--r-- | src/commands/look_up.rs | 7 | ||||
-rw-r--r-- | src/commands/quit.rs | 2 | ||||
-rw-r--r-- | src/commands/show_cache.rs | 18 | ||||
-rw-r--r-- | src/commands/thread.rs | 15 | ||||
-rw-r--r-- | src/commands/twete.rs | 67 | ||||
-rw-r--r-- | src/commands/view.rs | 13 | ||||
-rw-r--r-- | src/display/mod.rs | 98 | ||||
-rw-r--r-- | src/main.rs | 36 | ||||
-rw-r--r-- | src/tw/events.rs | 10 | ||||
-rw-r--r-- | src/tw/mod.rs | 75 | ||||
-rw-r--r-- | src/tw/tweet.rs | 17 | ||||
-rw-r--r-- | src/tw/user.rs | 39 |
14 files changed, 225 insertions, 193 deletions
diff --git a/src/commands/fav.rs b/src/commands/fav.rs index 89e1987..6109310 100644 --- a/src/commands/fav.rs +++ b/src/commands/fav.rs @@ -19,13 +19,16 @@ fn unfav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { match maybe_id { Ok(twid) => { if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self - queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id)); + match queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id)) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { tweeter.display_info.status(format!("No tweet for id: {:?}", twid)); } } Err(e) => { - println!("Invalid id: {}", e); + tweeter.display_info.status(format!("Invalid id: {}", e)); } } } @@ -42,13 +45,16 @@ fn fav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { Ok(twid) => { // tweeter.to_twitter_tweet_id(twid)... if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self - queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id)); + match queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id)) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { tweeter.display_info.status(format!("No tweet for id: {:?}", twid)); } } Err(e) => { - println!("Invalid id: {}", e); + tweeter.display_info.status(format!("Invalid id: {}", e)); } } } diff --git a/src/commands/follow.rs b/src/commands/follow.rs index ad121e5..b0dc8a7 100644 --- a/src/commands/follow.rs +++ b/src/commands/follow.rs @@ -14,7 +14,10 @@ pub static UNFOLLOW: Command = Command { fn unfl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let screen_name = line.trim(); - queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name)); + match queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name)) { + Ok(_resp) => (), + Err(e) => tweeter.display_info.status(format!("unfl request error: {}", e)) + } } pub static FOLLOW: Command = Command { @@ -25,5 +28,5 @@ pub static FOLLOW: Command = Command { fn fl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let screen_name = line.trim(); - println!("fl resp: {:?}", queryer.do_api_post(&format!("{}?screen_name={}", UNFOLLOW_URL, screen_name))); + tweeter.display_info.status(format!("fl resp: {:?}", queryer.do_api_post(&format!("{}?screen_name={}", UNFOLLOW_URL, screen_name)))); } diff --git a/src/commands/look_up.rs b/src/commands/look_up.rs index 701ce2d..dff56aa 100644 --- a/src/commands/look_up.rs +++ b/src/commands/look_up.rs @@ -12,10 +12,11 @@ pub static LOOK_UP_USER: Command = Command { }; fn look_up_user(line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { - if let Some(user) = tweeter.fetch_user(&line, &mut queryer) { - println!("{:?}", user); + // should probably just pass the id? + if let Some(user) = tweeter.fetch_user(&line, &mut queryer).map(|x| x.clone()) { + tweeter.display_info.recv(display::Infos::User(user)); } else { -// println!("Couldn't retrieve {}", userid); + tweeter.display_info.status(format!("Couldn't retrieve {}", line)); } } diff --git a/src/commands/quit.rs b/src/commands/quit.rs index 982c48f..716c412 100644 --- a/src/commands/quit.rs +++ b/src/commands/quit.rs @@ -12,7 +12,7 @@ pub static QUIT: Command = Command { }; fn quit(_line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) { - println!("Bye bye!"); + tweeter.display_info.status("Bye bye!".to_owned()); tweeter.store_cache(); exit(0); } diff --git a/src/commands/show_cache.rs b/src/commands/show_cache.rs index 3c31697..59ecfc2 100644 --- a/src/commands/show_cache.rs +++ b/src/commands/show_cache.rs @@ -9,23 +9,23 @@ pub static SHOW_CACHE: Command = Command { exec: show_cache }; -fn show_cache(line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { - println!("----* USERS *----"); +fn show_cache(_line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { + tweeter.display_info.status("----* USERS *----".to_owned()); for (uid, user) in &tweeter.users { - println!("User: {} -> {:?}", uid, user); + tweeter.display_info.status(format!("User: {} -> {:?}", uid, user)); } - println!("----* TWEETS *----"); + tweeter.display_info.status("----* TWEETS *----".to_owned()); for (tid, tweet) in &tweeter.tweets { - println!("Tweet: {} -> {:?}", tid, tweet); + tweeter.display_info.status(format!("Tweet: {} -> {:?}", tid, tweet)); } - println!("----* FOLLOWERS *----"); + tweeter.display_info.status("----* FOLLOWERS *----".to_owned()); for uid in &tweeter.followers.clone() { - let user_res = tweeter.fetch_user(uid, &mut queryer); + let user_res = tweeter.fetch_user(uid, &mut queryer).map(|x| x.clone()); match user_res { Some(user) => { - println!("Follower: {} - {:?}", uid, user); + tweeter.display_info.status(format!("Follower: {} - {:?}", uid, user)); } - None => { println!(" ..."); } + None => { tweeter.display_info.status(" ...".to_owned()); } } } } diff --git a/src/commands/thread.rs b/src/commands/thread.rs index 8880af7..6f05048 100644 --- a/src/commands/thread.rs +++ b/src/commands/thread.rs @@ -14,7 +14,7 @@ pub static FORGET_THREAD: Command = Command { fn forget(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) { tweeter.forget_thread(line.trim().to_string()); - println!("Ok! Forgot thread {}", line.trim().to_string()); + tweeter.display_info.status(format!("Ok! Forgot thread {}", line.trim().to_string())); } pub static REMEMBER_THREAD: Command = Command { @@ -55,20 +55,9 @@ pub static LIST_THREADS: Command = Command { }; fn ls_threads(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { - println!("Threads: "); let threads: Vec<String> = tweeter.threads().collect::<Vec<&String>>().into_iter().map(|x| x.to_owned()).collect::<Vec<String>>(); for k in threads { - println!("Thread: {}", k); let latest_inner_id = tweeter.latest_in_thread(k.to_owned()).unwrap().to_owned(); - // should be able to just directly render TweetId.. and threads should be Vec<TweetId>... - let twete_id_TEMP = tweeter.retrieve_tweet(&TweetId::Bare(latest_inner_id)).map(|x| x.id.to_owned()); - if let Some(twete) = twete_id_TEMP { - // gross.. - // and this ought to be a command to tweeter.display_info anyway... - display::render_twete(&TweetId::Twitter(twete), tweeter); - println!(""); - } else { - println!("ERROR no tweet for remembered thread."); - } + tweeter.display_info.recv(display::Infos::TweetWithContext(TweetId::Bare(latest_inner_id), format!("Thread: {}", k))) } } diff --git a/src/commands/twete.rs b/src/commands/twete.rs index eb21a15..f057e5f 100644 --- a/src/commands/twete.rs +++ b/src/commands/twete.rs @@ -5,8 +5,6 @@ use tw::TweetId; use commands::Command; -use std::str::FromStr; - static DEL_TWEET_URL: &str = "https://api.twitter.com/1.1/statuses/destroy"; static RT_TWEET_URL: &str = "https://api.twitter.com/1.1/statuses/retweet"; static CREATE_TWEET_URL: &str = "https://api.twitter.com/1.1/statuses/update.json"; @@ -22,13 +20,16 @@ fn del(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { Ok(twid) => { // TODO this really converts twid to a TweetId::Twitter if let Some(twitter_id) = tweeter.retrieve_tweet(&twid).map(|x| x.id.to_owned()) { - queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id)); + match queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id)) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { tweeter.display_info.status(format!("No tweet for id {:?}", twid)); } }, Err(e) => { - tweeter.display_info.status(format!("Invalid id: {:?}", line)); + tweeter.display_info.status(format!("Invalid id: {:?} ({})", line, e)); } } } @@ -39,17 +40,19 @@ pub static TWETE: Command = Command { exec: twete }; -fn twete(line: String, _tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { +fn twete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let text = line.trim(); let substituted = ::url_encode(text); - println!("msg len: {}", text.len()); - println!("excessively long? {}", text.len() > 140); - if text.len() > 140 { - queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted)); + if text.len() <= 140 { + match queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted)) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { - queryer.do_api_post(&format!("{}?status={}&weighted_character_count=true", CREATE_TWEET_URL, substituted)); + // TODO: this 140 is maybe sometimes 280.. :) + // and see if weighted_character_count still does things? + tweeter.display_info.status(format!("tweet is too long: {}/140 chars", text.len())); } -// println!("{}", &format!("{}?status={}", CREATE_TWEET_URL, substituted)); } pub static THREAD: Command = Command { @@ -69,13 +72,16 @@ fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { match maybe_id { Ok(twid) => { if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self - let handle = &tweeter.retrieve_user(&twete.author_id).unwrap().handle; + let handle = &tweeter.retrieve_user(&twete.author_id).unwrap().handle.to_owned(); // TODO: definitely breaks if you change your handle right now if handle == &tweeter.current_user.handle { let substituted = ::url_encode(reply); - queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)); + match queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { - println!("you can only thread your own tweets"); + tweeter.display_info.status("you can only thread your own tweets".to_owned()); // ask if it should .@ instead? } } @@ -85,10 +91,10 @@ fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { } } } else { - println!("thread <id> your sik reply"); + tweeter.display_info.status("thread <id> your sik reply".to_owned()); } } else { - println!("thread <id> your sik reply"); + tweeter.display_info.status("thread <id> your sik reply".to_owned()); } } @@ -129,21 +135,23 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let decorated_ats: Vec<String> = ats.into_iter().map(|x| format!("@{}", x)).collect(); let full_reply = format!("{} {}", decorated_ats.join(" "), reply); let substituted = ::url_encode(&full_reply); - // println!("{}", (&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id))); - queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)); + match queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { tweeter.display_info.status(format!("No tweet for id: {:?}", twid)); } }, Err(e) => { - tweeter.display_info.status(format!("Cannot parse input: {:?}", id_str)); + tweeter.display_info.status(format!("Cannot parse input: {:?} ({})", id_str, e)); } } } else { - println!("rep <id> your sik reply"); + tweeter.display_info.status("rep <id> your sik reply".to_owned()); } } else { - println!("rep <id> your sik reply"); + tweeter.display_info.status("rep <id> your sik reply".to_owned()); } } @@ -172,14 +180,16 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { twete.id ) ); - println!("{}", substituted); - queryer.do_api_post( + match queryer.do_api_post( &format!("{}?status={}&attachment_url={}", CREATE_TWEET_URL, substituted, attachment_url ) - ); + ) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { tweeter.display_info.status(format!("No tweet found for id {:?}", twid)); } @@ -189,10 +199,10 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { } } } else { - println!("rep <id> your sik reply"); + tweeter.display_info.status("rep <id> your sik reply".to_owned()); } } else { - println!("rep <id> your sik reply"); + tweeter.display_info.status("rep <id> your sik reply".to_owned()); } } @@ -207,7 +217,10 @@ fn retwete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) Ok(twid) => { // TODO this really converts twid to a TweetId::Twitter if let Some(twitter_id) = tweeter.retrieve_tweet(&twid).map(|x| x.id.to_owned()) { - queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id)); + match queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id)) { + Ok(_) => (), + Err(e) => tweeter.display_info.status(e) + } } else { tweeter.display_info.status(format!("No tweet for id {:?}", twid)); } diff --git a/src/commands/view.rs b/src/commands/view.rs index a6ce647..0c9e974 100644 --- a/src/commands/view.rs +++ b/src/commands/view.rs @@ -5,8 +5,6 @@ use tw::TweetId; use commands::Command; -use std::str::FromStr; - use display; pub static VIEW: Command = Command { @@ -19,7 +17,10 @@ fn view(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) { match TweetId::parse(line) { Ok(twid) => { if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { - tweeter.display_info.recv(display::Infos::Tweet(TweetId::Twitter(twete.id.to_owned()))); + tweeter.display_info.recv(display::Infos::TweetWithContext( + TweetId::Twitter(twete.id.to_owned()), + format!("link: https://twitter.com/i/web/status/{}", twete.id) + )); } else { tweeter.display_info.status(format!("No tweet for id {:?}", twid)); } @@ -28,8 +29,6 @@ fn view(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) { tweeter.display_info.status(format!("Invalid id {:?}", e)); } } -// display::render_twete(&twete.id, tweeter); -// println!(" link: https://twitter.com/i/web/status/{}", twete.id); } pub static VIEW_THREAD: Command = Command { @@ -60,8 +59,6 @@ fn view_tr(line: String, mut tweeter: &mut tw::TwitterCache, queryer: &mut Query } tweeter.display_info.recv(display::Infos::Thread(thread)); -// display::render_twete(&twete.id, tweeter); -// println!("link: https://twitter.com/i/web/status/{}", twete.id); } pub static VIEW_THREAD_FORWARD: Command = Command { @@ -70,7 +67,7 @@ pub static VIEW_THREAD_FORWARD: Command = Command { exec: view_tr_forward }; -fn view_tr_forward(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { +fn view_tr_forward(_line: String, _tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer) { // first see if we have a thread for the tweet named // if we do not, we'll have to mimic a request like // curl 'https://twitter.com/jojonila/status/914383908090691584' \ diff --git a/src/display/mod.rs b/src/display/mod.rs index 4faaf5b..73a1e09 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -15,45 +15,91 @@ use std; #[derive(Clone)] pub enum Infos { Tweet(TweetId), + TweetWithContext(TweetId, String), Thread(Vec<TweetId>), Event(tw::events::Event), - DM(String) + DM(String), + User(tw::user::User) } -pub fn paint(tweeter: &mut ::tw::TwitterCache) { +pub struct DisplayInfo { + pub log_seek: u32, + pub infos_seek: u32, + pub log: Vec<String>, + pub infos: Vec<Infos> +} + +impl Default for DisplayInfo { + fn default() -> Self { + DisplayInfo { + log_seek: 0, + infos_seek: 0, + log: Vec::new(), + infos: Vec::new() + } + } +} + +impl DisplayInfo { + pub fn status(&mut self, stat: String) { + self.log.push(stat); + } + + pub fn recv(&mut self, info: Infos) { + self.infos.push(info); + } +} + +pub fn paint(tweeter: &mut ::tw::TwitterCache) -> Result<(), std::io::Error> { match termion::terminal_size() { - Ok((width, height)) => { + Ok((_width, height)) => { // draw input prompt - println!("{}{}", cursor::Goto(1, height - 6), clear::CurrentLine); - println!("{}{}>", cursor::Goto(1, height - 5), clear::CurrentLine); - println!("{}{}", cursor::Goto(1, height - 4), clear::CurrentLine); + print!("{}{}", cursor::Goto(1, height - 6), clear::CurrentLine); + print!("{}{}>", cursor::Goto(1, height - 5), clear::CurrentLine); + print!("{}{}", cursor::Goto(1, height - 4), clear::CurrentLine); let mut i = 0; let log_size = 4; let last_elem = tweeter.display_info.log.len().saturating_sub(log_size); { - let to_show = tweeter.display_info.log.drain(last_elem..); + let to_show = tweeter.display_info.log[last_elem..].iter().rev(); for line in to_show { - println!("{}{}{}", cursor::Goto(1, height - i), clear::CurrentLine, line); + print!("{}{}{}/{}: {}", cursor::Goto(1, height - i), clear::CurrentLine, tweeter.display_info.log.len() - 1 - i as usize, tweeter.display_info.log.len() - 1, line); i = i + 1; } } while i < log_size as u16 { - println!("{}{}", cursor::Goto(1, height - i), clear::CurrentLine); + print!("{}{}", cursor::Goto(1, height - i), clear::CurrentLine); i = i + 1; } // draw status lines // draw tweets - let last_twevent = tweeter.display_info.infos.len().saturating_sub(height as usize - 4); + let last_twevent = tweeter.display_info.infos.len().saturating_sub(height as usize - 4).saturating_sub(tweeter.display_info.infos_seek as usize); let last_few_twevent: Vec<Infos> = tweeter.display_info.infos[last_twevent..].iter().map(|x| x.clone()).rev().collect::<Vec<Infos>>(); let mut h = 7; for info in last_few_twevent { - let mut to_draw = match info { + let to_draw: Vec<String> = match info { Infos::Tweet(id) => { - render_twete(&id, tweeter).iter().map(|x| x.to_owned()).rev().collect() + let pre_split: Vec<String> = render_twete(&id, tweeter); + let split_on_newline: Vec<String> = pre_split.into_iter().flat_map(|x| x.split("\n").map(|x| x.to_owned()).collect::<Vec<String>>()).collect(); + let wrapped: Vec<String> = split_on_newline.iter() + .map(|x| x.chars().collect::<Vec<char>>()) + .flat_map(|x| x.chunks(_width as usize) + .map(|x| x.into_iter().collect::<String>()) + .collect::<Vec<String>>()) + .collect(); + wrapped.into_iter().rev().collect() + } + Infos::TweetWithContext(id, context) => { + let mut lines = render_twete(&id, tweeter).iter().map(|x| x.to_owned()).rev().collect::<Vec<String>>(); + lines.push(context); + lines } - Infos::Thread(ids) => { - vec![format!("{}{}I'd show a thread if I knew how", cursor::Goto(1, height - h), clear::CurrentLine)] + Infos::Thread(_ids) => { + let mut lines = vec![format!("{}{}I'd show a thread if I knew how", cursor::Goto(1, height - h), clear::CurrentLine)]; + lines.push("".to_owned()); +// lines.push(format!("link: https://twitter.com/i/web/status/{}", id)); + lines }, Infos::Event(e) => { e.clone().render(tweeter).into_iter().rev().collect() @@ -61,35 +107,39 @@ pub fn paint(tweeter: &mut ::tw::TwitterCache) { Infos::DM(msg) => { vec![format!("{}{}DM: {}", cursor::Goto(1, height - h), clear::CurrentLine, msg)] } + Infos::User(user) => { + vec![ + format!("{} (@{})", user.name, user.handle) + ] + } }; for line in to_draw { - println!("{}{}{}", cursor::Goto(1, height - h), clear::CurrentLine, line); + print!("{}{}{}", cursor::Goto(1, height - h), clear::CurrentLine, line); h = h + 1; if h >= height { print!("{}", cursor::Goto(2, height - 6)); - stdout().flush(); - return; + return stdout().flush(); } } - println!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine); + print!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine); h = h + 1; if h >= height { print!("{}", cursor::Goto(2, height - 6)); - stdout().flush(); - return; + return stdout().flush(); } } while h < height { - println!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine); + print!("{}{}", cursor::Goto(1, height - h), clear::CurrentLine); h = h + 1; } - print!("{}", cursor::Goto(2, height - 6)); - stdout().flush(); + print!("{}", cursor::Goto(2, height - 5)); + stdout().flush()?; }, Err(e) => { println!("Can't get term dimensions: {}", e); } } + Ok(()) } fn color_for(handle: &String) -> termion::color::Fg<&color::Color> { @@ -125,7 +175,7 @@ impl Render for tw::events::Event { result.push("---------------------------------".to_string()); { let user = tweeter.retrieve_user(&user_id).unwrap(); - println!(" quoted_tweet : {} (@{})", user.name, user.handle); + result.push(format!(" quoted_tweet : {} (@{})", user.name, user.handle)); } render_twete(&TweetId::Twitter(twete_id), tweeter); } diff --git a/src/main.rs b/src/main.rs index 4352fe9..c46fec1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,10 +55,10 @@ pub struct Queryer { } impl Queryer { - fn do_api_get(&mut self, url: &str) -> Option<serde_json::Value> { + fn do_api_get(&mut self, url: &str) -> Result<serde_json::Value, String> { self.issue_request(signed_api_get(url)) } - fn do_api_post(&mut self, url: &str) -> Option<serde_json::Value> { + fn do_api_post(&mut self, url: &str) -> Result<serde_json::Value, String> { self.issue_request(signed_api_post(url)) } /* @@ -66,7 +66,7 @@ impl Queryer { self.issue_request(signed_web_get(url)) }*/ // TODO: make this return the status as well! - fn issue_request(&mut self, req: hyper::client::Request) -> Option<serde_json::Value> { + fn issue_request(&mut self, req: hyper::client::Request) -> Result<serde_json::Value, String> { let lookup = self.client.request(req); let resp: hyper::Response = self.core.run(lookup).unwrap(); @@ -79,19 +79,13 @@ impl Queryer { match serde_json::from_slice(&resp_body) { Ok(value) => { if status != hyper::StatusCode::Ok { - println!("!! Requests returned status: {}", status); - println!("{}", value); - None + Err(format!("!! Requests returned status: {}\n{}", status, value)) } else { - Some(value) + Ok(value) } } Err(e) => { - if status != hyper::StatusCode::Ok { - println!("!! Requests returned status: {}", status); - } - println!("error deserializing json: {}", e); - None + Err(format!("!! Requests returned status: {}\nerror deserializing json: {}", status, e)) } } } @@ -171,7 +165,6 @@ fn signed_api_req(url: &str, method: Method) -> hyper::client::Request { headers.set(Accept("*/*".to_owned())); }; -// println!("Request built: {:?}", req); req } @@ -181,8 +174,6 @@ fn main() { // let url = "https://stream.twitter.com/1.1/statuses/filter.json"; // let url = "https://stream.twitter.com/1.1/statuses/sample.json"; - println!("starting!"); - let (ui_tx, mut ui_rx) = chan::sync::<Vec<u8>>(0); let mut twete_rx = connect_twitter_stream(); @@ -231,8 +222,6 @@ fn main() { } } } - - println!("Bye bye"); } fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>, mut tweeter: &mut tw::TwitterCache, mut queryer: &mut ::Queryer) -> Option<(chan::Receiver<Vec<u8>>, chan::Receiver<Vec<u8>>)> { @@ -243,7 +232,6 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>, twete_rx.recv() -> twete => match twete { Some(line) => { let jsonstr = std::str::from_utf8(&line).unwrap().trim(); -// println!("{}", jsonstr); /* TODO: replace from_str with from_slice */ let json: serde_json::Value = serde_json::from_str(&jsonstr).unwrap(); tw::handle_message(json, &mut tweeter, &mut queryer); @@ -252,7 +240,7 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>, } } None => { - println!("Twitter stream hung up..."); + tweeter.display_info.status("Twitter stream hung up...".to_owned()); chan_select! { ui_rx_b.recv() -> input => match input { Some(line) => { @@ -271,13 +259,16 @@ fn do_ui(ui_rx_orig: chan::Receiver<Vec<u8>>, twete_rx: chan::Receiver<Vec<u8>>, Some(line) => { tweeter.handle_user_input(line, &mut queryer); }, - None => println!("UI thread hung up...") + None => tweeter.display_info.status("UI thread hung up...".to_owned()) } // and then we can introduce a channel that just sends a message every 100 ms or so // that acts as a clock! } // one day display_info should be distinct - display::paint(tweeter); + match display::paint(tweeter) { + Ok(_) => (), + Err(e) => println!("{}", e) // TODO: we got here because writing to stdout failed. what to do now? + }; } } @@ -326,9 +317,6 @@ fn connect_twitter_stream() -> chan::Receiver<Vec<u8>> { .connector(connector) .build(&core.handle()); - // println!("{}", do_web_req("https://caps.twitter.com/v2/capi/passthrough/1?twitter:string:card_uri=card://887655800482787328&twitter:long:original_tweet_id=887655800981925888&twitter:string:response_card_name=poll3choice_text_only&twitter:string:cards_platform=Web-12", &client, &mut core).unwrap()); - // println!("{}", look_up_tweet("887655800981925888", &client, &mut core).unwrap()); - let req = signed_api_get(STREAMURL); let work = client.request(req).and_then(|res| { let status = res.status(); diff --git a/src/tw/events.rs b/src/tw/events.rs index 35167a3..0da27a1 100644 --- a/src/tw/events.rs +++ b/src/tw/events.rs @@ -22,11 +22,13 @@ impl Event { fn get_source_target_ids(structure: serde_json::Map<String, serde_json::Value>) -> Result<(String, String), String> { match ( structure.get("source").and_then(|x| x.get("id_str").and_then(|x| x.as_str())), - structure.get("target_obj").and_then(|x| x.get("id_str").and_then(|x| x.as_str())) + structure.get("target_object").and_then(|x| x.get("id_str").and_then(|x| x.as_str())) ) { (Some(source_id), Some(target_id)) => Ok((source_id.to_string(), target_id.to_string())), - (None, Some(target_id)) => Err("No id_str string at .source.id_str".to_string()), - (Some(target_id), None) => Err("No id_str string at .target_object.id_str".to_string()), + // have more particular error types for "missing fields", "missing data", "invalid + // state", etc, so downstream we can opt to investigate the bad data or not.. + (None, Some(_)) => Err("No id_str string at .source.id_str: {}".to_string()), + (Some(_), None) => Err("No id_str string at .target_object.id_str: {}".to_string()), (None, None) => Err("No id_str at source or target_object".to_string()) } } @@ -96,7 +98,7 @@ impl Event { // what about removed? // "blocked" => Blocked { }, // "unblocked" => Unblocked { }, - e => { println!("unrecognized event: {}", e); Err(e.to_string()) } + e => Err(e.to_string()) } }, None => { diff --git a/src/tw/mod.rs b/src/tw/mod.rs index d0bad59..82dfe10 100644 --- a/src/tw/mod.rs +++ b/src/tw/mod.rs @@ -15,7 +15,6 @@ use std::fs::OpenOptions; pub mod events; -use display::Render; use display; pub mod tweet; @@ -104,7 +103,7 @@ pub struct TwitterCache { #[serde(skip)] id_conversions: IdConversions, #[serde(skip)] - pub display_info: DisplayInfo + pub display_info: display::DisplayInfo } // Internally, a monotonically increasin i64 is always the id used. @@ -217,30 +216,6 @@ impl IdConversions { } } -pub struct DisplayInfo { - pub log: Vec<String>, - pub infos: Vec<display::Infos> -} - -impl Default for DisplayInfo { - fn default() -> Self { - DisplayInfo { - log: Vec::new(), - infos: Vec::new() - } - } -} - -impl DisplayInfo { - pub fn status(&mut self, stat: String) { - self.log.push(stat); - } - - pub fn recv(&mut self, info: display::Infos) { - self.infos.push(info); - } -} - use commands::Command; use Queryer; @@ -284,7 +259,7 @@ impl TwitterCache { current_user: User::default(), threads: HashMap::new(), id_conversions: IdConversions::default(), - display_info: DisplayInfo::default() + display_info: display::DisplayInfo::default() } } @@ -298,7 +273,6 @@ impl TwitterCache { } else { self.display_info.status(format!("I don't know what {} means", command).to_string()); } -// println!(""); // temporaryish because there's no visual distinction between output atm } fn new_without_caching() -> TwitterCache { @@ -332,7 +306,7 @@ impl TwitterCache { self.number_and_insert_tweet(tweet); } } - pub fn store_cache(&self) { + pub fn store_cache(&mut self) { if Path::new(TwitterCache::PROFILE_DIR).is_dir() { let profile = OpenOptions::new() .write(true) @@ -342,7 +316,7 @@ impl TwitterCache { .unwrap(); serde_json::to_writer(profile, self).unwrap(); } else { - println!("No cache dir exists..."); + self.display_info.status("No cache dir exists...".to_owned()); } // store cache } @@ -398,23 +372,29 @@ impl TwitterCache { } } pub fn cache_api_tweet(&mut self, json: serde_json::Value) { - if let Some((rt, rt_user)) = json.get("retweeted_status").and_then(|x| Tweet::from_api_json(x.to_owned())) { + // TODO: log error somehow + if let Some(Ok((rt, rt_user))) = json.get("retweeted_status").map(|x| Tweet::from_api_json(x.to_owned())) { self.cache_user(rt_user); self.cache_tweet(rt); } - if let Some((qt, qt_user)) = json.get("quoted_status").and_then(|x| Tweet::from_api_json(x.to_owned())) { + // TODO: log error somehow + if let Some(Ok((qt, qt_user))) = json.get("quoted_status").map(|x| Tweet::from_api_json(x.to_owned())) { self.cache_user(qt_user); self.cache_tweet(qt); } - if let Some((twete, user)) = Tweet::from_api_json(json) { + // TODO: log error somehow + if let Ok((twete, user)) = Tweet::from_api_json(json) { self.cache_user(user); self.cache_tweet(twete); } } pub fn cache_api_user(&mut self, json: serde_json::Value) { - if let Some(user) = User::from_json(json) { + // TODO: log error somehow + // TODO: probably means display_info needs a more technical-filled log for debugging, + // independent of the user-facing statuses, like "invalid id" + if let Ok(user) = User::from_json(json) { self.cache_user(user); } } @@ -517,8 +497,8 @@ impl TwitterCache { &TweetId::Twitter(ref id) => { if !self.tweets.contains_key(id) { match self.look_up_tweet(id, &mut queryer) { - Some(json) => self.cache_api_tweet(json), - None => self.display_info.status(format!("Unable to retrieve tweet {}", id)) + Ok(json) => self.cache_api_tweet(json), + Err(e) => self.display_info.status(format!("Unable to retrieve tweet {}:\n{}", id, e)) }; } self.retrieve_tweet(tweet_id) @@ -529,8 +509,8 @@ impl TwitterCache { if !self.users.contains_key(user_id) { let maybe_parsed = self.look_up_user(user_id, &mut queryer).and_then(|x| User::from_json(x)); match maybe_parsed { - Some(tw) => self.cache_user(tw), - None => self.display_info.status(format!("Unable to retrieve user {}", user_id)) + Ok(tw) => self.cache_user(tw), + Err(e) => self.display_info.status(format!("Unable to retrieve user {}:\n{}", user_id, e)) } } self.users.get(user_id) @@ -540,13 +520,13 @@ impl TwitterCache { let new_uids = &uid_set - &self.following; for user in &new_uids { - println!("New following! {}", user); + self.display_info.status(format!("New following! {}", user)); self.add_following(user); } let lost_uids = &self.following - &uid_set; for user in &lost_uids { - println!("Bye, friend! {}", user); + self.display_info.status(format!("Bye, friend! {}", user)); self.remove_following(user); } } @@ -555,13 +535,13 @@ impl TwitterCache { let new_uids = &uid_set - &self.followers; for user in &new_uids { - println!("New follower! {}", user); + self.display_info.status(format!("New follower! {}", user)); self.add_follower(user); } let lost_uids = &self.followers - &uid_set; for user in &lost_uids { - println!("Bye, friend! {}", user); + self.display_info.status(format!("Bye, friend! {}", user)); self.remove_follower(user); } } @@ -588,21 +568,21 @@ impl TwitterCache { self.follower_history.insert(user_id.to_owned(), ("unfollow".to_string(), Utc::now().timestamp())); } - fn look_up_user(&mut self, id: &str, queryer: &mut ::Queryer) -> Option<serde_json::Value> { + fn look_up_user(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> { let url = &format!("{}?user_id={}", ::USER_LOOKUP_URL, id); queryer.do_api_get(url) } - fn look_up_tweet(&mut self, id: &str, queryer: &mut ::Queryer) -> Option<serde_json::Value> { + fn look_up_tweet(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> { let url = &format!("{}&id={}", ::TWEET_LOOKUP_URL, id); queryer.do_api_get(url) } - pub fn get_settings(&self, queryer: &mut ::Queryer) -> Option<serde_json::Value> { + pub fn get_settings(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> { queryer.do_api_get(::ACCOUNT_SETTINGS_URL) } - pub fn get_followers(&self, queryer: &mut ::Queryer) -> Option<serde_json::Value> { + pub fn get_followers(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> { queryer.do_api_get(::GET_FOLLOWER_IDS_URL) } @@ -684,7 +664,6 @@ fn handle_twitter_welcome( structure: serde_json::Map<String, serde_json::Value>, tweeter: &mut TwitterCache, queryer: &mut ::Queryer) { -// println!("welcome: {:?}", structure); let user_id_nums = structure["friends"].as_array().unwrap(); let user_id_strs = user_id_nums.into_iter().map(|x| x.as_u64().unwrap().to_string()); tweeter.set_following(user_id_strs.collect()); @@ -722,6 +701,8 @@ pub fn handle_message( handle_twitter_twete(objmap, tweeter, queryer); } else if objmap.contains_key("direct_message") { handle_twitter_dm(objmap, tweeter, queryer); + } else { + tweeter.display_info.status(format!("Unknown json: {:?}", objmap)); } // self.display_info.status(""); }, diff --git a/src/tw/tweet.rs b/src/tw/tweet.rs index a3fdde3..dc89774 100644 --- a/src/tw/tweet.rs +++ b/src/tw/tweet.rs @@ -38,14 +38,17 @@ impl Tweet { .collect() } - pub fn from_api_json(json: serde_json::Value) -> Option<(Tweet, User)> { + pub fn from_api_json(json: serde_json::Value) -> Result<(Tweet, User), String> { Tweet::from_json(json.clone()).and_then(|tw| { - json.get("user").and_then(|user_json| - User::from_json(user_json.to_owned()).map(|u| (tw, u)) - ) + match json.get("user") { + Some(user_json) => + User::from_json(user_json.to_owned()).map(|u| (tw, u)), + None => + Err("No user json".to_owned()) + } }) } - pub fn from_json(json: serde_json::Value) -> Option<Tweet> { + pub fn from_json(json: serde_json::Value) -> Result<Tweet, String> { if let serde_json::Value::Object(json_map) = json { let text = ::tw::full_twete_text(&json_map); let rt_twete = json_map.get("retweeted_status") @@ -67,7 +70,7 @@ impl Tweet { json_map["user"]["id_str"].as_str(), json_map["created_at"].as_str() ) { - return Some(Tweet { + return Ok(Tweet { id: id_str.to_owned(), author_id: author_id.to_owned(), text: text, @@ -82,6 +85,6 @@ impl Tweet { } } } - None + Err("Invalid tweet json".to_owned()) } } diff --git a/src/tw/user.rs b/src/tw/user.rs index 8f41b6d..0af4eb8 100644 --- a/src/tw/user.rs +++ b/src/tw/user.rs @@ -18,29 +18,28 @@ impl Default for User { } impl User { - pub fn from_json(json: serde_json::Value) -> Option<User> { + pub fn from_json(json: serde_json::Value) -> Result<User, String> { if let serde_json::Value::Object(json_map) = json { - if json_map.contains_key("id_str") && - json_map.contains_key("name") && - json_map.contains_key("screen_name") { - if let ( - Some(id_str), - Some(name), - Some(screen_name) - ) = ( - json_map["id_str"].as_str(), - json_map["name"].as_str(), - json_map["screen_name"].as_str() - ) { - return Some(User { - id: id_str.to_owned(), - name: name.to_owned(), - handle: screen_name.to_owned() - }) - } + if let ( + Some(id_str), + Some(name), + Some(screen_name) + ) = ( + json_map.get("id_str").and_then(|x| x.as_str()), + json_map.get("name").and_then(|x| x.as_str()), + json_map.get("screen_name").and_then(|x| x.as_str()) + ) { + Ok(User { + id: id_str.to_owned(), + name: name.to_owned(), + handle: screen_name.to_owned() + }) + } else { + Err("user json missing one of id_str, name, screen_name".to_owned()) } + } else { + Err(format!("Invalid json: {:?}", json)) } - None } } |