slack-smart-bot 1.12.8 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +85 -12
  3. data/lib/slack/smart-bot/comm/respond.rb +1 -0
  4. data/lib/slack/smart-bot/comm/update.rb +13 -0
  5. data/lib/slack/smart-bot/comm.rb +1 -0
  6. data/lib/slack/smart-bot/commands/general/add_team.rb +1 -0
  7. data/lib/slack/smart-bot/commands/general/add_vacation.rb +5 -0
  8. data/lib/slack/smart-bot/commands/general/allow_access.rb +1 -1
  9. data/lib/slack/smart-bot/commands/general/delete_team.rb +1 -0
  10. data/lib/slack/smart-bot/commands/general/deny_access.rb +1 -1
  11. data/lib/slack/smart-bot/commands/general/public_holidays.rb +144 -0
  12. data/lib/slack/smart-bot/commands/general/see_announcements.rb +2 -2
  13. data/lib/slack/smart-bot/commands/general/see_memos_team.rb +202 -0
  14. data/lib/slack/smart-bot/commands/general/see_teams.rb +3 -175
  15. data/lib/slack/smart-bot/commands/general/see_vacations.rb +41 -21
  16. data/lib/slack/smart-bot/commands/general/set_public_holidays.rb +21 -0
  17. data/lib/slack/smart-bot/commands/general/update_team.rb +1 -0
  18. data/lib/slack/smart-bot/commands/general_bot_commands.rb +100 -8
  19. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +27 -3
  20. data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +12 -8
  21. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +33 -4
  22. data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +22 -1
  23. data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +50 -4
  24. data/lib/slack/smart-bot/commands/on_bot/admin_master/update_message.rb +25 -0
  25. data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +8 -6
  26. data/lib/slack/smart-bot/commands/on_bot/repl.rb +55 -15
  27. data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +2 -1
  28. data/lib/slack/smart-bot/commands.rb +4 -0
  29. data/lib/slack/smart-bot/listen.rb +1 -1
  30. data/lib/slack/smart-bot/process.rb +36 -6
  31. data/lib/slack/smart-bot/process_first.rb +250 -188
  32. data/lib/slack/smart-bot/treat_message.rb +1 -1
  33. data/lib/slack/smart-bot/utils/build_help.rb +1 -1
  34. data/lib/slack/smart-bot/utils/create_routine_thread.rb +40 -4
  35. data/lib/slack/smart-bot/utils/decrypt.rb +15 -0
  36. data/lib/slack/smart-bot/utils/display_calendar.rb +86 -0
  37. data/lib/slack/smart-bot/utils/encrypt.rb +15 -0
  38. data/lib/slack/smart-bot/utils/encryption_get_key_iv.rb +29 -0
  39. data/lib/slack/smart-bot/utils/get_help.rb +1 -1
  40. data/lib/slack/smart-bot/utils/get_team_members.rb +39 -0
  41. data/lib/slack/smart-bot/utils/get_teams.rb +22 -16
  42. data/lib/slack/smart-bot/utils/get_vacations.rb +20 -15
  43. data/lib/slack/smart-bot/utils/save_stats.rb +2 -2
  44. data/lib/slack/smart-bot/utils/update_teams.rb +15 -9
  45. data/lib/slack/smart-bot/utils/update_vacations.rb +5 -3
  46. data/lib/slack/smart-bot/utils.rb +5 -0
  47. data/lib/slack-smart-bot.rb +9 -0
  48. data/lib/slack-smart-bot_general_commands.rb +33 -0
  49. data/lib/slack-smart-bot_general_rules.rb +2 -2
  50. data/whats_new.txt +15 -17
  51. metadata +27 -11
@@ -11,8 +11,6 @@ def general_bot_commands(user, command, dest, files = [])
11
11
  display_name = user.profile.display_name
12
12
  end
13
13
  case command
14
-
15
-
16
14
  # help: ----------------------------------------------
17
15
  # help: `bot help`
18
16
  # help: `bot help COMMAND`
@@ -29,6 +27,35 @@ def general_bot_commands(user, command, dest, files = [])
29
27
  # help: command_id: :bot_help
30
28
  # help:
31
29
 
