use crate::{core::Core, request::CallbackAPIRequest};
use rocket::{
config::{Config, Environment},
http::Status,
State,
};
use rocket_contrib::json::Json;
use rvk::APIClient;
use std::sync::{Arc, Mutex};
const VK_OK: &'static str = "ok";
#[derive(Debug, Clone)]
pub struct Bot {
api: Arc<Mutex<APIClient>>,
confirmation_token: String,
group_id: i32,
secret: String,
port: u16,
core: Core,
}
impl Bot {
#[must_use = "the bot does nothing unless started via `.start()`"]
pub fn new(
vk_token: &str,
confirmation_token: &str,
group_id: i32,
secret: &str,
port: u16,
core: Core,
) -> Self {
Self {
api: Arc::new(Mutex::new(APIClient::new(vk_token.into()))),
confirmation_token: confirmation_token.into(),
group_id,
secret: secret.into(),
port,
core,
}
}
pub fn handle(&self, req: &CallbackAPIRequest) {
self.core.handle(req, self.api());
}
pub fn start(self) -> ! {
info!("starting bot...");
let err = rocket::custom(
Config::build(Environment::Production)
.address("0.0.0.0")
.port(self.port)
.unwrap(),
)
.mount("/", routes![post, get])
.manage(self)
.launch();
panic!("{}", err);
}
pub fn api(&self) -> Arc<Mutex<APIClient>> {
Arc::clone(&self.api)
}
pub fn confirmation_token(&self) -> &String {
&self.confirmation_token
}
pub fn group_id(&self) -> i32 {
self.group_id
}
pub fn secret(&self) -> &String {
&self.secret
}
}
#[get("/")]
fn get() -> Status {
debug!("received a GET request");
Status::MethodNotAllowed
}
#[post("/", format = "json", data = "<data>")]
fn post(data: Json<CallbackAPIRequest>, state: State<Bot>) -> Result<String, Status> {
let bot = &*state;
match &data {
x if x.secret() != bot.secret() => {
debug!("received a POST request with invalid `secret`");
Err(Status::Forbidden)
}
x if x.group_id() != bot.group_id() => {
debug!("received a POST request with invalid `group_id`");
Err(Status::Forbidden)
}
x if x.r#type() == "confirmation" => {
debug!("responded with confirmation token");
Ok(bot.confirmation_token().clone())
}
_ => {
bot.handle(&data);
Ok(VK_OK.into())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_returns_405() {
assert_eq!(get(), Status::MethodNotAllowed);
}
fn post_test(secret: &str, group_id: i32, event: &str) -> Result<String, Status> {
let rocket = rocket::ignite().manage(Bot::new(
"vk_token",
"confirmation_token",
1,
"secret",
12345,
Default::default(),
));
post(
Json(CallbackAPIRequest::new(
secret,
group_id,
event,
Default::default(),
)),
State::from(&rocket).unwrap(),
)
}
#[test]
fn post_invalid_secret_returns_403() {
assert_eq!(post_test("wrong_secret", 1, ""), Err(Status::Forbidden));
}
#[test]
fn post_invalid_group_id_returns_403() {
assert_eq!(post_test("secret", 1337, ""), Err(Status::Forbidden));
}
#[test]
fn post_confirmation_returns_confirmation_token() {
assert_eq!(
post_test("secret", 1, "confirmation"),
Ok("confirmation_token".to_string())
);
}
}