Skip to content

Instantly share code, notes, and snippets.

@jules2689
Last active April 22, 2019 18:35
Show Gist options
  • Save jules2689/5ed58e6c1bfe161d682ce22e54f22e40 to your computer and use it in GitHub Desktop.
Save jules2689/5ed58e6c1bfe161d682ce22e54f22e40 to your computer and use it in GitHub Desktop.
Outline of message/greetings handling in a slack bot
require 'yaml'
require 'redis'
require 'slack_communication_service.rb'
module Events
class JoinedChannel < ::SlackEvent
private
def greetings
@greetings ||= YAML.load_file Rails.root.join('config', 'greetings.yml')
end
def process
channel_name = channel_name(@params[:channel])
return if greetings.dig(channel_name, 'text').nil?
Responses::Ephemeral.new(
channel: channel_name,
user: @params[:user],
text: greetings.dig(channel_name, 'text'),
as_user: false
)
end
end
end
require 'yaml'
require 'redis'
require 'slack_communication_service.rb'
module Events
class Message < ::SlackEvent
private
def messages
@messages ||= YAML.load_file Rails.root.join('config', 'messages.yml')
end
def process
user_id = @params[:user]
channel_name = channel_name(@params[:channel])
channel_message_entries = messages[channel_name]
return [] if channel_message_entries.blank?
# Return if we don't have an entry, the text doesn't match the format,
responses = channel_message_entries.map do |channel_message_entry|
handle_channel_message_entry(channel_message_entry, channel_name, user_id)
end
responses.compact
end
def handle_channel_message_entry(channel_message_entry, channel_name, user_id)
channel_message_entry = channel_message_entry.with_indifferent_access
return if channel_message_entry['text'].nil?
return unless @params[:text] =~ /#{channel_message_entry[:format]}/
# Keys for redis
user_channel_key = "user_message_#{@params[:channel]}_#{user_id}_#{channel_message_entry[:format]}"
channel_key = "message_#{@params[:channel]}_#{channel_message_entry[:format]}"
# Return if within "dont_repeat_within" time period or "dont_repeat_for_user_within" time period
# Depending on the settings
dont_repeat_within = channel_message_entry[:dont_repeat_within].to_i
dont_repeat_for_user_within = channel_message_entry[:dont_repeat_for_user_within].to_i
return if dont_repeat_within > 0 && redis.get(channel_key)
return if dont_repeat_for_user_within > 0 && redis.get(user_channel_key)
# Set sent time for user and channel
# This does not mean both will be checked, but it is easier logic this way
# We should only ever set these keys if we are 100% going to send a message
redis.set(user_channel_key, "accessed at #{Time.now}")
redis.expire(user_channel_key, dont_repeat_for_user_within)
redis.set(channel_key, "accessed at #{Time.now}")
redis.expire(channel_key, dont_repeat_within)
message_hash = {
channel: channel_name,
text: channel_message_entry['text'],
as_user: false
}
if channel_message_entry[:ephemeral]
Responses::Ephemeral.new(message_hash.merge(user: user_id))
else
Responses::Message.new(message_hash)
end
end
end
end
# frozen_string_literal: true
require 'json'
class EventsController < SlackController
def receive
case params[:type]
when 'url_verification'
Rails.logger.info "Sending challenge to Slack"
render status: 200, json: { challenge: params[:challenge] }
when 'event_callback'
RequestsJob.perform_later(params[:event].symbolize_keys)
head :ok
end
end
end
CHANNEL_NAME:
text: |
This is a multi line message
You can write whatever you want here and it will be told to the user on joining your channel

Adding your own channel greeting

All you need to do is to add your own text in the greetings.yml file.

Example

CHANNEL_NAME:
  text: |
    This is a multi line message
    You can write whatever you want here and it will be told to the user on joining your channel
    Links to channels may not work, see below for help

Adding your own message responder

All you need to do is to add your own text in the messages.yml.

Each channel is an entry into the YAML with an array of possible messages. The format of the entry is as follows:

Keys

  • format: regex to match text. E.g. (accounts?) matches anything with the word account or accounts)
  • dont_repeat_within: In seconds. How long should we wait until repeating the same message in the channel?
  • dont_repeat_for_user_within: In seconds. How long should we wait until repeating the same message for the same user in the channel?
  • text: What to respond with
  • ephemeral: Optional. Should this message be visible to only the user who asked the question (true) or everyone (false, default)

Example

CHANNEL_NAME:
  -
    format: <REGEX THAT MATCHES THE TEXT>
    dont_repeat_within: 1800 # 30M
    dont_repeat_for_user_within: 86400 # 24H
    text: |
      This is a multi line message
      You can write whatever you want here and it will be told to the user on matching messages
      Links to channels may not work, see below for help

Links to slack channels

Links to slack channels in messages or greetings may not work. You need to use the format <#ID_NUMBER|CHANNEL_NAME> to get it to link correctly.

To find the id:

  1. Right click the timestamp of a message in the channel you want to link to

image

  1. The link I got (https://slack-org.slack.com/archives/CABCDE/p1999999) contains the channel after archive. In this case, it would be CABCDE.

  2. The link I would use is <#CABCDE|channel_name> in the text.

CHANNEL_NAME:
-
format: <REGEX THAT MATCHES THE TEXT>
dont_repeat_within: 1800 # 30M
dont_repeat_for_user_within: 86400 # 24H
text: |
This is a multi line message
You can write whatever you want here and it will be told to the user on matching messages
class RequestsJob < ApplicationJob
queue_as :events_default
def perform(event_hash)
event = case event_hash[:type]
when 'member_joined_channel'
Events::JoinedChannel.new(event_hash)
when 'message'
Events::Message.new(event_hash)
else
Rails.logger.info "This slack bot currently does not support the event type: " + event_hash[:type]
return
end
event.responses.each(&:post)
end
end
require 'slack_communication_service.rb'
module Responses
class Ephemeral < ::SlackResponse
def post
Services::SlackCommunicationService.send_http_post('chat.postEphemeral', body_hash)
end
end
end
require 'slack_communication_service.rb'
module Responses
class Message < ::SlackResponse
def post
Services::SlackCommunicationService.send_http_post('chat.postMessage', body_hash)
end
end
end
require 'faraday'
require 'json'
module Services
class SlackCommunicationService
def self.send_http_post(event_type, body_hash)
http_client.post("/api/#{event_type}?token=#{token}", body_hash.to_query)
end
def self.send_channel_info_get(channel)
api_response = http_client.get "/api/channels.info?token=#{token}&channel=#{channel}"
JSON.parse(api_response.body)
end
def self.token
ENV['SLACK_OAUTH_TOKEN']
end
def self.http_client
@http ||= Faraday.new(url: 'https://slack.com') do |faraday|
faraday.request :url_encoded
faraday.response :logger do | logger |
logger.filter(/(token=)([\w-]+)/,'\1[REMOVED]')
end
faraday.adapter Faraday.default_adapter
end
end
private_class_method :token, :http_client
end
end
class SlackEvent
attr_reader :params
def initialize(params)
@params = params
end
def responses
log_event
[process].flatten.compact
end
private
def redis
@redis ||= Redis.new(url: ENV['REDIS_URL'])
end
def channel_name(channel_id)
if redis.exists(channel_id)
redis.get(channel_id)
else
Rails.logger.info "caching name to redis"
channel_name = Services::SlackCommunicationService.send_channel_info_get(channel_id).dig('channel', 'name')
redis.set(channel_id, channel_name)
channel_name
end
end
def process
raise 'Not Implemented'
end
def log_event
calling_class = self.class.name.sub("::", ".")
StatsD.increment("#{calling_class}.process.measure", tags: ["params:#{@params.inspect}"])
end
end
class SlackResponse
attr_reader :body_hash
def initialize(body_hash)
@body_hash = body_hash
end
def post
raise NotImplementedError
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment