slack-smart-bot 1.3.1 → 1.4.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.
- checksums.yaml +4 -4
- data/README.md +3 -4
- data/lib/slack-smart-bot.rb +5 -11
- data/lib/slack/smart-bot/comm.rb +7 -253
- data/lib/slack/smart-bot/comm/ask.rb +48 -0
- data/lib/slack/smart-bot/comm/dont_understand.rb +61 -0
- data/lib/slack/smart-bot/comm/respond.rb +51 -0
- data/lib/slack/smart-bot/comm/respond_direct.rb +6 -0
- data/lib/slack/smart-bot/comm/send_file.rb +25 -0
- data/lib/slack/smart-bot/comm/send_msg_channel.rb +30 -0
- data/lib/slack/smart-bot/comm/send_msg_user.rb +37 -0
- data/lib/slack/smart-bot/commands.rb +25 -0
- data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +1 -0
- data/lib/slack/smart-bot/listen.rb +1 -2
- data/lib/slack/smart-bot/process.rb +0 -26
- data/lib/slack/smart-bot/process_first.rb +0 -1
- data/lib/slack/smart-bot/treat_message.rb +16 -1
- data/lib/slack/smart-bot/utils.rb +12 -337
- data/lib/slack/smart-bot/utils/build_help.rb +15 -0
- data/lib/slack/smart-bot/utils/create_routine_thread.rb +86 -0
- data/lib/slack/smart-bot/utils/get_bots_created.rb +24 -0
- data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +21 -0
- data/lib/slack/smart-bot/utils/get_help.rb +131 -0
- data/lib/slack/smart-bot/utils/get_routines.rb +11 -0
- data/lib/slack/smart-bot/utils/get_rules_imported.rb +15 -0
- data/lib/slack/smart-bot/utils/remove_hash_keys.rb +17 -0
- data/lib/slack/smart-bot/utils/update_bots_file.rb +11 -0
- data/lib/slack/smart-bot/utils/update_routines.rb +16 -0
- data/lib/slack/smart-bot/utils/update_rules_imported.rb +8 -0
- data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +7 -0
- 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,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
|
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.
|
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
|
+
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
|
-
|
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
|