slack-smart-bot 1.12.9 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +85 -12
  3. data/lib/slack/smart-bot/comm/event_hello.rb +3 -1
  4. data/lib/slack/smart-bot/comm/respond.rb +1 -0
  5. data/lib/slack/smart-bot/comm/update.rb +13 -0
  6. data/lib/slack/smart-bot/comm.rb +1 -0
  7. data/lib/slack/smart-bot/commands/general/add_team.rb +1 -0
  8. data/lib/slack/smart-bot/commands/general/add_vacation.rb +5 -0
  9. data/lib/slack/smart-bot/commands/general/allow_access.rb +1 -1
  10. data/lib/slack/smart-bot/commands/general/delete_team.rb +1 -0
  11. data/lib/slack/smart-bot/commands/general/deny_access.rb +1 -1
  12. data/lib/slack/smart-bot/commands/general/public_holidays.rb +144 -0
  13. data/lib/slack/smart-bot/commands/general/see_announcements.rb +2 -2
  14. data/lib/slack/smart-bot/commands/general/see_memos_team.rb +202 -0
  15. data/lib/slack/smart-bot/commands/general/see_teams.rb +3 -175
  16. data/lib/slack/smart-bot/commands/general/see_vacations.rb +41 -21
  17. data/lib/slack/smart-bot/commands/general/set_public_holidays.rb +21 -0
  18. data/lib/slack/smart-bot/commands/general/update_team.rb +1 -0
  19. data/lib/slack/smart-bot/commands/general_bot_commands.rb +100 -8
  20. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +27 -3
  21. data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +12 -8
  22. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +33 -4
  23. data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +22 -1
  24. data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +50 -4
  25. data/lib/slack/smart-bot/commands/on_bot/admin_master/update_message.rb +25 -0
  26. data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +6 -4
  27. data/lib/slack/smart-bot/commands/on_bot/repl.rb +55 -15
  28. data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +2 -1
  29. data/lib/slack/smart-bot/commands.rb +4 -0
  30. data/lib/slack/smart-bot/listen.rb +1 -1
  31. data/lib/slack/smart-bot/process.rb +36 -6
  32. data/lib/slack/smart-bot/process_first.rb +250 -188
  33. data/lib/slack/smart-bot/treat_message.rb +1 -1
  34. data/lib/slack/smart-bot/utils/build_help.rb +1 -1
  35. data/lib/slack/smart-bot/utils/create_routine_thread.rb +45 -5
  36. data/lib/slack/smart-bot/utils/decrypt.rb +15 -0
  37. data/lib/slack/smart-bot/utils/display_calendar.rb +86 -0
  38. data/lib/slack/smart-bot/utils/encrypt.rb +15 -0
  39. data/lib/slack/smart-bot/utils/encryption_get_key_iv.rb +29 -0
  40. data/lib/slack/smart-bot/utils/get_help.rb +1 -1
  41. data/lib/slack/smart-bot/utils/get_team_members.rb +39 -0
  42. data/lib/slack/smart-bot/utils/get_teams.rb +22 -16
  43. data/lib/slack/smart-bot/utils/get_vacations.rb +20 -15
  44. data/lib/slack/smart-bot/utils/save_stats.rb +2 -2
  45. data/lib/slack/smart-bot/utils/update_teams.rb +15 -9
  46. data/lib/slack/smart-bot/utils/update_vacations.rb +5 -3
  47. data/lib/slack/smart-bot/utils.rb +5 -0
  48. data/lib/slack-smart-bot.rb +9 -0
  49. data/lib/slack-smart-bot_general_commands.rb +33 -0
  50. data/lib/slack-smart-bot_general_rules.rb +2 -2
  51. data/whats_new.txt +15 -17
  52. metadata +27 -11
@@ -10,7 +10,7 @@ class SlackSmartBot
10
10
  user_info = @users.select{|u| u.name == @routines[@channel_id][name][:creator]}[-1]
11
11
  @routines[@channel_id][name][:creator_id] = user_info.id unless user_info.nil? or user_info.empty?
12
12
  end
13
- @logger.info "Routine: #{@routines[@channel_id][name].inspect}"
13
+ @logger.info "Routine #{name}: #{@routines[@channel_id][name].inspect}"
14
14
  if @routines[@channel_id][name][:file_path].match?(/\.rb$/i)
15
15
  ruby = "ruby "
16
16
  else
@@ -62,11 +62,12 @@ class SlackSmartBot
62
62
  File.write("#{config.path}/routines/#{@channel_id}/#{name}_output.txt", stdout.to_s+stderr.to_s, mode: "a+")
