slack-smart-bot 1.7.0 → 1.9.2
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 +24 -12
- data/lib/slack-smart-bot.rb +53 -43
- data/lib/slack-smart-bot_general_rules.rb +7 -4
- data/lib/slack-smart-bot_rules.rb +8 -6
- data/lib/slack/smart-bot/comm.rb +6 -1
- data/lib/slack/smart-bot/comm/ask.rb +12 -5
- data/lib/slack/smart-bot/comm/dont_understand.rb +1 -1
- data/lib/slack/smart-bot/comm/event_hello.rb +30 -0
- data/lib/slack/smart-bot/comm/get_channel_members.rb +8 -0
- data/lib/slack/smart-bot/comm/get_channels.rb +20 -0
- data/lib/slack/smart-bot/comm/get_user_info.rb +16 -0
- data/lib/slack/smart-bot/comm/react.rb +21 -8
- data/lib/slack/smart-bot/comm/respond.rb +38 -12
- data/lib/slack/smart-bot/comm/send_file.rb +1 -1
- data/lib/slack/smart-bot/comm/send_msg_channel.rb +2 -2
- data/lib/slack/smart-bot/comm/send_msg_user.rb +4 -4
- data/lib/slack/smart-bot/comm/unreact.rb +29 -0
- data/lib/slack/smart-bot/commands.rb +3 -1
- data/lib/slack/smart-bot/commands/general/bot_help.rb +16 -3
- data/lib/slack/smart-bot/commands/general/bot_stats.rb +313 -0
- data/lib/slack/smart-bot/commands/general/bot_status.rb +1 -1
- data/lib/slack/smart-bot/commands/general/bye_bot.rb +1 -1
- data/lib/slack/smart-bot/commands/general/hi_bot.rb +1 -1
- data/lib/slack/smart-bot/commands/general/use_rules.rb +6 -9
- data/lib/slack/smart-bot/commands/general/whats_new.rb +19 -0
- data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +65 -33
- data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +42 -9
- data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +3 -7
- data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +9 -2
- data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +1 -1
- data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +52 -21
- data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +5 -5
- data/lib/slack/smart-bot/commands/on_bot/repl.rb +50 -18
- data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +34 -9
- data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +2 -3
- data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +1 -1
- data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +27 -9
- data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +14 -1
- data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +3 -3
- data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +1 -1
- data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +41 -0
- data/lib/slack/smart-bot/commands/on_master/create_bot.rb +4 -8
- data/lib/slack/smart-bot/listen.rb +6 -5
- data/lib/slack/smart-bot/process.rb +230 -186
- data/lib/slack/smart-bot/process_first.rb +104 -87
- data/lib/slack/smart-bot/treat_message.rb +128 -52
- data/lib/slack/smart-bot/utils.rb +2 -0
- data/lib/slack/smart-bot/utils/answer.rb +18 -0
- data/lib/slack/smart-bot/utils/answer_delete.rb +15 -0
- data/lib/slack/smart-bot/utils/build_help.rb +57 -5
- data/lib/slack/smart-bot/utils/create_routine_thread.rb +48 -8
- data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +1 -7
- data/lib/slack/smart-bot/utils/get_help.rb +79 -17
- data/lib/slack/smart-bot/utils/save_stats.rb +21 -8
- data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +6 -0
- data/whats_new.txt +24 -0
- metadata +23 -13
- data/lib/slack/smart-bot/commands/on_bot/admin_master/bot_stats.rb +0 -135
@@ -10,7 +10,7 @@ class SlackSmartBot
|
|
10
10
|
if typem==:on_extended
|
11
11
|
get_bots_created()
|
12
12
|
end
|
13
|
-
text = get_help(rules_file, dest, user.name, typem==:on_extended)
|
13
|
+
text = get_help(rules_file, dest, user.name, typem==:on_extended, true)
|
14
14
|
|
15
15
|
ff = text.scan(/\s*`\s*([^`]+)\s*`\s*/i).flatten
|
16
16
|
ff.delete("!THE_COMMAND")
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
def event_hello()
|
3
|
+
unless config.simulate
|
4
|
+
m = "Successfully connected, welcome '#{client.self.name}' to the '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
|
5
|
+
puts m
|
6
|
+
@logger.info m
|
7
|
+
config.nick = client.self.name
|
8
|
+
config.nick_id = client.self.id
|
9
|
+
end
|
10
|
+
@salutations = [config[:nick], "<@#{config[:nick_id]}>", "bot", "smart"]
|
11
|
+
|
12
|
+
gems_remote = `gem list slack-smart-bot --remote`
|
13
|
+
version_remote = gems_remote.to_s().scan(/slack-smart-bot \((\d+\.\d+\.\d+)/).join
|
14
|
+
version_message = ""
|
15
|
+
if Gem::Version.new(version_remote) > Gem::Version.new(VERSION)
|
16
|
+
version_message = ". There is a new available version: #{version_remote}."
|
17
|
+
end
|
18
|
+
if (!config[:silent] or ENV['BOT_SILENT'].to_s == 'false') and !config.simulate
|
19
|
+
ENV['BOT_SILENT'] = 'true' if config[:silent] and ENV['BOT_SILENT'].to_s != 'true'
|
20
|
+
respond "Smart Bot started v#{VERSION}#{version_message}\nIf you want to know what I can do for you: `bot help`.\n`bot rules` if you want to display just the specific rules of this channel.\nYou can talk to me privately if you prefer it."
|
21
|
+
end
|
22
|
+
@routines.each do |ch, rout|
|
23
|
+
rout.each do |k, v|
|
24
|
+
if !v[:running] and v[:channel_name] == config.channel
|
25
|
+
create_routine_thread(k)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
def get_channels(bot_is_in: false)
|
2
|
+
if config.simulate and config.key?(:client)
|
3
|
+
if bot_is_in
|
4
|
+
client.web_client.conversations_members.reject {|r,v| !v.members.include?(config.nick_id)}.values
|
5
|
+
else
|
6
|
+
client.web_client.conversations_members.values
|
7
|
+
end
|
8
|
+
else
|
9
|
+
if bot_is_in
|
10
|
+
client.web_client.users_conversations(exclude_archived: true, limit: 100, types: "im, public_channel,private_channel").channels
|
11
|
+
else
|
12
|
+
#todo: add pagination for case more than 1000 channels on the workspace
|
13
|
+
client.web_client.conversations_list(
|
14
|
+
types: "private_channel,public_channel",
|
15
|
+
limit: "1000",
|
16
|
+
exclude_archived: "true",
|
17
|
+
).channels
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
|
3
|
+
def get_user_info(user)
|
4
|
+
if user.to_s.length>0
|
5
|
+
if config.simulate and config.key?(:client)
|
6
|
+
if user[0]=='@' #name
|
7
|
+
client.web_client.users_info.select{|k, v| v[:user][:name] == user[1..-1]}.values[-1]
|
8
|
+
else #id
|
9
|
+
client.web_client.users_info[user.to_sym]
|
10
|
+
end
|
11
|
+
else
|
12
|
+
client.web_client.users_info(user: user)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,16 +1,29 @@
|
|
1
1
|
class SlackSmartBot
|
2
2
|
# list of available emojis: https://www.webfx.com/tools/emoji-cheat-sheet/
|
3
3
|
# react(:thumbsup)
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
# ts: can be true, false or a specific ts
|
5
|
+
def react(emoji, ts=false)
|
6
|
+
if ts.is_a?(TrueClass) or ts.is_a?(FalseClass)
|
7
|
+
parent = ts
|
8
|
+
ts = nil
|
7
9
|
else
|
8
|
-
|
10
|
+
parent = false
|
9
11
|
end
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
if ts.nil?
|
13
|
+
if parent or Thread.current[:ts].to_s == ''
|
14
|
+
ts = Thread.current[:thread_ts]
|
15
|
+
else
|
16
|
+
ts = Thread.current[:ts]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
if ts.nil?
|
20
|
+
@logger.warn 'react method no ts supplied'
|
21
|
+
else
|
22
|
+
begin
|
23
|
+
client.web_client.reactions_add(channel: Thread.current[:dest], name: emoji, timestamp: ts) unless config.simulate
|
24
|
+
rescue Exception => stack
|
25
|
+
@logger.warn stack
|
26
|
+
end
|
14
27
|
end
|
15
28
|
end
|
16
29
|
end
|
@@ -4,46 +4,71 @@ class SlackSmartBot
|
|
4
4
|
dest = Thread.current[:dest]
|
5
5
|
end
|
6
6
|
dest = @channels_id[dest] if @channels_id.key?(dest) #it is a name of channel
|
7
|
+
if !config.simulate #https://api.slack.com/docs/rate-limits
|
8
|
+
msg.to_s.size > 500 ? wait = 0.5 : wait = 0.1
|
9
|
+
sleep wait if Time.now <= (@last_respond+wait)
|
10
|
+
else
|
11
|
+
wait = 0
|
12
|
+
end
|
13
|
+
msgs = msg.chars.each_slice(4000).map(&:join) # max of 4000 characters per message
|
7
14
|
if dest.nil?
|
8
15
|
if config[:simulate]
|
9
16
|
open("#{config.path}/buffer_complete.log", "a") { |f|
|
10
|
-
f.puts "|#{@channel_id}|#{config[:nick_id]}|#{msg}~~~"
|
17
|
+
f.puts "|#{@channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
|
11
18
|
}
|
12
19
|
else
|
13
20
|
if Thread.current[:on_thread]
|
14
|
-
|
21
|
+
msgs.each do |msg|
|
22
|
+
client.message(channel: @channel_id, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
|
23
|
+
sleep wait
|
24
|
+
end
|
15
25
|
else
|
16
|
-
|
26
|
+
msgs.each do |msg|
|
27
|
+
client.message(channel: @channel_id, text: msg, as_user: true)
|
28
|
+
sleep wait
|
29
|
+
end
|
17
30
|
end
|
18
31
|
end
|
19
32
|
if config[:testing] and config.on_master_bot
|
20
33
|
open("#{config.path}/buffer.log", "a") { |f|
|
21
|
-
f.puts "|#{@channel_id}|#{config[:nick_id]}|#{msg}"
|
34
|
+
f.puts "|#{@channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
|
22
35
|
}
|
23
36
|
end
|
24
37
|
elsif dest[0] == "C" or dest[0] == "G" # channel
|
25
38
|
if config[:simulate]
|
26
39
|
open("#{config.path}/buffer_complete.log", "a") { |f|
|
27
|
-
f.puts "|#{dest}|#{config[:nick_id]}|#{msg}~~~"
|
40
|
+
f.puts "|#{dest}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
|
28
41
|
}
|
29
42
|
else
|
30
43
|
if Thread.current[:on_thread]
|
31
|
-
|
44
|
+
msgs.each do |msg|
|
45
|
+
client.message(channel: dest, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
|
46
|
+
sleep wait
|
47
|
+
end
|
32
48
|
else
|
33
|
-
|
49
|
+
msgs.each do |msg|
|
50
|
+
client.message(channel: dest, text: msg, as_user: true)
|
51
|
+
sleep wait
|
52
|
+
end
|
34
53
|
end
|
35
54
|
end
|
36
55
|
if config[:testing] and config.on_master_bot
|
37
56
|
open("#{config.path}/buffer.log", "a") { |f|
|
38
|
-
f.puts "|#{dest}|#{config[:nick_id]}|#{msg}"
|
57
|
+
f.puts "|#{dest}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
|
39
58
|
}
|
40
59
|
end
|
41
|
-
elsif dest[0] == "D" or dest[0] == "U" # Direct message
|
42
|
-
|
60
|
+
elsif dest[0] == "D" or dest[0] == "U" or dest[0] == "W" # Direct message
|
61
|
+
msgs.each do |msg|
|
62
|
+
send_msg_user(dest, msg)
|
63
|
+
sleep wait
|
64
|
+
end
|
43
65
|
elsif dest[0] == "@"
|
44
66
|
begin
|
45
|
-
user_info =
|
46
|
-
|
67
|
+
user_info = get_user_info(dest)
|
68
|
+
msgs.each do |msg|
|
69
|
+
send_msg_user(user_info.user.id, msg)
|
70
|
+
sleep wait
|
71
|
+
end
|
47
72
|
rescue Exception => stack
|
48
73
|
@logger.warn("user #{dest} not found.")
|
49
74
|
@logger.warn stack
|
@@ -54,6 +79,7 @@ class SlackSmartBot
|
|
54
79
|
else
|
55
80
|
@logger.warn("method respond not treated correctly: msg:#{msg} dest:#{dest}")
|
56
81
|
end
|
82
|
+
@last_respond = Time.now
|
57
83
|
end
|
58
84
|
|
59
85
|
end
|
@@ -8,7 +8,7 @@ class SlackSmartBot
|
|
8
8
|
def send_file(to, msg, file, title, format, type = "text", content: '')
|
9
9
|
unless config[:simulate]
|
10
10
|
file = 'myfile' if file.to_s == '' and content!=''
|
11
|
-
if to[0] == "U" #user
|
11
|
+
if to[0] == "U" or to[0] == "W" #user
|
12
12
|
im = client.web_client.im_open(user: to)
|
13
13
|
channel = im["channel"]["id"]
|
14
14
|
else
|
@@ -14,7 +14,7 @@ class SlackSmartBot
|
|
14
14
|
end
|
15
15
|
if config[:simulate]
|
16
16
|
open("#{config.path}/buffer_complete.log", "a") { |f|
|
17
|
-
f.puts "|#{channel_id}|#{config[:nick_id]}|#{msg}~~~"
|
17
|
+
f.puts "|#{channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
|
18
18
|
}
|
19
19
|
else
|
20
20
|
if Thread.current[:on_thread]
|
@@ -25,7 +25,7 @@ class SlackSmartBot
|
|
25
25
|
end
|
26
26
|
if config[:testing] and config.on_master_bot
|
27
27
|
open("#{config.path}/buffer.log", "a") { |f|
|
28
|
-
f.puts "|#{channel_id}|#{config[:nick_id]}|#{msg}"
|
28
|
+
f.puts "|#{channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
|
29
29
|
}
|
30
30
|
end
|
31
31
|
end
|
@@ -6,7 +6,7 @@ class SlackSmartBot
|
|
6
6
|
if id_user[0] == "D"
|
7
7
|
if config[:simulate]
|
8
8
|
open("#{config.path}/buffer_complete.log", "a") { |f|
|
9
|
-
f.puts "|#{id_user}|#{config[:nick_id]}|#{msg}~~~"
|
9
|
+
f.puts "|#{id_user}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
|
10
10
|
}
|
11
11
|
else
|
12
12
|
if Thread.current[:on_thread]
|
@@ -17,14 +17,14 @@ class SlackSmartBot
|
|
17
17
|
end
|
18
18
|
if config[:testing] and config.on_master_bot
|
19
19
|
open("#{config.path}/buffer.log", "a") { |f|
|
20
|
-
f.puts "|#{id_user}|#{config[:nick_id]}|#{msg}"
|
20
|
+
f.puts "|#{id_user}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
|
21
21
|
}
|
22
22
|
end
|
23
23
|
else
|
24
24
|
im = client.web_client.im_open(user: id_user)
|
25
25
|
if config[:simulate]
|
26
26
|
open("#{config.path}/buffer_complete.log", "a") { |f|
|
27
|
-
f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{msg}~~~"
|
27
|
+
f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
|
28
28
|
}
|
29
29
|
else
|
30
30
|
if Thread.current[:on_thread]
|
@@ -35,7 +35,7 @@ class SlackSmartBot
|
|
35
35
|
end
|
36
36
|
if config[:testing] and config.on_master_bot
|
37
37
|
open("#{config.path}/buffer.log", "a") { |f|
|
38
|
-
f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{msg}"
|
38
|
+
f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
|
39
39
|
}
|
40
40
|
end
|
41
41
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
# list of available emojis: https://www.webfx.com/tools/emoji-cheat-sheet/
|
3
|
+
# unreact(:thumbsup)
|
4
|
+
# ts: can be true, false or a specific ts
|
5
|
+
def unreact(emoji, ts=false)
|
6
|
+
if ts.is_a?(TrueClass) or ts.is_a?(FalseClass)
|
7
|
+
parent = ts
|
8
|
+
ts = nil
|
9
|
+
else
|
10
|
+
parent = false
|
11
|
+
end
|
12
|
+
if ts.nil?
|
13
|
+
if parent or Thread.current[:ts].to_s == ''
|
14
|
+
ts = Thread.current[:thread_ts]
|
15
|
+
else
|
16
|
+
ts = Thread.current[:ts]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
if ts.nil?
|
20
|
+
@logger.warn 'unreact method no ts supplied'
|
21
|
+
else
|
22
|
+
begin
|
23
|
+
client.web_client.reactions_remove(channel: Thread.current[:dest], name: emoji, timestamp: ts) unless config.simulate
|
24
|
+
rescue Exception => stack
|
25
|
+
@logger.warn stack
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative "commands/general/whats_new"
|
1
2
|
require_relative "commands/general/hi_bot"
|
2
3
|
require_relative "commands/general/bye_bot"
|
3
4
|
require_relative "commands/general/bot_help"
|
@@ -29,4 +30,5 @@ require_relative "commands/on_bot/delete_shortcut"
|
|
29
30
|
require_relative "commands/on_bot/see_shortcuts"
|
30
31
|
require_relative "commands/on_extended/bot_rules"
|
31
32
|
require_relative "commands/on_bot/admin_master/get_bot_logs"
|
32
|
-
require_relative "commands/
|
33
|
+
require_relative "commands/general/bot_stats"
|
34
|
+
require_relative "commands/on_master/admin_master/set_maintenance"
|
@@ -5,9 +5,12 @@ class SlackSmartBot
|
|
5
5
|
# help: `bot help COMMAND`
|
6
6
|
# help: `bot rules`
|
7
7
|
# help: `bot rules COMMAND`
|
8
|
+
# help: `bot help expanded`
|
9
|
+
# help: `bot rules expanded`
|
8
10
|
# help: `bot what can I do?`
|
9
|
-
# help: it will display this help
|
11
|
+
# help: it will display this help. For a more detailed help call `bot help expanded` or `bot rules expanded`.
|
10
12
|
# help: if COMMAND supplied just help for that command
|
13
|
+
# help: you can use the option 'expanded' or the alias 'extended'
|
11
14
|
# help: `bot rules` will show only the specific rules for this channel.
|
12
15
|
# help:
|
13
16
|
def bot_help(user, from, dest, dchannel, specific, help_command, rules_file)
|
@@ -19,8 +22,17 @@ class SlackSmartBot
|
|
19
22
|
help_found = false
|
20
23
|
|
21
24
|
message = ""
|
25
|
+
if help_command.to_s != ''
|
26
|
+
help_command = '' if help_command.to_s.match?(/^\s*expanded\s*$/i) or help_command.to_s.match?(/^\s*extended\s*$/i)
|
27
|
+
expanded = true
|
28
|
+
message_not_expanded = ''
|
29
|
+
else
|
30
|
+
expanded = false
|
31
|
+
message_not_expanded = "*If you want to see the expanded version of `bot help` or `bot rules`, please call `bot help expanded` or `bot rules expanded`*\n"
|
32
|
+
message_not_expanded += "*Also to get specific expanded help for a specific command or rule call `bot help COMMAND`*\n"
|
33
|
+
end
|
22
34
|
|
23
|
-
help_message = get_help(rules_file, dest, from, specific)
|
35
|
+
help_message = get_help(rules_file, dest, from, specific, expanded)
|
24
36
|
|
25
37
|
if help_command.to_s != ""
|
26
38
|
help_message.gsub(/====+/,'-'*30).split(/^\s*-------*$/).each do |h|
|
@@ -31,7 +43,7 @@ class SlackSmartBot
|
|
31
43
|
end
|
32
44
|
else
|
33
45
|
if Thread.current[:using_channel]!=''
|
34
|
-
message
|
46
|
+
message += "*You are using rules from another channel: <##{Thread.current[:using_channel]}>. These are the specific commands for that channel:*"
|
35
47
|
end
|
36
48
|
respond message, dest
|
37
49
|
end
|
@@ -70,6 +82,7 @@ class SlackSmartBot
|
|
70
82
|
elsif help_command.to_s == ""
|
71
83
|
respond "Slack Smart Bot Github project: https://github.com/MarioRuiz/slack-smart-bot", dest
|
72
84
|
end
|
85
|
+
respond message_not_expanded unless expanded
|
73
86
|
end
|
74
87
|
end
|
75
88
|
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
# help: ----------------------------------------------
|
3
|
+
# help: `bot stats`
|
4
|
+
# helpmaster: `bot stats USER_NAME`
|
5
|
+
# help: `bot stats exclude masters`
|
6
|
+
# help: `bot stats exclude routines`
|
7
|
+
# help: `bot stats from YYYY/MM/DD`
|
8
|
+
# help: `bot stats from YYYY/MM/DD to YYYY/MM/DD`
|
9
|
+
# help: `bot stats CHANNEL`
|
10
|
+
# help: `bot stats CHANNEL from YYYY/MM/DD`
|
11
|
+
# help: `bot stats CHANNEL from YYYY/MM/DD to YYYY/MM/DD`
|
12
|
+
# help: `bot stats command COMMAND`
|
13
|
+
# helpmaster: `bot stats USER_NAME from YYYY/MM/DD to YYYY/MM/DD`
|
14
|
+
# helpmaster: `bot stats CHANNEL USER_NAME from YYYY/MM/DD to YYYY/MM/DD`
|
15
|
+
# help: `bot stats CHANNEL exclude masters from YYYY/MM/DD to YYYY/MM/DD`
|
16
|
+
# help: `bot stats today`
|
17
|
+
# help: `bot stats exclude COMMAND_ID`
|
18
|
+
# help: `bot stats monthly`
|
19
|
+
# help: `bot stats alldata`
|
20
|
+
# help: To see the bot stats
|
21
|
+
# helpmaster: You can use this command only if you are a Master admin user and if you are in a private conversation with the bot
|
22
|
+
# helpmaster: You need to set stats to true to generate the stats when running the bot instance.
|
23
|
+
# help: If alldata option supplied then it will be attached files including all data and not only the top 10.
|
24
|
+
# help: Examples:
|
25
|
+
# help: _bot stats #sales_
|
26
|
+
# helpmaster: _bot stats @peter.wind_
|
27
|
+
# help: _bot stats #sales from 2019/12/15 to 2019/12/31_
|
28
|
+
# help: _bot stats #sales today_
|
29
|
+
# help: _bot stats #sales from 2020-01-01 monthly_
|
30
|
+
# help: _bot stats exclude routines masters from 2021/01/01 monthly_
|
31
|
+
# help:
|
32
|
+
def bot_stats(dest, from_user, typem, channel_id, from, to, user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data)
|
33
|
+
require 'csv'
|
34
|
+
if config.stats
|
35
|
+
message = []
|
36
|
+
else
|
37
|
+
message = ["You need to set stats to true to generate the stats when running the bot instance."]
|
38
|
+
end
|
39
|
+
save_stats(__method__)
|
40
|
+
if (from_user.id != user and (config.masters.include?(from_user.name) or @master_admin_users_id.include?(from_user.id)) and (typem==:on_dm or dest[0]=='D'))
|
41
|
+
on_dm_master = true #master admin user
|
42
|
+
else
|
43
|
+
on_dm_master = false
|
44
|
+
end
|
45
|
+
if on_dm_master or (from_user.id == user) # normal user can only see own stats
|
46
|
+
if !File.exist?("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log")
|
47
|
+
message<<'No stats'
|
48
|
+
else
|
49
|
+
from = "#{Time.now.strftime("%Y-%m")}-01" if from == ''
|
50
|
+
to = "#{Time.now.strftime("%Y-%m-%d")}" if to == ''
|
51
|
+
from_short = from
|
52
|
+
to_short = to
|
53
|
+
from_file = from[0..3] + '-' + from[5..6]
|
54
|
+
to_file = to[0..3] + '-' + to[5..6]
|
55
|
+
from+= " 00:00:00 +0000"
|
56
|
+
to+= " 23:59:59 +0000"
|
57
|
+
rows = []
|
58
|
+
rows_month = {}
|
59
|
+
users_month = {}
|
60
|
+
commands_month = {}
|
61
|
+
users_id_name = {}
|
62
|
+
users_name_id = {}
|
63
|
+
count_users = {}
|
64
|
+
count_channels_dest = {}
|
65
|
+
|
66
|
+
# to translate global and enterprise users since sometimes was returning different names/ids
|
67
|
+
if from[0..3]=='2020' # this was an issue only on that period
|
68
|
+
Dir["#{config.stats_path}.*.log"].sort.each do |file|
|
69
|
+
if file >= "#{config.stats_path}.#{from_file}.log" or file <= "#{config.stats_path}.#{to_file}.log"
|
70
|
+
CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
|
71
|
+
unless users_id_name.key?(row[:user_id])
|
72
|
+
users_id_name[row[:user_id]] = row[:user_name]
|
73
|
+
end
|
74
|
+
unless users_name_id.key?(row[:user_name])
|
75
|
+
users_name_id[row[:user_name]] = row[:user_id]
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if user!=''
|
84
|
+
user_info = get_user_info(user)
|
85
|
+
if users_id_name.key?(user_info.user.id)
|
86
|
+
user_name = users_id_name[user_info.user.id]
|
87
|
+
else
|
88
|
+
user_name = user_info.user.name
|
89
|
+
end
|
90
|
+
if users_name_id.key?(user_info.user.name)
|
91
|
+
user_id = users_name_id[user_info.user.name]
|
92
|
+
else
|
93
|
+
user_id = user_info.user.id
|
94
|
+
end
|
95
|
+
end
|
96
|
+
master_admins = config.masters.dup
|
97
|
+
if users_id_name.size > 0
|
98
|
+
config.masters.each do |u|
|
99
|
+
if users_id_name.key?(u)
|
100
|
+
master_admins << users_id_name[u]
|
101
|
+
elsif users_name_id.key?(u)
|
102
|
+
master_admins << users_name_id[u]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
Dir["#{config.stats_path}.*.log"].sort.each do |file|
|
108
|
+
if file >= "#{config.stats_path}.#{from_file}.log" or file <= "#{config.stats_path}.#{to_file}.log"
|
109
|
+
CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
|
110
|
+
row[:date] = row[:date].to_s
|
111
|
+
if row[:dest_channel_id].to_s[0]=='D'
|
112
|
+
row[:dest_channel] = 'DM'
|
113
|
+
elsif row[:dest_channel].to_s == ''
|
114
|
+
row[:dest_channel] = row[:dest_channel_id]
|
115
|
+
end
|
116
|
+
if users_name_id.size > 0
|
117
|
+
row[:user_name] = users_id_name[row[:user_id]]
|
118
|
+
row[:user_id] = users_name_id[row[:user_name]]
|
119
|
+
else
|
120
|
+
users_id_name[row[:user_id]] ||= row[:user_name]
|
121
|
+
end
|
122
|
+
if !exclude_masters or (exclude_masters and !master_admins.include?(row[:user_name]) and
|
123
|
+
!master_admins.include?(row[:user_id]) and
|
124
|
+
!@master_admin_users_id.include?(row[:user_id]))
|
125
|
+
if !exclude_routines or (exclude_routines and !row[:user_name].match?(/^routine\//) )
|
126
|
+
if exclude_command == '' or (exclude_command!='' and row[:command]!=exclude_command)
|
127
|
+
if st_command == '' or (st_command != '' and row[:command] == st_command)
|
128
|
+
if row[:bot_channel_id] == channel_id or channel_id == ''
|
129
|
+
if row[:date] >= from and row[:date] <= to
|
130
|
+
count_users[row[:user_id]] ||= 0
|
131
|
+
count_users[row[:user_id]] += 1
|
132
|
+
if user=='' or (user!='' and row[:user_name] == user_name) or (user!='' and row[:user_id] == user_id)
|
133
|
+
rows << row.to_h
|
134
|
+
count_channels_dest[row[:dest_channel]] ||= 0
|
135
|
+
count_channels_dest[row[:dest_channel]] += 1
|
136
|
+
if monthly
|
137
|
+
rows_month[row[:date][0..6]] = 0 unless rows_month.key?(row[:date][0..6])
|
138
|
+
users_month[row[:date][0..6]] = [] unless users_month.key?(row[:date][0..6])
|
139
|
+
commands_month[row[:date][0..6]] = [] unless commands_month.key?(row[:date][0..6])
|
140
|
+
rows_month[row[:date][0..6]] += 1
|
141
|
+
users_month[row[:date][0..6]] << row[:user_id]
|
142
|
+
commands_month[row[:date][0..6]] << row[:command]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
total = rows.size
|
155
|
+
if exclude_masters
|
156
|
+
message << 'Excluding master admins'
|
157
|
+
end
|
158
|
+
if exclude_routines
|
159
|
+
message << 'Excluding routines'
|
160
|
+
end
|
161
|
+
if exclude_command != ''
|
162
|
+
message << "Excluding command #{exclude_command}"
|
163
|
+
end
|
164
|
+
if st_command != ''
|
165
|
+
message << "Including only command #{st_command}"
|
166
|
+
end
|
167
|
+
if user!=''
|
168
|
+
if user==from_user.id
|
169
|
+
message << "Bot stats for <@#{user}>"
|
170
|
+
else
|
171
|
+
message << "Showing only user <@#{user}>"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
if channel_id == ''
|
175
|
+
message << "*Total calls*: #{total} from #{from_short} to #{to_short}"
|
176
|
+
else
|
177
|
+
message << "*Total calls <##{channel_id}>*: #{total} from #{from_short} to #{to_short}"
|
178
|
+
end
|
179
|
+
unless count_users.size == 0 or total == 0 or user == ''
|
180
|
+
my_place = (count_users.sort_by(&:last).reverse.to_h.keys.index(user_id)+1)
|
181
|
+
message <<"\tYou are the *\# #{my_place}* of *#{count_users.size}* users"
|
182
|
+
end
|
183
|
+
if total > 0
|
184
|
+
if monthly
|
185
|
+
if on_dm_master
|
186
|
+
message << '*Totals by month / commands / users (%new)*'
|
187
|
+
else
|
188
|
+
message << '*Totals by month / commands*'
|
189
|
+
end
|
190
|
+
|
191
|
+
all_users = []
|
192
|
+
new_users = []
|
193
|
+
rows_month.each do |k,v|
|
194
|
+
if all_users.empty?
|
195
|
+
message_new_users = ''
|
196
|
+
else
|
197
|
+
new_users = (users_month[k]-all_users).uniq
|
198
|
+
message_new_users = "(#{new_users.size*100/users_month[k].uniq.size}%)"
|
199
|
+
end
|
200
|
+
all_users += users_month[k]
|
201
|
+
if on_dm_master
|
202
|
+
message << "\t#{k}: #{v} (#{(v.to_f*100/total).round(2)}%) / #{commands_month[k].uniq.size} / #{users_month[k].uniq.size} #{message_new_users}"
|
203
|
+
else
|
204
|
+
message << "\t#{k}: #{v} (#{(v.to_f*100/total).round(2)}%) / #{commands_month[k].uniq.size}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
if channel_id == ''
|
210
|
+
message << "*SmartBots*"
|
211
|
+
channels = rows.bot_channel.uniq.sort
|
212
|
+
channels.each do |channel|
|
213
|
+
count = rows.count {|h| h.bot_channel==channel}
|
214
|
+
message << "\t#{channel}: #{count} (#{(count.to_f*100/total).round(2)}%)"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
channels_dest_attachment = []
|
218
|
+
count_channels_dest = count_channels_dest.sort_by(&:last).reverse.to_h
|
219
|
+
if count_channels_dest.size > 10
|
220
|
+
message << "*From Channel* - #{count_channels_dest.size} (Top 10)"
|
221
|
+
else
|
222
|
+
message << "*From Channel* - #{count_channels_dest.size}"
|
223
|
+
end
|
224
|
+
|
225
|
+
count_channels_dest.keys[0..9].each do |ch|
|
226
|
+
message << "\t#{ch}: #{count_channels_dest[ch]} (#{(count_channels_dest[ch].to_f*100/total).round(2)}%)"
|
227
|
+
end
|
228
|
+
if count_channels_dest.size > 10 and all_data
|
229
|
+
count_channels_dest.each do |ch, value|
|
230
|
+
channels_dest_attachment << "\t#{ch}: #{value} (#{(value.to_f*100/total).round(2)}%)"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
users_attachment = []
|
236
|
+
if user==''
|
237
|
+
users = rows.user_id.uniq.sort
|
238
|
+
if users.size > 10
|
239
|
+
message << "*Users* - #{users.size} (Top 10)"
|
240
|
+
else
|
241
|
+
message << "*Users* - #{users.size}"
|
242
|
+
end
|
243
|
+
count_user = {}
|
244
|
+
users.each do |user|
|
245
|
+
count = rows.count {|h| h.user_id==user}
|
246
|
+
count_user[user] = count
|
247
|
+
end
|
248
|
+
i = 0
|
249
|
+
count_user.sort_by {|k,v| -v}.each do |user, count|
|
250
|
+
i+=1
|
251
|
+
if i <= 10
|
252
|
+
message << "\t#{users_id_name[user]}: #{count} (#{(count.to_f*100/total).round(2)}%)"
|
253
|
+
end
|
254
|
+
if users.size > 10 and all_data
|
255
|
+
users_attachment << "\t#{users_id_name[user]}: #{count} (#{(count.to_f*100/total).round(2)}%)"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
commands_attachment = []
|
260
|
+
|
261
|
+
if st_command == ''
|
262
|
+
commands = rows.command.uniq.sort
|
263
|
+
count_command = {}
|
264
|
+
commands.each do |command|
|
265
|
+
count = rows.count {|h| h.command==command}
|
266
|
+
count_command[command] = count
|
267
|
+
end
|
268
|
+
|
269
|
+
if commands.size > 10
|
270
|
+
message << "*Commands* - #{commands.size} (Top 10)"
|
271
|
+
else
|
272
|
+
message << "*Commands* - #{commands.size}"
|
273
|
+
end
|
274
|
+
|
275
|
+
i = 0
|
276
|
+
count_command.sort_by {|k,v| -v}.each do |command, count|
|
277
|
+
i+=1
|
278
|
+
if i <= 10
|
279
|
+
message << "\t#{command}: #{count} (#{(count.to_f*100/total).round(2)}%)"
|
280
|
+
end
|
281
|
+
if commands.size > 10 and all_data
|
282
|
+
commands_attachment << "\t#{command}: #{count} (#{(count.to_f*100/total).round(2)}%)"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
message << "*Message type*"
|
288
|
+
types = rows.type_message.uniq.sort
|
289
|
+
types.each do |type|
|
290
|
+
count = rows.count {|h| h.type_message==type}
|
291
|
+
message << "\t#{type}: #{count} (#{(count.to_f*100/total).round(2)}%)"
|
292
|
+
end
|
293
|
+
|
294
|
+
if on_dm_master
|
295
|
+
message << "*Last activity*: #{rows[-1].date} #{rows[-1].bot_channel} #{rows[-1].type_message} #{rows[-1].user_name} #{rows[-1].command}"
|
296
|
+
end
|
297
|
+
if users_attachment.size>0
|
298
|
+
send_file(dest, "", 'users.txt', "", 'text/plain', "text", content: users_attachment.join("\n"))
|
299
|
+
end
|
300
|
+
if commands_attachment.size>0
|
301
|
+
send_file(dest, "", 'commands.txt', "", 'text/plain', "text", content: commands_attachment.join("\n"))
|
302
|
+
end
|
303
|
+
if channels_dest_attachment.size>0
|
304
|
+
send_file(dest, "", 'channels_dest.txt', "", 'text/plain', "text", content: channels_dest_attachment.join("\n"))
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
else
|
309
|
+
message<<"Only Master admin users on a private conversation with the bot can see this kind of bot stats."
|
310
|
+
end
|
311
|
+
respond "#{message.join("\n")}", dest
|
312
|
+
end
|
313
|
+
end
|