30
+ # help: ----------------------------------------------
31
+ # help: `for NUMBER times every NUMBER minutes COMMAND`
32
+ # help: `for NUMBER times every NUMBER seconds COMMAND`
33
+ # help: `NUMBER times every NUMBER minutes COMMAND`
34
+ # help: `NUMBER times every NUMBER seconds COMMAND`
35
+ # help: It will run the command every NUMBER minutes or seconds for NUMBER times.
36
+ # help: max 24 times. min every 10 seconds. max every 60 minutes.
37
+ # help: Call `quit loop LOOP_ID` to stop the loop.
38
+ # help: aliases for minutes: m, minute, minutes
39
+ # help: aliases for seconds: s, sc, second, seconds
40
+ # help: Examples:
41
+ # help: _for 5 times every 1 minute ^ruby puts Time.now_
42
+ # help: _10 times every 30s !ruby puts Time.now_
43
+ # help: _24 times every 60m !get sales today_
44
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#loops|more info>
45
+ # help: command_id: :create_loop
46
+ # help:
47
+
48
+ # help: ----------------------------------------------
49
+ # help: `quit loop LOOP_ID`
50
+ # help: It will stop the loop with the id LOOP_ID.
51
+ # help: Only the user who created the loop or an admin can stop it.
52
+ # help: aliases for loop: iterator, iteration
53
+ # help: aliases for quit: stop, exit, kill
54
+ # help: Examples:
55
+ # help: _quit loop 1_
56
+ # help: _stop iterator 12_
57
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#loops|more info>
58
+ # help: command_id: :quit_loop
32
59
 
33
60
  # help: ----------------------------------------------
34
61
  # help: `Hi Bot`
@@ -575,6 +602,27 @@ def general_bot_commands(user, command, dest, files = [])
575
602
  name = $2.downcase
576
603
  delete_team(user, name)
577
604
 
605
+ # help: ----------------------------------------------
606
+ # help: `see MEMO_TYPE from TEAM_NAME team`
607
+ # help: `see MEMO_TYPE from TEAM_NAME team TOPIC`
608
+ # help: `see all memos from TEAM_NAME team`
609
+ # help: `see all memos from TEAM_NAME team TOPIC`
610
+ # help: It will show the memos of the team.
611
+ # help: If TOPIC is supplied it will show the memos of the topic.
612
+ # help: MEMO_TYPE: memos, notes, issues, tasks, features, bugs, jira, github. In case of 'all memos' will display all of any type.
613
+ # help: Examples:
614
+ # help: _see memos from sales team_
615
+ # help: _see bugs from sales team_
616
+ # help: _see all memos from sales team webdev_
617
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
618
+ # help: command_id: :see_memos_team
619
+ # help:
620
+ when /\A\s*see\s+(memo|note|issue|task|feature|bug|jira|github|all\s+memo)s?\s+(from\s+)?([\w\-]+)\s+team(.*)\s*\z/i,
621
+ /\A\s*see\s+(memo|note|issue|task|feature|bug|jira|github|all\s+memo)s?\s+(from\s+)?team\s+([\w\-]+)(.*)\s*\z/i
622
+ type = $1.downcase.to_sym
623
+ name = $3.downcase
624
+ topic = $4.strip
625
+ see_memos_team(user, type: type, name: name, topic: topic)
578
626
 
579
627
  # help: ----------------------------------------------
580
628
  # help: `add vacation from YYYY/MM/DD to YYYY/MM/DD`
@@ -631,16 +679,19 @@ def general_bot_commands(user, command, dest, files = [])
631
679
  # help: `see my vacations`
632
680
  # help: `see my time off`
633
681
  # help: `see vacations @USER`
682
+ # help: `see my vacations YEAR`
634
683
  # help: It will display current and past time off.
684
+ # help: If you call this command on a DM, it will show your vacations for the year on a calendar.
635
685
  # help: <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
636
686
  # help: command_id: :see_vacations
637
687
  # help:
638
- when /\A\s*see\s+my\s+vacations\s*()\z/i,
639
- /\A\s*see\s+my\s+time\s+off\s*()\z/i,
640
- /\A\s*see\s+time\s+off\s+<@(\w+)>\s*\z/i,
641
- /\A\s*see\s+vacations\s+<@(\w+)>\s*\z/i
688
+ when /\A\s*see\s+my\s+vacations\s*()\s*(\d{4})?\s*\z/i,
689
+ /\A\s*see\s+my\s+time\s+off\s*()\s*(\d{4})?\s*\z/i,
690
+ /\A\s*see\s+time\s+off\s+<@(\w+)>\s*\s*(\d{4})?\s*\z/i,
691
+ /\A\s*see\s+vacations\s+<@(\w+)>\s*(\d{4})?\s*\z/i
642
692
  from_user = $1
643
- see_vacations(user, from_user: from_user)
693
+ year = $2
694
+ see_vacations(user, dest, from_user: from_user, year: year)
644
695
 
645
696
  # help: ----------------------------------------------
646
697
  # help: `vacations team NAME`
@@ -659,7 +710,48 @@ def general_bot_commands(user, command, dest, files = [])
659
710
  date = $4.to_s
660
711
  date = Date.today.strftime("%Y/%m/%d") if date.empty?
661
712
  see_vacations_team(user, team_name, date)
