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.
- 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
|