class Slacks::Connection

Constants

EVENT_CHANNEL_CREATED
EVENT_GROUP_JOINED
EVENT_MESSAGE
EVENT_USER_JOINED
MAX_PAGES
SEND_MESSAGE_PARAMS

Attributes

bot[R]
conversation_ids_by_name[R]
conversations_by_id[R]
team[R]
token[R]
typing_speed[RW]
user_id_by_name[R]
user_ids_dm_ids[R]
users_by_id[R]
websocket[R]
websocket_url[R]

Public Class Methods

new(token, options={}) click to toggle source
# File lib/slacks/connection.rb, line 18
def initialize(token, options={})
  raise ArgumentError, "Missing required parameter: 'token'" if token.nil? or token.empty?
  @token = token
  @typing_speed = options.fetch(:typing_speed, 100.0)

  @user_ids_dm_ids = {}
  @users_by_id = {}
  @user_id_by_name = {}
  @conversations_by_id = {}
  @conversation_ids_by_name = {}
end

Public Instance Methods

add_reaction(emojis, message) click to toggle source
# File lib/slacks/connection.rb, line 68
def add_reaction(emojis, message)
  Array(emojis).each do |emoji|
    api("reactions.add",
      name: emoji.gsub(/^:|:$/, ""),
      channel: message.channel.id,
      timestamp: message.timestamp)
  end
end
can_see?(channel) click to toggle source
# File lib/slacks/connection.rb, line 160
def can_see?(channel)
  channel_id = to_channel_id(channel)
  channel_id && !channel_id.empty?
rescue ArgumentError
  false
end
channels() click to toggle source
# File lib/slacks/connection.rb, line 151
def channels
  channels = user_id_by_name.keys + conversation_ids_by_name.keys
  if channels.empty?
    fetch_conversations!
    fetch_users!
  end
  channels
end
find_channel(id) click to toggle source
# File lib/slacks/connection.rb, line 169
def find_channel(id)
  case id
  when /^U/ then find_user(id)
  when /^D/
    user = find_user(get_user_id_for_dm(id))
    Slacks::Channel.new self, {
      "id" => id,
      "is_im" => true,
      "name" => user.username }
  else
    Slacks::Channel.new(self, find_conversation(id))
  end
end
find_conversation(id) click to toggle source
# File lib/slacks/connection.rb, line 183
def find_conversation(id)
  conversations_by_id.fetch(id) do
    fetch_conversations!
    conversations_by_id.fetch(id) do
      raise ArgumentError, "Unable to find a conversation with the ID #{id.inspect}"
    end
  end
end
find_user(id) click to toggle source
# File lib/slacks/connection.rb, line 192
def find_user(id)
  user = users_by_id.fetch(id) do
    fetch_users!
    users_by_id.fetch(id) do
      raise ArgumentError, "Unable to find a user with the ID #{id.inspect}"
    end
  end
  Slacks::User.new(self, user)
end
find_user_by_nickname(nickname) click to toggle source
# File lib/slacks/connection.rb, line 202
def find_user_by_nickname(nickname)
  find_user to_user_id(nickname)
end
get_message(channel, ts) click to toggle source
# File lib/slacks/connection.rb, line 46
def get_message(channel, ts)
  params = {
    channel: to_channel_id(channel),
    timestamp: ts }
  api("reactions.get", **params)
end
listen!() click to toggle source
# File lib/slacks/connection.rb, line 95
def listen!
  response = api("rtm.start")
  store_context!(response)

  @websocket = Slacks::Driver.new
  websocket.connect_to websocket_url
  trigger "connected"

  websocket.on(:error) do |event|
    raise ConnectionError.new(event)
  end

  websocket.on(:message) do |data|
    case data["type"]
    when NilClass
      # Every event has a `type` property:
      # https://api.slack.com/rtm#events
      # If an event comes across without
      # one, we'll skill it.
      next

    when EVENT_GROUP_JOINED, EVENT_CHANNEL_CREATED
      conversation = data["channel"]
      @conversations_by_id[conversation["id"]] = conversation
      @conversation_ids_by_name[conversation["name"]] = conversation["id"]

    when EVENT_USER_JOINED
      user = data["user"]
      @users_by_id[user["id"]] = user
      @user_id_by_name[user["name"]] = user["id"]

    when EVENT_MESSAGE
      # Don't respond to things that this bot said
      next if data["user"] == bot.id
      # ...or to messages with no text
      next if data["text"].nil? || data["text"].empty?
    end

    trigger data["type"], data
  end

  websocket.main_loop

