slack-smart-bot 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -4
  3. data/lib/slack-smart-bot.rb +5 -11
  4. data/lib/slack/smart-bot/comm.rb +7 -253
  5. data/lib/slack/smart-bot/comm/ask.rb +48 -0
  6. data/lib/slack/smart-bot/comm/dont_understand.rb +61 -0
  7. data/lib/slack/smart-bot/comm/respond.rb +51 -0
  8. data/lib/slack/smart-bot/comm/respond_direct.rb +6 -0
  9. data/lib/slack/smart-bot/comm/send_file.rb +25 -0
  10. data/lib/slack/smart-bot/comm/send_msg_channel.rb +30 -0
  11. data/lib/slack/smart-bot/comm/send_msg_user.rb +37 -0
  12. data/lib/slack/smart-bot/commands.rb +25 -0
  13. data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +1 -0
  14. data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +1 -0
  15. data/lib/slack/smart-bot/listen.rb +1 -2
  16. data/lib/slack/smart-bot/process.rb +0 -26
  17. data/lib/slack/smart-bot/process_first.rb +0 -1
  18. data/lib/slack/smart-bot/treat_message.rb +16 -1
  19. data/lib/slack/smart-bot/utils.rb +12 -337
  20. data/lib/slack/smart-bot/utils/build_help.rb +15 -0
  21. data/lib/slack/smart-bot/utils/create_routine_thread.rb +86 -0
  22. data/lib/slack/smart-bot/utils/get_bots_created.rb +24 -0
  23. data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +21 -0
  24. data/lib/slack/smart-bot/utils/get_help.rb +131 -0
  25. data/lib/slack/smart-bot/utils/get_routines.rb +11 -0
  26. data/lib/slack/smart-bot/utils/get_rules_imported.rb +15 -0
  27. data/lib/slack/smart-bot/utils/remove_hash_keys.rb +17 -0
  28. data/lib/slack/smart-bot/utils/update_bots_file.rb +11 -0
  29. data/lib/slack/smart-bot/utils/update_routines.rb +16 -0
  30. data/lib/slack/smart-bot/utils/update_rules_imported.rb +8 -0
  31. data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +7 -0
  32. metadata +29 -10
