class SlackbotFrd::SlackConnection
Constants
- APP_ROOT
- FILE_DIR
- FILE_PATH
- LOG_FILE
- PID_FILE_NAME
- PING_INTERVAL_SECONDS
Attributes
token[RW]
Public Class Methods
new(token:, errors_file:, monitor_connection: true)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 28 def initialize(token:, errors_file:, monitor_connection: true) log_and_add_to_error_file("No token passed to #{self.class}") unless token @token = token @errors_file = errors_file @monitor_connection = monitor_connection @event_id = 0 @on_connected_callbacks = [] @on_disconnected_callbacks = [] @on_message_callbacks = UserChannelCallbacks.new @on_channel_left_callbacks = UserChannelCallbacks.new @on_channel_joined_callbacks = UserChannelCallbacks.new # These hashes are used to map ids to names efficiently @user_id_to_name = {} @user_name_to_id = {} @channel_id_to_name = {} @channel_name_to_id = {} @pong_received = true SlackbotFrd::Log.debug("Done initializing #{self.class}") end
Public Instance Methods
channel_id_to_name(channel_id)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 298 def channel_id_to_name(channel_id) unless @channel_id_to_name && @channel_id_to_name.key?(channel_id) refresh_channel_info end unless @channel_id_to_name.include?(channel_id) SlackbotFrd::Log.warn( "#{self.class}: Channel id '#{channel_id}' not found" ) end @channel_id_to_name[channel_id] end
channel_ids(_force_refresh = false)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 266 def channel_ids(_force_refresh = false) @user_id_to_name.keys end
channel_name_to_id(channel_name)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 310 def channel_name_to_id(channel_name) return channel_name if channel_name == :any nc = normalize_channel_name(channel_name) unless @channel_name_to_id && @channel_name_to_id.key?(nc) refresh_channel_info end unless @channel_name_to_id.include?(nc) SlackbotFrd::Log.warn( "#{self.class}: Channel name '#{nc}' not found" ) end @channel_name_to_id[nc] end
channel_names(_force_refresh = false)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 270 def channel_names(_force_refresh = false) @channel_name_to_id.keys end
delete_message(channel:, timestamp:)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 176 def delete_message(channel:, timestamp:) SlackbotFrd::Log.debug("#{self.class}: Deleting message with timestamp '#{timestamp}' from channel '#{channel}'") resp = SlackbotFrd::SlackMethods::ChatDelete.delete( token: @token, channel: channel_name_to_id(channel), timestamp: timestamp ) SlackbotFrd::Log.debug("#{self.class}: Received response: #{resp}") end
event_id()
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 90 def event_id @event_id += 1 @event_id end
im_channel_for_user(user:)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 231 def im_channel_for_user(user:) SlackbotFrd::Log.debug( "#{self.class}: Opening or retrieving IM channel for user '#{user}'" ) resp = JSON.parse(SlackbotFrd::SlackMethods::ImOpen.openChannel( token: @token, user: user_name_to_id(user) )) SlackbotFrd::Log.debug("#{self.class}: Received response: #{resp}") return resp["channel"]["id"] if resp["channel"] resp end
invite_user(user:, channel:)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 203 def invite_user(user:, channel:) SlackbotFrd::Log.debug( "#{self.class}: Inviting user '#{user}' to channel '#{channel}'" ) resp = SlackbotFrd::SlackMethods::ChannelsInvite.invite( token: @token, user: user_name_to_id(user), channel: channel_name_to_id(channel) ) SlackbotFrd::Log.debug("#{self.class}: Received response: #{resp}") end
invite_user_to_group(user:, channel:)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 217 def invite_user_to_group(user:, channel:) SlackbotFrd::Log.debug( "#{self.class}: Inviting user '#{user}' to channel '#{channel}'" ) resp = SlackbotFrd::SlackMethods::GroupsInvite.invite( token: @token, user: user_name_to_id(user), channel: channel_name_to_id(channel) ) SlackbotFrd::Log.debug("#{self.class}: Received response: #{resp}") end
num_users_in_channel(channel)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 254 def num_users_in_channel(channel) users_in_channel(channel).count end
on_channel_joined(user: :any, channel: :any, &block)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 123 def on_channel_joined(user: :any, channel: :any, &block) wrap_user_or_channel_lookup_on_callback('on_message_channel_joined', user, channel) do u = user_name_to_id(user) c = channel_name_to_id(channel) @on_channel_joined_callbacks.add(user: u, channel: c, callback: block) end end
on_channel_left(user: :any, channel: :any, &block)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 113 def on_channel_left(user: :any, channel: :any, &block) wrap_user_or_channel_lookup_on_callback('on_message_channel_left', user, channel) do @on_channel_left_callbacks.add( user: user_name_to_id(user), channel: channel_name_to_id(channel), callback: block ) end end
on_close(&block)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 99 def on_close(&block) @on_disconnected_callbacks.push(block) end
on_connected(&block)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 95 def on_connected(&block) @on_connected_callbacks.push(block) end
on_message(user: :any, channel: :any, &block)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 103 def on_message(user: :any, channel: :any, &block) wrap_user_or_channel_lookup_on_callback('on_message', user, channel) do @on_message_callbacks.add( user: user_name_to_id(user), channel: channel_name_to_id(channel), callback: block ) end end
post_reaction(name:, channel: nil, timestamp: nil)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 188 def post_reaction(name:, channel: nil, timestamp: nil) SlackbotFrd::Log.debug( "#{self.class}: Posting reaction '#{name}' to channel '#{channel}' with timestamp '#{timestamp}'" ) resp = SlackbotFrd::SlackMethods::ReactionsAdd.add( token: @token, name: name, channel: channel_name_to_id(channel), timestamp: timestamp ) SlackbotFrd::Log.debug("#{self.class}: Received response: #{resp}") end
send_im(user:, message:, username: nil, avatar_emoji: nil, avatar_url: nil)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 131 def send_im(user:, message:, username: nil, avatar_emoji: nil, avatar_url: nil) send_message( channel: im_channel_for_user(user: user), message: message, username: username, avatar_emoji: avatar_emoji, avatar_url: avatar_url, channel_is_id: true ) end
send_message( channel:, message:, username: nil, avatar_emoji: nil, avatar_url: nil, channel_is_id: false, parse: 'full', thread_ts: nil, reply_broadcast: false )
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 142 def send_message( channel:, message:, username: nil, avatar_emoji: nil, avatar_url: nil, channel_is_id: false, parse: 'full', thread_ts: nil, reply_broadcast: false ) if (username && (avatar_emoji || avatar_url)) || parse != 'full' send_message_as_bot( channel: channel, message: message, username: username, avatar_emoji: avatar_emoji, avatar_url: avatar_url, channel_is_id: channel_is_id, parse: parse, thread_ts: thread_ts, reply_broadcast: reply_broadcast ) else send_message_as_user( channel: channel, message: message, channel_is_id: channel_is_id, thread_ts: thread_ts, reply_broadcast: reply_broadcast ) end end
start()
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 53 def start # Write pid file File.write(PID_FILE_NAME, "#{Process.pid}") SlackbotFrd::Log.info("#{self.class}: starting event machine") EM.run do begin wss_url = SlackbotFrd::SlackMethods::RtmStart.wss_url(@token) rescue SocketError => e log_and_add_to_error_file(socket_error_message(e)) end unless wss_url log_and_add_to_error_file( 'No Real Time stream opened by slack. Check for network connection and correct authentication token' ) return end @ws = Faye::WebSocket::Client.new(wss_url) @on_connected_callbacks.each { |callback| @ws.on(:open, &callback) } @on_disconnected_callbacks.each { |callback| @ws.on(:close, &callback) } @ws.on(:message) { |event| process_message_received(event) } # Clean up our pid file @ws.on(:close) { |_event| File.delete(PID_FILE_NAME) } # This should ensure that we get a pong back at least every # PING_INTERVAL_SECONDS, otherwise we die because our # connection is probably toast EM.add_periodic_timer(PING_INTERVAL_SECONDS) { check_ping } end SlackbotFrd::Log.info("#{self.class}: event machine loop terminated") end
user_id_to_name(user_id)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 274 def user_id_to_name(user_id) return user_id if user_id == :any || user_id == :bot unless @user_id_to_name && @user_id_to_name.key?(user_id) refresh_user_info end unless @user_id_to_name.include?(user_id) SlackbotFrd::Log.warn("#{self.class}: User id '#{user_id}' not found") end @user_id_to_name[user_id] end
user_ids(_force_refresh = false)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 258 def user_ids(_force_refresh = false) @user_id_to_name.keys end
user_info(username)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 324 def user_info(username) resp = SlackbotFrd::SlackMethods::UsersInfo.info( token: @token, user_id: user_name_to_id(username) ) end
user_name_to_id(user_name)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 285 def user_name_to_id(user_name) return user_name if user_name == :any || user_name == :bot unless @user_name_to_id && @user_name_to_id.key?(user_name) refresh_user_info end unless @user_name_to_id.include?(user_name) SlackbotFrd::Log.warn( "#{self.class}: User name '#{user_name}' not found" ) end @user_name_to_id[user_name] end
user_names(_force_refresh = false)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 262 def user_names(_force_refresh = false) @user_name_to_id.keys end
users_in_channel(channel)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 246 def users_in_channel(channel) a = SlackMethods::ChannelsInfo.members( token: @token, channel: channel_name_to_id(channel) ) a.map{ |id| user_id_to_name(id) } end
Private Instance Methods
check_ping()
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 579 def check_ping @pong_received ? send_ping : die_from_no_pong end
die_from_no_pong()
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 594 def die_from_no_pong SlackbotFrd::Log.error( 'Pong not received after 5 seconds. Stopping EM loop...' ) @ws.close EM.stop_event_loop end
extract_text(message)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 471 def extract_text(message) text = message['text'] text = message['message']['text'] if !text && message['message'] text end
extract_thread_ts(message)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 464 def extract_thread_ts(message) thread_ts = message['thread_ts'] thread_ts = message['message']['thread_ts'] if message['message'] && message['message']['thread_ts'] thread_ts end
extract_ts(message)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 457 def extract_ts(message) ts = message['ts'] ts = message['message']['ts'] if message['message'] && message['message']['ts'] ts end
extract_user(message)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 449 def extract_user(message) user = message['user'] user = :bot if message['subtype'] == 'bot_message' user = message['message']['user'] if !user && message['message'] user end
log_and_add_to_error_file(err)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 573 def log_and_add_to_error_file(err) SlackbotFrd::Log.error(err) File.append(@errors_file, "#{err}\n") end
normalize_channel_name(channel_name)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 416 def normalize_channel_name(channel_name) return channel_name[1..-1] if channel_name.start_with?('#') channel_name end
process_chat_message(message)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 478 def process_chat_message(message) SlackbotFrd::Log.verbose("#{self.class}: Processing chat message: #{message}") user = extract_user(message) channel = message['channel'] text = extract_text(message) ts = extract_ts(message) thread_ts = extract_thread_ts(message) unless user SlackbotFrd::Log.warn("#{self.class}: Chat message doesn't include user! message: #{message}") return end unless channel SlackbotFrd::Log.warn("#{self.class}: Chat message doesn't include channel! message: #{message}") return end @on_message_callbacks.where_include_all(user: user, channel: channel).each do |callback| # instance_exec allows the user to call send_message and send_message_as_user # without prefixing like this: slack_connection.send_message() # # However, it makes calling functions defined in the class not work, so # for now we aren't going to do it # #instance_exec(user_id_to_name(user), channel_id_to_name(channel), text, &callback) callback.call( user: user_id_to_name(user), channel: channel_id_to_name(channel), message: text, timestamp: ts, thread_ts: thread_ts ) end end
process_join_message(message)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 516 def process_join_message(message) SlackbotFrd::Log.verbose("#{self.class}: Processing join message: #{message}") user = message['user'] user = :bot if message['subtype'] == 'bot_message' channel = message['channel'] @on_channel_joined_callbacks.where_include_all(user: user, channel: channel).each do |callback| callback.call(user: user_id_to_name(user), channel: channel_id_to_name(channel)) end end
process_leave_message(message)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 527 def process_leave_message(message) SlackbotFrd::Log.verbose("#{self.class}: Processing leave message: #{message}") user = message['user'] user = :bot if message['subtype'] == 'bot_message' channel = message['channel'] @on_channel_left_callbacks.where_include_all(user: user, channel: channel).each do |callback| callback.call(user: user_id_to_name(user), channel: channel_id_to_name(channel)) end end
process_message_received(event)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 422 def process_message_received(event) message = JSON.parse(event.data) SlackbotFrd::Log.verbose("#{self.class}: Message received: #{message}") return unless message['type'] == 'message' if message['subtype'] == 'channel_join' process_join_message(message) elsif message['subtype'] == 'channel_leave' process_leave_message(message) elsif message['subtype'] == 'file_share' process_file_share(message) else process_chat_message(message) end end
refresh_channel_info()
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 549 def refresh_channel_info begin channels_list = SlackbotFrd::SlackMethods::ChannelsList.new(@token).connect @channel_id_to_name = channels_list.ids_to_names @channel_name_to_id = channels_list.names_to_ids im_channels_list = SlackbotFrd::SlackMethods::ImChannelsList.new(@token).connect @channel_id_to_name.merge!(im_channels_list.ids_to_names) @channel_name_to_id.merge!(im_channels_list.names_to_ids) groups_list = SlackbotFrd::SlackMethods::GroupsList.new(@token).connect @channel_id_to_name.merge!(groups_list.ids_to_names) @channel_name_to_id.merge!(groups_list.names_to_ids) rescue SocketError => e log_and_add_to_error_file(socket_error_message(e)) end end
refresh_user_info()
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 538 def refresh_user_info begin users_list = SlackbotFrd::SlackMethods::UsersList.new(@token).connect @user_id_to_name = users_list.ids_to_names @user_name_to_id = users_list.names_to_ids rescue SocketError => e log_and_add_to_error_file(socket_error_message(e)) end end
send_message_as_bot( channel:, message:, username: nil, avatar_emoji: nil, avatar_url: nil, parse: 'full', thread_ts: nil, reply_broadcast: false, channel_is_id: false )
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 368 def send_message_as_bot( channel:, message:, username: nil, avatar_emoji: nil, avatar_url: nil, parse: 'full', thread_ts: nil, reply_broadcast: false, channel_is_id: false ) SlackbotFrd::Log.debug( "#{self.class}: Sending message '#{message}' as bot user '#{username}' to channel '#{channel}'" ) channel_id = channel_is_id ? channel : channel_name_to_id(channel) resp = SlackbotFrd::SlackMethods::ChatPostMessage.postMessage( token: @token, channel: channel_id, message: message, username: username, avatar_emoji: avatar_emoji, avatar_url: avatar_url, parse: parse, thread_ts: thread_ts, reply_broadcast: reply_broadcast ) SlackbotFrd::Log.debug("#{self.class}: Received response: #{resp}") end
send_message_as_user( channel:, message:, channel_is_id: false, thread_ts: nil, reply_broadcast: false )
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 332 def send_message_as_user( channel:, message:, channel_is_id: false, thread_ts: nil, reply_broadcast: false ) unless @ws log_and_add_to_error_file( "Cannot send message '#{message}' as user to channel '#{channel}' because not connected to wss stream" ) end channel_id = channel_is_id ? channel : channel_name_to_id(channel) SlackbotFrd::Log.debug( "#{self.class}: Sending message '#{message}' as user to channel '#{channel}'" ) begin resp = @ws.send({ id: event_id, type: 'message', channel: channel_id, text: message, thread_ts: thread_ts, reply_broadcast: reply_broadcast }.to_json) SlackbotFrd::Log.debug("#{self.class}: Received response: #{resp}") rescue SocketError => e log_and_add_to_error_file(socket_error_message(e)) end end
send_ping()
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 584 def send_ping SlackbotFrd::Log.verbose('Sending ping') @pong_received = false @ws.ping do @pong_received = true SlackbotFrd::Log.verbose('Pong received') end end
socket_error_message(e)
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 568 def socket_error_message(e) "SocketError: Check your connection: #{e.message}" end
wrap_user_or_channel_lookup_on_callback(callback_name, user, channel) { || ... }
click to toggle source
# File lib/slackbot_frd/lib/slack_connection.rb, line 401 def wrap_user_or_channel_lookup_on_callback(callback_name, user, channel) begin return yield rescue SlackbotFrd::InvalidChannelError => _e log_and_add_to_error_file( "Unable to add #{callback_name} callback for channel '#{channel}'. Lookup of channel name to ID failed. Check network connection, and ensure channel exists and is accessible" ) rescue SlackbotFrd::InvalidUserError => _e log_and_add_to_error_file( "Unable to add #{callback_name} callback for user '#{user}'. Lookup of user name to ID failed. Check network connection and ensure user exists" ) end end