mv-slacker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/Gemfile +5 -0
  4. data/README.md +36 -0
  5. data/bin/mv-slacker +190 -0
  6. data/mv-slacker.gemspec +22 -0
  7. 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
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'byebug', platforms: [:mri_20, :mri_21, :mri_22]
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
+
@@ -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: