Docs and examples updates

This commit is contained in:
Abdulla Abdurakhmanov 2021-10-30 21:42:31 +02:00
parent 3821bad91b
commit 84a3058f63
9 changed files with 334 additions and 212 deletions

View file

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

View file

@ -9,13 +9,19 @@ Slack Morphism is a modern client library for Slack Web/Events API/Sockets Mode
Please follow to the official website: https://slack-rust.abdolence.dev
## Examples
https://github.com/abdolence/slack-morphism-rust/tree/master/src/examples/src
The example bot requires to work the following environment variables (from your Slack bot profile in api.slack.com):
- `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, `SLACK_BOT_SCOPE`, `SLACK_REDIRECT_HOST` - for OAuth
https://github.com/abdolence/slack-morphism-rust/tree/master/src/hyper/examples
The examples require to work the following environment variables (from your Slack bot profile in api.slack.com):
- `SLACK_TEST_TOKEN` - for Slack client example
- `SLACK_TEST_APP_TOKEN` - for Slack client with Socket Mode example
- `SLACK_SIGNING_SECRET` for all routes
- `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, `SLACK_BOT_SCOPE`, `SLACK_REDIRECT_HOST` - for OAuth routes for Events API example
- `SLACK_SIGNING_SECRET` for all routes for Events API example
To run example use with environment variables:
```
# SLACK_... cargo run --example <client|events_api_server|socket_mode>
```
Routes for this example are available on http://<your-host>:8080:

View file

