slack-smart-bot 0.9.6 → 1.0.0

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -1
  3. data/README.md +80 -175
  4. data/lib/slack-smart-bot.rb +95 -1276
  5. data/lib/slack-smart-bot_rules.rb +50 -69
  6. data/lib/slack/smart-bot/comm.rb +197 -0
  7. data/lib/slack/smart-bot/commands/general/bot_help.rb +74 -0
  8. data/lib/slack/smart-bot/commands/general/bot_status.rb +41 -0
  9. data/lib/slack/smart-bot/commands/general/bye_bot.rb +17 -0
  10. data/lib/slack/smart-bot/commands/general/hi_bot.rb +24 -0
  11. data/lib/slack/smart-bot/commands/general/stop_using_rules.rb +44 -0
  12. data/lib/slack/smart-bot/commands/general/use_rules.rb +48 -0
  13. data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +64 -0
  14. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +87 -0
  15. data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +63 -0
  16. data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +21 -0
  17. data/lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb +26 -0
  18. data/lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb +28 -0
  19. data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +53 -0
  20. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +57 -0
  21. data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +20 -0
  22. data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +27 -0
  23. data/lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb +30 -0
  24. data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +41 -0
  25. data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +48 -0
  26. data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +32 -0
  27. data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +37 -0
  28. data/lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb +38 -0
  29. data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +42 -0
  30. data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +35 -0
  31. data/lib/slack/smart-bot/commands/on_master/create_bot.rb +94 -0
  32. data/lib/slack/smart-bot/listen.rb +36 -0
  33. data/lib/slack/smart-bot/process.rb +169 -0
  34. data/lib/slack/smart-bot/process_first.rb +201 -0
  35. data/lib/slack/smart-bot/treat_message.rb +139 -0
  36. data/lib/slack/smart-bot/utils.rb +299 -0
  37. metadata +71 -5
@@ -10,28 +10,17 @@ def git_project()
10
10
  ""
11
11
  end
12
12
 
13
- #for the case of testing, just run this file adding in the end a call to rules with the parameters you want
14
- if defined?(respond)
15
- @testing = false
16
- else
17
- @testing = true
18
- @questions = Hash.new()
19
-
20
- def respond(message, dest)
21
- puts message
22
- end
23
-
24
- #context: previous message
25
- #to: user that should answer
26
- def ask(question, context, to, dest)
27
- puts "Bot: #{question}"
28
- @questions[to] = context
29
- end
30
- end
31
-
32
- # from: Full name of the person sending the message
13
+ # user: user slack object
33
14
  # command: command to run
34
15
  # processed: in case the command has been already processed on Bot class, by default false
16
+ # dest: channel_id
17
+ # files: files attached
18
+ # rules_file: rules_file name
19
+ #
20
+ # About the Help:
21
+ # Add as a comment starting by "help:" the help you need to add to the `bot help` and `bot rules` commands.
22
+ # The command logic needs to be added with ``, and the parameters to supply need to be in capital for example: `echo SOMETHING`
23
+ #
35
24
  # help: ===================================
36
25
  # help:
37
26
  # help: *These are specific commands for this bot on this Channel.*
@@ -42,18 +31,10 @@ end
42
31
  # help: `@NAME_OF_BOT THE_COMMAND`
43
32
  # help: `NAME_OF_BOT THE_COMMAND`
44
33
  # help:
45
- def rules(user, command, processed, dest, files=[])
34
+ def rules(user, command, processed, dest, files = [], rules_file = "")
46
35
  from = user.name
47
36
  display_name = user.profile.display_name
48
37
 
49
- if @testing
50
- puts "#{from}: #{command}"
51
- if @questions.keys.include?(from)
52
- context = @questions[from]
53
- @questions[from] = command
54
- command = context
55
- end
56
- end
57
38
  begin
58
39
  case command
59
40
 
@@ -62,7 +43,7 @@ def rules(user, command, processed, dest, files=[])
62
43
  # help: repeats SOMETHING
63
44
  # help:
64
45
  when /^echo\s(.+)/i
65
- respond $1, dest
46
+ respond $1
66
47
 
67
48
  # help: ----------------------------------------------
68
49
  # help: `go to sleep`
