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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -12
  3. data/lib/slack-smart-bot.rb +53 -43
  4. data/lib/slack-smart-bot_general_rules.rb +7 -4
  5. data/lib/slack-smart-bot_rules.rb +8 -6
  6. data/lib/slack/smart-bot/comm.rb +6 -1
  7. data/lib/slack/smart-bot/comm/ask.rb +12 -5
  8. data/lib/slack/smart-bot/comm/dont_understand.rb +1 -1
  9. data/lib/slack/smart-bot/comm/event_hello.rb +30 -0
  10. data/lib/slack/smart-bot/comm/get_channel_members.rb +8 -0
  11. data/lib/slack/smart-bot/comm/get_channels.rb +20 -0
  12. data/lib/slack/smart-bot/comm/get_user_info.rb +16 -0
  13. data/lib/slack/smart-bot/comm/react.rb +21 -8
  14. data/lib/slack/smart-bot/comm/respond.rb +38 -12
  15. data/lib/slack/smart-bot/comm/send_file.rb +1 -1
  16. data/lib/slack/smart-bot/comm/send_msg_channel.rb +2 -2
  17. data/lib/slack/smart-bot/comm/send_msg_user.rb +4 -4
  18. data/lib/slack/smart-bot/comm/unreact.rb +29 -0
  19. data/lib/slack/smart-bot/commands.rb +3 -1
  20. data/lib/slack/smart-bot/commands/general/bot_help.rb +16 -3
  21. data/lib/slack/smart-bot/commands/general/bot_stats.rb +313 -0
  22. data/lib/slack/smart-bot/commands/general/bot_status.rb +1 -1
  23. data/lib/slack/smart-bot/commands/general/bye_bot.rb +1 -1
  24. data/lib/slack/smart-bot/commands/general/hi_bot.rb +1 -1
  25. data/lib/slack/smart-bot/commands/general/use_rules.rb +6 -9
  26. data/lib/slack/smart-bot/commands/general/whats_new.rb +19 -0
  27. data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +65 -33
  28. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +42 -9
  29. data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +3 -7
  30. data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +1 -0
  31. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +9 -2
  32. data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +1 -0
  33. data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +1 -1
  34. data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +52 -21
  35. data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +5 -5
  36. data/lib/slack/smart-bot/commands/on_bot/repl.rb +50 -18
  37. data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +34 -9
  38. data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +2 -3
  39. data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +1 -1
  40. data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +27 -9
  41. data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +14 -1
  42. data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +3 -3
  43. data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +1 -1
  44. data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +41 -0
  45. data/lib/slack/smart-bot/commands/on_master/create_bot.rb +4 -8
  46. data/lib/slack/smart-bot/listen.rb +6 -5
  47. data/lib/slack/smart-bot/process.rb +230 -186
  48. data/lib/slack/smart-bot/process_first.rb +104 -87
  49. data/lib/slack/smart-bot/treat_message.rb +128 -52
  50. data/lib/slack/smart-bot/utils.rb +2 -0
  51. data/lib/slack/smart-bot/utils/answer.rb +18 -0
  52. data/lib/slack/smart-bot/utils/answer_delete.rb +15 -0
  53. data/lib/slack/smart-bot/utils/build_help.rb +57 -5
  54. data/lib/slack/smart-bot/utils/create_routine_thread.rb +48 -8
  55. data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +1 -7
  56. data/lib/slack/smart-bot/utils/get_help.rb +79 -17
  57. data/lib/slack/smart-bot/utils/save_stats.rb +21 -8
  58. data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +6 -0
  59. data/whats_new.txt +24 -0
  60. metadata +23 -13
  61. 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,8 @@
1
+ def get_channel_members(channel_id)
2
+ if config.simulate and config.key?(:client)
3
+ client.web_client.conversations_members[channel_id.to_sym].members
4
+ else
5
+ client.web_client.conversations_members(channel: channel_id).members
6
+ end
7
+
8
+ 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
- def react(emoji, parent=false)
5
- if parent
6
- ts = Thread.current[:thread_ts]
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
- ts = Thread.current[:ts]
10
+ parent = false
9
11
  end
10
- begin
11
- client.web_client.reactions_add(channel: Thread.current[:dest], name: emoji, timestamp: ts)
12
- rescue Exception => stack
13
- @logger.warn stack
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
- client.message(channel: @channel_id, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
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
- client.message(channel: @channel_id, text: msg, as_user: true)
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
- client.message(channel: dest, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
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
- client.message(channel: dest, text: msg, as_user: true)
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
- send_msg_user(dest, msg)
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 = client.web_client.users_info(user: dest)
46
- send_msg_user(user_info.user.id, msg)
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/on_bot/admin_master/bot_stats"
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 = "*You are using rules from another channel: <##{Thread.current[:using_channel]}>. These are the specific commands for that channel:*"
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