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.
@@ -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: []