@@ -70,20 +51,20 @@ def rules(user, command, processed, dest, files=[])
70
51
  # help:
71
52
  when /^go\sto\ssleep/i
72
53
  unless @questions.keys.include?(from)
73
- ask("do you want me to take a siesta?", command, from, dest)
54
+ ask "do you want me to take a siesta?"
74
55
  else
75
56
  case @questions[from]
76
57
  when /yes/i, /yep/i, /sure/i
77
58
  @questions.delete(from)
78
- respond "I'll be sleeping for 5 secs... just for you", dest
79
- respond "zZzzzzzZZZZZZzzzzzzz!", dest
59
+ respond "I'll be sleeping for 5 secs... just for you"
60
+ respond "zZzzzzzZZZZZZzzzzzzz!"
80
61
  sleep 5
81
62
  when /no/i, /nope/i, /cancel/i
82
63
  @questions.delete(from)
83
- respond "Thanks, I'm happy to be awake", dest
64
+ respond "Thanks, I'm happy to be awake"
84
65
  else
85
- respond "I don't understand", dest
86
- ask("are you sure do you want me to sleep? (yes or no)", "go to sleep", from, dest)
66
+ respond "I don't understand"
67
+ ask "are you sure do you want me to sleep? (yes or no)"
87
68
  end
88
69
  end
89
70
 
@@ -92,57 +73,57 @@ def rules(user, command, processed, dest, files=[])
92
73
  # help: It will run the process and report the results when done
93
74
  # help:
94
75
  when /^run something/i
95
- respond "Running", dest
76
+ respond "Running"
96
77
 
97
78
  process_to_run = "ruby -v"
98
79
  process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)
99
80
  stdout, stderr, status = Open3.capture3(process_to_run)
100
81
  if stderr == ""
101
82
  if stdout == ""
102
- respond "#{display_name}: Nothing returned.", dest
83
+ respond "#{display_name}: Nothing returned."
103
84
  else
104
- respond "#{display_name}: #{stdout}", dest
85
+ respond "#{display_name}: #{stdout}"
105
86
  end
106
87
  else
107
- respond "#{display_name}: #{stderr}", dest
88
+ respond "#{display_name}: #{stdout} #{stderr}"
108
89
  end
109
90
 
110
- # Example downloading a file from slack
111
- # if !files.nil? and files.size == 1 and files[0].filetype == 'yaml'
112
- # require 'nice_http'
113
- # http = NiceHttp.new(host: "https://files.slack.com", headers: { "Authorization" => "Bearer #{config[:token]}" })
114
- # http.get(files[0].url_private_download, save_data: './tmp/')
115
- # end
91
+ # Examples for respond and respond_direct
92
+ # # send 'the message' to the channel or direct message where the command was written
93
+ # respond "the message"
94
+ # # send 'the message' privately as a direct message to the user that sent the command
95
+ # respond_direct "the message"
96
+ # # send 'the message' to a specific channel name
97
+ # respond "the message", 'my_channel'
98
+ # # send 'the message' to a specific channel id
99
+ # respond "the message", 'CSU34D33'
100
+ # # send 'the message' to a specific user as direct message
101
+ # respond "the message", '@theuser'
102
+ # # send 'the message' to a specific user id as direct message
103
+ # respond "the message", 'US3344D3'
104
+
105
+ # Example downloading a file from slack
106
+ # if !files.nil? and files.size == 1 and files[0].filetype == 'yaml'
107
+ # require 'nice_http'
108
+ # http = NiceHttp.new(host: "https://files.slack.com", headers: { "Authorization" => "Bearer #{config[:token]}" })
109
+ # http.get(files[0].url_private_download, save_data: './tmp/')
110
+ # end
111
+
112
+ # Examples sending a file to slack:
113
+ # send_file(to, msg, filepath, title, format, type = "text")
114
+ # send_file(dest, 'the message', "#{project_folder}/temp/logs_ptBI.log", 'title', 'text/plain', "text")
115
+ # send_file(dest, 'the message', "#{project_folder}/temp/example.jpeg", 'title', 'image/jpeg', "jpg")
116
116
 