@ -27,3 +27,11 @@ use slack_morphism_models::events::*; // Slack Events Models
use slack_morphism_hyper::*; // Hyper/Tokio client implementation
```
## Ready to use examples
-
- Slack Web API client
- Events API
- Slack Web API client with Socket Mode
You can find them on [github](https://github.com/abdolence/slack-morphism-rust/tree/master/src/hyper/examples)

View file

@ -1,25 +0,0 @@
[package]
name = "slack-morphism-examples"
version = "0.10.0"
authors = ["Abdulla Abdurakhmanov <abdulla@latestbit.com>"]
edition = "2018"
publish = false
[dependencies]
slack-morphism-models = { path = "../models", version = "^0.10.0"}
slack-morphism-hyper = { path = "../hyper", version = "^0.10.0"}
slack-morphism = { path = "../client", version = "^0.10.0"}
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_with = "1.4"
futures = "0.3"
tokio = { version = "1.0", features = ["full"] }
chrono = { version = "0.4", features = ["serde"] }
hyper = { version ="0.14", features = ["full"] }
log = "0.4"
fern = { version = "0.6", features = ["colored"] }
rsb_derive = "0.3"
http = "0.2"
[package.metadata.release]
disable-tag=true

View file

@ -1,80 +0,0 @@
use chrono::prelude::*;
use rsb_derive::Builder;
use slack_morphism_models::blocks::*;
use slack_morphism_models::*;
#[derive(Debug, Clone, Builder)]
pub struct WelcomeMessageTemplateParams {
pub user_id: SlackUserId,
}
impl SlackMessageTemplate for WelcomeMessageTemplateParams {
fn render_template(&self) -> SlackMessageContent {
SlackMessageContent::new()
.with_text(format!("Hey {}", self.user_id.to_slack_format()))
.with_blocks(slack_blocks![
some_into(
SlackSectionBlock::new()
.with_text(md!("Hey {}", self.user_id.to_slack_format()))
),
some_into(SlackDividerBlock::new()),
some_into(SlackContextBlock::new(slack_blocks![
some(md!("This is an example of block message")),
some(md!(
"Current time is: {}",
fmt_slack_date(
Local::now(),
SlackDateTimeFormats::DatePretty.to_string().as_str(),
None
)
))
])),
some_into(SlackDividerBlock::new()),
some_into(SlackImageBlock::new(
"https://www.gstatic.com/webp/gallery3/2_webp_ll.png".into(),
"Test Image".into()
)),
some_into(SlackActionsBlock::new(slack_blocks![some_into(
SlackBlockButtonElement::new(
"simple-message-button".into(),
pt!("Simple button text")
)
)]))
])
}
}
#[derive(Debug, Clone, Builder)]
pub struct SlackHomeNewsItem {
pub title: String,
pub body: String,
pub published: DateTime<Utc>,
}
#[derive(Debug, Clone, Builder)]
pub struct SlackHomeTabBlocksTemplateExample {
pub latest_news: Vec<SlackHomeNewsItem>,
pub user_id: SlackUserId,
}
impl SlackBlocksTemplate for SlackHomeTabBlocksTemplateExample {
fn render_template(&self) -> Vec<SlackBlock> {
slack_blocks![
some_into(
SlackSectionBlock::new()
.with_text(md!("Home tab for {}", self.user_id.to_slack_format()))
),
some_into(SlackContextBlock::new(slack_blocks![
some(md!("This is an example of home tab")),
some(md!(
"Current time is: {}",
fmt_slack_date(
Local::now(),
SlackDateTimeFormats::DatePretty.to_string().as_str(),
None
)
))
]))
]
}
}

View file

@ -45,5 +45,10 @@ http = "0.2"
tokio-tungstenite = { version = "0.15.0", features = ["rustls-tls"] }
rand = "0.8.4"
[dev-dependencies]
chrono = { version = "0.4", features = ["serde"] }
log = "0.4"
fern = { version = "0.6", features = ["colored"] }
[package.metadata.release]
disable-tag=true

View file

@ -0,0 +1,186 @@
use chrono::prelude::*;
use slack_morphism::prelude::*;
use slack_morphism_hyper::*;
use rsb_derive::Builder;
use std::time::Duration;
use futures::stream::BoxStream;
use futures::TryStreamExt;
async fn test_client() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = SlackClient::new(SlackClientHyperConnector::new());
let token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_TOKEN")?.into();
let token: SlackApiToken = SlackApiToken::new(token_value);
let session = client.open_session(&token);
println!("{:#?}", session);
let test: SlackApiTestResponse = session
.api_test(&SlackApiTestRequest::new().with_foo("Test".into()))
.await?;
println!("{:#?}", test);
let message = WelcomeMessageTemplateParams::new("".into());
let post_chat_req =
SlackApiChatPostMessageRequest::new("#general".into(), message.render_template());
let post_chat_resp = session.chat_post_message(&post_chat_req).await?;
println!("post chat resp: {:#?}", &post_chat_resp);
let scroller_req: SlackApiUsersListRequest = SlackApiUsersListRequest::new().with_limit(1);
let scroller = scroller_req.scroller();
let mut resp_stream: BoxStream<ClientResult<SlackApiUsersListResponse>> =
scroller.to_stream(&session);
while let Some(item) = resp_stream.try_next().await? {
println!("res: {:#?}", item.members);
}
let collected_members: Vec<SlackUser> = scroller
.collect_items_stream(&session, Duration::from_millis(1000))
.await?;
println!("collected res: {:#?}", collected_members);
let mut items_stream = scroller.to_items_stream(&session);
while let Some(items) = items_stream.try_next().await? {
println!("res: {:#?}", items);
}
let mut items_throttled_stream =
scroller.to_items_throttled_stream(&session, Duration::from_millis(500));
while let Some(items) = items_throttled_stream.try_next().await? {
println!("res: {:#?}", items);
}
Ok(())
}
#[derive(Debug, Clone, Builder)]
pub struct WelcomeMessageTemplateParams {
pub user_id: SlackUserId,
}
impl SlackMessageTemplate for WelcomeMessageTemplateParams {
fn render_template(&self) -> SlackMessageContent {
SlackMessageContent::new()
.with_text(format!("Hey {}", self.user_id.to_slack_format()))
.with_blocks(slack_blocks![
some_into(
SlackSectionBlock::new()
.with_text(md!("Hey {}", self.user_id.to_slack_format()))
),
some_into(SlackDividerBlock::new()),
some_into(SlackContextBlock::new(slack_blocks![
some(md!("This is an example of block message")),
some(md!(
"Current time is: {}",
fmt_slack_date(
Local::now(),
SlackDateTimeFormats::DatePretty.to_string().as_str(),
None
)
))
])),
some_into(SlackDividerBlock::new()),
some_into(SlackImageBlock::new(
"https://www.gstatic.com/webp/gallery3/2_webp_ll.png".into(),
"Test Image".into()
)),
some_into(SlackActionsBlock::new(slack_blocks![some_into(
SlackBlockButtonElement::new(
"simple-message-button".into(),
pt!("Simple button text")
)
)]))
])
}
}
#[derive(Debug, Clone, Builder)]
pub struct SlackHomeNewsItem {
pub title: String,
pub body: String,
pub published: DateTime<Utc>,
}
#[derive(Debug, Clone, Builder)]
pub struct SlackHomeTabBlocksTemplateExample {
pub latest_news: Vec<SlackHomeNewsItem>,
pub user_id: SlackUserId,
}
impl SlackBlocksTemplate for SlackHomeTabBlocksTemplateExample {
fn render_template(&self) -> Vec<SlackBlock> {
slack_blocks![
some_into(
SlackSectionBlock::new()
.with_text(md!("Home tab for {}", self.user_id.to_slack_format()))
),
some_into(SlackContextBlock::new(slack_blocks![
some(md!("This is an example of home tab")),
some(md!(
"Current time is: {}",
fmt_slack_date(
Local::now(),
SlackDateTimeFormats::DatePretty.to_string().as_str(),
None
)
))
]))
]
}
}
fn init_log() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use fern::colors::{Color, ColoredLevelConfig};
let colors_level = ColoredLevelConfig::new()
.info(Color::Green)
.warn(Color::Magenta);
fern::Dispatch::new()
// Perform allocation-free log formatting
.format(move |out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}{}\x1B[0m",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
colors_level.color(record.level()),
format_args!(
"\x1B[{}m",
colors_level.get_color(&record.level()).to_fg_str()
),
message
))
})
// Add blanket level filter -
.level(log::LevelFilter::Debug)
// - and per-module overrides
.level_for("slack_morphism", log::LevelFilter::Trace)
.level_for("slack_morphism_hyper", log::LevelFilter::Trace)
.level_for("hyper", log::LevelFilter::Info)
.level_for("rustls", log::LevelFilter::Info)
// Output to stdout, files, and other Dispatch configurations
.chain(std::io::stdout())
// Apply globally
.apply()?;
Ok(())
}
pub fn config_env_var(name: &str) -> Result<String, String> {
std::env::var(name).map_err(|e| format!("{}: {}", name, e))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
init_log()?;
test_client().await?;
Ok(())
}

View file

@ -1,70 +1,12 @@
use slack_morphism::prelude::*;
use slack_morphism_hyper::*;
use futures::stream::BoxStream;
use futures::TryStreamExt;
use std::time::Duration;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response};
use log::*;
use std::sync::{Arc, RwLock};
mod templates;
use templates::*;
#[allow(dead_code)]
async fn test_client() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = SlackClient::new(SlackClientHyperConnector::new());
let token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_TOKEN")?.into();
let token: SlackApiToken = SlackApiToken::new(token_value);
let session = client.open_session(&token);
println!("{:#?}", session);
let test: SlackApiTestResponse = session
.api_test(&SlackApiTestRequest::new().with_foo("Test".into()))
.await?;
println!("{:#?}", test);
let message = WelcomeMessageTemplateParams::new("".into());
let post_chat_req =
SlackApiChatPostMessageRequest::new("#general".into(), message.render_template());
let post_chat_resp = session.chat_post_message(&post_chat_req).await?;
println!("post chat resp: {:#?}", &post_chat_resp);
let scroller_req: SlackApiUsersListRequest = SlackApiUsersListRequest::new().with_limit(1);
let scroller = scroller_req.scroller();
let mut resp_stream: BoxStream<ClientResult<SlackApiUsersListResponse>> =
scroller.to_stream(&session);
while let Some(item) = resp_stream.try_next().await? {
println!("res: {:#?}", item.members);
}
let collected_members: Vec<SlackUser> = scroller
.collect_items_stream(&session, Duration::from_millis(1000))
.await?;
println!("collected res: {:#?}", collected_members);
let mut items_stream = scroller.to_items_stream(&session);
while let Some(items) = items_stream.try_next().await? {
println!("res: {:#?}", items);
}
let mut items_throttled_stream =
scroller.to_items_throttled_stream(&session, Duration::from_millis(500));
while let Some(items) = items_throttled_stream.try_next().await? {
println!("res: {:#?}", items);
}
Ok(())
}
async fn test_oauth_install_function(
resp: SlackOAuthV2AccessTokenResponse,
_client: Arc<SlackHyperClient>,
@ -115,14 +57,6 @@ async fn test_command_events_function(
))
}
async fn test_push_events_sm_function(
event: SlackPushEventCallback,
_client: Arc<SlackHyperClient>,
_states: Arc<RwLock<SlackClientEventsUserStateStorage>>,
) {
println!("{:#?}", event);
}
fn test_error_handler(
err: Box<dyn std::error::Error + Send + Sync>,
_client: Arc<SlackHyperClient>,
@ -137,7 +71,6 @@ fn test_error_handler(
#[derive(Debug)]
struct UserStateExample(u64);
#[allow(dead_code)]
async fn test_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client: Arc<SlackHyperClient> =
Arc::new(SlackClient::new(SlackClientHyperConnector::new()));
@ -219,38 +152,6 @@ async fn test_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
})
}
#[allow(dead_code)]
async fn test_client_with_socket_mode() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Arc::new(SlackClient::new(SlackClientHyperConnector::new()));
let socket_mode_callbacks = SlackSocketModeListenerCallbacks::new()
.with_command_events(test_command_events_function)
.with_interaction_events(test_interaction_events_function)
.with_interaction_events(test_interaction_events_function)
.with_push_events(test_push_events_sm_function);
let listener_environment = Arc::new(
SlackClientEventsListenerEnvironment::new(client.clone())
.with_error_handler(test_error_handler)
.with_user_state(UserStateExample(0)),
);
let socket_mode_listener = SlackClientSocketModeListener::new(
&SlackClientSocketModeConfig::new(),
listener_environment.clone(),
socket_mode_callbacks,
);
let app_token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_APP_TOKEN")?.into();
let app_token: SlackApiToken = SlackApiToken::new(app_token_value);
socket_mode_listener.listen_for(&app_token).await?;
socket_mode_listener.serve().await;
Ok(())
}
fn init_log() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use fern::colors::{Color, ColoredLevelConfig};
@ -296,8 +197,7 @@ pub fn config_env_var(name: &str) -> Result<String, String> {
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
init_log()?;
//test_server().await?;
test_client_with_socket_mode().await?;
test_server().await?;
Ok(())
}

View file

@ -0,0 +1,123 @@
use slack_morphism::prelude::*;
use slack_morphism_hyper::*;
use std::sync::{Arc, RwLock};
async fn test_interaction_events_function(
event: SlackInteractionEvent,
_client: Arc<SlackHyperClient>,
_states: Arc<RwLock<SlackClientEventsUserStateStorage>>,
) {
println!("{:#?}", event);
}
async fn test_command_events_function(
event: SlackCommandEvent,
_client: Arc<SlackHyperClient>,
_states: Arc<RwLock<SlackClientEventsUserStateStorage>>,
) -> Result<SlackCommandEventResponse, Box<dyn std::error::Error + Send + Sync>> {
println!("{:#?}", event);
Ok(SlackCommandEventResponse::new(
SlackMessageContent::new().with_text("Working on it".into()),
))
}
async fn test_push_events_sm_function(
event: SlackPushEventCallback,
_client: Arc<SlackHyperClient>,
_states: Arc<RwLock<SlackClientEventsUserStateStorage>>,
) {
println!("{:#?}", event);
}
fn test_error_handler(
err: Box<dyn std::error::Error + Send + Sync>,
_client: Arc<SlackHyperClient>,
_states: Arc<RwLock<SlackClientEventsUserStateStorage>>,
) -> http::StatusCode {
println!("{:#?}", err);
// Defines what we return Slack server
http::StatusCode::BAD_REQUEST
}
#[allow(dead_code)]
async fn test_client_with_socket_mode() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = Arc::new(SlackClient::new(SlackClientHyperConnector::new()));
let socket_mode_callbacks = SlackSocketModeListenerCallbacks::new()
.with_command_events(test_command_events_function)
.with_interaction_events(test_interaction_events_function)
.with_interaction_events(test_interaction_events_function)
.with_push_events(test_push_events_sm_function);
let listener_environment = Arc::new(
SlackClientEventsListenerEnvironment::new(client.clone())
.with_error_handler(test_error_handler),
);
let socket_mode_listener = SlackClientSocketModeListener::new(
&SlackClientSocketModeConfig::new(),
listener_environment.clone(),
socket_mode_callbacks,
);
let app_token_value: SlackApiTokenValue = config_env_var("SLACK_TEST_APP_TOKEN")?.into();
let app_token: SlackApiToken = SlackApiToken::new(app_token_value);
socket_mode_listener.listen_for(&app_token).await?;
socket_mode_listener.serve().await;
Ok(())
}
fn init_log() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use fern::colors::{Color, ColoredLevelConfig};
let colors_level = ColoredLevelConfig::new()
.info(Color::Green)
.warn(Color::Magenta);
fern::Dispatch::new()
// Perform allocation-free log formatting
.format(move |out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}{}\x1B[0m",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
colors_level.color(record.level()),
format_args!(
"\x1B[{}m",
colors_level.get_color(&record.level()).to_fg_str()
),
message
))
})
// Add blanket level filter -
.level(log::LevelFilter::Debug)
// - and per-module overrides
.level_for("slack_morphism", log::LevelFilter::Trace)
.level_for("slack_morphism_hyper", log::LevelFilter::Trace)
.level_for("hyper", log::LevelFilter::Info)
.level_for("rustls", log::LevelFilter::Info)
// Output to stdout, files, and other Dispatch configurations
.chain(std::io::stdout())
// Apply globally
.apply()?;
Ok(())
}
pub fn config_env_var(name: &str) -> Result<String, String> {
std::env::var(name).map_err(|e| format!("{}: {}", name, e))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
init_log()?;
test_client_with_socket_mode().await?;
Ok(())
}