slack-smart-bot 1.12.8 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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]