aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Wortman <ixineeringeverywhere@gmail.com>2017-11-10 04:04:00 -0800
committerAndy Wortman <ixineeringeverywhere@gmail.com>2017-11-10 04:04:59 -0800
commitca0762652e293ad9d35b03b537c02d218e44a13f (patch)
tree877f3c97781efd2a8a5a28e1803314575b04382c
parentaa5f8ff4bce898907ffc0c0e2b7ea36d7f8c10b7 (diff)
very hackily add notion of user credentials and PIN auth
also fix bug where cached user info takes precedence over (possibly updated) api json user info
-rw-r--r--src/commands/auth.rs91
-rw-r--r--src/commands/fav.rs12
-rw-r--r--src/commands/follow.rs24
-rw-r--r--src/commands/mod.rs3
-rw-r--r--src/commands/twete.rs50
-rw-r--r--src/display/mod.rs2
-rw-r--r--src/main.rs184
-rw-r--r--src/tw/mod.rs53
-rw-r--r--src/tw/user.rs2
9 files changed, 311 insertions, 110 deletions
diff --git a/src/commands/auth.rs b/src/commands/auth.rs
new file mode 100644
index 0000000..0ed006b
--- /dev/null
+++ b/src/commands/auth.rs
@@ -0,0 +1,91 @@
+use tw;
+use std;
+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";
+static UNFAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/destroy.json";
+
+pub static AUTH: Command = Command {
+ keyword: "auth",
+ params: 0,
+ exec: auth
+};
+
+static OAUTH_REQUEST_TOKEN_URL: &str = "https://api.twitter.com/oauth/request_token";
+static OAUTH_AUTHORIZE_URL: &str = "https://api.twitter.com/oauth/authorize";
+static OAUTH_ACCESS_TOKEN_URL: &str = "https://api.twitter.com/oauth/access_token";
+
+fn auth(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
+ // step 0: get an oauth token.
+ // https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token with
+ // callback set to oob so the user will later get a PIN.
+ // step 1: now present the correect oauth/authorize URL
+ // this is as far as auth can get (rest depends on user PIN'ing with the right thing)
+ let res = queryer.raw_issue_request(::signed_api_req(&format!("{}?oauth_callback=oob", OAUTH_REQUEST_TOKEN_URL), hyper::Method::Post, &tweeter.app_key));
+ match res {
+ Ok(bytes) =>
+ match std::str::from_utf8(&bytes) {
+ Ok(url) => {
+ let parts: Vec<Vec<&str>> = url.split("&").map(|part| part.split("=").collect()).collect();
+ let mut as_map: HashMap<&str, &str> = HashMap::new();
+ for part in parts {
+ as_map.insert(part[0], part[1]);
+ }
+ tweeter.WIP_auth = Some(tw::Credential {
+ key: as_map["oauth_token"].to_owned(),
+ secret: as_map["oauth_token_secret"].to_owned()
+ });
+ tweeter.display_info.status(format!("Now enter `pin` with the code at {}?oauth_token={}", OAUTH_AUTHORIZE_URL, as_map["oauth_token"]));
+ }
+ Err(_) =>
+ tweeter.display_info.status("couldn't rebuild url".to_owned())
+ },
+ Err(e) =>
+ tweeter.display_info.status(format!("request token url error: {}", e))
+ };
+}
+
+pub static PIN: Command = Command {
+ keyword: "pin",
+ params: 1,
+ exec: pin
+};
+
+fn pin(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
+ if tweeter.WIP_auth.is_none() {
+ tweeter.display_info.status("Begin authorizing an account with `auth` first.".to_owned());
+ return;
+ }
+
+ let res = queryer.raw_issue_request(::signed_api_req_with_token(&format!("{}?oauth_verifier={}", OAUTH_ACCESS_TOKEN_URL, line), hyper::Method::Post, &tweeter.app_key, &tweeter.WIP_auth.clone().unwrap()));
+ match res {
+ Ok(bytes) =>
+ match std::str::from_utf8(&bytes) {
+ Ok(url) => {
+ let parts: Vec<Vec<&str>> = url.split("&").map(|part| part.split("=").collect()).collect();
+ let mut as_map: HashMap<&str, &str> = HashMap::new();
+ for part in parts {
+ as_map.insert(part[0], part[1]);
+ }
+ // turns out the "actual" oauth creds are different
+ tweeter.add_profile(tw::Credential {
+ key: as_map["oauth_token"].to_owned(),
+ secret: as_map["oauth_token_secret"].to_owned()
+ });
+ tweeter.WIP_auth = None;
+ tweeter.state = tw::AppState::Reconnect;
+ tweeter.display_info.status("Looks like you authed! Connecting...".to_owned());
+ },
+ Err(_) =>
+ tweeter.display_info.status("couldn't rebuild url".to_owned())
+ },
+ Err(e) =>
+ tweeter.display_info.status(format!("request token url error: {}", e))
+ };
+}
diff --git a/src/commands/fav.rs b/src/commands/fav.rs
index 6109310..08ad7f0 100644
--- a/src/commands/fav.rs
+++ b/src/commands/fav.rs
@@ -19,7 +19,11 @@ 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
- match queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id)) {
+ 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),
+ None => Err("No logged in user to unfav from".to_owned())
+ };
+ match result {
Ok(_) => (),
Err(e) => tweeter.display_info.status(e)
}
@@ -45,7 +49,11 @@ 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
- match queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id)) {
+ 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),
+ None => Err("No logged in user to fav from".to_owned())
+ };
+ match result {
Ok(_) => (),
Err(e) => tweeter.display_info.status(e)
}
diff --git a/src/commands/follow.rs b/src/commands/follow.rs
index b0dc8a7..e9099c9 100644
--- a/src/commands/follow.rs
+++ b/src/commands/follow.rs
@@ -14,7 +14,13 @@ pub static UNFOLLOW: Command = Command {
fn unfl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
let screen_name = line.trim();
- match queryer.do_api_post(&format!("{}?screen_name={}", FOLLOW_URL, screen_name)) {
+ 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)
+ },
+ None => Err("No logged in user to unfollow from".to_owned())
+ };
+ match result {
Ok(_resp) => (),
Err(e) => tweeter.display_info.status(format!("unfl request error: {}", e))
}
@@ -28,5 +34,19 @@ pub static FOLLOW: Command = Command {
fn fl(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
let screen_name = line.trim();
- tweeter.display_info.status(format!("fl resp: {:?}", queryer.do_api_post(&format!("{}?screen_name={}", UNFOLLOW_URL, screen_name))));
+ match tweeter.profile.clone() {
+ Some(user_creds) => {
+ tweeter.display_info.status(
+ format!(
+ "fl resp: {:?}",
+ queryer.do_api_post(
+ &format!("{}?screen_name={}", UNFOLLOW_URL, screen_name),
+ &tweeter.app_key,
+ &user_creds
+ )
+ )
+ )
+ },
+ None => tweeter.display_info.status("No logged in user to follow from".to_owned())
+ };
}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 9ec6c4b..f7536a0 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -7,6 +7,7 @@ pub struct Command {
pub exec: fn(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer)
}
+pub mod auth;
pub mod show_cache;
pub mod twete;
pub mod look_up;
@@ -17,6 +18,8 @@ pub mod follow;
pub mod thread;
pub static COMMANDS: &[&Command] = &[
+ &auth::AUTH,
+ &auth::PIN,
&show_cache::SHOW_CACHE,
&quit::QUIT,
&look_up::LOOK_UP_USER,
diff --git a/src/commands/twete.rs b/src/commands/twete.rs
index 4452df9..239e039 100644
--- a/src/commands/twete.rs
+++ b/src/commands/twete.rs
@@ -20,7 +20,11 @@ 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()) {
- match queryer.do_api_post(&format!("{}/{}.json", DEL_TWEET_URL, twitter_id)) {
+ 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),
+ None => Err("No logged in user to delete as".to_owned())
+ };
+ match result {
Ok(_) => (),
Err(e) => tweeter.display_info.status(e)
}
@@ -54,7 +58,11 @@ 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);
if text.len() <= 140 {
- match queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted)) {
+ let result = match tweeter.profile.clone() {
+ Some(user_creds) => queryer.do_api_post(&format!("{}?status={}", CREATE_TWEET_URL, substituted), &tweeter.app_key, &user_creds),
+ None => Err("No logged in user to tweet as".to_owned())
+ };
+ match result {
Ok(_) => (),
Err(e) => tweeter.display_info.status(e)
}
@@ -166,7 +174,13 @@ 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) {
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);
- match queryer.do_api_post(&format!("{}?status={}&in_reply_to_status_id={}", CREATE_TWEET_URL, substituted, twete.id)) {
+ let result = match tweeter.profile.clone() {
+ Some(user_creds) => {
+ 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())
+ };
+ match result {
Ok(_) => (),
Err(e) => tweeter.display_info.status(e)
}
@@ -200,13 +214,21 @@ fn quote(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer) {
twete.id
)
);
- match queryer.do_api_post(
- &format!("{}?status={}&attachment_url={}",
- CREATE_TWEET_URL,
- substituted,
- attachment_url
- )
- ) {
+ let result = match tweeter.profile.clone() {
+ Some(user_creds) => {
+ queryer.do_api_post(
+ &format!("{}?status={}&attachment_url={}",
+ CREATE_TWEET_URL,
+ substituted,
+ attachment_url
+ ),
+ &tweeter.app_key,
+ &user_creds
+ )
+ },
+ None => Err("No logged in user to tweet as".to_owned())
+ };
+ match result {
Ok(_) => (),
Err(e) => tweeter.display_info.status(e)
}
@@ -237,7 +259,13 @@ 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()) {
- match queryer.do_api_post(&format!("{}/{}.json", RT_TWEET_URL, twitter_id)) {
+ 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)
+ },
+ None => Err("No logged in user to retweet as".to_owned())
+ };
+ match result {
Ok(_) => (),
Err(e) => tweeter.display_info.status(e)
}
diff --git a/src/display/mod.rs b/src/display/mod.rs
index 4480855..92fa9dd 100644
--- a/src/display/mod.rs
+++ b/src/display/mod.rs
@@ -180,6 +180,8 @@ pub fn paint(tweeter: &mut ::tw::TwitterCache) -> Result<(), std::io::Error> {
lines
}
Infos::Thread(ids) => {
+ // TODO: group together thread elements by the same person a little
+ // better..
let mut tweets: Vec<Vec<String>> = ids.iter().rev().map(|x| into_display_lines(render_twete(x, tweeter), width)).collect();
let last = tweets.pop();
let mut lines = tweets.into_iter().fold(Vec::new(), |mut sum, lines| {
diff --git a/src/main.rs b/src/main.rs
index f8276ed..97d5315 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -39,13 +39,6 @@ mod tw;
mod display;
mod commands;
-//Change these values to your real Twitter API credentials
-static consumer_key: &str = "T879tHWDzd6LvKWdYVfbJL4Su";
-static consumer_secret: &str = "OAXXYYIozAZ4vWSmDziI1EMJCKXPmWPFgLbJpB896iIAMIAdpb";
-static token: &str = "629126745-Qt6LPq2kR7w58s7WHzSqcs4CIdiue64kkfYYB7RI";
-static token_secret: &str = "3BI3YC4WVbKW5icpHORWpsTYqYIj5oAZFkrgyIAoaoKnK";
-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";
@@ -64,11 +57,11 @@ pub struct Queryer {
}
impl Queryer {
- fn do_api_get(&mut self, url: &str) -> Result<serde_json::Value, String> {
- self.issue_request(signed_api_get(url))
+ fn do_api_get(&mut self, url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
+ self.issue_request(signed_api_get(url, app_cred, user_cred))
}
- fn do_api_post(&mut self, url: &str) -> Result<serde_json::Value, String> {
- self.issue_request(signed_api_post(url))
+ fn do_api_post(&mut self, url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> Result<serde_json::Value, String> {
+ self.issue_request(signed_api_post(url, app_cred, user_cred))
}
/*
fn do_web_req(&mut self, url: &str) -> Option<serde_json::Value> {
@@ -76,6 +69,10 @@ impl Queryer {
}*/
// TODO: make this return the status as well!
fn issue_request(&mut self, req: hyper::client::Request) -> Result<serde_json::Value, String> {
+ let resp_body = self.raw_issue_request(req);
+ resp_body.and_then(|body| serde_json::from_slice(&body).map_err(|e| e.to_string()))
+ }
+ fn raw_issue_request(&mut self, req: hyper::client::Request) -> Result<Vec<u8>, String> {
let lookup = self.client.request(req);
let resp: hyper::Response = self.core.run(lookup).unwrap();
@@ -84,18 +81,10 @@ impl Queryer {
let chunks: Vec<hyper::Chunk> = self.core.run(resp.body().collect()).unwrap();
let resp_body: Vec<u8> = chunks.into_iter().flat_map(|chunk| chunk.into_iter()).collect();
-
- match serde_json::from_slice(&resp_body) {
- Ok(value) => {
- if status != hyper::StatusCode::Ok {
- Err(format!("!! Requests returned status: {}\n{}", status, value))
- } else {
- Ok(value)
- }
- }
- Err(e) => {
- Err(format!("!! Requests returned status: {}\nerror deserializing json: {}", status, e))
- }
+ if status != hyper::StatusCode::Ok {
+ Err(format!("!! Requests returned status: {} - {:?}", status, std::str::from_utf8(&resp_body)))
+ } else {
+ Ok(resp_body)
}
}
}
@@ -137,15 +126,23 @@ fn signed_web_get(url: &str) -> hyper::client::Request {
}
*/
-fn signed_api_post(url: &str) -> hyper::client::Request {
- signed_api_req(url, Method::Post)
+fn signed_api_post(url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
+ signed_api_req_with_token(url, Method::Post, app_cred, user_cred)
}
-fn signed_api_get(url: &str) -> hyper::client::Request {
- signed_api_req(url, Method::Get)
+fn signed_api_get(url: &str, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
+ signed_api_req_with_token(url, Method::Get, app_cred, user_cred)
}
-fn signed_api_req(url: &str, method: Method) -> hyper::client::Request {
+fn signed_api_req_with_token(url: &str, method: Method, app_cred: &tw::Credential, user_cred: &tw::Credential) -> hyper::client::Request {
+ inner_signed_api_req(url, method, app_cred, Some(user_cred))
+}
+
+fn signed_api_req(url: &str, method: Method, app_cred: &tw::Credential) -> hyper::client::Request {
+ inner_signed_api_req(url, method, app_cred, None)
+}
+
+fn inner_signed_api_req(url: &str, method: Method, app_cred: &tw::Credential, maybe_user_cred: Option<&tw::Credential>) -> hyper::client::Request {
// let params: Vec<(String, String)> = vec![("track".to_string(), "london".to_string())];
let method_string = match method {
Method::Get => "GET",
@@ -156,15 +153,21 @@ fn signed_api_req(url: &str, method: Method) -> hyper::client::Request {
let params: Vec<(String, String)> = vec![];
let _param_string: String = params.iter().map(|p| p.0.clone() + &"=".to_string() + &p.1).collect::<Vec<String>>().join("&");
- let header = oauthcli::OAuthAuthorizationHeaderBuilder::new(
+ let parsed_url = url::Url::parse(url).unwrap();
+
+ let mut builder = oauthcli::OAuthAuthorizationHeaderBuilder::new(
method_string,
- &url::Url::parse(url).unwrap(),
- consumer_key,
- consumer_secret,
+ &parsed_url,
+ app_cred.key.to_owned(),
+ app_cred.secret.to_owned(),
oauthcli::SignatureMethod::HmacSha1,
- )
- .token(token, token_secret)
- .finish();
+ );
+
+ if let Some(user_cred) = maybe_user_cred {
+ builder.token(user_cred.key.to_owned(), user_cred.secret.to_owned());
+ }
+
+ let header = builder.finish();
let mut req = Request::new(method, url.parse().unwrap());
@@ -185,14 +188,6 @@ fn main() {
let (ui_tx, mut ui_rx) = chan::sync::<Result<termion::event::Event, std::io::Error>>(0);
- let mut twete_rx = connect_twitter_stream();
-
- std::thread::spawn(move || {
- for input in stdin().events() {
- ui_tx.send(input);
- }
- });
-
// I *would* want to load this before spawning the thread, but..
// tokio_core::reactor::Inner can't be moved between threads safely
// and beacuse it's an Option-al field, it might be present
@@ -205,6 +200,14 @@ fn main() {
println!("Loaded cache!");
+ let mut maybe_twete_rx: Option<chan::Receiver<Vec<u8>>> = tweeter.profile.clone().map(|user_creds| connect_twitter_stream(tweeter.app_key.clone(), user_creds));
+
+ std::thread::spawn(move || {
+ for input in stdin().events() {
+ ui_tx.send(input);
+ }
+ });
+
let c2 = Core::new().unwrap(); // i swear this is not where the botnet lives
let handle = &c2.handle();
let secondary_connector = HttpsConnector::new(4, handle).unwrap();
@@ -226,10 +229,10 @@ fn main() {
tcsetattr(0, TCSANOW, &new_termios).unwrap();
loop {
- match do_ui(ui_rx, twete_rx, &mut tweeter, &mut queryer) {
- Some((new_ui_rx, new_twete_rx)) => {
+ match do_ui(ui_rx, maybe_twete_rx, &mut tweeter, &mut queryer) {
+ Some((new_ui_rx, new_maybe_twete_rx)) => {
ui_rx = new_ui_rx;
- twete_rx = new_twete_rx;
+ maybe_twete_rx = new_maybe_twete_rx;
},
None => {
break;
@@ -266,6 +269,7 @@ fn handle_input(event: termion::event::Event, tweeter: &mut tw::TwitterCache, qu
_ => {}
}
}
+ // TODO: ctrl+u, ctrl+w
Event::Key(Key::Char(x)) => {
match tweeter.display_info.mode.clone() {
None => {
@@ -311,62 +315,66 @@ fn handle_input(event: termion::event::Event, tweeter: &mut tw::TwitterCache, qu
}
}
-fn do_ui(ui_rx_orig: chan::Receiver<Result<termion::event::Event, std::io::Error>>, twete_rx: chan::Receiver<Vec<u8>>, mut tweeter: &mut tw::TwitterCache, mut queryer: &mut ::Queryer) -> Option<(chan::Receiver<Result<termion::event::Event, std::io::Error>>, chan::Receiver<Vec<u8>>)> {
+fn handle_twitter_line(line: Vec<u8>, mut tweeter: &mut tw::TwitterCache, mut queryer: &mut ::Queryer) {
+ let jsonstr = std::str::from_utf8(&line).unwrap().trim();
+ /* TODO: replace from_str with from_slice */
+ match serde_json::from_str(&jsonstr) {
+ Ok(json) => {
+ tw::handle_message(json, &mut tweeter, &mut queryer);
+ if tweeter.needs_save && tweeter.caching_permitted {
+ tweeter.store_cache();
+ }
+ },
+ Err(e) =>
+ tweeter.display_info.status(format!("Error reading twitter line: {}", jsonstr))
+ }
+}
+
+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>>>)> {
loop {
let ui_rx_a = &ui_rx_orig;
let ui_rx_b = &ui_rx_orig;
- chan_select! {
- twete_rx.recv() -> twete => match twete {
- Some(line) => {
- let jsonstr = std::str::from_utf8(&line).unwrap().trim();
- /* TODO: replace from_str with from_slice */
- let json: serde_json::Value = serde_json::from_str(&jsonstr).unwrap();
- tw::handle_message(json, &mut tweeter, &mut queryer);
- if tweeter.needs_save && tweeter.caching_permitted {
- tweeter.store_cache();
- }
- }
- None => {
- tweeter.display_info.status("Twitter stream hung up...".to_owned());
- chan_select! {
- ui_rx_b.recv() -> input => match input {
- Some(maybe_event) => {
- if let Ok(event) = maybe_event {
- handle_input(event, tweeter, queryer);
- } else {
- // stdin closed?
- }
- }
- // twitter stream closed, ui thread closed, uhh..
- None => std::process::exit(0)
+ 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
}
}
},
- ui_rx_a.recv() -> user_input => match user_input {
- Some(maybe_event) => {
- if let Ok(event) = maybe_event {
- handle_input(event, tweeter, queryer); // eventually DisplayInfo too, as a separate piece of data...
- } else {
- // dunno how we'd reach this... stdin closed?
+ &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
}
- },
- None => tweeter.display_info.status("UI thread hung up...".to_owned())
+ }
}
- // and then we can introduce a channel that just sends a message every 100 ms or so
- // that acts as a clock!
}
- match tweeter.state {
- tw::AppState::Reconnect => return Some((ui_rx_orig.clone(), connect_twitter_stream())),
- _ => ()
- };
-
// one day display_info should be distinct
match display::paint(tweeter) {
Ok(_) => (),
Err(e) => println!("{}", e) // TODO: we got here because writing to stdout failed. what to do now?
};
+
+ match tweeter.state {
+ 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))));
+ }
+ _ => ()
+ };
}
}
@@ -402,7 +410,7 @@ fn url_encode(s: &str) -> String {
.replace("]", "%5d")
}
-fn connect_twitter_stream() -> chan::Receiver<Vec<u8>> {
+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);
std::thread::spawn(move || {
@@ -415,7 +423,7 @@ fn connect_twitter_stream() -> chan::Receiver<Vec<u8>> {
.connector(connector)
.build(&core.handle());
- let req = signed_api_get(STREAMURL);
+ let req = signed_api_get(STREAMURL, &app_cred, &user_cred);
let work = client.request(req).and_then(|res| {
let status = res.status();
if status != hyper::StatusCode::Ok {
diff --git a/src/tw/mod.rs b/src/tw/mod.rs
index b1bc9c2..767aae9 100644
--- a/src/tw/mod.rs
+++ b/src/tw/mod.rs
@@ -92,12 +92,24 @@ pub fn full_twete_text(twete: &serde_json::map::Map<String, serde_json::Value>)
twete_text
}
+#[derive(Clone, Serialize, Deserialize)]
+pub struct Credential {
+ pub key: String,
+ pub secret: String
+}
+
#[derive(Serialize, Deserialize)]
pub struct TwitterCache {
#[serde(skip)]
pub users: HashMap<String, User>,
#[serde(skip)]
pub tweets: HashMap<String, Tweet>,
+ #[serde(skip)]
+ pub WIP_auth: Option<Credential>,
+ 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>,
@@ -255,12 +267,18 @@ 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..
+ const PROFILE_CACHE: &'static str = "cache/profile.json";
fn new() -> TwitterCache {
TwitterCache {
users: HashMap::new(),
tweets: HashMap::new(),
+ WIP_auth: None,
+ app_key: Credential {
+ key: "".to_owned(),
+ secret: "".to_owned()
+ },
+ profile: None, // this will become a HashMap when multiple profiles are supported
following: HashSet::new(),
following_history: HashMap::new(),
followers: HashSet::new(),
@@ -293,8 +311,19 @@ impl TwitterCache {
cache.caching_permitted = false;
cache
}
+ pub fn add_profile(&mut self, creds: Credential) {
+ self.profile = Some(creds);
+ if self.caching_permitted {
+ self.store_cache();
+ }
+ }
fn cache_user(&mut self, user: User) {
- if !self.users.contains_key(&user.id) {
+ let update_cache = match self.users.get(&user.id) {
+ Some(cached_user) => &user != cached_user,
+ None => true
+ };
+
+ if update_cache {
let mut file =
OpenOptions::new()
.create(true)
@@ -583,20 +612,32 @@ impl TwitterCache {
fn look_up_user(&mut self, id: &str, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {
let url = &format!("{}?user_id={}", ::USER_LOOKUP_URL, id);
- queryer.do_api_get(url)
+ match self.profile {
+ Some(ref user_creds) => queryer.do_api_get(url, &self.app_key, &user_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);
- queryer.do_api_get(url)
+ match self.profile {
+ Some(ref user_creds) => queryer.do_api_get(url, &self.app_key, &user_creds),
+ None => Err("No authorized user to conduct lookup".to_owned())
+ }
}
pub fn get_settings(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {
- queryer.do_api_get(::ACCOUNT_SETTINGS_URL)
+ match self.profile {
+ Some(ref user_creds) => queryer.do_api_get(::ACCOUNT_SETTINGS_URL, &self.app_key, &user_creds),
+ None => Err("No authorized user to request settings".to_owned())
+ }
}
pub fn get_followers(&self, queryer: &mut ::Queryer) -> Result<serde_json::Value, String> {
- queryer.do_api_get(::GET_FOLLOWER_IDS_URL)
+ match self.profile {
+ Some(ref user_creds) => queryer.do_api_get(::GET_FOLLOWER_IDS_URL, &self.app_key, &user_creds),
+ None => Err("No authorized user to request followers".to_owned())
+ }
}
pub fn set_thread(&mut self, name: String, last_id: u64) -> bool {
diff --git a/src/tw/user.rs b/src/tw/user.rs
index 0af4eb8..86fbe3f 100644
--- a/src/tw/user.rs
+++ b/src/tw/user.rs
@@ -1,6 +1,6 @@
extern crate serde_json;
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct User {
pub id: String,
pub name: String,