1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
extern crate hyper;
extern crate url;
extern crate serde;
extern crate serde_json;
extern crate hyper_native_tls;
#[macro_use]
extern crate lazy_static;

use hyper::client::{Client};
use hyper::header::{ContentType};
use hyper::client::response::{Response};
use hyper::error::Error as HyperError;
use url::form_urlencoded::{Serializer};
use std::io::Read;
use std::io::Error;
use std::collections::BTreeMap;
use serde_json::error::Error as JsonError;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;

#[derive(Debug)]
pub enum CleverbotError {
    IncorrectCredentials,
    DuplicatedReferenceNames,
    Io(HyperError),
    Api(String),
    Std(Error),
    Json(JsonError),
    MissingValue(String)
}

impl From<HyperError> for CleverbotError {
    fn from(err: HyperError) -> CleverbotError {
        CleverbotError::Io(err)
    }
}

impl From<Error> for CleverbotError {
    fn from(err: Error) -> CleverbotError {
        CleverbotError::Std(err)
    }
}

impl From<JsonError> for CleverbotError {
    fn from(err: JsonError) -> CleverbotError {
        CleverbotError::Json(err)
    }
}

pub struct Cleverbot {
    user: String,
    key: String,
    pub nick: String,
}

lazy_static! {
    static ref CLIENT: Client = Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap()));
}

impl Cleverbot {
    /// Creates a new Cleverbot instance.
    ///
    /// * `user` - The API User.
    /// * `key` - The API Key.
    /// * `nick` - The reference nick, or None.
    pub fn new(user: String, key: String, nick: Option<String>) -> Result<Cleverbot, CleverbotError> {
        let mut response = {
            let mut args = vec![
                ("user", &*user),
                ("key", &*key),
            ];
            if let Some(ref nick) = nick { args.push(("nick", &*nick)) };
            try!(request("https://cleverbot.io/1.0/create", &*args))
        };
        let mut body = String::new();
        try!(response.read_to_string(&mut body));
        let json: BTreeMap<String, String> = try!(serde_json::from_str(&body));
        let opt_result = json.get("status");
        let result = match opt_result {
            Some(result) => result,
            None => return Err(CleverbotError::MissingValue(String::from("status"))),
        };
        match result.as_ref() {
            "success" => {
                let json_nick = json.get("nick");
                match json_nick {
                    Some(nick) => Ok(Cleverbot {
                        user: user,
                        key: key,
                        nick: nick.to_string()
                    }),
                    None => Err(CleverbotError::MissingValue(String::from("nick"))),
                }
            },
            "Error: API credentials incorrect" => Err(CleverbotError::IncorrectCredentials),
            "Error: reference name already exists" => Err(CleverbotError::DuplicatedReferenceNames),
            _ => Err(CleverbotError::Api(result.to_owned()))
        }
    }

    /// Sends the bot a message and returns its response. If the nick is not set, it will
    /// set it randomly through set_nick_randomly. Returns its response or error string.
    ///
    /// * `message` - The message to send to the bot.
    pub fn say(&mut self, message: &str) -> Result<String, CleverbotError> {
        let args = vec![
            ("user", &*self.user),
            ("key", &*self.key),
            ("nick", &*self.nick),
            ("text", message),
        ];
        let mut response = try!(request("https://cleverbot.io/1.0/ask", &*args));
        let mut body = String::new();
        try!(response.read_to_string(&mut body));
        let json: BTreeMap<String, String> = try!(serde_json::from_str(&body));
        let opt_result = json.get("status");
        let result = match opt_result {
            Some(result) => result,
            None => return Err(CleverbotError::MissingValue(String::from("status"))),
        };
        match result.as_ref() {
            "success" => {
                let json_response = json.get("response");
                match json_response {
                    Some(response) => Ok(response.to_string()),
                    None => Err(CleverbotError::MissingValue(String::from("nick"))),
                }
            },
            "Error: API credentials incorrect" => Err(CleverbotError::IncorrectCredentials),
            "Error: reference name already exists" => Err(CleverbotError::DuplicatedReferenceNames),
            _ => Err(CleverbotError::Api(result.to_owned()))
        }
    }
}

/// Submits a POST request to the URL with the given vec body.
///
/// * `base` - The URL
/// * `args` - A vector representing the request body.
fn request(base: &str, args: &[(&str, &str)]) -> Result<Response, HyperError> {
    let mut serializer = Serializer::new(String::new());
    for pair in args {
        serializer.append_pair(pair.0, pair.1);
    }
    let body = serializer.finish();
    CLIENT.post(base)
        .body(&body)
        .header(ContentType::form_url_encoded())
        .send()
}