slack-smart-bot 1.10.0 → 1.12.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.
- checksums.yaml +4 -4
- data/README.md +134 -23
- data/lib/slack/smart-bot/comm/delete.rb +13 -0
- data/lib/slack/smart-bot/comm/dont_understand.rb +2 -2
- data/lib/slack/smart-bot/comm/get_channel_members.rb +7 -3
- data/lib/slack/smart-bot/comm/get_presence.rb +20 -0
- data/lib/slack/smart-bot/comm/get_users.rb +1 -1
- data/lib/slack/smart-bot/comm/respond.rb +24 -13
- data/lib/slack/smart-bot/comm/send_msg_user.rb +12 -11
- data/lib/slack/smart-bot/comm/set_status.rb +21 -0
- data/lib/slack/smart-bot/comm.rb +3 -0
- data/lib/slack/smart-bot/commands/general/add_admin.rb +51 -0
- data/lib/slack/smart-bot/commands/general/add_announcement.rb +1 -1
- data/lib/slack/smart-bot/commands/general/add_memo_team.rb +117 -0
- data/lib/slack/smart-bot/commands/general/add_team.rb +80 -0
- data/lib/slack/smart-bot/commands/general/add_vacation.rb +51 -0
- data/lib/slack/smart-bot/commands/general/allow_access.rb +67 -0
- data/lib/slack/smart-bot/commands/general/bot_help.rb +20 -11
- data/lib/slack/smart-bot/commands/general/delete_announcement.rb +1 -1
- data/lib/slack/smart-bot/commands/general/delete_memo_team.rb +69 -0
- data/lib/slack/smart-bot/commands/general/delete_share.rb +1 -1
- data/lib/slack/smart-bot/commands/general/delete_team.rb +54 -0
- data/lib/slack/smart-bot/commands/general/deny_access.rb +36 -0
- data/lib/slack/smart-bot/commands/general/ping_team.rb +100 -0
- data/lib/slack/smart-bot/commands/general/poster.rb +116 -0
- data/lib/slack/smart-bot/commands/general/remove_admin.rb +58 -0
- data/lib/slack/smart-bot/commands/general/remove_vacation.rb +27 -0
- data/lib/slack/smart-bot/commands/general/see_access.rb +24 -0
- data/lib/slack/smart-bot/commands/general/see_admins.rb +33 -0
- data/lib/slack/smart-bot/commands/general/see_announcements.rb +7 -5
- data/lib/slack/smart-bot/commands/general/see_command_ids.rb +29 -0
- data/lib/slack/smart-bot/commands/general/see_favorite_commands.rb +3 -4
- data/lib/slack/smart-bot/commands/general/see_statuses.rb +34 -21
- data/lib/slack/smart-bot/commands/general/see_teams.rb +402 -0
- data/lib/slack/smart-bot/commands/general/see_vacations.rb +58 -0
- data/lib/slack/smart-bot/commands/general/see_vacations_team.rb +136 -0
- data/lib/slack/smart-bot/commands/general/set_memo_status.rb +58 -0
- data/lib/slack/smart-bot/commands/general/share_messages.rb +1 -1
- data/lib/slack/smart-bot/commands/general/update_team.rb +130 -0
- data/lib/slack/smart-bot/commands/general_bot_commands.rb +442 -13
- data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +3 -2
- data/lib/slack/smart-bot/commands/on_bot/admin/see_result_routine.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +10 -9
- data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/admin_master/delete_message.rb +25 -0
- data/lib/slack/smart-bot/commands/on_bot/admin_master/get_bot_logs.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb +3 -1
- data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +15 -2
- data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +5 -4
- data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +416 -0
- data/lib/slack/smart-bot/commands/{general → on_bot/general}/bot_status.rb +1 -0
- data/lib/slack/smart-bot/commands/{general → on_bot/general}/leaderboard.rb +1 -0
- data/lib/slack/smart-bot/commands/{general → on_bot/general}/stop_using_rules.rb +1 -0
- data/lib/slack/smart-bot/commands/{general → on_bot/general}/suggest_command.rb +6 -0
- data/lib/slack/smart-bot/commands/{general → on_bot/general}/use_rules.rb +1 -0
- data/lib/slack/smart-bot/commands/{general → on_bot/general}/whats_new.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/kill_repl.rb +32 -0
- data/lib/slack/smart-bot/commands/on_bot/repl.rb +73 -15
- data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +117 -28
- data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +2 -1
- data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +3 -2
- data/lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb +5 -4
- data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +3 -2
- data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +2 -1
- data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +6 -3
- data/lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb +2 -1
- data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +2 -1
- data/lib/slack/smart-bot/commands/on_master/create_bot.rb +1 -0
- data/lib/slack/smart-bot/commands/on_master/where_smartbot.rb +41 -0
- data/lib/slack/smart-bot/commands.rb +30 -7
- data/lib/slack/smart-bot/listen.rb +30 -30
- data/lib/slack/smart-bot/process.rb +53 -23
- data/lib/slack/smart-bot/process_first.rb +2 -2
- data/lib/slack/smart-bot/treat_message.rb +23 -17
- data/lib/slack/smart-bot/utils/build_help.rb +1 -1
- data/lib/slack/smart-bot/utils/check_vacations.rb +43 -0
- data/lib/slack/smart-bot/utils/create_routine_thread.rb +1 -1
- data/lib/slack/smart-bot/utils/get_access_channels.rb +13 -0
- data/lib/slack/smart-bot/utils/get_admins_channels.rb +33 -0
- data/lib/slack/smart-bot/utils/get_bots_created.rb +27 -10
- data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +7 -2
- data/lib/slack/smart-bot/utils/get_command_ids.rb +84 -0
- data/lib/slack/smart-bot/utils/get_help.rb +36 -19
- data/lib/slack/smart-bot/utils/get_repls.rb +22 -2
- data/lib/slack/smart-bot/utils/get_routines.rb +22 -2
- data/lib/slack/smart-bot/utils/get_teams.rb +22 -0
- data/lib/slack/smart-bot/utils/get_vacations.rb +22 -0
- data/lib/slack/smart-bot/utils/has_access.rb +25 -9
- data/lib/slack/smart-bot/utils/is_admin.rb +27 -0
- data/lib/slack/smart-bot/utils/save_stats.rb +52 -42
- data/lib/slack/smart-bot/utils/save_status.rb +22 -7
- data/lib/slack/smart-bot/utils/update_access_channels.rb +8 -0
- data/lib/slack/smart-bot/utils/update_admins_channels.rb +25 -0
- data/lib/slack/smart-bot/utils/update_bots_file.rb +28 -7
- data/lib/slack/smart-bot/utils/update_repls.rb +7 -4
- data/lib/slack/smart-bot/utils/update_routines.rb +9 -3
- data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +13 -6
- data/lib/slack/smart-bot/utils/update_teams.rb +16 -0
- data/lib/slack/smart-bot/utils/update_vacations.rb +16 -0
- data/lib/slack/smart-bot/utils.rb +11 -0
- data/lib/slack-smart-bot.rb +50 -12
- data/lib/slack-smart-bot_general_commands.rb +16 -1
- data/whats_new.txt +12 -30
- metadata +78 -21
- 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.
|
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
|