slackbot_frd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c5c6be64e04fc7a8be46b5dacab72fee1612e5b5
4
+ data.tar.gz: 9180a45cb3f7acbe6db6fb9cd74c4ca0f85c85e4
5
+ SHA512:
6
+ metadata.gz: 395ab58d08a47f062b529998bd0fe4a515930d3013176b0e60a58ffba5d587fc18e51c5b4c5629cb2c03c027d464dd867637c4041b012d4c629a509221eaeffe
7
+ data.tar.gz: e767617e733d4c580d08fb04a82e944f46d0cfb388f5e4265e5fd7c933cbb4684d3476befc95210bff29c85d56c81d41fe6464e399d5abce550ab1abce169242
data/bin/slackbot-frd ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thor'
4
+ require 'json'
5
+
6
+ require 'slackbot_frd/initializer/bot_starter'
7
+ require 'slackbot_frd/lib/slack_connection'
8
+ require 'slackbot_frd/lib/bot'
9
+
10
+ begin
11
+ require 'byebug'
12
+ rescue LoadError
13
+ end
14
+
15
+ DEBUG = true
16
+
17
+ PID_FILE_WATCHER = "/tmp/slackbot-frd-watcher.pid"
18
+ PID_FILE_CONNECTION = "/tmp/slackbot-frd-connection.pid"
19
+ BOT_LIST_FILE = "/tmp/slackbot-frd-bot-list.pid"
20
+ ERROR_FILE = "/tmp/slackbot-frd-error-file.pid"
21
+ DEFAULT_CONFIG_FILE = "slackbot-frd.conf"
22
+ LOG_FILE = "slackbot-frd.log"
23
+
24
+ class SlackbotFrdBin < Thor
25
+ desc "list", "List all bots"
26
+ long_desc <<-LONGDESC
27
+ list will print out all available bots
28
+
29
+ > $ slackbot-frd list -- List all installed bots
30
+ LONGDESC
31
+ def list
32
+ # TODO
33
+ end
34
+
35
+ desc "start [bot1] [bot2] [botx...]", "Start all specified bots, or all bots"
36
+ long_desc <<-LONGDESC
37
+ start [bot1] [bot2] [botx...] will start the specified bots.
38
+ If no bots are specified, all available bots will be run.
39
+
40
+ params set via explicit flags will overwrite conflicting environment variables,
41
+ and environment variables will overwrite conflicting config file params.
42
+
43
+ > $ slackbot-frd start -- Start all available bots
44
+ LONGDESC
45
+ option :daemonize, type: :boolean, aliases: 'd'
46
+ option :botdir, type: :string, aliases: 'b'
47
+ option 'config-file'.to_sym, type: :string, aliases: 'c'
48
+ option :token, type: :string, aliases: 't'
49
+ def start(*bots)
50
+ config_file = options['config-file'.to_sym]
51
+ config_file = "#{Dir.pwd}/#{DEFAULT_CONFIG_FILE}" unless config_file
52
+ json = config_file_json(config_file) if config_file
53
+ json ||= {}
54
+
55
+ daemonize = false
56
+ daemonize = json["daemonize"] if json["daemonize"]
57
+ daemonize = ENV["SLACKBOT_FRD_DAEMONIZE"] if ENV["SLACKBOT_FRD_DAEMONIZE"]
58
+ daemonize = options[:daemonize] if options[:daemonize]
59
+
60
+ botdir = Dir.pwd
61
+ botdir = json["botdir"] if json["botdir"]
62
+ botdir = ENV["SLACKBOT_FRD_BOTDIR"] if ENV["SLACKBOT_FRD_BOTDIR"]
63
+ botdir = options[:botdir] if options[:botdir]
64
+ botdir = File.expand_path(botdir)
65
+
66
+ SlackbotFrd::Log.logfile = "#{botdir}/#{LOG_FILE}"
67
+ SlackbotFrd::Log.info("Logging to file '#{SlackbotFrd::Log.logfile}'")
68
+
69
+ token = json["token"]
70
+ token = ENV["SLACKBOT_FRD_TOKEN"] if ENV["SLACKBOT_FRD_TOKEN"]
71
+ token = options[:token] if options[:token]
72
+ unless token
73
+ SlackbotFrd::Log.error("No token found. Cannot authenticate to Slack")
74
+ return
75
+ end
76
+
77
+ if daemonize
78
+ set_watcher_pid(Process.fork{ watch_connection(bots, token, botdir, true) })
79
+ else
80
+ watch_connection(bots, token, botdir)
81
+ end
82
+ end
83
+
84
+ desc "stop", "Stop all bots"
85
+ long_desc <<-LONGDESC
86
+ stop will stop all bots
87
+
88
+ > $ slackbot-frd stop -- Stop all running bots
89
+ LONGDESC
90
+
91
+ def stop
92
+ # first kill the watcher, then kill the connection
93
+ kill_pid(watcher_pid)
94
+ kill_pid(connection_pid)
95
+ end
96
+
97
+ desc "restart", "Stop all bots and restart them"
98
+ long_desc <<-LONGDESC
99
+ restart will restart all bots
100
+
101
+ > $ slackbot-frd restart -- Restart all running bots
102
+ LONGDESC
103
+ def restart
104
+ stop
105
+ start(set_bots)
106
+ end
107
+
108
+ private
109
+ def config_file_json(config_file)
110
+ if File.exists?(config_file)
111
+ content = File.read(config_file)
112
+ return JSON.parse(content)
113
+ end
114
+ nil
115
+ end
116
+
117
+ private
118
+ def bots_from_file
119
+ File.read(BOT_LIST_FILE).split
120
+ end
121
+
122
+ private
123
+ def set_bots_in_file(bots)
124
+ File.write(BOT_LIST_FILE, "#{bots.join("\n")}")
125
+ end
126
+
127
+ private
128
+ def kill_pid(pid)
129
+ # try 3 times to SIGINT the pid, then SIGKILL it
130
+ 3.times do
131
+ break unless running?(pid)
132
+ Process.kill('SIGINT', pid)
133
+ end
134
+ Process.kill('SIGKILL', pid) if running?(pid)
135
+ # TODO log if the process is still running
136
+ end
137
+
138
+ private
139
+ def watch_connection(bots, token, botdir, daemonize = false)
140
+ until errors
141
+ pid = Process.fork do
142
+ Process.daemon if daemonize
143
+ loop { BotStarter.start_bots(ERROR_FILE, token, botdir, bots) }
144
+ end
145
+ set_connection_pid(pid)
146
+ Process.wait(pid)
147
+ end
148
+ if errors
149
+ puts "Could not start connection: #{errors}"
150
+ end
151
+ end
152
+
153
+ private
154
+ def errors
155
+ return File.read(ERROR_FILE).split if File.exists?(ERROR_FILE)
156
+ nil
157
+ end
158
+
159
+ private
160
+ def running?(pid)
161
+ begin
162
+ Process.getpgid(pid.to_i)
163
+ return true
164
+ rescue Errno::ESRCH
165
+ return false
166
+ end
167
+ end
168
+
169
+ private
170
+ def watcher_pid
171
+ File.read(PID_FILE_WATCHER).to_i
172
+ end
173
+
174
+ private
175
+ def connection_pid
176
+ File.read(PID_FILE_CONNECTION).to_i
177
+ end
178
+
179
+ private
180
+ def set_watcher_pid(pid)
181
+ File.write(PID_FILE_WATCHER, pid)
182
+ end
183
+
184
+ private
185
+ def set_connection_pid(pid)
186
+ File.write(PID_FILE_CONNECTION, pid)
187
+ end
188
+
189
+ private
190
+ def delete_pid_files
191
+ File.delete(PID_FILE_WATCHER)
192
+ File.delete(PID_FILE_CONNECTION)
193
+ end
194
+ end
195
+
196
+ SlackbotFrdBin.start(ARGV)
@@ -0,0 +1,3 @@
1
+ Gem.find_files("slackbot_frd/**/*.rb").each do |path|
2
+ require path.gsub(/\.rb$/, '') unless path =~ /bot.*cli/
3
+ end
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thor'
4
+
5
+ require 'slackbot_frd/lib/slack_connection'
6
+ require 'slackbot_frd/lib/bot'
7
+
8
+ begin
9
+ require 'byebug'
10
+ rescue LoadError
11
+ end
12
+
13
+ class BotStarter
14
+ def self.start_bots(errors_file, token, botdir, bots)
15
+ bot_enabled = ->(bot) { bots.empty? || bots.include?(bot) }
16
+
17
+ # Create a new Connection to pass to the bot classes
18
+ slack_connection = SlackbotFrd::SlackConnection.new(token)
19
+
20
+ load_bot_files(botdir)
21
+
22
+ bots = []
23
+ # instantiate them, and then call their add_callbacks method
24
+ ObjectSpace.each_object(Class).select do |klass|
25
+ if klass != SlackbotFrd::Bot && klass.ancestors.include?(SlackbotFrd::Bot) && bot_enabled.call(klass.name)
26
+ SlackbotFrd::Log.debug("Instantiating class '#{klass.to_s}'")
27
+ b = klass.new
28
+ SlackbotFrd::Log.debug("Adding callbacks to bot '#{klass}'")
29
+ b.add_callbacks(slack_connection)
30
+ bots.push(b)
31
+ end
32
+ end
33
+
34
+ if bots.count == 0
35
+ @error ||= []
36
+ @error.push("No bots loaded")
37
+ SlackbotFrd::Log.error("Not starting: no bots found")
38
+ else
39
+ SlackbotFrd::Log.debug("Starting SlackConnection")
40
+ slack_connection.start
41
+ SlackbotFrd::Log.debug("Connection closed")
42
+ end
43
+ end
44
+
45
+ private
46
+ def self.load_bot_files(top_level_dir)
47
+ Dir["#{File.expand_path(top_level_dir)}/**/*.rb"].each do |f|
48
+ SlackbotFrd::Log.debug("Loading bot file '#{f}'")
49
+ load(f)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ require 'slackbot_frd/initializer/bot_starter'
2
+
3
+ class BotStarterCli < Thor
4
+ option :token, type: :string, required: true, aliases: 't'
5
+ option :botdir, type: :string, required: true, aliases: ['b', 'd']
6
+ def start(*bots)
7
+ BotStarter.start_bots(options[:token], options[:botdir], bots)
8
+ end
9
+ end
10
+
11
+ BotStarterCli.start(ARGV)
@@ -0,0 +1,18 @@
1
+ # Subclass the bot class to have your bot loaded and run
2
+ module SlackbotFrd
3
+ class Bot
4
+ def self.only(*bots)
5
+ @bots ||= []
6
+ @bots.push(bots).flatten!
7
+ end
8
+
9
+ class << self
10
+ attr_accessor :bots
11
+ end
12
+
13
+ # This is where the bot adds all of their callbacks to the bpbot
14
+ def add_callbacks(slack_connection)
15
+ raise StandardError.new("You must override the define() method for your bot to do anything")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module SlackbotFrd
3
+ class NoTokenError < StandardError
4
+ def initialize(message = nil)
5
+ if message
6
+ super(message)
7
+ else
8
+ super("An API token is required for authenticating to the Slack API")
9
+ end
10
+ end
11
+ end
12
+
13
+ class AuthenticationFailedError < StandardError
14
+ end
15
+
16
+ class InvalidUserError < StandardError
17
+ end
18
+
19
+ class InvalidChannelError < StandardError
20
+ end
21
+ end
@@ -0,0 +1,60 @@
1
+ require 'colorize'
2
+
3
+ module SlackbotFrd
4
+ class Log
5
+ @levels = {verbose: 1, debug: 2, info: 3, warn: 4, error: 5}
6
+ @default_level = :debug
7
+
8
+ class << self
9
+ attr_writer :level
10
+ attr_accessor :logfile
11
+ end
12
+
13
+ def self.level
14
+ return @default_level unless @level
15
+ @level
16
+ end
17
+
18
+ def self.level_on(level)
19
+ @levels[level] >= @levels[self.level]
20
+ end
21
+
22
+ def self.colors
23
+ [:green, :red, :yellow, :none, :blue]
24
+ end
25
+
26
+ def self.error(message)
27
+ log('Error', message, :red) if level_on(:error)
28
+ end
29
+
30
+ def self.warn(message)
31
+ log('Warn', message, :yellow) if level_on(:warn)
32
+ end
33
+
34
+ def self.debug(message)
35
+ log('Debug', message, :green) if level_on(:debug)
36
+ end
37
+
38
+ def self.info(message)
39
+ log('Info', message, :blue) if level_on(:info)
40
+ end
41
+
42
+ def self.verbose(message)
43
+ log('Verbose', message, :magenta) if level_on(:verbose)
44
+ end
45
+
46
+ def self.log(loglevel, message, color = :none)
47
+ om = "#{DateTime.now.strftime('%Y-%m-%e %H:%M:%S.%L %z')}: [#{loglevel}]: #{message}\n"
48
+ print om.send(color)
49
+ begin
50
+ raise StandardError.new("No log file specified. (Set with SlackbotFrd::Log.logfile=)") unless @logfile
51
+ File.open(@logfile, 'a') do |f|
52
+ f.write(om)
53
+ end
54
+ rescue StandardError => e
55
+ puts "OH NO! ERROR WRITING TO LOG FILE!: #{e}"
56
+ end
57
+ om
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,271 @@
1
+ require 'faye/websocket'
2
+ require 'eventmachine'
3
+ require 'json'
4
+
5
+ require 'slackbot_frd/lib/errors'
6
+ require 'slackbot_frd/lib/user_channel_callbacks'
7
+ require 'slackbot_frd/lib/log'
8
+
9
+ require 'slackbot_frd/slack_methods/rtm_start'
10
+ require 'slackbot_frd/slack_methods/chat_post_message'
11
+ require 'slackbot_frd/slack_methods/im_channels_list'
12
+ require 'slackbot_frd/slack_methods/channels_list'
13
+ require 'slackbot_frd/slack_methods/users_list'
14
+
15
+ module SlackbotFrd
16
+ class SlackConnection
17
+ FILE_PATH = File.expand_path(__FILE__)
18
+ APP_ROOT = File.expand_path(File.dirname(File.dirname(FILE_PATH)))
19
+ FILE_DIR = File.dirname(FILE_PATH)
20
+ LOG_FILE = "#{APP_ROOT}/bp-slackbot.log"
21
+ PID_FILE_NAME = "#{APP_ROOT}/bp-slackbot.pid"
22
+
23
+ attr_accessor :token
24
+
25
+ def initialize(token)
26
+ unless token
27
+ SlackbotFrd::Log::error("No token passed to #{self.class}")
28
+ raise NoTokenError.new
29
+ end
30
+
31
+ @token = token
32
+ @event_id = 0
33
+ @on_connected_callbacks = []
34
+ @on_disconnected_callbacks = []
35
+ @on_message_callbacks = UserChannelCallbacks.new
36
+ @on_channel_left_callbacks = UserChannelCallbacks.new
37
+ @on_channel_joined_callbacks = UserChannelCallbacks.new
38
+
39
+ # These hashes are used to map ids to names efficiently
40
+ @user_id_to_name = {}
41
+ @user_name_to_id = {}
42
+ @channel_id_to_name = {}
43
+ @channel_name_to_id = {}
44
+
45
+ restrict_actions_to_channels_joined
46
+ SlackbotFrd::Log::debug("Done initializing #{self.class}")
47
+ end
48
+
49
+ def start
50
+ # Write pid file
51
+ File.write(PID_FILE_NAME, "#{Process.pid}")
52
+
53
+ SlackbotFrd::Log::info("#{self.class}: starting event machine")
54
+
55
+ EM.run do
56
+ wss_url = SlackbotFrd::SlackMethods::RtmStart.wss_url(@token)
57
+ unless wss_url
58
+ str = "No Real Time stream opened by slack. Check for correct authentication token"
59
+ SlackbotFrd::Log.error(str)
60
+ File.append(@errors_file, str)
61
+ return
62
+ end
63
+ @ws = Faye::WebSocket::Client.new(wss_url)
64
+
65
+ @on_connected_callbacks.each { |callback| @ws.on(:open, &callback) }
66
+ @on_disconnected_callbacks.each { |callback| @ws.on(:close, &callback) }
67
+ @ws.on(:message) { |event| process_message_received(event) }
68
+
69
+ # Clean up our pid file
70
+ @ws.on(:close) { |event| File.delete(PID_FILE_NAME) }
71
+ end
72
+
73
+ SlackbotFrd::Log::debug("#{self.class}: event machine started")
74
+ end
75
+
76
+ def event_id
77
+ @event_id += 1
78
+ @event_id
79
+ end
80
+
81
+ def on_connected(&block)
82
+ @on_connected_callbacks.push(block)
83
+ end
84
+
85
+ def on_close(&block)
86
+ @on_disconnected_callbacks.push(block)
87
+ end
88
+
89
+ def on_message(user = :any, channel = :any, &block)
90
+ @on_message_callbacks.add(user_name_to_id(user), channel_name_to_id(channel), block)
91
+ end
92
+
93
+ def on_channel_left(user = :any, channel = :any, &block)
94
+ @on_channel_left_callbacks.add(user_name_to_id(user), channel_name_to_id(channel), block)
95
+ end
96
+
97
+ def on_channel_joined(user = :any, channel = :any, &block)
98
+ u = user_name_to_id(user)
99
+ c = channel_name_to_id(channel)
100
+ @on_channel_joined_callbacks.add(u, c, block)
101
+ end
102
+
103
+ def send_message_as_user(channel, message)
104
+ unless @ws
105
+ SlackbotFrd::Log::error("Cannot send message '#{message}' as user to channel '#{channel}' because not connected to wss stream")
106
+ raise NotConnectedError.new("Not connected to wss stream")
107
+ end
108
+
109
+ resp = @ws.send({
110
+ id: event_id,
111
+ type: "message",
112
+ channel: channel_name_to_id(channel),
113
+ text: message
114
+ }.to_json)
115
+
116
+ SlackbotFrd::Log::debug("#{self.class}: sending message '#{message}' as user to channel '#{channel}'. Response: #{resp}")
117
+ end
118
+
119
+ def send_message(channel, message, username, avatar, avatar_is_emoji)
120
+ resp = SlackbotFrd::SlackMethods::ChatPostMessage.postMessage(
121
+ @token,
122
+ channel_name_to_id(channel),
123
+ message,
124
+ username,
125
+ avatar,
126
+ avatar_is_emoji
127
+ )
128
+ SlackbotFrd::Log::debug("#{self.class}: sending message '#{message}' as user '#{username}' to channel '#{channel}'. Response: #{resp}")
129
+ end
130
+
131
+ def restrict_actions_to_channels_joined(value = true)
132
+ @restrict_actions_to_channels_joined = value
133
+ end
134
+
135
+ def user_id_to_name(user_id)
136
+ return user_id if user_id == :any || user_id == :bot
137
+ unless @user_id_to_name && @user_id_to_name.has_key?(user_id)
138
+ refresh_user_info
139
+ end
140
+ SlackbotFrd::Log::warn("#{self.class}: User id '#{user_id}' not found") unless @user_id_to_name.include?(user_id)
141
+ @user_id_to_name[user_id]
142
+ end
143
+
144
+ def user_name_to_id(user_name)
145
+ return user_name if user_name == :any || user_name == :bot
146
+ unless @user_name_to_id && @user_name_to_id.has_key?(user_name)
147
+ refresh_user_info
148
+ end
149
+ SlackbotFrd::Log::warn("#{self.class}: User name '#{user_name}' not found") unless @user_name_to_id.include?(user_name)
150
+ @user_name_to_id[user_name]
151
+ end
152
+
153
+ def channel_id_to_name(channel_id)
154
+ unless @channel_id_to_name && @channel_id_to_name.has_key?(channel_id)
155
+ refresh_channel_info
156
+ end
157
+ SlackbotFrd::Log::warn("#{self.class}: Channel id '#{channel_id}' not found") unless @channel_id_to_name.include?(channel_id)
158
+ @channel_id_to_name[channel_id]
159
+ end
160
+
161
+ def channel_name_to_id(channel_name)
162
+ return channel_name if channel_name == :any
163
+ nc = normalize_channel_name(channel_name)
164
+ unless @channel_name_to_id && @channel_name_to_id.has_key?(nc)
165
+ refresh_channel_info
166
+ end
167
+ SlackbotFrd::Log::warn("#{self.class}: Channel name '#{nc}' not found") unless @channel_name_to_id.include?(nc)
168
+ @channel_name_to_id[nc]
169
+ end
170
+
171
+ private
172
+ def normalize_channel_name(channel_name)
173
+ return channel_name[1..-1] if channel_name.start_with?('#')
174
+ channel_name
175
+ end
176
+
177
+ private
178
+ def process_message_received(event)
179
+ message = JSON.parse(event.data)
180
+ SlackbotFrd::Log::verbose("#{self.class}: Message received: #{message}")
181
+ if message["type"] == "message"
182
+ if message["subtype"] == "channel_join"
183
+ process_join_message(message)
184
+ elsif message["subtype"] == "channel_leave"
185
+ process_leave_message(message)
186
+ elsif message["subtype"] == "file_share"
187
+ process_file_share(message)
188
+ else
189
+ process_chat_message(message)
190
+ end
191
+ end
192
+ end
193
+
194
+ private
195
+ def process_file_share(message)
196
+ SlackbotFrd::Log::verbose("#{self.class}: Processing file share: #{message}")
197
+ SlackbotFrd::Log::debug("#{self.class}: Not processing file share because it is not implemented:")
198
+ end
199
+
200
+ private
201
+ def process_chat_message(message)
202
+ SlackbotFrd::Log::verbose("#{self.class}: Processing chat message: #{message}")
203
+
204
+ user = message["user"]
205
+ user = :bot if message["subtype"] == "bot_message"
206
+ channel = message["channel"]
207
+ text = message["text"]
208
+
209
+ unless user
210
+ SlackbotFrd::Log::warn("#{self.class}: Chat message doesn't include user! message: #{message}")
211
+ return
212
+ end
213
+
214
+ unless channel
215
+ SlackbotFrd::Log::warn("#{self.class}: Chat message doesn't include channel! message: #{message}")
216
+ return
217
+ end
218
+
219
+ @on_message_callbacks.where_include_all(user, channel).each do |callback|
220
+ # instance_exec allows the user to call send_message and send_message_as_user
221
+ # without prefixing like this: slack_connection.send_message()
222
+ #
223
+ # However, it makes calling functions defined in the class not work, so
224
+ # for now we aren't going to do it
225
+ #
226
+ #instance_exec(user_id_to_name(user), channel_id_to_name(channel), text, &callback)
227
+ callback.call(user_id_to_name(user), channel_id_to_name(channel), text)
228
+ end
229
+ end
230
+
231
+ private
232
+ def process_join_message(message)
233
+ SlackbotFrd::Log::verbose("#{self.class}: Processing join message: #{message}")
234
+ user = message["user"]
235
+ user = :bot if message["subtype"] == "bot_message"
236
+ channel = message["channel"]
237
+ @on_channel_joined_callbacks.where_include_all(user, channel).each do |callback|
238
+ callback.call(user_id_to_name(user), channel_id_to_name(channel))
239
+ end
240
+ end
241
+
242
+ private
243
+ def process_leave_message(message)
244
+ SlackbotFrd::Log::verbose("#{self.class}: Processing leave message: #{message}")
245
+ user = message["user"]
246
+ user = :bot if message["subtype"] == "bot_message"
247
+ channel = message["channel"]
248
+ @on_channel_left_callbacks.where_include_all(user, channel).each do |callback|
249
+ callback.call(user_id_to_name(user), channel_id_to_name(channel))
250
+ end
251
+ end
252
+
253
+ private
254
+ def refresh_user_info
255
+ users_list = SlackbotFrd::SlackMethods::UsersList.new(@token).connect
256
+ @user_id_to_name = users_list.ids_to_names
257
+ @user_name_to_id = users_list.names_to_ids
258
+ end
259
+
260
+ private
261
+ def refresh_channel_info
262
+ channels_list = SlackbotFrd::SlackMethods::ChannelsList.new(@token).connect
263
+ @channel_id_to_name = channels_list.ids_to_names
264
+ @channel_name_to_id = channels_list.names_to_ids
265
+
266
+ im_channels_list = SlackbotFrd::SlackMethods::ImChannelsList.new(@token).connect
267
+ @channel_id_to_name.merge!(im_channels_list.ids_to_names)
268
+ @channel_name_to_id.merge!(im_channels_list.names_to_ids)
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,53 @@
1
+ require 'slackbot_frd/lib/log'
2
+
3
+ module SlackbotFrd
4
+ class UserChannelCallbacks
5
+ def initialize
6
+ @conditions = {}
7
+ @conditions[:any] = {}
8
+ @conditions[:any][:any] = []
9
+ end
10
+
11
+ def init(user, channel)
12
+ unless user
13
+ Log::error("#{self.class}: Invalid user '#{user}'")
14
+ raise InvalidUserError.new
15
+ end
16
+ unless channel
17
+ Log::error("#{self.class}: Invalid channel '#{channel}'")
18
+ raise InvalidChannelError.new
19
+ end
20
+ @conditions[user] ||= {}
21
+ @conditions[user][:any] ||= []
22
+ @conditions[:any][channel] ||= []
23
+ @conditions[user][channel] ||= []
24
+ end
25
+
26
+ def add(user, channel, callback)
27
+ init(user, channel)
28
+ @conditions[user][channel].push(callback)
29
+ end
30
+
31
+ def where(user, channel)
32
+ init(user, channel)
33
+ @conditions[user][channel] || []
34
+ end
35
+
36
+ def where_all
37
+ @conditions[:any][:any] || []
38
+ end
39
+
40
+ def where_include_all(user, channel)
41
+ init(user, channel)
42
+ retval = @conditions[:any][:any].dup || []
43
+ retval.concat(@conditions[user][:any] || [])
44
+ retval.concat(@conditions[:any][channel] || [])
45
+ retval.concat(@conditions[user][channel] || [])
46
+ retval
47
+ end
48
+
49
+ def to_s
50
+ "#{@conditions}"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module SlackbotFrd
5
+ module SlackMethods
6
+ class ChannelsList
7
+ include HTTParty
8
+ base_uri 'https://slack.com/api/channels.list'
9
+
10
+ attr_reader :response
11
+
12
+ def initialize(token)
13
+ @token = token
14
+ end
15
+
16
+ def connect
17
+ @response = JSON.parse(self.class.post('', :body => { token: @token } ).body)
18
+ self
19
+ end
20
+
21
+ def ids_to_names
22
+ retval = {}
23
+ @response["channels"].each do |channel|
24
+ retval[channel["id"]] = channel["name"]
25
+ end
26
+ retval
27
+ end
28
+
29
+ def names_to_ids
30
+ retval = {}
31
+ @response["channels"].each do |channel|
32
+ retval[channel["name"]] = channel["id"]
33
+ end
34
+ retval
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module SlackbotFrd
5
+ module SlackMethods
6
+ class ChatPostMessage
7
+ include HTTParty
8
+ base_uri 'https://slack.com/api/chat.postMessage'
9
+
10
+ def self.postMessage(token, channel, message, username = nil, avatar = nil, avatar_is_emoji = nil)
11
+ r = ChatPostMessage.new(token, channel, message, username, avatar, avatar_is_emoji)
12
+ r.postMessage
13
+ end
14
+
15
+ def initialize(token, channel, message, username = nil, avatar = nil, avatar_is_emoji = nil)
16
+ @token = token
17
+ @channel = channel
18
+ @message = message
19
+ @username = username
20
+ @avatar = avatar
21
+ @avatar_is_emoji = avatar_is_emoji
22
+ end
23
+
24
+ def postMessage
25
+ body = {
26
+ token: @token,
27
+ channel: @channel,
28
+ text: @message,
29
+ }
30
+
31
+ if @username
32
+ body.merge!({ username: @username })
33
+
34
+ if @avatar_is_emoji
35
+ body.merge!({ icon_emoji: @avatar })
36
+ else
37
+ body.merge!({ icon_url: @avatar })
38
+ end
39
+ else
40
+ body.merge!({ as_user: true })
41
+ end
42
+
43
+ @response = self.class.post('', :body => body)
44
+ @response
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module SlackbotFrd
5
+ module SlackMethods
6
+ class ImChannelsList
7
+ include HTTParty
8
+ base_uri 'https://slack.com/api/im.list'
9
+
10
+ attr_reader :response
11
+
12
+ def initialize(token)
13
+ @token = token
14
+ end
15
+
16
+ def connect
17
+ @response = JSON.parse(self.class.post('', :body => { token: @token } ).body)
18
+ self
19
+ end
20
+
21
+ def ids_to_names
22
+ retval = {}
23
+ @response["ims"].each do |im|
24
+ retval[im["id"]] = im["user"]
25
+ end
26
+ retval
27
+ end
28
+
29
+ def names_to_ids
30
+ retval = {}
31
+ @response["ims"].each do |im|
32
+ retval[im["user"]] = im["id"]
33
+ end
34
+ retval
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module SlackbotFrd
5
+ module SlackMethods
6
+ class RtmStart
7
+ include HTTParty
8
+ base_uri 'https://slack.com/api/rtm.start'
9
+
10
+ def self.wss_url(token)
11
+ r = RtmStart.new(token)
12
+ r.connect
13
+ r.wss_url
14
+ end
15
+
16
+ def initialize(token)
17
+ @token = token
18
+ end
19
+
20
+ def connect
21
+ @response = JSON.parse(self.class.post('', :body => { token: @token } ).body)
22
+ @response
23
+ end
24
+
25
+ def wss_url
26
+ #return "ERR" unless @response.has_key?("url")
27
+ @response["url"]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module SlackbotFrd
5
+ module SlackMethods
6
+ class UsersList
7
+ include HTTParty
8
+ base_uri 'https://slack.com/api/users.list'
9
+
10
+ attr_reader :response
11
+
12
+ def initialize(token)
13
+ @token = token
14
+ end
15
+
16
+ def connect
17
+ @response = JSON.parse(self.class.post('', :body => { token: @token } ).body)
18
+ self
19
+ end
20
+
21
+ def ids_to_names
22
+ retval = {}
23
+ @response["members"].each do |user|
24
+ retval[user["id"]] = user["name"]
25
+ end
26
+ retval
27
+ end
28
+
29
+ def names_to_ids
30
+ retval = {}
31
+ @response["members"].each do |user|
32
+ retval[user["name"]] = user["id"]
33
+ end
34
+ retval
35
+ end
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slackbot_frd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben Porter
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: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
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.13'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.13'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faye-websocket
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
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
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.19'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.19'
83
+ - !ruby/object:Gem::Dependency
84
+ name: daemons
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: json
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.8'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.8'
111
+ - !ruby/object:Gem::Dependency
112
+ name: byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.1'
139
+ description: The slack web api is good, but very raw. What you need is a great ruby
140
+ framework to abstract away all that. This is it! This framework allows you to
141
+ write bots easily by providing methods that are easy to call. Behind the scenes,
142
+ the framework is negotiating your real time stream, converting channel names and
143
+ user names to and from IDs so you can use the names instead, and parsing/classifying
144
+ the real time messages into useful types that you can hook into. Don't write your
145
+ bot without this.
146
+ email: BenjaminPorter86@gmail.com
147
+ executables:
148
+ - slackbot-frd
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - bin/slackbot-frd
153
+ - lib/slackbot_frd.rb
154
+ - lib/slackbot_frd/initializer/bot_starter.rb
155
+ - lib/slackbot_frd/initializer/bot_starter_cli.rb
156
+ - lib/slackbot_frd/lib/bot.rb
157
+ - lib/slackbot_frd/lib/errors.rb
158
+ - lib/slackbot_frd/lib/log.rb
159
+ - lib/slackbot_frd/lib/slack_connection.rb
160
+ - lib/slackbot_frd/lib/user_channel_callbacks.rb
161
+ - lib/slackbot_frd/slack_methods/channels_list.rb
162
+ - lib/slackbot_frd/slack_methods/chat_post_message.rb
163
+ - lib/slackbot_frd/slack_methods/im_channels_list.rb
164
+ - lib/slackbot_frd/slack_methods/rtm_start.rb
165
+ - lib/slackbot_frd/slack_methods/users_list.rb
166
+ homepage: http://rubygems.org/gems/slackbot_frd
167
+ licenses:
168
+ - MIT
169
+ metadata: {}
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ requirements: []
185
+ rubyforge_project:
186
+ rubygems_version: 2.2.2
187
+ signing_key:
188
+ specification_version: 4
189
+ summary: slackbot_frd provides a dirt-simple framework for implementing one or more
190
+ slack bots
191
+ test_files: []