117
- # Examples sending a file to slack:
118
- # send_file(to, msg, filepath, title, format, type = "text")
119
- # send_file(dest, 'the message', "#{project_folder}/temp/logs_ptBI.log", 'title', 'text/plain', "text")
120
- # send_file(dest, 'the message', "#{project_folder}/temp/example.jpeg", 'title', 'image/jpeg', "jpg")
121
117
 
122
118
  else
123
119
  unless processed
124
- if @channel_id == dest or dest[0]=="D" or dest[0] == "G" #not on extended channels
125
- resp = %w{ what huh sorry }.sample
126
- respond "#{display_name}: #{resp}?", dest
120
+ if @channel_id == dest or dest[0] == "D" or dest[0] == "G" #not on extended channels
121
+ dont_understand()
127
122
  end
128
123
  end
129
124
  end
130
-
131
125
  rescue => exception
132
- if defined?(@logger)
133
- @logger.fatal exception
134
- respond "Unexpected error!! Please contact an admin to solve it: <@#{ADMIN_USERS.join('>, <@')}>", dest
135
- else
136
- puts exception
137
- end
126
+ @logger.fatal exception
127
+ respond "Unexpected error!! Please contact an admin to solve it: <@#{ADMIN_USERS.join(">, <@")}>"
138
128
  end
139
129
  end
