Slack Elixir - Socket Mode (websocket) event subscriptions and Web API

A thin wrapper around event subscriptions and web API. Existing Elixir slack libraries seem to use the deprecated RTM API (and don’t work for new apps/bots).

slack_elixir leverages Slack’s new Socket Mode for event subscriptions.

See it here:

5 Likes

Thanks for putting this library together, it’s exactly what we needed!

I’ve followed the README and when a user types “!Roll” for instance into a channel, I am receiving a response:

"debug] [Socket] message: %{“accepts_response_payload” => false, “envelope_id” => “ad67455d-8e23-4f59-a3de-fbb6b73332d6”, “payload” => %{“api_app_id” => “A0628UXU7FB”, “authorizations” => [%{“enterprise_id” => ni… "

So, all seems good there, but when the send_message function fires and returns {:ok}, I do not see the response message in the channel. Anything I could try to see the bot’s response in the channel?

Also, right now, for user / channel, I’m receiving the Slack ID versions of the user / channel, which is a string of integers/letters, but is there a way to return the actual user name / channel vs. just the ID? Thanks again and as I wrap my head around this library, will definitely try to contribute in the future!

Also, not sure this PR has anything to do w/ messages not being received in Slack: Fix issue with Enum.into by dotslashdash · Pull Request #3 · ryanwinchester/slack_elixir · GitHub

1 Like

Yes, I need to test this out and I’ll get back to you.

1 Like

Tested and released v1.0.1

2 Likes

You use the IDs for replying and mentions but if you need the usernames (or any other user info) for other reasons, I think you’ll need to use the API with the ID.

It’s not in the README but there is an API client:

https://hexdocs.pm/slack_elixir/Slack.API.html

I need to add more docs for this stuff.

2 Likes

Thanks so much! Send_message is still not posting messages to the channel, but it probably has do to w/ how I configured Slack (maybe my permissions aren’t correct or something) than your lib. Your API client is working great though!

You should add them to the channel and give them the correct permissions

Sorry, Ryan, I’m not sure what you mean by “You should add them to channel.” I’ve added the bot to the correct channel w/ correct permissions and able to receive messages and reply to messages using:

post(“https://slack.com/api/chat.postMessage”, token, message)

Also, was able to use your advice and get user_names/emails through /get user.list, but still unable to use the send_messages function (returns {:ok}, but no response in the channel.

Can you show some code?

Screenshot 2023-10-20 at 9.58.17 AM

Sure thing:

def start(_type, _args) do
    config = [
      app_token:
        "APP_TOKEN",
      bot_token: "BOT_TOKEN",
      bot: AppWeb.SlackBot
    ]

    children = [
      # Start the Telemetry supervisor
      AppWeb.Telemetry,
      # Start the Ecto repository
      App.Repo,
      # Start the PubSub system
      {Phoenix.PubSub, name: App.PubSub},
      # Start Finch
      {Finch, name: App.Finch},
      {Slack.Supervisor, config},
      # Start the Endpoint (http/https)
      AppWeb.Endpoint
      # Start a worker by calling: App.Worker.start_link(arg)
      # {App.Worker, arg}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: App.Supervisor]
    Supervisor.start_link(children, opts)
  end
__

defmodule AppWeb.SlackBot do
  @moduledoc false
  use Slack.Bot
  import Slack.API
  require Logger
  
  @impl true
  def handle_event(
        "message",
        %{"text" => "!" <> _command, "channel" => channel, "user" => user} = _params
      ) do

   send_message(channel, "Hello! <@#{user}>")
        
     {:ok, data} =
       get(
         "https://slack.com/api/users.list",
         "APP-TOKEN"
      )
      
     user_data =
       for member <- data["members"] do
         %{
           "real_name" => member["real_name"],
          "id" => member["id"]
         }
       end

     path = "PATH"

     Enum.each(user_data, fn %{"real_name" => _real_name, "id" => id} ->
       post_message_to_member(id, path)
      end)
  end

  defp post_message_to_member(id, path) do
    token = "BOT TOKEN"

    message = %{
      "channel" => id,
      "text" =>
      #"Hello" <> id <> ""
        "Hello <@#{id}>! Please take 30 seconds to reflect and share your key thing today at #{path}"
    }

    post("https://slack.com/api/chat.postMessage", token, message)
  end
end

The API calls are working, but not the send_message func. Thanks!

Very strange. Do you get any error message? slack_elixir/lib/slack/message_server.ex at main · ryanwinchester/slack_elixir · GitHub

If you can turn on debug logs (or run locally in dev env) are there any others from [MessageServer] that show the messages are queued and being sent?

Yeah, definitely strange. No, no error message (receiving {:ok}), and receiving this message:

“[debug] [Socket] Sending message event to Elixir.AppWeb.SlackBot,” which leads me to believe it should be working.

Also,

test = send_message(channel, "Hello! <@#{user}>")
    
Logger.debug("#{inspect(test)}")

That debug returns {:ok}

You get that log when the socket gets an incoming message from slack and then sends that as a message event to the bot which you receive in handle_event("message", ...).

From there you should be using send_message which should be adding outgoing messages to a queue (for self-rate-limiting) of messages to send to Slack. [MessageServer] debug logs should be showing up from that.

Ah, yeah, thanks, not receiving any [MessageServer] debug logs. I think I’m going to fork the repo, add some logging statements and go from there…

1 Like

@Fraih001 I think you’re trying to send a message on a private channel and channel_server tries to fetch only public channels via “users.conversations” API hence you must be facing this issue. CMIIW

To quick fix, you can fork the repo and add this in channel_server.ex

defp fetch_channels(token) do
    "users.conversations"
    |> Slack.API.stream(token, "channels", types: "public_channel, private_channel")
    |> Enum.map(& &1["id"])
  end

Note: I have added types as an additional argument to the stream.

@ryanwinchester I’m raising 2 issues I have found in the repo while using the library.

2 Likes

Fixed in v1.1.0 let me know if there are any more problems with that

2 Likes