662
-
713
+
714
+
715
+ # help: ----------------------------------------------
716
+ # help: `public holidays COUNTRY`
717
+ # help: `public holidays COUNTRY/STATE DATE`
718
+ # help: STATE: optional. If not specified, it will return all the holidays for the country.
719
+ # help: DATE: optional. It can be supplied as YYYY or YYYY-MM or YYYY-MM-DD. If not specified, it will return all the holidays for current year.
720
+ # help: Examples:
721
+ # help: _public holidays United States_
722
+ # help: _public holidays United States/California_
723
+ # help: _public holidays United States/California 2023_
724
+ # help: _public holidays Iceland 2023-12_
725
+ # help: _public holidays India 2023-12-25_
726
+ # help: command_id: :public_holidays
727
+ # help:
728
+ when /\A\s*public\s+(holiday?|vacation)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)()()()()\s*\z/i,
729
+ /\A\s*public\s+(holiday?|vacation)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)\/([a-zA-Z\s]+)()()()\s*\z/i,
730
+ /\A\s*public\s+(holiday?|vacation)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)\/([a-zA-Z\s]+)\s+(\d{4})[\/\-]?(\d\d)?[\/\-]?(\d\d)?\s*\z/i,
731
+ /\A\s*public\s+(holiday?|vacation)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)()\s+(\d{4})[\/\-]?(\d\d)?[\/\-]?(\d\d)?\s*\z/i
732
+ country = $3
733
+ state = $4.to_s
734
+ year = $5.to_s
735
+ month = $6.to_s
736
+ day = $7.to_s
737
+ year = Date.today.year if year.to_s == ''
738
+ public_holidays(country, state, year, month, day)
739
+
740
+ # help: ----------------------------------------------
741
+ # help: `set public holidays to COUNTRY/STATE`
742
+ # help: It will set the public holidays for the country and state specified.
743
+ # help: If STATE is not specified, it will set the public holidays for the country.
744
+ # help: Examples:
745
+ # help: _set public holidays to Iceland_
746
+ # help: _set public holidays to United States/California_
747
+ # help: command_id: :set_public_holidays
748
+ # help:
749
+ when /\A\s*set\s+public\s+(holiday?|vacation)s?\s+to\s+([^\/]+)\/([^\/]+)\s*\z/i,
750
+ /\A\s*set\s+public\s+(holiday?|vacation)s?\s+to\s+([^\/]+)\s*\z/i
751
+ country = $2
752
+ state = $3.to_s
753
+ set_public_holidays(country, state, user)
754
+
663
755
  else
664
756
  return false
665
757
  end
@@ -11,6 +11,7 @@ class SlackSmartBot
11
11
  # helpadmin: `add routine NAME at TIME #CHANNEL COMMAND`
12
12
  # helpadmin: `add routine NAME on DAYWEEK at TIME COMMAND`
13
13
  # helpadmin: `add routine NAME on DAYWEEK at TIME #CHANNEL COMMAND`
14
+ # helpadmin: `add routine NAME on the DAY_OF_MONTH at TIME COMMAND`
14
15
  # helpadmin: `add routine NAME at TIME`
15
16
  # helpadmin: `add silent routine NAME at TIME`
16
17
  # helpadmin: `create routine NAME at TIME`
@@ -34,6 +35,7 @@ class SlackSmartBot
34
35
  # helpadmin: _add bgroutine example on Mondays at 05:00 !run customer tests_
35
36
  # helpadmin: _add routine example on Tuesdays at 09:00 #SREChannel !run db cleanup_
36
37
  # helpadmin: _add routine example on weekdays at 22:00 suggest command_
38
+ # helpadmin: _add routine example on the 5th at 22:00 suggest command_
37
39
  # helpadmin: <https://github.com/MarioRuiz/slack-smart-bot#routines|more info>
38
40
  # helpadmin: command_id: :add_routine
39
41
  # helpadmin:
@@ -53,6 +55,7 @@ class SlackSmartBot
53
55
  every = ""
54
56
  at = ""
55
57
  dayweek = ''
58
+ daymonth = ''
56
59
  next_run = Time.now
57
60
  case period.downcase
58
61
  when "days", "d"
@@ -68,7 +71,28 @@ class SlackSmartBot
68
71
  every = "#{number_time} seconds"
69
72
  every_in_seconds = number_time.to_i
70
73
  else # time
71
- if type != 'at' and type!='weekday' and type!='weekend'
74
+ if type != 'at' and type.match?(/^\d+$/) # day of month
75
+ day = type.to_i
76
+ daymonth = type
77
+ if day > 31
78
+ respond "Wrong day of month specified: *#{day}*", dest
79
+ return
80
+ end
81
+ if Date.today.day > day
82
+ next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
83
+ else
84
+ next_month = Date.new(Date.today.year, Date.today.month, 1)
85
+ end
86
+ next_month_last_day = Date.new(next_month.year, next_month.month, -1)
87
+ if day > next_month_last_day.day
88
+ next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
89
+ else
90
+ next_time = Date.new(next_month.year, next_month.month, day)
91
+ end
92
+ days = (next_time - Date.today).to_i
93
+ every_in_seconds = days * 24 * 60 * 60 # one day
94
+
95
+ elsif type != 'at' and type!='weekday' and type!='weekend'
72
96
  dayweek = type.downcase
