diff options
-rw-r--r-- | src/commands/auth.rs | 7 | ||||
-rw-r--r-- | src/commands/fav.rs | 8 | ||||
-rw-r--r-- | src/commands/follow.rs | 12 | ||||
-rw-r--r-- | src/commands/help.rs | 2 | ||||
-rw-r--r-- | src/commands/quit.rs | 2 | ||||
-rw-r--r-- | src/commands/show_cache.rs | 2 | ||||
-rw-r--r-- | src/commands/twete.rs | 50 | ||||
-rw-r--r-- | src/display/mod.rs | 9 | ||||
-rw-r--r-- | src/main.rs | 110 | ||||
-rw-r--r-- | src/tw/mod.rs | 168 |
10 files changed, 223 insertions, 147 deletions
diff --git a/src/commands/auth.rs b/src/commands/auth.rs index 7d01451..65dcaf5 100644 --- a/src/commands/auth.rs +++ b/src/commands/auth.rs @@ -4,8 +4,6 @@ use std::collections::HashMap; use hyper; use ::Queryer; -use tw::TweetId; - use commands::Command; static FAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/create.json"; @@ -79,10 +77,11 @@ fn pin(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { as_map.insert(part[0], part[1]); } // turns out the "actual" oauth creds are different - tweeter.add_profile(tw::Credential { + // TODO: profile names? + tweeter.add_profile(tw::TwitterProfile::new(tw::Credential { key: as_map["oauth_token"].to_owned(), secret: as_map["oauth_token_secret"].to_owned() - }); + }, tw::user::User::default()), Some("iximeow".to_owned())); tweeter.WIP_auth = None; tweeter.state = tw::AppState::Reconnect; tweeter.display_info.status("Looks like you authed! Connecting...".to_owned()); diff --git a/src/commands/fav.rs b/src/commands/fav.rs index 7a4852e..5c12535 100644 --- a/src/commands/fav.rs +++ b/src/commands/fav.rs @@ -21,8 +21,8 @@ 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 - let result = match tweeter.profile.clone() { - Some(user_creds) => queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id), &tweeter.app_key, &user_creds), + let result = match tweeter.current_profile() { + Some(user_profile) => queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id), &tweeter.app_key, &user_profile.creds), None => Err("No logged in user to unfav from".to_owned()) }; match result { @@ -53,8 +53,8 @@ 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 - let result = match tweeter.profile.clone() { - Some(user_creds) => queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id), &tweeter.app_key, &user_creds), + let result = match tweeter.current_profile() { + Some(user_profile) => queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id), &tweeter.app_key, &user_profile.creds), None => Err("No logged in user to fav from".to_owned()) }; match result { diff --git a/src/commands/follow.rs b/src/commands/follow.rs index 3a29252..6e29788 100644 --- a/src/commands/follow.rs +++ b/src/commands/follow.rs @@ -16,9 +16,9 @@ pub static UNFOLLOW: Command = Command { fn unfl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let screen_name = line.trim(); - let result = match tweeter.profile.clone() { - Some(user_creds) => { - queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name), &tweeter.app_key, &user_creds) + let result = match tweeter.current_profile() { + Some(user_profile) => { + queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name), &tweeter.app_key, &user_profile.creds) }, None => Err("No logged in user to unfollow from".to_owned()) }; @@ -38,15 +38,15 @@ pub static FOLLOW: Command = Command { fn fl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let screen_name = line.trim(); - match tweeter.profile.clone() { - Some(user_creds) => { + match tweeter.current_profile().map(|profile| profile.to_owned()) { + Some(user_profile) => { tweeter.display_info.status( format!( "fl resp: {:?}", queryer.do_api_post( &format!("{}?screen_name={}", UNFOLLOW_URL, screen_name), &tweeter.app_key, - &user_creds + &user_profile.creds ) ) ) diff --git a/src/commands/help.rs b/src/commands/help.rs index c99681e..445684b 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -1,8 +1,6 @@ use tw; use ::Queryer; -use tw::TweetId; - use commands::Command; pub static HELP: Command = Command { diff --git a/src/commands/quit.rs b/src/commands/quit.rs index 6638299..0f5c582 100644 --- a/src/commands/quit.rs +++ b/src/commands/quit.rs @@ -3,8 +3,6 @@ use ::Queryer; use commands::Command; -use std::process::exit; - pub static QUIT: Command = Command { keyword: "q", params: 0, diff --git a/src/commands/show_cache.rs b/src/commands/show_cache.rs index fffcdb6..6dda8dc 100644 --- a/src/commands/show_cache.rs +++ b/src/commands/show_cache.rs @@ -12,6 +12,7 @@ pub static SHOW_CACHE: Command = Command { }; 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 { tweeter.display_info.status(format!("User: {} -> {:?}", uid, user)); @@ -30,4 +31,5 @@ fn show_cache(_line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Q None => { tweeter.display_info.status(" ...".to_owned()); } } } + */ } diff --git a/src/commands/twete.rs b/src/commands/twete.rs index c5c0c1a..0d82b76 100644 --- a/src/commands/twete.rs +++ b/src/commands/twete.rs @@ -22,8 +22,8 @@ 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()) { - let result = match tweeter.profile.clone() { - Some(user_creds) => queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id), &tweeter.app_key, &user_creds), + let result = match tweeter.current_profile() { + Some(user_profile) => queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id), &tweeter.app_key, &user_profile.creds), None => Err("No logged in user to delete as".to_owned()) }; match result { @@ -61,8 +61,8 @@ fn twete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { pub fn send_twete(text: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let substituted = ::url_encode(&text); - let result = match tweeter.profile.clone() { - Some(user_creds) => queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted), &tweeter.app_key, &user_creds), + let result = match tweeter.current_profile() { + Some(user_profile) => queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted), &tweeter.app_key, &user_profile.creds), None => Err("No logged in user to tweet as".to_owned()) }; match result { @@ -83,6 +83,13 @@ pub static THREAD: Command = Command { // the difference between threading and replying is not including // yourself in th @'s. fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { + let user_profile = match tweeter.current_profile().map(|profile| profile.to_owned()) { + Some(profile) => profile, + None => { + tweeter.display_info.status("To reply you must be authenticated as a user.".to_owned()); + return; + } + }; let mut text: String = line.trim().to_string(); if let Some(id_end_idx) = text.find(" ") { let reply_bare = text.split_off(id_end_idx + 1); @@ -95,8 +102,8 @@ fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { 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.to_owned(); // TODO: definitely breaks if you change your handle right now - if handle == &tweeter.current_user.handle { - send_reply(reply.to_owned(), twid, tweeter, queryer); + if handle == &user_profile.user.handle { + send_reply(reply.to_owned(), twid, tweeter, queryer, user_profile.creds); } else { tweeter.display_info.status("you can only thread your own tweets".to_owned()); // ask if it should .@ instead? @@ -125,6 +132,13 @@ pub static REP: Command = Command { }; fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { + let user_profile = match tweeter.current_profile().map(|profile| profile.to_owned()) { + Some(profile) => profile, + None => { + tweeter.display_info.status("To reply you must be authenticated as a user.".to_owned()); + return; + } + }; let mut text: String = line.trim().to_string(); let reply_bare = match text.find(" ") { None => "".to_owned(), @@ -152,8 +166,8 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { // if you're directly replying to yourself, i trust you know what you're doing and // want to @ yourself again (this keeps self-replies from showing up on your // profile as threaded tweets, f.ex) - if !(ats.len() > 0 && &ats[0] == &tweeter.current_user.handle) { - ats.remove_item(&tweeter.current_user.handle); + if !(ats.len() > 0 && &ats[0] == &user_profile.user.handle) { + ats.remove_item(&user_profile.user.handle); } //let ats_vec: Vec<&str> = ats.into_iter().collect(); //let full_reply = format!("{} {}", ats_vec.join(" "), reply); @@ -161,7 +175,7 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { let full_reply = format!("{} {}", decorated_ats.join(" "), reply); if reply.len() > 0 { - send_reply(full_reply, twid, tweeter, queryer); + send_reply(full_reply, twid, tweeter, queryer, user_profile.creds); } else { tweeter.display_info.mode = Some(::display::DisplayMode::Reply(twid, full_reply)); } @@ -175,11 +189,11 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { } } -pub fn send_reply(text: String, twid: TweetId, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { +pub fn send_reply(text: String, twid: TweetId, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, user_creds: tw::Credential) { if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self let substituted = ::url_encode(&text); - let result = match tweeter.profile.clone() { - Some(user_creds) => { + let result = match tweeter.current_profile() { + Some(user_profile) => { queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id), &tweeter.app_key, &user_creds) }, None => Err("No logged in user to tweet as".to_owned()) @@ -220,8 +234,8 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { twete.id ) ); - let result = match tweeter.profile.clone() { - Some(user_creds) => { + let result = match tweeter.current_profile() { + Some(user_profile) => { queryer.do_api_post( &format!("{}?status={}&attachment_url={}", CREATE_TWEET_URL, @@ -229,7 +243,7 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) { attachment_url ), &tweeter.app_key, - &user_creds + &user_profile.creds ) }, None => Err("No logged in user to tweet as".to_owned()) @@ -267,9 +281,9 @@ 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()) { - let result = match tweeter.profile.clone() { - Some(user_creds) => { - queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id), &tweeter.app_key, &user_creds) + let result = match tweeter.current_profile() { + Some(user_profile) => { + queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id), &tweeter.app_key, &user_profile.creds) }, None => Err("No logged in user to retweet as".to_owned()) }; diff --git a/src/display/mod.rs b/src/display/mod.rs index 954ed38..88722cc 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -119,10 +119,11 @@ pub fn paint(tweeter: &mut ::tw::TwitterCache) -> Result<(), std::io::Error> { */ let (cursor_x, cursor_y) = match tweeter.display_info.mode.clone() { None => { + let handle = tweeter.current_profile().map(|profile| profile.user.handle.to_owned()).unwrap_or("_default_".to_owned()); print!("{}{}", cursor::Goto(1, height - 6), clear::CurrentLine); - print!("{}{}@{}>{}", cursor::Goto(1, height - 5), clear::CurrentLine, tweeter.current_user.handle, tweeter.display_info.input_buf.clone().into_iter().collect::<String>()); + print!("{}{}@{}>{}", cursor::Goto(1, height - 5), clear::CurrentLine, handle, tweeter.display_info.input_buf.clone().into_iter().collect::<String>()); print!("{}{}", cursor::Goto(1, height - 4), clear::CurrentLine); - ((1 + tweeter.current_user.handle.len() + 2 + tweeter.display_info.input_buf.len()) as u16, height as u16 - 5) + ((1 + handle.len() + 2 + tweeter.display_info.input_buf.len()) as u16, height as u16 - 5) } Some(DisplayMode::Compose(x)) => { let mut lines: Vec<String> = vec![]; @@ -143,7 +144,7 @@ pub fn paint(tweeter: &mut ::tw::TwitterCache) -> Result<(), std::io::Error> { ); lines_drawn += 1; } - h += (lines_drawn - 3); + h += lines_drawn - 3; (cursor_idx as u16 + 3, height as u16 - 5) // TODO: panic on underflow } Some(DisplayMode::Reply(twid, msg)) => { @@ -166,7 +167,7 @@ pub fn paint(tweeter: &mut ::tw::TwitterCache) -> Result<(), std::io::Error> { ); lines_drawn += 1; } - h += (lines_drawn - 3); + h += lines_drawn - 3; (cursor_idx as u16 + 3, height as u16 - 5) // TODO: panic on underflow } }; diff --git a/src/main.rs b/src/main.rs index bfb60f9..cd15ea9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -180,6 +180,16 @@ fn inner_signed_api_req(url: &str, method: Method, app_cred: &tw::Credential, ma req } +static mut connection_id: u8 = 0; + +fn get_id() -> u8 { + unsafe { + let curr_id = connection_id; + connection_id += 1; + curr_id + } +} + fn main() { //Track words @@ -200,11 +210,13 @@ fn main() { tweeter.display_info.status("Cache loaded".to_owned()); - let mut maybe_twete_rx = tweeter.profile.clone() - .map(|user_creds| { - let rx = connect_twitter_stream(tweeter.app_key.clone(), user_creds); - tweeter.display_info.status("Twitter stream open".to_owned()); - rx + let (tweet_tx, mut twete_rx) = chan::sync::<Vec<u8>>(0); + let (coordination_tx, mut coordination_rx) = chan::sync::<TwitterConnectionState>(0); + + tweeter.current_profile() + .map(|user_profile| user_profile.to_owned()) + .map(|user_profile| { + connect_twitter_stream(tweeter.app_key.clone(), user_profile.creds, tweet_tx.clone(), coordination_tx.clone(), get_id()); }); std::thread::spawn(move || { @@ -240,10 +252,11 @@ fn main() { }; loop { - match do_ui(ui_rx, maybe_twete_rx, &mut tweeter, &mut queryer) { - Some((new_ui_rx, new_maybe_twete_rx)) => { + match do_ui(ui_rx, twete_rx, coordination_rx, &mut tweeter, &mut queryer) { + Some((new_ui_rx, new_twete_rx, new_coordination_rx)) => { ui_rx = new_ui_rx; - maybe_twete_rx = new_maybe_twete_rx; + twete_rx = new_twete_rx; + coordination_rx = new_coordination_rx; }, None => { break; @@ -314,8 +327,15 @@ fn handle_input(event: termion::event::Event, tweeter: &mut tw::TwitterCache, qu } Some(display::DisplayMode::Reply(twid, msg)) => { if x == '\n' { - // TODO: move this somewhere better. - ::commands::twete::send_reply(msg, twid, tweeter, queryer); + match tweeter.current_profile().map(|profile| profile.to_owned()) { + Some(profile) => { + // TODO: move this somewhere better. + ::commands::twete::send_reply(msg, twid, tweeter, queryer, profile.creds); + }, + None => { + tweeter.display_info.status("Cannot reply when not logged in".to_owned()); + } + } tweeter.display_info.mode = None; } else { tweeter.display_info.mode = Some(display::DisplayMode::Reply(twid, format!("{}{}", msg, x))); @@ -356,35 +376,32 @@ fn handle_twitter_line(line: Vec<u8>, mut tweeter: &mut tw::TwitterCache, mut qu } } -fn do_ui(ui_rx_orig: chan::Receiver<Result<termion::event::Event, std::io::Error>>, maybe_twete_rx: Option<chan::Receiver<Vec<u8>>>, mut tweeter: &mut tw::TwitterCache, mut queryer: &mut ::Queryer) -> Option<(chan::Receiver<Result<termion::event::Event, std::io::Error>>, Option<chan::Receiver<Vec<u8>>>)> { +fn do_ui( + ui_rx_orig: chan::Receiver<Result<termion::event::Event, std::io::Error>>, + twete_rx: chan::Receiver<Vec<u8>>, + coordination_rx: chan::Receiver<TwitterConnectionState>, + mut tweeter: &mut tw::TwitterCache, + mut queryer: &mut ::Queryer +) -> Option<(chan::Receiver<Result<termion::event::Event, std::io::Error>>, chan::Receiver<Vec<u8>>, chan::Receiver<TwitterConnectionState>)> { loop { let ui_rx_a = &ui_rx_orig; let ui_rx_b = &ui_rx_orig; - match &maybe_twete_rx { - &Some(ref twete_rx) => { - chan_select! { - twete_rx.recv() -> twete => match twete { - Some(line) => handle_twitter_line(line, tweeter, queryer), - None => { - tweeter.display_info.status("Twitter stream hung up...".to_owned()); - return Some((ui_rx_orig.clone(), None)) - } - }, - ui_rx_a.recv() -> user_input => match user_input { - Some(Ok(event)) => handle_input(event, tweeter, queryer), - Some(Err(_)) => (), /* stdin closed? */ - None => return None // UI ded - } - } + chan_select! { + coordination_rx.recv() -> coordination => { + tweeter.display_info.status(format!("{:?}", coordination)); }, - &None => { - chan_select! { - ui_rx_a.recv() -> user_input => match user_input { - Some(Ok(event)) => handle_input(event, tweeter, queryer), - Some(Err(_)) => (), /* stdin closed? */ - None => return None // UI ded - } + twete_rx.recv() -> twete => match twete { + Some(line) => handle_twitter_line(line, tweeter, queryer), + None => { + tweeter.display_info.status("Twitter stream hung up...".to_owned()); + display::paint(tweeter).unwrap(); + return None // if the twitter channel died, something real bad happeneed? } + }, + ui_rx_a.recv() -> user_input => match user_input { + Some(Ok(event)) => handle_input(event, tweeter, queryer), + Some(Err(_)) => (), /* stdin closed? */ + None => return None // UI ded } } @@ -424,7 +441,8 @@ fn do_ui(ui_rx_orig: chan::Receiver<Result<termion::event::Event, std::io::Error } tw::AppState::Reconnect => { tweeter.state = tw::AppState::View; - return Some((ui_rx_orig.clone(), tweeter.profile.clone().map(|creds| connect_twitter_stream(tweeter.app_key.clone(), creds)))); + // TODO: reconnect *which*? + return None // Some((ui_rx_orig.clone(), tweeter.profile.clone().map(|creds| connect_twitter_stream(tweeter.app_key.clone(), creds)))); }, tw::AppState::Shutdown => { tweeter.display_info.status("Saving cache...".to_owned()); @@ -471,10 +489,22 @@ fn url_encode(s: &str) -> String { .replace("]", "%5d") } -fn connect_twitter_stream(app_cred: tw::Credential, user_cred: tw::Credential) -> chan::Receiver<Vec<u8>> { - let (twete_tx, twete_rx) = chan::sync::<Vec<u8>>(0); - +// let (twete_tx, twete_rx) = chan::sync::<Vec<u8>>(0); +#[derive(Debug)] +enum TwitterConnectionState { + Connecting(u8), + Connected(u8), + Closed(u8) +} +fn connect_twitter_stream( + app_cred: tw::Credential, + user_cred: tw::Credential, + twete_tx: chan::Sender<Vec<u8>>, + coordination_tx: chan::Sender<TwitterConnectionState>, + conn_id: u8 +) { std::thread::spawn(move || { + coordination_tx.send(TwitterConnectionState::Connecting(conn_id)); let mut core = Core::new().unwrap(); let connector = HttpsConnector::new(1, &core.handle()).unwrap(); @@ -491,6 +521,7 @@ fn connect_twitter_stream(app_cred: tw::Credential, user_cred: tw::Credential) - println!("Twitter stream connect was abnormal: {}", status); println!("result: {:?}", res); } + coordination_tx.send(TwitterConnectionState::Connected(conn_id)); LineStream::new(res.body() .map(|chunk| futures::stream::iter_ok(chunk.into_iter())) .flatten()) @@ -507,7 +538,6 @@ fn connect_twitter_stream(app_cred: tw::Credential, user_cred: tw::Credential) - Ok(_good) => (), Err(e) => println!("Error in setting up: {}", e) } + coordination_tx.send(TwitterConnectionState::Closed(conn_id)); }); - - twete_rx } diff --git a/src/tw/mod.rs b/src/tw/mod.rs index acee0c2..52ab31c 100644 --- a/src/tw/mod.rs +++ b/src/tw/mod.rs @@ -98,20 +98,14 @@ pub struct TwitterCache { pub app_key: Credential, // right now we're stuck assuming one profile. // alts and such will be others here. - pub profile: Option<Credential>, - following: HashSet<String>, - following_history: HashMap<String, (String, i64)>, // userid:date?? - pub followers: HashSet<String>, - lost_followers: HashSet<String>, - follower_history: HashMap<String, (String, i64)>, // userid:date?? + pub curr_profile: Option<String>, + pub profiles: HashMap<String, TwitterProfile>, threads: HashMap<String, u64>, // thread : latest_tweet_in_thread #[serde(skip)] pub needs_save: bool, #[serde(skip)] pub caching_permitted: bool, #[serde(skip)] - pub current_user: User, - #[serde(skip)] id_conversions: IdConversions, #[serde(skip)] pub display_info: display::DisplayInfo, @@ -271,6 +265,31 @@ fn parse_word_command<'a, 'b>(line: &'b str, commands: &[&'a Command]) -> Option return None } +#[derive(Serialize, Deserialize, Clone)] +pub struct TwitterProfile { + pub creds: Credential, + pub user: User, + following: HashSet<String>, + following_history: HashMap<String, (String, i64)>, // userid:date?? + pub followers: HashSet<String>, + lost_followers: HashSet<String>, + follower_history: HashMap<String, (String, i64)> // userid:date?? +} + +impl TwitterProfile { + pub fn new(creds: Credential, user: User) -> TwitterProfile { + TwitterProfile { + creds: creds, + user: user, + following: HashSet::new(), + following_history: HashMap::new(), + followers: HashSet::new(), + lost_followers: HashSet::new(), + follower_history: HashMap::new() + } + } +} + impl TwitterCache { const PROFILE_DIR: &'static str = "cache/"; const TWEET_CACHE: &'static str = "cache/tweets.json"; @@ -289,15 +308,10 @@ impl TwitterCache { // So, supporting multiple profiles will be ... interesting? // how do we support a variable number of channels? which will be necessary as we'll // have one channel up per twitter stream... - profile: None, // this will become a HashMap when multiple profiles are supported - following: HashSet::new(), - following_history: HashMap::new(), - followers: HashSet::new(), - lost_followers: HashSet::new(), - follower_history: HashMap::new(), + curr_profile: None, + profiles: HashMap::new(), needs_save: false, caching_permitted: true, - current_user: User::default(), threads: HashMap::new(), id_conversions: IdConversions::default(), display_info: display::DisplayInfo::default(), @@ -305,6 +319,17 @@ impl TwitterCache { } } + pub fn current_profile(&self) -> Option<&TwitterProfile> { + match &self.curr_profile { + &Some(ref profile_name) => self.profiles.get(profile_name), + &None => None + } + } + + pub fn current_handle(&self) -> Option<String> { + self.current_profile().map(|profile| profile.user.handle.to_owned()) + } + // TODO: pull out the "Cache" part of TwitterCache, it can be serialized/deserialized - the // rest of the history is just for the running instance.. pub fn handle_user_input(&mut self, line: Vec<u8>, mut queryer: &mut Queryer) { @@ -322,8 +347,8 @@ impl TwitterCache { cache.caching_permitted = false; cache } - pub fn add_profile(&mut self, creds: Credential) { - self.profile = Some(creds); + pub fn add_profile(&mut self, profile: TwitterProfile, name: Option<String>) { + self.profiles.insert(name.unwrap_or(profile.user.handle.to_owned()), profile); if self.caching_permitted { self.store_cache(); } @@ -578,84 +603,96 @@ impl TwitterCache { self.users.get(user_id) } pub fn set_following(&mut self, user_ids: Vec<String>) { - let uid_set = user_ids.into_iter().collect::<HashSet<String>>(); + self.current_profile().map(|profile| profile.to_owned()).map(|mut profile| { + let uid_set = user_ids.into_iter().collect::<HashSet<String>>(); - let new_uids = &uid_set - &self.following; - for user in &new_uids { - self.display_info.status(format!("New following! {}", user)); - self.add_following(user); - } + let new_uids = &uid_set - &profile.following; + for user in &new_uids { + self.display_info.status(format!("New following! {}", user)); + self.add_following(user); + } - let lost_uids = &self.following - &uid_set; - for user in &lost_uids { - self.display_info.status(format!("Bye, friend! {}", user)); - self.remove_following(user); - } + let lost_uids = &profile.following - &uid_set; + for user in &lost_uids { + self.display_info.status(format!("Bye, friend! {}", user)); + self.remove_following(user); + } + }); } pub fn set_followers(&mut self, user_ids: Vec<String>) { - let uid_set = user_ids.into_iter().collect::<HashSet<String>>(); + self.current_profile().map(|profile| profile.to_owned()).map(|mut profile| { + let uid_set = user_ids.into_iter().collect::<HashSet<String>>(); - let new_uids = &uid_set - &self.followers; - for user in &new_uids { - self.display_info.status(format!("New follower! {}", user)); - self.add_follower(user); - } + let new_uids = &uid_set - &profile.followers; + for user in &new_uids { + self.display_info.status(format!("New follower! {}", user)); + self.add_follower(user); + } - let lost_uids = &self.followers - &uid_set; - for user in &lost_uids { - self.display_info.status(format!("Bye, friend! {}", user)); - self.remove_follower(user); - } + let lost_uids = &profile.followers - &uid_set; + for user in &lost_uids { + self.display_info.status(format!("Bye, friend! {}", user)); + self.remove_follower(user); + } + }); } pub fn add_following(&mut self, user_id: &String) { - self.needs_save = true; - self.following.insert(user_id.to_owned()); - self.following_history.insert(user_id.to_owned(), ("following".to_string(), Utc::now().timestamp())); + self.current_profile().map(|profile| profile.to_owned()).map(|mut profile| { + self.needs_save = true; + profile.following.insert(user_id.to_owned()); + profile.following_history.insert(user_id.to_owned(), ("following".to_string(), Utc::now().timestamp())); + }); } pub fn remove_following(&mut self, user_id: &String) { - self.needs_save = true; - self.following.remove(user_id); - self.following_history.insert(user_id.to_owned(), ("unfollowing".to_string(), Utc::now().timestamp())); + self.current_profile().map(|profile| profile.to_owned()).map(|mut profile| { + self.needs_save = true; + profile.following.remove(user_id); + profile.following_history.insert(user_id.to_owned(), ("unfollowing".to_string(), Utc::now().timestamp())); + }); } pub fn add_follower(&mut self, user_id: &String) { - self.needs_save = true; - self.followers.insert(user_id.to_owned()); - self.lost_followers.remove(user_id); - self.follower_history.insert(user_id.to_owned(), ("follow".to_string(), Utc::now().timestamp())); + self.current_profile().map(|profile| profile.to_owned()).map(|mut profile| { + self.needs_save = true; + profile.followers.insert(user_id.to_owned()); + profile.lost_followers.remove(user_id); + profile.follower_history.insert(user_id.to_owned(), ("follow".to_string(), Utc::now().timestamp())); + }); } pub fn remove_follower(&mut self, user_id: &String) { - self.needs_save = true; - self.followers.remove(user_id); - self.lost_followers.insert(user_id.to_owned()); - self.follower_history.insert(user_id.to_owned(), ("unfollow".to_string(), Utc::now().timestamp())); + self.current_profile().map(|profile| profile.to_owned()).map(|mut profile| { + self.needs_save = true; + profile.followers.remove(user_id); + profile.lost_followers.insert(user_id.to_owned()); + profile.follower_history.insert(user_id.to_owned(), ("unfollow".to_string(), Utc::now().timestamp())); + }); } 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); - match self.profile { - Some(ref user_creds) => queryer.do_api_get(url, &self.app_key, &user_creds), + match self.current_profile() { + Some(ref user_profile) => queryer.do_api_get(url, &self.app_key, &user_profile.creds), None => Err("No authorized user to conduct lookup".to_owned()) } } fn look_up_tweet(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> { let url = &format!("{}&id={}", ::TWEET_LOOKUP_URL, id); - match self.profile { - Some(ref user_creds) => queryer.do_api_get(url, &self.app_key, &user_creds), + match self.current_profile() { + Some(ref user_profile) => queryer.do_api_get(url, &self.app_key, &user_profile.creds), None => Err("No authorized user to conduct lookup".to_owned()) } } pub fn get_settings(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> { - match self.profile { - Some(ref user_creds) => queryer.do_api_get(::ACCOUNT_SETTINGS_URL, &self.app_key, &user_creds), + match self.current_profile() { + Some(ref user_profile) => queryer.do_api_get(::ACCOUNT_SETTINGS_URL, &self.app_key, &user_profile.creds), None => Err("No authorized user to request settings".to_owned()) } } pub fn get_followers(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> { - match self.profile { - Some(ref user_creds) => queryer.do_api_get(::GET_FOLLOWER_IDS_URL, &self.app_key, &user_creds), + match self.current_profile() { + Some(ref user_profile) => queryer.do_api_get(::GET_FOLLOWER_IDS_URL, &self.app_key, &user_profile.creds), None => Err("No authorized user to request followers".to_owned()) } } @@ -746,12 +783,9 @@ fn handle_twitter_welcome( let settings = tweeter.get_settings(queryer).unwrap(); let maybe_my_name = settings["screen_name"].as_str(); if let Some(my_name) = maybe_my_name { - tweeter.current_user = User { - id: "".to_string(), - handle: my_name.to_owned(), - name: my_name.to_owned() - }; - tweeter.display_info.status(format!("You are {}", tweeter.current_user.handle)) + // TODO: come back to this when custom profile names are supported? + tweeter.curr_profile = Some(my_name.to_owned()); + tweeter.display_info.status(format!("You are {}", my_name)) } else { tweeter.display_info.status("Unable to make API call to figure out who you are...".to_string()); } |