Slack client module

This commit is contained in:
Abdulla Abdurakhmanov 2020-05-22 14:14:06 +03:00
parent ebbe7714c7
commit c16d33c0a9
10 changed files with 241 additions and 41 deletions

View file

@ -2,5 +2,6 @@
members = [
"src/models",
"src/client",
"src/examples"
]

22
src/client/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "slack-morphism-client"
version = "0.1.0"
authors = ["Abdulla Abdurakhmanov <abdulla@latestbit.com>"]
edition = "2018"
[lib]
name = "slack_morphism_client"
path = "src/lib.rs"
[dependencies]
slack-morphism-models = { path = "../models", version = "0.1.0" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_with = "1.4"
rvs_derive = "0.1"
rsb_derive = "0.2"
hyper = "0.13"
tokio = { version = "0.2", features = ["full"] }
url = "2.1"
futures = "0.3"
bytes = "0.5"

View file

@ -0,0 +1 @@

160
src/client/src/lib.rs Normal file
View file

@ -0,0 +1,160 @@
pub mod chat;
use bytes::buf::BufExt as _;
use hyper::client::*;
use hyper::{Body, Request, Uri};
use rsb_derive::Builder;
use url::Url;
#[derive(Debug, PartialEq, Clone, Builder)]
pub struct SlackApiToken {
value: String,
workspace_id: Option<String>,
scope: Option<String>,
}
#[derive(Debug)]
pub struct SlackClient {
connector: Client<HttpConnector>,
}
#[derive(Debug)]
pub struct SlackClientSession<'a> {
client: &'a SlackClient,
token: SlackApiToken,
}
pub type ClientResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
impl SlackClient {
const SLACK_API_URI_STR: &'static str = "https://slack.com/api";
fn create_method_uri_path(method_relative_uri: &str) -> String {
format!("{}/{}", SlackClient::SLACK_API_URI_STR, method_relative_uri)
}
fn create_url(url_str: &String) -> Uri {
url_str.parse().unwrap()
}
fn create_url_with_params<PT, TS>(url_str: &String, params: PT) -> Uri
where
PT: std::iter::IntoIterator<Item = (TS, Option<TS>)>,
TS: std::string::ToString,
{
let url_query_params: Vec<(String, String)> = params
.into_iter()
.map(|(k, vo)| vo.map(|v| (k.to_string(), v.to_string())))
.flatten()
.collect();
Url::parse_with_params(url_str.as_str(), url_query_params)
.unwrap()
.as_str()
.parse()
.unwrap()
}
pub fn new() -> Self {
SlackClient {
connector: Client::new(),
}
}
pub async fn send_webapi_request<RS>(&self, request: Request<Body>) -> ClientResult<RS>
where
RS: for<'de> serde::de::Deserialize<'de>,
{
let http_res = self.connector.request(request).await?;
//let http_status = http_res.status();
let http_body = hyper::body::aggregate(http_res).await?;
let http_reader = http_body.reader();
let decoded_body = serde_json::from_reader(http_reader)?;
Ok(decoded_body)
}
pub fn open_session(&self, token: &SlackApiToken) -> SlackClientSession {
SlackClientSession {
client: &self,
token: token.clone(),
}
}
pub async fn get<RS, PT, TS>(&self, method_relative_uri: &str, params: PT) -> ClientResult<RS>
where
RS: for<'de> serde::de::Deserialize<'de>,
PT: std::iter::IntoIterator<Item = (TS, Option<TS>)>,
TS: std::string::ToString,
{
let full_uri = SlackClient::create_url_with_params(
&SlackClient::create_method_uri_path(&method_relative_uri),
params,
);
let body = self
.send_webapi_request(Request::get(full_uri).body(Body::empty())?)
.await?;
Ok(body)
}
}
impl<'a> SlackClientSession<'_> {
fn setup_token_auth_header(
&self,
request_builder: hyper::http::request::Builder,
) -> hyper::http::request::Builder {
let token_header_value = format!("Bearer {}", self.token.value);
request_builder.header("Authorization", token_header_value)
}
pub async fn get<RS, PT, TS>(&self, method_relative_uri: &str, params: PT) -> ClientResult<RS>
where
RS: for<'de> serde::de::Deserialize<'de>,
PT: std::iter::IntoIterator<Item = (TS, Option<TS>)>,
TS: std::string::ToString,
{
let full_uri = SlackClient::create_url_with_params(
&SlackClient::create_method_uri_path(&method_relative_uri),
params,
);
let body = self
.client
.send_webapi_request(
self.setup_token_auth_header(Request::get(full_uri))
.body(Body::empty())?,
)
.await?;
Ok(body)
}
pub async fn post<RQ, RS, PT, TS>(
&self,
method_relative_uri: &str,
request: RQ,
) -> ClientResult<RS>
where
RQ: serde::ser::Serialize,
RS: for<'de> serde::de::Deserialize<'de>,
PT: std::iter::IntoIterator<Item = (TS, Option<TS>)>,
TS: std::string::ToString,
{
let full_uri =
SlackClient::create_url(&SlackClient::create_method_uri_path(&method_relative_uri));
let post_json = serde_json::to_string(&request)?;
let response_body = self
.client
.send_webapi_request(
self.setup_token_auth_header(Request::get(full_uri))
.header("content-type", "application/json")
.body(Body::from(post_json))?,
)
.await?;
Ok(response_body)
}
}

View file