73
97
 
74
98
  days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
@@ -133,11 +157,11 @@ class SlackSmartBot
133
157
  channel_id = dest if channel_id.to_s == ''
134
158
  @routines[@channel_id] = {} unless @routines.key?(@channel_id)
135
159
  @routines[@channel_id][name] = { channel_name: config.channel, creator: from, creator_id: user.id, status: :on,
136
- every: every, every_in_seconds: every_in_seconds, at: at, dayweek: dayweek, file_path: file_path,
160
+ every: every, every_in_seconds: every_in_seconds, at: at, dayweek: dayweek, daymonth: daymonth, file_path: file_path,
137
161
  command: command_to_run.to_s.strip, silent: silent,
138
162
  next_run: next_run.to_s, dest: channel_id, last_run: "", last_elapsed: "",
139
163
  running: false, routine_type: routine_type}
140
- update_routines
164
+ update_routines()
141
165
  respond "Added routine *`#{name}`* to the channel", dest
142
166
  create_routine_thread(name, @routines[@channel_id][name])
143
167
  end
@@ -38,15 +38,19 @@ class SlackSmartBot
38
38
  respond "routine *`#{name}`*: #{stdout} #{stderr}", @routines[@channel_id][name][:dest]
39
39
  end
40
40
  else #command
41
- respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
41
+ message = respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest], return_message: true
42
42
  started = Time.now
43
- treat_message({ channel: @routines[@channel_id][name][:dest],
44
- user: @routines[@channel_id][name][:creator_id],
45
- text: @routines[@channel_id][name][:command],
46
- files: nil,
47
- routine_name: name,
48
- routine_type: @routines[@channel_id][name][:routine_type],
49
- routine: true })
43
+ data = { channel: @routines[@channel_id][name][:dest],
44
+ user: @routines[@channel_id][name][:creator_id],
45
+ text: @routines[@channel_id][name][:command],
46
+ files: nil,
47
+ routine_name: name,
48
+ routine_type: @routines[@channel_id][name][:routine_type],
49
+ routine: true }
50
+ if @routines[@channel_id][name][:command].match?(/^!!/) or @routines[@channel_id][name][:command].match?(/^\^/)
51
+ data[:ts] = message.ts
52
+ end
53
+ treat_message(data)
50
54
  end
51
55
  @routines[@channel_id][name][:last_elapsed] = (Time.now - started)
52
56
  @routines[@channel_id][name][:last_run] = started.to_s
@@ -1,14 +1,17 @@
1
1
  class SlackSmartBot
2
2
  # helpadmin: ----------------------------------------------
3
3
  # helpadmin: `see routines`
4
+ # helpadmin: `see routines HEADER /REGEXP/`
4
5
  # helpadmin: `see all routines`
6
+ # helpadmin: `see all routines HEADER /REGEXP/`
5
7
  # helpadmin: It will show the routines of the channel
6
- # helpadmin: In case of `all` and on the master channel, it will show all the routines from all channels
8
+ # helpadmin: In case of 'all' and on the master channel, it will show all the routines from all channels
9
+ # helpadmin: If you use HEADER it will show only the routines that match the REGEXP on the header. Available headers: name, creator, status, next_run, last_run, command
7
10
  # helpadmin: You can use this command only if you are an admin user
8
11
  # helpadmin: <https://github.com/MarioRuiz/slack-smart-bot#routines|more info>
9
12
  # helpadmin: command_id: :see_routines
10
13
  # helpadmin:
11
- def see_routines(dest, from, user, all)
14
+ def see_routines(dest, from, user, all, header, regexp)
12
15
  save_stats(__method__)
13
16
  if is_admin?
14
17
  if all
@@ -33,11 +36,37 @@ class SlackSmartBot
33
36
  end
34
37
  end
35
38
 
39
+ if header != ''
40
+ routines_filtered = {}
41
+
42
+ routines.each do |ch, rout_ch|
43
+ routines_filtered[ch] = rout_ch.dup
44
+ rout_ch.each do |k, v|
45
+ if header == 'name'
46
+ if k.match(/#{regexp}/i).nil?
47
+ routines_filtered[ch].delete(k)
48
+ end
49
+ elsif v[header.to_sym].to_s.match(/#{regexp}/i).nil?
50
+ routines_filtered[ch].delete(k)
51
+ end
52
+ end
53
+ end
54
+ routines = routines_filtered
55
+ end
56
+
36
57
  if routines.get_values(:channel_name).size == 0
37
- respond "There are no routines added.", dest
58
+ if header != ''
59
+ respond "There are no routines added that match the header *#{header}* and the regexp *#{regexp}*.", dest
60
+ else
61
+ respond "There are no routines added.", dest
62
+ end
38
63
  else
39
64
  routines.each do |ch, rout_ch|
40
- respond "Routines on channel *#{rout_ch.get_values(:channel_name).values.flatten.uniq[0]}*", dest
65
+ if header != ''
66
+ respond "Routines on channel *#{rout_ch.get_values(:channel_name).values.flatten.uniq[0]}* that match the header *#{header}* and the regexp *#{regexp}*", dest
67
+ else
68
+ respond "Routines on channel *#{rout_ch.get_values(:channel_name).values.flatten.uniq[0]}*", dest
69
+ end
41
70
  rout_ch.each do |k, v|
42
71
  msg = []
43
72
  if v[:dest][0] == 'D'
@@ -18,7 +18,28 @@ class SlackSmartBot
18
18
  respond "It's only possible to start routines from MASTER channel from a direct message with the bot.", dest
19
19
  elsif @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
20
20
  @routines[@channel_id][name][:status] = :on
21
- if @routines[@channel_id][name][:at]!=''
21
+ if @routines[@channel_id][name].key?(:daymonth) and @routines[@channel_id][name][:daymonth] != ''
22
+ started = Time.now
23
+ daymonth = @routines[@channel_id][name][:daymonth]
24
+ day = daymonth.to_i
25
+ nt = @routines[@channel_id][name][:at].split(":")
26
+ if Time.now > Time.new(Time.now.year, Time.now.month, day, nt[0], nt[1], nt[2])
27
+ next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
28
+ else
29
+ next_month = Date.new(Date.today.year, Date.today.month, 1)
30
+ end
31
+ next_month_last_day = Date.new(next_month.year, next_month.month, -1)
32
+ if day > next_month_last_day.day
33
+ next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
34
+ else
35
+ next_time = Date.new(next_month.year, next_month.month, day)
36
+ end
37
+ days = (next_time - Date.today).to_i
38
+ next_run = started + (days * 24 * 60 * 60) # one more day/week
39
+ next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
40
+ @routines[@channel_id][name][:next_run] = next_run.to_s
41
+ @routines[@channel_id][name][:sleeping] = (next_run - started).ceil
42
+ elsif @routines[@channel_id][name][:at]!=''
22
43
  started = Time.now
23
44
  if started.strftime("%H:%M:%S") < @routines[@channel_id][name][:at]
24
45
  nt = @routines[@channel_id][name][:at].split(":")
@@ -7,23 +7,69 @@ class SlackSmartBot
7
7
  # helpadmin: `send message to URL : MESSAGE`
8
8
  # helpadmin: `send message to @USER1 @USER99 : MESSAGE`
9
9
  # helpadmin: `send message to #CHANNEL1 #CHANNEL99 : MESSAGE`
10
+ # helpadmin: `send message to users from YYYY/MM/DD to YYYY/MM/DD #CHANNEL COMMAND_ID: MESSAGE`
10
11
  # helpadmin: It will send the specified message as SmartBot
11
12
  # helpadmin: You can use this command only if you are a Master admin user and if you are in a private conversation with the bot
13
+ # helpadmin: In case from and to specified will send a DM to all users that have been using the SmartBot according to the SmartBot Stats. One message every 5sc. #CHANNEL and COMMAND_ID are optional filters.
12
14
  # helpadmin: command_id: :send_message
13
15
  # helpadmin:
14
- def send_message(dest, from, typem, to, thread_ts, message)
16
+ def send_message(dest, from, typem, to, thread_ts, stats_from, stats_to, stats_channel_filter, stats_command_filter, message)
15
17
  save_stats(__method__)
16
18
  if config.masters.include?(from) and typem==:on_dm #master admin user
19
+ react :runner
17
20
  unless Thread.current[:command_orig].to_s == ''
18
21
  message_orig = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").scan(/[^:]+\s*:\s+(.+)/im).join
19
22
  message = message_orig unless message_orig == ''
20
23
  end
21
24
  succ = true
22
- to.each do |t|
23
- unless t.match?(/^\s*$/)
24
- succ = (respond message, t, thread_ts: thread_ts, web_client: true) && succ
25
+ if stats_from!='' and stats_to!=''
26
+ users = []
27
+ user_ids = []
28
+ stats_from.gsub!('/', '-')
29
+ stats_to.gsub!('/', '-')
30
+ stats_from += " 00:00:00 +0000"
31
+ stats_to += " 23:59:59 +0000"
32
+ Dir["#{config.stats_path}.*.log"].sort.each do |file|
33
+ if file >= "#{config.stats_path}.#{stats_from[0..6]}.log" and file <= "#{config.stats_path}.#{stats_to[0..6]}.log"
34
+ CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
35
+ if row[:date] >= stats_from and row[:date] <= stats_to and !users.include?(row[:user_name])
36
+ if (stats_channel_filter=='' and stats_command_filter=='') or
37
+ (stats_channel_filter!='' and stats_command_filter=='' and (row[:bot_channel_id]==stats_channel_filter or row[:dest_channel_id]==stats_channel_filter)) or
38
+ (stats_command_filter!='' and stats_channel_filter=='' and row[:command]==stats_command_filter) or
39
+ (stats_channel_filter!='' and stats_command_filter!='' and ((row[:bot_channel_id]==stats_channel_filter or row[:dest_channel_id]==stats_channel_filter) and row[:command]==stats_command_filter))
40
+
41
+ user_ids << row[:user_id]
42
+ users << row[:user_name]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ users_success = []
50
+ users_failed = []
51
+
52
+ user_ids.each do |u|
53
+ succ = (respond message, u, thread_ts: thread_ts, web_client: true)
54
+ if succ
55
+ users_success << u
56
+ else
57
+ users_failed << u
58
+ end
59
+ sleep 5
60
+ end
61
+ respond "Users that received the message (#{users_success.size}): <@#{users_success.join('>, <@')}>", dest if users_success.size > 0
62
+ respond "Users that didn't receive the message (#{users_failed.size}): <@#{users_failed.join('>, <@')}>", dest if users_failed.size > 0
63
+ respond "No users selected to send the message.", dest if users_success.size == 0 and users_failed.size == 0
64
+ succ = false if users_failed.size > 0
65
+ else
66
+ to.each do |t|
67
+ unless t.match?(/^\s*$/)
68
+ succ = (respond message, t, thread_ts: thread_ts, web_client: true) && succ
69
+ end
25
70
  end
26
71
  end
72
+ unreact :runner
27
73
  if succ
28
74
  react :heavy_check_mark
29
75
  else
@@ -0,0 +1,25 @@
1
+ class SlackSmartBot
2
+
3
+ # helpadmin: ----------------------------------------------
4
+ # helpadmin: `update message URL TEXT`
5
+ # helpadmin: It will update the SmartBot message supplied
6
+ # helpadmin: You can use this command only if you are a Master admin user and if you are in a private conversation with the bot
7
+ # helpadmin: command_id: :update_message
8
+ # helpadmin:
9
+ def update_message(from, typem, url, text)
10
+ save_stats(__method__)
11
+ channel, ts = url.scan(/\/archives\/(\w+)\/(\w\d+)/)[0]
12
+ if config.masters.include?(from) and typem==:on_dm and !channel.nil? #master admin user
13
+ ts = "#{ts[0..-7]}.#{ts[-6..-1]}"
14
+ succ = update(channel, ts, text)
15
+ if succ
16
+ react :heavy_check_mark
17
+ else
18
+ react :x
19
+ end
20
+ else
21
+ respond "Only master admin users on a private conversation with the SmartBot can update SmartBot messages"
22
+ end
23
+ end
24
+ end
25
+
@@ -39,7 +39,7 @@ class SlackSmartBot
39
39
  # help: _bot stats #sales today_
40
40
  # help: _bot stats #sales from 2020-01-01 monthly_
41
41
  # help: _bot stats exclude routines masters from 2021/01/01 monthly_
42
- # help: _bot stats members #development from 2022/01/01 to 2022/01/31_\
42
+ # help: _bot stats members #development from 2022/01/01 to 2022/01/31_
43
43
  # help: _bot stats type_message /(on_pub|on_pg)/_
44
44
  # help: <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
45
45
  # help: command_id: :bot_stats
@@ -60,7 +60,7 @@ class SlackSmartBot
60
60
  user = "" # for the case we are on the stats channel
61
61
  end
62
62
  if (from_user.id != user and
63
- (config.masters.include?(from_user.name) or master_admin_users_id.include?(from_user.id) or dest == @channels_id[config.stats_channel]) and #Jal
63
+ (config.masters.include?(from_user.name) or master_admin_users_id.include?(from_user.id) or dest == @channels_id[config.stats_channel]) and
64
64
  (typem == :on_dm or dest[0] == "D" or dest == @channels_id[config.stats_channel]))
65
65
  on_dm_master = true #master admin user
66
66
  else
@@ -202,7 +202,7 @@ class SlackSmartBot
202
202
  unless header.empty?
203
203
  add = true
204
204
  header.each_with_index do |h, i|
205
- if row[h.downcase.to_sym].to_s.match?(Regexp.new(regexp[i])) == false
205
+ if !row[h.downcase.to_sym].to_s.match?(/#{regexp[i]}/i)
206
206
  add = false
207
207
  break
208
208
  end
@@ -297,10 +297,11 @@ class SlackSmartBot
297
297
  message_new_users = "(#{new_users.size * 100 / users_month[k].uniq.size}%)"
298
298
  end
299
299
  all_users += users_month[k]
300
+ graph = ":large_yellow_square: " * (v.to_f * (10*rows_month.size) / total).round(2)
300
301
  if on_dm_master
301
- message << "\t#{k}: #{v} (#{(v.to_f * 100 / total).round(2)}%) / #{commands_month[k].uniq.size} / #{users_month[k].uniq.size} #{message_new_users}"
302
+ message << "\t#{k}: #{graph} #{v} (#{(v.to_f * 100 / total).round(2)}%) / #{commands_month[k].uniq.size} / #{users_month[k].uniq.size} #{message_new_users}"
302
303
  else
303
- message << "\t#{k}: #{v} (#{(v.to_f * 100 / total).round(2)}%) / #{commands_month[k].uniq.size}"
304
+ message << "\t#{k}: #{graph} #{v} (#{(v.to_f * 100 / total).round(2)}%) / #{commands_month[k].uniq.size}"
304
305
  end
305
306
  end
306
307
  end
@@ -450,7 +451,8 @@ class SlackSmartBot
450
451
  total_known = 0
451
452
  tzone_users.each do |tzone, num|
452
453
  unless tzone.to_s == ""
453
- message << "\t#{tzone}: #{num} (#{(num.to_f * 100 / total_without_routines).round(2)}%)"
454
+ abb_tzone = tzone.split.map{|i| i[0,1].upcase}.join
455
+ message << "\t#{abb_tzone} _#{tzone}_: #{num} (#{(num.to_f * 100 / total_without_routines).round(2)}%)"
454
456
  total_known += num
455
457
  end
456
458
  end
@@ -24,6 +24,7 @@ class SlackSmartBot
24
24
  # help: To pre-execute some ruby when starting the session add the code to .smart-bot-repl file on the project root folder defined on project_folder
25
25
  # help: If you want to see the methods of a class or module you created use _ls TheModuleOrClass_
26
26
  # help: You can supply the Environmental Variables you need for the Session
27
+ # help: You can add collaborators by sending _add collaborator @USER_ to the session.
27
28
  # help: Examples:
28
29
  # help: _repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_
29
30
  # help: _repl CreateCustomer: "It creates a random customer for testing" LOCATION=spain HOST='https://10.30.40.50:8887'_
@@ -61,7 +62,10 @@ class SlackSmartBot
61
62
  finished: Time.now,
62
63
  input: [],
63
64
  on_thread: Thread.current[:on_thread],
64
- thread_ts: Thread.current[:thread_ts]
65
+ thread_ts: Thread.current[:thread_ts],
66
+ collaborators: [],
67
+ user_type: :creator,
68
+ user_creator: from
65
69
  }
66
70
 
67
71
  unless temp_repl
@@ -90,6 +94,7 @@ class SlackSmartBot
90
94
 
91
95
  message = "Session name: *#{session_name}*
92
96
  From now on I will execute all you write as a Ruby command and I will keep the session open until you send `quit` or `bye` or `exit`.
97
+ In case you need someone to help you with the session you can add collaborators by sending `add collaborator @USER` to the session.
93
98
  I will respond with the result so it is not necessary you send `print`, `puts`, `p` or `pp` unless you want it as the output when calling `run repl`.
94
99
  Use `p` to print a message raw, exacly like it is returned.
95
100
  If you want to avoid a message to be treated by me, start the message with '-'.
@@ -239,6 +244,9 @@ class SlackSmartBot
239
244
  rescue
240
245
  end
241
246
  end
247
+ @repl_sessions[from][:collaborators].each do |collaborator|
248
+ @repl_sessions.delete(collaborator)
249
+ end
242
250
  @repl_sessions.delete(from)
243
251
  break
244
252
  end
@@ -280,24 +288,56 @@ class SlackSmartBot
280
288
  code.match?(/=?\s*(require|load)(\(|\s)/i)
281
289
 
282
290
  respond "Sorry I cannot run this due security reasons", dest
291
+ elsif code.match(/\A\s*add\s+collaborator\s+<@(\w+)>\s*\z/i)
292
+ collaborator = $1
293
+ user_info = @users.select{|u| u.id == collaborator or (u.key?(:enterprise_user) and u.enterprise_user.id == collaborator)}[-1]
294
+ collaborator_name = user_info.name
295
+ if @repl_sessions.key?(collaborator_name)
296
+ respond "Sorry, <@#{collaborator}> is already in a repl. Please ask her/him to quit it first.", dest
297
+ else
298
+ respond "Collaborator added. Now <@#{collaborator}> can interact with this repl.", dest
299
+ creator = @repl_sessions[from][:user_creator]
300
+ @repl_sessions[creator][:collaborators] << collaborator_name
301
+ @repl_sessions[collaborator_name] = {
302
+ name: @repl_sessions[from][:name],
303
+ dest: dest,
304
+ on_thread: Thread.current[:on_thread],
305
+ thread_ts: Thread.current[:thread_ts],
306
+ user_type: :collaborator,
307
+ user_creator: creator
308
+ }
309
+ end
283
310
  else
284
- @repl_sessions[from][:input]<<code
311
+ if @repl_sessions[from][:user_type] == :collaborator
312
+ @repl_sessions[@repl_sessions[from][:user_creator]][:input] << code
313
+ else
314
+ @repl_sessions[from][:input] << code
315
+ end
285
316
  case code
286
- when /^\s*(quit|exit|bye|bye bot)\s*$/i
287
- open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
288
- f.puts code
289
- }
290
- respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
291
- unreact :running, @ts_react[@repl_sessions[from].name]
292
- pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
293
- pids.each do |pid|
294
- begin
295
- Process.kill("KILL", pid)
296
- rescue
317
+ when /^\s*(quit|exit|bye|bye\s+bot)\s*$/i
318
+ if @repl_sessions[from][:user_type] == :collaborator
319
+ respond "Collaborator <@#{user.id}> removed.", dest
320
+ @repl_sessions[@repl_sessions[from][:user_creator]][:collaborators].delete(from)
321
+ @repl_sessions.delete(from)
322
+ else
323
+ open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
324
+ f.puts code
325
+ }
326
+ respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
327
+ unreact :running, @ts_react[@repl_sessions[from].name]
328
+ pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
329
+ pids.each do |pid|
330
+ begin
331
+ Process.kill("KILL", pid)
332
+ rescue
333
+ end
334
+ end
335
+ @repl_sessions[from][:collaborators].each do |collaborator|
336
+ @repl_sessions.delete(collaborator)
297
337
  end
338
+ @repl_sessions.delete(from)
298
339
  end
299
- @repl_sessions.delete(from)
300
- when /^\s*-/i
340
+ when /\A\s*-/i
301
341
  #ommit
302
342
  else
303
343
  if @ts_repl[@repl_sessions[from].name].to_s == ''
@@ -28,7 +28,8 @@ class SlackSmartBot
28
28
  respond "Running", dest if code.size > 200
29
29
 
30
30
  begin
31
- code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting
31
+ #todo: check. Commented next line because it was causing problems with the code, for the case the line was for example any of these chars: { } [ ] ( ) < > | & ; $ ` " ' \ * ? ~ # = ! ^ -
32
+ #code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting
32
33
  code.gsub!('$','\$') #to take $ as literal, fex: puts '$lolo' => puts '\$lolo'
33
34
  ruby = "ruby -e \"#{code.gsub('"', '\"')}\""
34
35
  if defined?(project_folder) and project_folder.to_s != "" and Dir.exist?(project_folder)
@@ -36,6 +36,7 @@ require_relative "commands/on_extended/bot_rules"
36
36
  require_relative "commands/on_bot/admin_master/get_bot_logs"
37
37
  require_relative "commands/on_bot/admin_master/send_message"
38
38
  require_relative "commands/on_bot/admin_master/delete_message"
39
+ require_relative "commands/on_bot/admin_master/update_message"
39
40
  require_relative "commands/on_bot/admin_master/react_to"
40
41
  require_relative "commands/on_bot/general/bot_stats"
41
42
  require_relative "commands/on_bot/general/leaderboard"
@@ -63,6 +64,7 @@ require_relative "commands/general/add_team"
63
64
  require_relative "commands/general/add_memo_team"
64
65
  require_relative "commands/general/set_memo_status"
65
66
  require_relative "commands/general/delete_memo_team"
67
+ require_relative "commands/general/see_memos_team"
66
68
  require_relative "commands/general/see_teams"
67
69
  require_relative "commands/general/update_team"
68
70
  require_relative "commands/general/ping_team"
@@ -71,3 +73,5 @@ require_relative "commands/general/add_vacation"
71
73
  require_relative "commands/general/remove_vacation"
72
74
  require_relative "commands/general/see_vacations"
73
75
  require_relative "commands/general/see_vacations_team"
76
+ require_relative "commands/general/public_holidays"
77
+ require_relative "commands/general/set_public_holidays"
@@ -5,7 +5,7 @@ class SlackSmartBot
5
5
  @last_activity_check = Time.now
6
6
  get_bots_created()
7
7
  @buffer_complete = [] unless defined?(@buffer_complete)
8
- b = File.read("#{config.path}/buffer_complete.log")
8
+ b = File.read("#{config.path}/buffer_complete.log", encoding: "UTF-8")
9
9
  result = b.scan(/^\|(\w+)\|(\w+)\|(\w+)\|([^~]+)~~~/m)
10
10
  result.delete(nil)
11
11
  new_messages = result[@buffer_complete.size..-1]