vkapi 0.2.0

Rust create for interacting with VK API
Documentation
use futures::future::Future;
use reqwest::multipart::Form; // FIXME: Add non-blocking

use crate::longpoll::Longpoll;
use crate::params::Params;
use crate::types::destination::Destination;
use crate::types::file::File;

/// Request type - used for make request to VK API
type Request = String;

/// VK structure, used for call api method, use longpoll api and etc
pub struct VK<'a> {
    access_token: Option<String>,
    api_version: &'a str,
    language: &'a str,
}

impl<'a> VK<'a> {
    /// Create new instance of `VK` struct
    ///
    /// # Arguments:
    /// * `api_version` - pick up one [here](https://vk.com/dev/versions)
    /// * `language` - [here](https://vk.com/dev/api_requests)
    pub fn new(api_version: &'a str, language: &'a str) -> Self {
        Self {
            access_token: None,
            api_version,
            language,
        }
    }

    /// Set access token
    pub fn set_access_token(&mut self, token: String) {
        self.access_token = Some(token);
    }

    /// Get a reference to token
    pub fn get_access_token(&self) -> &String {
        self.access_token.as_ref().unwrap()
    }

    ///  This methond starts to longpolling, It's sad, but as for now there're ways to stop it (but I'm gonna make it)
    /// # Arguments:
    /// * `group_id` - your group ID
    /// * `wait` - Maximal time to waiting, max value is 90
    /// * `callback` - closure which have 1 argument: [event](https://vk.com/dev/groups_events)
    pub async fn start_longpoll(
        &self,
        group_id: u32,
        wait: u16,
        callback: Box<dyn Fn(&json::JsonValue) -> ()>,
    ) {
        let access_token = self.access_token.clone();
        let longpoll = Longpoll::new(
            group_id,
            wait,
            access_token,
            self.api_version.to_string().clone(),
            self.language.to_string().clone(),
        );
        longpoll.start(callback).await;
    }

    /// Use if you want to upload any file
    /// # Arguments:
    /// [`file`] - is File struct
    ///
    /// # Return:
    /// this method returns JsonValue like this:
    ///  ``` [{"id":457239436,"album_id":-64,"owner_id":-142102660,"user_id":100,"sizes":[{"type":"s","url":"https://sun9-64.userapi.com/c857724/v857724964/1cd30a/OkprPbgIA4M.jpg","width":75,"height":42},{"type":"m","url":"https://sun9-11.userapi.com/c857724/v857724964/1cd30b/zf7PALt9LiQ.jpg","width":130,"height":73},{"type":"x","url":"https://sun9-42.userapi.com/c857724/v857724964/1cd30c/QkU_GWrWrtg.jpg",":"o","url":"https://sun9-55.userapi.com/c857724/v857724964/1cd30f/VuJoCsfzNhQ.jpg","width":130,"height":87},{"type":"p","url":"https://sun9-7.userapi.com/c857724/v857724964/1cd310/03M7NFSwldw.jpg","width":200,"height":133},{"type":"q","url":"https://sun9-27.userapi.com/c857724/v857724964/1cd311/E-l93bSWpSk.jpg","width":320,"height":213},{"type":"r","url":"https://sun9-61.userapi.com/c857724/v857724964/1cd312/0aqWUY6M9jE.jpg","width":510,"height":340}],"text":"","date":1587293486,"access_key":"cb0b2221048b3d8021"}]     ```
    /// More details about return [here](https://vk.com/dev/upload_files)
    pub async fn upload(&self, file: File, params: Params) -> Result<json::JsonValue, String> {
        // TODO: Avoid unwrap
        let mut form = Form::new();
        let mut params = Params::new(); // TODO: Add additional params
        let upload = self
            .request(file.destination.pick_method_load(), &mut params)
            .await?;

        let upload_url = upload["response"]["upload_url"]
            .as_str()
            .unwrap()
            .to_owned();
        let param_for_sending = file.destination.pick_param();
        form = form.part(
            param_for_sending,
            crate::utils::file_to_part(file.path.clone()).await,
        );
        let method_for_saving = file.destination.pick_method_save();

        let client = reqwest::Client::new();
        let res = client
            .post(&upload_url)
            .multipart(form)
            .send()
            .await
            .unwrap()
            .text()
            .await
            .unwrap();
        let mut upload_info = json::parse(&res).unwrap();

        if method_for_saving != "none" {
            let mut params = Params::new();
            crate::utils::make_params_for_saving_photo(&mut params, upload_info);
            let mut saved_file = self.request(method_for_saving, &mut params).await?;
            return Ok(saved_file.remove("response"));
        }

        Ok(upload_info.remove("response"))
    }

    /// Used for request API
    /// # Arguments:
    /// * `method` - all methods are listed [here](https://vk.com/dev/methods)
    /// * `params` - params to call API. use `Params::new()` and then `params.add()`
    pub async fn request(
        &self,
        method: &str,
        params: &mut Params,
    ) -> std::result::Result<json::JsonValue, String> {
        let request_url = self.build_request(method, params);
        let response = reqwest::get(&request_url)
            .await
            .unwrap()
            .text()
            .await
            .unwrap();
        let parsed = json::parse(&response).unwrap(); // TODO: Get rid of unwrap (need help)
        if !parsed["error"].is_null() {
            return Err(parsed["error"]["error_msg"].as_str().unwrap().to_owned());
        }

        Ok(parsed)
    }

    /// ** I'm not recommend use it **
    /// You can use `request` but without constructing VK instance
    pub async fn request_public(
        // TODO: get rid of shitcode
        method: &str,
        params: &mut Params,
        access_token: &Option<String>,
        api_version: &str,
        language: &str,
    ) -> std::result::Result<json::JsonValue, String> {
        let request_url = {
            if access_token.is_none() {
                panic!("Access token is empty! Did you forget to call set_access_token() ?");
            }
            let access_token = access_token.as_ref().unwrap();
            let result = format!(
                "https://api.vk.com/method/{}?{}access_token={}&v={}&lang={}",
                method,
                &params.concat(),
                &access_token,
                api_version,
                language
            );
            result
        };

        let response = reqwest::get(&request_url)
            .await
            .unwrap()
            .text()
            .await
            .unwrap();
        let parsed = json::parse(&response).unwrap(); // TODO: Get rid of unwrap (need help)
        if !parsed["error"].is_null() {
            return Err(parsed["error"]["error_msg"].as_str().unwrap().to_owned());
        }
        Ok(parsed)
    }

    /// Used for direct auth
    /// * `login` - email or phone number
    /// * `password` - Hm..what it's supposed to be
    pub async fn direct_auth(
        &mut self,
        login: &str,
        password: &str,
    ) -> Result<(), json::JsonValue> {
        let url = format!("https://api.vk.com/oauth/token?grant_type=password&client_id=2274003&scope=notify,photos,friends,audio,video,notes,pages,docs,status,questions,offers,wall,groups,messages,notifications,stats,ads,offline&client_secret=hHbZxrka2uZ6jB1inYsH&username={}&password={}", login, password);
        let response = reqwest::get(&url).await.unwrap().text().await.unwrap();
        let parsed = json::parse(&response).unwrap();
        if parsed["access_token"].is_null() {
            panic!("Authencication has failed! We doesnt support 2fa right now");
        }

        let access_token = parsed["access_token"].as_str().unwrap();
        let access_token = access_token.to_string();
        self.set_access_token(access_token);

        Ok(())
    }

    fn build_request(&self, method: &str, params: &mut Params) -> Request {
        let access_token = self.access_token.as_ref();
        if access_token.is_none() {
            panic!("Access token is empty! Did you forget to call set_access_token() ?");
        }

        let access_token = access_token.unwrap();
        let result = format!(
            "https://api.vk.com/method/{}?{}access_token={}&v={}&lang={}",
            method,
            &params.concat(),
            &access_token,
            &self.api_version,
            &self.language
        );
        result
    }
}