@ -6,6 +6,8 @@ edition = "2018"
[dependencies]
slack-morphism-models = { path = "../models", version = "0.1.0" }
slack-morphism-client = { path = "../client", version = "0.1.0" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_with = "1.4"
futures = "0.3"

View file

@ -1,6 +1,10 @@
use slack::blocks::kit::*;
use slack::*;
use slack_morphism_models as slack;
use slack_morphism_client as slack_client;
use slack_morphism_models as slack_models;
use futures::executor::block_on;
use slack_client::*;
use slack_models::blocks::kit::*;
use slack_models::*;
fn main() {
let sb: SlackSectionBlock = SlackSectionBlock::new().with_block_id("test".into());
@ -23,18 +27,29 @@ fn main() {
.into(),
);
let context_block: SlackContextBlock = SlackContextBlock::new(
slack_blocks! [
some(SlackBlockImageElement::new("http://example.net/img1".into(), "text 1".into())),
some(SlackBlockImageElement::new("http://example.net/img2".into(), "text 2".into()))
]
);
let context_block: SlackContextBlock = SlackContextBlock::new(slack_blocks![
some(SlackBlockImageElement::new(
"http://example.net/img1".into(),
"text 1".into()
)),
some(SlackBlockImageElement::new(
"http://example.net/img2".into(),
"text 2".into()
))
]);
let blocks: Vec<SlackBlock> =
slack_blocks! [
some ( section_block ),
optionally( !sb_ser.is_empty() => context_block)
];
let blocks: Vec<SlackBlock> = slack_blocks! [
some ( section_block ),
optionally( !sb_ser.is_empty() => context_block)
];
println!("{:#?}", blocks);
let client = SlackClient::new();
let token: SlackApiToken = SlackApiToken::new("test".into());
let session = client.open_session(&token);
println!("{:#?}", session);
let test: slack_client::ClientResult<String> =
block_on(session.get("", vec![].into_iter())).unwrap();
}

View file

@ -1,15 +1,15 @@
use chrono::serde::ts_milliseconds;
use chrono::{DateTime, TimeZone, Utc};
use rvs_derive::ValueStruct;
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc, TimeZone};
use chrono::serde::ts_milliseconds;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, ValueStruct)]
pub struct SlackTs(pub String);
impl SlackTs {
pub fn to_date_time(&self) -> Result<DateTime<Utc>,std::num::ParseIntError> {
let parts : Vec<&str> = self.value().split('.').collect();
let ts_int : i64 = parts[0].parse()?;
pub fn to_date_time(&self) -> Result<DateTime<Utc>, std::num::ParseIntError> {
let parts: Vec<&str> = self.value().split('.').collect();
let ts_int: i64 = parts[0].parse()?;
Ok(Utc.timestamp_millis(ts_int))
}
}

View file

@ -13,13 +13,13 @@ pub enum SlackPushEvent {
#[serde(rename = "event_callback")]
EventCallback(SlackEventCallback),
#[serde(rename = "app_rate_limited")]
AppRateLimited(SlackAppRateLimitedEvent)
AppRateLimited(SlackAppRateLimitedEvent),
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackUrlVerificationEvent {
pub challenge: String
pub challenge: String,
}
#[skip_serializing_none]
@ -27,7 +27,7 @@ pub struct SlackUrlVerificationEvent {
pub struct SlackAppRateLimitedEvent {
pub team_id: String,
pub minute_rate_limited: SlackDateTime,
pub api_app_id: String
pub api_app_id: String,
}
#[skip_serializing_none]
@ -38,44 +38,43 @@ pub struct SlackEventCallback {
pub event: SlackEventCallbackBody,
pub event_id: SlackEventId,
pub event_time: SlackDateTime,
pub authed_users: Option<Vec<SlackUserId>>
pub authed_users: Option<Vec<SlackUserId>>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SlackEventCallbackBody {
#[serde(rename = "message")]
Message(SlackMessageEvent)
Message(SlackMessageEvent),
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
pub struct SlackMessageEvent {
#[serde(flatten)]
pub origin : SlackMessageOrigin,
pub origin: SlackMessageOrigin,
#[serde(flatten)]
pub content : SlackMessageContent,
pub subtype : Option<SlackMessageEventType>,
pub hidden: Option<bool>
pub content: SlackMessageContent,
pub subtype: Option<SlackMessageEventType>,
pub hidden: Option<bool>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum SlackMessageEventType {
#[serde(rename="bot_message")]
#[serde(rename = "bot_message")]
BotMessage,
#[serde(rename="me_message")]
#[serde(rename = "me_message")]
MeMessage,
#[serde(rename="channel_join")]
#[serde(rename = "channel_join")]
ChannelJoin,
#[serde(rename="bot_add")]
#[serde(rename = "bot_add")]
BotAdd,
#[serde(rename="bot_remove")]
#[serde(rename = "bot_remove")]
BotRemove,
#[serde(rename="channel_topic")]
#[serde(rename = "channel_topic")]
ChannelTopic,
#[serde(rename="channel_purpose")]
#[serde(rename = "channel_purpose")]
ChannelPurpose,
#[serde(rename="channel_name")]
ChannelName
}
#[serde(rename = "channel_name")]
ChannelName,
}

View file

@ -1,4 +1,4 @@
pub mod blocks;
pub mod common;
pub mod events;
pub mod messages;
pub mod events;

View file

@ -1,8 +1,8 @@
use crate::blocks::kit::*;
use crate::common::*;
use rsb_derive::Builder;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use rsb_derive::Builder;
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]