Git Product home page Git Product logo

slack-morphism-rust's Introduction

Cargo tests and formatting security audit

Slack Morphism for Rust

Slack Morphism is a modern client library for Slack Web/Events API/Socket Mode and Block Kit.

Documentation

Please follow to the official website: https://slack-rust.abdolence.dev.

Examples

https://github.com/abdolence/slack-morphism-rust/tree/master/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_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|axum_events_api_server|socket_mode> --all-features

Routes for this example are available on http://:8080:

  • /auth/install - to begin OAuth installation
  • /auth/callback - a callback endpoint for Slack OAuth profile config
  • /push - for Slack Push Events
  • /interaction - for Slack Interaction Events
  • /command - for Slack Command Events

Testing Events API with ngrok

For development/testing purposes you can use ngrok:

ngrok http 8080

and copy the URL it gives for you to the example parameters for SLACK_REDIRECT_HOST.

Example testing with ngrok:

SLACK_CLIENT_ID=<your-client-id> \
SLACK_CLIENT_SECRET=<your-client-secret> \
SLACK_BOT_SCOPE=app_mentions:read,incoming-webhook \
SLACK_REDIRECT_HOST=https://<your-ngrok-url>.ngrok.io \
SLACK_SIGNING_SECRET=<your-signing-secret> \
cargo run --example events_api_server  --all-features

Licence

Apache Software License (ASL)

Author

Abdulla Abdurakhmanov

slack-morphism-rust's People

Contributors

abdolence avatar arcsecant avatar augustoccesar avatar bbktsk avatar bmalicoat avatar caquillo07 avatar dax avatar dutycycle avatar fkrauthan avatar fussybeaver avatar git-bruh avatar jichaos avatar jtmichelson avatar kawaemon avatar matthewgapp avatar murilobsd avatar ncb000gt avatar noxasaxon avatar palladinium avatar reharri7 avatar renovate-bot avatar renovate[bot] avatar sedyn avatar shantanuraj avatar siketyan avatar soiha avatar turuturu avatar x3ro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

slack-morphism-rust's Issues

Missing rustfmt.toml?

Hi Abdulla,

Thanks for your work on this project!

I was working on a fork of the project to see if I can make it independent of tokio (and maybe hyper) and noticed that the pre-push hook fails for me on an unchanged project. Could you check if you have a custom format config that is missing from git?

Re-export `http::status::StatusCode`

Thanks for the work on slack-morphism-rust! Recently, when dependabot was updating dependencies on my slack bot, it tried to update http from 0.2 to 1.0 which breaks the compilation.

Which made me wonder if http::status::StatusCode could be re-exported so I don't have to take a direct dependency on http?

I noticed you already have a bot submitting PR updates for dependencies, and I was just wondering how it compares to dependabot?

How can I update my activity? ?

Hello,

Thank you for your wrapper.
I'd like to add some automation to my activity (The status message and the indicator if I'm online or offline)

Do I need to keep open a client to be considered as online? Or can I use only the API/SDK?
And how can I set my activity and the presence indicator using the SDK? I can't find it in the documentation

Kind regards!

[Security] Workflow gh-pages.yml is using vulnerable action actions/checkout

The workflow gh-pages.yml is referencing action actions/checkout using references v1. However this reference is missing the commit a6747255bd19d7a757dbdda8c654a9f84db19839 which may contain fix to the some vulnerability.
The vulnerability fix that is missing by actions version could be related to:
(1) CVE fix
(2) upgrade of vulnerable dependency
(3) fix to secret leak and others.
Please consider to update the reference to the action.

SlackApiScrollableRequest across await

The scroller method in trait SlackApiScrollableRequest returns a SlackApiResponseScroller which is not Send. This means you cannot paginate from inside a thread (or I am doing it wrong). The minimal example from your website, but inside a tokio thread:

use slack_morphism::*;
use slack_morphism::api::*;
use slack_morphism_hyper::*;

async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let hyper_connector = SlackClientHyperConnector::new();
    let client = SlackClient::new(hyper_connector);

    let token_value: SlackApiTokenValue = "xoxb-89.....".into();
    let token: SlackApiToken = SlackApiToken::new(token_value);
    let session = client.open_session(&token);

    let scroller_req: SlackApiUsersListRequest = SlackApiUsersListRequest::new().with_limit(5);

    let scroller = scroller_req.scroller();

    use futures::TryStreamExt;
    let mut items_stream = scroller.to_items_stream(&session);
    while let Some(items) = items_stream.try_next().await? {
        println!("users batch: {:#?}", items);
    }

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    tokio::spawn(async move {
        example().await
    }).await?
}

yields

$ cargo build
    Updating crates.io index
   Compiling slack-send v0.1.0 (/home/hey/platform/slack-send)