63
63
  end
64
64
  else #command
65
+ message = nil
65
66
  if !@routines[@channel_id][name][:silent] and !config.on_maintenance
66
67
  if @routines[@channel_id][name][:dest]!=@channel_id
67
- respond "routine from <##{@channel_id}> *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
68
+ message = respond "routine from <##{@channel_id}> *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest], return_message: true
68
69
  else
69
- respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
70
+ message = respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest], return_message: true
70
71
  end
71
72
  end
72
73
  started = Time.now
@@ -78,6 +79,9 @@ class SlackSmartBot
78
79
  routine: true,
79
80
  routine_name: name,
80
81
  routine_type: hroutine[:routine_type] }
82
+ if !message.nil? and (@routines[@channel_id][name][:command].match?(/^!!/) or @routines[@channel_id][name][:command].match?(/^\^/))
83
+ data[:ts] = message.ts
84
+ end
81
85
  treat_message(data)
82
86
  end
83
87
  # in case the routine was deleted while running the process
@@ -95,7 +99,24 @@ class SlackSmartBot
95
99
  require "time"
96
100
  every_in_seconds = Time.parse(@routines[@channel_id][name][:next_run]) - Time.now
97
101
  elsif @routines[@channel_id][name][:at] != "" #coming from start after pause for 'at'
98
- if @routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!=''and
102
+ if @routines[@channel_id][name].key?(:daymonth) and @routines[@channel_id][name][:daymonth].to_s!='' # day of month
103
+ weekly = false
104
+ daymonth = @routines[@channel_id][name][:daymonth]
105
+ day = daymonth.to_i
106
+ if Date.today.day > day
107
+ next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
108
+ else
109
+ next_month = Date.new(Date.today.year, Date.today.month, 1)
110
+ end
111
+ next_month_last_day = Date.new(next_month.year, next_month.month, -1)
112
+ if day > next_month_last_day.day
113
+ next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
114
+ else
115
+ next_time = Date.new(next_month.year, next_month.month, day)
116
+ end
117
+ days = (next_time - Date.today).to_i
118
+ every_in_seconds = Time.parse(@routines[@channel_id][name][:next_run]) - Time.now
119
+ elsif @routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!='' and
99
120
  @routines[@channel_id][name][:dayweek].to_s!='weekend' and @routines[@channel_id][name][:dayweek].to_s!='weekday'
100
121
 
101
122
  day = @routines[@channel_id][name][:dayweek]
@@ -126,10 +147,25 @@ class SlackSmartBot
126
147
  if started.strftime("%H:%M:%S") < @routines[@channel_id][name][:at] and days == 0
127
148
  nt = @routines[@channel_id][name][:at].split(":")
128
149
  next_run = Time.new(started.year, started.month, started.day, nt[0], nt[1], nt[2])
129
- else
150
+ else
130
151
  if days == 0 and started.strftime("%H:%M:%S") >= @routines[@channel_id][name][:at]
131
152
  if weekly
132
153
  days = 7
154
+ elsif @routines[@channel_id][name].key?(:daymonth) and @routines[@channel_id][name][:daymonth].to_s!=''
155
+ daymonth = @routines[@channel_id][name][:daymonth]
156
+ day = daymonth.to_i
157
+ if Date.today.day >= day
158
+ next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
159
+ else
160
+ next_month = Date.new(Date.today.year, Date.today.month, 1)
161
+ end
162
+ next_month_last_day = Date.new(next_month.year, next_month.month, -1)
163
+ if day > next_month_last_day.day
164
+ next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
165
+ else
166
+ next_time = Date.new(next_month.year, next_month.month, day)
167
+ end
168
+ days = (next_time - Date.today).to_i
133
169
  else
134
170
  days = 1
135
171
  end
@@ -152,6 +188,10 @@ class SlackSmartBot
152
188
  update_routines()
153
189
  sleep(@routines[@channel_id][name][:sleeping]) unless elapsed > every_in_seconds
154
190
  else
191
+ if !@routines[@channel_id][name][:running]
192
+ @routines[@channel_id][name][:running] = true
193
+ update_routines()
194
+ end
155
195
  sleep 30
156
196
  end
157
197
  end
