GithubHelp home page GithubHelp logo

slack-ruby-bot's Introduction

Slack-Ruby-Bot

Gem Version Build Status Code Climate


Warning: As of December 4th, 2020 Slack no longer accept resubmissions from apps that are not using granular permissions, or so-called "classic apps". On November 18, 2021 Slack will start delisting apps that have not migrated to use granular permissions. This library implements legacy, real-time support for classic apps. You should not be building a new bot with it and use slack-ruby-bot-server-events instead. For a rudimentary bot you can even start with slack-ruby-bot-server-events-app-mentions. See MIGRATION for migration help.


The slack-ruby-bot library is a generic Slack bot framework written in Ruby on top of slack-ruby-client. This library does all the heavy lifting, such as message parsing, so you can focus on implementing slack bot commands. It also attempts to introduce the bare minimum number of requirements or any sorts of limitations. It's a Slack bot boilerplate.

If you are not familiar with Slack bots or Slack API concepts, you might want to watch this video.

Table of Contents

Useful to Me?

  • If you are just trying to send messages to Slack, use slack-ruby-client, which this library is built on top of.
  • If you're trying to roll out a full service with Slack button integration, check out slack-ruby-bot-server, which uses this library.
  • Otherwise, this piece of the puzzle will help you create a single bot instance for one team.

Stable Release

You're reading the documentation for the next release of slack-ruby-bot. Please see the documentation for the last stable release, v0.16.1 unless you're integrating with HEAD. See CHANGELOG for a history of changes and UPGRADING for how to upgrade to more recent versions.

Usage

A Minimal Bot

Gemfile

source 'https://rubygems.org'

gem 'slack-ruby-bot'
gem 'async-websocket', '~>0.8.0'

pongbot.rb

require 'slack-ruby-bot'

class PongBot < SlackRubyBot::Bot
  command 'ping' do |client, data, match|
    client.say(text: 'pong', channel: data.channel)
  end
end

PongBot.run

After registering the bot, run with SLACK_API_TOKEN=... bundle exec ruby pongbot.rb. Have the bot join a channel and send it a ping.

A Production Bot

A typical production Slack bot is a combination of a vanilla web server and a websocket application that talks to the Slack Real Time Messaging API. See our Writing a Production Bot tutorial for more information.

More Involved Examples

The following examples of bots based on slack-ruby-bot are listed in growing order of complexity.

Commands and Operators

Bots are addressed by name, they respond to commands and operators. You can combine multiple commands.

class CallBot < SlackRubyBot::Bot
  command 'call', '呼び出し' do |client, data, match|
    client.say(channel: data.channel, text: 'called')
  end
end

Command match data includes match['bot'], match['command'] and match['expression']. The bot match always checks against the SlackRubyBot::Config.user and SlackRubyBot::Config.user_id values obtained when the bot starts.

The command method can take strings, which will have to be escaped with Regexp.escape, and regular expressions.

class CallBot < SlackRubyBot::Bot
  command 'string with spaces', /some\s*regex+\?*/ do |client, data, match|
    client.say(channel: data.channel, text: match['command'])
  end
end

Operators are 1-letter long and are similar to commands. They don't require addressing a bot nor separating an operator from its arguments. The following class responds to =2+2.

class MathBot < SlackRubyBot::Bot
  operator '=' do |client, data, match|
    # implementation detail
  end
end

Operator match data includes match['operator'] and match['expression']. The bot match always checks against the SlackRubyBot::Config.user setting.

Threaded Messages

To reply to a message in a thread you must provide a reference to the first message that initiated the thread, which is available as either data.ts if no threaded messages have been sent, or data.thread_ts if the message being replied to is already in a thread. See message-threading for more information.

command 'reply in thread' do |client, data, match|
  client.say(
    channel: data.channel,
    text: "let's avoid spamming everyone, I will tell you what you need in this thread",
    thread_ts: data.thread_ts || data.ts
  )
end

Note that sending a message using only thread_ts: data.ts can cause some permanent issues where Slack will keep reporting inaccessible messages as unread. At the time of writing the slack team is still having problems clearing those notifications. As recommended by the slack documentation ...

A true parent's thread_ts should be used when replying. Providing a child's message ID will result in a new, detached thread breaking all context and sense.

... the replies to a thread should always be sent to the message ts that started the thread, available as thread_ts for subsequent messages. Hence data.thread_ts || data.ts.

For additional options, including broadcasting, see slack-ruby-client#chat_postMessage.

Bot Aliases

A bot will always respond to its name (eg. rubybot) and Slack ID (eg. @rubybot), but you can specify multiple aliases via the SLACK_RUBY_BOT_ALIASES environment variable or via an explicit configuration.

SLACK_RUBY_BOT_ALIASES=:pp: table-tennis
SlackRubyBot.configure do |config|
  config.aliases = [':pong:', 'pongbot']
end

Bots will also respond to a direct message, with or without the bot name in the message itself.

Generic Routing

Commands and operators are generic versions of bot routes. You can respond to just about anything by defining a custom route.

class Weather < SlackRubyBot::Bot
  match /^How is the weather in (?<location>\w*)\?$/ do |client, data, match|
    client.say(channel: data.channel, text: "The weather in #{match[:location]} is nice.")
  end
end

You can also capture multiple matchers with scan.

class Market < SlackRubyBot::Bot
  scan(/([A-Z]{2,5})/) do |client, data, stocks|
    # lookup stock market price
  end
end

See examples/market for a working example.

Matching text in message attachments

You can respond to text in attachments with attachment. It will scan text, pretext and title fields in each attachment until a first match is found.

For example you can match this example attachment by its title with the following bot:

class Attachment < SlackRubyBot::Bot
  attachment 'Slack API Documentation' do |client, data, match|
    client.say(channel: data.channel, text: "Matched by #{match.attachment_field}.")
    client.say(channel: data.channel, text: "The attachment's text: #{match.attachment.text}.")
  end
end

You can also define which fields in attachment object should be scanned.

Scan only a single field:

class Attachment < SlackRubyBot::Bot
  attachment 'Slack API Documentation', :title do |client, data, match|
    # implementation details
  end
end

Scan multiple fields:

class Attachment < SlackRubyBot::Bot
  attachment 'Slack API Documentation', %i[text pretext author_name] do |client, data, match|
    # implementation details
  end
end

Providing description for your bot and commands

You can specify help information for bot or commands with help block, for example:

in case of bot:

class WeatherBot < SlackRubyBot::Bot
  help do
    title 'Weather Bot'
    desc 'This bot tells you the weather.'

    command 'clouds' do
      desc 'Tells you how many clouds there\'re above you.'
    end

    command 'What\'s the weather in <city>?' do
      desc 'Tells you the weather in a <city>.'
      long_desc "Accurate 10 Day Weather Forecasts for thousands of places around the World.\n" \
        'Bot provides detailed Weather Forecasts over a 10 day period updated four times a day.'
    end
  end

  # commands implementation
end

in case of your own command:

class Deploy < SlackRubyBot::Commands::Base
  help do
    title 'deploy'
    desc 'deploys your app'
    long_desc 'command format: *deploy <branch> to <env>* where <env> is production or staging'
  end
end

Customize your command help output

If you've used the help block described above to document your commands, you can provide your own implementation of outputting help for commands like so:

class Market < SlackRubyBot::Bot
  command 'help' do |client, data, match|
    user_command = match[:expression]
    help_attrs = SlackRubyBot::Commands::Support::Help.instance.find_command_help_attrs(user_command)
    client.say(channel: data.channel, text: "#{help_attrs.command_desc}\n\n#{help_attrs.command_long_desc}")
  end
end

SlackRubyBot::Commands::Base

The SlackRubyBot::Bot class is DSL sugar deriving from SlackRubyBot::Commands::Base. For more involved bots you can organize the bot implementation into subclasses of SlackRubyBot::Commands::Base manually. By default a command class responds, case-insensitively, to its name. A class called Phone that inherits from SlackRubyBot::Commands::Base responds to phone and Phone and calls the call method when implemented.

class Phone < SlackRubyBot::Commands::Base
  command 'call'

  def self.call(client, data, match)
    client.say(channel: data.channel, text: 'called')
  end
end

To respond to custom commands and to disable automatic class name matching, use the command keyword. The following command responds to call and 呼び出し (call in Japanese).

class Phone < SlackRubyBot::Commands::Base
  command 'call'
  command '呼び出し'

  def self.call(client, data, match)
    client.say(channel: data.channel, text: 'called')
  end
end

Authorization

The framework does not provide any user authentication or command authorization capability out of the box. However, the SlackRubyBot::Commands::Base class does check every command invocation for permission prior to executing the command. The default method always returns true.

Therefore, subclasses of SlackRubyBot::Commands::Base can override the permitted? private method to provide its own authorization logic. This method is intended to be exploited by user code or external gems that want to provide custom authorization logic for command execution.

class AuthorizedBot < SlackRubyBot::Commands::Base
  command 'phone home' do |client, data, match|
    client.say(channel: data.channel, text: 'Elliot!')
  end

  # Only allow user 'Uxyzabc' to run this command
  def self.permitted?(client, data, match)
    data && data.user && data.user == 'Uxyzabc'
  end
end

Built-In Commands

Slack-ruby-bot comes with several built-in commands. You can re-define built-in commands, normally, as described above.

[bot name]

This is also known as the default command. Shows bot version and links.

[bot name] hi

Politely says 'hi' back.

[bot name] help

Get help.

Hooks

Hooks are event handlers and respond to Slack RTM API events, such as hello or message. You can implement your own in a couple of ways:

Implementing and registering a Hook Handler

A Hook Handler is any object that respond to a call message, like a proc, instance of an object, class with a call class method, etc.

Hooks can be registered using different methods based on user preference / use case. Currently someone can use one of the following methods:

  • Pass hooks in SlackRubyBot::Server initialization.
  • Register hooks on SlackRubyBot::Server using on class method.
  • Register hooks on SlackRubyBot::Server using on instance method.
Hooks registration on SlackRubyBot::Server initialization
SlackRubyBot::Server.new(hook_handlers: {
  hello: MyBot::Hooks::UserChange.new,
  user_change: [->(client, data) {  }, ->(client, data) {}]
})
Hooks registration on a SlackRubyBot::Server instance
# Register an object that implements `call` method
class MyBot::Hooks::Hello
  def call(client, data)
    puts "Hello"
  end
end

server.on(:hello, MyBot::Hooks::Hello.new)

# or register a lambda function to handle the event
server.on(:hello, ->(client, data) { puts "Hello!" })

For example, the following hook handles user_change, an event sent when a team member updates their profile or data. This can be useful to update the local user cache when a user is renamed.

module MyBot
  module Hooks
    class UserChange
      def call(client, data)
        # data['user']['id'] contains the user ID
        # data['user']['name'] contains the new user name
        # ...
      end
    end
  end
end
Hooks registration on SlackRubyBot::Server class

Example:

module MyBot
  class MyServer < SlackRubyBot::Server
    on 'hello' do |client, data|
      # data['user']['id'] contains the user ID
      # data['user']['name'] contains the new user name
    end

    on 'user_change', ->(client, data) {
      # data['user']['id'] contains the user ID
      # data['user']['name'] contains the new user name
    }
  end
end

These will get pushed into the hook set on initialization.

Either by configuration, explicit assignment or hook blocks, multiple handlers can exist for the same event type.

Bot Message Protection

By default bots do not respond to self or other bots. If you wish to change that behavior globally, set allow_bot_messages to true.

SlackRubyBot.configure do |config|
  config.allow_bot_messages = true
end

Message Loop Protection

By default bots do not respond to their own messages. If you wish to change that behavior globally, set allow_message_loops to true.

SlackRubyBot.configure do |config|
  config.allow_message_loops = true
end

Logging

By default bots set a logger to $stdout with DEBUG level. The logger is used in both the RealTime and Web clients.

Silence logger as follows.

SlackRubyBot::Client.logger.level = Logger::WARN

If you wish to customize logger, set logger to your logger.

SlackRubyBot.configure do |config|
  config.logger = Logger.new("slack-ruby-bot.log", "daily")
end

Advanced Integration

You may want to integrate a bot or multiple bots into other systems, in which case a globally configured bot may not work for you. You may create instances of SlackRubyBot::Server which accepts token and aliases.

EM.run do
  bot1 = SlackRubyBot::Server.new(token: token1, aliases: ['bot1'])
  bot1.start_async

  bot2 = SlackRubyBot::Server.new(token: token2, aliases: ['bot2'])
  bot2.start_async
end

For an example of advanced integration that supports multiple teams, see slack-gamebot and playplay.io that is built on top of it.

Proxy Configuration

There are several proxy options that can be configured on Slack::Web::Client. You can also control what proxy options are used by modifying the http_proxy environment variable per Net::HTTP's documentation.

Note that Docker on OSX seems to incorrectly set the proxy, causing Faraday::ConnectionFailed, ERROR -- : Failed to open TCP connection to : (getaddrinfo: Name or service not known). You might need to manually unset http_proxy in that case, eg. http_proxy="" bundle exec ruby ./my_bot.rb.

Model-View-Controller Design

The command method is essentially a controller method that receives input from the outside and acts upon it. Complex behaviors could lead to a long and difficult-to-understand command block. A complex command block is a candidate for separation into classes conforming to the Model-View-Controller pattern popularized by Rails.

The library provides three helpful base classes named SlackRubyBot::MVC::Model::Base, SlackRubyBot::MVC::View::Base, and SlackRubyBot::MVC::Controller::Base.

Testing a command block is difficult. As separate classes, the Model/View/Controller's behavior can be tested via rspec or a similar tool.

Controller

The Controller is the focal point of the bot behavior. Typically the code that would go into the command block will now go into an instance method in a Controller subclass. The instance method name should match the command name exactly (case sensitive).

As an example, these two classes are functionally equivalent.

Consider the following Agent class which is the simplest default approach to take.

class Agent < SlackRubyBot::Bot
  command 'sayhello', 'alternate way to call hello' do |client, data, match|
    client.say(channel: data.channel, text: "Received command #{match[:command]} with args #{match[:expression]}")
  end
end

Using the MVC functionality, we would create a controller instead to encapsulate this function.

class MyController < SlackRubyBot::MVC::Controller::Base
  def sayhello
    client.say(channel: data.channel, text: "Received command #{match[:command]} with args #{match[:expression]}")
  end
  alternate_name :sayhello, :alternate_way_to_call_hello
end
MyController.new(MyModel.new, MyView.new)

Note in the above example that the Controller instance method sayhello does not receive any arguments. When the instance method is called, the Controller class sets up some accessor methods to provide the normal client, data, and match objects. These are the same objects passed to the command block.

However, the Controller anticipates that the model and view objects should contain business logic that will also operate on the client, data, and match objects. The controller provides access to the model and view via the model and view accessor methods. The inventory example provides a full example of a Model, View, and Controller working together.

A Controller may need helper methods for certain work. To prevent the helper method from creating a route that the bot will respond to directly, the instance method name should begin with an underscore (e.g. _my_helper_method). When building the bot routes, these methods will be skipped.

Calling alternate_name after the method definition allows for method aliases similar to the regular command structure. When commands can be triggered by multiple text strings it's useful to have that ability map to the controller methods too.

Lastly, the Controller class includes ActiveSupport::Callbacks which allows for full flexibility in creating before, after, and around hooks for all methods. Again, see the inventory example for more information.

Model

A complex bot may need to read or write data from a database or other network resource. Setting up and tearing down these connections can be costly, so the model can do it once upon instantiation.

The Model also includes ActiveSupport::Callbacks.

class MyModel < SlackRubyBot::MVC::Model::Base
  define_callbacks :sanitize
  set_callback :sanitize, :around, :sanitize_resource
  attr_accessor :_resource

  def initialize
    @db = setup_database_connection
  end

  def read(resource)
    self._resource = resource
    run_callbacks :sanitize do
      @db.select(:column1 => resource)
      # ... do some expensive work
    end
  end

  private

  def sanitize_resource
    self._resource.downcase
    result = yield
    puts "After read, result is #{result.inspect}"
  end
end

Like Controllers, the Model is automatically loaded with the latest version of the client, data, and match objects each time the controller method is called. Therefore the model will always have access to the latest objects when doing its work. It will typically only use the data and match objects.

Model methods are not matched to routes, so there is no restriction on how to name methods as there is in Controllers.

View

A typical bot just writes to a channel or uses the web client to react/unreact to a message. More complex bots will probably require more complex behaviors. These should be stored in a SlackRubyBot::MVC::View::Base subclass.

class MyView < SlackRubyBot::MVC::View::Base
  define_callbacks :logit
  set_callbacks :logit, :around, :audit_trail

  def initialize
    @mailer = setup_mailer
    @ftp = setup_ftp_handler
  end

  def email_admin(message)
    run_callbacks :logit do
      @mailer.send(:administrator, message)
    end
  end

  def react_thumbsup
    client.web_client.reactions_add(
      name: :thumbsup,
      channel: data.channel,
      timestamp: data.ts,
      as_user: true)
  end

  def react_thumbsdown
    client.web_client.reactions_remove(
      name: :thumbsup,
      channel: data.channel,
      timestamp: data.ts,
      as_user: true)
  end

  private

  def audit_trail
    Logger.audit("Sending email at [#{Time.now}]")
    yield
    Logger.audit("Email sent by [#{Time.now}]")
  end
end

Again, the View will have access to the most up to date client, data, and match objects. It will typically only use the client and data objects.

View methods are not matched to routes, so there is no restriction on how to name methods as there is in Controllers.

Testing

RSpec Shared Behaviors

Slack-ruby-bot comes with a number of shared RSpec behaviors that can be used in your RSpec tests.

Require slack-ruby-bot/rspec in your spec_helper.rb along with the following dependencies in Gemfile.

group :development, :test do
  gem 'rack-test'
  gem 'rspec'
  gem 'vcr'
  gem 'webmock'
end

Use the respond_with_slack_message matcher.

describe SlackRubyBot::Commands do
  it 'responds with any message' do
    expect(message: "#{SlackRubyBot.config.user} hi").to respond_with_slack_message
  end
  it 'says hi' do
    expect(message: "#{SlackRubyBot.config.user} hi").to respond_with_slack_message('hi')
  end
end

Use the respond_with_slack_messages matcher for multiple messages.

describe SlackRubyBot::Commands do
  it 'responds with more than one message' do
    expect(message: "#{SlackRubyBot.config.user} count").to respond_with_slack_messages
  end
  it 'says one and two' do
    expect(message: "#{SlackRubyBot.config.user} count").to respond_with_slack_messages(['one', 'two'])
  end
end

Message matchers support regular expressions.

describe SlackRubyBot::Commands do
  it 'says hi' do
    expect(message: "#{SlackRubyBot.config.user} hi").to respond_with_slack_message(/hi/)
  end
end

Check that the bot called client.start_typing(channel: 'channel').

describe SlackRubyBot::Commands do
 it 'starts typing on channel' do
    expect(message: "#{SlackRubyBot.config.user} hi").to start_typing(channel: 'channel')
  end
end

Testing Lower Level Messages

You can test client behavior at a lower level by fetching the message hook. The following example expects a bot command to call client.typing(channel: data.channel).

describe SlackRubyBot::Commands do
  let(:app) { Server.new }
  let(:client) { app.send(:client) }
  let(:message_hook) { SlackRubyBot::Hooks::Message.new }
  it 'receives a typing event' do
      expect(client).to receive(:typing)
      message_hook.call(
        client,
        Hashie::Mash.new(text: "#{SlackRubyBot.config.user} type something", channel: 'channel')
      )
    end
  end