error: future cannot be sent between threads safely
   --> src/main.rs:28:5
    |
28  |     tokio::spawn(async move {
    |     ^^^^^^^^^^^^ future created by async block is not `Send`
    |
   ::: /home/hey/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.9.0/src/task/spawn.rs:127:21
    |
127 |         T: Future + Send + 'static,
    |                     ---- required by this bound in `tokio::spawn`
    |
    = help: the trait `std::marker::Send` is not implemented for `dyn slack_morphism::SlackApiResponseScroller<slack_morphism_hyper::SlackClientHyperConnector, CursorType = SlackCursorId, ResponseItemType = SlackUser, ResponseType = SlackApiUsersListResponse>`
note: future is not `Send` as this value is used across an await
   --> src/main.rs:19:29
    |
15  |     let scroller = scroller_req.scroller();
    |         -------- has type `Box<dyn slack_morphism::SlackApiResponseScroller<slack_morphism_hyper::SlackClientHyperConnector, CursorType = SlackCursorId, ResponseItemType = SlackUser, ResponseType = SlackApiUsersListResponse>>` which is not `Send`
...
19  |     while let Some(items) = items_stream.try_next().await? {
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `scroller` maybe used later
...
24  | }
    | - `scroller` is later dropped here

The program compiles if I mark the return value of the scroller method as Send and Sync. I deem that this is safe since all of its members are Send and Sync, but I am a rust novice.

  1. Is there a reason not to mark this as Send and Sync?
  2. If not, do you want me to PR the changes? (also to the extension in the hyper module).

`Options Load URL` payload types for request and response

Hello,

Thanks for the crate, it makes Slack API navigation and learning a lot easier!

I cannot find the types to use to deserialize and respond to messages that arrive to the Options Load URL, which is used for select menus that need to populate its variants from an external source. Is it somewhere I missed?

I thought about migrating all my app to Socket Mode, but this is not a valid choice for me as I won’t be able to publish it

Apps using Socket Mode are not currently allowed in the public Slack App Directory.

Example on how to create lists

Couldn't get this to work with the md! macro. It would be nice to have an example included in client on how to achieve that.

`SlackAppMentionEvent` doesn't include `edited` field

Hi,

The SlackAppMentionEvent struct doesn't include the edited field, so it's not currently possible to detect if an AppMention is being triggered due to a user editing their message to the bot.

While not explicitly called out in the app_mention documentation, the field does exist on an edited bot mention, just like it does in the message event and the app_mention documentation indicates it exists with the following comment:

app_mention events are just like other message events sent over the Events API, but their type indicates app_mention.

I had a look through the other fields on the SlackAppMentionEvent struct just in case the edited field was tucked away somewhere else, but it doesn't appear to be.

Would it be possible to have the edited field added to the event?

Thanks.

Upload binary files

Thanks for writing this library!

I'd like to use files_upload to upload a binary file, like a PDF or an image. But SlackApiFilesUploadRequest.content is Option<String> instead of Option<Vec<u8>>. Any suggested workaround?

It seems simple enough to switch the type of that field, but it would break compatibility.

Missing element types for actions block

Hi!

I'm trying to use an actions block but the SlackActionBlockElement enum seems to be missing a few of the possible elements. In my case I'm trying to use a static_select but I think there are more missing, such as users_select.

Any chance those could be added?

Thanks for a really nice crate!

Windows feature flag

Hey! :)

When trying to build a project with this crate as a dependency on MS Windows, I get an error:

error: failed to run custom build command for `signal-hook v0.3.15`

signal-hook appears to be a crate dealing with Unix signals, which is obviously not necessary on Microsoft's OS. I'm not a big fan of it, but from time to time, I have to use it, and when I do, I can't use this library. That makes me sad.

Looking into the issue superficially gives me the impression that its a problem with the use of the hyper and/or axum crates. When I only use slack-morphism-rust as a dependency without any features enabled, I can compile. With any or both of said features, I get the error above. Maybe it's possible to feature flag something there?

Looking forward to build a Slack app with your crate!

feature: dedicated Error type for ClientResult

Error handling is much nicer now with callbacks falling back to the error_handler, thanks for that.

It'd also be great ergonomics if the errors from the slack client have a dedicated type, because it allows consumers to write error handling enums that handle those errors. At the moment the slack client will always return a ClientResult, which just has a boxed trait object std error: https://docs.rs/slack-morphism/0.14.0/slack_morphism/type.ClientResult.html

For example:

client.open_session(...).conversations_create(...).await? // <- Result<SlackApiConversationsCreateResponse, Box<dyn std::error::Error>> instead of, say Result<SlackApiConversationsCreateResponse, SlackClientError>

Possibly missing field "user" in message_changed event

After upgrading from 1.14.5 to 1.16.0, we are experiencing an error:

Slack listener error occurred: SlackClientProtocolError { json_error: Error("missing field `user`", line: 0, column: 0), json_body: Some(...)

It seems slack-morphism-rust expects SlackMessageEventEdited::user is not optional, but actually it is omitted when the message was edited by a bot.

Related PR: #226

This is an example of the payload which occur the error:

{
  "token": "[REDACTED]",
  "team_id": "[REDACTED]",
  "enterprise_id": "[REDACTED]",
  "context_team_id": "[REDACTED]",
  "context_enterprise_id": "[REDACTED]",
  "api_app_id": "[REDACTED]",
  "event": {
    "type": "message",
    "subtype": "message_changed",
    "message": {
      "type": "message",
      "subtype": "bot_message",
      "text": "[REDACTED]",
      "username": "[REDACTED]",
      "bot_id": "[REDACTED]",
      "app_id": "[REDACTED]",
      "blocks": [
        {
          "type": "section",
          "block_id": "FF1Ng",
          "text": {
            "type": "mrkdwn",
            "text": "[REDACTED]",
            "verbatim": false
          }
        }
      ],
      "edited": {
        "user": "[REDACTED]",
        "ts": "1702549421.000000"
      },
      "ts": "1702549413.998139",
      "source_team": "[REDACTED]",
      "user_team": "[REDACTED]"
    },
    "previous_message": {
      "type": "message",
      "subtype": "bot_message",
      "text": "[REDACTED]",
      "ts": "1702549413.998139",
      "username": "[REDACTED]",
      "bot_id": "[REDACTED]",
      "app_id": "[REDACTED]",
      "blocks": [
        {
          "type": "section",
          "block_id": "jT48G",
          "text": {
            "type": "mrkdwn",
            "text": "[REDACTED]",
            "verbatim": false
          }
        }
      ]
    },
    "channel": "[REDACTED]",
    "hidden": true,
    "ts": "1702549421.000900",
    "event_ts": "1702549421.000900",
    "channel_type": "channel"
  },
  "type": "event_callback",
  "event_id": "[REDACTED]",
  "event_time": 1702549421,
  "authorizations": [
    {
      "enterprise_id": "[REDACTED]",
      "team_id": "[REDACTED]",
      "user_id": "[REDACTED]",
      "is_bot": false,
      "is_enterprise_install": false
    }
  ],
  "is_ext_shared_channel": false,
  "event_context": "[REDACTED]"
}

Error handler for socket mode callbacks

Not sure if it's entirely related to socket mode, but I'm finding that errors bubbled up in the callback functions get swallowed without a log message (unless I handle it inline):

async fn slack_command_events_fn(
    event: SlackCommandEvent,
    client: Arc<SlackHyperClient>,
    states: Arc<RwLock<SlackClientEventsUserStateStorage>>,
) -> Result<SlackCommandEventResponse, Box<dyn std::error::Error + Send + Sync>> {
            match something_that_might_fail {
                Ok(_) => (),
                Err(e) => {
                    error!("Failed: {:#?}", e);
                    return Err(e.into());
                }
            };
}

I thought maybe the error_handler defined on the SlackClientEventsListenerEnvironment would trigger, but looking through the code that's more for the initial socket connection?

De-serializing views requires optional parameters to be present

Hey! Thanks for building this library, it's made playing around with the Slack API from Rust a lot easier 🚀

What I've been trying to do is open a modal from my app. I tried to create a view in the Block kit builder, and then deserialize it into the model that you provide in your application, like so:

const VIEW_JSON: &str = include_str!("modal.json");
let view: SlackModalView = serde_json::from_str(viewstr).unwrap();

The modal JSON generated by the block kit builder is the following:

{
	"title": {
		"type": "plain_text",
		"text": "Add info to feedback",
		"emoji": true
	},
	"submit": {
		"type": "plain_text",
		"text": "Save",
		"emoji": true
	},
	"type": "modal",
	"blocks": [
		{
			"type": "input",
			"element": {
				"type": "plain_text_input"
			},
			"label": {
				"type": "plain_text",
				"text": "Label",
				"emoji": true
			}
		}
	]
}

Now, I would expect this to de-serialize without error, but instead it seems to require the fields marked as #[serde(with = "serde_with::rust::string_empty_as_none")] to be present. If I understand the Slack API docs correctly, these should be optional fields. An example error is

Error("missing field `callback_id`", line: 17, column: 1)

There's a good chance I'm just holding it wrong™, since this is the first time I'm working with the Slack API. I'd be happy to contribute an example once I figure out how this work 😅

How to use SlackClientHttpConnector for SlackClient?

Discussed in #166

Originally posted by ox4ka5h January 26, 2023

let client = SlackClient::new(<SlackClientHttpConnector>);
let token_value: SlackApiTokenValue = "xoxb---".into();
let token: SlackApiToken = SlackApiToken::new(token_value);
let session = client.open_session(&token);

let post_chat_req =
 SlackApiChatPostMessageRequest::new("#reports".into(),
        SlackMessageContent::new().with_text("Hey there! pinged from rust".into())
);

let post_chat_resp = session.chat_post_message(&post_chat_req).await?;

What's the difference between event types

In the docs here https://slack-rust.abdolence.dev/socket-mode.html there are 3 "test_" functions that each use a different type for the event argument. What are the differences between them?
For example, if I want to catch a message event (when a message is being sent in the channel), should I use SlackPushEventCallback -> SlackEventCallbackBody -> SlackMessageEvent or SlackInteractionMessageActionEvent -> SlackHistoryMessage -> SlackMessageContent?
Same question for catching app mentions (when someone mentions the app like @bot_name).
What are the differences between interaction, command, and push events?

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

What is the recommended way to share a slack session?

I am building a slack bot using Slack Morphism with the Axum web framework and because I am only using one token, I'd like to share the client session around.

Currently I have not found a way to share the session in an arc because the "client" is dropped while borrowed, so I have been creating a struct to hold the Client & Token together, and creating the session in each parent thread as needed.

Also with the recent Slack Morphism changes, I've now needed to include hyper & hyper-rustls in my cargo.toml just to be able to specify the new type of a slack client and slack session. Is there a way around this?

demo repo demonstrating these issues: https://github.com/noxasaxon/slack-axum-demo

Thanks!

ProtocolError when issuing update message

I am writing a tool that needs to update an existing message through the life of a deployment. I first create the post then loop and update the message using the ts from the post but I am getting a ProtocolError saying the ts is missing (or perhaps it's somethin else). My code:

            let client = SlackClient::new(SlackClientHyperConnector::new());
            let token = SlackApiToken::new(token.into());
            let session = client.open_session(&token);
            // TODO: paginate...
            let channels = session
                .conversations_list(&SlackApiConversationsListRequest::new().opt_limit(Some(500)))
                .await
                .unwrap();
            let channel = channels
                .channels
                .into_iter()
                .find(|c| c.name_normalized.as_deref() == Some("cousteau-development"))
                .unwrap()
                .id;

            // Initial message to have something to update
            println!("initial message");
            let chat_message = SlackApiChatPostMessageRequest::new(
                channel.clone(),
                render_template(&environment, DeploymentState::MigrationPending),
            );
            let post_result = session.chat_post_message(&chat_message).await.unwrap();

            loop {
                match recv.recv().await {
                    Some(deployment_state) => {
                        println!(
                            "handling state: {:?} {:?}",
                            deployment_state,
                            post_result.ts.clone()
                        );
                        let chat_message = SlackApiChatUpdateRequest {
                            channel: channel.clone(),
                            // content: render_template(&environment, deployment_state),
                            content: SlackMessageContent {
                                text: Some("hi".into()),
                                blocks: Some(vec![]),
                            },
                            ts: post_result.ts.clone(),
                            as_user: Some(true),
                            parse: None,
                            link_names: None,
                        };
                        let message = serde_json::to_string_pretty(&chat_message).unwrap();
                        println!("chat_message: {}", message);
                        let post_result = session.chat_update(&chat_message).await;
                        if let Err(e) = post_result {
                            println!("yikes: {:#?}", e);
                            break;
                        }
                    }
                    None => {
                        println!("got nothing...");
                        break;
                    }
                }
            }
            eyre::Result::Ok(())

and runnin this code yields:

running 1 test
sending...
sending...
new client
initial message
looping
handling state: MigrationPending SlackTs("1641424787.006000")
chat_message: {
  "channel": "C02SFPRNCLR",
  "text": "hi",
  "blocks": [],
  "ts": "1641424787.006000",
  "as_user": true
}
sending...
yikes: ProtocolError(
    SlackClientProtocolError {
        json_error: Error("missing field `ts`", line: 1, column: 615),
        json_body: Some(
            "{\"ok\":true,\"channel\":\"C02SFPRNCLR\",\"ts\":\"1641424787.006000\",\"text\":\"hi\",\"message\":{\"bot_id\":\"B02S38LH4FL\",\"type\":\"message\",\"text\":\"hi\",\"user\":\"U02S39PKH6F\",\"team\":\"T045P84S5\",\"bot_profile\":{\"id\":\"B02S38LH4FL\",\"app_id\":\"A02S39FSVSP\",\"name\":\"Cousteau\",\"icons\":{\"image_36\":\"https:\\/\\/a.slack-edge.com\\/80588\\/img\\/plugins\\/app\\/bot_36.png\",\"image_48\":\"https:\\/\\/a.slack-edge.com\\/80588\\/img\\/plugins\\/app\\/bot_48.png\",\"image_72\":\"https:\\/\\/a.slack-edge.com\\/80588\\/img\\/plugins\\/app\\/service_72.png\"},\"deleted\":false,\"updated\":1640672681,\"team_id\":\"T045P84S5\"},\"edited\":{\"user\":\"B02S38LH4FL\",\"ts\":\"1641424787.000000\"}}}",
        ),
    },
)

Missing event types: `block_suggestion` UrlEncoded payload POST from slack and a the required response for populating a dropdown

I'm working on getting a PR through, but before i forget i wanted to drop a note here for future users.

image

and heres a scrubbed example of what the URL post is from slack (when used in a View Modal). note the type is block_suggestion and its sent as a url encoded params (Form extractor in Axum):

{
    "payload": {
        "type": "block_suggestion",
        "user": {
            "id": "<user_id>",
            "username": "<name>",
            "name": "<name>",
            "team_id": "<team_id>"
        },
        "container": {
            "type": "view",
            "view_id": "<view_id>"
        },
        "api_app_id": "<api_app_id>",
        "token": "<token>",
        "action_id": "<action_id>",
        "block_id": "<block_id>",
        "value": "test",
        "team": {
            "id": "<team_id>",
            "domain": "<domain>",
            "enterprise_id": "<enterprise_id>",
            "enterprise_name": "<enterprise_name>"
        },
        "enterprise": {
            "id": "<ent_id>",
            "name": "ent_name>"
        },
        "is_enterprise_install": false,
        "view": {
            ...<view_obj>
        }
    }
}

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

cargo
Cargo.toml
  • serde 1.0
  • serde_json 1.0
  • serde_with 3.7
  • rvstruct 0.3
  • rsb_derive 0.5
  • futures 0.3
  • futures-util 0.3
  • futures-locks 0.7
  • base64 0.22
  • hex 0.4
  • tracing 0.1
  • ring 0.17
  • lazy_static 1.4
  • http 1.1
  • async-trait 0.1
  • bytes 1
  • rand 0.8
  • async-recursion 1.0
  • mime 0.3
  • mime_guess 2
  • chrono 0.4
  • url 2.5
  • http-body-util 0.1
  • hyper 1.2
  • hyper-util 0.1
  • tokio 1
  • tokio-stream 0.1
  • hyper-rustls 0.26
  • tokio-tungstenite 0.21.0
  • axum 0.7
  • tower 0.4
  • cargo-husky 1
  • cargo-audit 0.20
  • tracing-subscriber 0.3
  • hyper-proxy2 0.1
  • hyper 1.2
  • tokio 1
  • signal-hook 0.3
  • signal-hook-tokio 0.3
  • ctrlc 3.4
github-actions
.github/workflows/gh-pages.yml
  • actions/checkout v4
  • peaceiris/actions-mdbook v1
  • peaceiris/actions-gh-pages v3
.github/workflows/security-audit.yml
  • actions/checkout v4
.github/workflows/tests.yml
  • actions/checkout v4
.github/workflows/windows-build.yml
  • actions/checkout v4
  • windows 2022

  • Check this box to trigger a request for Renovate to run again on this repository

Error on reaction_added events

json_error: Error("unknown variant `reaction_added`, expected one of `message`, `app_home_opened`, `app_mention`, `app_uninstalled`, `link_shared`, `emoji_changed`, `member_joined_channel`, `member_left_channel`, `channel_created`, `channel_deleted`, `channel_archive`, `channel_rename`, `channel_unarchive`, `team_join`, `file_created`, `file_change`, `file_deleted`, `file_shared`, `file_unshared`, `file_public`", line: 0, column: 0),

There's probably other events that aren't handled, but this is just one I came across.

Cannot re-use `SlackMessageContent` from Events /w Web API

If I am writing an echo bot (ie sends identical messages back when receiving messages) then I cannot re-use the SlackMessageContent struct I receive from the Events API, ie

                content: SlackMessageContent {
                    text: Some(
                        "p",
                    ),
                    blocks: Some(
                        [
                            RichText,
                        ],
                    ),
                },

results in

    ApiError(
        SlackClientApiError {
            code: "invalid_blocks",
            warnings: None,
            http_response_body: Some(
                "{\"ok\":false,\"error\":\"invalid_blocks\",\"errors\":[\"missing required field: elements [json-pointer:\\/blocks\\/0]\"],\"response_metadata\":{\"messages\":[\"[ERROR] missing required field: elements [json-pointer:\\/blocks\\/0]\"]}}",
            ),
        },
    ),

Snippet of sample code (its an extension of the example)

...
                    listener.push_events_service_fn(
                        thread_push_events_config,
                        test_push_events_function,
                    ),
...
async fn test_push_events_function(event: SlackPushEvent, _client: Arc<SlackHyperClient>) {
    println!("{:#?}", event);
    if let SlackPushEvent::EventCallback(callback) = event {
        if let SlackEventCallbackBody::Message(message) = callback.event {
            if let Some(channel_type) = message.origin.channel_type {
                if channel_type == SlackChannelType("im".to_owned()) {
                    let resp = send_message(
                        &message
                            .origin
                            .channel
                            .expect("Message didn't have a channel"),
                        &message.content,
                    )
                    .await; // TODO catch failures here and log them
                }
            }
        }
    }
}


async fn send_message(
    channel: &SlackChannelId,
    content: &SlackMessageContent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    println!("triggered");
    let request = SlackApiChatPostMessageRequest::new(channel.clone(), content.clone());

    let hyper_connector = SlackClientHyperConnector::new();
    let client = SlackClient::new(hyper_connector);
    let token_value: SlackApiTokenValue = config_env_var("SLACK_BOT_TOKEN")?.into();
    let token: SlackApiToken = SlackApiToken::new(token_value);
    let session = client.open_session(&token);

    let response = session.chat_post_message(&request).await?;
    println!("chat_post_message response: {:#?}", response);
    Ok(())
}

`SlackEventsApiMiddlewareService` loses all information from the original request

Hi,

I've started using this crate in a project and for the most part if works great! The only issue I've noticed is in the Axum middleware: it looks like after decoding/verifying the incoming request, it creates a new empty Request and only bothers setting the decoded body in it. In doing so, all other information about the original request is lost i.e. request_uri, extensions, headers... It would be great to preserve this information (in particular any extension).

Error when using example

Just running the example get me this error:
[2020-09-01][18:44:28][test][INFO] Loading server: 127.0.0.1:8080
Error: NotPresent

Any idea ?
Thanks

Allow dynamic Slack API base URL

For testing purposes (ie. testing with a mock server), I would like to be able to inject a dynamically computed Slack API base URL.

Instead of using the hardcoded SLACK_API_URI_STR base URL, while building a SlackClientHttpConnector, I would like to inject the mock URL instead so that I can control what is returned to the Slack client.

What would be the best way to do it?

Message change event parsing is broken

Missing fields from SlackMessageEvent struct

On SlackMessageEvent the edited field is None when it should contain the
edited message's id in ts and user

pub edited: Option<SlackMessageEventEdited>,

The debug value for the struct I get is

SlackMessageEvent {
	origin: SlackMessageOrigin {
		ts: SlackTs("1701742037.000300"),
		channel: Some(SlackChannelId("CXXXXXXXXXX")),
		channel_type: Some(SlackChannelType("group")),
		thread_ts: None,
		client_msg_id: None
	},
	content: Some(SlackMessageContent {
		text: None,
		blocks: None,
		attachments: None,
		upload: None,
		files: None,
		reactions: None
	}),
	sender: SlackMessageSender {
		user: None,
		bot_id: None, 
		username: None, 
		display_as_bot: None
	},
	subtype: Some(MessageChanged),
	hidden: Some(true),
	edited: None,
	deleted_ts: None
}

trying a different echo server to inspect the request payload I see the field is present

{
  "token": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "team_id": "TXXXXXXXXXX",
  "context_team_id": "TXXXXXXXXXX",
  "context_enterprise_id": null,
  "api_app_id": "AXXXXXXXXXX",
  "event": {
    "type": "message",
    "subtype": "message_changed",
    "message": {
      "client_msg_id": "000000000000000000000000000000000000",
      "type": "message",
      "text": "hi!",
      "user": "UXXXXXXXXXX",
      "blocks": [
        {
          "type": "rich_text",
          "block_id": "wcwS3",
          "elements": [
            {
              "type": "rich_text_section",
              "elements": [
                {
                  "type": "text",
                  "text": "hi!"
                }
              ]
            }
          ]
        }
      ],
      "team": "TXXXXXXXXXX",
      "edited": {
        "user": "UXXXXXXXXXX",
        "ts": "1701743154.000000"
      },
      "ts": "1701735043.989889",
      "source_team": "TXXXXXXXXXX",
      "user_team": "TXXXXXXXXXX"
    },
    "previous_message": {
      "client_msg_id": "000000000000000000000000000000000000",
      "type": "message",
      "text": "hey!",
      "user": "UXXXXXXXXXX",
      "ts": "1701735043.989889",
      "blocks": [
        {
          "type": "rich_text",
          "block_id": "zUXnE",
          "elements": [
            {
              "type": "rich_text_section",
              "elements": [
                {
                  "type": "text",
                  "text": "hey!"
                }
              ]
            }
          ]
        }
      ],
      "team": "TXXXXXXXXXX",
      "edited": {
        "user": "UXXXXXXXXXX",
        "ts": "1701742890.000000"
      }
    },
    "channel": "CXXXXXXXXXX",
    "hidden": true,
    "ts": "1701743154.000500",
    "event_ts": "1701743154.000500",
    "channel_type": "group"
  },
  "type": "event_callback",
  "event_id": "EXXXXXXXXXXX",
  "event_time": 1701743154,
  "authorizations": [
    {
      "enterprise_id": null,
      "team_id": "TXXXXXXXXXX",
      "user_id": "UXXXXXXXXXX",
      "is_bot": true,
      "is_enterprise_install": false
    }
  ],
  "is_ext_shared_channel": false,
  "event_context": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}

I guess the edited field is nested under message which is missing in the struct here.

Context

  • https://api.slack.com/events/message/message_changed
    {
      "type": "message",
      "subtype": "message_changed",
      "hidden": true,
      "channel": "C123ABC456",
      "ts": "1358878755.000001",
      "message": {
          "type": "message",
          "user": "U123ABC456",
          "text": "Hello, world!",
          "ts": "1355517523.000005",
          "edited": {
              "user": "U123ABC456",
              "ts": "1358878755.000001"
          }
      }
    }

ps. thanks for all the work, it works great

Proposal: Organising type names and re-exports for v2

In this crate, may symbols are re-exported everywhere. This may causes namespace pollution. For example, the root module re-exports 152 structs and 6 enums. This is not common in Rust crates. I think symbols should be imported from the separated namespaces. How do you think about this? Before releasing v2, should we remove unnecessary re-exports and minimise the duplicated importing paths?

Using blocking push events functions

Is there a way that I can pass a blocking function (i.e. not async) somehow to SlackSocketModeListenerCallbacks in with_push_events()?
Currently the asyncness forces me to turn almost all my code into async and that is not my intention. It causes weird behaviours of the bot replying several times to the same request etc.

Missing `app_mention` in the model

I'm experimenting /w the example and added app_mention bot event. When I @ my bot (echo-bot) I get the following error

SlackClientProtocolError {
    json_error: Error("unknown variant `app_mention`, expected `message` or `app_home_opened`", line: 0, column: 0),
    http_response_body: "{\"token\":\"fYGMuMwItDR0DHT70bXofHoW\",\"team_id\":\"T01RJKRAW3A\",\"api_app_id\":\"A01R68WN84U\",\"event\":{\"client_msg_id\":\"22f15c93-03fb-4b0a-946f-88944e18d853\",\"type\":\"app_mention\",\"text\":\"<@U01R0BG5K6X> I mentioned echo bot\",\"user\":\"U01R7DVQXCH\",\"ts\":\"1615501392.000800\",\"team\":\"T01RJKRAW3A\",\"blocks\":[{\"type\":\"rich_text\",\"block_id\":\"eduC\",\"elements\":[{\"type\":\"rich_text_section\",\"elements\":[{\"type\":\"user\",\"user_id\":\"U01R0BG5K6X\"},{\"type\":\"text\",\"text\":\" I mentioned echo bot\"}]}]}],\"channel\":\"C01QV1E7TCK\",\"event_ts\":\"1615501392.000800\"},\"type\":\"event_callback\",\"event_id\":\"Ev01RQ3W7XLG\",\"event_time\":1615501392,\"authorizations\":[{\"enterprise_id\":null,\"team_id\":\"T01RJKRAW3A\",\"user_id\":\"U01R0BG5K6X\",\"is_bot\":true,\"is_enterprise_install\":false}],\"is_ext_shared_channel\":false,\"event_context\":\"1-app_mention-T01RJKRAW3A-C01QV1E7TCK\"}",
}

Is it because we need to fill out the SlackEventCallbackBody enum?

If this is the case it puts subscribers at risk of being throttled and disabled. Slack will disable bots that fail more than 5% of the time..

Socket mode in a tokio task breaks on Ctrl-C.

Putting socket_mode_listener.serve().await; into a tokio::spawn(async move { ... }), then hitting Ctrl-C makes tokio panic with

thread 'ctrl-c' panicked at C:\Users\Gremious\.cargo\registry\src\index.crates.io-6f17d22bba15001f\slack-morphism-1.16.1\src\hyper_tokio\socket_mode\tokio_clients_manager.rs:169:48:
Could not send signal on channel.: SendError { .. }

This is on Windows with PowerShell.

Letting the socket sit in actual main and hitting Ctrl-C there works just fine, thankfully.

Add `member_joined_channel` event

It would be great to have support for member_joined_channel event (https://api.slack.com/events/member_joined_channel).

Currently the error is:

json_error: Error("unknown variant `member_joined_channel`, expected one of `message`, `app_home_opened`, `app_mention`, `app_uninstalled`, `link_shared`, `emoji_changed`", line: 0, column: 0),
        json_body: Some(
            "{\"envelope_id\":\"f75fbe8a-a4eb-4666-***-439f0986c1fe\",\"payload\":{\"token\":\"***\",\"team_id\":\"T04N165GK0T\",\"context_team_id\":\"***\",\"context_enterprise_id\":null,\"api_app_id\":\"***\",\"event\":{\"type\":\"member_joined_channel\",\"user\":\"U04NDV8606M\",\"channel\":\"C04NR0SQBRN\",\"channel_type\":\"C\",\"team\":\"***\",\"inviter\":\"U04NR0GHBGQ\",\"event_ts\":\"1677093519.000500\"},\"type\":\"event_callback\",\"event_id\":\"Ev04QDN07G23\",\"event_time\":1677093519,\"authorizations\":[{\"enterprise_id\":null,\"team_id\":\"***\",\"user_id\":\"U04NDV8606M\",\"is_bot\":true,\"is_enterprise_install\":false}],\"is_ext_shared_channel\":false,\"event_context\":\"4-***\"},\"type\":\"events_api\",\"accepts_response_payload\":false,\"retry_attempt\":0,\"retry_reason\":\"\"}",
        ),

or is there currently a way to add support for additional types myself?

Socket callbacks require Sync auto-trait

Thanks for adding socket support recently. I gave it a try, though quite quickly ran into an issue - the callbacks on SlackSocketModeListenerCallbacks require the Sync auto-trait, which is not implemented on hyper's response Futures. So, you cannot simply use the client in a callback function to do arbitrary API requests:

async fn slack_command_events_fn(
    event: SlackCommandEvent,
    client: Arc<SlackHyperClient>,
    _states: Arc<RwLock<SlackClientEventsUserStateStorage>>,
) -> Result<SlackCommandEventResponse, Box<dyn std::error::Error + Send + Sync>> {
    let session = client.open_session(&...);
    let res = session
        .conversations_create(&SlackApiConversationsCreateRequest {
            name: "my_fancy_new_channel",
            is_private: Some(false),
            user_ds: None,
        })
        .await?;
    }
    // ...
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // ...    
    let socket_mode_callbacks = SlackSocketModeListenerCallbacks::new()
        .with_command_events(slack_command_events_fn);
    // ...
}

error[E0277]: `dyn Future<Output = Result<slack_morphism::api::SlackApiConversationsCreateResponse, Box<dyn std::error::Error + Send + Sync>>> + Send` cannot be shared between threads safely
   --> src/main.rs:140:10
    |
140 |         .with_command_events(slack_command_events_fn)
    |          ^^^^^^^^^^^^^^^^^^^ `dyn Future<Output = Result<slack_morphism::api::SlackApiConversationsCreateResponse, Box<dyn std::error::Error + Send + Sync>>> + Send` cannot be shared between threads safely
    |
    = help: the trait `Sync` is not implemented for `dyn Future<Output = Result<slack_morphism::api::SlackApiConversationsCreateResponse, Box<dyn std::error::Error + Send + Sync>>> + Send`

Let me know if I'm doing something wrong here...

`"Unexpected binary received from Slack" BrokenPipe` after some time when running in SocketMode

I have written a socketmode client that largely follows the example, with the only addition being my actual logic in the push events handler.

After running for some time, these log lines begin coming in at a rapid rate (about every 10 microseconds according to the tracing timestamps):

socketmodeapp  | 2022-06-29T18:26:09.667061Z ERROR slack_morphism_hyper::socket_mode::tungstenite_wss_client: [0/0/0] Slack WSS error: Io(Os { code: 32, kind: BrokenPipe, message: "Broken pipe" }) slack_wss_client_id="0/0/0"
socketmodeapp  | SocketModeProtocolError(
socketmodeapp  |     SlackClientSocketModeProtocolError {
socketmodeapp  |         message: "Unexpected binary received from Slack: Io(Os { code: 32, kind: BrokenPipe, message: \"Broken pipe\" })",
socketmodeapp  |     },
socketmodeapp  | )
socketmodeapp  | 2022-06-29T18:26:09.667070Z ERROR slack_morphism_hyper::socket_mode::tungstenite_wss_client: [1/1/0] Slack WSS error: Io(Os { code: 32, kind: BrokenPipe, message: "Broken pipe" }) slack_wss_client_id="1/1/0"
socketmodeapp  | SocketModeProtocolError(
socketmodeapp  |     SlackClientSocketModeProtocolError {
socketmodeapp  |         message: "Unexpected binary received from Slack: Io(Os { code: 32, kind: BrokenPipe, message: \"Broken pipe\" })",
socketmodeapp  |     },
socketmodeapp  | )

Waiting for a while never causes these to stop.

Obviously, the socket has been closed somewhere, but I'm not sure how that happened. Does slack_morphism (or hyper or tungstentite) have a facility to open a new session when this happens?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.