@@ -0,0 +1,15 @@
1
+ class SlackSmartBot
2
+ def decrypt(data)
3
+ require "openssl"
4
+ require "base64"
5
+
6
+ key, iv = encryption_get_key_iv()
7
+ encrypted = Base64.decode64(data)
8
+ cipher = OpenSSL::Cipher.new("AES-256-CBC")
9
+ cipher.decrypt
10
+ cipher.key = key
11
+ cipher.iv = iv
12
+ plain = cipher.update(encrypted) + cipher.final
13
+ return plain
14
+ end
15
+ end
@@ -0,0 +1,86 @@
1
+ class SlackSmartBot
2
+ def display_calendar(from_user_name, year)
3
+ if @vacations.key?(from_user_name) and @vacations[from_user_name][:public_holidays].to_s != ""
4
+ country_region = @vacations[from_user_name][:public_holidays].downcase
5
+ elsif config[:public_holidays].key?(:default_calendar)
6
+ country_region = config[:public_holidays][:default_calendar].downcase
7
+ else
8
+ country_region = ""
9
+ end
10
+ country, location = country_region.split("/")
11
+ public_holidays(country.to_s, location.to_s, year, "", "", add_stats: false, publish_results: false)
12
+ messages = ["*Time off #{year}*"]
13
+ days_of_vacations = 0
14
+ (1..12).each do |m|
15
+ date = Date.parse("#{year}/#{m}/1")
16
+ month_name = date.strftime("%B")
17
+ month_line = ""
18
+ (1..6).each do |w|
19
+ if date.month == m
20
+ month_line += "#{date.strftime("%d")} "
21
+ else
22
+ month_line += ":white_small_square: "
23
+ end
24
+
25
+ if @public_holidays.key?(country_region) and @public_holidays[country_region].key?(year.to_s)
26
+ phd = @public_holidays[country_region][year.to_s].date.iso
27
+ else
28
+ phd = []
29
+ end
30
+ (1..7).each do |d|
31
+ wday = date.wday
32
+ wday = 7 if wday == 0
33
+ break if d >= 3 and w == 6 # week 6 cannot be more than wednesday
34
+ date_text = date.strftime("%Y-%m-%d")
35
+ if wday == d and date.month == m
36
+ vacations_set = false
37
+ public_holiday_set = false
38
+ if phd.include?(date_text)
39
+ month_line += ":large_red_square: "
40
+ public_holiday_set = true
41
+ end
42
+ if !public_holiday_set
43
+ if @vacations.key?(from_user_name) and @vacations[from_user_name].key?(:periods)
44
+ @vacations[from_user_name][:periods].each do |period|
45
+ if date >= Date.parse(period[:from]) and date <= Date.parse(period[:to])
46
+ if period[:type] == "sick"
47
+ month_line += ":face_with_thermometer: "
48
+ elsif period[:type] == "sick child"
49
+ month_line += ":baby: "
50
+ elsif period[:type] == "vacation"
51
+ month_line += ":palm_tree: "
52
+ if wday <= 5
53
+ days_of_vacations += 1
54
+ end
55
+ end
56
+ vacations_set = true
57
+ break
58
+ end
59
+ end
60
+ end
61
+ if !vacations_set
62
+ if wday == 6 || wday == 7
63
+ month_line += ":large_yellow_square: "
64
+ else
65
+ month_line += ":white_square: "
66
+ end
67
+ end
68
+ end
69
+ date += 1
70
+ else
71
+ month_line += ":white_small_square: "
72
+ end
73
+ end
74
+ end
75
+ messages << "#{month_line} #{month_name}\n"
76
+ end
77
+ messages << "\n\n:large_yellow_square: weekend / :white_square: weekday / :white_small_square: not in month / :large_red_square: Public Holiday / :palm_tree: Vacation / :face_with_thermometer: Sick / :baby: Sick child"
78
+ if country_region != ""
79
+ messages << "Your public holidays are set for #{country_region.downcase}. Call `set public holidays to COUNTRY/REGION` if you want to change it.\n"
80
+ else
81
+ messages << "Your public holidays are not set. Call `set public holidays to COUNTRY/REGION` to set it.\n"
82
+ end
83
+ messages << "You have spent #{days_of_vacations} days of vacations in #{year} considering only weekdays.\n\n"
84
+ respond messages.join("\n")
85
+ end
86
+ end
@@ -0,0 +1,15 @@
1
+ class SlackSmartBot
2
+ def encrypt(data)
3
+ require "openssl"
4
+ require "base64"
5
+
6
+ key, iv = encryption_get_key_iv()
7
+ cipher = OpenSSL::Cipher::Cipher.new "AES-256-CBC"
8
+ cipher.encrypt
9
+ cipher.key = key
10
+ cipher.iv = iv
11
+ encrypted = cipher.update(data) + cipher.final
12
+ encrypted = Base64.encode64(encrypted)
13
+ return encrypted
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ class SlackSmartBot
2
+ def encryption_get_key_iv
3
+ if defined?(@encryption_key_built)
4
+ key = @encryption_key_built
5
+ iv = @encryption_iv_built
6
+ else
7
+ if config.key?(:encryption) and config.encryption.key?(:key) and config.encryption.key?(:iv)
8
+ key = config[:encryption][:key]
9
+ iv = config[:encryption][:iv]
10
+ else
11
+ key = (Socket.gethostname + config.token.reverse)[0..49]
12
+ iv = config.token[0..15]
13
+ end
14
+
15
+ #Convert from hex to raw bytes:
16
+ key = [key].pack('H*')
17
+ #Pad with zero bytes to correct length:
18
+ key << ("\x00" * (32 - key.length))
19
+
20
+ #Convert from hex to raw bytes:
21
+ iv = [iv].pack('H*')
22
+ #Pad with zero bytes to correct length:
23
+ iv << ("\x00" * (16 - iv.length))
24
+ @encryption_key_built = key
25
+ @encryption_iv_built = iv
26
+ end
27
+ return key, iv
28
+ end
29
+ end
@@ -4,7 +4,7 @@ class SlackSmartBot
4
4
  general: [:bot_help, :hi_bot, :bye_bot, :add_admin, :remove_admin, :see_admins, :poster, :add_announcement, :delete_announcement,
5
5
  :see_announcements, :see_command_ids, :share_messages, :see_shares, :delete_share, :see_favorite_commands, :see_statuses,
6
6
  :allow_access, :see_access, :deny_access, :add_team, :add_memo_team, :delete_memo_team, :set_memo_status, :see_teams, :update_team, :ping_team, :delete_team,
7
- :add_vacation, :remove_vacation, :see_vacations, :see_vacations_team],
7
+ :see_memos_team, :add_vacation, :remove_vacation, :see_vacations, :see_vacations_team, :public_holidays, :create_loop, :quit_loop],
8
8
  on_bot_general: [:whats_new, :suggest_command, :bot_status, :use_rules, :stop_using_rules, :bot_stats, :leaderboard],
