// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to
// This line shouldn't be necessary in a real project.
extern crate hyper_pkg as hyper;
use std::fs::File;
use std::path::PathBuf;
use futures::prelude::*;
use http::header::{HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use serde::de;
use serde::Deserialize;
use twitter_stream::Token;
#[derive(Deserialize)]
#[serde(untagged)]
enum StreamMessage {
Tweet(Tweet),
Other(de::IgnoredAny),
}
struct Tweet {
entities: Option,
id: u64,
text: String,
user: User,
is_retweet: bool,
}
#[derive(Deserialize)]
struct Entities {
user_mentions: Vec,
}
#[derive(Deserialize)]
struct UserMention {
id: u64,
}
#[derive(Deserialize)]
struct User {
id: u64,
screen_name: String,
}
/// Represents a GET statuses/update request.
#[derive(oauth::Request)]
struct StatusUpdate<'a> {
status: &'a str,
in_reply_to_status_id: Option,
}
#[derive(Deserialize)]
#[serde(remote = "Token")]
struct TokenDef {
// The `getter` attribute is required to make the `Deserialize` impl use the `From` conversion,
// even if we are not deriving `Serialize` here.
#[serde(getter = "__")]
consumer_key: String,
consumer_secret: String,
access_key: String,
access_secret: String,
}
impl From for Token {
fn from(def: TokenDef) -> Token {
Token::from_parts(
def.consumer_key,
def.consumer_secret,
def.access_key,
def.access_secret,
)
}
}
type HttpsConnector = hyper_tls::HttpsConnector;
#[tokio::main]
async fn main() {
let mut credential_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
credential_path.push("credential.json");
let conn = HttpsConnector::new();
let mut client = hyper::Client::builder().build::<_, hyper::Body>(conn);
let credential = File::open(credential_path).unwrap();
let token =
TokenDef::deserialize(&mut serde_json::Deserializer::from_reader(credential)).unwrap();
// Information of the authenticated user:
let user = verify_credentials(&token, &client).await;
let mut stream = twitter_stream::Builder::new(token.as_ref())
.track(format!("@{}", user.screen_name))
.listen_with_client(&mut client)
.try_flatten_stream();
while let Some(json) = stream.next().await {
if let Ok(StreamMessage::Tweet(tweet)) = serde_json::from_str(&json.unwrap()) {
let mentions_me = |entities: Entities| {
entities
.user_mentions
.iter()
.any(|mention| mention.id == user.id)
};
if !tweet.is_retweet
&& tweet.user.id != user.id
&& tweet.entities.map_or(false, mentions_me)
{
// Send a reply
let tweeting = StatusUpdate {
status: &format!("@{} {}", tweet.user.screen_name, tweet.text),
in_reply_to_status_id: Some(tweet.id),
}
.send(&token, &client);
tokio::spawn(tweeting);
}
}
}
}
// The custom `Deserialize` impl is needed to handle Tweets with >140 characters.
// Once the Labs API is available, this impl should be unnecessary.
impl<'de> Deserialize<'de> for Tweet {
fn deserialize>(d: D) -> Result {
#[derive(Deserialize)]
struct Prototype {
entities: Option,
id: u64,
// Add the following attribute if you want to deserialize REST API responses too.
// #[serde(alias = "full_text")]
text: String,
user: User,
extended_tweet: Option,
retweeted_status: Option,
}
#[derive(Deserialize)]
struct ExtendedTweet {
full_text: String,
entities: Option,
}
Prototype::deserialize(d).map(|p| {
let (text, entities) = p
.extended_tweet
.map_or((p.text, p.entities), |e| (e.full_text, e.entities));
Tweet {
entities,
id: p.id,
text,
user: p.user,
is_retweet: p.retweeted_status.is_some(),
}
})
}
}
impl<'a> StatusUpdate<'a> {
/// Performs the GET statuses/update request.
fn send(
&self,
token: &Token,
client: &hyper::Client,
) -> impl Future