140
-
141
- #for the case of testing just running this file, write the dialogue in here:
142
- if @testing
143
- require "nice_hash"
144
- user = { name: "Peter Johnson", id: "Uxxxxxx" }
145
-
146
- rules user, "go to sleep, you look tired", false, nil
147
- rules user, "yes", false, nil
148
- end
@@ -0,0 +1,197 @@
1
+ class SlackSmartBot
2
+ def respond(msg, dest = nil)
3
+ if dest.nil? and Thread.current.key?(:dest)
4
+ dest = Thread.current[:dest]
5
+ end
6
+ dest = @channels_id[dest] if @channels_id.key?(dest) #it is a name of channel
7
+
8
+ if dest.nil?
9
+ client.message(channel: @channel_id, text: msg, as_user: true)
10
+ if config[:testing] and ON_MASTER_BOT
11
+ open("./buffer.log", "a") { |f|
12
+ f.puts "|#{@channel_id}|#{config[:nick_id]}|#{msg}"
13
+ }
14
+ end
15
+ elsif dest[0] == "C" or dest[0] == "G" # channel
16
+ client.message(channel: dest, text: msg, as_user: true)
17
+ if config[:testing] and ON_MASTER_BOT
18
+ open("./buffer.log", "a") { |f|
19
+ f.puts "|#{dest}|#{config[:nick_id]}|#{msg}"
20
+ }
21
+ end
22
+ elsif dest[0] == "D" or dest[0] == "U" # Direct message
23
+ send_msg_user(dest, msg)
24
+ elsif dest[0] == "@"
25
+ begin
26
+ user_info = client.web_client.users_info(user: dest)
27
+ send_msg_user(user_info.user.id, msg)
28
+ rescue Exception => stack
29
+ @logger.warn("user #{dest} not found.")
30
+ @logger.warn stack
31
+ if Thread.current.key?(:dest)
32
+ respond("User #{dest} not found.")
33
+ end
34
+ end
35
+ else
36
+ @logger.warn("method respond not treated correctly: msg:#{msg} dest:#{dest}")
37
+ end
38
+ end
39
+
40
+ def respond_direct(msg)
41
+ dest = Thread.current[:user].id
42
+ respond(msg, dest)
43
+ end
44
+
45
+ #context: previous message
46
+ #to: user that should answer
47
+ def ask(question, context = nil, to = nil, dest = nil)
48
+ if dest.nil? and Thread.current.key?(:dest)
49
+ dest = Thread.current[:dest]
50
+ end
51
+ if to.nil?
52
+ to = Thread.current[:user].name
53
+ end
54
+ if context.nil?
55
+ context = Thread.current[:command]
56
+ end
57
+ message = "#{to}: #{question}"
58
+ if dest.nil?
59
+ client.message(channel: @channel_id, text: message, as_user: true)
60
+ if config[:testing] and ON_MASTER_BOT
61
+ open("./buffer.log", "a") { |f|
62
+ f.puts "|#{@channel_id}|#{config[:nick_id]}|#{message}"
63
+ }
64
+ end
65
+ elsif dest[0] == "C" or dest[0] == "G" # channel
66
+ client.message(channel: dest, text: message, as_user: true)
67
+ if config[:testing] and ON_MASTER_BOT
68
+ open("./buffer.log", "a") { |f|
69
+ f.puts "|#{dest}|#{config[:nick_id]}|#{message}"
70
+ }
71
+ end
72
+ elsif dest[0] == "D" #private message
73
+ send_msg_user(dest, message)
74
+ end
75
+ @questions[to] = context
76
+ end
77
+
78
+ # to: (String) Channel name or id
79
+ # msg: (String) message to send
80
+ def send_msg_channel(to, msg)
81
+ unless msg == ""
82
+ get_channels_name_and_id() unless @channels_name.key?(to) or @channels_id.key?(to)
83
+ if @channels_name.key?(to) #it is an id
84
+ channel_id = to
85
+ elsif @channels_id.key?(to) #it is a channel name
86
+ channel_id = @channels_id[to]
87
+ else
88
+ @logger.fatal "Channel: #{to} not found. Message: #{msg}"
89
+ end
90
+ client.message(channel: channel_id, text: msg, as_user: true)
91
+ if config[:testing] and ON_MASTER_BOT
92
+ open("./buffer.log", "a") { |f|
93
+ f.puts "|#{to}|#{config[:nick_id]}|#{msg}"
94
+ }
95
+ end
96
+ end
97
+ end
98
+
99
+ #to send messages without listening for a response to users
100
+ def send_msg_user(id_user, msg)
101
+ unless msg == ""
102
+ if id_user[0] == "D"
103
+ client.message(channel: id_user, as_user: true, text: msg)
104
+ if config[:testing] and ON_MASTER_BOT
105
+ open("./buffer.log", "a") { |f|
106
+ f.puts "|#{id_user}|#{config[:nick_id]}|#{msg}"
107
+ }
108
+ end
109
+ else
110
+ im = client.web_client.im_open(user: id_user)
111
+ client.message(channel: im["channel"]["id"], as_user: true, text: msg)
112
+ if config[:testing] and ON_MASTER_BOT
113
+ open("./buffer.log", "a") { |f|
114
+ f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{msg}"
115
+ }
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ #to send a file to an user or channel
122
+ #send_file(dest, 'the message', "#{project_folder}/temp/logs_ptBI.log", 'message to be sent', 'text/plain', "text")
123
+ #send_file(dest, 'the message', "#{project_folder}/temp/example.jpeg", 'message to be sent', 'image/jpeg', "jpg")
124
+ def send_file(to, msg, file, title, format, type = "text")
125
+ if to[0] == "U" #user
126
+ im = client.web_client.im_open(user: to)
127
+ channel = im["channel"]["id"]
128
+ else
129
+ channel = to
130
+ end
131
+
132
+ client.web_client.files_upload(
133
+ channels: channel,
134
+ as_user: true,
135
+ file: Faraday::UploadIO.new(file, format),
136
+ title: title,
137
+ filename: file,
138
+ filetype: type,
139
+ initial_comment: msg,
140
+ )
141
+ end
142
+
143
+ def dont_understand(rules_file = nil, command = nil, user = nil, dest = nil, answer = ["what?", "huh?", "sorry?", "what do you mean?", "I don't understand"])
144
+ if command.nil?
145
+ command = Thread.current[:command]
146
+ end
147
+ if user.nil?
148
+ user = Thread.current[:user]
149
+ end
150
+ if dest.nil?
151
+ dest = Thread.current[:dest]
152
+ end
153
+ if rules_file.nil?
154
+ rules_file = Thread.current[:rules_file]
155
+ end
156
+
157
+ if rules_file == ""
158
+ res_final = []
159
+ else
160
+ text = get_help(rules_file, dest, user.name)
161
+
162
+ ff = text.scan(/\s*`\s*([^`]+)\s*`\s*/i).flatten
163
+ ff.delete("!THE_COMMAND")
164
+ ff.delete("@NAME_OF_BOT THE_COMMAND")
165
+ ff.delete("NAME_OF_BOT THE_COMMAND")
166
+
167
+ ff2 = {}
168
+ acommand = command.split(/\s+/)
169
+ ff.each do |f|
170
+ ff2[f] = ""
171
+ af = f.split(/\s+/)
172
+ af.each_with_index do |word, i|
173
+ if acommand.size >= (i - 1) and word.match?(/[A-Z_\-#@]+/)
174
+ ff2[f] += "#{acommand[i]} "
175
+ else
176
+ ff2[f] += "#{word} "
177
+ end
178
+ end
179
+ ff2[f].rstrip!
180
+ end
181
+
182
+ spell_checker = DidYouMean::SpellChecker.new(dictionary: ff2.values)
183
+ res = spell_checker.correct(command).uniq
184
+ res_final = []
185
+ res.each do |r|
186
+ res_final << (ff2.select { |k, v| v == r }).keys
187
+ end
188
+ res_final.flatten!
189
+ end
190
+ if res_final.empty?
191
+ resp = answer.sample
192
+ respond "#{user.profile.display_name}, #{resp}", dest
193
+ else
194
+ respond "#{user.profile.display_name}, I don't understand. Maybe you are trying to say:\n`#{res_final[0..4].join("`\n`")}`", dest
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,74 @@
1
+ class SlackSmartBot
2
+
3
+ # help: ----------------------------------------------
4
+ # help: `bot help`
5
+ # help: `bot help COMMAND`
6
+ # help: `bot rules`
7
+ # help: `bot rules COMMAND`
8
+ # help: `bot what can I do?`
9
+ # help: it will display this help
10
+ # help: if COMMAND supplied just help for that command
11
+ # help: `bot rules` will show only the specific rules for this channel.
12
+ # help:
13
+ def bot_help(user, from, dest, dchannel, specific, help_command, rules_file)
14
+ help_message_rules = ""
15
+ help_found = false
16
+
17
+ message = ""
18
+
19
+ help_message = get_help(rules_file, dest, from, specific)
20
+
21
+ if help_command.to_s != ""
22
+ help_message.split(/^\s*-------*$/).each do |h|
23
+ if h.match?(/[`_]#{help_command}/i)
24
+ respond h, dest
25
+ help_found = true
26
+ end
27
+ end
28
+ else
29
+ if (dest[0] == "C") || (dest[0] == "G") and @rules_imported.key?(user.id) &&
30
+ @rules_imported[user.id].key?(dchannel) && @bots_created.key?(@rules_imported[user.id][dchannel])
31
+ message = "*You are using rules from another channel: <##{@rules_imported[user.id][dchannel]}>. These are the specific commands for that channel:*"
32
+ elsif dest[0] == "D" && @rules_imported.key?(user.id) && @rules_imported[user.id].key?(user.id) and
33
+ @bots_created.key?(@rules_imported[user.id][user.id])
34
+ message = "*You are using rules from another channel: <##{@rules_imported[user.id][user.id]}>. These are the specific commands for that channel:*"
35
+ end
36
+ respond message, dest
37
+ end
38
+
39
+ if (help_command.to_s == "")
40
+ help_message.split(/^\s*=========*$/).each do |h|
41
+ respond("#{"=" * 35}\n#{h}", dest) unless h.match?(/\A\s*\z/)
42
+ end
43
+ else
44
+ unless help_found
45
+ if specific
46
+ respond("I didn't find any rule starting by `#{help_command}`", dest)
47
+ else
48
+ respond("I didn't find any command starting by `#{help_command}`", dest)
49
+ end
50
+ end
51
+ end
52
+
53
+ if specific
54
+ unless rules_file.empty?
55
+ begin
56
+ eval(File.new(rules_file).read) if File.exist?(rules_file)
57
+ end
58
+ end
59
+ if defined?(git_project) && (git_project.to_s != "") && (help_message_rules != "") && (help_command.to_s == "")
60
+ respond "Git project: #{git_project}", dest
61
+ else
62
+ def git_project
63
+ ""
64
+ end
65
+
66
+ def project_folder
67
+ ""
68
+ end
69
+ end
70
+ elsif help_command.to_s == ""
71
+ respond "Slack Smart Bot Github project: https://github.com/MarioRuiz/slack-smart-bot", dest
72
+ end
73
+ end
74
+ end