diff --git a/Cargo.toml b/Cargo.toml index 73dfda4..4c1b4ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ reqwest = { version = "0.10.4", features = ["blocking", "stream", "json"] } tokio = { version = "0.2", features = ["full"] } serde = { version = "1.0.110", features = ["derive"] } serde_json = "1.0.53" +urlencoding = "1" futures = "0.3" anyhow = "1.0.31" tokio-util = "0.3.1" diff --git a/examples/keyboard/Cargo.toml b/examples/keyboard/Cargo.toml new file mode 100644 index 0000000..b4e3a4b --- /dev/null +++ b/examples/keyboard/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "keyboard" +version = "0.1.0" +authors = ["PatriotRossii "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "0.2", features = ["full"] } +vkapi = { path = "../../" } +rand = "0" \ No newline at end of file diff --git a/examples/keyboard/src/main.rs b/examples/keyboard/src/main.rs new file mode 100644 index 0000000..032b93d --- /dev/null +++ b/examples/keyboard/src/main.rs @@ -0,0 +1,40 @@ +use vkapi::{ + VK, param, + types::keyboard::{Keyboard, Button, ButtonColor, TextButton, VKPayButton, LocationButton} +}; +use rand::Rng; + +#[tokio::main] +async fn main() { + let access_token = std::env::var("ACCESS_TOKEN").expect("Failed to get ACCESS_TOKEN environment variable"); + let peer_id = std::env::var("PEER_ID").expect("Failed to get PEER_ID environment variable"); + + let buttons = vec![ + vec![ + Button::new(LocationButton::new(""), None).unwrap(), + ], + vec![ + Button::new(VKPayButton::new("", "action=transfer-to-group&group_id=1&aid=10"), None).unwrap(), + ], + vec![ + Button::new(TextButton::new("Negative", ""), Some(ButtonColor::Negative)).unwrap(), + Button::new(TextButton::new("Positive", ""), Some(ButtonColor::Positive)).unwrap(), + Button::new(TextButton::new("Primary", ""), Some(ButtonColor::Primary)).unwrap(), + ] + ]; + let keyboard = Keyboard::new(buttons, true, None).expect("Failed to build a keyboard"); + println!("{}", keyboard.to_string()); + + let mut rng = rand::thread_rng(); + let random_id: i64 = rng.gen_range(0, 50_000_000); + let mut params = param! {"peer_id" => &peer_id, "message" => "Сообщение", "keyboard" => &keyboard.to_string(), "random_id" => &format!("{}", random_id)}; + + let mut vk_api = vkapi::VK::new("5.130".to_string(), "en".to_string()); + vk_api.set_access_token(access_token); + + let response = vk_api + .request::("messages.send", &mut params) + .await + .unwrap(); + println!("{}", response); +} diff --git a/src/types/destination.rs b/src/types/destination.rs index ab5ce4a..e6bb4b2 100644 --- a/src/types/destination.rs +++ b/src/types/destination.rs @@ -81,25 +81,24 @@ impl Destination { } } - /// __TODO__: Replace `String` to `&str` - pub fn pick_param(&self) -> String { + pub fn pick_param(&self) -> &'static str { match self { - Album => "file1".to_owned(), - Wall => "photo".to_owned(), - OwnerPhoto => "photo".to_owned(), - Message => "photo".to_owned(), - ChatPhoto => "file".to_owned(), - MarketPhoto => "file".to_owned(), - MarketAlbum => "file".to_owned(), - Audio => "file".to_owned(), - Video => "video_file".to_owned(), - Document => "file".to_owned(), - DocumentWall => "file".to_owned(), - DocumentMessage => "file".to_owned(), - Cover => "photo".to_owned(), - AudioMessage => "file".to_owned(), - StoryPhoto => "file".to_owned(), - StoryVideo => "video_file".to_owned(), + Album => "file1", + Wall => "photo", + OwnerPhoto => "photo", + Message => "photo", + ChatPhoto => "file", + MarketPhoto => "file", + MarketAlbum => "file", + Audio => "file", + Video => "video_file", + Document => "file", + DocumentWall => "file", + DocumentMessage => "file", + Cover => "photo", + AudioMessage => "file", + StoryPhoto => "file", + StoryVideo => "video_file", } } } diff --git a/src/types/keyboard.rs b/src/types/keyboard.rs index 1b40f2b..9f308f3 100644 --- a/src/types/keyboard.rs +++ b/src/types/keyboard.rs @@ -1 +1,406 @@ -// TODO: Add macro to make Keyboard +use urlencoding::encode; +use serde::{Serialize}; +use std::fmt; + +#[derive(Debug, Serialize)] +#[serde(rename_all="lowercase")] +pub enum ButtonColor { + Primary, + Secondary, + Negative, + Positive +} + +#[derive(Debug, Serialize)] +pub struct TextButton { + r#type: String, + label: String, + payload: String, +} + +impl TextButton { + pub fn new(label: S1, payload: S2) -> Self + where + S1: Into, + S2: Into, + { + Self { + r#type: "text".to_owned(), + label: label.into(), + payload: payload.into(), + } + } + pub fn label(mut self, label: S) -> Self + where + S: Into + { + self.label = label.into(); + self + } + pub fn payload(mut self, payload: S) -> Self + where + S: Into + { + self.payload = payload.into(); + self + } +} + +impl From for Action { + fn from(val: TextButton) -> Self { + Self::Text(val) + } +} + +#[derive(Debug, Serialize)] +pub struct OpenLinkButton { + r#type: String, + link: String, + label: String, + payload: String, +} + +impl OpenLinkButton { + pub fn new(link: S1, label: S2, payload: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into + { + Self { + r#type: "open_link".to_owned(), + link: link.into(), + label: label.into(), + payload: payload.into(), + } + } + pub fn link(mut self, link: S) -> Self + where + S: Into + { + self.link = link.into(); + self + } + pub fn label(mut self, label: S) -> Self + where + S: Into + { + self.label = label.into(); + self + } + pub fn payload(mut self, payload: S) -> Self + where + S: Into + { + self.payload = payload.into(); + self + } +} + +impl From for Action { + fn from(val: OpenLinkButton) -> Self { + Self::OpenLink(val) + } +} + +#[derive(Debug, Serialize)] +pub struct LocationButton { + r#type: String, + payload: String, +} + +impl LocationButton { + pub fn new(payload: S) -> Self + where + S: Into + { + Self { + r#type: "location".to_owned(), + payload: payload.into(), + } + } + pub fn payload(mut self, payload: S) -> Self + where + S: Into + { + self.payload = payload.into(); + self + } +} + +impl From for Action { + fn from(val: LocationButton) -> Self { + Self::Location(val) + } +} + +#[derive(Debug, Serialize)] +pub struct VKPayButton { + r#type: String, + payload: String, + hash: String, +} + +impl VKPayButton { + pub fn new(payload: S1, hash: S2) -> Self + where + S1: Into, + S2: Into + { + Self { + r#type: "vkpay".to_owned(), + payload: payload.into(), + hash: hash.into(), + } + } + pub fn payload(mut self, payload: S) -> Self + where + S: Into + { + self.payload = payload.into(); + self + } + pub fn hash(mut self, hash: S) -> Self + where + S: Into + { + self.hash = hash.into(); + self + } +} + +impl From for Action { + fn from(val: VKPayButton) -> Self { + Self::VKPay(val) + } +} + +#[derive(Debug, Serialize)] +pub struct VKAppsButton { + r#type: String, + + app_id: i64, + owner_id: i64, + + payload: String, + label: String, + hash: String, +} + +impl VKAppsButton { + pub fn new(app_id: i64, owner_id: i64, payload: S1, label: S2, hash: S3) -> Self + where + S1: Into, + S2: Into, + S3: Into + { + Self { + r#type: "open_app".to_owned(), + + app_id, + owner_id, + + payload: payload.into(), + label: label.into(), + hash: hash.into(), + } + } + pub fn app_id(mut self, app_id: i64) -> Self { + self.app_id = app_id; + self + } + pub fn owner_id(mut self, owner_id: i64) -> Self { + self.owner_id = owner_id; + self + } + pub fn payload(mut self, payload: S) -> Self + where + S: Into + { + self.payload = payload.into(); + self + } + pub fn label(mut self, label: S) -> Self + where + S: Into + { + self.label = label.into(); + self + } + pub fn hash(mut self, hash: S) -> Self + where + S: Into + { + self.hash = hash.into(); + self + } +} + +impl From for Action { + fn from(val: VKAppsButton) -> Self { + Self::VKApps(val) + } +} + +#[derive(Debug, Serialize)] +pub struct CallbackButton { + r#type: String, + label: String, + payload: String, +} + +impl CallbackButton { + pub fn new(label: S1, payload: S2) -> Self + where + S1: Into, + S2: Into + { + Self { + r#type: "text".to_owned(), + label: label.into(), + payload: payload.into(), + } + } + pub fn payload(mut self, payload: S) -> Self + where + S: Into + { + self.payload = payload.into(); + self + } + pub fn label(mut self, label: S) -> Self + where + S: Into + { + self.label = label.into(); + self + } +} + +impl From for Action { + fn from(val: CallbackButton) -> Self { + Action::Callback(val) + } +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum Action { + Text(TextButton), + OpenLink(OpenLinkButton), + Location(LocationButton), + VKPay(VKPayButton), + VKApps(VKAppsButton), + Callback(CallbackButton), +} + +#[derive(Debug, Serialize)] +pub struct Button { + action: Action, + #[serde(skip_serializing_if = "Option::is_none")] + color: Option, +} + +impl Button { + fn validate(action: &Action, color: &Option) -> bool { + match *color { + None => true, + Some(_) => { + matches!(*action, Action::Text(_) | Action::Callback(_)) + } + } + } + pub fn new(action: T, color: Option) -> Result + where T: + Into + { + let action = action.into(); + if Self::validate(&action, &color) { + Ok(Self { action, color }) + } else { + Err("Color can be used only with text or callback button") + } + } + pub fn action(mut self, action: T) -> Self + where T: + Into + { + let action = action.into(); + if Self::validate(&action, &self.color) { + self.action = action; + } + self + } + pub fn color(mut self, color: Option) -> Self { + if Self::validate(&self.action, &color) { + self.color = color; + } + self + } +} + +#[derive(Debug, Serialize)] +pub struct Keyboard { + #[serde(skip_serializing_if = "Option::is_none")] + one_time: Option, + buttons: Vec>, + inline: bool, +} + +impl Keyboard { + fn validate(inline: bool, one_time: &Option) -> bool { + !(matches!(one_time, &Option::Some(_)) && inline) + } + pub fn new(buttons: I1, inline: bool, one_time: Option) -> Result + where + I1: Into>, + I2: Into> + { + if Self::validate(inline, &one_time) { + Ok(Self { + one_time, + buttons: buttons.into().into_iter().map(Into::into).collect(), + inline, + }) + } else { + Err("One_time field is not available for inline keyboard") + } + } + pub fn one_time(mut self, one_time: Option) -> Self { + if Self::validate(self.inline, &one_time) { + self.one_time = one_time; + } + self + } + pub fn buttons(mut self, buttons: I1) -> Self + where + I1: Into>, + I2: Into> + { + self.buttons = buttons.into().into_iter().map(Into::into).collect(); + self + } + pub fn append_row(mut self, buttons: Vec