9
9
  on_bot: [:ruby_code, :repl, :get_repl, :run_repl, :delete_repl, :see_repls, :kill_repl, :add_shortcut, :delete_shortcut, :see_shortcuts],
10
10
  on_bot_admin: [:extend_rules, :stop_using_rules_on, :start_bot, :pause_bot, :add_routine,
@@ -0,0 +1,39 @@
1
+ class SlackSmartBot
2
+ def get_team_members(team)
3
+ assigned_members = team.members.values.flatten
4
+ assigned_members.uniq!
5
+ assigned_members.dup.each do |m|
6
+ user_info = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) or u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
7
+ assigned_members.delete(m) if user_info.nil? or user_info.deleted
8
+ end
9
+ channels_members = []
10
+ all_team_members = assigned_members.dup
11
+ if team.channels.key?("members")
12
+ team_members = []
13
+ team.channels["members"].each do |ch|
14
+ get_channels_name_and_id() unless @channels_id.key?(ch)
15
+ tm = get_channel_members(@channels_id[ch])
16
+ if tm.nil?
17
+ respond ":exclamation: Add the Smart Bot to *##{ch}* channel to be able to get the list of members.", dest
18
+ else
19
+ channels_members << @channels_id[ch]
20
+ tm.each do |m|
21
+ user_info = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
22
+ team_members << user_info.name unless user_info.is_app_user or user_info.is_bot
23
+ end
24
+ end
25
+ end
26
+ team_members.flatten!
27
+ team_members.uniq!
28
+ unassigned_members = team_members - assigned_members
29
+ unassigned_members.delete(config.nick)
30
+ not_on_team_channel = assigned_members - team_members
31
+ all_team_members += team_members
32
+ else
33
+ unassigned_members = []
34
+ not_on_team_channel = []
35
+ end
36
+
37
+ return assigned_members, unassigned_members, not_on_team_channel, channels_members, all_team_members
38
+ end
39
+ end
@@ -1,22 +1,28 @@
1
1
  class SlackSmartBot
2
2
  def get_teams
3
3
  @teams ||= {}