end

Useful Libraries

Contributing

See CONTRIBUTING.

Upgrading

See CHANGELOG for a history of changes and UPGRADING for how to upgrade to more recent versions.

Copyright and License

Copyright (c) 2015-2020, Daniel Doubrovkine, Artsy and Contributors.

This project is licensed under the MIT License.

slack-ruby-bot's People

Contributors

accessd avatar ahmad19 avatar alexagranov avatar amatsuda avatar bogidon avatar cabello avatar chuckremes avatar corprew avatar crayment avatar dblock avatar derekddecker avatar dikond avatar dmvt avatar dramalho avatar edruder-block avatar fidrelity avatar gcraig99 avatar jbarber avatar jbristow avatar jcraigk avatar kstole avatar laertispappas avatar maclover7 avatar mdudzinski avatar mlr avatar mrbrutti avatar nateberkopec avatar startouf avatar wasabigeek avatar zubkonst 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

slack-ruby-bot's Issues

E, [2016-02-11T18:38:55.596707 #160] ERROR -- : listen loop error: undefined method `[]=' for nil:NilClass (NoMethodError)

config.ru:20:in `block (2 levels) in <main>'
E, [2016-02-11T18:38:55.596707 #160] ERROR -- : listen loop error: undefined method `[]=' for nil:NilClass (NoMethodError)
E, [2016-02-11T18:38:55.596840 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.6.0/lib/slack/real_time/event_handlers/pref_change.rb:9:in `call'
E, [2016-02-11T18:38:55.596881 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.6.0/lib/slack/real_time/client.rb:161:in `run_handlers'
E, [2016-02-11T18:38:55.596909 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.6.0/lib/slack/real_time/client.rb:155:in `dispatch'
E, [2016-02-11T18:38:55.596936 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.6.0/lib/slack/real_time/client.rb:109:in `block (2 levels) in run_loop'
E, [2016-02-11T18:38:55.596960 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `call'
E, [2016-02-11T18:38:55.596984 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `block in emit'
E, [2016-02-11T18:38:55.597012 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `each'
E, [2016-02-11T18:38:55.597061 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `emit'
E, [2016-02-11T18:38:55.597108 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/faye-websocket-0.10.2/lib/faye/websocket/api/event_target.rb:45:in `dispatch_event'
E, [2016-02-11T18:38:55.597154 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/faye-websocket-0.10.2/lib/faye/websocket/api.rb:106:in `receive_message'
E, [2016-02-11T18:38:55.597202 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/faye-websocket-0.10.2/lib/faye/websocket/api.rb:40:in `block in initialize'
E, [2016-02-11T18:38:55.597253 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `call'
E, [2016-02-11T18:38:55.597285 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `block in emit'
E, [2016-02-11T18:38:55.597310 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `each'
E, [2016-02-11T18:38:55.597334 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `emit'
E, [2016-02-11T18:38:55.597378 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/hybi.rb:400:in `emit_message'
E, [2016-02-11T18:38:55.597427 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/hybi.rb:383:in `emit_frame'
E, [2016-02-11T18:38:55.597465 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/hybi.rb:121:in `parse'
E, [2016-02-11T18:38:55.597490 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/websocket-driver-0.6.3/lib/websocket/driver/client.rb:63:in `parse'
E, [2016-02-11T18:38:55.597530 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/faye-websocket-0.10.2/lib/faye/websocket/api.rb:145:in `parse'
E, [2016-02-11T18:38:55.597591 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/faye-websocket-0.10.2/lib/faye/websocket/client.rb:71:in `receive_data'
E, [2016-02-11T18:38:55.597641 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.9.1/lib/eventmachine.rb:193:in `run_machine'
E, [2016-02-11T18:38:55.597690 #160] ERROR -- : /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.9.1/lib/eventmachine.rb:193:in `run'
E, [2016-02-11T18:38:55.597740 #160] ERROR -- : config.ru:20:in `block (2 levels) in <main>'
E, [2016-02-11T18:41:38.821515 #160] ERROR -- : Slack::Web::Api::Error: code_already_used

How to match against incoming webhooks?

I want to match against data being posted from an incoming Webhook and can not get it to match. I can match the same the same data against users without any problems.

Can anyone please advise

Cheers

Pushing to heroku fails on v0.7.0

It was working on 0.6.0

Could be related to faye/faye-websocket-ruby#80?

remote:        Running: rake assets:precompile
remote:        Starting up a new ElasticSearch client with https://atm84j46:[email protected]
remote:        I, [2016-03-24T16:40:25.391736 #793]  INFO -- : post https://slack.com/api/rtm.start
remote:        D, [2016-03-24T16:40:25.394671 #793] DEBUG -- request: Accept: "application/json; charset=utf-8"
remote:        User-Agent: "Slack Ruby Client/0.7.1"
remote:        Content-Type: "application/x-www-form-urlencoded"
remote:        I, [2016-03-24T16:40:26.158368 #793]  INFO -- Status: 200
remote:        D, [2016-03-24T16:40:26.160574 #793] DEBUG -- response: content-type: "application/json; charset=utf-8"
remote:        transfer-encoding: "chunked"
remote:        connection: "close"
remote:        access-control-allow-origin: "*"
remote:        cache-control: "private, no-cache, no-store, must-revalidate"
remote:        content-security-policy: "referrer no-referrer;"
remote:        date: "Thu, 24 Mar 2016 16:40:25 GMT"
remote:        expires: "Mon, 26 Jul 1997 05:00:00 GMT"
remote:        pragma: "no-cache"
remote:        server: "Apache"
remote:        strict-transport-security: "max-age=31536000; includeSubDomains; preload"
remote:        vary: "Accept-Encoding"
remote:        x-accepted-oauth-scopes: "rtm:stream,client"
remote:        x-content-type-options: "nosniff"
remote:        x-oauth-scopes: "identify,read,post,client"
remote:        x-xss-protection: "0"
remote:        x-cache: "Miss from cloudfront"
remote:        via: "1.1 8dae7df87719a07f42e1196c2adda1c1.cloudfront.net (CloudFront)"
remote:        x-amz-cf-id: "FVsd3I5jzLo-PRrNI1CmRH1TY7l5Zm5JEBAVerjBvPfkIozjDGKWiA=="
remote:        D, [2016-03-24T16:40:26.361188 #793] DEBUG -- Slack::RealTime::Concurrency::Eventmachine::Socket#connect!: Slack::RealTime::Concurrency::Eventmachine::Client
remote:        D, [2016-03-24T16:40:26.452678 #793] DEBUG -- Slack::RealTime::Concurrency::Eventmachine::Client#write: GET /websocket/zLN_5Y3pO3MoXLyLkEwDdTzuAI-anlALkOObM5BylUgk2nCSaXQBxAtldAP1bqg6q_2I7pOIdVvqQgL3GmrmpXmqBFZDMbOZr3mQpu_xgb_v6SskVaK1OsSO4a5BbU_YB4idS4fKC3QcgDugsVtwKQ== HTTP/1.1
remote:        Host: mpmulti-hzby.slack-msgs.com
remote:        Upgrade: websocket
remote:        Connection: Upgrade
remote:        Sec-WebSocket-Key: vfz282AWkDZplIzcHW6U5A==
remote:        Sec-WebSocket-Version: 13
remote:        
remote:        D, [2016-03-24T16:40:26.583035 #793] DEBUG -- Slack::RealTime::Concurrency::Eventmachine::Client#parse: HTTP/1.1 101 Switching Protocols
remote:        Upgrade: websocket
remote:        Connection: Upgrade
remote:        Sec-WebSocket-Accept: 1zNNtIeHvGKi6bkEacgF4EzIs9M=
remote:        
remote:        D, [2016-03-24T16:40:26.587803 #793] DEBUG -- SlackRubyBot::Client#run_loop: Faye::WebSocket::API::OpenEvent
remote:  !
remote:  !     invalid byte sequence in UTF-8
remote:  !
remote: /app/tmp/buildpacks/ruby/lib/language_pack/shell_helpers.rb:113:in `split': invalid byte sequence in UTF-8 (ArgumentError)
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/shell_helpers.rb:113:in `puts'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/shell_helpers.rb:94:in `block in pipe'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/shell_helpers.rb:90:in `popen'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/shell_helpers.rb:90:in `pipe'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/helpers/rake_runner.rb:50:in `block in invoke'
remote:     from /app/vendor/ruby-2.3.0/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/helpers/rake_runner.rb:44:in `invoke'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails4.rb:85:in `block (2 levels) in run_assets_precompile_rake_task'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:131:in `log'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails4.rb:71:in `block in run_assets_precompile_rake_task'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:18:in `block (2 levels) in instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:40:in `yield_with_block_depth'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:17:in `block in instrument'
remote:     from /app/vendor/ruby-2.3.0/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:16:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:48:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:44:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails4.rb:70:in `run_assets_precompile_rake_task'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/ruby.rb:104:in `block (2 levels) in compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/ruby.rb:767:in `allow_git'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/ruby.rb:98:in `block in compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:18:in `block (2 levels) in instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:40:in `yield_with_block_depth'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:17:in `block in instrument'
remote:     from /app/vendor/ruby-2.3.0/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:16:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:48:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:44:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/ruby.rb:88:in `compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails2.rb:49:in `block in compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:18:in `block (2 levels) in instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:40:in `yield_with_block_depth'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:17:in `block in instrument'
remote:     from /app/vendor/ruby-2.3.0/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:16:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:48:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:44:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails2.rb:47:in `compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails3.rb:38:in `block in compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:18:in `block (2 levels) in instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:40:in `yield_with_block_depth'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:17:in `block in instrument'
remote:     from /app/vendor/ruby-2.3.0/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:16:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:48:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:44:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails3.rb:37:in `compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails4.rb:41:in `block in compile'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:18:in `block (2 levels) in instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:40:in `yield_with_block_depth'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:17:in `block in instrument'
remote:     from /app/vendor/ruby-2.3.0/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:16:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:48:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:44:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/rails4.rb:40:in `compile'
remote:     from /app/tmp/buildpacks/ruby/bin/compile:16:in `block (2 levels) in <main>'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/base.rb:131:in `log'
remote:     from /app/tmp/buildpacks/ruby/bin/compile:15:in `block in <main>'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:35:in `block in trace'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:18:in `block (2 levels) in instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:40:in `yield_with_block_depth'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:17:in `block in instrument'
remote:     from /app/vendor/ruby-2.3.0/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:16:in `instrument'
remote:     from /app/tmp/buildpacks/ruby/lib/language_pack/instrument.rb:35:in `trace'
remote:     from /app/tmp/buildpacks/ruby/bin/compile:11:in `<main>'
remote: 
remote:  !     Push rejected, failed to compile Ruby app
remote: 

Bot listening to HTTP requests?

I think about building a development supporting service that gathers key data and bot that will act as a middleman between this all. I've had a glance on examples and through bot code, and noticed that bot is listening only messages via websocket. I've seen sinatra used with example bots you provided, but didn't notice any cooperation between them :)

How could I do http call that will trigger bot to output something in channel?

how can i override server instance to register my own `star_added` hook?

hi all,

i was following https://github.com/dblock/slack-ruby-bot/blob/master/TUTORIAL.md
and read the readme on hooks.

though, i can't figure out how (at what point of tutorial mentioned above) can i obtain server instance or how to force bot to use MyServer so I can register own hooks?

sorry, looks like very simple question. many thanks.

p.s. i've tried this:

module SlackFbrunbot
  class Bot < SlackRubyBot::Bot
    def self.instance
      SlackFbrunbot::MyServer.instance
    end
  end
end
module SlackFbrunbot
  class MyServer < SlackRubyBot::Server
    on 'hello' do |client, data|
      logger.info 'heloooo'
      # data['user']['id'] contains the user ID
      # data['user']['name'] contains the new user name
    end
  end
end

on rackup getting

undefined method 'on' for SlackFbrunbot::MyServer:Class (NoMethodError)

How can I get a list of the users in a channel within a matcher?

Suppose I have a matcher in a subclass of SlackRubyBot::Bot. I'm trying to do something like the following:

match /^something$/ do |client, data, match|
  # do something with match

  client.channels[data.channel].users.values.each do |user|
    # do something for each user in the channel
  end
end

I essentially would like to perform some operation looping over the list of users in the channel from which I received the message which matched my matcher, but I'm not sure how to get that list of users.

what is the proper way to send attachments?

From a command, what is the proper way to send an attachment (https://api.slack.com/docs/attachments) ?

I've tried:

send_message( client, data.channel, message, attachments: [{ fallback: "fb", pretext: "pt", title: "the title", text: "some text" }] )

I also tried:

client.web_client.chat_postMessage( channel: data.channel, text: message, attachments: [{ fallback: "fb", pretext: "pt", title: "the title", text: "some text" }] )

But it just sends what is in message and no fancy stuff.

$ bundle list |grep ruby
  * slack-ruby-bot (0.4.5)
  * slack-ruby-client (0.3.1)

Thanks!

chat_postMessage ignores fields in attachments?

Hey There,
According to the Slack API I should be able to use fields (https://api.slack.com/docs/attachments)

I tried the following code and it doesn't work:
.chat_postMessage(
channel: data.channel,
as_user: true,
attachments: [
{
fallback: "List couldn't be displayed",
title: "Latest Jobs",
fields: '[
{
title: "ID"
value: "1"
short: "true"
},
{
title: "ID"
value: "1"
short: "true"
}
]',
color: '#82C045'
}
].to_json
)

Am I missing something. Apparently it never goes through the API either:
Faye::WebSocket::API::MessageEvent, {"type":"message","user":"XXXXXXXX","text":"","attachments":[{"fallback":"List couldn't be displayed","title":"Latest Jobs","id":1,"color":"82C045"}],"channel":"C0NG9JDTL","ts":"XXXXXXXXXXX"}

Failure to boot should cause the bot to exit

2015-08-21T14:26:17.365071+00:00 app[web.1]: * Listening on tcp://0.0.0.0:5023
2015-08-21T14:26:17.450818+00:00 app[web.1]: I, [2015-08-21T14:26:17.450607 #3]  INFO -- : Welcome 'pongbot' to the 'Artsy' team at https://artsy.slack.com/.
2015-08-21T14:26:17.371591+00:00 app[web.1]: Use Ctrl-C to stop
2015-08-21T14:26:17.945314+00:00 app[web.1]: I, [2015-08-21T14:26:17.945185 #3]  INFO -- : Successfully connected to https://artsy.slack.com/.
2015-08-21T17:03:43.354298+00:00 app[web.1]: failed to read response string
2015-08-21T17:03:43.354376+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:9:in `block in call'
2015-08-21T17:03:43.354368+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/faraday/response/raise_error.rb:8:in `on_complete'
2015-08-21T17:03:43.354377+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:57:in `on_complete'
2015-08-21T17:03:43.354381+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:8:in `call'
2015-08-21T17:03:43.354382+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:8:in `call'
2015-08-21T17:03:43.354386+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/request/url_encoded.rb:15:in `call'
2015-08-21T17:03:43.354390+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/request/multipart.rb:14:in `call'
2015-08-21T17:03:43.354395+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/rack_builder.rb:139:in `build_response'
2015-08-21T17:03:43.354404+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/connection.rb:177:in `post'
2015-08-21T17:03:43.354399+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/connection.rb:377:in `run_request'
2015-08-21T17:03:43.354409+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/faraday/request.rb:25:in `request'
2015-08-21T17:03:43.354412+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/faraday/request.rb:10:in `post'
2015-08-21T17:03:43.354418+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/api/endpoints/rtm.rb:15:in `rtm_start'
2015-08-21T17:03:43.354427+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/real_time/client.rb:39:in `block in start!'
2015-08-21T17:03:43.354433+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `run_machine'
2015-08-21T17:03:43.354432+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `call'
2015-08-21T17:03:43.354437+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `run'
2015-08-21T17:03:43.354440+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/real_time/client.rb:38:in `start!'
2015-08-21T17:03:43.354445+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:46:in `block in start!'
2015-08-21T17:03:43.354449+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:45:in `loop'
2015-08-21T17:03:43.354454+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:45:in `start!'
2015-08-21T17:03:43.354458+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:28:in `run'
2015-08-21T17:03:43.354464+00:00 app[web.1]: config.ru:7:in `block (2 levels) in <main>'

Provide an automatic way of handling migration_in_progress and other start errors

Bot doesn't survive predictable errors like migration_in_progress. In this case it should report an error and automatically retry or exit.

2015-07-17T22:39:13.867979+00:00 heroku[web.1]: State changed from starting to up
2015-07-17T22:39:14.326781+00:00 app[web.1]: I, [2015-07-17T22:39:14.326661 #3]  INFO -- : Successfully connected to https://artsy.slack.com/.
2015-07-18T16:19:10.943269+00:00 app[web.1]: migration_in_progress
2015-07-18T16:19:10.943310+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-ruby-bot-0.2.0/lib/initializers/slack/request.rb:9:in `request'
2015-07-18T16:19:10.943314+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-api-1.1.6/lib/slack/request.rb:11:in `post'
2015-07-18T16:19:10.943317+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-api-1.1.6/lib/slack/api.rb:26:in `realtime'
2015-07-18T16:19:10.943319+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-api-1.1.6/lib/slack.rb:20:in `method_missing'
2015-07-18T16:19:10.943321+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-ruby-bot-0.2.0/lib/slack-ruby-bot/app.rb:52:in `client'
2015-07-18T16:19:10.943326+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-ruby-bot-0.2.0/lib/slack-ruby-bot/app.rb:45:in `block in start!'
2015-07-18T16:19:10.943329+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-ruby-bot-0.2.0/lib/slack-ruby-bot/app.rb:44:in `loop'
2015-07-18T16:19:10.943334+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-ruby-bot-0.2.0/lib/slack-ruby-bot/app.rb:44:in `start!'
2015-07-18T16:19:10.943339+00:00 app[web.1]: /app/vendor/bundle/ruby/2.1.0/gems/slack-ruby-bot-0.2.0/lib/slack-ruby-bot/app.rb:27:in `run'
2015-07-18T16:19:10.943342+00:00 app[web.1]: config.ru:7:in `block (2 levels) in <main>'

And that's the beginning and the end.

See dblock/slack-gamebot#23

Support for alternative slack autocomplete syntax

I just noticed this when following your Mathbot tutorial. @mathbot: calculate works but mathbot: calculate doesn't.

The syntax for the second command can be a result of slacks autocomplete (unless you switch it off) and you need to explicitly add it as an alias to get it working. Can we support this by default?

Using slack ruby client commands

Hi,

Thanks a lot for the amazing gem.
Unfortunately I can't use slack ruby client commands with it such as:

  • client.channels_list.channels
    or
  • client.channels_info(channel: '#general')

I get a "undefined method `channels_info' for #SlackRubyBot::Client:0x007fb2e1624030" error each time. Am I doing something wrong here?

Thank you

Example using oauth tokens?

We're integrating with slack on a per-user/per-team basis with oauth tokens. We get back a user-specific token and a team-specific token. We use the team-specific token to send messages with a bot id. I want to create a bot using slack-ruby-bot within this framework.

I see that you can start a generic server with SlackRubyBot::Server.new(token: bot_token) but then how does the server know which subclass of SlackRubyBot::Bot to handle messages with? I'm assuming I'll need one bot per Team that at least one of my oauthed users is authenticated against and I'll use the team-specific token to run that server.

Ability to have complex logic in command matchers

Not sure if this is currently possible but after leafing through the source, it does not seem so:

We would like to have a command that only matches if complex conditions are met. By complex conditions, I mean, conditions that are more than a simple regex that could refer to client data to switch whether that command matches and executes.

As an example, imagine a bot that would respond to anything a user writes(ie a regex is insufficient) but only if its in direct response to a question the bot has asked. That is, it would only respond if the very last message was a bots question, otherwise it would defer to the other commands.

SlackRubyBot::Server#restart will eventually blow up the stack

SlackRubyBot::Server#restart will eventually blow up the stack, needs to either abort after a certain amount of tries or something else.

 def restart!(wait = 1)
      if @async
        start_async
      else
        start!
      end
    rescue StandardError => e
      sleep wait
      logger.error "#{e.message}, reconnecting in #{wait} second(s)."
      logger.debug e
      restart! [wait * 2, 60].min
    end

NotImplementedError kills bot on `bot`

16:19:02 worker.1 | rake aborted!
16:19:02 worker.1 | NotImplementedError: bot
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/commands/base.rb:65:in `block in invoke'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/commands/base.rb:54:in `each_pair'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/commands/base.rb:54:in `invoke'
16:19:02 worker.1 | /Users/dblock/source/slack-gamebot/dblock/config/initializers/slack-ruby-bot/commands/base.rb:10:in `invoke'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/hooks/message.rb:10:in `block in message'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/hooks/message.rb:10:in `each'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/hooks/message.rb:10:in `detect'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/hooks/message.rb:10:in `message'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-bot-0.6.0/lib/slack-ruby-bot/server.rb:96:in `block (2 levels) in client'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-client-0.5.2/lib/slack/real_time/client.rb:146:in `call'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-client-0.5.2/lib/slack/real_time/client.rb:146:in `block in dispatch'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-client-0.5.2/lib/slack/real_time/client.rb:145:in `each'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-client-0.5.2/lib/slack/real_time/client.rb:145:in `dispatch'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/slack-ruby-client-0.5.2/lib/slack/real_time/client.rb:98:in `block (2 levels) in run_loop'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `call'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `block in emit'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `each'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `emit'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/faye-websocket-0.10.2/lib/faye/websocket/api/event_target.rb:45:in `dispatch_event'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/faye-websocket-0.10.2/lib/faye/websocket/api.rb:106:in `receive_message'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/faye-websocket-0.10.2/lib/faye/websocket/api.rb:40:in `block in initialize'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `call'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:39:in `block in emit'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `each'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/event_emitter.rb:38:in `emit'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/hybi.rb:400:in `emit_message'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/hybi.rb:383:in `emit_frame'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/hybi.rb:121:in `parse'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/websocket-driver-0.6.3/lib/websocket/driver/client.rb:63:in `parse'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/faye-websocket-0.10.2/lib/faye/websocket/api.rb:145:in `parse'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/faye-websocket-0.10.2/lib/faye/websocket/client.rb:71:in `receive_data'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `run_machine'
16:19:02 worker.1 | /Users/dblock/.rvm/gems/ruby-2.2.4/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `run'
16:19:02 worker.1 | /Users/dblock/source/slack-gamebot/dblock/tasks/ps.rake:19:in `block (3 levels) in <top (required)>'
16:19:02 worker.1 | Tasks: TOP => ps:worker:run
16:19:02 worker.1 | (See full trace by running task with --trace)
16:19:02 worker.1 | exited with code 1
16:19:02 system   | sending SIGTERM to all processes

Support free-formed routes.

Sometimes I just want to say it, like What's the weather tomorrow?, so this would be a route like /What's the weather (?<when>.*)?/. This should be called match along with command and operator.

data.user contains the Slack ID, not the user's name

example code:

  command 'start' do |client, data, match|
      client.say(text: "hi #{data.user}, a new game started. have players set their amounts.", channel: data.channel)
      @game = Game.new
    end 
  end

The output in the chat looks like the following:

"hi U0Q7SU64S, a new game started. have players set their amounts."

Ruby 2.3.0
slack-ruby-bot 0.6.2

Q: Long-running operation is blocking message?

Hello!

I'm putting together a Slack client (shock horror) and running into the following issue. If there's a long-running operation in my bot, I want to post a message before it starts, asking the user to wait:

class LongRunningOperation < SlackRubyBot::Commands::Base
  command 'run' do |client, data, _match|
    client.say(channel: data.channel, text: 'Please wait')
    result = long_running_operation
    client.say(channel: data.channel, text: result)
  end

  def self.long_running_operation
    sleep 10
    "Done!"
  end
end

When I talk to my bot, I am expecting "Please wait" to show first, and then "Done!" 10 seconds later. However, I receive both messages at the same time, after 10 seconds.

Is there something different I should be doing, or is this a known issue?

undefined method `strip!' for nil:NilClass

Possibly when a message is deleted.

2015-07-21T16:02:16.628243+00:00 app[web.1]: E, [2015-07-21T16:02:16.628006 #3] ERROR -- : undefined method `strip!' for nil:NilClass (NoMethodError)
2015-07-21T16:02:16.628248+00:00 app[web.1]: /app/config/initializers/slack-ruby-bot/hooks/message.rb:6:in `message'
2015-07-21T16:02:16.628246+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/bundler/gems/slack-ruby-bot-4bbcc92fd6c1/lib/slack-ruby-bot/hooks/message.rb:8:in `message'

Extend command and operator with block.

Instead of implementing call, I would like to be able to:

  class Ping < SlackRubyBot::Commands::Base
    operator 'ping' do |command, arguments|
      send_message data.channel, 'pong'
    end
  end

This will work well with match, see #5.

Disconnect not handled

This happened on slack-gamebot. It died.

2015-09-14T05:21:31.554035+00:00 app[web.1]: I, [2015-09-14T05:21:31.553943 #3]  INFO -- : Successfully connected to https://artsy.slack.com/.
2015-09-14T20:22:45.274731+00:00 app[web.1]: can't connect
2015-09-14T20:22:45.274747+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/faraday/response/raise_error.rb:8:in `on_complete'
2015-09-14T20:22:45.274749+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:9:in `block in call'
2015-09-14T20:22:45.274751+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:57:in `on_complete'
2015-09-14T20:22:45.274752+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:8:in `call'
2015-09-14T20:22:45.274753+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/response.rb:8:in `call'
2015-09-14T20:22:45.274755+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/request/url_encoded.rb:15:in `call'
2015-09-14T20:22:45.274756+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/request/multipart.rb:14:in `call'
2015-09-14T20:22:45.274757+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/rack_builder.rb:139:in `build_response'
2015-09-14T20:22:45.274758+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/connection.rb:377:in `run_request'
2015-09-14T20:22:45.274760+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/faraday-0.9.1/lib/faraday/connection.rb:177:in `post'
2015-09-14T20:22:45.274761+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/faraday/request.rb:25:in `request'
2015-09-14T20:22:45.274762+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/faraday/request.rb:10:in `post'
2015-09-14T20:22:45.274763+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/web/api/endpoints/rtm.rb:15:in `rtm_start'
2015-09-14T20:22:45.274764+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/real_time/client.rb:39:in `block in start!'
2015-09-14T20:22:45.274766+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `call'
2015-09-14T20:22:45.274767+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `run_machine'
2015-09-14T20:22:45.274768+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/eventmachine-1.0.8/lib/eventmachine.rb:193:in `run'
2015-09-14T20:22:45.274769+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-client-0.2.1/lib/slack/real_time/client.rb:38:in `start!'
2015-09-14T20:22:45.274770+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:46:in `block in start!'
2015-09-14T20:22:45.274771+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:45:in `loop'
2015-09-14T20:22:45.274772+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:45:in `start!'
2015-09-14T20:22:45.274773+00:00 app[web.1]: /app/vendor/bundle/ruby/2.2.0/gems/slack-ruby-bot-0.4.2/lib/slack-ruby-bot/app.rb:28:in `run'
2015-09-14T20:22:45.274775+00:00 app[web.1]: config.ru:7:in `block (2 levels) in <main>'

Are slash commands supported?

Just started using this gem, fantastic!

I use @bot [command] but would like to use /[command] just like /giphy [gifname]

Is this supported? Thanks!

How should I call start_async on Rails?

I'm stuck with implementing ruby-slack-bot on rails for starting multiple bots.
Like this, I run bundle exec rails runner bin/slack_bot_server.rb with below code, but it didn't run at all.

# bin/slack_bot_server.rb
SlackBotServer::Service.instance.start_from_database!

Could you give me a hint for starting ruby-slack-bot on Rails?
Thank you

Re-use the Client for the web server

Hey,

I have been playing around with the framework over the weekend and I want to implement a bot that listens to chat but also has some webhooks (i.e. hook it up to GitHub to react to events that happen with our pull-requests). I set it up like you did in the tutorial and everything worked when I stayed within the Slack world ...

But when I wanted to send something to the chat room from the web server I ran into an issue. The client property on the SlackMathbot::Bot is private and I cannot access it from the sinatra app. In #20 and #31 you basically suggest to use Slack::Web::Client.new directly... But since my bot already has a connection to the server open, I don't really see why it should be needed to create another connection to the Slack server.

Am I just missing a way to access the client directly?

How to properly reimplement base hooks?

Say I want to perform an action once I have confirmation that the client is connected.

Looking at the api documentation it seems like the hello event is exactly what I want, so I implement a simple hook for that event and I include it in my class (in my case a subclass of App).

When I run the code though - a simple logger line to check that it's being called properly - I get two calls instead of one. The reason is, Hooks::Base pushes the included class name into an array, and Server already includes a base Hello hook, so the hook stack looks like

[
    [0] :hello,
    [1] :message,
    [2] :hello
]

However, because these are just methods on the App object (and each new include will overwrite these) we basically end up with the same hook method being called twice.

So, it might have been the way I did it (extending App, including my own hook) and that might not be the right approach - I couldn't find another though. Or, it could be that the Hook::Base simply dedups the hook stack and this gets sorted .

Not sure, though I run this by you first :)

NoMethodError: undefined method `scan' for SlackMathbot::Commands::Calculate:Class

scan method does not seem to work

I followed the tutorial exactly and was able to run foreman and successfully test the bot in Slack. Then I tried out the scan method in my calculate.rb

module SlackMathbot
  module Commands
    class Calculate < SlackRubyBot::Commands::Base
      scan(/hey/) do |client, data, _match|
        client.say(channel: data.channel, text: '4')
      end
    end
  end
end

And when trying to run foreman I get

19:39:54 web.1  | ! Unable to load application: NoMethodError: undefined method `scan' for SlackMathbot::Commands::Calculate:Class
19:39:54 web.1  | /Users/brandonchu/hackoff/slackbot/slack-mathbot/commands/calculate.rb:4:in `<class:Calculate>': undefined method `scan' for SlackMathbot::Commands::Calculate:Class (NoMethodError)
19:39:54 web.1  |   from /Users/brandonchu/hackoff/slackbot/slack-mathbot/commands/calculate.rb:3:in `<module:Commands>'
19:39:54 web.1  |   from /Users/brandonchu/hackoff/slackbot/slack-mathbot/commands/calculate.rb:2:in `<module:SlackMathbot>'
19:39:54 web.1  |   from /Users/brandonchu/hackoff/slackbot/slack-mathbot/commands/calculate.rb:1:in `<top (required)>'
19:39:54 web.1  |   from /Users/brandonchu/hackoff/slackbot/slack-mathbot.rb:2:in `require'
19:39:54 web.1  |   from /Users/brandonchu/hackoff/slackbot/slack-mathbot.rb:2:in `<top (required)>'
19:39:54 web.1  |   from config.ru:6:in `require'
19:39:54 web.1  |   from config.ru:6:in `block in <main>'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/rack-1.6.4/lib/rack/builder.rb:55:in `instance_eval'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/rack-1.6.4/lib/rack/builder.rb:55:in `initialize'
19:39:54 web.1  |   from config.ru:in `new'
19:39:54 web.1  |   from config.ru:in `<main>'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/rack-1.6.4/lib/rack/builder.rb:49:in `eval'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/rack-1.6.4/lib/rack/builder.rb:49:in `new_from_string'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/rack-1.6.4/lib/rack/builder.rb:40:in `parse_file'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/puma-3.0.2/lib/puma/configuration.rb:310:in `load_rackup'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/puma-3.0.2/lib/puma/configuration.rb:239:in `app'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/puma-3.0.2/lib/puma/runner.rb:119:in `load_and_bind'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/puma-3.0.2/lib/puma/single.rb:84:in `run'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/puma-3.0.2/lib/puma/launcher.rb:173:in `run'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/puma-3.0.2/lib/puma/cli.rb:74:in `run'
19:39:54 web.1  |   from /Library/Ruby/Gems/2.0.0/gems/puma-3.0.2/bin/puma:10:in `<top (required)>'
19:39:54 web.1  |   from /usr/bin/puma:23:in `load'
19:39:54 web.1  |   from /usr/bin/puma:23:in `<main>'
19:39:54 web.1  | exited with code 1
19:39:54 system | sending SIGTERM to all processes

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.