slackathon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1fcf29e8a5acc45438a8fc54a02f1ea217e44085
4
+ data.tar.gz: b01ce369c3c331327d2a2351322088417b58dc55
5
+ SHA512:
6
+ metadata.gz: cd294da8d4605b9aa7a08817e09b91b5775caa0a516585fa732d700ad9a047f4e05a486ad041dc656419ed5d95dca43c05a63a98610c578c3b7ed917d5dd8bb9
7
+ data.tar.gz: '05856998418c02103a51ae0e37a1cd69796b55d51b813c0788f71bdf908eb6e1ae09b60b0fd8cc996c18aa2610127b522d7e5a55a877945004f36ccf58f86224'
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Godfrey Chan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,187 @@
1
+ # Slackathon
2
+
3
+ A simple way to build Slack interations inside a Rails app. Check out our
4
+ [blog post](http://blog.skylight.io/the-slackathon) for the story behind
5
+ this gem!
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'slackathon'
13
+ ```
14
+
15
+ Also, add this to `config/routes.rb`
16
+
17
+ ```ruby
18
+ mount Slackathon::Engine => "/slack"
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ ```bash
24
+ $ bundle
25
+ ```
26
+
27
+ ## Development Workflow
28
+
29
+ ### Basic Slack Command
30
+
31
+ 1. Set up https://ngrok.com/
32
+
33
+ This gives you a public URL for Slack to reach your development machine.
34
+ On a Mac, you can also install it via `brew cask install ngrok`.
35
+
36
+ Assuming the Rails app is already running on port 3000, you can expose the
37
+ Rails app with `ngrok http 3000`, which should give you a public URL like
38
+ `http://00bea6f5.ngrok.io.`
39
+
40
+ 1. Create a Slack app at https://api.slack.com/apps
41
+
42
+ The "App Name" will be used as the display name when the app is replying to
43
+ commands.
44
+
45
+ To get everything working perfectly, you probably want to mirror all the
46
+ settings on the production app, the here are the most important bits.
47
+
48
+ 1. Go to "Your App > Settings > Install App" to add it to your Slack.
49
+
50
+ 1. Create your command under "Your App > Features > Slash Commands"
51
+
52
+ - The command is whatever you would type after the "/" in Slack, e.g.
53
+ `/monkey`.
54
+ - The request URL should be `http://00bea6f5.ngrok.io/slack/commands`.
55
+ - You probably want to turn on "Escape channels, users, and links sent to
56
+ your app" which will make it easier to parse @mentions and #channels.
57
+
58
+ 1. Create a `monkey_command.rb` in `app/slack`
59
+
60
+ - The name of the file (`monkey_command.rb`) and the class name (`MonkeyCommand`)
61
+ has to match the name of the command (`/monkey`).
62
+ - You should inherit from the `Slackathon::Command` class.
63
+ - You have access to the `params` hash.
64
+ - At minimum, you will need to implement the `call` method.
65
+ - Example:
66
+ ```ruby
67
+ class MonkeyCommand < Slackathon::Command
68
+ def call
69
+ {
70
+ response_type: "in_channel",
71
+ text: "#{user} said #{params[:text]} :see_no_evil:"
72
+ }
73
+ end
74
+
75
+ private
76
+
77
+ def user
78
+ "<@#{params[:user_id]}>"
79
+ end
80
+ end
81
+ ```
82
+ - See https://api.slack.com/slash-commands#responding_to_a_command and
83
+ https://api.slack.com/docs/message-formatting for more documentation about
84
+ the responses you can send.
85
+
86
+ ### Adding Buttons
87
+
88
+ In this section, we will modify the `MonkeyBot` and let the user pick which
89
+ monkey emoji to use.
90
+
91
+ 1. Enable "Your App > Features > Interactive Components"
92
+
93
+ - This is required to support buttons, menus and dialogs.
94
+ - The request URL should be `http://00bea6f5.ngrok.io/slack/interactions`.
95
+ - You probably won't need to worry about "Options Load URL".
96
+
97
+ 1. Instead of immediately posting to the channel, we will reply to the user
98
+ only, asking for their emoji preference:
99
+
100
+ ```ruby
101
+ class MonkeyCommand < Slackathon::Command
102
+ def call
103
+ {
104
+ response_type: "ephemeral",
105
+ attachments: [{
106
+ callback_id: "monkey",
107
+ text: "Please pick a style",
108
+ actions: [{
109
+ type: "button",
110
+ text: "Click to use :see_no_evil:",
111
+ name: "post_in_channel",
112
+ value: "#{params[:text]} :see_no_evil:"
113
+ }, {
114
+ type: "button",
115
+ text: "Click to use :hear_no_evil:",
116
+ name: "post_in_channel",
117
+ value: "#{params[:text]} :hear_no_evil:"
118
+ }, {
119
+ type: "button",
120
+ text: "Click to use :speak_no_evil:",
121
+ name: "post_in_channel",
122
+ value: "#{params[:text]} :speak_no_evil:"
123
+ }]
124
+ }]
125
+ }
126
+ end
127
+
128
+ def post_in_channel(value)
129
+ # do something with value, see below...
130
+ end
131
+ end
132
+ ```
133
+
134
+ - Here, we are using the `ephemeral` response type (as opposed to `in_channel`
135
+ as we did previously), which makes the response visible only to the user
136
+ who sent the command.
137
+ - In addition to the message text, we are including a few buttons in the
138
+ `attachments` array.
139
+ - The `callback_id` need to match the name of your command (e.g. `monkey` in
140
+ this case).
141
+ - The `actions` array has the button(s) you want to include.
142
+ - The `text` is the label of the button (e.g. "Click me!!!").
143
+ - The `name` is the name of the method to call when the button is clicked
144
+ (see below).
145
+ - The `value` is an optional string that will be passed to the method (see
146
+ below).
147
+ - See https://api.slack.com/interactive-messages and https://api.slack.com/dialogs
148
+ for more documentation.
149
+
150
+ 1. When the user clicks on one of the buttons, it will call the method you
151
+ specified:
152
+
153
+ ```ruby
154
+ class MonkeyCommand < Slackathon::Command
155
+ # def call ...
156
+ def post_in_channel(value)
157
+ {
158
+ response_type: "in_channel",
159
+ delete_original: true,
160
+ text: "<@#{params[:user][:id]}> said #{value}"
161
+ }
162
+ end
163
+ end
164
+ ```
165
+
166
+ Here, `value` is the string that we attached to the original buttons.
167
+
168
+ ## Promoting to Production
169
+
170
+ 1. Remove the command from your development Slack app.
171
+
172
+ 1. Create a production Slack app and your command/interactive component
173
+ following the instructions above (with the URLs pointing to your
174
+ production Rails app).
175
+
176
+ 1. Find the "Verification Token" from `https://api.slack.com/apps/<your app>`.
177
+ Assign its value to the `SLACK_VERIFICATION_TOKEN` ENV variable in
178
+ your production environment (or set it with `Slackathon.verification_token = ...`).
179
+
180
+ 1. Make sure your Active Job adapter is configured to process the `slack`
181
+ queue (e.g. `bundle exec sidekiq -q default -q slack ...`). Alternatively,
182
+ you can change the queue with `Slackathon.queue = ...`.
183
+
184
+ 1. Deploy your changes!
185
+
186
+ ## License
187
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,36 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Slackathon'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ require 'bundler/gem_tasks'
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'test'
31
+ t.pattern = 'test/**/*_test.rb'
32
+ t.verbose = false
33
+ end
34
+
35
+
36
+ task default: :test
@@ -0,0 +1,4 @@
1
+ module Slackathon
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,38 @@
1
+ module Slackathon
2
+ class WebhooksController < ApplicationController
3
+ before_action :verify_token
4
+
5
+ def command
6
+ command = payload[:command][1..-1]
7
+ klass = "#{command}_command".classify.constantize
8
+
9
+ SlackCommandJob.perform_later(klass.name, "command", payload.to_unsafe_h)
10
+
11
+ head :ok
12
+ end
13
+
14
+ def interaction
15
+ command = payload[:callback_id]
16
+ klass = "#{command}_command".classify.constantize
17
+
18
+ SlackCommandJob.perform_later(klass.name, "interaction", payload)
19
+
20
+ head :ok
21
+ end
22
+
23
+ private
24
+
25
+ def payload
26
+ @payload ||= params[:payload] ? JSON.parse(params[:payload]).with_indifferent_access : params
27
+ end
28
+
29
+ def verify_token
30
+ expected_token = Slackathon.verification_token
31
+ actual_token = payload.delete(:token)
32
+
33
+ if Rails.env.production? || expected_token
34
+ raise "Incorrect slack verification token" unless expected_token == actual_token
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ module Slackathon
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,50 @@
1
+ require "net/http"
2
+ require "uri"
3
+
4
+ module Slackathon
5
+ class SlackCommandJob < ApplicationJob
6
+ queue_as Slackathon.queue
7
+
8
+ def perform(name, type, params)
9
+ klass = name.constantize
10
+ params = params.with_indifferent_access
11
+ url = params[:response_url]
12
+
13
+ body = case type
14
+ when "command"
15
+ klass.dispatch_command(params)
16
+ when "interaction"
17
+ klass.dispatch_interaction(params)
18
+ end
19
+
20
+ say(url, body)
21
+ rescue => e
22
+ say(url, {
23
+ response_type: "ephemeral",
24
+ text: <<~TEXT
25
+ :mac_bomb: Darn - that slash command (#{name.underscore.gsub("_command", "")}) didn't work
26
+ ```
27
+ #{e.class}: #{e.message}
28
+ #{e.backtrace.take(5).map { |line| line.indent(4) }.join("\n")}
29
+ ...
30
+ ```
31
+ TEXT
32
+ })
33
+
34
+ raise e
35
+ end
36
+
37
+ private
38
+
39
+ def say(url, body)
40
+ uri = URI(url)
41
+ headers = { "Content-Type" => "application/json" }
42
+ body = (String === body) ? body : body.to_json
43
+ response = Net::HTTP.post uri, body, headers
44
+
45
+ unless Net::HTTPSuccess === response
46
+ raise "#{response.code}: #{response.body}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ Slackathon::Engine.routes.draw do
2
+ post "commands" => "webhooks#command"
3
+ post "interactions" => "webhooks#interaction"
4
+ end
@@ -0,0 +1,20 @@
1
+ require "slackathon/engine"
2
+ require "slackathon/command"
3
+
4
+ module Slackathon
5
+ def self.verification_token
6
+ @verification_token ||= ENV["SLACK_VERIFICATION_TOKEN"]
7
+ end
8
+
9
+ def self.verification_token=(token)
10
+ @verification_token = token
11
+ end
12
+
13
+ def self.queue
14
+ @queue ||= "slack"
15
+ end
16
+
17
+ def self.queue=(queue)
18
+ @queue = queue
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ module Slackathon
2
+ class Command
3
+ def self.dispatch_command(params)
4
+ self.new(params).call
5
+ end
6
+
7
+ def self.dispatch_interaction(params)
8
+ action = params[:actions][0]
9
+ method = self.new(params).public_method(action[:name])
10
+ value = action[:value]
11
+
12
+ if method.arity == 0
13
+ method.call
14
+ else
15
+ method.call(self.unescape(value))
16
+ end
17
+ end
18
+
19
+ def self.unescape(message)
20
+ message.gsub(/&amp;/, "&")
21
+ .gsub(/&lt;/, "<")
22
+ .gsub(/&gt;/, ">")
23
+ end
24
+
25
+ def initialize(params)
26
+ @params = params
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :params
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Slackathon
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Slackathon
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Slackathon
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slackathon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Godfrey Chan
8
+ - Yehuda Katz
9
+ - Krystan HuffMenne
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2017-11-21 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: 5.1.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: 5.1.4
29
+ description:
30
+ email:
31
+ - godfreykfc@gmail.com
32
+ - wycats@gmail.com
33
+ - krystan@tilde.io
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - MIT-LICENSE
39
+ - README.md
40
+ - Rakefile
41
+ - app/controllers/slackathon/application_controller.rb
42
+ - app/controllers/slackathon/webhooks_controller.rb
43
+ - app/jobs/slackathon/application_job.rb
44
+ - app/jobs/slackathon/slack_command_job.rb
45
+ - config/routes.rb
46
+ - lib/slackathon.rb
47
+ - lib/slackathon/command.rb
48
+ - lib/slackathon/engine.rb
49
+ - lib/slackathon/version.rb
50
+ homepage: https://github.com/tildeio/slackathon
51
+ licenses:
52
+ - MIT
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.6.13
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: A simple way to build Slack interations inside a Rails app.
74
+ test_files: []