4
- teams_file = config.file_path.gsub(".rb", "_teams.yaml")
5
- if File.exist?(teams_file)
6
- if !defined?(@datetime_teams_file) or @datetime_teams_file != File.mtime(teams_file)
7
- require 'yaml'
8
- teams = @teams
9
- 10.times do
10
- teams = YAML.load(File.read(teams_file))
11
- if teams.is_a?(Hash)
12
- break
13
- else
14
- sleep (0.1*(rand(2)+1))
15
- end
16
- end
17
- @teams = teams unless teams.is_a?(FalseClass)
18
- @datetime_teams_file = File.mtime(teams_file)
4
+ old_teams_file = config.file_path.gsub(".rb", "_teams.yaml") #to be backward compatible
5
+ require 'yaml'
6
+ if File.exist?(old_teams_file)
7
+ @logger.info 'Migrating teams to new format'
8
+ teams = YAML.load(File.read(old_teams_file))
9
+ @logger.info "@teams: #{teams.inspect}}"
10
+ teams.each do |key, value|
11
+ File.write(File.join(config.path, "teams", "t_#{key}.yaml"), encrypt(value.to_yaml))
12
+ end
13
+ @logger.info "Deleting old_teams_file: #{old_teams_file}"
14
+ File.delete(old_teams_file)
15
+ end
16
+ files = Dir.glob(File.join(config.path, "teams", "t_*.yaml"))
17
+ @datetime_teams_file ||= {}
18
+ files.each do |file|
19
+ if !defined?(@datetime_teams_file) or !@datetime_teams_file.key?(file) or @datetime_teams_file[file] != File.mtime(file)
20
+ teams_team = YAML.load(decrypt(File.read(file)))
21
+ team_name = File.basename(file).gsub("t_","").gsub(".yaml","")
22
+ teams_team[:name] = team_name unless teams_team.key?(:name) #to be backward compatible
23
+ @teams[team_name.to_sym] = teams_team
24
+ @datetime_teams_file[file] = File.mtime(file)
19
25
  end
20
26
  end
21
27
  end
22
- end
28
+ end
@@ -1,21 +1,26 @@
1
1
  class SlackSmartBot
2
2
  def get_vacations
3
3
  @vacations ||= {}
4
- vacations_file = config.file_path.gsub(".rb", "_vacations.yaml")
5
- if File.exist?(vacations_file)
6
- if !defined?(@datetime_vacations_file) or @datetime_vacations_file != File.mtime(vacations_file)
7
- require 'yaml'
8
- vacations = @vacations
9
- 10.times do
10
- vacations = YAML.load(File.read(vacations_file))
11
- if vacations.is_a?(Hash)
12
- break
13
- else
14
- sleep (0.1*(rand(2)+1))
15
- end
16
- end
17
- @vacations = vacations unless vacations.is_a?(FalseClass)
18
- @datetime_vacations_file = File.mtime(vacations_file)
4
+ old_vacations_file = config.file_path.gsub(".rb", "_vacations.yaml") #to be backward compatible
5
+ require 'yaml'
6
+ if File.exist?(old_vacations_file)
7
+ @logger.info 'Migrating vacations to new format'
8
+ vacations = @vacations
9
+ vacations = YAML.load(File.read(old_vacations_file))
10
+ @vacations = vacations unless vacations.is_a?(FalseClass)
11
+ @vacations.each do |key, value|
12
+ File.write(File.join(config.path, "vacations", "v_#{key}.yaml"), encrypt(value.to_yaml))
13
+ end
14
+ @logger.info "Deleting old_vacations_file: #{old_vacations_file}"
15
+ File.delete(old_vacations_file)
16
+ end
17
+ files = Dir.glob(File.join(config.path, "vacations", "v_*.yaml"))
18
+ @datetime_vacations_file ||= {}
19
+ files.each do |file|
20
+ if !defined?(@datetime_vacations_file) or !@datetime_vacations_file.key?(file) or @datetime_vacations_file[file] != File.mtime(file)
21
+ vacations_user = YAML.load(decrypt(File.read(file)))
22
+ @vacations[File.basename(file).gsub("v_","").gsub(".yaml","")] = vacations_user
23
+ @datetime_vacations_file[file] = File.mtime(file)
19
24
  end
20
25
  end
21
26
  end
@@ -1,6 +1,6 @@
1
1
  class SlackSmartBot
2
- def save_stats(method, data: {})
3
- if has_access?(method, Thread.current[:user])
2
+ def save_stats(method, data: {}, forced: false)
3
+ if has_access?(method, Thread.current[:user]) or forced
4
4
  if config.stats
5
5
  begin
6
6
  require "csv"
@@ -1,16 +1,22 @@
1
1
  class SlackSmartBot
2
2
  def update_teams(team=nil)
3
3
  require 'yaml'