@@ -0,0 +1,15 @@
1
+ class SlackSmartBot
2
+
3
+ def build_help(path)
4
+ help_message = {}
5
+ Dir["#{path}/*"].each do |t|
6
+ if Dir.exist?(t)
7
+ help_message[t.scan(/\/(\w+)$/).join.to_sym] = build_help(t)
8
+ else
9
+ help_message[t.scan(/\/(\w+)\.rb$/).join.to_sym] = IO.readlines(t).join.scan(/#\s*help\s*\w*:(.*)/).join("\n")
10
+ end
11
+ end
12
+ return help_message
13
+ end
14
+
15
+ end
@@ -0,0 +1,86 @@
1
+ class SlackSmartBot
2
+
3
+ def create_routine_thread(name)
4
+ t = Thread.new do
5
+ while @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
6
+ @routines[@channel_id][name][:thread] = Thread.current
7
+ started = Time.now
8
+ if @status == :on and @routines[@channel_id][name][:status] == :on
9
+ @logger.info "Routine: #{@routines[@channel_id][name].inspect}"
10
+ if @routines[@channel_id][name][:file_path].match?(/\.rb$/i)
11
+ ruby = "ruby "
12
+ else
13
+ ruby = ""
14
+ end
15
+ @routines[@channel_id][name][:silent] = false if !@routines[@channel_id][name].key?(:silent)
16
+
17
+ if @routines[@channel_id][name][:at] == "" or
18
+ (@routines[@channel_id][name][:at] != "" and @routines[@channel_id][name][:running] and
19
+ @routines[@channel_id][name][:next_run] != "" and Time.now.to_s >= @routines[@channel_id][name][:next_run])
20
+ if @routines[@channel_id][name][:file_path] != ""
21
+ process_to_run = "#{ruby}#{Dir.pwd}#{@routines[@channel_id][name][:file_path][1..-1]}"
22
+ process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)
23
+
24
+ stdout, stderr, status = Open3.capture3(process_to_run)
25
+ if !@routines[@channel_id][name][:silent] or (@routines[@channel_id][name][:silent] and
26
+ (!stderr.match?(/\A\s*\z/) or !stdout.match?(/\A\s*\z/)))
27
+ respond "routine *`#{name}`*: #{@routines[@channel_id][name][:file_path]}", @routines[@channel_id][name][:dest]
28
+ end
29
+ if stderr == ""
30
+ unless stdout.match?(/\A\s*\z/)
31
+ respond stdout, @routines[@channel_id][name][:dest]
32
+ end
33
+ else
34
+ respond "#{stdout} #{stderr}", @routines[@channel_id][name][:dest]
35
+ end
36
+ else #command
37
+ if !@routines[@channel_id][name][:silent]
38
+ respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
39
+ end
40
+ started = Time.now
41
+ data = { channel: @routines[@channel_id][name][:dest],
42
+ user: @routines[@channel_id][name][:creator_id],
43
+ text: @routines[@channel_id][name][:command],
44
+ files: nil }
45
+ treat_message(data)
46
+ end
47
+ # in case the routine was deleted while running the process
48
+ if !@routines.key?(@channel_id) or !@routines[@channel_id].key?(name)
49
+ Thread.exit
50
+ end
51
+ @routines[@channel_id][name][:last_run] = started.to_s
52
+ end
53
+ if @routines[@channel_id][name][:last_run] == "" and @routines[@channel_id][name][:next_run] != "" #for the first create_routine of one routine with at
54
+ elapsed = 0
55
+ require "time"
56
+ every_in_seconds = Time.parse(@routines[@channel_id][name][:next_run]) - Time.now
57
+ elsif @routines[@channel_id][name][:at] != "" #coming from start after pause for 'at'
58
+ if started.strftime("%H:%M:%S") < @routines[@channel_id][name][:at]
59
+ nt = @routines[@channel_id][name][:at].split(":")
60
+ next_run = Time.new(started.year, started.month, started.day, nt[0], nt[1], nt[2])
61
+ else
62
+ next_run = started + (24 * 60 * 60) # one more day
63
+ nt = @routines[@channel_id][name][:at].split(":")
64
+ next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
65
+ end
66
+ @routines[@channel_id][name][:next_run] = next_run.to_s
67
+ elapsed = 0
68
+ every_in_seconds = next_run - started
69
+ else
70
+ every_in_seconds = @routines[@channel_id][name][:every_in_seconds]
71
+ elapsed = Time.now - started
72
+ @routines[@channel_id][name][:last_elapsed] = elapsed
73
+ @routines[@channel_id][name][:next_run] = (started + every_in_seconds).to_s
74
+ end
75
+ @routines[@channel_id][name][:running] = true
76
+ @routines[@channel_id][name][:sleeping] = (every_in_seconds - elapsed).ceil
77
+ update_routines()
78
+ sleep(@routines[@channel_id][name][:sleeping]) unless elapsed > every_in_seconds
79
+ else
80
+ sleep 30
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,24 @@
1
+ class SlackSmartBot
2
+ def get_bots_created
3
+ if File.exist?(config.file_path.gsub(".rb", "_bots.rb"))
4
+ if !defined?(@datetime_bots_created) or @datetime_bots_created != File.mtime(config.file_path.gsub(".rb", "_bots.rb"))
5
+ file_conf = IO.readlines(config.file_path.gsub(".rb", "_bots.rb")).join
6
+ if file_conf.to_s() == ""
7
+ @bots_created = {}
8
+ else
9
+ @bots_created = eval(file_conf)
10
+ end
11
+ @datetime_bots_created = File.mtime(config.file_path.gsub(".rb", "_bots.rb"))
12
+ @extended_from = {}
13
+ @bots_created.each do |k, v|
14
+ v[:extended] = [] unless v.key?(:extended)
15
+ v[:extended].each do |ch|
16
+ @extended_from[ch] = [] unless @extended_from.key?(ch)
17
+ @extended_from[ch] << k
18
+ end
19
+ v[:rules_file].gsub!(/^\./, '')
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ class SlackSmartBot
2
+
3
+ def get_channels_name_and_id
4
+ #todo: add pagination for case more than 1000 channels on the workspace
5
+ channels = client.web_client.conversations_list(
6
+ types: "private_channel,public_channel",
7
+ limit: "1000",
8
+ exclude_archived: "true",
9
+ ).channels
10
+
11
+ @channels_id = Hash.new()
12
+ @channels_name = Hash.new()
13
+ channels.each do |ch|
14
+ unless ch.is_archived
15
+ @channels_id[ch.name] = ch.id
16
+ @channels_name[ch.id] = ch.name
17
+ end
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,131 @@
1
+ class SlackSmartBot
2
+ def get_help(rules_file, dest, from, only_rules = false)
3
+ order = {
4
+ general: [:hi_bot, :bye_bot, :bot_help, :bot_status, :use_rules, :stop_using_rules],
5
+ on_bot: [:ruby_code, :add_shortcut, :delete_shortcut, :see_shortcuts],
6
+ on_bot_admin: [:extend_rules, :stop_using_rules_on, :start_bot, :pause_bot, :add_routine,
7
+ :see_routines, :start_routine, :pause_routine, :remove_routine, :run_routine],
8
+ }
9
+ # user_type: :admin, :user, :admin_master
10
+ if config.masters.include?(from)
11
+ user_type = :admin_master
12
+ elsif config.admins.include?(from)
13
+ user_type = :admin
14
+ else
15
+ user_type = :user
16
+ end
17
+ # channel_type: :bot, :master_bot, :direct, :extended, :external
18
+ if dest[0] == "D"
19
+ channel_type = :direct
20
+ elsif config.on_master_bot
21
+ channel_type = :master_bot
22
+ elsif @channel_id != dest
23
+ channel_type = :extended
24
+ else
25
+ channel_type = :bot
26
+ end
27
+
28
+ @help_messages ||= build_help("#{__dir__}/../commands")
29
+ if only_rules
30
+ help = {}
31
+ else
32
+ help = @help_messages.deep_copy
33
+ end
34
+ if rules_file != ""
35
+ help[:rules_file] = IO.readlines(config.path+rules_file).join.scan(/#\s*help\s*\w*:(.*)/i).join("\n")
36
+ end
37
+
38
+ help = remove_hash_keys(help, :admin_master) unless user_type == :admin_master
39
+ help = remove_hash_keys(help, :admin) unless user_type == :admin or user_type == :admin_master
40
+ help = remove_hash_keys(help, :on_master) unless channel_type == :master_bot
41
+ help = remove_hash_keys(help, :on_extended) unless channel_type == :extended
42
+ help = remove_hash_keys(help, :on_dm) unless channel_type == :direct
43
+ txt = ""
44
+ if channel_type == :bot or channel_type == :master_bot
45
+ txt += "===================================
46
+ For the Smart Bot start listening to you say *hi bot*
47
+ To run a command on demand even when the Smart Bot is not listening to you:
48
+ *!THE_COMMAND*
49
+ *@NAME_OF_BOT THE_COMMAND*
50
+ *NAME_OF_BOT THE_COMMAND*\n"
51
+ end
52
+ if channel_type == :direct
53
+ txt += "===================================
54
+ When on a private conversation with the Smart Bot, I'm always listening to you.\n"
55
+ end
56
+ unless channel_type == :master_bot or channel_type == :extended
57
+ txt += "===================================
58
+ *Commands from Channels without a bot:*
59
+ ----------------------------------------------
60
+ `@BOT_NAME on #CHANNEL_NAME COMMAND`
61
+ `@BOT_NAME #CHANNEL_NAME COMMAND`
62
+ It will run the supplied command using the rules on the channel supplied.
63
+ You need to join the specified channel to be able to use those rules.
64
+ Also you can use this command to call another bot from a channel with a running bot.
65
+
66
+ The commands you will be able to use from a channel without a bot:
67
+ *bot rules*, *ruby CODE*, *add shortcut NAME: COMMAND*, *delete shortcut NAME*, *see shortcuts*, *shortcut NAME*
68
+ *And all the specific rules of the Channel*\n"
69
+ end
70
+
71
+ if help.key?(:general)
72
+ unless channel_type == :direct
73
+ txt += "===================================
74
+ *General commands even when the Smart Bot is not listening to you:*\n"
75
+ end
76
+ order.general.each do |o|
77
+ txt += help.general[o]
78
+ end
79
+ if channel_type == :master_bot
80
+ txt += help.on_master.create_bot
81
+ end
82
+ end
83
+
84
+ if help.key?(:on_bot)
85
+ unless channel_type == :direct
86
+ txt += "===================================
87
+ *General commands only when the Smart Bot is listening to you or on demand:*\n"
88
+ end
89
+ order.on_bot.each do |o|
90
+ txt += help.on_bot[o]
91
+ end
92
+ end
93
+ if help.key?(:on_bot) and help.on_bot.key?(:admin)
94
+ txt += "===================================
95
+ *Admin commands:*\n"
96
+ txt += "\n\n"
97
+ order.on_bot_admin.each do |o|
98
+ txt += help.on_bot.admin[o]
99
+ end
100
+ if help.key?(:on_master) and help.on_master.key?(:admin)
101
+ help.on_master.admin.each do |k, v|
102
+ txt += v if v.is_a?(String)
103
+ end
104
+ end
105
+ end
106
+
107
+ if help.key?(:on_master) and help.on_master.key?(:admin_master)
108
+ txt += "===================================
109
+ *Master Admin commands:*\n"
110
+ help.on_master.admin_master.each do |k, v|
111
+ txt += v if v.is_a?(String)
112
+ end
113
+ end
114
+
115
+ if help.key?(:on_bot) and help.on_bot.key?(:admin_master) and help.on_bot.admin_master.size > 0
116
+ txt += "===================================
117
+ *Master Admin commands:*\n"
118
+ end
119
+
120
+ if help.key?(:rules_file)
121
+ @logger.info channel_type if config.testing
122
+ if channel_type == :extended or channel_type == :direct
123
+ @logger.info help.rules_file if config.testing
124
+ help.rules_file = help.rules_file.gsub(/^\s*\*These are specific commands.+NAME_OF_BOT THE_COMMAND`\s*$/im, "")
125
+ end
126
+ txt += help.rules_file
127
+ end
128
+
129
+ return txt
130
+ end
131
+ end
@@ -0,0 +1,11 @@
1
+ class SlackSmartBot
2
+
3
+ def get_routines(channel = @channel_id)
4
+ if File.exist?("#{config.path}/routines/routines_#{channel}.rb")
5
+ file_conf = IO.readlines("#{config.path}/routines/routines_#{channel}.rb").join
6
+ unless file_conf.to_s() == ""
7
+ @routines = eval(file_conf)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ class SlackSmartBot
2
+
3
+ def get_rules_imported
4
+ if File.exist?("#{config.path}/rules/rules_imported.rb")
5
+ if !defined?(@datetime_rules_imported) or @datetime_rules_imported != File.mtime("#{config.path}/rules/rules_imported.rb")
6
+ @datetime_rules_imported = File.mtime("#{config.path}/rules/rules_imported.rb")
7
+ file_conf = IO.readlines("#{config.path}/rules/rules_imported.rb").join
8
+ unless file_conf.to_s() == ""
9
+ @rules_imported = eval(file_conf)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,17 @@
1
+ class SlackSmartBot
2
+
3
+ def remove_hash_keys(hash, key)
4
+ newh = Hash.new
5
+ hash.each do |k, v|
6
+ unless k == key
7
+ if v.is_a?(String)
8
+ newh[k] = v
9
+ else
10
+ newh[k] = remove_hash_keys(v, key)
11
+ end
12
+ end
13
+ end
14
+ return newh
15
+ end
16
+
17
+ end
@@ -0,0 +1,11 @@
1
+ class SlackSmartBot
2
+ def update_bots_file
3
+ file = File.open(config.file_path.gsub(".rb", "_bots.rb"), "w")
4
+ bots_created = @bots_created.dup
5
+ bots_created.each { |k, v|
6
+ v[:thread] = ""
7
+ }
8
+ file.write bots_created.inspect
9
+ file.close
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ class SlackSmartBot
2
+
3
+ def update_routines(channel = @channel_id)
4
+ routines = {}
5
+ file = File.open("#{config.path}/routines/routines_#{channel}.rb", "w")
6
+ @routines.each do |k,v|
7
+ routines[k]={}
8
+ v.each do |kk,vv|
9
+ routines[k][kk] = vv.dup
10
+ routines[k][kk][:thread]=""
11
+ end
12
+ end
13
+ file.write (routines.inspect)
14
+ file.close
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ class SlackSmartBot
2
+
3
+ def update_rules_imported
4
+ file = File.open("#{config.path}/rules/rules_imported.rb", "w")
5
+ file.write @rules_imported.inspect
6
+ file.close
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class SlackSmartBot
2
+ def update_shortcuts_file
3
+ file = File.open("#{config.path}/shortcuts/#{config.shortcuts_file}", "w")
4
+ file.write @shortcuts.inspect
5
+ file.close
6
+ end
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slack-smart-bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Ruiz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-25 00:00:00.000000000 Z
11
+ date: 2019-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: slack-ruby-client
@@ -82,22 +82,22 @@ dependencies:
82
82
  name: rspec
83
83
  requirement: !ruby/object:Gem::Requirement
84
84
  requirements:
85
- - - "~>"
86
- - !ruby/object:Gem::Version
87
- version: '3.8'
88
85
  - - ">="
89
86
  - !ruby/object:Gem::Version
90
87
  version: 3.8.0
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '3.8'
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
- - - "~>"
96
- - !ruby/object:Gem::Version
97
- version: '3.8'
98
95
  - - ">="
99
96
  - !ruby/object:Gem::Version
100
97
  version: 3.8.0
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '3.8'
101
101
  description: "Create a Slack bot that is smart and so easy to expand, create new bots
102
102
  on demand, run ruby code on chat, create shortcuts... \n The main scope of this
103
103
  gem is to be used internally in the company so teams can create team channels with
@@ -119,6 +119,14 @@ files:
119
119
  - lib/slack-smart-bot.rb
120
120
  - lib/slack-smart-bot_rules.rb
121
121
  - lib/slack/smart-bot/comm.rb
122
+ - lib/slack/smart-bot/comm/ask.rb
123
+ - lib/slack/smart-bot/comm/dont_understand.rb
124
+ - lib/slack/smart-bot/comm/respond.rb
125
+ - lib/slack/smart-bot/comm/respond_direct.rb
126
+ - lib/slack/smart-bot/comm/send_file.rb
127
+ - lib/slack/smart-bot/comm/send_msg_channel.rb
128
+ - lib/slack/smart-bot/comm/send_msg_user.rb
129
+ - lib/slack/smart-bot/commands.rb
122
130
  - lib/slack/smart-bot/commands/general/bot_help.rb
123
131
  - lib/slack/smart-bot/commands/general/bot_status.rb
124
132
  - lib/slack/smart-bot/commands/general/bye_bot.rb
@@ -149,6 +157,18 @@ files:
149
157
  - lib/slack/smart-bot/process_first.rb
150
158
  - lib/slack/smart-bot/treat_message.rb
151
159
  - lib/slack/smart-bot/utils.rb
160
+ - lib/slack/smart-bot/utils/build_help.rb
161
+ - lib/slack/smart-bot/utils/create_routine_thread.rb
162
+ - lib/slack/smart-bot/utils/get_bots_created.rb
163
+ - lib/slack/smart-bot/utils/get_channels_name_and_id.rb
164
+ - lib/slack/smart-bot/utils/get_help.rb
165
+ - lib/slack/smart-bot/utils/get_routines.rb
166
+ - lib/slack/smart-bot/utils/get_rules_imported.rb
167
+ - lib/slack/smart-bot/utils/remove_hash_keys.rb
168
+ - lib/slack/smart-bot/utils/update_bots_file.rb
169
+ - lib/slack/smart-bot/utils/update_routines.rb
170
+ - lib/slack/smart-bot/utils/update_rules_imported.rb
171
+ - lib/slack/smart-bot/utils/update_shortcuts_file.rb
152
172
  homepage: https://github.com/MarioRuiz/slack-smart-bot
153
173
  licenses:
154
174
  - MIT
@@ -168,8 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
188
  - !ruby/object:Gem::Version
169
189
  version: '0'
170
190
  requirements: []
171
- rubyforge_project:
172
- rubygems_version: 2.7.6
191
+ rubygems_version: 3.0.3
173
192
  signing_key:
174
193
  specification_version: 4
175
194
  summary: Create a Slack bot that is smart and so easy to expand, create new bots on