aboutsummaryrefslogtreecommitdiff
path: root/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'main.rs')
-rw-r--r--main.rs914
1 files changed, 10 insertions, 904 deletions
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<Self>, _tweeter: &::tw::TwitterCache) { }
- }
- impl Event for RT_RT {
- fn render(self: Box<Self>, 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<Self>, 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<Self>, 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<Self>, 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<Self>, 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<Self>, 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<Self>, tweeter: &::tw::TwitterCache);
- }
-
- impl Event {
- pub fn from_json(structure: serde_json::Map<String, serde_json::Value>) -> Option<Box<Event>> {
- 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<User> {
- 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<String>,
- #[serde(skip_serializing_if="Option::is_none")]
- #[serde(default = "Option::default")]
- pub rt_tweet: Option<String>,
- #[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<Tweet> {
- 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, serde_json::Value>) -> 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("&amp;", "&")
- .replace("&gt;", ">")
- .replace("&lt;", "<");
-
- 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<String, User>,
- #[serde(skip)]
- pub tweets: HashMap<String, Tweet>,
- 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??
- #[serde(skip)]
- id_to_tweet_id: HashMap<u64, String>,
- #[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<String, serde_json::Value>, 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<String>) {
- let uid_set = user_ids.into_iter().collect::<HashSet<String>>();
-
- 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<String>) {
- let uid_set = user_ids.into_iter().collect::<HashSet<String>>();
-
- 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<serde_json::Value> {
- 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> {
- 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> {
- queryer.do_api_get(::ACCOUNT_SETTINGS_URL)
- }
- }
-}
pub struct Queryer {
client: hyper::client::Client<HttpsConnector<hyper::client::HttpConnector>>,
@@ -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 <id> your sik reply");
- }
- } else {
- println!("thread <id> 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<String> = 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<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));
- }
- }
- } else {
- println!("rep <id> your sik reply");
- }
- } else {
- println!("rep <id> 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 <id> your sik reply");
- }
- } else {
- println!("rep <id> 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<u8>, 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,
- &QUOTE,
- &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);