4
- unless team.nil?
4
+ if team.nil?
5
+ teams = @teams.keys
6
+ else
5
7
  get_teams()
6
8
  @teams.merge!(team)
9
+ teams = team.keys
10
+ end
11
+
12
+ teams.each do |team|
13
+ team_file = File.join(config.path, "teams", "t_#{team}.yaml")
14
+ File.open(team_file, 'w') {|file|
15
+ file.flock(File::LOCK_EX)
16
+ file.write(encrypt(@teams[team].to_yaml))
17
+ file.flock(File::LOCK_UN)
18
+ }
19
+ @datetime_teams_file[team_file] = File.mtime(team_file)
7
20
  end
8
- teams_file = config.file_path.gsub(".rb", "_teams.yaml")
9
- File.open(teams_file, 'w') {|file|
10
- file.flock(File::LOCK_EX)
11
- file.write(@teams.to_yaml)
12
- file.flock(File::LOCK_UN)
13
- }
14
- @datetime_teams_file = File.mtime(teams_file)
15
21
  end
16
- end
22
+ end
@@ -5,12 +5,14 @@ class SlackSmartBot
5
5
  get_vacations()
6
6
  @vacations.merge!(vacation)
7
7
  end
8
- vacations_file = config.file_path.gsub(".rb", "_vacations.yaml")
8
+ user = Thread.current[:user]
9
+ vacations_file = File.join(config.path, "vacations", "v_#{user.name}.yaml")
10
+
9
11
  File.open(vacations_file, 'w') {|file|
10
12
  file.flock(File::LOCK_EX)
11
- file.write(@vacations.to_yaml)
13
+ file.write(encrypt(@vacations[user.name].to_yaml))
12
14
  file.flock(File::LOCK_UN)
13
15
  }
14
- @datetime_vacations_file = File.mtime(vacations_file)
16
+ @datetime_vacations_file[vacations_file] = File.mtime(vacations_file)
15
17
  end
16
18
  end
@@ -29,4 +29,9 @@ require_relative 'utils/get_vacations'
29
29
  require_relative 'utils/update_teams'
30
30
  require_relative 'utils/update_vacations'
31
31
  require_relative 'utils/check_vacations'
32
+ require_relative 'utils/display_calendar'
33
+ require_relative 'utils/encryption_get_key_iv'
34
+ require_relative 'utils/encrypt'
35
+ require_relative 'utils/decrypt'
36
+ require_relative 'utils/get_team_members'
32
37
 
@@ -54,6 +54,10 @@ class SlackSmartBot
54
54
  config[:github] = {token: '' } unless config.key?(:github) and config[:github].key?(:token)
55
55
  config[:github][:host] ||= "https://api.github.com"
56
56
  config[:github][:host] = "https://#{config[:github][:host]}" unless config[:github][:host] == '' or config[:github][:host].match?(/^http/)
57
+ config[:public_holidays] = { api_key: '' } unless config.key?(:public_holidays) and config[:public_holidays].key?(:api_key)
58
+ config[:public_holidays][:host] ||= "https://calendarific.com"
59
+ config[:public_holidays][:host] = "https://#{config[:public_holidays][:host]}" unless config[:public_holidays][:host] == '' or config[:public_holidays][:host].match?(/^http/)
60
+
57
61
  if config.path.to_s!='' and config.file.to_s==''
58
62
  config.file = File.basename($0)
59
63
  end
@@ -74,6 +78,8 @@ class SlackSmartBot
74
78
  Dir.mkdir("#{config.path}/announcements") unless Dir.exist?("#{config.path}/announcements")
75
79
  Dir.mkdir("#{config.path}/shares") unless Dir.exist?("#{config.path}/shares")
76
80
  Dir.mkdir("#{config.path}/rules") unless Dir.exist?("#{config.path}/rules")
81
+ Dir.mkdir("#{config.path}/vacations") unless Dir.exist?("#{config.path}/vacations")
82
+ Dir.mkdir("#{config.path}/teams") unless Dir.exist?("#{config.path}/teams")
77
83
  File.delete("#{config.path}/config_tmp.status") if File.exist?("#{config.path}/config_tmp.status")
78
84
 
79
85
  config.masters = MASTER_USERS if config.masters.to_s=='' and defined?(MASTER_USERS)
@@ -202,6 +208,8 @@ class SlackSmartBot
202
208
  @last_status_change = Time.now
203
209
  @vacations_check = (Date.today - 1)
204
210
  @announcements_activity_after = Hash.new()
211
+ @public_holidays = Hash.new()
212
+ @loops = Hash.new()
205
213
 
