Giter VIP home page Giter VIP logo

elixir-slack's Introduction

Elixir-Slack

Elixir CI Module Version Hex Docs Total Download License Last Updated

This is a Slack Real Time Messaging API client for Elixir. You'll need a Slack API token which can be retrieved by following the Token Generation Instructions or by creating a new bot integration.

Installing

Add :slack to your mix.exs dependencies function:

def application do
  [
    extra_applications: [:logger]
  ]
end

def deps do
  [
    {:slack, "~> 0.23.6"}
  ]
end

Upgrading from 0.x to 0.20+

The newest version of the Slack client introduces breaking changes with regards to starting and connecting to the Real Time Messaging API. rtm.start is now deprecated and has since been replaced with rtm.connect. This has removed the list of bots, channels, groups, users, and ims that are normally returned from rtm.start. Additionally, these lists are now rate-limited. In order to achieve relative parity to the old way of doing things, you'll need to make one change in your code:

Make additional calls to the Slack API to fetch bots, channels, groups, users, and IMs

Wherever you grab the passed in slack state, add in additional calls to populate these lists:

slack
|> Map.put(:bots, Slack.Web.Bots.info(%{token: token}) |> Map.get("bot"))
|> Map.put(:channels, Slack.Web.Channels.list(%{token: token}) |> Map.get("channels"))
|> Map.put(:groups, Slack.Web.Groups.list(%{token: token}) |> Map.get("groups"))
|> Map.put(:ims, Slack.Web.Im.list(%{token: token}) |> Map.get("ims"))
|> Map.put(:users, Slack.Web.Users.list(%{token: token}) |> Map.get("members"))

Real Time Messaging (RTM) Bot Usage

Define a module that uses the Slack behaviour and defines the appropriate callback methods.

defmodule SlackRtm do
  use Slack

  def handle_connect(slack, state) do
    IO.puts "Connected as #{slack.me.name}"
    {:ok, state}
  end

  def handle_event(message = %{type: "message"}, slack, state) do
    send_message("I got a message!", message.channel, slack)
    {:ok, state}
  end
  def handle_event(_, _, state), do: {:ok, state}

  def handle_info({:message, text, channel}, slack, state) do
    IO.puts "Sending your message, captain!"

    send_message(text, channel, slack)

    {:ok, state}
  end
  def handle_info(_, _, state), do: {:ok, state}
end

To run this example, you'll want to call Slack.Bot.start_link(SlackRtm, [], "TOKEN_HERE") and run the project with mix run --no-halt.

You can send messages to channels using send_message/3 which takes the message as the first argument, channel/user as the second, and the passed in slack state as the third.

The passed in slack state holds the current user properties as me, team properties as team, and the current websocket connection as socket.

If you want to do things like trigger the sending of messages outside of your Slack handlers, you can leverage the handle_info/3 callback to implement an external API.

This allows you to both respond to Slack RTM events and programmatically control your bot from external events.

{:ok, rtm} = Slack.Bot.start_link(SlackRtm, [], "token")
send rtm, {:message, "External message", "#general"}
#=> {:message, "External message", "#general"}
#==> Sending your message, captain!

Slack has a lot of message types so it's a good idea to define a callback like above where unhandled message types don't crash your application. You can find a list of message types and examples on the RTM API page.

You can find more detailed documentation on the Slack hexdocs page.

Web API Usage

The complete Slack Web API is implemented by generating modules/functions from the JSON documentation. You can view this project's documentation for more details.

There are two ways to authenticate your API calls. You can configure api_token on slack that will authenticate all calls to the API automatically.

config :slack, api_token: "VALUE"

Alternatively you can pass in %{token: "VALUE"} to any API call in optional_params. This also allows you to override the configured api_token value if desired.

Quick example, getting the names of everyone on your team:

names = Slack.Web.Users.list(%{token: "TOKEN_HERE"})
|> Map.get("members")
|> Enum.map(fn(member) ->
  member["real_name"]
end)

Web Client Configuration

A custom client callback module can be configured for cases in which you need extra control over how calls to the web API are performed. This can be used to control timeouts, or to add additional custom error handling as needed.

config :slack, :web_http_client, YourApp.CustomClient

All Web API calls from documentation-generated modules/functions will call post!/2 with the generated url and body passed as arguments.

In the case where you only need to control the options passed to HTTPoison/hackney, the default client accepts a keyword list as an additional configuration parameter. Note that this is ignored if configuring a custom client.

See HTTPoison docs for a list of available options.

config :slack, :web_http_client_opts, [timeout: 10_000, recv_timeout: 10_000]

Testing

For integration tests, you can change the default Slack URL to your fake Slack server:

config :slack, url: "http://localhost:8000"

Copyright and License

Copyright (c) 2014 Blake Williams

Source code is released under the MIT license.

elixir-slack's People

Contributors

aawilson avatar acconrad avatar afaur avatar ashkan18 avatar axelson avatar binaryseed avatar blakewilliams avatar christophermaier avatar davydog187 avatar devl avatar dylangriffith avatar entertainyou avatar fozcodes avatar iamjarvo avatar intentionally-left-nil avatar jameskbride avatar jeg2 avatar jmnsf avatar juanazam avatar kevinkoltz avatar kianmeng avatar mgwidmann avatar mmartinson avatar ne1ro avatar nrw505 avatar povilas avatar roehst avatar soriyath avatar vanstee avatar waynehoover 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elixir-slack's Issues

Loosing connection does not trigger handle_close

Hey

If I connec to the Slack RT API, and afterwards disconnects my internet, the handle_close callback is never called.

def handle_close(reason, slack, state) do
    Logger.info "[#{state.token}] Closing Slack session. Reason: #{reason}"
    {:ok, state}
  end

I want to be able to perform some cleanup, and then restart the connection, if it fails at any point

Issues using handle_info

Sending a generic message causes a ** (FunctionClauseError) no function clause matching in FitBot.SlackComm.websocket_info/3 error. Currently have a

defmodule FitBot.SlackComm do
  use Slack

  def init(initial_state, slack) do
    IO.puts "Connected as #{slack.me.name}"
    {:ok, initial_state}
  end

  def handle_message(message = %{type: "message"}, slack, state) do
    message_to_send = "Received #{length(state)} messages so far!"
    send_message(message_to_send, message.channel, slack)

    {:ok, state ++ [message.text]}
  end

  def handle_message(_message, _slack, state) do
    {:ok, state}
  end

  def handle_info(message = {:test}, _slack, state) do
    IO.puts("Got a message!")
    {:ok, state}
  end

  def handle_close({:handler, :function_clause}, _, _), do: IO.puts Exception.format(:error, :function_clause, System.stacktrace)
end

running

{:ok, slack_comm} = FitBot.SlackComm.start_link("token", []) 
send slack_comm, {:test}   

Triggers the error.

Websocket handler Mentioner.Slack terminating

I've got this error:

iex(1)> 
13:36:39.175 [error] ** Websocket handler Mentioner.Slack terminating
** for the reason {:handler, %RuntimeError{message: "argument error"}}
** Handler state was %{slack: %{bots: ...

Main process was still alive, but bot has gone offline. That probaly was due to missing internet connection, but I'm not sure.
Bot source code is here: https://github.com/denispeplin/mentioner

Changing the Slack state?

Hi

Is the slack state automatically synced behind the scenes? Ie. will the state of a user in the Slack construct automatically update based on incoming presense_changed events, or will I have to manually update this myself?

In case of the latter, how do I update the Slack state from the handlers?

Complete RTM API

In addition to sending messages, the Slack RTM API allows a couple of other actions over the socket:

For robustness I propose adding support for these.

Typing Indicators

  • Slack.indicate_typing/2

    Accepts a channel and a Slack.State. Activates the bot's typing indicator in that channel.

Pinging

Right now pinging is simulated in this function:

def websocket_handle({:ping, data}, _connection, state) do
  {:reply, {:pong, data}, state}
end

Rather than have a no-op, it'd be nice if it actually sent the ping to the server. To accommodate this, I propose:

  • Slack.send_ping/1

    Accepts a Slack.State. Pings the slack server.

  • Slack.send_ping/2

    Accepts a Slack.State and a Dict implementation. Pings the slack server with the extra data.

  • Slack.handle_pong/3

    Accepts a response, a Slack.State, and the server state. Of course, the response is expected to be of type pong with any extra data sent passed back in.

ID Tagging and Confirmation

Every payload you send slack will respond with a confirmation message. If you send an ID with it, the specific confirmation for that payload will return with the same ID.

Currently, Slack.send_message/3 doesn't allow injecting any extra parameters into the JSONโ€“namely, an ID. This would be handy for a few of my usecases. Additionally, it'd be nice to have for the ping API above.

Even if we could tag outgoing payloads with IDs, we'd have no way to handle the incoming response confirmations right now. Maybe we could make a Slack.handle_confirmation mechanism for receiving them somehow. I'm less sure what the API for this feature might look like, though. If implemented, Slack.handle_pong would probably be subsumed by it.

Turn Slack map into struct

The "slack" state is currently a map but would be better as a struct so it can be better documented and functions that manipulate/interact with it can be separated from the rest of the code.

Retrieving the reply for send_message

Hi

As noted here

https://api.slack.com/rtm

"Once the JSON has been sent to the server visual clients should immediately display the text in the channel, grayed out or otherwise marked to indicate that it is "pending". At some point after that, usually a few milliseconds later, the server will send a confirmation that the message was received:"

As it does not seem to be caught in any of the handlers, how can I retrieve this reply?

Can you provide a fully working example app?

I'm new to elixir, and having followed your example running mix run on the project seems to cause it to prints out a hello message but then the app finishes, and the bot disconnects. I know this may sound tedious, but it'd be nice if you could provide a fully working example application (sans the Slack token) so I can compare and see what I did wrong. This'd also help other Elixir newbies to get going quickly as well.

Does not actually run in its own process

The Slack RTM client does not run in its own process like a real GenServer would. This caused me a lot of confusion when I was trying to add a Supervisor that restarts the Slack client in case of the remote host closing the websocket connection. The PID returned from Slack.start_link/1 is for the underlying websocket_client process.

The Slack client should run in its own process and be better at being supervisable. A workaround might be to do something like this:

defmodule SlackRtmClient do
  use GenServer

  def start_link(token) do
    {:ok, pid} = GenServer.start_link(__MODULE__, :ok, name: SlackRtmClient.Instance)
    spawn fn -> SlackRtmClientImpl.start_link(token) end
    {:ok, pid}
  end

  def handle_cast({:exit, reason}, _) do
    {:noreply, Process.exit(self(), reason)}
  end

  # rest of GenServer stuff
end

defmodule SlackRtmClientImpl do
  use Slack
  require Logger

  def handle_close(:remote = reason, slack) do
    Logger.warn "Connection closed by remote host"
    GenServer.cast(SlackRtmClient.Instance, {:exit, reason})
  end

  # rest of implementation
end

As an aside to aid people debugging: To generate cases where Slack closes the connection on their end, use a test token and force it to be re-issued (you can do it on the test tokens page). This will close all connections opened with that token before giving you a new one.

Example send_message from outside a handler

It's a bit of a head-scratcher to figure out how to call send_message, particularly how to include the slack parameter. It's easy if you're in an event handler, but what if you want to send a message in response to something non-slack related (ie. "A new user just signed up!")

I'd love an example that shows how to catch and store the resulting slack parameter from the init and then retrieve and use it for future calls to send_message

Protocol.UndefinedError when declaring handle_message callback

Hi

I can define the handle_connect callback without any problems. However, I get the following error when I declare the handle_message callback. If I remove the declaration it runs fine.

[error] ** Websocket handler Slacker.SlackAPI.Session terminating
** for the reason {:handler, :function_clause}
** Handler state was %{slack: %{bots: %{"B0HKAQ11B" => %{deleted: false,
...

Code

defmodule Slacker.SlackAPI.Session do
  use Slack
  alias Slacker.Message

  @user_fields ~w( id name presence image_72 )a

  def handle_connect(slack, state) do
    %{users: users, me: me} = slack
    %{channel_id: channel_id, topic: topic} = state

    IO.puts "Connected to Slack Realtime API..."

    users = users
    |> Enum.map(fn {user_id, data} -> Map.merge(data, data.profile) end)
    |> Enum.map(fn data -> Map.take(data, @user_fields) end)

    payload = %{channel_id: channel_id, data: users}
    event = "new_users"
    Slacker.Endpoint.broadcast!(topic, event, payload)

    {:ok, state}
  end

  def handle_message(message = %{type: "message"}, slack, state) do
    if message.text == "Hi" do
      send_message("Hi has been said #\{state} times", message.channel, slack)
    end
    {:ok, state}
  end

post_message with attachments is not working

I create following request for example:

Slack.Web.Chat.post_message("D5LDLCNF3", "hello", %{ attachments: %{ text: "Choose answer" } }, token: "token" })

As a result, I got following error:

** (ArgumentError) argument error
          :erlang.list_to_binary([%{text: "Choose answer"}])

I couldn't resolve problem. I need help, please

Elixir-Slack converts slack API response keys to atoms

Atoms are not garbage collected in the BEAM vm, so converting strings to atoms in the slack API response is dangerous.

Consider changing the Elixir-Slack API contract to use strings as keys rather than converting them to atoms.

Relevent code:

Posting a message (not as a response)

I'd like to use this library to post a simple message to a given channel (not as a response to another message โ€“ for the sake of simplicity let's say I want to trigger it myself from iex).

Is this something that is supported? It seems like I could use send_message, but then I don't know where to get (or how to construct) the Slack argument.

I'm afraid that there is an obvious answer to this but I can't figure it out because I'm too new to the ecosystem. If that's the case I hope you'll excuse me for using Issues as a support channel.

Superfluous_charset warnings

When I use the post_message api:

Slack.Web.Chat.post_message("#general", "test", %{attachments: [%{pretext: "pre-hello", text: "text-world"}] |> Poison.encode! })

I get a superfluous_charset warning.

"response_metadata" => %{
  "warnings" => ["superfluous_charset"]
}

Due to https://api.slack.com/methods/chat.postMessage it seems to be a missing content-type, is there a way to set this via the api?

problem when start ws

** (Mix) Could not start application ebot: Ebot.Application.start(:normal, []) returned an error: shutdown: failed to start child: Slack.Bot
** (EXIT) an exception was raised:
** (MatchError) no match of right hand side value: {:error, {:undef, [{:crypto, :rand_bytes, [16], []}, {:wsc_lib, :generate_ws_key, 0, [file: '/data/slack-robot/ebot/deps/websocket_client/src/wsc_lib.erl', line: 227]}, {:websocket_client, :init, 1, [file: '/data/slack-robot/ebot/deps/websocket_client/src/websocket_client.erl', line: 158]}, {:gen_fsm, :init_it, 6, [file: 'gen_fsm.erl', line: 346]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]}}
(slack) lib/slack/bot.ex:40: Slack.Bot.start_link/4
(stdlib) supervisor.erl:365: :supervisor.do_start_child/2
(stdlib) supervisor.erl:348: :supervisor.start_children/3
(stdlib) supervisor.erl:314: :supervisor.init_children/2
(stdlib) gen_server.erl:328: :gen_server.init_it/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

do we expect send_message to work with private channels?

My company recently converted a channel that my bot was working with to a private channel. Now I'm finding this error when it tries to send a message to slack:

[error] expected a map, got: nil
[error]     (stdlib) :maps.find(:id, nil)
[web.1]:     (elixir) lib/map.ex:145: Map.get/3
[web.1]:     (slack) lib/slack/sends.ex:10: Slack.Sends.send_message/3
[web.1]:     (bot) lib/bot/slack.ex:46: Bot.Slack.handle_info/3
[web.1]:     (slack) lib/slack/bot.ex:99: Slack.Bot.websocket_info/3

My code is basically doing

handle_info(my_message, slack, state) do
  send_message("my message", "#mychannel", slack)
  {:ok, state}
end

Perhaps my real issue that I can't refer to private channels simply with the # prefixed name. If anyone has insights, I'd appreciate it.

Bot goes offline but handle_close is not called

Running with mix run --no-halt eventually has my bot go offline, and my handle_close is not called.

my handle close is as follows

 def handle_close(reason, _slack) do  
    IO.puts "websocket closed"  
    raise reason  
  end

And my bot will go offline from slack without this output appearing, and with no error being raised.

Stub Version for Testing?

Hey, I'm really enjoying the package, but I'm having problems testing my bot since it makes real connections to Slack each time. I was wondering if we could add a way to stub the Slack connection. I'm already mocking things like Slack.Sends.send_message, but the initial connection happens before I can stub it.

It looks like we would need to stub out Slack.Rtm.start to return the initial state, and add a fake websocket client. I'd be happy to implement this, but I'm not sure how best to go about it. Any ideas or direction would be appreciated.

Allow typing indication

Extracted from #11:

Slack supports typing indicators. We should as well. This ought to be trivial through send_raw.

API Additions

  • Slack.indicate_typing/2

    Accepts a channel and a Slack.State. Activates the bot's typing indicator in that channel.

I'll throw together a PR for this tonight.

fail to connect ws in Ubuntu

code works well in Mac osx
but failed in Ubuntu
exception shows:

** (Mix) Could not start application ebot: Ebot.Application.start(:normal, []) returned an error: shutdown: failed to start child: Slack.Bot
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, {:undef, [{:crypto, :rand_bytes, [16], []}, {:wsc_lib, :generate_ws_key, 0, [file: '/data/ebot/ebot/deps/websocket_client/src/wsc_lib.erl', line: 227]}, {:websocket_client, :init, 1, [file: '/data/ebot/ebot/deps/websocket_client/src/websocket_client.erl', line: 158]}, {:gen_fsm, :init_it, 6, [file: 'gen_fsm.erl', line: 348]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]}}
            (slack) lib/slack/bot.ex:40: Slack.Bot.start_link/4
            (stdlib) supervisor.erl:365: :supervisor.do_start_child/2
            (stdlib) supervisor.erl:348: :supervisor.start_children/3
            (stdlib) supervisor.erl:314: :supervisor.init_children/2
            (stdlib) gen_server.erl:365: :gen_server.init_it/2
            (stdlib) gen_server.erl:333: :gen_server.init_it/6
            (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

How to handle events?

I've been trying to handle events in my app, however I can't manage them to work. For instance, I can't manage the team_join event to work and welcome the new member

def handle_message(message = %{type: "team_join"}, slack, state) do
    send_message("Welcome, young alchemist", message.channel, slack)
    {:ok, state}
end

Same goes with every event here https://api.slack.com/rtm , what is the right way to handle them?

Thank you in advance!

Threading support

Are there are any plans to support threading in the rtm api
? https://api.slack.com/docs/message-threading

I started looking into it and I think you can get by with send_raw as a workaround, but I was wondering if there were plans for api support. I wouldn't know how the api would need to change to support it.

Exposing the sender in handle_info

Because handle_info receives any messages sent to the server that are not handled elsewhere, it would be convenient channel to get information (e.g. about the slack connection) from the server. However, because the process that sent the message is not exposed in handle_info, there is no way to send a message back to the server.

If possible, it would be nice to include the sender in the parameters to handle_info so information could be returned.

Operability behind a proxy

Right now, elixir-slack based bots do not work behind a proxy.
The networking modules that Elixir-Slack is based on, are not passing proxy and proxy_auth options that are required to enable the app behind the prxy. This significantly limits the potential usage of Elixir-Slack bots.

Handling revoked app

I'm trying to handle the case where the bot app has been revoked by the installer. Any help would be appreciated. Here's the output:

[error] ** State machine #PID<0.435.0> terminating 
** Last message in was {:ssl_closed,
 {:sslsocket, {:gen_tcp, #Port<0.14714>, :tls_connection, :undefined},
  #PID<0.438.0>}}
** When State == :connected
**      Data  == {:context,
 {:websocket_req, :wss, 'mpmulti-7vw0.slack-msgs.com', 443,
  '/websocket/dX1EXdhnVR67erOZCHGNDmkwF58fQ2FJ0i16Rxolk0VNFBOm1-cCOeZs59NDPe2U8aG45e7l_oPnzCI0llu-oOwSZxQXXYC1ed0S5WSjeaqNxkrKT5Miuv-AJF5UJNT-jDiuGqOQpQLPA0=',
  10000, #Reference<0.0.1.4905>, 1,
  {:sslsocket, {:gen_tcp, #Port<0.14714>, :tls_connection, :undefined},
   #PID<0.438.0>},
  {:transport, :ssl, :ssl, :ssl_closed, :ssl_error,
   [mode: :binary, active: true, verify: :verify_none, packet: 0]},
  "f+UuVxqh6FEkhaw==", :undefined, 1, :undefined, :undefined,
  :undefined},
 {:transport, :ssl, :ssl, :ssl_closed, :ssl_error,
  [mode: :binary, active: true, verify: :verify_none, packet: 0]}, [],
 {:wss, 'mpmulti-7vw0.slack-msgs.com', 443,
  '/websocket/dX1EXdhnVR67erOmcyhUNDmkwF58fQ2FJ0i16Rxolk0VNFBOm1-cCOeZs59NDPe2U8azCI0llu-oOwSZxQXXYC1ed0S5WSjeaqNxkrKT5Miuv-AJF5UJNT-jDiuGqOQpQLPA0='},
 {Slack.Bot,
  %{bot_handler: MyApp.SlackRtm, process_state: [],
    slack: %Slack.State{bots: %{"BXXXXXXXX" => %{app_id: "A00000000",
         deleted: true, ...

Consider replacing exjsx with Poison

Elixir-Slack currently uses exjsx for json parsing, however this project doesn't have great documentation, and Poison has a lot more traction with the community.

If you think this is a good idea, I would be happy to make these changes.

message.channel is not a human readable.

I'm not sure if this is a side effect of my slack server or something else, but my channel names appear to be things like "A123B456" instead of "general". This makes sending messages to a direct channel really challenging.

In the readme.md, during the send_message example, you used "general" there and that doesn't appear to work for me because the general channel is named something else.

compile slack error on ubuntu

05:50:21.184 [error] Loading of /data/slack-robot/ebot/_build/dev/lib/jsx/ebin/jsx_config.beam failed: :badfile

05:50:21.184 [error] beam/beam_load.c(1287): Error loading module jsx_config:
mandatory chunk of type 'Atom' not found

== Compilation error on file lib/slack/web/web.ex ==

05:50:21.221 [error] Loading of /data/slack-robot/ebot/_build/dev/lib/jsx/ebin/jsx_config.beam failed: :badfile

05:50:21.221 [error] beam/beam_load.c(1287): Error loading module jsx_config:
mandatory chunk of type 'Atom' not found

** (UndefinedFunctionError) function :jsx_config.extract_config/1 is undefined (module :jsx_config is not available)
:jsx_config.extract_config([])
lib/jsx.ex:14: JSX.decode!/2
lib/slack/web/web.ex:12: anonymous fn/2 in Slack.Web.format_documentation/1
(elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
lib/slack/web/web.ex:25: (file)

Change base handler from `handle_message`

Extracted from #11

For clarity between message-type responses and other events, the base event handler should be changed from handle_message to something more general.

This change should be backwards compatible and issue a deprecation warning.

Discuss

Preference in handle_reply vs handle_response vs handle_event, or something else, for the generic handler?

:error:{:badmap, {:message, "testing", "#channel"}} - When trying to use handle_info

In the README, it says you can use the Slack bot to send messages from other in-app events using handle_info, like so

{:ok, rtm} = Slack.Bot.start_link(SlackRtm, [], "token")
send rtm, {:message, "External message", "#general"}

This gives me a badmap error, which I believe is related to https://github.com/BlakeWilliams/Elixir-Slack/blob/master/lib/slack/bot.ex#L98

It's expecting a Map, not a tuple. Am I missing something here?

Loosen HTTPoison version requirement

Hello, mind loosening the httpoison version requirement ~> 0.9 instead of ~> 0.9.0

Failed to use "httpoison" (versions 0.10.0 and 0.11.0) because
  <redacted>/deps/bugsnag/mix.exs requires ~> 0.9
  <redacted>/deps/exvcr/mix.exs requires ~> 0.8
  <redacted>/deps/nadia/mix.exs requires ~> 0.9
  slack (version 0.9.2) requires ~> 0.9.0
  mix.exs specifies ~> 0.10

I just realized #103, but my point still stands, loosen that version please

Slack.Bot.start_link does not link process

It appears that the sub process in Slack.Bot.start_link when initiated, will register the PID to a name if given, but Process.link(pid) is never called. If the parent function (i.e. a GenServer) calls Slack.Bot.start_link no link is actually made.

I can add a PR to fix the issue by either calling Process.link or by renaming the method to Slack.Bot.start so that the correct behavior occurs.

Is `attachments` parameter supported for `post_message`?

Hi,

Is it possible to send attachments via the Web API post_message function?

Something like:

image_url = "https://images-na.ssl-images-amazon.com/images/G/01/img15/pet-products/small-tiles/30423_pets-products_january-site-flip_3-cathealth_short-tile_592x304._CB286975940_.jpg"

opts = %{as_user: true, unfurl_links: true, attachments: [%{image_url: image_url}]}                                                                                 

Slack.Web.Chat.post_message("#general", "test", opts)                                                                                                                                            

Events not firing

Hi

I don't receive any events related to typing indicators or user presence changes

Are these caught somewhere else?

Slack connection dies every x days/week

Hey,

I built a bot using your Slack library but I'm encountering some disconnects lately. I can't reproduce the issue for the time being, but I would like to help out anywhere I can to help fix this.

I have been looking through the code and the map that is shown {:context,...} is not to be found in the library. So perhaps this is an underlying issue in the websocket library?

So what happens is, the connection dies and spits out this error in the logs:

Mar 29 20:55:53 parzival mix[18353]: 20:55:53.013 [error] ** State machine #PID<0.210.0> terminating
Mar 29 20:55:53 parzival mix[18353]: ** Last message in was :keepalive
Mar 29 20:55:53 parzival mix[18353]: ** When State == :connected
Mar 29 20:55:53 parzival mix[18353]: **      Data  == {:context,
Mar 29 20:55:53 parzival mix[18353]: {:websocket_req, :wss, 'mpmulti-mdh7.slack-msgs.com', 443,
Mar 29 20:55:53 parzival mix[18353]: '/websocket/WV-1FHAYKpn5IlUeIQMK4p9kpWkPRf0mUMLzigAV_D5Qh0KvHXXTkRZrWkSIwnjzklScsDTH8_WuD1rCBLIHzhfIgl7tiyXAiR6j01Dzzenf-UKAflVAkSU8r8brlS1BzMr3haKapyl4bWgxkE0o3wNC0Ss0ri9CIANk9BJNBaE=',
Mar 29 20:55:53 parzival mix[18353]: 10000, #Reference<0.0.1.385>, 1,
Mar 29 20:55:53 parzival mix[18353]: {:sslsocket, {:gen_tcp, #Port<0.7727>, :tls_connection, :undefined},
Mar 29 20:55:53 parzival mix[18353]: #PID<0.223.0>},
Mar 29 20:55:53 parzival mix[18353]: {:transport, :ssl, :ssl, :ssl_closed, :ssl_error,
Mar 29 20:55:53 parzival mix[18353]: [mode: :binary, active: true, verify: :verify_none, packet: 0]},
Mar 29 20:55:53 parzival mix[18353]: "yI4YNXWjjvTnHKO5WSga6w==", :undefined, 1, :undefined, :undefined,
Mar 29 20:55:53 parzival mix[18353]: :undefined},
Mar 29 20:55:53 parzival mix[18353]: {:transport, :ssl, :ssl, :ssl_closed, :ssl_error,
Mar 29 20:55:53 parzival mix[18353]: [mode: :binary, active: true, verify: :verify_none, packet: 0]}, [],
Mar 29 20:55:53 parzival mix[18353]: {:wss, 'mpmulti-mdh7.slack-msgs.com', 443,
Mar 29 20:55:53 parzival mix[18353]: '/websocket/WV-1FHAYKpn5IlUeIQMK4p9kpWkPRf0mUMLzigAV_D5Qh0KvHXXTkRZrWkSIwnjzklScsDTH8_WuD1rCBLIHzhfIgl7tiyXAiR6j01Dzzenf-UKAflVAkSU8r8brlS1BzMr3haKapyl4bWgxkE0o3wNC0Ss0ri9CIANk9BJNBaE='},
Mar 29 20:55:53 parzival mix[18353]: {Slack.Bot,
Mar 29 20:55:53 parzival mix[18353]: %{bot_handler: SlackLogic, process_state: [],
Mar 29 20:55:53 parzival mix[18353]: slack: %Slack.State{bots: %{"B0AU37KEW" => %{app_id: "A0F7YS32P",
Mar 29 20:55:53 parzival mix[18353]: deleted: true,
Mar 29 20:55:53 parzival mix[18353]: icons: %{image_36: "https://a.slack-edge.com/12b5a/plugins/gdrive/assets/service_36.png",
Mar 29 20:55:53 parzival mix[18353]: image_48: "https://a.slack-edge.com/12b5a/plugins/gdrive/assets/service_48.png",
Mar 29 20:55:53 parzival mix[18353]: image_72: "https://a.slack-edge.com/12b5a/plugins/gdrive/assets/service_72.png"},
Mar 29 20:55:53 parzival mix[18353]: id: "B0AU37KEW", name: "gdrive", updated: 1456853053},
Mar 29 20:55:53 parzival mix[18353]: "B0AU4QVGD" => %{app_id: "A0F827J2C", deleted: false,
Mar 29 20:55:53 parzival mix[18353]: icons: %{image_36: "https://a.slack-edge.com/2fac/plugins/giphy/assets/service_36.png",
Mar 29 20:55:53 parzival mix[18353]: image_48: "https://a.slack-edge.com/2fac/plugins/giphy/assets/service_48.png",
Mar 29 20:55:53 parzival mix[18353]: image_72: "https://a.slack-edge.com/2fac/plugins/giphy/assets/service_72.png"},
Mar 29 20:55:53 parzival mix[18353]: id: "B0AU4QVGD", name: "giphy", updated: 1442582842},
Mar 29 20:55:53 parzival mix[18353]: "B0B5VNNVA" => %{app_id: "A0F7XDU93", deleted: true,
Mar 29 20:55:53 parzival mix[18353]: icons: %{image_36: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_36.png",
Mar 29 20:55:53 parzival mix[18353]: image_48: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_48.png",
Mar 29 20:55:53 parzival mix[18353]: image_72: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_72.png"},
Mar 29 20:55:53 parzival mix[18353]: id: "B0B5VNNVA", name: "hubot", updated: 1449776018},
Mar 29 20:55:53 parzival mix[18353]: "B0B65FUUS" => %{app_id: "A0F7XDUAZ", deleted: false,
Mar 29 20:55:53 parzival mix[18353]: icons: %{emoji: ":name_badge:",
Mar 29 20:55:53 parzival mix[18353]: image_64: "https://a.slack-edge.com/d4bf/img/emoji_2015_2/apple/1f4db.png"},
Mar 29 20:55:53 parzival mix[18353]: id: "B0B65FUUS", name: "IG4Toolio", updated: 1443002723},
Mar 29 20:55:53 parzival mix[18353]: "B0B6BP8A3" => %{app_id: "A0F7XDU93", deleted: true,
Mar 29 20:55:53 parzival mix[18353]: icons: %{image_36: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_36.png",
Mar 29 20:55:53 parzival mix[18353]: image_48: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_48.png",
Mar 29 20:55:53 parzival mix[18353]: image_72: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_72.png"},
Mar 29 20:55:53 parzival mix[18353]: id: "B0B6BP8A3", name: "hubot", updated: 1449776007},
Mar 29 20:55:53 parzival mix[18353]: "B0B6Y68FQ" => %{app_id: "A0F7XDU93", deleted: true,
Mar 29 20:55:53 parzival mix[18353]: icons: %{image_36: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_36.png",
Mar 29 20:55:53 parzival mix[18353]: image_48: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_48.png",
Mar 29 20:55:53 parzival mix[18353]: image_72: "https://a.slack-edge.com/12b5a/plugins/hubot/assets/service_72.png"},
Mar 29 20:55:53 parzival mix[18353]: id: "B0B6Y68FQ", name: "hubot", updated: 1444134719},```

Pass arguments into server

I may be missing something but I find the API of this library a bit awkward. If I want to forward the messages received by this process to another one I need to be able to pass in a PID that's saved in the state. There doesn't seem to be a way to set the state on handle_info, it's getting overwritten somehow. It seems I have to resort to using name PIDs everywhere which isn't ideal

Any ideas?

Elixir 1.4: "expected a keyword list as the second argument" in Slack.Web.Reactions.add

My code:

  def handle_event(message = %{type: "message"}, slack, state) do
    target_channel = System.get_env("SLACK_CHANNEL")
    case message.channel do
      ^target_channel -> handle_message(message, slack)
      _ -> nil
    end

    {:ok, state}
  end
  def handle_event(_, _, state), do: {:ok, state}

  defp handle_message(message = %{text: _}, _slack) do
    is_track_link = Regex.match?(~r'https://(open|play).spotify.com/track/', message.text)

    if is_track_link do
      options = %{channel: message.channel, timestamp: message.ts, token: System.get_env("SLACK_TOKEN")}
      Slack.Web.Reactions.add('musical_note', options)
      nil
    end
  end

When this code runs on Elixir 1.4.0, I get this error:

11:11:51 PM web.1 |  23:11:51.179 [error]     (elixir) lib/keyword.ex:589: Keyword.merge/2
11:11:51 PM web.1 |      (slack) lib/slack/web/web.ex:44: Slack.Web.Reactions.add/2
11:11:51 PM web.1 |      (botify) lib/botify/slack_rtm.ex:25: Botify.SlackRtm.handle_message/2
11:11:51 PM web.1 |      (botify) lib/botify/slack_rtm.ex:12: Botify.SlackRtm.handle_event/3
11:11:51 PM web.1 |      (slack) lib/slack/bot.ex:123: Slack.Bot.websocket_handle/3
11:11:51 PM web.1 |      (websocket_client) /Users/jacksonpopkin/Developer/usr/src/me/elixir/botify/deps/websocket_client/src/websocket_client.erl:448: :websocket_client.handle_websocket_frame/2
11:11:51 PM web.1 |      (stdlib) gen_fsm.erl:451: :gen_fsm.handle_msg/7
11:11:51 PM web.1 |      (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
11:11:51 PM web.1 |  23:11:51.208 [error] ** Websocket client Slack.Bot terminating in :websocket_handle/3
11:11:51 PM web.1 |     for the reason :error:%RuntimeError{message: "expected a keyword list as the second argument, got: [{\"name\", 'musical_note'}]"}

But if I run brew switch elixir 1.3.4 and run again, this works as I would expect it to - my Slack bot reacts with ๐ŸŽต.

(I know I'm passing a map and not a keyword, but passing a keyword here doesn't seem to work on either version of Elixir.)

I'm very new to Elixir; is it possible that this change from the 1.4 release notes has broken Slack.Web.Reactions.add? [Macro] Do not print aliases as keys inside keyword lists in Macro.to_string/2

Manually archiving a channel crashes handler

Hi

The following piece of code will crash with an {:handler, :badarg} error message, if you archive a channel using the Slack web/desktop client. It is however difficult to debug due to the very uninformative error

defmodule Test.Stream.Connection do
  use Slack

  def handle_connect(slack, state) do
    IO.inspect "Connected as #{slack.me.name}"
    {:ok, state}
  end

  def handle_message(message, slack, state) do
    {:ok, state}
  end

Full error message:

error] ** Websocket handler Slacker.Slack.Stream.Connection terminating
** for the reason {:handler, :badarg}
** Handler state was %{slack: %{bots: %{"B0JAQGSDU" => %{deleted: false, id: "B0JAQGSDU",
        name: "gdrive"}},
    channels: %{"C0LN67485" => %{created: 1455043602, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LN67485", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_tju5as7vep8"},
      "C0M2X67K7" => %{created: 1455266494, creator: "U0JAQGSCW",
        has_pins: false, id: "C0M2X67K7", is_archived: true, is_channel: true,
        is_general: false, is_member: true, members: ["U0JAQGSCW"],
        name: "z_127_0_0_1_whvidfq"},
      "C0LMVS78U" => %{created: 1455039521, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LMVS78U", is_archived: false, is_channel: true,
        is_general: false, is_member: false, name: "channel_b2cwhcr0pau"},
      "C0LN7R7KQ" => %{created: 1455042306, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LN7R7KQ", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_x09njzebcne"},
      "C0LNRRUJV" => %{created: 1455052874, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNRRUJV", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_qfbmoalvxky"},
      "C0LNP92PK" => %{created: 1455051374, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNP92PK", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_sq9wqkxczgk"},
      "C0LNVDKU2" => %{created: 1455054589, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNVDKU2", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_8iskpzwwcqu"},
      "C0JAQ9STZ" => %{created: 1452664560, creator: "U0JAQGSCW",
        has_pins: false, id: "C0JAQ9STZ", is_archived: false, is_channel: true,
        is_general: true, is_member: true, last_read: "1452664560.745557",
        latest: %{subtype: "channel_join",
          text: "<@U0JAQGSCW|mads> has joined the channel",
          ts: "1452664560.745557", type: "message", user: "U0JAQGSCW"},
        members: ["U0JAQGSCW"], name: "general",
        purpose: %{creator: "", last_set: 0,
          value: "This channel is for team-wide communication and announcements. All team members are in this channel."},
        topic: %{creator: "", last_set: 0,
          value: "Company-wide announcements and work-based matters"},
        unread_count: 0, unread_count_display: 0},
      "C0LGDKCN7" => %{created: 1454866721, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LGDKCN7", is_archived: false, is_channel: true,
        is_general: false, is_member: false, name: "channel_ip3wclr85vq"},
      "C0LHYFW05" => %{created: 1454939628, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LHYFW05", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_4y9ie2wlcba"},
      "C0LN4336F" => %{created: 1455042185, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LN4336F", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_1fedjx0k0ms"},
      "C0LNK2PQA" => %{created: 1455048575, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNK2PQA", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_pph7qh0p_m4"},
      "C0LNS7QGG" => %{created: 1455052345, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNS7QGG", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_t7hy0i8wbq8"},
      "C0M0JCSF9" => %{created: 1455218586, creator: "U0JAQGSCW",
        has_pins: false, id: "C0M0JCSF9", is_archived: false, is_channel: true,
        is_general: false, is_member: false, name: "z_127_0_0_1_vm6rvlw"},
      "C0LNT3S8M" => %{created: 1455055243, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNT3S8M", is_archived: false, is_channel: true,
        is_general: false, is_member: false, name: "channel_d_2nxoggzhm"},
      "C0LNDDS3Y" => %{created: 1455045260, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNDDS3Y", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_70she9wigpu"},
      "C0LJ38YGG" => %{created: 1454939713, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LJ38YGG", is_archived: false, is_channel: true,
        is_general: false, is_member: false, name: "channel_ppuowhm_-9s"},
      "C0LNLQCQ4" => %{created: 1455053120, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNLQCQ4", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_3ysc6n_bcek"},
      "C0M0C4CSG" => %{created: 1455218346, creator: "U0JAQGSCW",
        has_pins: false, id: "C0M0C4CSG", is_archived: false, is_channel: true,
        is_general: false, is_member: true, last_read: "1455266419.000002",
        latest: %{text: "test", ts: "1455266419.000002", type: "message",
          user: "U0JAQGSCW"}, members: ["U0JAQGSCW"],
        name: "z_127_0_0_1_aghtnfs",
        purpose: %{creator: "", last_set: 0, value: ""},
        topic: %{creator: "", last_set: 0, value: ""}, unread_count: 0,
        unread_count_display: 0},
      "C0LNJ4FMF" => %{created: 1455048430, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNJ4FMF", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_pi4mgba6kic"},
      "C0LNQNY6N" => %{created: 1455051836, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNQNY6N", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_5liisfzgbw8"},
      "C0LUFD8GK" => %{created: 1455191024, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LUFD8GK", is_archived: false, is_channel: true,
        is_general: false, is_member: true, last_read: "1455191025.000003",
        latest: %{attachments: [%{fallback: "NO FALLBACK DEFINED",
             fields: [%{short: false, title: "IP Address", value: "127.0.0.1"},
              %{short: false, title: "Browser",
                value: "Chrome (48.0.2564.103)"},
              %{short: false, title: "OS", value: "mac (10.11.2)"}], id: 1,
             mrkdwn_in: ["pretext"], pretext: "*Visitor Information*"}],
          subtype: "bot_message", text: "", ts: "1455191025.000003",
          type: "message", username: "bot"}, members: ["U0JAQGSCW"],
        name: "channel_jk4dklsnrky",
        purpose: %{creator: "", last_set: 0, value: ""},
        topic: %{creator: "", last_set: 0, value: ""}, unread_count: 0,
        unread_count_display: 0},
      "C0LN1KL2V" => %{created: 1455039643, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LN1KL2V", is_archived: false, is_channel: true,
        is_general: false, is_member: false, name: "channel_r_jzzlb61wc"},
      "C0LNUM6DP" => %{created: 1455054504, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNUM6DP", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_bvhkgqj3heq"},
      "C0JAQDJ02" => %{created: 1452664922, creator: "U0JAQGSCW",
        has_pins: false, id: "C0JAQDJ02", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_xb2wifmo2bs"},
      "C0LN3FG3W" => %{created: 1455043548, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LN3FG3W", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_ks351ozkf_a"},
      "C0LNT8HQX" => %{created: 1455053422, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNT8HQX", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_dv8ecrorkye"},
      "C0LNLEEHK" => %{created: 1455051416, creator: "U0JAQGSCW",
        has_pins: false, id: "C0LNLEEHK", is_archived: true, is_channel: true,
        is_general: false, is_member: false, name: "channel_ybckv-rbfou"},
      "C0LS5173L" => %{created: 1455130814, creato (truncated)

Please update readme

The code samples in the README have a few minor issues:

  • the match should be on %{type: "message"} in the handler and not a two value tuple
  • it should be message.channel and not response.channel
  • do not call start_link at compile time

Here is an actual example that worked for me. This can be run with

mix run --no-halt
defmodule MyBot do

  use Slack

  def handle_message(message = %{type: "message"}, slack, state) do
    send_message("hello world", message.channel, slack)
    {:ok, state}
  end

  def handle_message(message, _slack, state) do
    {:ok, state}
  end

  def start(_type,_args) do
    MyBot.start_link("<YOUR API KEY GOES HERE>", [])
  end

end

New example app needed

The two example apps are using 0.0.4 and 0.0.5 of slack hex package (https://github.com/BlakeWilliams/Elixir-Slack).

  • Cleverslack which relies on Cleverbot has issues with newest erlang.
  • Just_Bot will compile but blows up after trying to run

System Info:

-> % erl
Erlang/OTP 18 [erts-7.0] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
-> % elixir --version
Elixir 1.0.5
-> % sysctl -n machdep.cpu.brand_string
Intel(R) Core(TM) i7-3635QM CPU @ 2.40GHz
-> % uname
Darwin

Cleverslack Errors:

ERROR: OTP release 18 does not match required regex R15|R16|17
ERROR: compile failed while processing CleverSlack/deps/hackney: rebar_abort
clever_slack
** (Mix) Could not compile dependency hackney, ~/.mix/rebar command failed. If you want to recompile this dependency, please run: mix deps.compile hackney

Cleverslack Full Report

-> % git clone https://github.com/BlakeWilliams/CleverSlack.git
Cloning into 'CleverSlack'...
remote: Counting objects: 18, done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 18 (delta 2), reused 18 (delta 2), pack-reused 0
Unpacking objects: 100% (18/18), done.
Checking connectivity... done.
-> % cd CleverSlack 
-> % mix deps.get
* Getting websocket_client (https://github.com/jeremyong/websocket_client)
Cloning into 'CleverSlack/deps/websocket_client'...
remote: Counting objects: 371, done.        
remote: Total 371 (delta 0), reused 0 (delta 0), pack-reused 371        
Receiving objects: 100% (371/371), 74.80 KiB | 0 bytes/s, done.
Resolving deltas: 100% (215/215), done.
Checking connectivity... done.
A new Hex version is available (v0.9.0), please update with `mix local.hex`
Running dependency resolution
* Getting slack (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/slack-0.0.4.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/slack-0.0.4.tar)
* Getting cleverbot (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/cleverbot-0.0.1.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/cleverbot-0.0.1.tar)
* Getting httpoison (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/httpoison-0.5.0.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/httpoison-0.5.0.tar)
* Getting hackney (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/hackney-0.14.3.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/hackney-0.14.3.tar)
* Getting idna (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/idna-1.0.1.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/idna-1.0.1.tar)
* Getting exjsx (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/exjsx-3.1.0.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/exjsx-3.1.0.tar)
* Getting jsx (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/jsx-2.4.0.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/jsx-2.4.0.tar)
-> % SLACK_TOKEN=xxxREAL_TOKEN_REMOVEDxxx mix run --no-halt
==> idna (compile)
Compiled src/idna_unicode.erl
Compiled src/punycode.erl
Compiled src/idna_ucs.erl
Compiled src/idna.erl
Compiled src/idna_unicode_data.erl
==> websocket_client (compile)
Compiled src/websocket_client_handler.erl
Compiled src/websocket_req.erl
Compiled src/websocket_client.erl
==> jsx
Compiled src/jsx_decoder.erl
Compiled src/jsx_encoder.erl
Compiled src/jsx_config.erl
Compiled src/jsx_parser.erl
Compiled src/jsx.erl
Compiled src/jsx_to_term.erl
Compiled src/jsx_verify.erl
Compiled src/jsx_to_json.erl
Generated jsx app
==> exjsx
Compiled lib/jsx.ex
Generated exjsx app
==> hackney (compile)
ERROR: OTP release 18 does not match required regex R15|R16|17
ERROR: compile failed while processing CleverSlack/deps/hackney: rebar_abort
==> clever_slack
** (Mix) Could not compile dependency hackney, ~/.mix/rebar command failed. If you want to recompile this dependency, please run: mix deps.compile hackney

Justbot Errors:

21:41:57.245 [error] ** Websocket handler Slack.Socket terminating
** for the reason :undef
** Handler state was %{handler_state: [], module: JustBot.Bot, module_state: [],
  slack_state: %Slack.State{channels: #PID<0.246.0>,
   me: %{created: 1444960558, id: "U0CJ48ZME", manual_presence: "active",
     name: "bot_name",
     prefs: %{push_mention_alert: true, seen_spaces_new_xp_tooltip: false,
       winssb_run_from_tray: true, growls_enabled: true,
       has_created_channel: false, collapsible: false,
       seen_onboarding_direct_messages: false, ssb_space_window: "",
       msg_preview_persistent: true, at_channel_suppressed_channels: "",
       show_all_skin_tones: false, sidebar_behavior: "",
       onboarding_cancelled: false, seen_domain_invite_reminder: false,
       privacy_policy_seen: true, email_misc: true,
       start_scroll_at_oldest: true, msg_preview_displaces: true,
       autoplay_chat_sounds: true, two_factor_auth_enabled: false,
       search_exclude_channels: "", last_seen_at_channel_warning: 0,
       sidebar_theme: "default", ls_disabled: false, push_mention_channels: "",
       fuzzy_matching: false, seen_onboarding_start: false,
       push_loud_channels_set: "", seen_onboarding_slackbot_conversation: false,
       search_exclude_bots: false, show_member_presence: true,
       push_loud_channels: "", graphic_emoticons: false,
       posts_formatting_guide: true, tz: nil, no_macssb1_banner: false,
       obey_inline_img_limit: true, email_weekly: true, email_alerts: "instant",
       push_sound: "b2.mp3", welcome_message_hidden: false, ...}},
   socket: {:websocket_req, :wss, 'ms308.slack-msgs.com', 443,
    '/websocket/B4RLlnAQ8UB3KO9roibNvIcQv2v3JEzSqMGb7e8JwDVcjzdDxXl121dJqVQaCIz0oNMVmVOyzwsv4uBqxrsgKQCWrHBfYOnTXF_Xr3SeDH20XJ8mkqxq7KzPE-DA_2wyJtzuPtXNdiPh7W-gRD7-xA==',
    :infinity, :undefined,
    {:sslsocket, {:gen_tcp, #Port<0.8812>, :tls_connection, :undefined},
     #PID<0.244.0>}, :ssl, Slack.Socket, "eH/RbXviRm+4NJX9gh7yKA==", :undefined,
    :undefined, :undefined, :undefined, :undefined}, users: #PID<0.245.0>}}
** Stacktrace: [{JustBot.Bot, :handle_close,
  [{:handler,
    %KeyError{key: :text,
     term: %{channel: "C02FS2N7Y", deleted_ts: "1444963311.000009",
       event_ts: "1444963317.920964", hidden: true, subtype: "message_deleted",
       ts: "1444963317.000010", type: "message"}}},
   %Slack.State{channels: #PID<0.246.0>,
    me: %{created: 1444960558, id: "U0CJ48ZME", manual_presence: "active",
      name: "bot_name",
      prefs: %{push_mention_alert: true, seen_spaces_new_xp_tooltip: false,
        winssb_run_from_tray: true, growls_enabled: true,
        has_created_channel: false, collapsible: false,
        seen_onboarding_direct_messages: false, ssb_space_window: "",
        msg_preview_persistent: true, at_channel_suppressed_channels: "",
        show_all_skin_tones: false, sidebar_behavior: "",
        onboarding_cancelled: false, seen_domain_invite_reminder: false,
        privacy_policy_seen: true, email_misc: true,
        start_scroll_at_oldest: true, msg_preview_displaces: true,
        autoplay_chat_sounds: true, two_factor_auth_enabled: false,
        search_exclude_channels: "", last_seen_at_channel_warning: 0,
        sidebar_theme: "default", ls_disabled: false, push_mention_channels: "",
        fuzzy_matching: false, seen_onboarding_start: false,
        push_loud_channels_set: "",
        seen_onboarding_slackbot_conversation: false,
        search_exclude_bots: false, show_member_presence: true,
        push_loud_channels: "", graphic_emoticons: false,
        posts_formatting_guide: true, tz: nil, no_macssb1_banner: false,
        obey_inline_img_limit: true, email_weekly: true, ...}},
    socket: {:websocket_req, :wss, 'ms308.slack-msgs.com', 443,
     '/websocket/B4RLlnAQ8UB3KO9roibNvIcQv2v3JEzSqMGb7e8JwDVcjzdDxXl121dJqVQaCIz0oNMVmVOyzwsv4uBqxrsgKQCWrHBfYOnTXF_Xr3SeDH20XJ8mkqxq7KzPE-DA_2wyJtzuPtXNdiPh7W-gRD7-xA==',
     :infinity, :undefined,
     {:sslsocket, {:gen_tcp, #Port<0.8812>, :tls_connection, :undefined},
      #PID<0.244.0>}, :ssl, Slack.Socket, "eH/RbXviRm+4NJX9gh7yKA==",
     :undefined, :undefined, :undefined, :undefined, :undefined},
    users: #PID<0.245.0>}, []], []},
 {Slack.Socket, :websocket_terminate, 3,
  [file: 'lib/slack/socket.ex', line: 39]},
 {:websocket_client, :websocket_close, 3,
  [file: 'src/websocket_client.erl', line: 219]},
 {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 239]}]

Justbot Full Report:

-> % cd JustBot 
-> % git reset --hard HEAD
HEAD is now at 0cdc212 Initial commit
-> % mix deps.get
A new Hex version is available (v0.9.0), please update with `mix local.hex`
Running dependency resolution
* Updating slack (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/slack-0.0.5.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/slack-0.0.5.tar)
* Updating httpoison (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/httpoison-0.6.2.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/httpoison-0.6.2.tar)
* Updating hackney (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/hackney-1.2.0.tar)
Using locally cached package
Unpacked package tarball (~/.hex/packages/hackney-1.2.0.tar)
-> % SLACK_TOKEN=xxxREAL_TOKEN_REMOVEDxxx mix run --no-halt
==> hackney (compile)
Compiled src/hackney_lib/hackney_url.erl
Compiled src/hackney_lib/hackney_multipart.erl
Compiled src/hackney_lib/hackney_mimetypes.erl
Compiled src/hackney_lib/hackney_http.erl
Compiled src/hackney_lib/hackney_headers.erl
Compiled src/hackney_lib/hackney_date.erl
Compiled src/hackney_connect/hackney_tcp_transport.erl
Compiled src/hackney_lib/hackney_cookie.erl
Compiled src/hackney_connect/hackney_ssl_transport.erl
Compiled src/hackney_connect/hackney_pool_handler.erl
Compiled src/hackney_connect/hackney_socks5.erl
Compiled src/hackney_lib/hackney_bstr.erl
Compiled src/hackney_connect/hackney_http_connect.erl
Compiled src/hackney_client/hackney_util.erl
Compiled src/hackney_connect/hackney_pool.erl
Compiled src/hackney_connect/hackney_connect.erl
Compiled src/hackney_client/hackney_stream.erl
Compiled src/hackney_client/hackney_response.erl
Compiled src/hackney_client/hackney_idna.erl
Compiled src/hackney_client/hackney_folsom_metrics.erl
Compiled src/hackney_client/hackney_exometer_metrics.erl
Compiled src/hackney_client/hackney_dummy_metrics.erl
Compiled src/hackney_client/hackney_request.erl
Compiled src/hackney_client/hackney_manager.erl
Compiled src/hackney_app/hackney_trace.erl
Compiled src/hackney_app/hackney_sup.erl
Compiled src/hackney_app/hackney_app.erl
Compiled src/hackney_app/hackney_deps.erl
Compiled src/hackney_client/hackney.erl
==> httpoison
Compiled lib/httpoison/base.ex
Compiled lib/httpoison.ex
Generated httpoison app
==> slack
Compiled lib/slack/rtm.ex
Compiled lib/slack/handler.ex
Compiled lib/slack.ex
Compiled lib/slack/socket.ex
Compiled lib/slack/state.ex
Generated slack app
==> just_bot
Compiled lib/just_bot.ex
Compiled lib/just_bot/bot.ex
Generated just_bot app
21:41:57.245 [error] ** Websocket handler Slack.Socket terminating
** for the reason :undef
** Handler state was %{handler_state: [], module: JustBot.Bot, module_state: [],
  slack_state: %Slack.State{channels: #PID<0.246.0>,
   me: %{created: 1444960558, id: "U0CJ48ZME", manual_presence: "active",
     name: "bot_name",
     prefs: %{push_mention_alert: true, seen_spaces_new_xp_tooltip: false,
       winssb_run_from_tray: true, growls_enabled: true,
       has_created_channel: false, collapsible: false,
       seen_onboarding_direct_messages: false, ssb_space_window: "",
       msg_preview_persistent: true, at_channel_suppressed_channels: "",
       show_all_skin_tones: false, sidebar_behavior: "",
       onboarding_cancelled: false, seen_domain_invite_reminder: false,
       privacy_policy_seen: true, email_misc: true,
       start_scroll_at_oldest: true, msg_preview_displaces: true,
       autoplay_chat_sounds: true, two_factor_auth_enabled: false,
       search_exclude_channels: "", last_seen_at_channel_warning: 0,
       sidebar_theme: "default", ls_disabled: false, push_mention_channels: "",
       fuzzy_matching: false, seen_onboarding_start: false,
       push_loud_channels_set: "", seen_onboarding_slackbot_conversation: false,
       search_exclude_bots: false, show_member_presence: true,
       push_loud_channels: "", graphic_emoticons: false,
       posts_formatting_guide: true, tz: nil, no_macssb1_banner: false,
       obey_inline_img_limit: true, email_weekly: true, email_alerts: "instant",
       push_sound: "b2.mp3", welcome_message_hidden: false, ...}},
   socket: {:websocket_req, :wss, 'ms308.slack-msgs.com', 443,
    '/websocket/B4RLlnAQ8UB3KO9roibNvIcQv2v3JEzSqMGb7e8JwDVcjzdDxXl121dJqVQaCIz0oNMVmVOyzwsv4uBqxrsgKQCWrHBfYOnTXF_Xr3SeDH20XJ8mkqxq7KzPE-DA_2wyJtzuPtXNdiPh7W-gRD7-xA==',
    :infinity, :undefined,
    {:sslsocket, {:gen_tcp, #Port<0.8812>, :tls_connection, :undefined},
     #PID<0.244.0>}, :ssl, Slack.Socket, "eH/RbXviRm+4NJX9gh7yKA==", :undefined,
    :undefined, :undefined, :undefined, :undefined}, users: #PID<0.245.0>}}
** Stacktrace: [{JustBot.Bot, :handle_close,
  [{:handler,
    %KeyError{key: :text,
     term: %{channel: "C02FS2N7Y", deleted_ts: "1444963311.000009",
       event_ts: "1444963317.920964", hidden: true, subtype: "message_deleted",
       ts: "1444963317.000010", type: "message"}}},
   %Slack.State{channels: #PID<0.246.0>,
    me: %{created: 1444960558, id: "U0CJ48ZME", manual_presence: "active",
      name: "bot_name",
      prefs: %{push_mention_alert: true, seen_spaces_new_xp_tooltip: false,
        winssb_run_from_tray: true, growls_enabled: true,
        has_created_channel: false, collapsible: false,
        seen_onboarding_direct_messages: false, ssb_space_window: "",
        msg_preview_persistent: true, at_channel_suppressed_channels: "",
        show_all_skin_tones: false, sidebar_behavior: "",
        onboarding_cancelled: false, seen_domain_invite_reminder: false,
        privacy_policy_seen: true, email_misc: true,
        start_scroll_at_oldest: true, msg_preview_displaces: true,
        autoplay_chat_sounds: true, two_factor_auth_enabled: false,
        search_exclude_channels: "", last_seen_at_channel_warning: 0,
        sidebar_theme: "default", ls_disabled: false, push_mention_channels: "",
        fuzzy_matching: false, seen_onboarding_start: false,
        push_loud_channels_set: "",
        seen_onboarding_slackbot_conversation: false,
        search_exclude_bots: false, show_member_presence: true,
        push_loud_channels: "", graphic_emoticons: false,
        posts_formatting_guide: true, tz: nil, no_macssb1_banner: false,
        obey_inline_img_limit: true, email_weekly: true, ...}},
    socket: {:websocket_req, :wss, 'ms308.slack-msgs.com', 443,
     '/websocket/B4RLlnAQ8UB3KO9roibNvIcQv2v3JEzSqMGb7e8JwDVcjzdDxXl121dJqVQaCIz0oNMVmVOyzwsv4uBqxrsgKQCWrHBfYOnTXF_Xr3SeDH20XJ8mkqxq7KzPE-DA_2wyJtzuPtXNdiPh7W-gRD7-xA==',
     :infinity, :undefined,
     {:sslsocket, {:gen_tcp, #Port<0.8812>, :tls_connection, :undefined},
      #PID<0.244.0>}, :ssl, Slack.Socket, "eH/RbXviRm+4NJX9gh7yKA==",
     :undefined, :undefined, :undefined, :undefined, :undefined},
    users: #PID<0.245.0>}, []], []},
 {Slack.Socket, :websocket_terminate, 3,
  [file: 'lib/slack/socket.ex', line: 39]},
 {:websocket_client, :websocket_close, 3,
  [file: 'src/websocket_client.erl', line: 219]},
 {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 239]}]

Unable to upload using Slack.Web.Files api

According to the docs for Slack.Web.Files.upload/3, the first parameter to the upload function is the file, which will be sent as multipart/form-data. I've tried specifying a file, but the Slack API just response that no file data was sent:

iex(28)> Slack.Web.Files.upload "/tmp/test.jpg", "front_door.jpg", %{as_user: true, channels: ["@bklang"]}
%{"error" => "no_file_data", "ok" => false, "warning" => "superfluous_charset"}

I am able to use the upload/3 function if I use the :content key, but then I can't specify a mime-type (I can only list one of the Slack-provided pseudo-types, like "php"), and am limited to just 1MB. Am I doing something wrong, or is this a limitation of the auto-generated code?

Sending too many requests results in obscure error

Hi

If you send too many RTM connection requests, you will eventually receive the following response

You are sending too many requests. Please relax.

which due to being plain text and not JSON (response.body doesnt exist), fails with following error

ArgumentError) argument error
        (jsx) src/jsx_decoder.erl:234: :jsx_decoder.value/4
      (exjsx) lib/jsx.ex:15: JSX.decode!/2
      (slack) lib/slack/rtm.ex:8: Slack.Rtm.start/1

Strange start_link behavior

Hi,

First of all, thanks for the code! I'm starting to write Slack bots and have recently discovered Elixir from Python.

I'm finding a weird problem: running start_link from inside the code drops an error, while running it from iex works.

file lib/slack_bq.ex

defmodule SlackBq do                                                                                                       
  use Slack                                                                                                                

  def handle_connect(slack, state) do                                                                                      
    IO.puts "Connected as #{slack.me.name}"                                                                                
    {:ok, state}                                                                                                           
  end                                                                                                                      

  def handle_message(message = %{type: "message"}, slack, state) do                                                        
    reply = "Received #{length(state)} messages so far"                                                                 
    send_message(reply, message.channel, slack)                                                                                                                                                                
    {:ok, state ++ [message.text]}                                                                                                       
  end                                                                                                                      

  def handle_message(message, _slack, state) do                                                                            
    {:ok, state}                                                                                                           
  end                                                                                                                   
end                                                                                                                        

SlackBq.start_link("mytoken", []) 

result of iex -S mix

$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]


== Compilation error on file lib/slack_bq.ex ==
** (exit) exited in: :gen_server.call(:hackney_manager, {:new_request, #PID<0.138.0>, #Reference<0.0.2.1120>, {:client, :undefined, {:metrics_ng, :metrics_dummy}, :hackney_ssl_transport, 'slack.com', 443, "slack.com", [], nil, nil, nil, true, :hackney_pool, 5000, false, 5, false, 5, nil, nil, nil, :undefined, :start, nil, :normal, false, false, false, :undefined, false, nil, :waiting, nil, 4096, "", [], :undefined, nil, nil, nil, nil, :undefined, nil}}, :infinity)
    ** (EXIT) no process
    (stdlib) gen_server.erl:212: :gen_server.call/3
    src/hackney_manager.erl:66: :hackney_manager.init_request/1
    src/hackney_manager.erl:56: :hackney_manager.new_request/1
    src/hackney_connect.erl:184: :hackney_connect.socket_from_pool/4
    src/hackney_connect.erl:36: :hackney_connect.connect/5
    src/hackney.erl:328: :hackney.request/5
    lib/httpoison/base.ex:396: HTTPoison.Base.request/9
    lib/slack/rtm.ex:6: Slack.Rtm.start/1

The same happens if I call start_link from another module.

However, deleting that last line from lib/slack_bq.ex and running iex -S mix:

$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> SlackBq.start_link("mytoken", [])
Connected as testbot
{:ok, #PID<0.170.0>}

Maybe it's a noob mistake, but I've been fighting with it for hours, and can't seem to fix it. Any ideas?

Thanks!
Carlos

Add travis to check builds

Add travis to repository to check builds. This will enable..

  • An indication if tests are passing on new Pull Requests.
  • Indicator to show status of tests against the master branch on the top of the README.

Write a "fake slack" for integration testing

Currently testing in the application works, but it's not ideal.

What would be great to have is a fake slack server that we can spin up and have an actual bot interact with it.

This would also be good to at least smoke test the web API modules that are generated.

Trying to include Slack.Bot as child under supervisor - disconnects straight away

Hej! I am trying to get started with this lib to learn how to make a Slack bot. The problem is that as soon as I connect I am disconnected again but I can't see what went wrong due to the bloated message.

Is there something else that needs to be configured other than the token? It is included via an env variable at the start etc, but I just can't seem to get it to stay connected.

The error message starts with:

14:07:31.002 [error] ** State machine #PID<0.181.0> terminating ** Last event = {:internal, :init_state} ** When server state = {:disconnected, {:context, {:websocket_req, :wss, 'mpmulti-1dr8.slack-msgs.com', 443, '/websocket/lH22ahxOe3fParwUOg4EXoh8RwLtKpy3jYZCXDm3OWQI5UNX1FLnpBsDiAmLZnBYOd7Ub6HFo1yqcLApqz9tjt5_tEzetbEcduVhiTkCl33yIwaRpw62llMgDuWBh4NZXUwlDMyfD7-A9xCQ5Akqynia4PXQ-0R_GxWhE7OnQ9w=', 10000, :undefined, 1, :undefined, {:transport, :ssl, :ssl, :ssl_closed, :ssl_error, [mode: :binary, active: true, verify: :verify_none, packet: 0]}, "9ARMFhoaKr2hY1ws3ii7AQ==", :undefined, :undefined, :undefined, :undefined, :undefined}, {:transport, :ssl, :ssl, :ssl_closed, :ssl_error, [mode: :binary, active: true, verify: :verify_none, packet: 0]}, [], {:wss, 'mpmulti-1dr8.slack-msgs.com', 443, '/websocket/lH22ahxOe3fParwUOg4EXoh8RwLtKpy3jYZCXDm3OWQI5UNX1FLnpBsDiAmLZnBYOd7Ub6HFo1yqcLApqz9tjt5_tEzetbEcduVhiTkCl33yIwaRpw62llMgDuWBh4NZXUwlDMyfD7-A9xCQ5Akqynia4PXQ-0R_GxWhE7OnQ9w='}, {Slack.Bot, %{bot_handler: SlackKemisten, process_state: [], slack: %Slack.State{bots: %{"B5HTU6K1P" => %{app_id: "A0F7YS32P", deleted: false,

The rest of the message is the Slack.state which has been truncated.

I want to use it in a supervision tree so that I can deploy it as an OTP application later on.

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.