aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/fav.rs29
-rw-r--r--src/commands/look_up.rs1
-rw-r--r--src/commands/thread.rs24
-rw-r--r--src/commands/twete.rs16
-rw-r--r--src/commands/view.rs6
-rw-r--r--src/display/mod.rs16
-rw-r--r--src/tw/mod.rs159
7 files changed, 210 insertions, 41 deletions
diff --git a/src/commands/fav.rs b/src/commands/fav.rs
index 3e2b00d..7b9cce6 100644
--- a/src/commands/fav.rs
+++ b/src/commands/fav.rs
@@ -1,6 +1,8 @@
use tw;
use ::Queryer;
+use tw::TweetId;
+
use commands::Command;
use std::str::FromStr;
@@ -16,9 +18,17 @@ pub static UNFAV: Command = Command {
fn unfav(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));
+// let inner_twid = u64::from_str(&line).unwrap();
+ let maybe_id = TweetId::parse(line.to_owned());
+ match maybe_id {
+ Some(twid) => {
+ let twete = tweeter.retrieve_tweet(&twid).unwrap();
+ queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id));
+ }
+ None => {
+ println!("Invalid id: {}", line);
+ }
+ }
}
pub static FAV: Command = Command {
@@ -29,7 +39,14 @@ pub static FAV: Command = Command {
fn fav(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));
+ let maybe_id = TweetId::parse(line.to_owned());
+ match maybe_id {
+ Some(twid) => {
+ let twete = tweeter.retrieve_tweet(&twid).unwrap();
+ queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id));
+ }
+ None => {
+ println!("Invalid id: {}", line);
+ }
+ }
}
diff --git a/src/commands/look_up.rs b/src/commands/look_up.rs
index d04f984..386fade 100644
--- a/src/commands/look_up.rs
+++ b/src/commands/look_up.rs
@@ -23,6 +23,7 @@ pub static LOOK_UP_TWEET: Command = Command {
exec: look_up_tweet
};
+// TODO: make this parse a proper tweet id
fn look_up_tweet(line: String, tweeter: &mut tw::TwitterCache, mut queryer: &mut Queryer) {
if let Some(tweet) = tweeter.fetch_tweet(&line, &mut queryer) {
println!("{:?}", tweet);
diff --git a/src/commands/thread.rs b/src/commands/thread.rs
index 92566fc..57b410c 100644
--- a/src/commands/thread.rs
+++ b/src/commands/thread.rs
@@ -2,6 +2,8 @@ use tw;
use ::Queryer;
use ::display;
+use tw::TweetId;
+
use commands::Command;
use std::str::FromStr;
@@ -30,10 +32,15 @@ fn remember(line: String, tweeter: &mut tw::TwitterCache, _queryer: &mut Queryer
let name = name_bare.trim();
let id_str = text.trim();
if name.len() > 0 {
- if let Some(inner_twid) = u64::from_str(&id_str).ok() {
- if tweeter.tweet_by_innerid(inner_twid).is_some() {
- tweeter.set_thread(name.to_string(), inner_twid);
- println!("Ok! Recorded {} as thread {}", inner_twid, name);
+ let maybe_id = TweetId::parse(line.to_owned());
+ match maybe_id {
+ Some(twid) => {
+ let twete = tweeter.retrieve_tweet(&twid).unwrap().clone();
+ tweeter.set_thread(name.to_string(), twete.internal_id);
+ println!("Ok! Recorded {:?} as thread {}", twid, name);
+ }
+ None => {
+ println!("Invalid id: {}", line);
}
}
}
@@ -51,9 +58,12 @@ fn ls_threads(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Querye
for k in tweeter.threads() {
println!("Thread: {}", k);
let latest_inner_id = tweeter.latest_in_thread(k.to_owned()).unwrap();
- let twete = tweeter.tweet_by_innerid(*latest_inner_id).unwrap();
+ if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(*latest_inner_id)) {
// gross..
- display::render_twete(&twete.id, tweeter);
- println!("");
+ display::render_twete(&twete.id, tweeter);
+ println!("");
+ } else {
+ println!("ERROR no tweet for remembered thread.");
+ }
}
}
diff --git a/src/commands/twete.rs b/src/commands/twete.rs
index f1f19cf..a66d5eb 100644
--- a/src/commands/twete.rs
+++ b/src/commands/twete.rs
@@ -1,6 +1,8 @@
use tw;
use ::Queryer;
+use tw::TweetId;
+
use commands::Command;
use std::str::FromStr;
@@ -17,7 +19,7 @@ pub static DEL: Command = Command {
fn del(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();
+ let twete = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).unwrap();
queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twete.id));
}
@@ -54,7 +56,7 @@ fn thread(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
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) {
+ if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(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 {
@@ -90,7 +92,7 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
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) {
+ if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(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();
@@ -102,12 +104,12 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
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)) {
+ if let Some(rt_tweet) = twete.rt_tweet.to_owned().and_then(|id| tweeter.retrieve_tweet(&TweetId::Twitter(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)) {
+ if let Some(qt_tweet) = twete.quoted_tweet_id.to_owned().and_then(|id| tweeter.retrieve_tweet(&TweetId::Twitter(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);
@@ -143,7 +145,7 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
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) {
+ if let Some(twete) = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)) {
let substituted = ::url_encode(reply);
let attachment_url = ::url_encode(
&format!(
@@ -178,7 +180,7 @@ pub static RETWETE: Command = Command {
fn retwete(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();
+ let twete = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).unwrap();
queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twete.id));
}
diff --git a/src/commands/view.rs b/src/commands/view.rs
index b9cbf96..c14446a 100644
--- a/src/commands/view.rs
+++ b/src/commands/view.rs
@@ -1,6 +1,8 @@
use tw;
use ::Queryer;
+use tw::TweetId;
+
use commands::Command;
use std::str::FromStr;
@@ -16,7 +18,7 @@ pub static VIEW: Command = Command {
fn view(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();
+ let twete = tweeter.retrieve_tweet(&TweetId::Bare(inner_twid)).unwrap();
display::render_twete(&twete.id, tweeter);
println!(" link: https://twitter.com/i/web/status/{}", twete.id);
}
@@ -34,7 +36,7 @@ fn view_tr(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer)
}
fn view_tr_inner(id: u64, mut tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
- let twete: tw::tweet::Tweet = tweeter.tweet_by_innerid(id).unwrap().to_owned();
+ let twete: tw::tweet::Tweet = tweeter.retrieve_tweet(&TweetId::Bare(id)).unwrap().to_owned();
if let Some(reply_id) = twete.reply_to_tweet.clone() {
if let Some(reply_internal_id) = tweeter.fetch_tweet(&reply_id, queryer).map(|x| x.internal_id).map(|x| x.to_owned()) {
view_tr_inner(reply_internal_id, tweeter, queryer);
diff --git a/src/display/mod.rs b/src/display/mod.rs
index 15e23d7..e7f2454 100644
--- a/src/display/mod.rs
+++ b/src/display/mod.rs
@@ -4,6 +4,8 @@ use self::termion::color;
use ::tw;
+use ::tw::TweetId;
+
use std;
fn color_for(handle: &String) -> termion::color::Fg<&color::Color> {
@@ -42,7 +44,7 @@ impl Render for tw::events::Event {
}
tw::events::Event::Deleted { user_id, twete_id } => {
if let Some(handle) = tweeter.retrieve_user(&user_id).map(|x| &x.handle) {
- if let Some(_tweet) = tweeter.retrieve_tweet(&twete_id) {
+ if let Some(_tweet) = tweeter.retrieve_tweet(&TweetId::Twitter(twete_id.to_owned())) {
println!("-------------DELETED------------------");
render_twete(&twete_id, tweeter);
println!("-------------DELETED------------------");
@@ -102,7 +104,7 @@ impl Render for tw::events::Event {
pub fn render_twete(twete_id: &String, tweeter: &tw::TwitterCache) {
let id_color = color::Fg(color::Rgb(180, 80, 40));
- let maybe_twete = tweeter.retrieve_tweet(twete_id);
+ let maybe_twete = tweeter.retrieve_tweet(&TweetId::Twitter(twete_id.to_owned()));
if maybe_twete.is_none() {
println!("No such tweet: {}", twete_id);
return;
@@ -113,13 +115,13 @@ pub fn render_twete(twete_id: &String, tweeter: &tw::TwitterCache) {
match twete.rt_tweet {
Some(ref rt_id) => {
// same for a retweet
- let rt = tweeter.retrieve_tweet(rt_id).unwrap();
+ let rt = tweeter.retrieve_tweet(&TweetId::Twitter(rt_id.to_owned())).unwrap();
// and its author
let rt_author = tweeter.retrieve_user(&rt.author_id).unwrap();
println!("{} id:{} (rt_id:{}){}{}",
id_color, rt.internal_id, twete.internal_id,
rt.reply_to_tweet.clone()
- .map(|id| tweeter.retrieve_tweet(&id)
+ .map(|id| tweeter.retrieve_tweet(&TweetId::Twitter(id.to_owned()))
.and_then(|tw| Some(format!(" reply_to:{}", tw.internal_id)))
.unwrap_or(format!(" reply_to:twitter::{}", id))
)
@@ -137,7 +139,7 @@ pub fn render_twete(twete_id: &String, tweeter: &tw::TwitterCache) {
println!("{} id:{}{}{}",
id_color, twete.internal_id,
twete.reply_to_tweet.clone()
- .map(|id| tweeter.retrieve_tweet(&id)
+ .map(|id| tweeter.retrieve_tweet(&TweetId::Twitter(id.to_owned()))
.and_then(|tw| Some(format!(" reply_to:{}", tw.internal_id)))
.unwrap_or(format!(" reply_to:twitter::{}", id))
)
@@ -154,12 +156,12 @@ pub fn render_twete(twete_id: &String, tweeter: &tw::TwitterCache) {
println!(" {}", twete.text.replace("\r", "\\r").split("\n").collect::<Vec<&str>>().join("\n "));
if let Some(ref qt_id) = twete.quoted_tweet_id {
- if let Some(ref qt) = tweeter.retrieve_tweet(qt_id) {
+ if let Some(ref qt) = tweeter.retrieve_tweet(&TweetId::Twitter(qt_id.to_owned())) {
let qt_author = tweeter.retrieve_user(&qt.author_id).unwrap();
println!("{} id:{}{}{}",
id_color, qt.internal_id,
qt.reply_to_tweet.clone()
- .map(|id| tweeter.retrieve_tweet(&id)
+ .map(|id| tweeter.retrieve_tweet(&TweetId::Twitter(id.to_owned()))
.and_then(|tw| Some(format!(" reply_to:{}", tw.internal_id)))
.unwrap_or(format!(" reply_to:twitter::{}", id))
)
diff --git a/src/tw/mod.rs b/src/tw/mod.rs
index 90610f1..daf1088 100644
--- a/src/tw/mod.rs
+++ b/src/tw/mod.rs
@@ -1,4 +1,5 @@
use std::path::Path;
+use std::str::FromStr;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
extern crate chrono;
@@ -95,13 +96,136 @@ pub struct TwitterCache {
follower_history: HashMap<String, (String, i64)>, // userid:date??
threads: HashMap<String, u64>, // thread : latest_tweet_in_thread
#[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
+ pub current_user: User,
+ #[serde(skip)]
+ id_conversions: IdConversions
+}
+
+// Internally, a monotonically increasin i64 is always the id used.
+// This is the client's id, not the twitter id for a tweet.
+//
+// Id forms:
+// num // implicitly today:num
+// today:num // last_tweet_of_yesterday_id + num
+// 20171009:num // last_tweet_of_previous_day_id + num
+// open question: what about 20171009:123451234 .. does this reference a future tweet? should
+// probably cause an issue if this would overflow into the next day.
+// ::num // tweet id num
+// twitter::num // twiter tweet id num
+struct IdConversions {
+ // maps a day to the base id for tweets off that day.
+ id_per_date: HashMap<String, u64>,
+ id_to_tweet_id: HashMap<u64, String>
+ // twitter id to id is satisfied by looking up the twitter id in tweeter.tweets and getting
+ // .inner_id
+}
+
+impl Default for IdConversions {
+ fn default() -> Self {
+ IdConversions {
+ id_per_date: HashMap::new(),
+ id_to_tweet_id: HashMap::new()
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum TweetId {
+ Today(u64), // just a number
+ Dated(String, u64), // 20171002:number
+ Bare(u64), // ::number
+ Twitter(String) // twitter::number
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn tweet_id_parse_test() {
+ assert_eq!(TweetId::parse("12345".to_string()), Some(TweetId::Today(12345)));
+ assert_eq!(TweetId::parse("20170403:12345".to_string()), Some(TweetId::Dated("20170403".to_string(), 12345)));
+ assert_eq!(TweetId::parse(":12345".to_string()), Some(TweetId::Bare(12345)));
+ assert_eq!(TweetId::parse("twitter:12345".to_string()), Some(TweetId::Twitter("12345".to_string())));
+ assert_eq!(TweetId::parse("twitter:asdf".to_string()), Some(TweetId::Twitter("asdf".to_string())));
+ assert_eq!(TweetId::parse("a2345".to_string()), None);
+ assert_eq!(TweetId::parse(":".to_string()), None);
+ assert_eq!(TweetId::parse("::".to_string()), None);
+ assert_eq!(TweetId::parse("a:13234".to_string()), None);
+ assert_eq!(TweetId::parse(":a34".to_string()), None);
+ assert_eq!(TweetId::parse("asdf:34".to_string()), None);
+ }
+}
+
+impl TweetId {
+ pub fn parse(id_str: String) -> Option<TweetId> {
+ // ...
+ if id_str.starts_with("twitter:") {
+ Some(TweetId::Twitter(id_str.chars().skip("twitter:".len()).collect()))
+ } else if id_str.starts_with(":") {
+ let rest = id_str.chars().skip(1);
+ if rest.clone().all(|x| x.is_digit(10)) {
+ match u64::from_str(&rest.collect::<String>()) {
+ Ok(num) => Some(TweetId::Bare(num)),
+ Err(e) => {
+ println!("Invalid id: {} - {}", id_str, e);
+ None
+ }
+ }
+ } else {
+ None
+ }
+ } else if id_str.find(":") == Some(8) {
+ let strings: Vec<&str> = id_str.split(":").collect();
+ if strings.len() == 2 {
+ match (strings[0].chars().all(|x| x.is_digit(10)), u64::from_str(strings[1])) {
+ (true, Ok(num)) => Some(TweetId::Dated(strings[0].to_owned(), num)),
+ _ => {
+ println!("Invalid format.");
+ None
+ }
+ }
+ } else {
+ None
+ }
+ } else if id_str.chars().all(|x| x.is_digit(10)) {
+ // today
+ match u64::from_str(&id_str) {
+ Ok(num) => Some(TweetId::Today(num)),
+ Err(e) => {
+ println!("Invalid id: {} - {}", id_str, e);
+ None
+ }
+ }
+ } else {
+ None
+ }
+ }
+}
+
+impl IdConversions {
+ // So this is twid -> Option<u64>
+ // elsewhere we u64 -> Option<Tweet>
+ //
+ // except in the TweetId::Twitter case we TweetId -> Option<Tweet> -> Option<u64> ... to ->
+ // Option<Tweet> in the future?
+ fn to_inner_id(&self, tweeter: &TwitterCache, twid: TweetId) -> Option<u64> {
+ match twid {
+ TweetId::Today(num) => {
+ let first_for_today: u64 = 0;
+ Some(first_for_today + num)
+ },
+ TweetId::Dated(date, num) => {
+ let first_for_date: u64 = 0;
+ Some(first_for_date + num)
+ },
+ TweetId::Bare(num) => Some(num),
+ twid @ TweetId::Twitter(_) => tweeter.retrieve_tweet(&twid).map(|x| x.internal_id)
+ }
+ }
}
impl TwitterCache {
@@ -119,11 +243,11 @@ impl TwitterCache {
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(),
- threads: HashMap::new()
+ threads: HashMap::new(),
+ id_conversions: IdConversions::default()
}
}
fn new_without_caching() -> TwitterCache {
@@ -175,7 +299,7 @@ impl TwitterCache {
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.id_conversions.id_to_tweet_id.insert(tw.internal_id, tw.id.to_owned());
self.tweets.insert(tw.id.to_owned(), tw);
}
}
@@ -298,12 +422,23 @@ impl TwitterCache {
/* 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_tweet(&self, tweet_id: &TweetId) -> Option<&Tweet> {
+ match tweet_id {
+ &TweetId::Bare(ref id) => {
+ self.id_conversions.id_to_tweet_id.get(id)
+ .and_then(|x| self.tweets.get(x))
+ },
+ &TweetId::Today(ref id) => {
+ let inner_id = self.id_conversions.id_to_tweet_id.get(id);
+ panic!("Retrieving tweets with dated IDs is not yet supported.");
+ None
+ },
+ &TweetId::Dated(ref date, ref id) => {
+ panic!("Retrieving tweets with dated IDs is not yet supported.");
+ None
+ },
+ &TweetId::Twitter(ref id) => self.tweets.get(id)
+ }
}
pub fn retrieve_user(&self, user_id: &String) -> Option<&User> {
self.users.get(user_id)