206
214
  if File.exist?("#{config.path}/shortcuts/#{config.shortcuts_file}".gsub('.yaml','.rb')) #backwards compatible
207
215
  file_conf = IO.readlines("#{config.path}/shortcuts/#{config.shortcuts_file}".gsub('.yaml','.rb')).join
@@ -317,6 +325,7 @@ class SlackSmartBot
317
325
  get_shares()
318
326
  get_admins_channels()
319
327
  get_access_channels()
328
+ get_vacations()
320
329
 
321
330
  if @routines.key?(@channel_id)
322
331
  @routines[@channel_id].each do |k, v|
@@ -19,6 +19,39 @@ def general_commands(user, command, dest, files = [])
19
19
  lines = 200 if lines > 200
20
20
  respond (">#{"\n"*lines}<")
21
21
 
22
+ # help: ----------------------------------------------
23
+ # help: `blink TEXT`
24
+ # help: `INTEGER blink TEXT`
25
+ # help: It will blink the text supplied. One or more lines of text. Emoticons or text format are allowed.
26
+ # help: INTEGER (optional): number of times. Default 50. Max 200.
27
+ # help: Examples:
28
+ # help: blink Hello World!
29
+ # help: blink :moneybag: Pay attention! *Sales* are published!
30
+ # help: 100 blink :new: *Party is about to start* :i_love_you_hand_sign:
31
+ # help: command_id: :blink
32
+ # help:
33
+ when /\A\s*(\d+)?\s*blink\s+(.+)\s*\z/im
34
+ save_stats :blink
35
+ num_times = $1.to_s == '' ? 50 : $1.to_i
36
+ text = $2
37
+ @blinking ||= []
38
+ if num_times > 200 or num_times < 1
39
+ respond "The number of times must be between 1 and 200"
40
+ elsif @blinking.include?(user.name)
41
+ respond "I'm already blinking something for you. Please wait until I finish"
42
+ elsif @blinking.size >= 3 # rate limit in theory update can be done only once per second
43
+ respond "I'm already blinking something for too many people. Please wait until I finish at least one of them."
44
+ else
45
+ @blinking << user.name
46
+ msg = respond(text, return_message: true)
47
+ num_times.times do
48
+ sleep 2
49
+ update(dest, msg.ts, ' ')
50
+ sleep 0.5
51
+ update(dest, msg.ts, text)
52
+ end
53
+ @blinking.delete(user.name)
54
+ end
22
55
 
23
56
  # this is a hidden command that it is not listed when calling bot help
