Skip to content
Permalink
Browse files

Add twitter search endpoint

  • Loading branch information
rogeriochaves committed May 30, 2018
1 parent 07954d7 commit 5da8ac5cf588c1624b2e47e1e2897809394c9c70
Showing with 184 additions and 11 deletions.
  1. +2 −1 api/.env.sample
  2. +180 −10 api/src/endpoints/twitter.rs
  3. +2 −0 api/src/lib.rs
@@ -3,4 +3,5 @@ ADMIN_EMAIL="admin@fakenewsdetector.org"
ADMIN_PASSWORD="123"
ROCKET_SECRET_KEY="secretKeyJustForDevelopmentB3VRq9qA2y4lc+Mg="
TWITTER_APP_KEY="you have to create a twitter app and fill this"
TWITTER_APP_SECRET="you have to create a twitter app and fill this"
TWITTER_APP_SECRET="you have to create a twitter app and fill this"
TWITTER_CALLBACK="http://localhost:8000/twitter/callback"
@@ -9,19 +9,31 @@ use tokio_core::reactor;
use rocket::http::{Cookie, Cookies};
use cookie::SameSite;
use std::env;
use rocket_contrib::Json;
use std::borrow::Cow;

#[get("/twitter/auth")]
fn auth(mut cookies: Cookies) -> Result<Redirect, status::Custom<String>> {
#[derive(FromForm)]
pub struct AuthParams {
return_to: String,
}

#[get("/twitter/auth?<params>")]
fn auth(mut cookies: Cookies, params: AuthParams) -> Result<Redirect, status::Custom<String>> {
let (mut core, handle, con_token) = get_config();
let request_token = core.run(egg_mode::request_token(
&con_token,
"http://localhost:8000/twitter/callback",
env::var("TWITTER_CALLBACK").unwrap_or(String::from(
"http://localhost:8000/twitter/callback",
)),
&handle,
)).map_err(|err| internal_error(&*format!("{}", err)))?;

let mut secret_cookie = Cookie::new("request_token_secret", request_token.secret.to_owned());
secret_cookie.set_same_site(SameSite::Lax);
cookies.add_private(secret_cookie);
cookies = add_cookie(
"twitter_request_token_secret",
request_token.secret.to_owned(),
cookies,
);
add_cookie("twitter_return_to", params.return_to, cookies);

Ok(Redirect::found(
&*egg_mode::authenticate_url(&request_token),
@@ -41,24 +53,149 @@ fn callback(
) -> Result<Redirect, status::Custom<String>> {
let (mut core, handle, con_token) = get_config();
let request_token_secret = cookies
.get_private("request_token_secret")
.get_private("twitter_request_token_secret")
.map(|cookie| cookie.value().to_owned())
.unwrap_or(String::from(""));
let return_to = cookies
.get_private("twitter_return_to")
.map(|cookie| cookie.value().to_owned())
.unwrap_or(String::from("https://fakenewsdetector.org/"));

if request_token_secret.is_empty() {
return Ok(Redirect::found("/twitter/auth"));
return Ok(Redirect::found(
&*format!("/twitter/auth?return_to={}", return_to),
));
}

let request_token = egg_mode::KeyPair::new(params.oauth_token, request_token_secret);

let (_token, _user_id, screen_name) = core.run(egg_mode::access_token(
let (token, _user_id, _screen_name) = core.run(egg_mode::access_token(
con_token,
&request_token,
params.oauth_verifier,
&handle,
)).map_err(|err| internal_error(&*format!("{}", err)))?;

Err(status::Custom(Status::Ok, String::from(screen_name)))
match token {
egg_mode::Token::Access {
ref access,
consumer: ref _consumer,
} => {
cookies = add_cookie("twitter_access_key", access.key.to_owned(), cookies);
add_cookie("twitter_access_secret", access.secret.to_owned(), cookies);
}
egg_mode::Token::Bearer(_token) => {}
}

Ok(Redirect::found(&*return_to))
}

#[derive(Serialize)]
struct TwitterUser {
screen_name: String,
}

#[get("/twitter/user")]
fn user(cookies: Cookies) -> Result<Json<TwitterUser>, status::Custom<String>> {
let (mut core, handle, _) = get_config();
let access_token = get_authenticated_token(cookies).ok_or(not_authenticated())?;

let user = core.run(egg_mode::verify_tokens(&access_token, &handle))
.map_err(|err| internal_error(&*format!("{}", err)))?;

Ok(Json(
TwitterUser { screen_name: user.screen_name.to_owned() },
))
}

#[derive(Debug, Serialize)]
struct SearchResponse {
statuses: Vec<SearchTweet>,
}

#[derive(Debug, Serialize)]
struct SearchTweet {
id_str: String,
user: Option<SearchUser>,
retweeted_status: Option<IdAndUser>,
}

#[derive(Debug, Serialize)]
struct SearchUser {
id_str: String,
screen_name: String,
}

#[derive(Debug, Serialize)]
struct IdAndUser {
id_str: String,
user: Option<SearchUser>,
}

fn parse_statuses(result: &egg_mode::Response<egg_mode::search::SearchResult>) -> Vec<SearchTweet> {
result
.statuses
.iter()
.map(|tweet| {
SearchTweet {
id_str: format!("{}", tweet.id),
user: tweet.to_owned().user.map(|user| {
SearchUser {
id_str: format!("{}", user.id),
screen_name: user.screen_name,
}
}),
retweeted_status: tweet.to_owned().retweeted_status.map(|rt| {
IdAndUser {
id_str: format!("{}", rt.id),
user: tweet.to_owned().user.map(|user| {
SearchUser {
id_str: format!("{}", user.id),
screen_name: user.screen_name,
}
}),
}
}),
}
})
.collect()
}

#[derive(FromForm)]
pub struct TwitterSearchParams {
q: String,
}

#[get("/twitter/search?<params>")]
fn search(
cookies: Cookies,
params: TwitterSearchParams,
) -> Result<Json<SearchResponse>, status::Custom<String>> {
let (mut core, handle, _) = get_config();
let access_token = get_authenticated_token(cookies).ok_or(not_authenticated())?;

println!("Search page {} for {}", 1, params.q);
let mut search_result = core.run(
egg_mode::search::search(params.q)
.result_type(egg_mode::search::ResultType::Mixed)
.count(100)
.call(&access_token, &handle),
).map_err(|err| internal_error(&*format!("{}", err)))?;

let mut statuses = parse_statuses(&search_result);
let mut page = 1;
while page < 10 {
if search_result.statuses.is_empty() {
break;
}
println!("Search page {}", page + 1);
search_result = core.run(search_result.older(&access_token, &handle))
.map_err(|err| internal_error(&*format!("{}", err)))?;
statuses.extend(parse_statuses(&search_result));
page = page + 1;
}

Ok(Json(SearchResponse { statuses: statuses }))
}

fn get_config() -> (tokio_core::reactor::Core, tokio_core::reactor::Handle, egg_mode::KeyPair) {
@@ -74,4 +211,37 @@ fn get_config() -> (tokio_core::reactor::Core, tokio_core::reactor::Handle, egg_

fn internal_error(text: &str) -> status::Custom<String> {
status::Custom(Status::InternalServerError, String::from(text))
}

fn not_authenticated() -> status::Custom<String> {
status::Custom(
Status::Unauthorized,
String::from("User is not authenticated"),
)
}

fn add_cookie<'a, V>(name: &'static str, value: V, mut cookies: Cookies<'a>) -> Cookies<'a>
where
V: Into<Cow<'static, str>>,
{
let mut secret_cookie = Cookie::new(name, value);
secret_cookie.set_same_site(SameSite::Lax);
cookies.add_private(secret_cookie);
cookies
}

fn get_authenticated_token(mut cookies: Cookies) -> Option<egg_mode::Token> {
let (_, _, con_token) = get_config();
let acesss_key = cookies.get_private("twitter_access_key").map(|cookie| {
cookie.value().to_owned()
})?;
let acesss_secret = cookies.get_private("twitter_access_secret").map(|cookie| {
cookie.value().to_owned()
})?;
let access_token = egg_mode::KeyPair::new(acesss_key, acesss_secret);

Some(egg_mode::Token::Access {
access: access_token,
consumer: con_token,
})
}
@@ -44,6 +44,8 @@ pub fn start_server() {
endpoints::healthcheck::healthcheck,
endpoints::twitter::auth,
endpoints::twitter::callback,
endpoints::twitter::user,
endpoints::twitter::search,
endpoints::categories::get_categories,
endpoints::votes::get_votes,
endpoints::votes::get_votes_by_content,

0 comments on commit 5da8ac5

Please sign in to comment.
You can’t perform that action at this time.