slackathon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +187 -0
- data/Rakefile +36 -0
- data/app/controllers/slackathon/application_controller.rb +4 -0
- data/app/controllers/slackathon/webhooks_controller.rb +38 -0
- data/app/jobs/slackathon/application_job.rb +4 -0
- data/app/jobs/slackathon/slack_command_job.rb +50 -0
- data/config/routes.rb +4 -0
- data/lib/slackathon.rb +20 -0
- data/lib/slackathon/command.rb +33 -0
- data/lib/slackathon/engine.rb +5 -0
- data/lib/slackathon/version.rb +3 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -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'
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|
data/Rakefile
ADDED
@@ -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,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,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
|
data/config/routes.rb
ADDED
data/lib/slackathon.rb
ADDED
@@ -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(/&/, "&")
|
21
|
+
.gsub(/</, "<")
|
22
|
+
.gsub(/>/, ">")
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(params)
|
26
|
+
@params = params
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :params
|
32
|
+
end
|
33
|
+
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: []
|