24
57
  when /\s*(that's\s+)?(thanks|thank\s+you|I\s+love\s+you|nice|cool)\s+(#{@salutations.join("|")})\s*!*\s*$/i
@@ -8,8 +8,8 @@ def general_rules(user, command, processed, dest, files = [], rules_file = "")
8
8
 
9
9
  # help: ----------------------------------------------
10
10
  # help: `echo SOMETHING`
11
- # help: `INTEGER echo SOMETHING`
12
- # help: repeats SOMETHING. If INTEGER supplied then that number of times.
11
+ # help: `NUMBER echo SOMETHING`
12
+ # help: repeats SOMETHING. If NUMBER supplied then that number of times.
13
13
  # help: Examples:
14
14
  # help: _echo I am the Smart Bot_
15
15
  # help: _100 echo :heart:_
data/whats_new.txt CHANGED
@@ -1,23 +1,21 @@
1
- *Version 1.12.9* Released 2022-Nov-17
1
+ *Version 1.13.0* Released 2023-Mar-01
2
2
 
3
3
  *For General users*
4
- - `run repl` accepts local parameters and precode to be supplied (<https://github.com/MarioRuiz/slack-smart-bot/issues/60|#60>).
5
- - `!!run repl` will respond as soon as any result is released (<https://github.com/MarioRuiz/slack-smart-bot/issues/63|#63>)
6
- - Now you can add notes for the team, even you can specify if those notes are private so only the members of the team can see them, or even personal. You can use different types of notes: memo, note, issue, task, feature, bug. Also you can indicate the topic of the note. To be able to add or delete notes you need to be a member of that team. Example: `add private bug to Moon team SRE : Logs should not be accessible from outside VPN` (<https://github.com/MarioRuiz/slack-smart-bot/issues/64|#64> <https://github.com/MarioRuiz/slack-smart-bot/issues/67|#67>)
7
- - It is possible to add a JIRA JQL to the team memos that will automatically create the memos from the JIRA issues (<https://github.com/MarioRuiz/slack-smart-bot/issues/68|#68>)
8
- - `kill repl RUN_REPL_ID` Will kill a running repl previously executed with `run repl` command. (<https://github.com/MarioRuiz/slack-smart-bot/issues/69|#69>)
9
- - Now you can add a GitHub url to the issues you want to add as memos for teams. (<https://github.com/MarioRuiz/slack-smart-bot/issues/71|#71>)
10
- - It is possible to add the status of a memo on teams feature. (<https://github.com/MarioRuiz/slack-smart-bot/issues/72|#72>)
11
- - Added 'Time off' (Vacation/Sick/Sick Child) management. The SmartBot will automatically set your status on the periods specified. It will be possible to see the 'Time off' plan for a specific team and period. (<https://github.com/MarioRuiz/slack-smart-bot/issues/73|#73>)
12
- - More natural language for 'Time off' command: (<https://github.com/MarioRuiz/slack-smart-bot/issues/80|#80>)
4
+ - It is possible to run any command on a loop: `for 3 times every 5 minutes !ruby puts Time.now` (<https://github.com/MarioRuiz/slack-smart-bot/issues/83|#83>).
5
+ - You can add collaborators to your REPL by sending `add collaborator @USER` to the session. (<https://github.com/MarioRuiz/slack-smart-bot/issues/85|#85>).
6
+ - If using the `monthly` option when calling `bot stats` it will show a graph (<https://github.com/MarioRuiz/slack-smart-bot/issues/86|#86>).
7
+ - New `public holidays COUNTRY/REGION` command (<https://github.com/MarioRuiz/slack-smart-bot/issues/88|#88>)
8
+ - When calling `see my vacations` on a DM will display a calendar of the year with the days off, including public holidays (<https://github.com/MarioRuiz/slack-smart-bot/issues/89|#89>)
9
+ - `set public holidays to COUNTRY/REGION` will set the public holidays for the country and region specified. (<https://github.com/MarioRuiz/slack-smart-bot/issues/90|#90>)
10
+ - `see MEMO_TYPE from TEAM_NAME team` will show the memos of the team. (<https://github.com/MarioRuiz/slack-smart-bot/issues/91|#91>)
13
11
 
14
12
  *For Admin users*
15
- - Bot Stats is now saving the time_zone of the user (<https://github.com/MarioRuiz/slack-smart-bot/issues/66|#66>)
16
- - Announcements won't be published if no activity on channel. (<https://github.com/MarioRuiz/slack-smart-bot/issues/74|#74>)
17
- - Bot Stats is now saving the job title of the user (<https://github.com/MarioRuiz/slack-smart-bot/issues/77|#77>)
18
- - Any member of smartbot-stats channel can use the `bot stats` command on the channel (<https://github.com/MarioRuiz/slack-smart-bot/issues/78|#78>)
19
- - Added options for `bot stats` command: today, yesterday, this week, last week, this year, last year (<https://github.com/MarioRuiz/slack-smart-bot/issues/79|#79>)
20
- - `bot stats HEADER /regexp/` filter results by stats header following the regexp supplied (<https://github.com/MarioRuiz/slack-smart-bot/issues/81|#81>)
13
+ - It is possible to see routines filtering the HEADER supplied using the given REGEXP: `see routines HEADER /REGEXP/` (<https://github.com/MarioRuiz/slack-smart-bot/issues/84|#84>).
14
+ - `send message to users from YYYY/MM/DD to YYYY/MM/DD #CHANNEL COMMAND_ID: MESSAGE` 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 are optional filters. (<https://github.com/MarioRuiz/slack-smart-bot/issues/87|#87>)
15
+ - `update message URL TEXT` It will update the SmartBot message supplied. You can use this command only if you are a Master admin user and if you are in a private conversation with the bot. (<https://github.com/MarioRuiz/slack-smart-bot/issues/33|#33>)
16
+ - Routines now can create threads (<https://github.com/MarioRuiz/slack-smart-bot/issues/36|#36>)
17
+ - `add routine NAME on the DAY_OF_MONTH at TIME COMMAND` (<https://github.com/MarioRuiz/slack-smart-bot/issues/82|#82>)
18
+
21
19
  ------------------------------
22
20
 
23
- *Previous*: <https://github.com/MarioRuiz/slack-smart-bot/blob/b1a368c3342094e886f53d96dc4d12ecd81ab04b/whats_new.txt|1.11.0>
21
+ *Previous*: <https://github.com/MarioRuiz/slack-smart-bot/blob/2b93317d7556ceaeb724494f53cfa9e8907c3e52/whats_new.txt|1.12.9>