slacky 0.1

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,4 @@
1
+ .bundle
2
+ .idea
3
+ .DS_Store
4
+ pkg
@@ -0,0 +1 @@
1
+ 1.9.3-p448
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,70 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ slacky (0.1)
5
+ dotenv
6
+ em-cron
7
+ eventmachine
8
+ faye-websocket
9
+ pg
10
+ slack-ruby-client (>= 0.6)
11
+ tzinfo
12
+ tzinfo-data
13
+
14
+ GEM
15
+ remote: http://rubygems.org/
16
+ specs:
17
+ activesupport (4.2.6)
18
+ i18n (~> 0.7)
19
+ json (~> 1.7, >= 1.7.7)
20
+ minitest (~> 5.1)
21
+ thread_safe (~> 0.3, >= 0.3.4)
22
+ tzinfo (~> 1.1)
23
+ dotenv (2.1.1)
24
+ em-cron (0.2.0)
25
+ eventmachine
26
+ parse-cron (~> 0.1)
27
+ eventmachine (1.2.0.1)
28
+ factory_girl (4.7.0)
29
+ activesupport (>= 3.0.0)
30
+ faraday (0.9.2)
31
+ multipart-post (>= 1.2, < 3)
32
+ faraday_middleware (0.10.0)
33
+ faraday (>= 0.7.4, < 0.10)
34
+ faye-websocket (0.10.3)
35
+ eventmachine (>= 0.12.0)
36
+ websocket-driver (>= 0.5.1)
37
+ gli (2.13.4)
38
+ hashie (3.4.4)
39
+ i18n (0.7.0)
40
+ json (1.8.3)
41
+ minitest (5.8.4)
42
+ multipart-post (2.0.0)
43
+ parse-cron (0.1.4)
44
+ pg (0.18.4)
45
+ rake (11.1.2)
46
+ slack-ruby-client (0.7.2)
47
+ activesupport
48
+ faraday
49
+ faraday_middleware
50
+ gli
51
+ hashie
52
+ json
53
+ websocket-driver
54
+ thread_safe (0.3.5)
55
+ tzinfo (1.2.2)
56
+ thread_safe (~> 0.1)
57
+ tzinfo-data (1.2016.4)
58
+ tzinfo (>= 1.0.0)
59
+ websocket-driver (0.6.3)
60
+ websocket-extensions (>= 0.1.0)
61
+ websocket-extensions (0.1.2)
62
+
63
+ PLATFORMS
64
+ ruby
65
+
66
+ DEPENDENCIES
67
+ factory_girl
68
+ minitest
69
+ rake
70
+ slacky!
@@ -0,0 +1,15 @@
1
+ # Slacky
2
+
3
+ ### Carbon Five Slack bot starter kit
4
+
5
+ This gem is an out of the box Slack bot running in Ruby. It runs as a
6
+ daemon, has a CLI, and in the future will integrate with other Carbon
7
+ Five services (like Timesheet).
8
+
9
+ ## Installation
10
+
11
+ ...
12
+
13
+ ## Usage
14
+
15
+ ...
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/*_test.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,12 @@
1
+ module Slacky
2
+ require 'slacky/version'
3
+ require 'slacky/cli'
4
+ require 'slacky/config'
5
+ require 'slacky/daemon'
6
+ require 'slacky/service'
7
+ require 'slacky/bot'
8
+ require 'slacky/bookkeeper'
9
+ require 'slacky/user'
10
+ require 'slacky/channel'
11
+ require 'slacky/message'
12
+ end
@@ -0,0 +1,55 @@
1
+ module Slacky
2
+ class Bookkeeper
3
+ def initialize(client)
4
+ @client = client
5
+ end
6
+
7
+ def web_client
8
+ @client.web_client
9
+ end
10
+
11
+ def keep_the_books
12
+ @client.on :presence_change do |data|
13
+ next unless ( user = User.find data.user )
14
+ user.presence = data['presence']
15
+ user.save
16
+ end
17
+
18
+ @client.on :channel_created do |data|
19
+ web_client.channels_info(channel: data.channel.id).tap do |resp|
20
+ if resp.ok
21
+ channel = Channel.channel resp.channel
22
+ puts "Channel ##{channel.name}: channel_created"
23
+ end
24
+ end
25
+ end
26
+
27
+ handle_channel(:channel_deleted) { |c| c.delete }
28
+ handle_channel(:channel_archive) { |c| c.archive }
29
+ handle_channel(:channel_unarchive) { |c| c.unarchive }
30
+ handle_channel(:channel_rename) { |c, data| c.rename data.channel.name }
31
+
32
+ @client.on :group_joined do |data|
33
+ next unless data.channel.is_group
34
+ channel = Channel.group data.channel
35
+ puts "Channel ##{channel.name}: group_joined"
36
+ end
37
+
38
+ handle_channel(:group_left) { |g| g.delete }
39
+ handle_channel(:group_archive) { |g| g.archive }
40
+ handle_channel(:group_unarchive) { |g| g.unarchive }
41
+ handle_channel(:group_rename) { |g, data| g.rename data.channel.name }
42
+ end
43
+
44
+ def handle_channel(event)
45
+ @client.on event do |data|
46
+ channel_id = data.channel.is_a?(String) ? data.channel : data.channel.id
47
+ Channel.find(channel_id).tap do |channel|
48
+ yield channel, data
49
+ puts "Channel ##{channel.name}: #{event}"
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,229 @@
1
+ require 'slack-ruby-client'
2
+ require 'set'
3
+ require 'tzinfo'
4
+ require 'em/cron'
5
+
6
+ module Slacky
7
+ class Bot
8
+ attr_reader :client, :config, :slack_id
9
+
10
+ def initialize(config)
11
+ puts "#{config.name} is starting up..."
12
+
13
+ @config = config
14
+ @restarts = []
15
+ @command_handlers = []
16
+ @channel_handlers = []
17
+ @im_handlers = []
18
+ @raw_handlers = []
19
+ @cron_handlers = []
20
+
21
+ unless @config.slack_api_token
22
+ @config.log "No Slack API token found. Use environment variable SLACK_API_TOKEN."
23
+ return
24
+ end
25
+
26
+ Slack.configure do |slack_cfg|
27
+ slack_cfg.token = @config.slack_api_token
28
+ end
29
+
30
+ @client = Slack::RealTime::Client.new
31
+
32
+ auth = web_client.auth_test
33
+ if auth.ok
34
+ @slack_id = auth.user_id
35
+ @config.log "Slackbot is active!"
36
+ else
37
+ @config.log "Slackbot is doomed :-("
38
+ return
39
+ end
40
+
41
+ @bookkeeper = Bookkeeper.new @client
42
+
43
+ Channel.bot = self
44
+ Message.bot = self
45
+
46
+ populate_users
47
+ populate_channels
48
+ end
49
+
50
+ def web_client
51
+ @client.web_client
52
+ end
53
+
54
+ def name
55
+ @config.down_name
56
+ end
57
+
58
+ def known_commands
59
+ @command_handlers.map { |ch| ch[:command] }
60
+ end
61
+
62
+ def on(type, &block)
63
+ @raw_handlers << { type: type, handler: block }
64
+ end
65
+
66
+ def on_command(command, &block)
67
+ @command_handlers << { command: command.downcase, handler: block }
68
+ end
69
+
70
+ def on_message(attrs, &block)
71
+ attrs ||= {}
72
+ @channel_handlers << { match: attrs[:match], channels: attrs[:channels], handler: block }
73
+ end
74
+
75
+ def on_im(attrs, &block)
76
+ attrs ||= {}
77
+ @im_handlers << { match: attrs[:match], handler: block }
78
+ end
79
+
80
+ def at(cron, &block)
81
+ @cron_handlers << { cron: cron, handler: block }
82
+ end
83
+
84
+ def handle_channel(message)
85
+ handled = false
86
+
87
+ if message.command?
88
+ @command_handlers.each do |h|
89
+ command, handler = h.values_at :command, :handler
90
+ next unless command == message.command
91
+ @client.typing channel: message.channel.slack_id
92
+ handler.call message
93
+ handled = true
94
+ end
95
+ end
96
+
97
+ return if handled
98
+
99
+ @channel_handlers.each do |h|
100
+ match, channels, handler = h.values_at :match, :channels, :handler
101
+ accept = Channel.find channels
102
+ next if accept && accept.include?(message.channel)
103
+ next if match && ! match === message
104
+ @client.typing channel: message.channel.slack_id
105
+ handler.call message
106
+ end
107
+ end
108
+
109
+ def handle_im(message)
110
+ unless message.user.slack_im_id == message.channel.slack_id
111
+ message.user.slack_im_id = message.channel.slack_id
112
+ message.user.save
113
+ end
114
+
115
+ handled = false
116
+
117
+ @command_handlers.each do |h|
118
+ command, handler = h.values_at :command, :handler
119
+ next unless command == message.command
120
+ @client.typing channel: message.channel.slack_id
121
+ handler.call message
122
+ handled = true
123
+ end
124
+
125
+ return if handled
126
+
127
+ @im_handlers.each do |h|
128
+ match, handler = h.values_at :match, :handler
129
+ next if match && ! match === message
130
+ @client.typing channel: message.channel.slack_id
131
+ handler.call message
132
+ end
133
+ end
134
+
135
+ def run
136
+ @bookkeeper.keep_the_books
137
+
138
+ @client.on :message do |data|
139
+ next unless ( user = User.find data.user )
140
+
141
+ channel = Channel.find data.channel
142
+ channel = Channel.im data.channel, user if data.channel =~ /^D/ && ! channel
143
+ next unless channel
144
+
145
+ reject = Channel.find @config.slack_reject_channels
146
+ next if reject && reject.find { |c| c.slack_id == data.channel }
147
+
148
+ accept = Channel.find @config.slack_accept_channels
149
+ next if accept && ! accept.find { |c| c.slack_id == data.channel }
150
+
151
+ message = Message.new(user, channel, data)
152
+ handle_channel message if [ :group, :channel ].include? channel.type
153
+ handle_im message if [ :im ].include? channel.type
154
+ end
155
+
156
+ @raw_handlers.each do |h|
157
+ type, handler = h.values_at :type, :handler
158
+ @client.on type do |data|
159
+ handler.call data
160
+ end
161
+ end
162
+
163
+ @cron_thread ||= Thread.new do
164
+ EM.run do
165
+ @cron_handlers.each do |h|
166
+ cron, handler = h.values_at :cron, :handler
167
+ EM::Cron.schedule cron do |time|
168
+ begin
169
+ handler.call
170
+ rescue => e
171
+ @config.log "An error ocurred inside the Slackbot (in a scheduled block)", e
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ puts "#{@config.name} is listening to: #{@config.slack_accept_channels}"
179
+
180
+ @client.start!
181
+ rescue => e
182
+ @config.log "An error ocurred inside the Slackbot", e
183
+ @restarts << Time.new
184
+ @restarts.shift while (@restarts.length > 3)
185
+ if @restarts.length == 3 and ( Time.new - @restarts.first < 30 )
186
+ @config.log "Too many errors. Not restarting anymore."
187
+ else
188
+ run
189
+ end
190
+ end
191
+
192
+ def populate_users
193
+ print "Getting users from Slack..."
194
+ resp = web_client.users_list presence: 1
195
+ throw resp unless resp.ok
196
+ resp.members.map do |member|
197
+ next unless member.profile.email # no bots
198
+ next if member.deleted # no ghosts
199
+ next if member.is_ultra_restricted # no single channel guests
200
+ user = User.find(member.id) || User.new(slack_id: member.id)
201
+ user.populate(member).save
202
+ end
203
+ puts " done!"
204
+ end
205
+
206
+ def populate_channels
207
+ print "Getting channels from Slack..."
208
+ resp = web_client.channels_list
209
+ throw resp unless resp.ok
210
+ resp.channels.map do |channel|
211
+ Channel.channel channel
212
+ end
213
+
214
+ resp = web_client.groups_list
215
+ throw resp unless resp.ok
216
+ resp.groups.map do |group|
217
+ Channel.group group
218
+ end
219
+ puts " done!"
220
+ end
221
+
222
+ def blowup(user, data, args, &respond)
223
+ respond.call "Tick... tick... tick... BOOM! Goodbye."
224
+ EM.next_tick do
225
+ raise "kablammo!"
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,103 @@
1
+ module Slacky
2
+ class Channel
3
+ attr_reader :slack_id, :topic, :purpose, :members, :type
4
+ attr_accessor :name
5
+
6
+ @@channels = {}
7
+ @@bot = nil
8
+
9
+ def self.bot=(bot)
10
+ @@bot = bot
11
+ end
12
+
13
+ def self.find(channel)
14
+ return nil unless channel
15
+ return channel.map { |c| Channel.find c }.compact if channel.is_a? Array
16
+ return channel if channel.is_a? Channel
17
+ @@channels[channel]
18
+ end
19
+
20
+ def self.im(channel_id, user)
21
+ Channel.new.populate_im(channel_id, user).save
22
+ end
23
+
24
+ def self.channel(channel_data)
25
+ Channel.new.populate_channel(channel_data).save
26
+ end
27
+
28
+ def self.group(group_data)
29
+ Channel.new.populate_group(group_data).save
30
+ end
31
+
32
+ def archived?
33
+ @archived
34
+ end
35
+
36
+ def archive
37
+ @archived = true
38
+ end
39
+
40
+ def unarchive
41
+ @archived = false
42
+ end
43
+
44
+ def delete
45
+ @@channels.delete @slack_id
46
+ @@channels.delete "##{@name}"
47
+ @@channels.delete "@#{@user.username}" if @user
48
+ end
49
+
50
+ def rename(name)
51
+ @@channels.delete "##{@name}"
52
+ @name = name
53
+ @@channels["##{@name}"] = self
54
+ end
55
+
56
+ def member?
57
+ case @type
58
+ when :channel ; @member
59
+ when :group ; @members[@@bot.slack_id]
60
+ when :im ; true
61
+ else throw "Unknown channel type: #{@type}"
62
+ end
63
+ end
64
+
65
+ def save
66
+ @@channels[@slack_id] = self
67
+ @@channels["##{@name}"] = self if @name
68
+ @@channels["@#{@user.username}"] = self if @user
69
+ self
70
+ end
71
+
72
+ def populate_channel(channel)
73
+ populate channel
74
+ @type = :channel
75
+ @member = channel.is_member
76
+ self
77
+ end
78
+
79
+ def populate_group(group)
80
+ populate group
81
+ @type = :group
82
+ @members = User.find group.members
83
+ self
84
+ end
85
+
86
+ def populate_im(slack_id, user)
87
+ @type = :im
88
+ @slack_id = slack_id
89
+ @user = user
90
+ self
91
+ end
92
+
93
+ private
94
+
95
+ def populate(channel)
96
+ @slack_id = channel.id
97
+ @name = channel.name
98
+ @archived = channel.is_archived
99
+ @topic = channel.topic.value
100
+ @purpose = channel.purpose.value
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,35 @@
1
+ module Slacky
2
+ class CLI
3
+ attr_reader :bot
4
+
5
+ def initialize(name, opts)
6
+ throw "CLI must be passed a name" unless name
7
+ @options = { :verbose => false }.merge opts
8
+ config = Config.new name
9
+ @bot = Bot.new config
10
+ daemon = Daemon.new config, bot
11
+ @service = Service.new config, daemon
12
+ end
13
+
14
+ def run(params)
15
+ @service.run
16
+ end
17
+
18
+ def start(params)
19
+ @service.start
20
+ end
21
+
22
+ def stop(params)
23
+ @service.stop
24
+ end
25
+
26
+ def restart(params)
27
+ @service.restart
28
+ end
29
+
30
+ def status(params)
31
+ @service.status
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,66 @@
1
+ require 'yaml'
2
+ require 'time'
3
+ require 'pg'
4
+ require 'dotenv'
5
+
6
+ module Slacky
7
+ class Config
8
+ attr_reader :pid_file, :name, :db
9
+
10
+ def initialize(name, opts = {})
11
+ @name = name
12
+ Dotenv.load ".env", "#{config_dir}/.env"
13
+ FileUtils.mkdir config_dir unless File.directory? config_dir
14
+ @pid_file = "#{config_dir}/pid"
15
+ @db = PG.connect(db_connect_params)
16
+ User.db = @db
17
+ User.initialize_table
18
+
19
+ @timestamps = {}
20
+ end
21
+
22
+ def down_name
23
+ @name.downcase
24
+ end
25
+
26
+ def slack_api_token
27
+ ENV['SLACK_API_TOKEN']
28
+ end
29
+
30
+ def config_dir
31
+ ENV['CONFIG_DIR'] || "#{ENV['HOME']}/.#{down_name}"
32
+ end
33
+
34
+ def slack_reject_channels
35
+ return nil unless ENV['REJECT_CHANNELS']
36
+ ENV['REJECT_CHANNELS'].split(',').map {|c| c.strip}
37
+ end
38
+
39
+ def slack_accept_channels
40
+ return nil unless ENV['ACCEPT_CHANNELS']
41
+ ENV['ACCEPT_CHANNELS'].split(',').map {|c| c.strip}
42
+ end
43
+
44
+ def log(msg, ex = nil)
45
+ log = File.new(log_file, 'a')
46
+ timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
47
+ type = ex ? 'ERROR' : ' INFO'
48
+ log.puts "#{type} #{timestamp} #{msg}"
49
+ if ex
50
+ log.puts ex.message
51
+ log.puts("Stacktrace:\n" + ex.backtrace.join("\n"))
52
+ end
53
+ log.flush
54
+ end
55
+
56
+ private
57
+
58
+ def db_connect_params
59
+ ENV['DATABASE_URL'] || { dbname: "slacky_#{down_name}" }
60
+ end
61
+
62
+ def log_file
63
+ "#{config_dir}/#{down_name}.log"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,67 @@
1
+ module Slacky
2
+ class Daemon
3
+
4
+ def initialize(config, bot)
5
+ @config = config
6
+ @bot = bot
7
+ @active = true
8
+ @running = false
9
+ end
10
+
11
+ def start(daemonize = true)
12
+ Process.daemon if daemonize
13
+ write_pid
14
+
15
+ [ 'HUP', 'INT', 'QUIT', 'TERM' ].each do |sig|
16
+ Signal.trap(sig) do
17
+ @config.log "Interrupted with signal: #{sig}"
18
+ kill
19
+ end
20
+ end
21
+
22
+ begin
23
+ @slackthread = Thread.new { @bot.run }
24
+ run @bot
25
+ rescue => e
26
+ @config.log "Unexpected error", e
27
+ ensure
28
+ cleanup
29
+ end
30
+ end
31
+
32
+ def cleanup
33
+ delete_pid
34
+ end
35
+
36
+ private
37
+
38
+ def run(slackbot)
39
+ @config.log "#{@config.name} is running."
40
+ while active? do
41
+ # TODO: handle timed tasks
42
+ #slackbot.ask_all if time.min % 10 == 0 # every 10 minutes
43
+ sleep 0.5
44
+ end
45
+ @config.log "#{@config.name} got killed"
46
+ @slackthread.kill
47
+ end
48
+
49
+ def active?
50
+ @active
51
+ end
52
+
53
+ def kill
54
+ @active = false
55
+ end
56
+
57
+ def write_pid
58
+ File.open @config.pid_file, 'w' do |f|
59
+ f.write Process.pid.to_s
60
+ end
61
+ end
62
+
63
+ def delete_pid
64
+ File.delete @config.pid_file if File.exists? @config.pid_file
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,61 @@
1
+ module Slacky
2
+ class Message
3
+ attr_reader :raw, :text, :user, :channel
4
+
5
+ @@decorator = @@bot = nil
6
+
7
+ def self.decorator=(decorator)
8
+ @@decorator = decorator
9
+ end
10
+
11
+ def self.bot=(bot)
12
+ @@bot = bot
13
+ end
14
+
15
+ def initialize(user, channel, raw)
16
+ @raw = raw
17
+ @user = user
18
+ @channel = channel
19
+ @text = raw.text.strip
20
+ @pieces = @text.split ' '
21
+ self.extend @@decorator if @@decorator
22
+ end
23
+
24
+ def reply(msg)
25
+ @@bot.client.message channel: @channel.slack_id, reply_to: @raw.id, text: msg
26
+ end
27
+
28
+ def command?
29
+ first = word 0
30
+ return false unless first
31
+ first == @@bot.name || first == "<@#{@@bot.slack_id}>"
32
+ end
33
+
34
+ def command
35
+ if @channel.type == :im
36
+ command? ? downword(1) : downword(0)
37
+ else
38
+ command? ? downword(1) : nil
39
+ end
40
+ end
41
+
42
+ def yes?
43
+ [ 'y', 'yes' ].include? @text
44
+ end
45
+
46
+ def no?
47
+ [ 'n', 'no' ].include? @text
48
+ end
49
+
50
+ private
51
+
52
+ def word(n)
53
+ return nil unless @pieces.length > n
54
+ @pieces[n]
55
+ end
56
+
57
+ def downword(n)
58
+ word(n) && word(n).downcase
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,73 @@
1
+ require 'fileutils'
2
+
3
+ class Slacky::Service
4
+ def initialize(config, daemon)
5
+ @config = config
6
+ @daemon = daemon
7
+ end
8
+
9
+ def run
10
+ @daemon.start false
11
+ end
12
+
13
+ def start(persist = false)
14
+ pid = get_pid
15
+ if pid
16
+ puts "#{@config.name} is already running with PID #{pid}"
17
+ return
18
+ end
19
+
20
+ print "Starting #{@config.name}... "
21
+ new_pid = Process.fork { @daemon.start }
22
+ Process.detach new_pid
23
+ puts "started"
24
+ end
25
+
26
+ def stop(persist = false)
27
+ pid = get_pid
28
+ unless pid
29
+ puts "#{@config.name} is not running"
30
+ return
31
+ end
32
+
33
+ print "Stopping #{@config.name}..."
34
+
35
+ begin
36
+ Process.kill 'HUP', pid
37
+ rescue
38
+ @daemon.cleanup
39
+ end
40
+
41
+ ticks = 0
42
+ while pid = get_pid && ticks < 40
43
+ sleep 0.5
44
+ ticks += 1
45
+ print '.' if ticks % 4 == 0
46
+ end
47
+ puts " #{pid.nil? ? 'stopped' : 'failed'}"
48
+ end
49
+
50
+ def restart
51
+ stop
52
+ start
53
+ end
54
+
55
+ def status
56
+ pid = get_pid
57
+ if pid
58
+ puts "#{@config.name} is running with PID #{pid}"
59
+ true
60
+ else
61
+ puts "#{@config.name} is not running"
62
+ false
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def get_pid
69
+ return nil unless File.exists? @config.pid_file
70
+ IO.read(@config.pid_file).to_i
71
+ end
72
+
73
+ end
@@ -0,0 +1,91 @@
1
+ require 'json'
2
+
3
+ module Slacky
4
+ class User
5
+ attr_accessor :username, :slack_id, :slack_im_id, :first_name, :last_name, :email, :timezone, :presence, :data
6
+ attr_reader :tz
7
+
8
+ @@decorator = @@db = nil
9
+
10
+ def self.decorator=(decorator)
11
+ @@decorator = decorator
12
+ end
13
+
14
+ def self.db=(db)
15
+ @@db = db
16
+ end
17
+
18
+ def self.initialize_table
19
+ @@db.exec <<-SQL
20
+ create table if not exists users (
21
+ username varchar(64) not null,
22
+ slack_id varchar(20) not null,
23
+ slack_im_id varchar(20),
24
+ first_name varchar(64),
25
+ last_name varchar(64),
26
+ email varchar(128) not null,
27
+ timezone varchar(256),
28
+ presence varchar(64),
29
+ data jsonb not null
30
+ );
31
+ SQL
32
+ end
33
+
34
+ def self.find(user)
35
+ return user.map { |u| User.find u }.compact if user.is_a? Array
36
+ result = @@db.exec_params "select * from users where slack_id = $1", [ user ]
37
+ if result.ntuples == 0
38
+ result = @@db.exec_params "select * from users where username = $1", [ user ]
39
+ end
40
+ return nil if result.ntuples == 0
41
+
42
+ row = result[0]
43
+ user = self.new username: row['username'],
44
+ slack_id: row['slack_id'],
45
+ slack_im_id: row['slack_im_id'],
46
+ first_name: row['first_name'],
47
+ last_name: row['last_name'],
48
+ email: row['email'],
49
+ timezone: row['timezone'],
50
+ presence: row['presence'],
51
+ data: JSON.parse(row['data'])
52
+ user.extend @@decorator if @@decorator
53
+ user
54
+ end
55
+
56
+ def initialize(attrs={})
57
+ @username = attrs[:username]
58
+ @slack_id = attrs[:slack_id]
59
+ @slack_im_id = attrs[:slack_im_id]
60
+ @first_name = attrs[:first_name]
61
+ @last_name = attrs[:last_name]
62
+ @email = attrs[:email]
63
+ @timezone = attrs[:timezone] || "America/Los_Angeles"
64
+ @presence = attrs[:presence]
65
+ @data = attrs[:data] || {}
66
+ end
67
+
68
+ def populate(member)
69
+ @username = member.name
70
+ @first_name = member.profile.first_name
71
+ @last_name = member.profile.last_name
72
+ @email = member.profile.email
73
+ @timezone = member.tz
74
+ @presence = member.presence
75
+ @data = {} unless @data
76
+ self
77
+ end
78
+
79
+ def save
80
+ @@db.exec_params "delete from users where slack_id = $1", [ @slack_id ]
81
+ @@db.exec_params "insert into users (username, slack_id, slack_im_id, first_name, last_name, email, timezone, presence, data)
82
+ values ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
83
+ [ @username, @slack_id, @slack_im_id, @first_name, @last_name, @email, @timezone, @presence, JSON.dump(@data) ]
84
+ self
85
+ end
86
+
87
+ def reset
88
+ @data = {}
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module Slacky
2
+ VERSION = "0.1"
3
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH << File.dirname(__FILE__) + "/lib"
3
+ require 'slacky/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "slacky"
7
+ s.version = Slacky::VERSION
8
+ s.authors = ["Michael Wynholds"]
9
+ s.email = ["mike@carbonfive.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Carbon Five Slack bot gem}
12
+ s.description = %q{Carbon Five Slack bot gem}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_runtime_dependency 'slack-ruby-client', ">= 0.6"
20
+ s.add_runtime_dependency 'pg'
21
+ s.add_runtime_dependency 'eventmachine'
22
+ s.add_runtime_dependency 'faye-websocket'
23
+ s.add_runtime_dependency 'em-cron'
24
+ s.add_runtime_dependency 'tzinfo'
25
+ s.add_runtime_dependency 'tzinfo-data'
26
+ s.add_runtime_dependency 'dotenv'
27
+
28
+ s.add_development_dependency 'rake'
29
+ s.add_development_dependency 'minitest'
30
+ s.add_development_dependency 'factory_girl'
31
+ end
metadata ADDED
@@ -0,0 +1,245 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slacky
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Wynholds
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-05-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: slack-ruby-client
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0.6'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0.6'
30
+ - !ruby/object:Gem::Dependency
31
+ name: pg
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: eventmachine
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: faye-websocket
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: em-cron
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: tzinfo
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: tzinfo-data
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: dotenv
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: rake
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: minitest
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: factory_girl
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ description: Carbon Five Slack bot gem
191
+ email:
192
+ - mike@carbonfive.com
193
+ executables: []
194
+ extensions: []
195
+ extra_rdoc_files: []
196
+ files:
197
+ - .gitignore
198
+ - .ruby-version
199
+ - Gemfile
200
+ - Gemfile.lock
201
+ - README.md
202
+ - Rakefile
203
+ - lib/slacky.rb
204
+ - lib/slacky/bookkeeper.rb
205
+ - lib/slacky/bot.rb
206
+ - lib/slacky/channel.rb
207
+ - lib/slacky/cli.rb
208
+ - lib/slacky/config.rb
209
+ - lib/slacky/daemon.rb
210
+ - lib/slacky/message.rb
211
+ - lib/slacky/service.rb
212
+ - lib/slacky/user.rb
213
+ - lib/slacky/version.rb
214
+ - slacky.gemspec
215
+ homepage: ''
216
+ licenses: []
217
+ post_install_message:
218
+ rdoc_options: []
219
+ require_paths:
220
+ - lib
221
+ required_ruby_version: !ruby/object:Gem::Requirement
222
+ none: false
223
+ requirements:
224
+ - - ! '>='
225
+ - !ruby/object:Gem::Version
226
+ version: '0'
227
+ segments:
228
+ - 0
229
+ hash: -3373873093616992315
230
+ required_rubygems_version: !ruby/object:Gem::Requirement
231
+ none: false
232
+ requirements:
233
+ - - ! '>='
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
236
+ segments:
237
+ - 0
238
+ hash: -3373873093616992315
239
+ requirements: []
240
+ rubyforge_project:
241
+ rubygems_version: 1.8.23
242
+ signing_key:
243
+ specification_version: 3
244
+ summary: Carbon Five Slack bot gem
245
+ test_files: []