cksh_commander 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 25be78fc47c94ddecfec773202ec0a40affdc40d
4
- data.tar.gz: 1d2d18a2cd6856cffcdabdd9d531ba7cdcdec109
3
+ metadata.gz: f98c333fb1994d93150486351c05fac07578f1b1
4
+ data.tar.gz: 587802b3cb82c7bf0da152b10d5a25d836f7d1b1
5
5
  SHA512:
6
- metadata.gz: f67bbb6ccf941cd8188fb83da89a89fe7eb55d81adcd90cf9de12b0a9fcd2ed0bcdfe90f91861b9b347dfea7b088c755675152147af55a30e9589338adc7d068
7
- data.tar.gz: 1a0f136ad3651a93d6c469e576469c89e164a726dad83450200f84a4d77ae5791d9583cfa4cc3d6097fbf00a3c18e55e6dc7fcd0fe72fde1b73f2ed898c43a86
6
+ metadata.gz: 63295c5c7b1c4107287cac3ffb856f23828bef9be29fb45f6e4369091e192bad95bf15bb04efc4e5b38da4cce7be3ca1b3040d9b8f5ada096147da693f6c871a
7
+ data.tar.gz: bbbfe5fc04d24897757a5ef04f382e837fdbb631481d95d181621195f0cad60192471254b0318178c6677e701ed4073dd17ed11ae873eea453fad90fea43b34d
data/README.md CHANGED
@@ -10,6 +10,15 @@ arguments. Check out the
10
10
  that comes bootstrapped with everything you need to process Slack slash
11
11
  commands using this gem.
12
12
 
13
+ - [Installation](#installation)
14
+ - [Usage](#usage)
15
+ - [Getting Started](#getting-started)
16
+ - [Command API](#command-api)
17
+ - [Running a Command](#running-a-command)
18
+ - [Examples](#examples)
19
+ - [Development](#development)
20
+ - [Contributing](#contributing)
21
+
13
22
  ### Installation
14
23
 
15
24
  You can install `cksh_commander` via RubyGems. In your Gemfile:
@@ -26,6 +35,8 @@ gem install cksh_commander
26
35
 
27
36
  ### Usage
28
37
 
38
+ #### Getting Started
39
+
29
40
  Defining a custom command is easy. First, create a `commands/` directory and tell
30
41
  `CKSHCommander` where it can find your commands. Let's say we've created a `commands/`
31
42
  directory at the root of our project. Our configuration is as follows:
@@ -93,11 +104,28 @@ module Example
93
104
  end
94
105
  ```
95
106
 
96
- We set our slash command authentication token at the class level, and define
107
+ #### Command API
108
+
109
+ We `set` our slash command authentication token at the class level, and define
97
110
  methods for processing subcommands. Attachments (which take the form of a hash)
98
111
  can be added using `add_response_attachment(attachment)`. You can also set the
99
112
  response to `'in_channel'` at the method level with `respond_in_channel!`.
100
113
 
114
+ We can access the Slack payload data via the `data` reader.
115
+
116
+ ```ruby
117
+ data.token #=> "gIkuvaNzQIHg97ATvDxqgjtO"
118
+ data.team_id #=> "T0001"
119
+ data.team_domain #=> "example"
120
+ data.channel_id #=> "C2147483705"
121
+ data.channel_name #=> "test"
122
+ data.user_id #=> "U2147483697"
123
+ data.user_name #=> "Randy"
124
+ data.command #=> "/test"
125
+ data.text #=> "subcommand"
126
+ data.response_url #=> "https://hooks.slack.com/commands/1234/5678"
127
+ ```
128
+
101
129
  Similar to [Thor](http://whatisthor.com/), we are able to document our
102
130
  command's API with the class-level `desc` method—as is shown in the example
103
131
  above. CKSHCommander provides a `help` subcommand out of the box, and this will
@@ -108,10 +136,47 @@ echo the documentation back to the Slack user.
108
136
  /example test1 [TEXT] # Subcommand 1 description.
109
137
  /example [TEXT] # Root command description.
110
138
  ```
139
+ Use `authorize` to whitelist the user IDs of Slack users whom you've authorized
140
+ to perform a subcommand. An unauthorized user who tries to use this subcommand will
141
+ receive the response, "You are unauthorized to use this subcommand!" You can
142
+ find the IDs of Slack users on your team using
143
+ [Slack's REST API](https://slack.com/api/users.list?token=YOURTOKEN). Note that
144
+ authorization is not performed unless `authorize` is used explicitly.
145
+
146
+ ```ruby
147
+ ...
148
+ desc "privatecmd", "A private subcommand."
149
+ def privatecmd
150
+ authorize(%w[U2147483697])
151
+ set_response_text("You are authorized!")
152
+ end
153
+ ...
154
+ ```
155
+
156
+ If you need to debug a subcommand and forward a caught exception to your Slack
157
+ client, you can use `debug!` at the top of your subcommand's method body.
158
+ Otherwise, exceptions at the subcommand level will result in the `set` error
159
+ message text being returned to the client. Using `debug!` like this allows you
160
+ to isolate testing to a single subcommand without disrupting all usage of the
161
+ slash command.
162
+
163
+ ```ruby
164
+ ...
165
+ set error_message: "Hm. Something went wrong!"
166
+
167
+ def subcommand
168
+ debug!
169
+ undefined_method #=> NameError
170
+ set_response_text("Never evaluated...")
171
+ end
172
+ ...
173
+ ```
174
+
175
+ #### Running a Command
111
176
 
112
177
  To run a command, we use `CKSHCommander::Runner`. In the example below, we've
113
- created a simple Sinatra app to illustrate its usage with the standard Slack
114
- slash command payload.
178
+ created a [simple Sinatra app](https://github.com/openarcllc/cksh_commander_api)
179
+ to illustrate its usage with the standard Slack slash command payload.
115
180
 
116
181
  ```ruby
117
182
  # app.rb
@@ -133,6 +198,19 @@ post "/" do
133
198
  end
134
199
  ```
135
200
 
201
+ ### Examples
202
+
203
+ Check out (and/or use) [example commands provided by the
204
+ community](https://github.com/openarcllc/cksh_commander/tree/master/commands).
205
+ Want to contribute a command? Great! Add your command to the `commands/` directory
206
+ and open a pull request! The implementations below provide convenient reference
207
+ points.
208
+
209
+ | command | description |
210
+ | --------|--------------- |
211
+ | [jira](https://github.com/openarcllc/cksh_commander/tree/master/commands/jira) | Display JIRA issue details in a Slack channel or message. |
212
+ | [lunch](https://github.com/openarcllc/cksh_commander/tree/master/commands/lunch) | State or revoke your intention to attend the weekly lunch gathering. |
213
+
136
214
  ### Development
137
215
 
138
216
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Dependencies
4
+ gem "jira-ruby"
@@ -0,0 +1,84 @@
1
+ require "cksh_commander"
2
+ require "jira"
3
+
4
+ module Jira
5
+ class Command < CKSHCommander::Command
6
+ set token: "YOUR_SLASH_COMMAND_TOKEN"
7
+
8
+ # Location of your JIRA instance
9
+ JIRABASE = "http://localhost:3000/jira"
10
+
11
+ desc "issues [PROJECT] [STATUS]", "Get project issues."
12
+ def issues(project, status = nil)
13
+ content = "Issues for project: *#{project.upcase}*\n\n"
14
+
15
+ begin
16
+ issues = get_issues(project, status)
17
+ if issues.any?
18
+ issueoutput = issues.map { |i| issue_output(i) }.join("\n")
19
+ respond_in_channel!
20
+ add_response_attachment({
21
+ text: issueoutput, mrkdwn_in: ['text'], color: 'good'
22
+ })
23
+ else
24
+ content += "No matching issues found..."
25
+ end
26
+ rescue
27
+ content = "Encountered an error..."
28
+ end
29
+
30
+ set_response_text(content)
31
+ end
32
+
33
+ desc "[TICKET]", "Get ticket details."
34
+ def ___(issueid)
35
+ begin
36
+ issue = client.Issue.find(issueid)
37
+ set_response_text(issue_output(issue))
38
+ respond_in_channel!
39
+ rescue
40
+ set_response_text("Cannot find issue!")
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def client
47
+ @jira ||= JIRA::Client.new({
48
+ username: "YOUR_JIRA_SYSTEM_USERNAME",
49
+ password: "YOUR_JIRA_SYSTEM_PASSWORD",
50
+ site: JIRABASE,
51
+ auth_type: :basic,
52
+ context_path: ""
53
+ })
54
+ end
55
+
56
+ def get_issues(project, status)
57
+ query = "project = #{project}"
58
+ query += " AND status = \"#{status}\"" if status
59
+
60
+ client.Issue.jql(query, {
61
+ startAt: 0,
62
+ max: 50,
63
+ fields: %w[summary assignee status issuetype key]
64
+ })
65
+ end
66
+
67
+ def issue_output(issue)
68
+ output = "<#{JIRABASE}/browse/#{issue.key}|*#{issue.key}*: #{issue.summary}>\n"
69
+ output += "*Type*: #{issue.fields['issuetype']['name']}\n"
70
+ output += "*Assignee*: #{assignee_output(issue.fields['assignee'])}\n"
71
+ output += "*Status*: #{issue.fields['status']['name']}\n"
72
+ end
73
+
74
+ def assignee_output(assignee)
75
+ return "Not Assigned" unless assignee
76
+ "#{assignee["displayName"]} " +
77
+ "<#{user_email(assignee["emailAddress"])}>"
78
+ end
79
+
80
+ def user_email(address)
81
+ "<mailto:#{address}|#{address}>"
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Dependencies
4
+ gem "httparty"
@@ -0,0 +1,96 @@
1
+ require "cksh_commander"
2
+ require "httparty"
3
+ require "json"
4
+
5
+ module Lunch
6
+ class Command < CKSHCommander::Command
7
+ set token: "YOUR_SLASH_COMMAND_TOKEN"
8
+
9
+ # Keep track of who is in for lunch.
10
+ INFILE = File.expand_path("../in.txt", __FILE__)
11
+
12
+ desc "in", "State intention to attend the upcoming lunch."
13
+ def in
14
+ if user_in?
15
+ set_response_text("You're already in!")
16
+ else
17
+ add_user
18
+ set_response_text("You're in!")
19
+ end
20
+ end
21
+
22
+ desc "out", "Revoke intention to attend the upcoming lunch."
23
+ def out
24
+ if !user_in?
25
+ set_response_text("You aren't in, so you can't be out!")
26
+ else
27
+ remove_user
28
+ set_response_text("You're out!")
29
+ end
30
+ end
31
+
32
+ desc "who", "See who is attending the upcoming lunch."
33
+ def who
34
+ if attendees.any?
35
+ content = "Those IN for lunch: "
36
+ attendees.each_with_index do |a, i|
37
+ content += username_from_slug(a)
38
+ content += ", " unless i + 1 == attendees.count
39
+ end
40
+ else
41
+ content = "No one has committed to lunch..."
42
+ end
43
+
44
+ set_response_text(content)
45
+ end
46
+
47
+ # Allow a user to send out a lunch reminder; this requires Slack
48
+ # incoming webhook integration: https://api.slack.com/incoming-webhooks
49
+ def remind
50
+ payload = { text: "Lunch alert! Food in ~10 minutes!" }
51
+
52
+ attendees.each do |a|
53
+ # NOTE: Skip reminding the reminder...
54
+ # next if a == userslug
55
+
56
+ HTTParty.post(
57
+ "YOUR_SLACK_WEBHOOK_URL",
58
+ body: payload.merge({ channel: "@#{username_from_slug(a)}" }).to_json,
59
+ headers: { "Content-Type" => "application/json" })
60
+ end
61
+
62
+ set_response_text(attendees.any? ? "Reminders sent!" : "No one to remind...")
63
+ end
64
+
65
+ private
66
+
67
+ def attendees
68
+ File.read(INFILE).split("\n")
69
+ end
70
+
71
+ def add_user(slug = nil)
72
+ slug ||= userslug
73
+ File.open(INFILE, 'a') { |f| f.write("#{slug}\n") }
74
+ end
75
+
76
+ def remove_user
77
+ attending = attendees.dup
78
+ File.open(INFILE, 'w')
79
+ attending.dup.each do |a|
80
+ add_user(a) if userslug != a && a.size > 1
81
+ end
82
+ end
83
+
84
+ def user_in?
85
+ attendees.include?(userslug)
86
+ end
87
+
88
+ def userslug
89
+ "#{data.user_name}|#{data.user_id}"
90
+ end
91
+
92
+ def username_from_slug(slug)
93
+ slug.split('|')[0]
94
+ end
95
+ end
96
+ end
File without changes
@@ -21,11 +21,7 @@ module CKSHCommander
21
21
  attr_accessor :commands_path
22
22
 
23
23
  def initialize
24
- @commands_path = nil
25
- end
26
-
27
- def commands_path
28
- @commands_path || "../commands"
24
+ @commands_path = "../commands"
29
25
  end
30
26
  end
31
27
  end
@@ -3,7 +3,7 @@ require "cksh_commander/response"
3
3
  module CKSHCommander
4
4
  class Command
5
5
  class << self
6
- attr_accessor :token
6
+ attr_accessor :token, :error_message
7
7
  attr_reader :docs
8
8
 
9
9
  def set(opts)
@@ -24,6 +24,8 @@ module CKSHCommander
24
24
 
25
25
  def initialize(data = nil)
26
26
  @data = data
27
+ @authorized = true
28
+ @debugging = false
27
29
  @response = Response.new
28
30
  end
29
31
 
@@ -83,10 +85,12 @@ module CKSHCommander
83
85
  end
84
86
 
85
87
  def set_response_text(text)
88
+ return unless @authorized
86
89
  @response.text = text
87
90
  end
88
91
 
89
92
  def add_response_attachment(attachment)
93
+ return unless @authorized
90
94
  unless attachment.is_a?(Hash)
91
95
  raise ArgumentError, "Attachment must be a Hash"
92
96
  end
@@ -95,9 +99,32 @@ module CKSHCommander
95
99
  end
96
100
 
97
101
  def respond_in_channel!
102
+ return unless @authorized
98
103
  @response.type = 'in_channel'
99
104
  end
100
105
 
106
+ # Authorize users to use a subcommand by
107
+ # whitelisting user IDs.
108
+ def authorize(ids)
109
+ @authorized = ids.any? { |id| @data.user_id == id }
110
+ unless @authorized
111
+ @response = Response.new("You are unauthorized to use this subcommand!")
112
+ end
113
+ end
114
+
115
+ def debug!
116
+ @debugging = true
117
+ end
118
+
119
+ def debugging?
120
+ @debugging
121
+ end
122
+
123
+ def error_message
124
+ self.class.error_message ||
125
+ "Something went awry..."
126
+ end
127
+
101
128
  private
102
129
 
103
130
  def subcommand_methods
@@ -12,8 +12,10 @@ module CKSHCommander
12
12
  cmd = Kernel.const_get(name).new(data)
13
13
 
14
14
  response = cmd.authenticated? ? cmd.run : Response.new("Invalid token!")
15
- rescue NameError
16
- response = Response.new("Command not found...")
15
+ rescue => e
16
+ text = cmd && cmd.debugging? ? e.message :
17
+ cmd ? cmd.error_message : "Command not found..."
18
+ response = Response.new(text)
17
19
  end
18
20
 
19
21
  response
@@ -1,3 +1,3 @@
1
1
  module CKSHCommander
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cksh_commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Travis Loncar
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-12-11 00:00:00.000000000 Z
11
+ date: 2015-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -68,6 +68,11 @@ files:
68
68
  - bin/console
69
69
  - bin/setup
70
70
  - cksh_commander.gemspec
71
+ - commands/jira/Gemfile
72
+ - commands/jira/command.rb
73
+ - commands/lunch/Gemfile
74
+ - commands/lunch/command.rb
75
+ - commands/lunch/in.txt
71
76
  - lib/cksh_commander.rb
72
77
  - lib/cksh_commander/command.rb
73
78
  - lib/cksh_commander/data.rb