Skip to content

Commit fde6122

Browse files
mateusdeapABizzinotto
authored andcommitted
Implement puzzle suggestion slackbot
1 parent 6404f6e commit fde6122

8 files changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
class Slack::ApplicationController < ApplicationController
2+
skip_before_action :verify_authenticity_token
3+
before_action :valid_slack_request?
4+
5+
private
6+
7+
def valid_slack_request?
8+
@verified ||= verify_slack_signature
9+
end
10+
11+
def verify_slack_signature
12+
timestamp = request.headers["X-Slack-Request-Timestamp"]
13+
signature = request.headers["X-Slack-Signature"]
14+
15+
if Time.now.to_i - timestamp.to_i > 300
16+
@verified = false
17+
return
18+
end
19+
20+
base_string = "v0:#{timestamp}:#{request.raw_post}"
21+
my_signature = "v0=" + OpenSSL::HMAC.hexdigest(
22+
"SHA256",
23+
ENV["SLACK_SIGNING_SECRET"],
24+
base_string
25+
)
26+
27+
ActiveSupport::SecurityUtils.secure_compare(signature, my_signature)
28+
end
29+
30+
def slack_client
31+
@slack_client ||= SlackClient::Client.instance
32+
end
33+
34+
def open_view(view, trigger_id:)
35+
slack_client = SlackClient::Client.instance
36+
slack_client.views_open view: view, trigger_id: trigger_id
37+
rescue Slack::Web::Api::Errors::SlackError => e
38+
Rails.logger.error "Failed to open Slack modal: #{e.message} #{e.response_metadata}"
39+
head :unprocessable_entity
40+
end
41+
42+
def send_message(message, channel_id:)
43+
SlackClient::Client.instance.chat_postMessage(channel: channel_id, blocks: message)
44+
rescue Slack::Web::Api::Errors::SlackError
45+
head :unprocessable_entity
46+
end
47+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Slack::CommandsController < Slack::ApplicationController
2+
def new_puzzle
3+
puts "I'm a new puzzle!"
4+
view = SlackClient::Views::PuzzleForm.new.create
5+
open_view(view, trigger_id: params[:trigger_id])
6+
end
7+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class Slack::PuzzlesController < Slack::ApplicationController
2+
def create
3+
payload = JSON.parse(params[:payload])
4+
user_id = payload["user"]["id"]
5+
values = payload["view"]["state"]["values"]
6+
question = values.dig("question", "question", "value")
7+
answer = values.dig("answer", "answer", "selected_option", "value")
8+
explanation = values.dig("explanation", "explanation", "value")
9+
link = values.dig("link", "link", "value")
10+
puzzle = Puzzle.new(question:, answer:, explanation:, link:, status: :pending)
11+
12+
view = if puzzle.save
13+
SlackClient::Views::Success.new.create
14+
else
15+
SlackClient::Views::Failure.new.create
16+
end
17+
18+
render json: { response_action: "update", view: view }, status: :ok
19+
end
20+
end

app/lib/slack_client/client.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
require "singleton"
2+
3+
module SlackClient
4+
# This class is a wrapper around the Slack::Web::Client class that allows us to use the
5+
# singleton pattern to create a single instance of the Slack client.
6+
# This is useful because we can reuse the same client instance across the application,
7+
# which can help reduce the number of connections to the Slack API.
8+
#
9+
# Example:
10+
# slack_client = SlackClient::Client.instance
11+
#
12+
class Client
13+
include Singleton
14+
15+
def initialize
16+
@slack_client = Slack::Web::Client.new(token: ENV["SLACK_TOKEN"])
17+
end
18+
19+
private
20+
21+
def method_missing(method, *args, &block)
22+
if @slack_client.respond_to?(method)
23+
@slack_client.send(method, *args, &block)
24+
else
25+
super
26+
end
27+
rescue Slack::Web::Api::Errors::SlackError => e
28+
Rails.logger.error "Failed to complete Slack request: #{e.message} #{e.response_metadata}"
29+
raise
30+
end
31+
32+
def respond_to_missing?(method, include_private = false)
33+
@slack_client.respond_to?(method) || super
34+
end
35+
end
36+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module SlackClient
2+
module Views
3+
class Failure
4+
def create
5+
blocks = Slack::BlockKit.blocks do |block|
6+
block.section do |section|
7+
section.plain_text text: ":blob-no: Sorry, I could not register the puzzle suggestion."
8+
end
9+
end
10+
Slack::BlockKit.modal blocks: blocks, title: "Uh oh..." do |modal|
11+
modal.close text: "Close"
12+
end.as_json
13+
end
14+
end
15+
end
16+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module SlackClient
2+
module Views
3+
class PuzzleForm
4+
def create
5+
blocks = Slack::BlockKit.blocks do |blocks|
6+
blocks.input label: "What is the Puzzle question?", block_id: "question" do |input|
7+
input.plain_text_input action_id: "question", multiline: true
8+
end
9+
10+
blocks.input label: "Answer", block_id: "answer" do |input|
11+
input.radio_buttons action_id: "answer" do |radio_button|
12+
radio_button.option text: "Ruby", value: "ruby"
13+
radio_button.option text: "Rails", value: "rails"
14+
end
15+
end
16+
17+
blocks.input label: "Explanation", block_id: "explanation" do |input|
18+
input.plain_text_input action_id: "explanation", multiline: true
19+
end
20+
21+
blocks.input label: "Link to documentation", block_id: "link", optional: true do |input|
22+
input.plain_text_input action_id: "link"
23+
end
24+
end
25+
26+
Slack::BlockKit.modal blocks: blocks, external_id: "puzzle_form", title: "Suggest a Puzzle" do |modal|
27+
modal.submit text: "Submit"
28+
modal.close text: "Close"
29+
end.as_json
30+
end
31+
end
32+
end
33+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module SlackClient
2+
module Views
3+
class Success
4+
def create
5+
blocks = Slack::BlockKit.blocks do |block|
6+
block.section do |section|
7+
section.mrkdwn text: ":white_check_mark: All set! Puzzle submitted!"
8+
end
9+
end
10+
Slack::BlockKit.modal blocks: blocks, title: "Thank you!" do |modal|
11+
modal.close text: "Close"
12+
end.as_json
13+
end
14+
end
15+
end
16+
end

config/routes.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@
1818

1919
# Defines the root path route ("/")
2020
root "puzzles#index"
21+
22+
namespace :slack do
23+
post :new_puzzle, module: "slack", to: "commands#new_puzzle"
24+
post :puzzle, module: "slack", to: "puzzles#create"
25+
end
2126
end

0 commit comments

Comments
 (0)