mv-slacker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/Gemfile +5 -0
- data/README.md +36 -0
- data/bin/mv-slacker +190 -0
- data/mv-slacker.gemspec +22 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cc0db0d227f0687d12768e746210e61346d6f7d4
|
4
|
+
data.tar.gz: 7a87e22b24e76d102bae1b78d5c08c9e9199a703
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f4174aaa74fc7c4eff812a1798eb48984f0dc0b30191e7ab8efe1f099b3cdecf5a063474779fe373ac32a6176033c6bcf0a3e27a6ff2f417f23890a1f1fb48d0
|
7
|
+
data.tar.gz: 6c56fd937df97ad74fd512aad132339f1435b797ffa54bbaec57ca06d7d33591e95fd1664c744cd04384c10df239759dd3e795fd6c966f14019805d5304dd350
|
data/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/vendor/bundle
|
26
|
+
/lib/bundler/man/
|
27
|
+
|
28
|
+
# for a library or gem, you might want to ignore these files since the code is
|
29
|
+
# intended to run in multiple environments; otherwise, check them in:
|
30
|
+
# Gemfile.lock
|
31
|
+
# .ruby-version
|
32
|
+
# .ruby-gemset
|
33
|
+
|
34
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
35
|
+
.rvmrc
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# mv-slacker
|
2
|
+
A thing that lets you send Motivosity dollars via Slack
|
3
|
+
|
4
|
+
## Description
|
5
|
+
This program listens for you to say certain magic words in Slack. When it hears them, it performs a Motivosity transaction on your behalf and posts the result to the channel.
|
6
|
+
|
7
|
+
## Example use
|
8
|
+
```
|
9
|
+
<user> $1 :motivosity: to Jane Doe, for Customer Experience. Thanks for helping the customer!
|
10
|
+
<mv-slacker> Success! Jane Doe has received your appreciation.
|
11
|
+
```
|
12
|
+
|
13
|
+
### Message format
|
14
|
+
```
|
15
|
+
$<amount> :motivosity: to <user>[, for <company value>][.[ <note>]]
|
16
|
+
```
|
17
|
+
|
18
|
+
### Identifying the user
|
19
|
+
mv-slacker will complete your request only if `<user>` matches exactly one Motivosity user. If the `<user>` term matches more than one name, mv-slacker will DM you the list of matching names.
|
20
|
+
|
21
|
+
## Why isn't this an outgoing webhook?
|
22
|
+
That would be a lot simpler, wouldn't it? But then you'd have to trust some random service with your Motivosity credentials. If and when Motivosity supports OAuth, I'd be happy to take that approach. That way, a single service would allow any Slack user to register with the bot by providing a token, and it'd be beautiful. But for now, this inelegant solution at least keeps your password on your local machine.
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
```
|
26
|
+
gem install mv-slacker
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
```
|
31
|
+
mv-slacker
|
32
|
+
```
|
33
|
+
The first time you run it, it will prompt you to enter your Motivosity credentials and your Slack token. These will be stored in `~/.mv-slacker-auth` for later use. If that file is deleted, or if a login fails, mv-slacker will prompt you again. If the permissions on this file are too loose, mv-slacker will refuse to use them, so be sure it's readable only by you.
|
34
|
+
|
35
|
+
## Technical information
|
36
|
+
mv-slacker uses the Slack Real-Time Messaging API to watch for your commands. It doesn't depend on any Slack integrations. It uses the mvclient gem to communicate with Motivosity.
|
data/bin/mv-slacker
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'highline/import'
|
4
|
+
require 'mvclient'
|
5
|
+
require 'slack-rtmapi'
|
6
|
+
require 'json'
|
7
|
+
require 'date'
|
8
|
+
require 'colorize'
|
9
|
+
|
10
|
+
CONFIG_FILE = File.expand_path("~/.mv-slacker-auth")
|
11
|
+
MESSAGE_REGEX = /\A\s*(?:\$|\+\$?)(\d+\.?\d*)\s*\:(?:mv|motivosity)\:\s*to\s+([^,.]+)(?:,\s*for\s+([^.]+))?(?:\.\s*(.*))?/
|
12
|
+
|
13
|
+
$slack_login_time = nil
|
14
|
+
|
15
|
+
class SlackAPI
|
16
|
+
class Error < RuntimeError; end
|
17
|
+
|
18
|
+
class Http
|
19
|
+
include HTTParty
|
20
|
+
base_uri 'https://slack.com'
|
21
|
+
debug_output $stderr if ENV['SLACK_DEBUG'].to_i == 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get(path, url_params = {})
|
25
|
+
response = Http.get(path, query: url_params.merge(token: $config[:slack_token]))
|
26
|
+
$slack_login_time ||= DateTime.httpdate(response.headers['Date']).to_time.to_i
|
27
|
+
raise Error.new("#{response.code} #{response.message}") unless response.code == 200
|
28
|
+
json = JSON.parse(response.body)
|
29
|
+
raise Error.new(response["error"]) unless response["ok"]
|
30
|
+
json
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def check_config
|
36
|
+
$config = {}
|
37
|
+
config_changed = false
|
38
|
+
config_file = File.new(CONFIG_FILE, File::CREAT|File::RDWR, 0600)
|
39
|
+
raise "Permissions for #{CONFIG_FILE} are too liberal" if config_file.stat.world_readable?
|
40
|
+
data = config_file.read
|
41
|
+
unless data.empty?
|
42
|
+
$config = JSON.parse(data).tap do |h|
|
43
|
+
h.keys.each { |k| h[k.to_sym] = h.delete(k) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# log in to slack
|
48
|
+
loop do
|
49
|
+
if $config[:slack_token]
|
50
|
+
begin
|
51
|
+
result = SlackAPI.get("/api/auth.test")
|
52
|
+
$slack_user_id = result["user_id"]
|
53
|
+
puts "Logged into Slack"
|
54
|
+
break
|
55
|
+
rescue SlackAPI::Error => e
|
56
|
+
puts "Slack login failed: #{e.message}"
|
57
|
+
$config.delete(:slack_token)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
if !$config[:slack_token]
|
61
|
+
config_changed = true
|
62
|
+
$config[:slack_token] = ask("Paste your Slack token: ")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# log in to motivosity
|
67
|
+
loop do
|
68
|
+
if $config[:mv_username] && $config[:mv_password]
|
69
|
+
begin
|
70
|
+
$mvclient.login! $config[:mv_username], $config[:mv_password]
|
71
|
+
$company_values = $mvclient.get_values
|
72
|
+
puts "Logged in to Motivosity"
|
73
|
+
break
|
74
|
+
rescue Motivosity::UnauthorizedError
|
75
|
+
puts "Motivosity login failed"
|
76
|
+
$config.delete(:mv_username)
|
77
|
+
$config.delete(:mv_password)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
if !$config[:mv_username] || !$config[:mv_password]
|
81
|
+
config_changed = true
|
82
|
+
$config[:mv_username] = ask("Enter your Motivosity username: ")
|
83
|
+
$config[:mv_password] = ask("Enter your Motivosity password: ") { |q| q.echo = false }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# save config
|
88
|
+
if config_changed
|
89
|
+
config_file.rewind
|
90
|
+
config_file.truncate(0)
|
91
|
+
config_file.write $config.to_json
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def open_im_channel(user_id)
|
96
|
+
json = SlackAPI.get "/api/im.open", user: user_id
|
97
|
+
json["channel"]["id"]
|
98
|
+
end
|
99
|
+
|
100
|
+
def send_in_channel(channel, text)
|
101
|
+
SlackAPI.get "/api/chat.postMessage", channel: channel, username: 'Motivosity Slacker', as_user: false, text: text,
|
102
|
+
unfurl_links: false, unfurl_media: false, icon_emoji: ":motivosity:"
|
103
|
+
end
|
104
|
+
|
105
|
+
# send a direct message to the caller
|
106
|
+
def pm(message, text)
|
107
|
+
send_in_channel(open_im_channel($slack_user_id), text)
|
108
|
+
end
|
109
|
+
|
110
|
+
# reply to the message in channel
|
111
|
+
def reply(message, text)
|
112
|
+
send_in_channel(message["channel"], text)
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_user(message, name)
|
116
|
+
users = []
|
117
|
+
begin
|
118
|
+
users = $mvclient.search_for_user(name)
|
119
|
+
rescue Motivosity::UnauthorizedError
|
120
|
+
# in case session has expired
|
121
|
+
$mvclient.login! $config[:mv_username], $config[:mv_password]
|
122
|
+
users = $mvclient.search_for_user(name)
|
123
|
+
end
|
124
|
+
if users.size > 1
|
125
|
+
# check for an exact match among the search results
|
126
|
+
matching_users = users.select { |user| user['fullName'] == name }
|
127
|
+
if matching_users.size != 1
|
128
|
+
puts "Ambiguous name: " + name.bold
|
129
|
+
pm(message, "Multiple users match `#{name}`! Try one of #{users.map{|user| "`#{user['fullName']}`"}.join(', ')}")
|
130
|
+
return [nil, nil]
|
131
|
+
else
|
132
|
+
users = matching_users
|
133
|
+
end
|
134
|
+
end
|
135
|
+
if users.empty?
|
136
|
+
puts "User not found: " + name.bold
|
137
|
+
pm(message, "User `#{name}` not found")
|
138
|
+
return [nil, nil]
|
139
|
+
end
|
140
|
+
[users[0]['id'], users[0]['fullName']]
|
141
|
+
end
|
142
|
+
|
143
|
+
def process_message(message, amount, name, company_value, note)
|
144
|
+
# slack likes to replay old messages when you first log in
|
145
|
+
# let's avoid sending redundant thanks by ignoring messages older than our slack login
|
146
|
+
if message['ts'].to_f < $slack_login_time
|
147
|
+
puts "ignoring old message: #{message['text']}".light_black
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
# find the company value, if one was given
|
152
|
+
value = $company_values.detect { |v| v['name'].downcase == company_value.downcase } if company_value
|
153
|
+
value_id = value['id'] if value
|
154
|
+
|
155
|
+
# look up the user
|
156
|
+
user_id, user_name = find_user(message, name)
|
157
|
+
return unless user_id
|
158
|
+
|
159
|
+
# let's do this
|
160
|
+
print "Sending $" + "#{amount}".on_green + " to " + "#{user_name}".on_red
|
161
|
+
print " for " + "#{value['name']}".on_blue if value
|
162
|
+
puts ": " + note.bold
|
163
|
+
begin
|
164
|
+
response = $mvclient.send_appreciation! user_id, amount: amount, company_value_id: value_id, note: note
|
165
|
+
rescue => e
|
166
|
+
puts "Failed to send appreciation: ".red + e.inspect
|
167
|
+
pm(message, "Failed to send appreciation: `#{e.message}`")
|
168
|
+
return
|
169
|
+
end
|
170
|
+
reply(message, "*#{response['growl']['title']}* #{response['growl']['content']}")
|
171
|
+
end
|
172
|
+
|
173
|
+
# ...
|
174
|
+
|
175
|
+
$slack_user_id = nil
|
176
|
+
|
177
|
+
$mvclient = Motivosity::Client.new
|
178
|
+
check_config
|
179
|
+
|
180
|
+
$slack_client = SlackRTM::Client.new(websocket_url: SlackRTM.get_url(token: $config[:slack_token]))
|
181
|
+
$slack_client.on(:message) do |data|
|
182
|
+
if data["type"] == "message" && data["user"] == $slack_user_id && data["text"] =~ MESSAGE_REGEX
|
183
|
+
process_message(data, $1, $2, $3, $4)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
puts "Connected to Slack Real Time Messaging API"
|
188
|
+
|
189
|
+
$slack_client.main_loop
|
190
|
+
|
data/mv-slacker.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'mv-slacker'
|
6
|
+
s.version = '0.0.1'
|
7
|
+
s.date = '2015-04-09'
|
8
|
+
s.summary = "A thing that lets you send Motivosity dollars via Slack"
|
9
|
+
s.description = "A thing that lets you send Motivosity dollars via Slack"
|
10
|
+
s.authors = ["Jeremy Stanley"]
|
11
|
+
s.email = 'jstanley0@gmail.com'
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.homepage = 'http://github.com/jstanley0/mv-slacker'
|
14
|
+
s.license = 'Apache'
|
15
|
+
s.bindir = 'bin'
|
16
|
+
s.executables << 'mv-slacker'
|
17
|
+
s.required_ruby_version = '>= 1.9.3'
|
18
|
+
s.add_dependency 'mvclient', '0.0.4'
|
19
|
+
s.add_dependency 'httparty', '~> 0'
|
20
|
+
s.add_dependency 'slack-rtmapi', '1.0.0.rc4'
|
21
|
+
s.add_dependency 'colorize', '~> 0.7'
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mv-slacker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Stanley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mvclient
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: slack-rtmapi
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.0.rc4
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0.rc4
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: colorize
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.7'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.7'
|
69
|
+
description: A thing that lets you send Motivosity dollars via Slack
|
70
|
+
email: jstanley0@gmail.com
|
71
|
+
executables:
|
72
|
+
- mv-slacker
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- README.md
|
79
|
+
- bin/mv-slacker
|
80
|
+
- mv-slacker.gemspec
|
81
|
+
homepage: http://github.com/jstanley0/mv-slacker
|
82
|
+
licenses:
|
83
|
+
- Apache
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.9.3
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.2.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: A thing that lets you send Motivosity dollars via Slack
|
105
|
+
test_files: []
|
106
|
+
has_rdoc:
|