aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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,