#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[warn(rust_2018_idioms)]
#[macro_use]
extern crate serde_json;
use std::sync::Arc;
use futures::future::{Future, Loop};
use http::header::HeaderValue;
use parking_lot::Mutex;
use reqwest::header::HeaderMap;
use reqwest::Method;
use reqwest::r#async::{
Client as ReqwestClient,
ClientBuilder,
multipart::Form,
};
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use serde_json::Value;
pub(crate) use ratelimit::*;
use spectacles_model::channel::Channel;
use spectacles_model::guild::{CreateGuildOptions, Guild};
use spectacles_model::invite::Invite;
use spectacles_model::snowflake::Snowflake;
use spectacles_model::User;
use spectacles_model::voice::VoiceRegion;
pub use views::*;
pub use crate::errors::{Error, Result};
mod errors;
mod ratelimit;
mod views;
mod constants;
#[derive(Clone, Debug)]
pub struct RestClient {
pub token: String,
pub base_url: String,
pub http: ReqwestClient,
ratelimiter: Option<Arc<Mutex<Ratelimter>>>,
}
impl RestClient {
pub fn new(token: String, using_ratelimiter: bool) -> Self {
let token = if token.starts_with("Bot ") {
token
} else {
format!("Bot {}", token)
};
let mut headers = HeaderMap::new();
let value = HeaderValue::from_str(&token).unwrap();
let agent = HeaderValue::from_str(
"DiscordBot (https://github.com/spec-tacles/spectacles-rs, v1.0.0)"
).unwrap();
headers.insert("Authorization", value);
headers.insert("User-Agent", agent);
let client = ClientBuilder::new().default_headers(headers).build()
.expect("Failed to build HTTP client");
let mut rest = RestClient {
token,
http: client.clone(),
base_url: constants::BASE_URL.to_string(),
ratelimiter: None,
};
if using_ratelimiter {
rest.ratelimiter = Some(Arc::new(Mutex::new(Ratelimter::new(client))));
};
rest
}
pub fn set_proxy(mut self, url: String) -> Self {
self.base_url = url;
self
}
pub fn channel(&self, id: &Snowflake) -> ChannelView {
ChannelView::new(id.0, self.clone())
}
pub fn guild(&self, id: &Snowflake) -> GuildView {
GuildView::new(id.0, self.clone())
}
pub fn webhook(&self, id: &Snowflake) -> WebhookView {
WebhookView::new(id.0, self.clone())
}
pub fn get_user(&self, id: &Snowflake) -> impl Future<Item=User, Error=Error> {
self.request(Endpoint::new(
Method::GET,
format!("/users/{}", id.0),
))
}
pub fn create_dm(&self, user: &Snowflake) -> impl Future<Item=Channel, Error=Error> {
let json = json!({
"recipient_id": user.0
});
self.request(Endpoint::new(
Method::POST,
String::from("/users/@me/channels"),
).json(json))
}
pub fn create_guild(&self, opts: CreateGuildOptions) -> impl Future<Item=Guild, Error=Error> {
self.request(Endpoint::new(
Method::POST,
String::from("/guilds"),
).json(opts))
}
pub fn leave_guild(&self, id: &Snowflake) -> impl Future<Item=(), Error=Error> {
self.request_empty(Endpoint::new(
Method::DELETE,
format!("/users/@me/guilds/{}", id.0),
))
}
pub fn get_voice_regions(&self) -> impl Future<Item=Vec<VoiceRegion>, Error=Error> {
self.request(Endpoint::new(
Method::GET,
String::from("/voice/regions"),
))
}
pub fn get_invite(&self, code: &str, member_counts: bool) -> impl Future<Item=Invite, Error=Error> {
self.request(Endpoint::new(
Method::GET,
format!("/invites/{}?with_counts={}", code, member_counts),
))
}
pub fn delete_invite(&self, code: &str) -> impl Future<Item=Invite, Error=Error> {
self.request(Endpoint::new(
Method::GET,
format!("/invites/{}", code),
))
}
pub fn request<T>(&self, mut endpt: Endpoint) -> Box<Future<Item=T, Error=Error> + Send>
where T: DeserializeOwned + Send + 'static
{
if let Some(ref rl) = self.ratelimiter {
let http = self.http.clone();
let base = self.base_url.clone();
Box::new(futures::future::loop_fn(Arc::clone(rl), move |ratelimit| {
let req_url = format!("{}{}", base, &endpt.url);
let route = Bucket::make_route(endpt.method.clone(), req_url.clone());
let mut req = http.request(endpt.method.clone(), &req_url)
.query(&endpt.query)
.json(&endpt.json);
if endpt.multipart.is_some() {
let form = endpt.multipart.take().unwrap();
req = req.multipart(form);
};
let limiter = Arc::clone(&ratelimit);
let limiter_2 = Arc::clone(&limiter);
ratelimit.lock().enqueue(route.clone())
.and_then(|_| req.send().from_err())
.and_then(move |resp| limiter.lock().handle_resp(route, resp))
.map(move |status| match status {
ResponseStatus::Success(resp) => Loop::Break(resp),
ResponseStatus::Ratelimited | ResponseStatus::ServerError => Loop::Continue(limiter_2)
})
}).and_then(|mut resp| resp.json().from_err()))
} else {
let req_url = format!("{}{}", self.base_url, &endpt.url);
let req = self.http.request(endpt.method.clone(), &req_url)
.query(&endpt.query)
.json(&endpt.json);
Box::new(req.send().map_err(Error::from)
.and_then(|mut resp| resp.json().from_err())
)
}
}
pub fn request_empty(&self, mut endpt: Endpoint) -> Box<Future<Item=(), Error=Error> + Send> {
if let Some(ref rl) = self.ratelimiter {
let http = self.http.clone();
let base = self.base_url.clone();
Box::new(futures::future::loop_fn(Arc::clone(rl), move |ratelimit| {
let req_url = format!("{}{}", base, &endpt.url);
let route = Bucket::make_route(endpt.method.clone(), req_url.clone());
let mut req = http.request(endpt.method.clone(), &req_url)
.query(&endpt.query)
.json(&endpt.json);
if endpt.multipart.is_some() {
let form = endpt.multipart.take().unwrap();
req = req.multipart(form);
};
let limiter = Arc::clone(&ratelimit);
let limiter_2 = Arc::clone(&limiter);
ratelimit.lock().enqueue(route.clone())
.and_then(|_| req.send().from_err())
.and_then(move |resp| limiter.lock().handle_resp(route, resp))
.map(move |status| match status {
ResponseStatus::Success(resp) => Loop::Break(resp),
ResponseStatus::Ratelimited | ResponseStatus::ServerError => Loop::Continue(limiter_2)
})
}).map(|_| ()))
} else {
let req_url = format!("{}{}", self.base_url, &endpt.url);
let req = self.http.request(endpt.method.clone(), &req_url)
.query(&endpt.query)
.json(&endpt.json);
Box::new(req.send().map_err(Error::from)
.map(|_| ())
)
}
}
}
#[derive(Debug)]
pub struct Endpoint {
url: String,
method: Method,
json: Option<Value>,
query: Option<Value>,
multipart: Option<Form>,
}
impl Endpoint {
pub fn new(method: Method, url: String) -> Self {
Self {
method,
url,
json: None,
query: None,
multipart: None,
}
}
pub fn json<T: Serialize>(mut self, payload: T) -> Endpoint {
match serde_json::to_value(payload) {
Ok(val) => self.json = Some(val),
Err(_) => self.json = None
};
self
}
pub fn query<T: Serialize>(mut self, payload: T) -> Endpoint {
match serde_json::to_value(payload) {
Ok(val) => self.query = Some(val),
Err(_) => self.query = None
};
self
}
pub fn multipart(mut self, payload: Form) -> Endpoint {
self.multipart = Some(payload);
self
}
}