rescue EOFError
  # Slack hung up on us, we'll ask for a new WebSocket URL and reconnect.
  trigger "error", "Websocket Driver received EOF; reconnecting"
  retry
end
listening?() click to toggle source
# File lib/slacks/connection.rb, line 89
def listening?
  !websocket.nil?
end
ping() click to toggle source
# File lib/slacks/connection.rb, line 84
def ping
  raise NotListeningError unless listening?
  websocket.ping
end
say(message, options={})
Alias for: send_message
send_message(message, options={}) click to toggle source
# File lib/slacks/connection.rb, line 32
def send_message(message, options={})
  channel = options.fetch(:channel) { raise ArgumentError, "Missing parameter :channel" }
  attachments = Array(options[:attachments])
  params = {
    channel: to_channel_id(channel),
    text: message,
    as_user: true, # post as the authenticated user (rather than as slackbot)
    link_names: 1} # find and link channel names and user names
  params.merge!(attachments: MultiJson.dump(attachments)) if attachments.any?
  params.merge!(options.select { |key, _| SEND_MESSAGE_PARAMS.member?(key) })
  api("chat.postMessage", **params)
end
Also aliased as: say
typing_on(channel) click to toggle source
# File lib/slacks/connection.rb, line 79
def typing_on(channel)
  raise NotListeningError unless listening?
  websocket.write MultiJson.dump(type: "typing", channel: to_channel_id(channel))
end
update_message(ts, message, options={}) click to toggle source
# File lib/slacks/connection.rb, line 53
def update_message(ts, message, options={})
  channel = options.fetch(:channel) { raise ArgumentError, "Missing parameter :channel" }
  attachments = Array(options[:attachments])
  params = {
    ts: ts,
    channel: to_channel_id(channel),
    text: message,
    as_user: true, # post as the authenticated user (rather than as slackbot)
    link_names: 1} # find and link channel names and user names
  params.merge!(attachments: MultiJson.dump(attachments)) if attachments.any?
  params.merge!(options.select { |key, _| [:username, :as_user, :parse, :link_names,
    :unfurl_links, :unfurl_media, :icon_url, :icon_emoji].member?(key) })
  api("chat.update", **params)
end
user_exists?(username) click to toggle source
# File lib/slacks/connection.rb, line 208
def user_exists?(username)
  return false if username.nil?
  user_id = to_user_id(username)
  user_id && !user_id.empty?
rescue ArgumentError
  false
end
users() click to toggle source
# File lib/slacks/connection.rb, line 216
def users
  fetch_users! if @users_by_id.empty?
  @users_by_id.values
end

Private Instance Methods

api(command, page_limit: MAX_PAGES, **params) click to toggle source
# File lib/slacks/connection.rb, line 307
def api(command, page_limit: MAX_PAGES, **params)
  params_with_token = params.merge(token: token)
  response = api_post command, params_with_token
  fetched_pages = 1
  cursor = response.dig("response_metadata", "next_cursor")
  while cursor && !cursor.empty? && fetched_pages < page_limit do
    api_post(command, params_with_token.merge(cursor: cursor)).each do |key, value|
      if value.is_a?(Array)
        response[key].concat value
      elsif value.is_a?(Hash)
        response[key].merge! value
      else
        response[key] = value
      end
    end
    fetched_pages += 1
    cursor = response.dig("response_metadata", "next_cursor")
  end
  response
end
api_post(command, params) click to toggle source
# File lib/slacks/connection.rb, line 328
def api_post(command, params)
  response = http.post(command, params)
  response = MultiJson.load(response.body)
  unless response["ok"]
    response["error"].split(/,\s*/).each do |error_code|
      raise ::Slacks::Response.fetch(error_code).new(command, params, response)
    end
  end
  response

rescue MultiJson::ParseError
  $!.additional_information[:response_body] = response.body
  $!.additional_information[:response_status] = response.status
  raise
end
fetch_conversations!() click to toggle source
# File lib/slacks/connection.rb, line 270
def fetch_conversations!
  conversations, ims = api("conversations.list", types: "public_channel,private_channel,mpim,im")["channels"].partition { |attrs| attrs["is_channel"] || attrs["is_group"] }
  user_ids_dm_ids.merge! Hash[ims.map { |attrs| attrs.values_at("user", "id") }]
  @conversations_by_id = Hash[conversations.map { |attrs| [ attrs.fetch("id"), attrs ] }]
  @conversation_ids_by_name = Hash[conversations.map { |attrs| [ attrs["name"], attrs["id"] ] }]
end
fetch_users!() click to toggle source
# File lib/slacks/connection.rb, line 277
def fetch_users!
  response = api("users.list")
  @users_by_id = response["members"].each_with_object({}) { |attrs, hash| hash[attrs["id"]] = attrs }
  @user_id_by_name = Hash[response["members"].map { |attrs| ["@#{attrs["name"]}", attrs["id"]] }]
end
get_dm_for_user_id(user_id) click to toggle source
# File lib/slacks/connection.rb, line 262
def get_dm_for_user_id(user_id)
  user_ids_dm_ids[user_id] ||= begin
    response = api("conversations.open", users: user_id)
    response["channel"]["id"]
  end
end
get_dm_for_username(name) click to toggle source
# File lib/slacks/connection.rb, line 258
def get_dm_for_username(name)
  get_dm_for_user_id to_user_id(name)
end
get_user_id_for_dm(dm) click to toggle source
# File lib/slacks/connection.rb, line 295
def get_user_id_for_dm(dm)
  user_id = user_ids_dm_ids.key(dm)
  unless user_id
    fetch_conversations!
    user_id = user_ids_dm_ids.key(dm)
  end
  raise ArgumentError, "Unable to find a user for the direct message ID #{dm.inspect}" unless user_id
  user_id
end
http() click to toggle source
# File lib/slacks/connection.rb, line 344
def http
  @http ||= Faraday.new(url: "https://slack.com/api").tap do |connection|
    connection.response :raise_error
  end
end
missing_conversation!(name) click to toggle source
# File lib/slacks/connection.rb, line 285
def missing_conversation!(name)
  raise ArgumentError, "Couldn't find a conversation named #{name}"
end
missing_user!(name) click to toggle source
# File lib/slacks/connection.rb, line 289
def missing_user!(name)
  raise ArgumentError, "Couldn't find a user named #{name}"
end
store_context!(response) click to toggle source
# File lib/slacks/connection.rb, line 234
def store_context!(response)
  @websocket_url = response.fetch("url")
  @bot = BotUser.new(response.fetch("self"))
  @team = Team.new(response.fetch("team"))

  @conversations_by_id = Hash[response.fetch("channels").map { |attrs| [ attrs.fetch("id"), attrs ] }]
  @conversation_ids_by_name = Hash[response.fetch("channels").map { |attrs| [ attrs["name"], attrs["id"] ] }]
end
to_channel_id(name) click to toggle source
# File lib/slacks/connection.rb, line 245
def to_channel_id(name)
  return name.id if name.is_a?(Slacks::Channel)
  return name if name =~ /^[DGC]/ # this already looks like a channel id
  return get_dm_for_username(name) if name.start_with?("@")

  name = name.gsub(/^#/, "") # Leading hashes are no longer a thing in the conversations API
  conversation_ids_by_name[name] || fetch_conversations![name] || missing_conversation!(name)
end
to_user_id(name) click to toggle source
# File lib/slacks/connection.rb, line 254
def to_user_id(name)
  user_id_by_name[name] || fetch_users![name] || missing_user!(name)
end