From 132de644b77b42174ab470ea653352b05f4eca8d Mon Sep 17 00:00:00 2001 From: iximeow Date: Sun, 1 Oct 2017 20:55:15 -0700 Subject: extract commands and twitter model into modules --- main.rs | 914 +--------------------------------------------------------------- 1 file changed, 10 insertions(+), 904 deletions(-) (limited to 'main.rs') diff --git a/main.rs b/main.rs index 8be302c..5b5f493 100644 --- a/main.rs +++ b/main.rs @@ -2,7 +2,6 @@ extern crate serde_json; use std::str; -use std::str::FromStr; //use std::io::BufRead; #[macro_use] extern crate chan; @@ -24,6 +23,8 @@ use futures::Stream; use hyper_tls::HttpsConnector; //use json_streamer::JsonObjectStreamer; +mod tw; + //Change these values to your real Twitter API credentials static consumer_key: &str = "T879tHWDzd6LvKWdYVfbJL4Su"; static consumer_secret: &str = "OAXXYYIozAZ4vWSmDziI1EMJCKXPmWPFgLbJpB896iIAMIAdpb"; @@ -34,11 +35,6 @@ static lol_auth_token: &str = "641cdf3a4bbddb72c118b5821e8696aee6300a9a"; static STREAMURL: &str = "https://userstream.twitter.com/1.1/user.json?tweet_mode=extended"; static TWEET_LOOKUP_URL: &str = "https://api.twitter.com/1.1/statuses/show.json?tweet_mode=extended"; static USER_LOOKUP_URL: &str = "https://api.twitter.com/1.1/users/show.json"; -static FAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/create.json"; -static UNFAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/destroy.json"; -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"; static ACCOUNT_SETTINGS_URL: &str = "https://api.twitter.com/1.1/account/settings.json"; header! { (Authorization, "Authorization") => [String] } @@ -46,628 +42,6 @@ header! { (Accept, "Accept") => [String] } header! { (ContentType, "Content-Type") => [String] } header! { (Cookie, "cookie") => [String] } -mod tw { - use std::path::Path; - use std::fs::File; - use std::io::{BufRead, BufReader, Read}; - extern crate chrono; - - use self::chrono::prelude::*; - - use std::collections::{HashMap, HashSet}; - extern crate serde_json; - use std::io::Write; - - use std::fs::OpenOptions; - - #[derive(Debug, Serialize, Deserialize)] - pub struct User { - pub id: String, - pub name: String, - pub handle: String - } - - impl Default for User { - fn default() -> User { - User { - id: "".to_owned(), - name: "_default_".to_owned(), - handle: "_default_".to_owned() - } - } - } - - pub mod events { - extern crate termion; - use self::termion::color; - - extern crate serde_json; - - pub struct Deleted { - user_id: String, - twete_id: String - } - - pub struct RT_RT { - user_id: String, - twete_id: String - } - - pub struct Fav_RT { - user_id: String, - twete_id: String - } - - pub struct Fav { - user_id: String, - twete_id: String - } - - pub struct Unfav { - user_id: String, - twete_id: String - } - - pub struct Followed { - user_id: String - } - - pub struct Unfollowed { - user_id: String - } - - impl Event for Deleted { - fn render(self: Box, _tweeter: &::tw::TwitterCache) { } - } - impl Event for RT_RT { - fn render(self: Box, tweeter: &::tw::TwitterCache) { - println!("---------------------------------"); - { - let user = tweeter.retrieve_user(&self.user_id).unwrap(); - println!(" +rt_rt : {} (@{})", user.name, user.handle); - } - { - ::render_twete(&self.twete_id, tweeter); - } - println!(""); - } - } - impl Event for Fav_RT { - fn render(self: Box, tweeter: &::tw::TwitterCache) { - println!("---------------------------------"); - { - let user = tweeter.retrieve_user(&self.user_id).unwrap(); - println!(" +rt_fav : {} (@{})", user.name, user.handle); - } - { - ::render_twete(&self.twete_id, tweeter); - } - println!(""); - } - } - impl Event for Fav { - fn render(self: Box, tweeter: &::tw::TwitterCache) { - println!("---------------------------------"); - { - let user = tweeter.retrieve_user(&self.user_id).unwrap(); - println!("{} +fav : {} (@{}){}", color::Fg(color::Yellow), user.name, user.handle, color::Fg(color::Reset)); - } - { - ::render_twete(&self.twete_id, tweeter); - } - println!(""); - } - } - impl Event for Unfav { - fn render(self: Box, tweeter: &::tw::TwitterCache) { - println!("---------------------------------"); - { - let user = tweeter.retrieve_user(&self.user_id).unwrap(); - println!("{} -fav : {} (@{}){}", color::Fg(color::Yellow), user.name, user.handle, color::Fg(color::Reset)); - } - { - ::render_twete(&self.twete_id, tweeter); - } - println!(""); - } - } - impl Event for Followed { - fn render(self: Box, tweeter: &::tw::TwitterCache) { - let user = tweeter.retrieve_user(&self.user_id).unwrap(); - println!("---------------------------------"); - println!(" +fl : {} (@{})", user.name, user.handle); - println!(""); - } - } - impl Event for Unfollowed { - fn render(self: Box, tweeter: &::tw::TwitterCache) { - let user = tweeter.retrieve_user(&self.user_id).unwrap(); - println!("---------------------------------"); - println!(" -fl : {} (@{})", user.name, user.handle); - println!(""); - } - } - - /* - impl Event for Blocked { - - } - */ - - pub trait Event { - fn render(self: Box, tweeter: &::tw::TwitterCache); - } - - impl Event { - pub fn from_json(structure: serde_json::Map) -> Option> { - match &structure["event"].as_str().unwrap() { - &"follow" => Some(Box::new(Followed { - user_id: structure["source"]["id_str"].as_str().unwrap().to_owned() - })), - &"unfollow" => Some(Box::new(Unfollowed { - user_id: structure["source"]["id_str"].as_str().unwrap().to_owned() - })), - &"favorite" => Some(Box::new(Fav { - user_id: structure["source"]["id_str"].as_str().unwrap().to_owned(), - twete_id: structure["target_object"]["id_str"].as_str().unwrap().to_owned() - })), - &"unfavorite" => Some(Box::new(Unfav { - user_id: structure["source"]["id_str"].as_str().unwrap().to_owned(), - twete_id: structure["target_object"]["id_str"].as_str().unwrap().to_owned() - })), - &"favorited_retweet" => Some(Box::new(Fav_RT { - user_id: structure["source"]["id_str"].as_str().unwrap().to_owned(), - twete_id: structure["target_object"]["id_str"].as_str().unwrap().to_owned() - })), - &"retweeted_retweet" => Some(Box::new(RT_RT { - user_id: structure["source"]["id_str"].as_str().unwrap().to_owned(), - twete_id: structure["target_object"]["id_str"].as_str().unwrap().to_owned() - })), - // &"blocked" => Blocked { }, - // &"unblocked" => Unblocked { }, - // &"quoted_tweet" => ???, - e => { println!("unrecognized event: {}", e); None } - } - } - } - } - - impl User { - pub fn from_json(json: serde_json::Value) -> Option { - 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() - }) - } - } - } - None - } - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Tweet { - pub id: String, - pub author_id: String, - pub text: String, - pub created_at: String, // lol - #[serde(skip_serializing_if="Option::is_none")] - #[serde(default = "Option::default")] - pub quoted_tweet_id: Option, - #[serde(skip_serializing_if="Option::is_none")] - #[serde(default = "Option::default")] - pub rt_tweet: Option, - #[serde(skip)] - pub internal_id: u64 - } - - impl Tweet { - pub fn get_mentions(&self) -> Vec<&str> { - self.text.split(&[ - ',', '.', '/', ';', '\'', - '[', ']', '\\', '~', '!', - '@', '#', '$', '%', '^', - '&', '*', '(', ')', '-', - '=', '{', '}', '|', ':', - '"', '<', '>', '?', '`', - ' ' // forgot this initially. awkward. - ][..]) - .filter(|x| x.starts_with("@") && x.len() > 1) - .collect() - } - - pub fn from_api_json(json: serde_json::Value) -> Option<(Tweet, User)> { - 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)) - ) - }) - } - pub fn from_json(json: serde_json::Value) -> Option { - if let serde_json::Value::Object(json_map) = json { - let text = full_twete_text(&json_map); - let rt_twete = json_map.get("retweeted_status") - .and_then(|x| x.get("id_str")) - .and_then(|x| x.as_str()) - .map(|x| x.to_owned()); - if json_map.contains_key("id_str") && - json_map.contains_key("user") && - json_map.contains_key("created_at") { - if let ( - Some(id_str), - Some(author_id), - Some(created_at) - ) = ( - json_map["id_str"].as_str(), - json_map["user"]["id_str"].as_str(), - json_map["created_at"].as_str() - ) { - return Some(Tweet { - id: id_str.to_owned(), - author_id: author_id.to_owned(), - text: text, - created_at: created_at.to_owned(), - quoted_tweet_id: json_map.get("quoted_status_id_str") - .and_then(|x| x.as_str()) - .map(|x| x.to_owned()), - rt_tweet: rt_twete, - internal_id: 0 - }) - } - } - } - None - } - } - - pub fn full_twete_text(twete: &serde_json::map::Map) -> String { - if twete.contains_key("retweeted_status") { - return full_twete_text(twete["retweeted_status"].as_object().unwrap()) - } - let mut twete_text: String; - twete_text = if twete["truncated"].as_bool().unwrap() { - twete["extended_tweet"]["full_text"].as_str().unwrap().to_string() - } else { - twete["text"].as_str().unwrap().to_string() - }; - - let quoted_tweet_id = twete.get("quoted_tweet_id_str").and_then(|x| x.as_str()); - - twete_text = twete_text - .replace("&", "&") - .replace(">", ">") - .replace("<", "<"); - - for url in twete["entities"]["urls"].as_array().unwrap() { - let display_url = url["url"].as_str().unwrap(); - let expanded_url = url["expanded_url"].as_str().unwrap(); - if expanded_url.len() < 200 { - if let Some(twid) = quoted_tweet_id { - if expanded_url.ends_with(twid) { - twete_text = twete_text.replace(display_url, ""); - continue; - } - } - twete_text = twete_text.replace(display_url, expanded_url); - } - } - - twete_text - } - - #[derive(Serialize, Deserialize)] - pub struct TwitterCache { - #[serde(skip)] - pub users: HashMap, - #[serde(skip)] - pub tweets: HashMap, - following: HashSet, - following_history: HashMap, // userid:date?? - pub followers: HashSet, - lost_followers: HashSet, - follower_history: HashMap, // userid:date?? - #[serde(skip)] - id_to_tweet_id: HashMap, - #[serde(skip)] - pub needs_save: bool, - #[serde(skip)] - pub caching_permitted: bool, - #[serde(skip)] - pub current_user: User - } - - impl TwitterCache { - const PROFILE_DIR: &'static str = "cache/"; - const TWEET_CACHE: &'static str = "cache/tweets.json"; - const USERS_CACHE: &'static str = "cache/users.json"; - const PROFILE_CACHE: &'static str = "cache/profile.json"; // this should involve MY user id.. - - fn new() -> TwitterCache { - TwitterCache { - users: HashMap::new(), - tweets: HashMap::new(), - following: HashSet::new(), - following_history: HashMap::new(), - followers: HashSet::new(), - lost_followers: HashSet::new(), - follower_history: HashMap::new(), - id_to_tweet_id: HashMap::new(), - needs_save: false, - caching_permitted: true, - current_user: User::default() - } - } - fn new_without_caching() -> TwitterCache { - let mut cache = TwitterCache::new(); - cache.caching_permitted = false; - cache - } - fn cache_user(&mut self, user: User) { - if !self.users.contains_key(&user.id) { - let mut file = - OpenOptions::new() - .create(true) - .append(true) - .open(TwitterCache::USERS_CACHE) - .unwrap(); - writeln!(file, "{}", serde_json::to_string(&user).unwrap()).unwrap(); - self.users.insert(user.id.to_owned(), user); - } - } - - fn cache_tweet(&mut self, tweet: Tweet) { - if !self.tweets.contains_key(&tweet.id) { - - let mut file = - OpenOptions::new() - .create(true) - .append(true) - .open(TwitterCache::TWEET_CACHE) - .unwrap(); - writeln!(file, "{}", serde_json::to_string(&tweet).unwrap()).unwrap(); - self.number_and_insert_tweet(tweet); - } - } - pub fn store_cache(&self) { - if Path::new(TwitterCache::PROFILE_DIR).is_dir() { - let profile = OpenOptions::new() - .write(true) - .create(true) - .append(false) - .open(TwitterCache::PROFILE_CACHE) - .unwrap(); - serde_json::to_writer(profile, self).unwrap(); - } else { - println!("No cache dir exists..."); - } - // store cache - } - fn number_and_insert_tweet(&mut self, mut tw: Tweet) { - if !self.tweets.contains_key(&tw.id.to_owned()) { - if tw.internal_id == 0 { - tw.internal_id = (self.tweets.len() as u64) + 1; - self.id_to_tweet_id.insert(tw.internal_id, tw.id.to_owned()); - self.tweets.insert(tw.id.to_owned(), tw); - } - } - } - pub fn load_cache() -> TwitterCache { - if Path::new(TwitterCache::PROFILE_CACHE).is_file() { - let mut buf = vec![]; - let mut profile = File::open(TwitterCache::PROFILE_CACHE).unwrap(); - match profile.read_to_end(&mut buf) { - Ok(_sz) => { - match serde_json::from_slice(&buf) { - Ok(result) => { - let mut cache: TwitterCache = result; - cache.tweets = HashMap::new(); - for line in BufReader::new(File::open(TwitterCache::TWEET_CACHE).unwrap()).lines() { - let t: Tweet = serde_json::from_str(&line.unwrap()).unwrap(); - cache.number_and_insert_tweet(t); - } - for line in BufReader::new(File::open(TwitterCache::USERS_CACHE).unwrap()).lines() { - let u: User = serde_json::from_str(&line.unwrap()).unwrap(); - cache.users.insert(u.id.to_owned(), u); - } - cache.caching_permitted = true; - cache.needs_save = false; - cache - } - Err(e) => { - // TODO! should be able to un-frick profile after startup. - println!("Error reading profile, profile caching disabled... {}", e); - TwitterCache::new_without_caching() - } - } - } - Err(e) => { - println!("Error reading cached profile: {}. Profile caching disabled.", e); - TwitterCache::new_without_caching() - } - } - } else { - println!("Hello! First time setup?"); - TwitterCache::new() - } - } - 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())) { - 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())) { - self.cache_user(qt_user); - self.cache_tweet(qt); - } - - if let Some((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) { - self.cache_user(user); - } - } - pub fn cache_api_event(&mut self, json: serde_json::Map, mut queryer: &mut ::Queryer) { - /* don't really care to hold on to who fav, unfav, ... when, just pick targets out. */ - match json.get("event").and_then(|x| x.as_str()) { - Some("favorite") => { - self.cache_api_tweet(json["target_object"].clone()); - self.cache_api_user(json["source"].clone()); - self.cache_api_user(json["target"].clone()); - }, - Some("unfavorite") => { - self.cache_api_tweet(json["target_object"].clone()); - self.cache_api_user(json["source"].clone()); - self.cache_api_user(json["target"].clone()); - }, - Some("retweeted_retweet") => ()/* cache rt */, - Some("favorited_retweet") => ()/* cache rt */, - Some("delete") => { - let user_id = json["delete"]["status"]["user_id_str"].as_str().unwrap().to_string(); - self.fetch_user(&user_id, &mut queryer); - }, - Some("follow") => { - let follower = json["source"]["id_str"].as_str().unwrap().to_string(); - let followed = json["target"]["id_str"].as_str().unwrap().to_string(); - self.cache_api_user(json["target"].clone()); - self.cache_api_user(json["source"].clone()); - if follower == "iximeow" { - // self.add_follow( - } else { - self.add_follower(&follower); - } - }, - Some("unfollow") => { - let follower = json["source"]["id_str"].as_str().unwrap().to_string(); - let followed = json["target"]["id_str"].as_str().unwrap().to_string(); - self.cache_api_user(json["target"].clone()); - self.cache_api_user(json["source"].clone()); - if follower == "iximeow" { - // self.add_follow( - } else { - self.remove_follower(&follower); - } - }, - Some(_) => () /* an uninteresting event */, - None => () // not really an event? should we log something? - /* nothing else to care about now, i think? */ - } - } - pub fn tweet_by_innerid(&self, inner_id: u64) -> Option<&Tweet> { - let id = &self.id_to_tweet_id[&inner_id]; - self.retrieve_tweet(id) - } - pub fn retrieve_tweet(&self, tweet_id: &String) -> Option<&Tweet> { - self.tweets.get(tweet_id) - } - pub fn retrieve_user(&self, user_id: &String) -> Option<&User> { - self.users.get(user_id) - } - pub fn fetch_tweet(&mut self, tweet_id: &String, mut queryer: &mut ::Queryer) -> Option<&Tweet> { - if !self.tweets.contains_key(tweet_id) { - match self.look_up_tweet(tweet_id, &mut queryer) { - Some(json) => self.cache_api_tweet(json), - None => println!("Unable to retrieve tweet {}", tweet_id) - }; - } - self.tweets.get(tweet_id) - } - pub fn fetch_user(&mut self, user_id: &String, mut queryer: &mut ::Queryer) -> Option<&User> { - 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 => println!("Unable to retrieve user {}", user_id) - }; - } - self.users.get(user_id) - } - pub fn set_following(&mut self, user_ids: Vec) { - let uid_set = user_ids.into_iter().collect::>(); - - let new_uids = &uid_set - &self.following; - for user in &new_uids { - println!("New following! {}", user); - self.add_following(user); - } - - let lost_uids = &self.following - &uid_set; - for user in &lost_uids { - println!("Bye, friend! {}", user); - self.remove_following(user); - } - } - pub fn set_followers(&mut self, user_ids: Vec) { - let uid_set = user_ids.into_iter().collect::>(); - - let new_uids = &uid_set - &self.followers; - for user in &new_uids { - println!("New follower! {}", user); - self.add_follower(user); - } - - let lost_uids = &self.followers - &uid_set; - for user in &lost_uids { - println!("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())); - } - 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())); - } - 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())); - } - 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())); - } - - fn look_up_user(&mut self, id: &str, queryer: &mut ::Queryer) -> Option { - 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 { - let url = &format!("{}?id={}", ::TWEET_LOOKUP_URL, id); - queryer.do_api_get(url) - } - - pub fn get_settings(&self, queryer: &mut ::Queryer) -> Option { - queryer.do_api_get(::ACCOUNT_SETTINGS_URL) - } - } -} pub struct Queryer { client: hyper::client::Client>, @@ -1071,267 +445,14 @@ fn url_encode(s: &str) -> String { .replace("]", "%5d") } -struct Command { - keyword: &'static str, - params: u8, - exec: fn(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) -} - -static SHOW_CACHE: Command = Command { - keyword: "show_cache", - params: 0, - exec: |_line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer| { - println!("----* USERS *----"); - for (uid, user) in &tweeter.users { - println!("User: {} -> {:?}", uid, user); - } - println!("----* TWEETS *----"); - for (tid, tweet) in &tweeter.tweets { - println!("Tweet: {} -> {:?}", tid, tweet); - } - println!("----* FOLLOWERS *----"); - for uid in &tweeter.followers.clone() { - let user_res = tweeter.fetch_user(uid, &mut queryer); - match user_res { - Some(user) => { - println!("Follower: {} - {:?}", uid, user); - } - None => { println!(" ..."); } - } - } - } -}; - -static QUIT: Command = Command { - keyword: "q", - params: 0, - exec: |_line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer| { - println!("Bye bye!"); - tweeter.store_cache(); - std::process::exit(0); - } -}; - -static LOOK_UP_USER: Command = Command { - keyword: "look_up_user", - params: 1, - exec: |line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer| { - if let Some(user) = tweeter.fetch_user(&line, &mut queryer) { - println!("{:?}", user); - } else { -// println!("Couldn't retrieve {}", userid); - } - } -}; - -static LOOK_UP_TWEET: Command = Command { - keyword: "look_up_tweet", - params: 1, - exec: |line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer| { - if let Some(tweet) = tweeter.fetch_tweet(&line, &mut queryer) { - println!("{:?}", tweet); - } else { -// println!("Couldn't retrieve {}", tweetid); - } - } -}; - -static VIEW: Command = Command { - keyword: "view", - params: 1, - exec: |line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer| { - // TODO handle this unwrap - let inner_twid = u64::from_str(&line).unwrap(); - let twete = tweeter.tweet_by_innerid(inner_twid).unwrap(); - render_twete(&twete.id, tweeter); - println!("link: https://twitter.com/i/web/status/{}", twete.id); - } -}; - -static UNFAV: Command = Command { - keyword: "unfav", - params: 1, - exec: |line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer| { - // TODO handle this unwrap - let inner_twid = u64::from_str(&line).unwrap(); - let twete = tweeter.tweet_by_innerid(inner_twid).unwrap(); - queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id)); - } -}; - -static FAV: Command = Command { - keyword: "fav", - params: 1, - exec: |line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer| { - // TODO handle this unwrap - let inner_twid = u64::from_str(&line).unwrap(); - let twete = tweeter.tweet_by_innerid(inner_twid).unwrap(); - queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id)); - } -}; - -static DEL: Command = Command { - keyword: "del", - params: 1, - exec: |line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer| { - let inner_twid = u64::from_str(&line).unwrap(); - let twete = tweeter.tweet_by_innerid(inner_twid).unwrap(); - queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twete.id)); - } -}; - -static TWETE: Command = Command { - keyword: "t", - params: 1, - exec: |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)); - } else { - queryer.do_api_post(&format!("{}?status={}&weighted_character_count=true", CREATE_TWEET_URL, substituted)); - } -// println!("{}", &format!("{}?status={}", CREATE_TWEET_URL, substituted)); - } -}; - -static THREAD: Command = Command { - keyword: "thread", - params: 2, - exec: |line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer| { - 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); - let reply = reply_bare.trim(); - let id_str = text.trim(); - if reply.len() > 0 { - if let Some(inner_twid) = u64::from_str(&id_str).ok() { - if let Some(twete) = tweeter.tweet_by_innerid(inner_twid) { - let handle = &tweeter.retrieve_user(&twete.author_id).unwrap().handle; - // 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)); - } else { - println!("you can only thread your own tweets"); - // ask if it should .@ instead? - } - let substituted = url_encode(reply); - queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)); - } - } - } else { - println!("thread your sik reply"); - } - } else { - println!("thread your sik reply"); - } - } -}; - -static REP: Command = Command { - keyword: "rep", - params: 2, - exec: |line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer| { - 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); - let reply = reply_bare.trim(); - let id_str = text.trim(); - if reply.len() > 0 { - if let Some(inner_twid) = u64::from_str(&id_str).ok() { - if let Some(twete) = tweeter.tweet_by_innerid(inner_twid) { - // get handles to reply to... - let author_handle = tweeter.retrieve_user(&twete.author_id).unwrap().handle.to_owned(); - let mut ats: Vec = twete.get_mentions().into_iter().map(|x| x.to_owned()).collect(); //std::collections::HashSet::new(); - /* - for handle in twete.get_mentions() { - ats.insert(handle); - } - */ - ats.remove_item(&author_handle); - ats.insert(0, author_handle); - // no idea why i have to .to_owned() here --v-- what about twete.rt_tweet is a move? - if let Some(rt_tweet) = twete.rt_tweet.to_owned().and_then(|id| tweeter.retrieve_tweet(&id)) { - let rt_author_handle = tweeter.retrieve_user(&rt_tweet.author_id).unwrap().handle.to_owned(); - ats.remove_item(&rt_author_handle); - ats.insert(1, rt_author_handle); - } - if let Some(qt_tweet) = twete.quoted_tweet_id.to_owned().and_then(|id| tweeter.retrieve_tweet(&id)) { - let qt_author_handle = tweeter.retrieve_user(&qt_tweet.author_id).unwrap().handle.to_owned(); - ats.remove_item(&qt_author_handle); - ats.insert(1, qt_author_handle); - } - //let ats_vec: Vec<&str> = ats.into_iter().collect(); - //let full_reply = format!("{} {}", ats_vec.join(" "), reply); - let decorated_ats: Vec = 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)); - } - } - } else { - println!("rep your sik reply"); - } - } else { - println!("rep your sik reply"); - } - } -}; - -static QUOTE: Command = Command { - keyword: "qt", - params: 2, - exec: |line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer| { - 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); - let reply = reply_bare.trim(); - let id_str = text.trim(); - if reply.len() > 0 { - if let Some(inner_twid) = u64::from_str(&id_str).ok() { - if let Some(twete) = tweeter.tweet_by_innerid(inner_twid) { - let substituted = url_encode(reply); - let attachment_url = url_encode( - &format!( - "https://www.twitter.com/{}/status/{}", - tweeter.retrieve_user(&twete.author_id).unwrap().handle, - twete.id - ) - ); - println!("{}", substituted); - queryer.do_api_post( - &format!("{}?status={}&attachment_url={}", - CREATE_TWEET_URL, - substituted, - attachment_url - ) - ); - } - } - } else { - println!("rep your sik reply"); - } - } else { - println!("rep your sik reply"); - } - } -}; - -static RETWETE: Command = Command { - keyword: "rt", - params: 1, - exec: |line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer| { - let inner_twid = u64::from_str(&line).unwrap(); - let twete = tweeter.tweet_by_innerid(inner_twid).unwrap(); - queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twete.id)); - } -}; +mod commands; +use commands::Command; -fn parse_word_command<'a, 'b>(line: &'b str, commands: Vec<&'a Command>) -> Option<(&'b str, &'a Command)> { +// is there a nice way to make this accept commands: Iterable<&'a Command>? eg either a Vec or an +// array or whatever? +// (extra: WITHOUT having to build an iterator?) +// ((extra 2: when compiled with -O3, how does `commands` iteration look? same as array?)) +fn parse_word_command<'a, 'b>(line: &'b str, commands: &[&'a Command]) -> Option<(&'b str, &'a Command)> { for cmd in commands.into_iter() { if cmd.params == 0 { if line == cmd.keyword { @@ -1346,24 +467,9 @@ fn parse_word_command<'a, 'b>(line: &'b str, commands: Vec<&'a Command>) -> Opti } fn handle_user_input(line: Vec, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) { - let commands = vec![ - &SHOW_CACHE, - &QUIT, - &LOOK_UP_USER, - &LOOK_UP_TWEET, - &VIEW, - &UNFAV, - &FAV, - &DEL, - &TWETE, - "E, - &RETWETE, - &REP, - &THREAD - ]; let command_bare = String::from_utf8(line).unwrap(); let command = command_bare.trim(); - if let Some((line, cmd)) = parse_word_command(&command, commands) { + if let Some((line, cmd)) = parse_word_command(&command, commands::COMMANDS) { (cmd.exec)(line.to_owned(), tweeter, &mut queryer); } else { println!("I don't know what {} means", command); -- cgit v1.1