cksh_commander 0.2.1 → 0.2.2

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 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