slack-smart-bot 1.10.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +134 -23
  3. data/lib/slack/smart-bot/comm/delete.rb +13 -0
  4. data/lib/slack/smart-bot/comm/dont_understand.rb +2 -2
  5. data/lib/slack/smart-bot/comm/get_channel_members.rb +7 -3
  6. data/lib/slack/smart-bot/comm/get_presence.rb +20 -0
  7. data/lib/slack/smart-bot/comm/get_users.rb +1 -1
  8. data/lib/slack/smart-bot/comm/respond.rb +24 -13
  9. data/lib/slack/smart-bot/comm/send_msg_user.rb +12 -11
  10. data/lib/slack/smart-bot/comm/set_status.rb +21 -0
  11. data/lib/slack/smart-bot/comm.rb +3 -0
  12. data/lib/slack/smart-bot/commands/general/add_admin.rb +51 -0
  13. data/lib/slack/smart-bot/commands/general/add_announcement.rb +1 -1
  14. data/lib/slack/smart-bot/commands/general/add_memo_team.rb +117 -0
  15. data/lib/slack/smart-bot/commands/general/add_team.rb +80 -0
  16. data/lib/slack/smart-bot/commands/general/add_vacation.rb +51 -0
  17. data/lib/slack/smart-bot/commands/general/allow_access.rb +67 -0
  18. data/lib/slack/smart-bot/commands/general/bot_help.rb +20 -11
  19. data/lib/slack/smart-bot/commands/general/delete_announcement.rb +1 -1
  20. data/lib/slack/smart-bot/commands/general/delete_memo_team.rb +69 -0
  21. data/lib/slack/smart-bot/commands/general/delete_share.rb +1 -1
  22. data/lib/slack/smart-bot/commands/general/delete_team.rb +54 -0
  23. data/lib/slack/smart-bot/commands/general/deny_access.rb +36 -0
  24. data/lib/slack/smart-bot/commands/general/ping_team.rb +100 -0
  25. data/lib/slack/smart-bot/commands/general/poster.rb +116 -0
  26. data/lib/slack/smart-bot/commands/general/remove_admin.rb +58 -0
  27. data/lib/slack/smart-bot/commands/general/remove_vacation.rb +27 -0
  28. data/lib/slack/smart-bot/commands/general/see_access.rb +24 -0
  29. data/lib/slack/smart-bot/commands/general/see_admins.rb +33 -0
  30. data/lib/slack/smart-bot/commands/general/see_announcements.rb +7 -5
  31. data/lib/slack/smart-bot/commands/general/see_command_ids.rb +29 -0
  32. data/lib/slack/smart-bot/commands/general/see_favorite_commands.rb +3 -4
  33. data/lib/slack/smart-bot/commands/general/see_statuses.rb +34 -21
  34. data/lib/slack/smart-bot/commands/general/see_teams.rb +402 -0
  35. data/lib/slack/smart-bot/commands/general/see_vacations.rb +58 -0
  36. data/lib/slack/smart-bot/commands/general/see_vacations_team.rb +136 -0
  37. data/lib/slack/smart-bot/commands/general/set_memo_status.rb +58 -0
  38. data/lib/slack/smart-bot/commands/general/share_messages.rb +1 -1
  39. data/lib/slack/smart-bot/commands/general/update_team.rb +130 -0
  40. data/lib/slack/smart-bot/commands/general_bot_commands.rb +442 -13
  41. data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +2 -1
  42. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +2 -1
  43. data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +2 -1
  44. data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +2 -1
  45. data/lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb +2 -1
  46. data/lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb +2 -1
  47. data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +3 -2
  48. data/lib/slack/smart-bot/commands/on_bot/admin/see_result_routine.rb +2 -1
  49. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +10 -9
  50. data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +2 -1
  51. data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +2 -1
  52. data/lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb +2 -1
  53. data/lib/slack/smart-bot/commands/on_bot/admin_master/delete_message.rb +25 -0
  54. data/lib/slack/smart-bot/commands/on_bot/admin_master/get_bot_logs.rb +1 -0
  55. data/lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb +3 -1
  56. data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +15 -2
  57. data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +2 -1
  58. data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +5 -4
  59. data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +416 -0
  60. data/lib/slack/smart-bot/commands/{general → on_bot/general}/bot_status.rb +1 -0
  61. data/lib/slack/smart-bot/commands/{general → on_bot/general}/leaderboard.rb +1 -0
  62. data/lib/slack/smart-bot/commands/{general → on_bot/general}/stop_using_rules.rb +1 -0
  63. data/lib/slack/smart-bot/commands/{general → on_bot/general}/suggest_command.rb +6 -0
  64. data/lib/slack/smart-bot/commands/{general → on_bot/general}/use_rules.rb +1 -0
  65. data/lib/slack/smart-bot/commands/{general → on_bot/general}/whats_new.rb +2 -1
  66. data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +2 -1
  67. data/lib/slack/smart-bot/commands/on_bot/kill_repl.rb +32 -0
  68. data/lib/slack/smart-bot/commands/on_bot/repl.rb +73 -15
  69. data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +1 -0
  70. data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +117 -28
  71. data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +2 -1
  72. data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +3 -2
  73. data/lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb +5 -4
  74. data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +3 -2
  75. data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +2 -1
  76. data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +6 -3
  77. data/lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb +2 -1
  78. data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +2 -1
  79. data/lib/slack/smart-bot/commands/on_master/create_bot.rb +1 -0
  80. data/lib/slack/smart-bot/commands/on_master/where_smartbot.rb +41 -0
  81. data/lib/slack/smart-bot/commands.rb +30 -7
  82. data/lib/slack/smart-bot/listen.rb +30 -30
  83. data/lib/slack/smart-bot/process.rb +53 -23
  84. data/lib/slack/smart-bot/process_first.rb +2 -2
  85. data/lib/slack/smart-bot/treat_message.rb +23 -17
  86. data/lib/slack/smart-bot/utils/build_help.rb +1 -1
  87. data/lib/slack/smart-bot/utils/check_vacations.rb +43 -0
  88. data/lib/slack/smart-bot/utils/create_routine_thread.rb +1 -1
  89. data/lib/slack/smart-bot/utils/get_access_channels.rb +13 -0
  90. data/lib/slack/smart-bot/utils/get_admins_channels.rb +33 -0
  91. data/lib/slack/smart-bot/utils/get_bots_created.rb +27 -10
  92. data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +7 -2
  93. data/lib/slack/smart-bot/utils/get_command_ids.rb +84 -0
  94. data/lib/slack/smart-bot/utils/get_help.rb +36 -19
  95. data/lib/slack/smart-bot/utils/get_repls.rb +22 -2
  96. data/lib/slack/smart-bot/utils/get_routines.rb +22 -2
  97. data/lib/slack/smart-bot/utils/get_teams.rb +22 -0
  98. data/lib/slack/smart-bot/utils/get_vacations.rb +22 -0
  99. data/lib/slack/smart-bot/utils/has_access.rb +25 -9
  100. data/lib/slack/smart-bot/utils/is_admin.rb +27 -0
  101. data/lib/slack/smart-bot/utils/save_stats.rb +52 -42
  102. data/lib/slack/smart-bot/utils/save_status.rb +22 -7
  103. data/lib/slack/smart-bot/utils/update_access_channels.rb +8 -0
  104. data/lib/slack/smart-bot/utils/update_admins_channels.rb +25 -0
  105. data/lib/slack/smart-bot/utils/update_bots_file.rb +28 -7
  106. data/lib/slack/smart-bot/utils/update_repls.rb +7 -4
  107. data/lib/slack/smart-bot/utils/update_routines.rb +9 -3
  108. data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +13 -6
  109. data/lib/slack/smart-bot/utils/update_teams.rb +16 -0
  110. data/lib/slack/smart-bot/utils/update_vacations.rb +16 -0
  111. data/lib/slack/smart-bot/utils.rb +11 -0
  112. data/lib/slack-smart-bot.rb +50 -12
  113. data/lib/slack-smart-bot_general_commands.rb +16 -1
  114. data/whats_new.txt +12 -30
  115. metadata +78 -21
  116. data/lib/slack/smart-bot/commands/general/bot_stats.rb +0 -314
@@ -0,0 +1,402 @@
1
+ class SlackSmartBot
2
+ def see_teams(user, team_name, search = "", add_stats: true)
3
+ save_stats(__method__) if add_stats
4
+
5
+ get_teams()
6
+ teams = @teams.deep_copy
7
+ if teams.empty?
8
+ respond "There are no teams added yet. Use `add team` command to add a team."
9
+ elsif team_name.to_s != "" and !teams.key?(team_name.to_sym) and (teams.keys.select { |t| (t.to_s.gsub("-", "").gsub("_", "") == team_name.to_s) }).empty?
10
+ respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
11
+ else
12
+ users_link = (Thread.current[:dest][0] == "D")
13
+ filter = (search != "")
14
+ react :runner
15
+ @users = get_users() if add_stats
16
+
17
+ messages = []
18
+ search_members = []
19
+ search_channels = []
20
+ search_info = []
21
+ if filter
22
+ search.split(" ").each do |s|
23
+ if s.match(/<@(\w+)>/i)
24
+ m = $1
25
+ user_info = @users.select { |u| u.id.downcase == m.downcase or (u.key?(:enterprise_user) and u.enterprise_user.id.downcase == m.downcase) }[-1]
26
+ search_members << user_info.name unless user_info.nil?
27
+ elsif s.match(/<#(\w+)\|[^>]*>/i)
28
+ c = $1.upcase
29
+ search_channels << @channels_name[c] if @channels_name.key?(c)
30
+ else
31
+ search_info << s
32
+ end
33
+ end
34
+ end
35
+ if team_name.to_s == "" and search.to_s == ""
36
+ dest = :on_thread
37
+ messages.unshift("Since there are many lines returned the results are returned on a thread by default.")
38
+ else
39
+ dest = Thread.current[:dest]
40
+ end
41
+
42
+ teams.each do |name, team|
43
+ filter ? add = false : add = true
44
+ if team_name.to_s == "" or (team_name.to_s == name.to_s) or (name.to_s.gsub("-", "").gsub("_", "") == team_name.to_s)
45
+ message = []
46
+ message << "*#{name.capitalize}*"
47
+
48
+ if filter and search_info.size > 0
49
+ all_info = true
50
+ search_info.each do |s|
51
+ if (team.members.keys.find { |e| /#{s}/i =~ e })
52
+ add = true
53
+ break
54
+ end
55
+ if !name.match?(/#{s}/i)
56
+ all_info = false
57
+ break
58
+ end
59
+ end
60
+ add = true if all_info
61
+ end
62
+
63
+ message << " > *_members_*"
64
+
65
+ assigned_members = team.members.values.flatten
66
+ assigned_members.uniq!
67
+ assigned_members.dup.each do |m|
68
+ 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]
69
+ assigned_members.delete(m) if user_info.nil? or user_info.deleted
70
+ end
71
+ channels_members = []
72
+ all_team_members = assigned_members.dup
73
+ if team.channels.key?("members")
74
+ team_members = []
75
+ team.channels["members"].each do |ch|
76
+ get_channels_name_and_id() unless @channels_id.key?(ch)
77
+ tm = get_channel_members(@channels_id[ch])
78
+ if tm.nil?
79
+ respond ":exclamation: Add the Smart Bot to *##{ch}* channel to be able to get the list of members.", dest
80
+ else
81
+ channels_members << @channels_id[ch]
82
+ tm.each do |m|
83
+ user_info = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
84
+ team_members << user_info.name unless user_info.is_app_user or user_info.is_bot
85
+ end
86
+ end
87
+ end
88
+ team_members.flatten!
89
+ team_members.uniq!
90
+ unassigned_members = team_members - assigned_members
91
+ unassigned_members.delete(config.nick)
92
+ not_on_team_channel = assigned_members - team_members
93
+ all_team_members += team_members
94
+ else
95
+ unassigned_members = []
96
+ not_on_team_channel = []
97
+ end
98
+ unless unassigned_members.empty?
99
+ um = unassigned_members.dup
100
+ um.each do |m|
101
+ user_info = @users.select { |u| u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
102
+ unless user_info.nil? or user_info.profile.title.to_s == ""
103
+ team.members[user_info.profile.title.to_snake_case] ||= []
104
+ team.members[user_info.profile.title.to_snake_case] << m
105
+ unassigned_members.delete(m)
106
+ end
107
+ end
108
+ unless unassigned_members.empty?
109
+ team.members["unassigned"] ||= []
110
+ team.members["unassigned"] += unassigned_members
111
+ team.members["unassigned"].sort!
112
+ end
113
+ end
114
+ unless not_on_team_channel.empty?
115
+ team.members["not on members channel"] = not_on_team_channel
116
+ team.members["not on members channel"].sort!
117
+ end
118
+ add = true if (team.members.values.flatten & search_members).size > 0
119
+ add = true if (team.channels.values.flatten & search_channels).size > 0
120
+ if filter and search_info.size > 0
121
+ all_info = true
122
+ search_info.each do |s|
123
+ if (team.members.keys.find { |e| /#{s}/i =~ e })
124
+ add = true
125
+ break
126
+ end
127
+ if !team.info.match?(/#{s}/i)
128
+ all_info = false
129
+ break
130
+ end
131
+ end
132
+ add = true if all_info
133
+ end
134
+
135
+ if add
136
+ if team_name.to_s != ""
137
+ team.members.each do |type, members|
138
+ message << " _`#{type}`_: "
139
+ members.each do |member|
140
+ types = [":palm_tree:", ":spiral_calendar_pad:", ":face_with_thermometer:", ":baby:"]
141
+ member_info = @users.select { |u| u.name == member }[-1]
142
+ if !member_info.nil? and !member_info.deleted
143
+ member_id = member_info.id
144
+ info = get_user_info(member_id)
145
+ emoji = info.user.profile.status_emoji
146
+ if types.include?(emoji)
147
+ status = emoji
148
+ else
149
+ active = (get_presence(member_id).presence.to_s == "active")
150
+ if active
151
+ user_info = @users.select { |u| u.id == member_id or (u.key?(:enterprise_user) and u.enterprise_user.name == member_id) }[-1]
152
+ if (user_info.tz_offset - user.tz_offset).abs <= (4 * 3600)
153
+ status = ":large_green_circle:"
154
+ else
155
+ status = ":large_yellow_circle:"
156
+ end
157
+ else
158
+ status = ":white_circle:"
159
+ end
160
+ end
161
+ else
162
+ status = ":exclamation:"
163
+ end
164
+ unless status == ":exclamation:"
165
+ if users_link
166
+ message[-1] += " #{status}<@#{member}>, "
167
+ else
168
+ user_info = @users.select { |u| u.name == member or (u.key?(:enterprise_user) and u.enterprise_user.name == member) }[-1]
169
+ unless user_info.nil?
170
+ if user_info.profile.display_name == ""
171
+ name = user_info.name
172
+ else
173
+ name = user_info.profile.display_name
174
+ end
175
+ message[-1] += " #{status} #{name}, "
176
+ end
177
+ end
178
+ end
179
+ end
180
+ message[-1].chop!
181
+ message[-1].chop!
182
+ end
183
+ else
184
+ team.members.each do |type, members|
185
+ if users_link
186
+ message << " _`#{type}`_: <@#{members.join(">, <@")}>"
187
+ else
188
+ membersn = []
189
+ members.each do |m|
190
+ user_info = @users.select { |u| u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
191
+ unless user_info.nil? or user_info.deleted
192
+ if user_info.profile.display_name == ""
193
+ name = user_info.name
194
+ else
195
+ name = user_info.profile.display_name
196
+ end
197
+ membersn << name
198
+ end
199
+ end
200
+ message << " _`#{type}`_: #{membersn.join(" / ")}"
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ if add
207
+ message << " > *_channels_*"
208
+ team.channels.each do |type, channels|
209
+ channel_ids = []
210
+ channels.each do |ch|
211
+ channel_info = @channels_list.select { |c| c.name.to_s.downcase == ch.to_s.downcase }[-1]
212
+ if @channels_id.key?(ch) and (!channel_info.is_private or (channel_info.is_private and (team.members.values + [team.creator]).flatten.include?(user.name)))
213
+ channel_ids << @channels_id[ch]
214
+ end
215
+ end
216
+ message << " _`#{type}`_: <##{channel_ids.join("> <#")}>" unless channel_ids.empty?
217
+ end
218
+
219
+ unless !team.key?(:memos) or team.memos.empty? or (team_name.to_s == "" and search.to_s == "")
220
+ all_memos = {}
221
+ team.memos.each do |memo|
222
+ if memo.privacy.empty? or
223
+ (memo.privacy == "private" and (all_team_members.include?(user.name) and (users_link or channels_members.include?(Thread.current[:dest])))) or
224
+ (memo.privacy == "personal" and memo.user == user.name and users_link)
225
+ if memo.type == "jira" and config.jira.host != ""
226
+ http = NiceHttp.new(config.jira.host)
227
+ http.headers.authorization = NiceHttpUtils.basic_authentication(user: config.jira.user, password: config.jira.password)
228
+ if memo.message.match?(/^\w+\-\d+$/)
229
+ resp = http.get("/rest/api/latest/issue/#{memo.message}")
230
+ issues = [resp.data.json] if resp.code == 200
231
+ else
232
+ resp = http.get("/rest/api/latest/search/?jql=#{memo.message}")
233
+ issues = resp.data.json().issues if resp.code == 200
234
+ end
235
+ if resp.code == 200
236
+ unless issues.empty?
237
+ issues.each do |issue|
238
+ jira_memo = { jira: true, github: false, status: "", memo_id: memo.memo_id, topic: memo.topic, privacy: memo.privacy, user: memo.user, date: memo.date, message: "", type: memo.type }
239
+ jira_memo.message = issue.fields.summary
240
+ jira_memo.user = issue.fields.reporter.name
241
+ jira_memo.date = issue.fields.created
242
+ if memo.topic == :no_topic and !issue.fields.labels.empty?
243
+ jira_memo.topic = issue.fields.labels.sort.join("_").split(" ").join("_")
244
+ end
245
+ case issue.fields.issuetype.name
246
+ when "Story"; jira_memo.type = ":abc:"
247
+ when "Bug"; jira_memo.type = ":bug:"
248
+ when "Task"; jira_memo.type = ":clock1:"
249
+ when "New Feature", "Improvement"; jira_memo.type = ":sunny:"
250
+ else jira_memo.type = ":memo:"
251
+ end
252
+ case issue.fields.status.statusCategory.name
253
+ when "Done"; jira_memo.status = ":heavy_check_mark:"
254
+ when "To Do"; jira_memo.status = ":new:"
255
+ when "In Progress"; jira_memo.status = ":runner:"
256
+ else jira_memo.status = ":heavy_minus_sign:"
257
+ end
258
+ #todo: check if possible to add link to status instead of jira issue
259
+ #jira_memo.status = " <#{config.jira.host}/browse/#{issue[:key]}|#{jira_memo.status}> "
260
+ jira_memo.status += " <#{config.jira.host}/browse/#{issue[:key]}|#{issue[:key]}>"
261
+
262
+ all_memos[jira_memo.topic] ||= []
263
+ all_memos[jira_memo.topic] << jira_memo
264
+ end
265
+ end
266
+ end
267
+ http.close
268
+ elsif memo.type == "github" and config.github.host != ""
269
+ http = NiceHttp.new(config.github.host)
270
+ http.headers.authorization = "token #{config.github.token}"
271
+ memo.message+="?" unless memo.message.include?('?')
272
+ memo.message+="&per_page=50"
273
+
274
+ resp = http.get("/repos/#{memo.message}")
275
+ issues = resp.data.json()
276
+ issues = [issues] unless issues.is_a?(Array)
277
+ if resp.code == 200
278
+ unless issues.empty?
279
+ issues.each do |issue|
280
+ github_memo = { jira: false, github: true, status: "", memo_id: memo.memo_id, topic: memo.topic, privacy: memo.privacy, user: memo.user, date: memo.date, message: "", type: memo.type }
281
+ github_memo.message = issue.title
282
+ github_memo.user = issue.user.login
283
+ github_memo.date = issue.created_at
284
+ if issue.labels.empty?
285
+ labels = ''
286
+ else
287
+ labels = issue.labels.name.sort.join("_").split(" ").join("_")
288
+ end
289
+ if memo.topic == :no_topic and !issue.labels.empty?
290
+ github_memo.topic = labels
291
+ end
292
+ case labels
293
+ when /bug/i; github_memo.type = ":bug:"
294
+ when /docum/i; github_memo.type = ":abc:"
295
+ when /task/i; github_memo.type = ":clock1:"
296
+ when /enhancem/i, /improvement/i; github_memo.type = ":sunny:"
297
+ else github_memo.type = ":memo:"
298
+ end
299
+ if issue.key?(:events_url)
300
+ resp_events = http.get(issue.events_url)
301
+ events = resp_events.data.json(:event)
302
+ issue.state = "in progress" if events.include?('referenced')
303
+ end
304
+ case issue.state
305
+ when "closed"; github_memo.status = ":heavy_check_mark:"
306
+ when "open"; github_memo.status = ":new:"
307
+ when "in progress"; github_memo.status = ":runner:"
308
+ else github_memo.status = ":heavy_minus_sign:"
309
+ end
310
+ #todo: check if possible to add link to status instead of github issue
311
+ github_memo.status += " <#{issue.html_url}|##{issue.number}>"
312
+
313
+ all_memos[github_memo.topic] ||= []
314
+ all_memos[github_memo.topic] << github_memo
315
+ end
316
+ end
317
+ end
318
+ http.close
319
+ else
320
+ memo.jira = false
321
+ memo.github = false
322
+ all_memos[memo.topic] ||= []
323
+ case memo.type
324
+ when "memo"; memo.type = ":memo:"
325
+ when "note"; memo.type = ":abc:"
326
+ when "bug"; memo.type = ":bug:"
327
+ when "task"; memo.type = ":clock1:"
328
+ when "feature"; memo.type = ":sunny:"
329
+ when "issue"; memo.type = ":hammer:"
330
+ else memo.type = ":heavy_minus_sign:"
331
+ end
332
+ all_memos[memo.topic] << memo
333
+ end
334
+ end
335
+ end
336
+ message << " > *_memos_*" unless all_memos.empty?
337
+
338
+ if all_memos.key?(:no_topic)
339
+ all_memos[:no_topic].sort_by { |memo| memo[:date] }.each do |memo|
340
+ case memo.privacy
341
+ when "private"; priv = " `private`"
342
+ when "personal"; priv = " `personal`"
343
+ else priv = ""
344
+ end
345
+ message << " #{memo.type} #{memo.date.gsub("-", "/")[0..9]}: #{memo.status} #{memo.message} (#{memo.user} #{memo.memo_id})#{priv}"
346
+ end
347
+ end
348
+ all_memos[:no_topic] = []
349
+ all_memos.each do |topic, mems|
350
+ unless mems.empty?
351
+ message << " _`#{topic}`_:"
352
+ mems.sort_by { |m| m[:date] }.each do |memo|
353
+ case memo.privacy
354
+ when "private"; priv = " `private`"
355
+ when "personal"; priv = " `personal`"
356
+ else priv = ""
357
+ end
358
+ message << " #{memo.type} #{memo.date.gsub("-", "/")[0..9]}: #{memo.status} #{memo.message} (#{memo.user} #{memo.memo_id})#{priv}"
359
+ end
360
+ end
361
+ end
362
+ end
363
+
364
+ unless team.info.empty?
365
+ team.info.split("\n").each do |m|
366
+ message << ">#{m}"
367
+ end
368
+ message << "> "
369
+ message << "> "
370
+ end
371
+ messages << message.join("\n")
372
+ end
373
+ end
374
+ end
375
+ unreact :runner
376
+ if messages.empty?
377
+ if filter
378
+ respond "It seems like we didn't find any team with the criteria supplied. Call `see teams` for a full list of teams."
379
+ else
380
+ respond "It seems like there are no teams added.\nUse `add team TEAM_NAME PROPERTIES` to add one. Call `bot help add team` for extended info."
381
+ end
382
+ else
383
+ if team_name.to_s != ""
384
+ message = "\n\n:palm_tree: On vacation / "
385
+ message += ":spiral_calendar_pad: In a meeting / "
386
+ message += ":face_with_thermometer: :baby: Sick leave / "
387
+ message += ":white_circle: Away / "
388
+ message += ":large_yellow_circle: Available in remote timezone / "
389
+ message += ":large_green_circle: Available"
390
+ messages[-1] << message
391
+ messages[-1] << "\n:information_source: Remote Time zone is >4h away from your current (#{user.tz_label})"
392
+ end
393
+ messages.each do |msg|
394
+ respond msg, dest, unfurl_links: false, unfurl_media: false
395
+ end
396
+ unless team_name.to_s.empty?
397
+ see_vacations_team(user, team_name, Date.today.strftime("%Y/%m/%d"), add_stats: false)
398
+ end
399
+ end
400
+ end
401
+ end
402
+ end
@@ -0,0 +1,58 @@
1
+ class SlackSmartBot
2
+ def see_vacations(user, from_user: '', add_stats: true)
3
+ save_stats(__method__) if add_stats
4
+
5
+ get_vacations()
6
+
7
+ from_user_name = ''
8
+
9
+ if from_user.empty?
10
+ from_user_name = user.name
11
+ else
12
+ @users = get_users() if @users.empty?
13
+ user_info = @users.select{|u| u.id == from_user or (u.key?(:enterprise_user) and u.enterprise_user.id == from_user)}[-1]
14
+ from_user_name = user_info.name
15
+ end
16
+
17
+ if !@vacations.key?(from_user_name) or @vacations[from_user_name].periods.empty?
18
+ if from_user.empty?
19
+ respond "You didn't add any time off yet. Use `add vacation from YYYY/MM/DD to YYYY/MM/DD`"
20
+ else
21
+ respond "No time off added yet for <@#{from_user}>"
22
+ end
23
+ else
24
+ messages = []
25
+ messages << "*Time off <@#{from_user}>*" if !from_user.empty?
26
+ today = Date.today.strftime("%Y/%m/%d")
27
+ current_added = false
28
+ past_added = false
29
+ @vacations[from_user_name].periods.sort_by { |v| v[:from]}.reverse.each do |vac|
30
+ if !current_added and vac.to >= today
31
+ messages << "*Current and future periods*"
32
+ current_added = true
33
+ end
34
+ if !past_added and vac.to < today and from_user.empty?
35
+ messages << "\n*Past periods*"
36
+ past_added = true
37
+ end
38
+ unless !from_user.empty? and vac.to < today
39
+ if !from_user.empty?
40
+ icon = ":beach_with_umbrella:"
41
+ elsif vac.type == 'vacation'
42
+ icon = ':palm_tree:'
43
+ elsif vac.type == 'sick'
44
+ icon = ':face_with_thermometer:'
45
+ elsif vac.type == 'sick child'
46
+ icon = ':baby:'
47
+ end
48
+ if vac.from == vac.to
49
+ messages << " #{icon} #{vac.from} ##{vac.vacation_id}"
50
+ else
51
+ messages << " #{icon} #{vac.from} -> #{vac.to} ##{vac.vacation_id}"
52
+ end
53
+ end
54
+ end
55
+ respond messages.join("\n")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,136 @@
1
+ class SlackSmartBot
2
+ def see_vacations_team(user, team_name, date, add_stats: true)
3
+ save_stats(__method__) if add_stats
4
+
5
+ get_teams()
6
+ teams = @teams.deep_copy
7
+ if teams.empty?
8
+ respond "There are no teams added yet. Use `add team` command to add a team."
9
+ elsif team_name.to_s != "" and !teams.key?(team_name.to_sym) and (teams.keys.select { |t| (t.to_s.gsub("-", "").gsub("_", "") == team_name.to_s) }).empty?
10
+ respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
11
+ else
12
+ teams.each do |name, team|
13
+ if team_name==name.to_s or (name.to_s.gsub("-", "").gsub("_", "") == team_name.to_s)
14
+ team_name = name.to_s
15
+ break
16
+ end
17
+ end
18
+ date.gsub!('-','/')
19
+ get_vacations()
20
+ team = teams[team_name.to_sym]
21
+ assigned_members = team.members.values.flatten
22
+ assigned_members.uniq!
23
+ assigned_members.dup.each do |m|
24
+ 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]
25
+ assigned_members.delete(m) if user_info.nil? or user_info.deleted
26
+ end
27
+
28
+ channels_members = []
29
+ all_team_members = assigned_members.dup
30
+ if team.channels.key?("members")
31
+ team_members = []
32
+ team.channels["members"].each do |ch|
33
+ get_channels_name_and_id() unless @channels_id.key?(ch)
34
+ tm = get_channel_members(@channels_id[ch])
35
+ if tm.nil?
36
+ respond ":exclamation: Add the Smart Bot to *##{ch}* channel to be able to get the list of members.", dest
37
+ else
38
+ channels_members << @channels_id[ch]
39
+ tm.each do |m|
40
+ user_info = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
41
+ team_members << user_info.name unless user_info.is_app_user or user_info.is_bot
42
+ end
43
+ end
44
+ end
45
+ team_members.flatten!
46
+ all_team_members += team_members
47
+ all_team_members.uniq!
48
+ end
49
+ unless all_team_members.empty?
50
+ blocks_header =
51
+ {
52
+ "type": "context",
53
+ elements: [
54
+ {
55
+ type: "mrkdwn",
56
+ text: "*Time Off #{team_name} team* from #{date} ",
57
+ },
58
+ ],
59
+ }
60
+
61
+ from = Date.parse(date, "%Y/%m/%d")
62
+ blocks = []
63
+ all_team_members.each do |m|
64
+ @users = get_users() if @users.empty?
65
+ 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]
66
+ unless info.nil?
67
+ info = get_user_info(info.id)
68
+ if @vacations.key?(m)
69
+ v = ""
70
+ (from..(from+20)).each do |d|
71
+ v+="#{d.strftime("%d")} " if d.wday==1 or d==from
72
+ on_vacation = false
73
+ @vacations[m].periods.each do |p|
74
+ if p.from <= d.strftime("%Y/%m/%d") and p.to >= d.strftime("%Y/%m/%d")
75
+ if d.wday == 0 or d.wday == 6
76
+ v+=":large_orange_square: "
77
+ else
78
+ v+=":large_red_square: "
79
+ end
80
+ on_vacation=true
81
+ break
82
+ end
83
+ end
84
+ unless on_vacation
85
+ if d.wday == 0 or d.wday == 6
86
+ v += ":large_yellow_square: "
87
+ else
88
+ v+= ":white_square: "
89
+ end
90
+ end
91
+ end
92
+ else
93
+ v = ""
94
+ (from..(from+20)).each do |d|
95
+ if d.wday==1 or d==from
96
+ v += "#{d.strftime("%d")} "
97
+ end
98
+ if d.wday == 0 or d.wday == 6
99
+ v += ":large_yellow_square: "
100
+ else
101
+ v += ":white_square: "
102
+ end
103
+ end
104
+ end
105
+
106
+ blocks << {
107
+ type: "context",
108
+ elements: [
109
+ {
110
+ type: "image",
111
+ image_url: info.user.profile.image_24,
112
+ alt_text: info.user.name,
113
+ },
114
+ {
115
+ type: "plain_text",
116
+ text: v
117
+ }
118
+ ],
119
+ }
120
+ end
121
+ end
122
+ first = true
123
+ blocks.each_slice(10).each do |b|
124
+ if first
125
+ b.unshift(blocks_header)
126
+ first = false
127
+ end
128
+ respond blocks: b
129
+ end
130
+
131
+ end
132
+
133
+
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,58 @@
1
+ class SlackSmartBot
2
+ def set_memo_status(user, team_name, memo_id, status)
3
+ save_stats(__method__) if answer.empty?
4
+
5
+ get_teams()
6
+ if @teams.key?(team_name.to_sym)
7
+ assigned_members = @teams[team_name.to_sym].members.values.flatten
8
+ assigned_members.uniq!
9
+ all_team_members = assigned_members.dup
10
+ team_members = []
11
+ if @teams[team_name.to_sym].channels.key?("members")
12
+ @teams[team_name.to_sym].channels["members"].each do |ch|
13
+ get_channels_name_and_id() unless @channels_id.key?(ch)
14
+ tm = get_channel_members(@channels_id[ch])
15
+ tm.each do |m|
16
+ user_info = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
17
+ team_members << user_info.name unless user_info.is_app_user or user_info.is_bot
18
+ end
19
+ end
20
+ end
21
+ team_members.flatten!
22
+ team_members.uniq!
23
+ all_team_members += team_members
24
+ all_team_members.uniq!
25
+ end
26
+
27
+ if !@teams.key?(team_name.to_sym)
28
+ respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
29
+ elsif !(all_team_members + config.masters).flatten.include?(user.name)
30
+ respond "You have to be a member of the team or a Master admin to be able to set the status of a memo."
31
+ elsif !@teams[team_name.to_sym].key?(:memos) or @teams[team_name.to_sym][:memos].empty? or !@teams[team_name.to_sym][:memos].memo_id.include?(memo_id.to_i)
32
+ respond "It seems like there is no memo with id #{memo_id}"
33
+ elsif @teams[team_name.to_sym][:memos].memo_id.include?(memo_id.to_i)
34
+ memo_selected = @teams[team_name.to_sym][:memos].select { |m| m.memo_id == memo_id.to_i }[-1]
35
+ if memo_selected.type == 'jira' or memo_selected.type == 'github'
36
+ #todo: add tests for jira and github
37
+ respond "The memo specified is a #{memo_selected.type} and the status in those cases are linked to the specific issues in #{memo_selected.type}."
38
+ elsif memo_selected.privacy == "personal" and memo_selected.user != user.name
39
+ respond "Only the creator can set the status of a personal memo."
40
+ else
41
+ answer_delete
42
+ memos = []
43
+ message = ""
44
+ get_teams()
45
+ @teams[team_name.to_sym][:memos].each do |memo|
46
+ if memo.memo_id == memo_id.to_i
47
+ memo.status = status
48
+ message = memo.message
49
+ end
50
+ memos << memo
51
+ end
52
+ @teams[team_name.to_sym][:memos] = memos
53
+ update_teams()
54
+ respond "The memo status has been updated on team #{team_name}: #{message}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -24,7 +24,7 @@ class SlackSmartBot
24
24
  else
25
25
  type = :reaction
26
26
  end
27
- if File.exists?("#{config.path}/shares/#{from_channel}.csv")
27
+ if File.exist?("#{config.path}/shares/#{from_channel}.csv")
28
28
  t = CSV.table("#{config.path}/shares/#{from_channel}.csv", headers: ['share_id', 'user_deleted', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
29
29
  @shares[from_channel] = t
30
30
  if t.size>0