slack-smart-bot 1.14.1 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +237 -48
  3. data/img/chat_gpt.png +0 -0
  4. data/img/chat_gpt_session.png +0 -0
  5. data/img/chat_gpt_share.png +0 -0
  6. data/img/command_add_sc.png +0 -0
  7. data/img/command_bot_help_echo.png +0 -0
  8. data/img/command_loop.png +0 -0
  9. data/img/command_my_timeoff.png +0 -0
  10. data/img/command_recap.png +0 -0
  11. data/img/command_repl1.png +0 -0
  12. data/img/command_repl2.png +0 -0
  13. data/img/command_ruby.png +0 -0
  14. data/img/command_run_repl.png +0 -0
  15. data/img/command_see_announcements.png +0 -0
  16. data/img/command_see_statuses.png +0 -0
  17. data/img/command_see_team.png +0 -0
  18. data/img/command_summarize.png +0 -0
  19. data/img/commands_inline.png +0 -0
  20. data/img/commands_on_demand.png +0 -0
  21. data/img/commands_on_external_call.png +0 -0
  22. data/img/image_editing.png +0 -0
  23. data/img/image_generation.png +0 -0
  24. data/img/image_variations.png +0 -0
  25. data/img/openai-300.png +0 -0
  26. data/img/openai.png +0 -0
  27. data/img/slack-300.png +0 -0
  28. data/img/slack.png +0 -0
  29. data/img/smart-bot-150.png +0 -0
  30. data/img/smart-bot-profile-pic-2.png +0 -0
  31. data/img/smart-bot-profile-pic.png +0 -0
  32. data/img/smart-bot.png +0 -0
  33. data/img/whisper.png +0 -0
  34. data/lib/slack/smart-bot/ai/open_ai/connect.rb +165 -43
  35. data/lib/slack/smart-bot/ai/open_ai/models.rb +61 -9
  36. data/lib/slack/smart-bot/ai/open_ai/send_gpt_chat.rb +67 -11
  37. data/lib/slack/smart-bot/ai/open_ai/send_image_edit.rb +4 -3
  38. data/lib/slack/smart-bot/ai/open_ai/send_image_generation.rb +4 -4
  39. data/lib/slack/smart-bot/ai/open_ai/send_image_variation.rb +4 -3
  40. data/lib/slack/smart-bot/ai/open_ai/whisper_transcribe.rb +4 -3
  41. data/lib/slack/smart-bot/comm/ask.rb +20 -8
  42. data/lib/slack/smart-bot/comm/dont_understand.rb +2 -2
  43. data/lib/slack/smart-bot/comm/event_hello.rb +30 -1
  44. data/lib/slack/smart-bot/comm/get_channel_members.rb +2 -1
  45. data/lib/slack/smart-bot/comm/get_presence.rb +1 -0
  46. data/lib/slack/smart-bot/comm/get_smartbot_team_info.rb +10 -0
  47. data/lib/slack/smart-bot/comm/get_user_info.rb +45 -6
  48. data/lib/slack/smart-bot/comm/get_users.rb +8 -1
  49. data/lib/slack/smart-bot/comm/respond.rb +225 -196
  50. data/lib/slack/smart-bot/comm/send_msg_channel.rb +2 -2
  51. data/lib/slack/smart-bot/comm/send_msg_user.rb +10 -9
  52. data/lib/slack/smart-bot/comm/unreact.rb +2 -2
  53. data/lib/slack/smart-bot/comm.rb +1 -0
  54. data/lib/slack/smart-bot/commands/general/add_admin.rb +16 -6
  55. data/lib/slack/smart-bot/commands/general/add_announcement.rb +3 -3
  56. data/lib/slack/smart-bot/commands/general/add_vacation.rb +28 -12
  57. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat.rb +272 -23
  58. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_add_collaborator.rb +42 -0
  59. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_copy_session.rb +89 -0
  60. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_delete_session.rb +45 -0
  61. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_get_prompts.rb +41 -0
  62. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_list_sessions.rb +81 -0
  63. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_share_session.rb +52 -0
  64. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_use_model.rb +52 -0
  65. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_edit_image.rb +14 -11
  66. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_generate_image.rb +15 -11
  67. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_models.rb +29 -17
  68. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_variations_image.rb +16 -13
  69. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_whisper.rb +13 -7
  70. data/lib/slack/smart-bot/commands/general/allow_access.rb +8 -4
  71. data/lib/slack/smart-bot/commands/general/bot_help.rb +24 -9
  72. data/lib/slack/smart-bot/commands/general/bye_bot.rb +9 -5
  73. data/lib/slack/smart-bot/commands/general/delete_announcement.rb +2 -1
  74. data/lib/slack/smart-bot/commands/general/delete_share.rb +2 -1
  75. data/lib/slack/smart-bot/commands/general/deny_access.rb +1 -1
  76. data/lib/slack/smart-bot/commands/general/get_smartbot_readme.rb +15 -0
  77. data/lib/slack/smart-bot/commands/general/hi_bot.rb +10 -4
  78. data/lib/slack/smart-bot/commands/general/personal_settings.rb +26 -8
  79. data/lib/slack/smart-bot/commands/general/poster.rb +26 -2
  80. data/lib/slack/smart-bot/commands/general/public_holidays.rb +14 -24
  81. data/lib/slack/smart-bot/commands/general/recap.rb +399 -0
  82. data/lib/slack/smart-bot/commands/general/remove_admin.rb +19 -9
  83. data/lib/slack/smart-bot/commands/general/remove_vacation.rb +23 -6
  84. data/lib/slack/smart-bot/commands/general/see_access.rb +2 -1
  85. data/lib/slack/smart-bot/commands/general/see_admins.rb +8 -4
  86. data/lib/slack/smart-bot/commands/general/see_announcements.rb +5 -5
  87. data/lib/slack/smart-bot/commands/general/see_favorite_commands.rb +4 -4
  88. data/lib/slack/smart-bot/commands/general/see_shares.rb +1 -1
  89. data/lib/slack/smart-bot/commands/general/see_vacations.rb +34 -17
  90. data/lib/slack/smart-bot/commands/general/set_public_holidays.rb +4 -2
  91. data/lib/slack/smart-bot/commands/general/share_messages.rb +3 -3
  92. data/lib/slack/smart-bot/commands/general/summarize.rb +191 -0
  93. data/lib/slack/smart-bot/commands/general/teams/add_team.rb +4 -8
  94. data/lib/slack/smart-bot/commands/general/teams/delete_team.rb +3 -3
  95. data/lib/slack/smart-bot/commands/general/teams/memos/add_memo_team.rb +34 -29
  96. data/lib/slack/smart-bot/commands/general/teams/memos/add_memo_team_comment.rb +1 -1
  97. data/lib/slack/smart-bot/commands/general/teams/memos/delete_memo_team.rb +6 -4
  98. data/lib/slack/smart-bot/commands/general/teams/memos/see_memo_team.rb +26 -15
  99. data/lib/slack/smart-bot/commands/general/teams/memos/see_memos_team.rb +33 -24
  100. data/lib/slack/smart-bot/commands/general/teams/memos/set_memo_status.rb +4 -4
  101. data/lib/slack/smart-bot/commands/general/teams/ping_team.rb +10 -8
  102. data/lib/slack/smart-bot/commands/general/teams/see_teams.rb +73 -61
  103. data/lib/slack/smart-bot/commands/general/teams/see_vacations_team.rb +28 -13
  104. data/lib/slack/smart-bot/commands/general/teams/update_team.rb +9 -9
  105. data/lib/slack/smart-bot/commands/general_bot_commands.rb +1153 -835
  106. data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +19 -17
  107. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +12 -9
  108. data/lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb +2 -0
  109. data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +1 -0
  110. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +5 -3
  111. data/lib/slack/smart-bot/commands/on_bot/admin_master/delete_message.rb +2 -3
  112. data/lib/slack/smart-bot/commands/on_bot/admin_master/get_bot_logs.rb +2 -3
  113. data/lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb +2 -3
  114. data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +7 -6
  115. data/lib/slack/smart-bot/commands/on_bot/admin_master/update_message.rb +2 -3
  116. data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +1 -1
  117. data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +21 -20
  118. data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +40 -7
  119. data/lib/slack/smart-bot/commands/on_bot/general/bot_status.rb +6 -2
  120. data/lib/slack/smart-bot/commands/on_bot/general/stop_using_rules.rb +7 -6
  121. data/lib/slack/smart-bot/commands/on_bot/general/suggest_command.rb +5 -4
  122. data/lib/slack/smart-bot/commands/on_bot/general/use_rules.rb +4 -3
  123. data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +4 -4
  124. data/lib/slack/smart-bot/commands/on_bot/kill_repl.rb +1 -1
  125. data/lib/slack/smart-bot/commands/on_bot/repl.rb +110 -53
  126. data/lib/slack/smart-bot/commands/on_bot/repl_client.rb +35 -29
  127. data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +5 -5
  128. data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +1 -2
  129. data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +5 -4
  130. data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +22 -11
  131. data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +12 -7
  132. data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +2 -2
  133. data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +1 -1
  134. data/lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb +5 -5
  135. data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +5 -5
  136. data/lib/slack/smart-bot/commands/on_master/create_bot.rb +3 -3
  137. data/lib/slack/smart-bot/commands.rb +10 -0
  138. data/lib/slack/smart-bot/config.rb +126 -0
  139. data/lib/slack/smart-bot/listen.rb +12 -11
  140. data/lib/slack/smart-bot/process.rb +62 -55
  141. data/lib/slack/smart-bot/process_first.rb +106 -65
  142. data/lib/slack/smart-bot/treat_message.rb +79 -47
  143. data/lib/slack/smart-bot/utils/answer.rb +11 -3
  144. data/lib/slack/smart-bot/utils/answer_delete.rb +11 -3
  145. data/lib/slack/smart-bot/utils/check_vacations.rb +21 -3
  146. data/lib/slack/smart-bot/utils/create_routine_thread.rb +13 -13
  147. data/lib/slack/smart-bot/utils/display_calendar.rb +42 -8
  148. data/lib/slack/smart-bot/utils/encryption/decrypt.rb +16 -9
  149. data/lib/slack/smart-bot/utils/encryption/encrypt.rb +14 -11
  150. data/lib/slack/smart-bot/utils/find_user.rb +71 -0
  151. data/lib/slack/smart-bot/utils/get_access_channels.rb +22 -3
  152. data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +3 -4
  153. data/lib/slack/smart-bot/utils/get_command_ids.rb +5 -5
  154. data/lib/slack/smart-bot/utils/get_countries_candelarific.rb +18 -0
  155. data/lib/slack/smart-bot/utils/get_help.rb +21 -19
  156. data/lib/slack/smart-bot/utils/get_openai_sessions.rb +47 -0
  157. data/lib/slack/smart-bot/utils/get_personal_settings.rb +29 -3
  158. data/lib/slack/smart-bot/utils/get_rules_imported.rb +27 -6
  159. data/lib/slack/smart-bot/utils/get_shares.rb +1 -1
  160. data/lib/slack/smart-bot/utils/get_team_members.rb +4 -4
  161. data/lib/slack/smart-bot/utils/get_vacations.rb +15 -7
  162. data/lib/slack/smart-bot/utils/has_access.rb +10 -4
  163. data/lib/slack/smart-bot/utils/is_admin.rb +25 -17
  164. data/lib/slack/smart-bot/utils/local_time.rb +29 -0
  165. data/lib/slack/smart-bot/utils/save_stats.rb +5 -3
  166. data/lib/slack/smart-bot/utils/update_access_channels.rb +19 -3
  167. data/lib/slack/smart-bot/utils/update_openai_sessions.rb +42 -0
  168. data/lib/slack/smart-bot/utils/update_personal_settings.rb +11 -3
  169. data/lib/slack/smart-bot/utils/update_rules_imported.rb +18 -3
  170. data/lib/slack/smart-bot/utils/update_vacations.rb +5 -2
  171. data/lib/slack/smart-bot/utils/upgrade_to_use_team_ids.rb +276 -0
  172. data/lib/slack/smart-bot/utils.rb +6 -1
  173. data/lib/slack-smart-bot.rb +182 -76
  174. data/lib/slack-smart-bot_general_commands.rb +10 -9
  175. data/whats_new.txt +30 -13
  176. metadata +152 -18
@@ -0,0 +1,399 @@
1
+ class SlackSmartBot
2
+
3
+ #todo: add tests
4
+ def recap(user, dest, channel, date_start, date_end, my_recap)
5
+ save_stats(__method__)
6
+
7
+ require "date"
8
+ require "time"
9
+ react :runner
10
+ @history_still_running ||= false
11
+
12
+ if channel[0] == "D"
13
+ messages = []
14
+ messages << "Sorry, `recap` command is not available in direct messages."
15
+ messages << "Specify a channel instead or call it from a channel."
16
+ messages << "For example: `my recap 2023 #channel`"
17
+ messages << ""
18
+ messages << "You can also call from a DM:"
19
+ messages << "\t\t`bot stats last year`"
20
+ messages << "\t\t`bot stats last year monthly graph`"
21
+ messages << "\t\t`bot stats`"
22
+ messages << ""
23
+ messages << "Call `bot help bot stats` for more information."
24
+ respond messages.join("\n")
25
+ elsif @history_still_running
26
+ respond "Due to Slack API rate limit, `recap` command is limited. Waiting for other `recap` command to finish."
27
+ num_times = 0
28
+ while @history_still_running and num_times < 30
29
+ num_times += 1
30
+ sleep 1
31
+ end
32
+ if @history_still_running
33
+ respond "Sorry, Another `recap` command is still running after 30 seconds. Please try again later."
34
+ end
35
+ end
36
+
37
+ if !@history_still_running and channel[0] != "D"
38
+ @history_still_running = true
39
+ begin
40
+ date_start = date_start.gsub(".", "/").gsub("-", "/")
41
+ date_end = date_end.gsub(".", "/").gsub("-", "/")
42
+ if date_start.to_s == ""
43
+ date_start = Date.today.strftime("%Y/01/01")
44
+ elsif date_start.match(/^\d\d\d\d$/)
45
+ date_start = "#{date_start}/01/01"
46
+ end
47
+ if date_end.to_s == ""
48
+ date_end = Date.parse(date_start).strftime("%Y/12/31")
49
+ end
50
+ if date_end < date_start
51
+ respond "Sorry, `recap` command requires `date_start` to be before `date_end`."
52
+ else
53
+ # if more than one year respond max one year
54
+ num_days = (Date.parse(date_end) - Date.parse(date_start)).to_i
55
+ if num_days > 365
56
+ respond "Sorry, `recap` command is limited to one year."
57
+ else
58
+ messages = []
59
+ #if date_start is 1st of January and date_end is 31st of December, then use the year only
60
+ if Date.parse(date_start).strftime("%m/%d") == "01/01" and Date.parse(date_end).strftime("%m/%d") == "12/31"
61
+ messages << "*#{Date.parse(date_start).strftime("%Y")} Recap <##{channel}>*"
62
+ else
63
+ messages << "*Recap for <##{channel}> from #{date_start} to #{date_end}*"
64
+ end
65
+ date_start = date_start + " 00:00:00"
66
+ date_start = Time.strptime(date_start, "%Y/%m/%d %H:%M:%S")
67
+ date_end = date_end + " 23:59:59"
68
+ date_end = Time.strptime(date_end, "%Y/%m/%d %H:%M:%S")
69
+
70
+ # get all messages from the channel
71
+ hist = []
72
+
73
+ begin
74
+ members = get_channel_members(channel)
75
+ rescue
76
+ members = []
77
+ end
78
+ if !members.include?(config.nick_id_granular) or !members.include?(config.nick_id) #monitor-smartbot #smartbot
79
+ respond "Sorry, `recap` command requires <@#{config.nick_id_granular}> and <@#{config.nick_id}> to be in the channel. Please add them and try again."
80
+ elsif !members.include?(user.id)
81
+ respond "Sorry, `recap` command requires you to be in the channel. Please join the channel and try again."
82
+ else
83
+
84
+ client_granular.conversations_history(channel: channel, limit: 200, oldest: date_start.to_f, latest: date_end.to_f) do |response|
85
+ hist << response.messages
86
+ end
87
+ hist_replies = hist.deep_copy()
88
+ hist.flatten!
89
+
90
+ # sort hist by reply_count from highest to lowest, reply_count is not always, return a hash
91
+ hist.each do |message|
92
+ if !message.key?("reply_count")
93
+ message[:reply_count] = 0
94
+ end
95
+ end
96
+ num_posts = hist.length
97
+ num_threads = hist.select { |msg| msg.key?("thread_ts") }.length
98
+
99
+ hist_replies_count = hist.sort_by { |k| k[:reply_count] }
100
+ # get the top 3 threads
101
+ top_three_threads = []
102
+ #hist_replies_count is not always 3, so we need to check if the index exists
103
+ top_three_threads << hist_replies_count[-1] if hist_replies_count[-1] and hist_replies_count[-1].key?("thread_ts")
104
+ top_three_threads << hist_replies_count[-2] if hist_replies_count[-2] and hist_replies_count[-2].key?("thread_ts")
105
+ top_three_threads << hist_replies_count[-3] if hist_replies_count[-3] and hist_replies_count[-3].key?("thread_ts")
106
+
107
+ num_messages_skipped = 0
108
+ num_messages_not_skipped = 0
109
+ hist.each do |message|
110
+ if Time.at(message[:ts].to_f) >= date_start and Time.at(message[:ts].to_f) <= date_end
111
+ if message.key?("thread_ts")
112
+ if message.reply_users_count == 1 and message.reply_users[0] == config.nick_id
113
+ #if the thread has only one replier and it is from smartbot, then we don't need to get the replies from the thread
114
+ num_messages_skipped += 1
115
+ message[:reply_count].times do
116
+ hist_replies << { user: config.nick_id, ts: message.thread_ts, text: "" }
117
+ end
118
+ else
119
+ num_messages_not_skipped += 1
120
+ thread_ts = message.thread_ts
121
+ passed = false
122
+ num_tries = 0
123
+ while !passed and num_tries < 30
124
+ begin
125
+ num_tries += 1
126
+ replies = client_granular.conversations_replies(channel: channel, ts: thread_ts)
127
+ passed = true
128
+ rescue => e
129
+ @logger.info "recap command: threads num_tries: #{num_tries}"
130
+ @logger.fatal "recap command: threads error: #{e.message}"
131
+ sleep 1
132
+ passed = false
133
+ end
134
+ end
135
+ if !passed
136
+ respond "Sorry, `recap` command failed. <@#{config.admins.join(">,<@")}> please check the logs."
137
+ @history_still_running = false
138
+ unreact :runner
139
+ return
140
+ end
141
+ hist_replies << replies.messages
142
+ sleep 0.5 #to avoid rate limit Tier 3 (50 requests per minute)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ hist_replies.flatten!
148
+
149
+ hist_user = {}
150
+ hist_replies.each do |msg|
151
+ hist_user[msg.user] ||= 0
152
+ hist_user[msg.user] += 1
153
+ end
154
+ #sort hist_user by value from highest to lowest, return a hash
155
+ hist_user = hist_user.sort_by { |k, v| v }.reverse.to_h
156
+ #top_three_users excluding smartbot
157
+ hist_user_wo_smartbot = hist_user.reject { |k, v| k == config.nick_id }
158
+
159
+ top_three_users = hist_user_wo_smartbot.first(3).to_h
160
+ messages << "\t :bar_chart: Totals:"
161
+ messages << "\t\t\t messages: *#{hist_replies.length}*"
162
+ messages << "\t\t\t SmartBot messages: *#{hist_user[config.nick_id]}* (#{hist_user[config.nick_id] * 100 / hist_replies.length} %)" if hist_user.key?(config.nick_id)
163
+ messages << "\t\t\t posts: *#{num_posts}*"
164
+ messages << "\t\t\t threads: *#{num_threads}*"
165
+
166
+ num_replies = hist_replies.length - num_posts
167
+ messages << "\t\t\t replies: *#{num_replies}*"
168
+
169
+ messages << "\t\t\t users: *#{hist_user.length}*"
170
+ if top_three_users.length > 0
171
+ messages << "\n\t :boom: Top 3 users:"
172
+ top_three_users.each do |user, num|
173
+ messages << "\t\t\t<@#{user}>: *#{num}*"
174
+ end
175
+ end
176
+
177
+ #top 3 users posting excluding smartbot
178
+ hist_user_posts = {}
179
+ hist.each do |msg|
180
+ hist_user_posts[msg.user] ||= 0
181
+ hist_user_posts[msg.user] += 1
182
+ end
183
+ hist_user_posts = hist_user_posts.reject { |k, v| k == config.nick_id }
184
+ hist_user_posts = hist_user_posts.sort_by { |k, v| v }.reverse.to_h
185
+ top_three_users_posts = hist_user_posts.first(3).to_h
186
+ if top_three_users_posts.length > 0
187
+ messages << "\n\t :bust_in_silhouette: Top 3 users posting:"
188
+ top_three_users_posts.each do |user, num|
189
+ messages << "\t\t\t<@#{user}>: *#{num}*"
190
+ end
191
+ end
192
+
193
+ if top_three_threads.length > 0
194
+ messages << "\n\t :star: Top 3 threads:"
195
+ top_three_threads.each do |thread|
196
+ messages << "\t\t\t<@#{thread.user}>: *#{thread.reply_count}* replies. *<https://greenqloud.slack.com/archives/#{channel}/p#{thread.ts.gsub(".", "")}|#{thread.text[0..50].gsub(/[^a-zA-Z0-9\s]/, "").gsub("\n", "").strip}>*"
197
+ end
198
+ end
199
+
200
+ #get the stats from the files, they are like smart-bot.stats.2020-01.log, smart-bot.stats.2020-02.log, etc.
201
+ smartbot_stats = []
202
+ Dir["#{config.stats_path}.*.log"].sort.each do |file|
203
+ #read only the files that are in the range of date_start and date_end
204
+ if file.match(/#{config.stats_path}\.(\d\d\d\d-\d\d)\.log/)
205
+ if $1 >= date_start.strftime("%Y-%m") and $1 <= date_end.strftime("%Y-%m")
206
+ smartbot_stats_file = CSV.read(file, headers: true, converters: :numeric)
207
+ #convert smartbot_stats_file to array of hashes
208
+ smartbot_stats_file = smartbot_stats_file.map(&:to_hash)
209
+ smartbot_stats << smartbot_stats_file
210
+ end
211
+ end
212
+ end
213
+ smartbot_stats.flatten!
214
+
215
+ #filter by bot_channel_id==channel and dest_channel_id!=channel and type_message==on_dm
216
+ smartbot_stats_on_dm = smartbot_stats.deep_copy
217
+ smartbot_stats_on_dm = smartbot_stats_on_dm.select { |msg| msg["bot_channel_id"] == channel and msg["dest_channel_id"] != channel and msg["type_message"] == "on_dm" }
218
+ #filter by date_start and date_end. We store the date on "date" field like this: 2020-01-14 16:34:16
219
+ smartbot_stats_on_dm = smartbot_stats_on_dm.select { |msg| Date.parse(msg["date"]) >= Date.parse(date_start.strftime("%Y-%m-%d")) and Date.parse(msg["date"]) <= Date.parse(date_end.strftime("%Y-%m-%d")) }
220
+
221
+ #filter by bot_channel_id==channel and dest_channel_id!=channel and type_message!=on_dm
222
+ smartbot_stats_external = smartbot_stats.deep_copy
223
+ smartbot_stats_external = smartbot_stats_external.select { |msg| msg["bot_channel_id"] == channel and msg["dest_channel_id"] != channel and msg["type_message"] != "on_dm" }
224
+ #filter by date_start and date_end. We store the date on "date" field like this: 2020-01-14 16:34:16
225
+ smartbot_stats_external = smartbot_stats_external.select { |msg| Date.parse(msg["date"]) >= Date.parse(date_start.strftime("%Y-%m-%d")) and Date.parse(msg["date"]) <= Date.parse(date_end.strftime("%Y-%m-%d")) }
226
+
227
+ # filter by dest_channel_id
228
+ smartbot_stats = smartbot_stats.select { |msg| msg["dest_channel_id"] == channel }
229
+ # filter by date_start and date_end. We store the date on "date" field like this: 2020-01-14 16:34:16
230
+ smartbot_stats = smartbot_stats.select { |msg| Date.parse(msg["date"]) >= Date.parse(date_start.strftime("%Y-%m-%d")) and Date.parse(msg["date"]) <= Date.parse(date_end.strftime("%Y-%m-%d")) }
231
+
232
+ #get the number of times SmartBot was called by a user, if field 'user_name' is not including 'routine/'
233
+ smartbot_stats_user = {}
234
+ smartbot_stats.each do |msg|
235
+ if !msg["user_name"].include?("routine/")
236
+ smartbot_stats_user[msg["user_name"]] ||= 0
237
+ smartbot_stats_user[msg["user_name"]] += 1
238
+ end
239
+ end
240
+
241
+ smartbot_stats_total = smartbot_stats_user.values.sum
242
+ if smartbot_stats_total > 0
243
+ messages << "\t :robot_face: *SmartBot* Stats:"
244
+ #total of times SmartBot was called
245
+ messages << "\t\t\t called by a user: *#{smartbot_stats_total}*"
246
+ #total of times SmartBot was called by any user including routines
247
+ smartbot_stats_total_all = smartbot_stats.length
248
+ messages << "\t\t\t called on this channel: *#{smartbot_stats_total_all}*" if smartbot_stats_total != smartbot_stats_total_all
249
+ #total of times SmartBot was called using this channel on a DM
250
+ messages << "\t\t\t called on a DM using this channel: *#{smartbot_stats_on_dm.length}*" if smartbot_stats_on_dm.length > 0
251
+ #total of times SmartBot was called using this channel from another channel
252
+ messages << "\t\t\t called from another channel using this channel: *#{smartbot_stats_external.length}*" if smartbot_stats_external.length > 0
253
+
254
+ #total of times SmartBot was used
255
+ total_sb_calls = smartbot_stats_total_all + smartbot_stats_on_dm.length + smartbot_stats_external.length
256
+ messages << "\t\t\t Total times SmartBot was used: *#{total_sb_calls}*" if total_sb_calls != smartbot_stats_total_all
257
+
258
+ #get the three most used commands. The command is stored on "command" field
259
+ smartbot_stats_commands = {}
260
+ smartbot_stats.each do |msg|
261
+ smartbot_stats_commands[msg["command"]] ||= 0
262
+ smartbot_stats_commands[msg["command"]] += 1
263
+ end
264
+
265
+ #total number of different commands
266
+ smartbot_stats_total_commands = smartbot_stats_commands.keys.length
267
+ messages << "\t\t\t Total different commands used: *#{smartbot_stats_total_commands}*"
268
+
269
+ #top three commands
270
+ smartbot_stats_commands = smartbot_stats_commands.sort_by { |k, v| v }.reverse.to_h
271
+ smartbot_stats_commands = smartbot_stats_commands.first(3).to_h
272
+ messages << "\t\t\t :speech_balloon: Top 3 commands:"
273
+ smartbot_stats_commands.each do |command, num|
274
+ messages << "\t\t\t\t*#{command}*: *#{num}*"
275
+ end
276
+
277
+ #top three commands excluding routines only if the top 3 is different from including routines
278
+ smartbot_stats_commands_include_routines = smartbot_stats_commands.deep_copy
279
+
280
+ smartbot_stats_commands = {}
281
+ smartbot_stats.each do |msg|
282
+ if !msg["user_name"].include?("routine/")
283
+ smartbot_stats_commands[msg["command"]] ||= 0
284
+ smartbot_stats_commands[msg["command"]] += 1
285
+ end
286
+ end
287
+ smartbot_stats_commands = smartbot_stats_commands.sort_by { |k, v| v }.reverse.to_h
288
+ smartbot_stats_commands = smartbot_stats_commands.first(3).to_h
289
+ if smartbot_stats_commands != smartbot_stats_commands_include_routines
290
+ messages << "\t\t\t :speech_balloon: Top 3 commands excluding routines:"
291
+ smartbot_stats_commands.each do |command, num|
292
+ messages << "\t\t\t\t*#{command}*: *#{num}*"
293
+ end
294
+ end
295
+
296
+ #get the three most used users. The user is stored on "user_name" field
297
+ smartbot_stats_user = smartbot_stats_user.sort_by { |k, v| v }.reverse.to_h
298
+ smartbot_stats_user = smartbot_stats_user.first(3).to_h
299
+ messages << "\t\t\t :bust_in_silhouette: Top 3 users calling SmartBot:"
300
+ smartbot_stats_user.each do |user, num|
301
+ messages << "\t\t\t\t<@#{user}>: *#{num}*"
302
+ end
303
+ end
304
+
305
+ #number of messages by month
306
+ hist_month = {}
307
+ hist_replies.each do |msg|
308
+ if Time.at(msg[:ts].to_f) >= date_start and Time.at(msg[:ts].to_f) <= date_end
309
+ date = Time.at(msg[:ts].to_f).strftime("%Y/%m")
310
+ hist_month[date] ||= 0
311
+ hist_month[date] += 1
312
+ end
313
+ end
314
+ hist_month = hist_month.sort_by { |k, v| k }.to_h
315
+ respond messages.join("\n")
316
+ messages = []
317
+ messages << "\t :memo: Number of messages by month:" if hist_month.size > 0
318
+ hist_month.each do |date, num|
319
+ graph = ":large_yellow_square: " * (num.to_f * (10 * hist_month.size) / hist_replies.size).round(2)
320
+ messages << "\t\t\t#{date}: #{graph} #{num} (#{num * 100 / hist_replies.size}%)"
321
+ end
322
+
323
+ #number of users by month
324
+ hist_month_user = {}
325
+ hist_replies.each do |msg|
326
+ if Time.at(msg[:ts].to_f) >= date_start and Time.at(msg[:ts].to_f) <= date_end
327
+ date = Time.at(msg[:ts].to_f).strftime("%Y/%m")
328
+ hist_month_user[date] ||= []
329
+ hist_month_user[date] << msg.user
330
+ end
331
+ end
332
+ hist_month_user = hist_month_user.sort_by { |k, v| k }.to_h
333
+ respond messages.join("\n")
334
+ messages = []
335
+ messages << "\t :person_with_crown: Number of users by month:" if hist_month_user.size > 0
336
+ total_users_by_month = 0
337
+ hist_month_user.each do |date, users|
338
+ total_users_by_month += users.uniq.length
339
+ end
340
+ hist_month_user.each do |date, users|
341
+ graph = ":large_orange_square: " * (users.uniq.length.to_f * (10 * hist_month_user.size) / total_users_by_month).round(2)
342
+ messages << "\t\t\t#{date}: #{graph} #{users.uniq.length} "
343
+ end
344
+ if my_recap
345
+ respond messages.join("\n")
346
+ messages = []
347
+ messages << "\t :medal: *Recap for <@#{user[:id]}>*:"
348
+ messages << "\t\t\tTotal messages: *#{hist_user[user[:id]]}*"
349
+ # top three posts of user.name
350
+ hist_user_posts = hist.select { |msg| msg.user == user[:id] }
351
+ hist_user_posts = hist_user_posts.sort_by { |k| k[:reply_count] }
352
+ top_three_posts = [hist_user_posts[-1], hist_user_posts[-2], hist_user_posts[-3]]
353
+ messages << "\t\t\tTop 3 threads:"
354
+ top_three_posts.each do |post|
355
+ if !post.nil?
356
+ messages << "\t\t\t\t *#{post.reply_count}* replies: *<https://greenqloud.slack.com/archives/#{channel}/p#{post.ts.gsub(".", "")}|#{post.text[0..50].gsub(/[^a-zA-Z0-9\s]/, "").gsub("\n", "").strip}>*"
357
+ end
358
+ end
359
+
360
+ #Add also stats from SmartBot commands for this user
361
+ #filter stats by user
362
+ smartbot_stats_user = smartbot_stats.select { |msg| msg["user_id"] == user[:id] }
363
+ #get the number of times SmartBot was called by this user
364
+ smartbot_stats_user_total = smartbot_stats_user.length
365
+ messages << "\t\t\t SmartBot calls: *#{smartbot_stats_user_total}*"
366
+ #get the three most used commands by this user
367
+ smartbot_stats_user_commands = {}
368
+ smartbot_stats_user.each do |msg|
369
+ smartbot_stats_user_commands[msg["command"]] ||= 0
370
+ smartbot_stats_user_commands[msg["command"]] += 1
371
+ end
372
+ if smartbot_stats_user_total > 0
373
+ #total number of different commands
374
+ smartbot_stats_user_total_commands = smartbot_stats_user_commands.keys.length
375
+ messages << "\t\t\t Different commands: *#{smartbot_stats_user_total_commands}*"
376
+ #top three commands
377
+ smartbot_stats_user_commands = smartbot_stats_user_commands.sort_by { |k, v| v }.reverse.to_h
378
+ smartbot_stats_user_commands = smartbot_stats_user_commands.first(3).to_h
379
+ messages << "\t\t\t :speech_balloon: Top 3 commands:"
380
+ smartbot_stats_user_commands.each do |command, num|
381
+ messages << "\t\t\t\t\t*#{command}*: *#{num}*"
382
+ end
383
+ end
384
+ end
385
+
386
+ respond messages.join("\n")
387
+ end
388
+ end
389
+ end
390
+ rescue => e
391
+ @logger.fatal "recap command failed: #{e.message}"
392
+ @logger.fatal e.backtrace.inspect
393
+ respond "Sorry, `recap` command failed. <@#{config.admins.join(">,<@")}> please check the logs."
394
+ end
395
+ @history_still_running = false
396
+ end
397
+ unreact :runner
398
+ end
399
+ end
@@ -14,37 +14,47 @@ class SlackSmartBot
14
14
  end
15
15
  messages = []
16
16
  admins = config.masters.dup
17
+ team_id_admins = config.team_id_masters.dup
17
18
  channels = get_channels()
18
19
  channel_found = channels.detect { |c| c.id == channel }
19
20
  if !channel_found.nil? and channel_found.creator.to_s != ''
20
- creator_info = @users.select{|u| u.id == channel_found.creator or (u.key?(:enterprise_user) and u.enterprise_user.id == channel_found.creator)}[-1]
21
+ creator_info = find_user(channel_found.creator)
21
22
  admins << creator_info.name
23
+ team_id_admins << "#{creator_info.team_id}_#{creator_info.name}"
22
24
  end
23
25
  if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
24
26
  admins << config.admins.dup
27
+ team_id_admins << config.team_id_admins.dup
25
28
  end
26
29
  if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
27
- admins << @admins_channels[channel]
30
+ team_id_admins << @admins_channels[channel]
31
+ #remove the team_id from the names on @admins_channels
32
+ admins << @admins_channels[channel].map { |a| a.split('_')[1..-1].join('_') }
28
33
  end
29
34
  admins.flatten!
30
35
  admins.uniq!
31
36
  admins.delete(nil)
32
- if admins.include?(user.name)
33
- admin_info = @users.select{|u| u.id == admin_user or (u.key?(:enterprise_user) and u.enterprise_user.id == admin_user)}[-1]
34
- if creator_info.name == admin_info.name
37
+ team_id_admins.flatten!
38
+ team_id_admins.uniq!
39
+ team_id_admins.delete(nil)
40
+
41
+ if team_id_admins.include?("#{user.team_id}_#{user.name}")
42
+ admin_info = find_user(admin_user)
43
+ if creator_info.name == admin_info.name and creator_info.team_id == admin_info.team_id
35
44
  messages << "This user created the channel and cannot be removed as an admin."
36
- elsif config.masters.include?(admin_info.name) or config.masters.include?(admin_user)
45
+ elsif config.team_id_masters.include?("#{admin_info.team_id}_#{admin_info.name}") or config.team_id_masters.include?(admin_user)
37
46
  messages << "Master admins cannot be removed as admins of this channel."
38
- elsif config.admins.include?(admin_info.name) or config.admins.include?(admin_user)
47
+ elsif config.team_id_admins.include?("#{admin_info.team_id}_#{admin_info.name}") or config.team_id_admins.include?(admin_user)
39
48
  messages << "This user is a defaulted admin for this channel and cannot be removed using this command."
40
- elsif !admins.include?(admin_info.name)
49
+ elsif !team_id_admins.include?("#{admin_info.team_id}_#{admin_info.name}")
41
50
  messages << "This user is not an admin of this channel."
42
51
  else
43
52
  @admins_channels[channel] ||= []
44
- @admins_channels[channel].delete(admin_info.name)
53
+ @admins_channels[channel].delete("#{admin_info.team_id}_#{admin_info.name}")
45
54
  update_admins_channels()
46
55
  messages << "The user is not an admin of this channel from now on."
47
56
  admins.delete(admin_info.name)
57
+ team_id_admins.delete("#{admin_info.team_id}_#{admin_info.name}")
48
58
  end
49
59
  messages << "*Admins*: <@#{admins.join('>, <@')}>"
50
60
  else
@@ -3,24 +3,41 @@ class SlackSmartBot
3
3
  save_stats(__method__)
4
4
 
5
5
  get_vacations()
6
- if !@vacations.key?(user.name)
6
+ team_id_user = "#{user.team_id}_#{user.name}"
7
+
8
+ if !@vacations.key?(team_id_user)
7
9
  respond "It seems like you don't have any time off added."
8
- elsif @vacations[user.name].periods.empty? or !@vacations[user.name].periods.vacation_id.include?(vacation_id)
10
+ elsif @vacations[team_id_user].periods.empty? or !@vacations[team_id_user].periods.vacation_id.include?(vacation_id)
9
11
  respond "It seems like the ID supplied doesn't exist. Please call `see my time off` and check the ID."
10
12
  else
11
- vacations = @vacations[user.name].deep_copy
13
+ if @vacations.key?(team_id_user) and @vacations[team_id_user][:public_holidays].to_s != ""
14
+ country_region = @vacations[team_id_user][:public_holidays].downcase
15
+ elsif config[:public_holidays].key?(:default_calendar)
16
+ country_region = config[:public_holidays][:default_calendar].downcase
17
+ else
18
+ country_region = ''
19
+ end
20
+
21
+ local_day_time = local_time(country_region)
22
+ if local_day_time.nil?
23
+ today = Date.today
24
+ else
25
+ today = local_day_time.to_date
26
+ end
27
+
28
+ vacations = @vacations[team_id_user].deep_copy
12
29
  vacation = vacations.periods.select {|v| v.vacation_id == vacation_id }[-1]
13
30
  vacations.periods.delete_if {|v| v.vacation_id == vacation_id }
14
- update_vacations({user.name => vacations})
31
+ update_vacations({team_id_user => vacations})
15
32
  respond "Your time off has been removed."
16
- if vacation.from <= Date.today.strftime("%Y/%m/%d") and vacation.to >= Date.today.strftime("%Y/%m/%d")
33
+ if vacation.from <= today.strftime("%Y/%m/%d") and vacation.to >= today.strftime("%Y/%m/%d")
17
34
  info = get_user_info(vacations.user_id)
18
35
  emoji = info.user.profile.status_emoji
19
36
  if (vacation.type == 'vacation' and emoji == ':palm_tree:') or (vacation.type == 'sick' and emoji == ':face_with_thermometer:') or
20
37
  (vacation.type == 'sick child' and emoji == ':baby:')
21
38
  set_status(vacations.user_id, status: '', expiration: '', message: '')
22
39
  end
23
- check_vacations(date: Date.today, user: user.name, set_status: true, only_first_day: false)
40
+ check_vacations(date: today, team_id: user.team_id, user: user.name, set_status: true, only_first_day: false)
24
41
  end
25
42
  end
26
43
  end
@@ -11,7 +11,8 @@ class SlackSmartBot
11
11
  command_ids = get_command_ids()
12
12
  if command_ids.values.flatten.include?(command_id)
13
13
  if @access_channels.key?(channel) and @access_channels[channel].key?(command_id) and @access_channels[channel][command_id].size > 0
14
- respond "Only these users have access to `#{command_id}` in this channel: <@#{@access_channels[channel][command_id].join(">, <@")}>"
14
+ names = @access_channels[channel][command_id].map { |a| a.split('_')[1..-1].join('_') }
15
+ respond "Only these users have access to `#{command_id}` in this channel: <@#{names.join(">, <@")}>"
15
16
  elsif @access_channels.key?(channel) and @access_channels[channel].key?(command_id) and @access_channels[channel][command_id].empty?
16
17
  respond "`#{command_id}` is not possible to be used in this channel. Please contact an admin if you want to use it."
17
18
  else
@@ -11,22 +11,26 @@ class SlackSmartBot
11
11
 
12
12
  messages = []
13
13
  admins = []
14
+ team_id_admins = []
14
15
  channels = get_channels()
15
16
  channel_found = channels.detect { |c| c.id == channel }
16
17
  if !channel_found.nil? and channel_found.creator.to_s != ''
17
- messages << "*Channel creator*: <@#{channel_found.creator}>"
18
- creator_info = @users.select{|u| u.id == channel_found.creator or (u.key?(:enterprise_user) and u.enterprise_user.id == channel_found.creator)}[-1]
18
+ messages << "*Channel creator*: <@#{channel_found.creator}>"
19
+ creator_info = find_user(channel_found.creator)
19
20
  else
20
- creator_info = {name: []}
21
+ creator_info = {name: [], team_id: []}
21
22
  end
22
23
  messages << "*Master admins*: <@#{config.masters.join('>, <@')}>"
23
24
  if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
24
25
  admins = config.admins.dup
26
+ team_id_admins = config.team_id_admins.dup
25
27
  end
26
28
  if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
27
- admins = (@admins_channels[channel] + admins).uniq
29
+ team_id_admins = (@admins_channels[channel] + team_id_admins).uniq
30
+ admins = (@admins_channels[channel].map { |a| a.split('_')[1..-1].join('_') } + admins).uniq
28
31
  end
29
32
  admins = admins - config.masters - [creator_info.name]
33
+ team_id_admins = team_id_admins - config.team_id_masters - ["#{creator_info.team_id}_#{creator_info.name}"]
30
34
  messages << "*Admins*: <@#{admins.join('>, <@')}>" unless admins.empty?
31
35
  respond messages.join("\n")
32
36
  end
@@ -16,9 +16,9 @@ class SlackSmartBot
16
16
  else
17
17
  dest = Thread.current[:dest]
18
18
  end
19
-
19
+
20
20
  if type == 'all'
21
- if config.masters.include?(user.name) and typem==:on_dm
21
+ if config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm
22
22
  channels = Dir.entries("#{config.path}/announcements/")
23
23
  channels.select! {|i| i[/\.csv$/]}
24
24
  else
@@ -45,14 +45,14 @@ class SlackSmartBot
45
45
  end
46
46
  end
47
47
  if has_access?(__method__, user)
48
- if (channel_id!=Thread.current[:dest] and config.masters.include?(user.name) and typem==:on_dm) or publish
48
+ if (channel_id!=Thread.current[:dest] and config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm) or publish
49
49
  see_announcements_on_demand = true
50
50
  else
51
51
  see_announcements_on_demand = false
52
52
  end
53
53
  if channel_id == Thread.current[:dest] or see_announcements_on_demand or publish #master admin user or publish_announcements
54
54
  if File.exist?("#{config.path}/announcements/#{channel_id}.csv") and (!@announcements.key?(channel_id) or see_announcements_on_demand) # to force to have the last version that maybe was updated by other SmartBot in case of demand
55
- t = CSV.table("#{config.path}/announcements/#{channel_id}.csv", headers: ['message_id', 'user_deleted', 'user_created', 'date', 'time', 'type', 'message'])
55
+ t = CSV.table("#{config.path}/announcements/#{channel_id}.csv", headers: ['message_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'message'])
56
56
  @announcements[channel_id] = t
57
57
  end
58
58
  if @announcements.key?(channel_id)
@@ -70,7 +70,7 @@ class SlackSmartBot
70
70
  user_created = "<@#{m[:user_created]}>"
71
71
  else
72
72
  user_created = m[:user_created]
73
- user_info = @users.select { |u| u.name == user_created or (u.key?(:enterprise_user) and u.enterprise_user.name == user_created) }[-1]
73
+ user_info = find_user(user_created)
74
74
  user_created = user_info.profile.display_name unless user_info.nil?
75
75
  end
76
76
  if type == 'all' and channel_id[0]=='D'
@@ -9,18 +9,18 @@ class SlackSmartBot
9
9
  else
10
10
  channel = Thread.current[:using_channel]
11
11
  end
12
-
12
+
13
13
  files = Dir["#{config.stats_path}.*.log"].sort.reverse[0..1]
14
14
  if files.empty?
15
15
  respond "There is no data stored."
16
16
  else
17
17
  count_commands = {}
18
-
18
+
19
19
  files.each do |file|
20
20
  CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
21
21
  row[:dest_channel_id] = row[:bot_channel_id] if row[:dest_channel_id].to_s[0] == "D"
22
- if ((only_mine and row[:user_name]==user.name) or (!only_mine and !config.masters.include?(row[:user_name]))) and
23
- row[:dest_channel_id] == channel and !row[:user_name].include?('routine/') and
22
+ if ((only_mine and row[:user_name]==user.name) or (!only_mine and !config.team_id_masters.include?("#{row[:team_id]}_#{row[:user_name]}"))) and
23
+ row[:dest_channel_id] == channel and !row[:user_name].include?('routine/') and
24
24
  row[:command] != 'dont_understand'
25
25
  row[:command] = 'bot_help' if row[:command] == 'bot_rules'
26
26
  count_commands[row[:command]] ||= 0
@@ -12,7 +12,7 @@ class SlackSmartBot
12
12
 
13
13
  general_message = "\nRelated commands `share messages /RegExp/ on #CHANNEL`, `share messages \"TEXT\" on #CHANNEL`, `delete share ID`"
14
14
  if File.exist?("#{config.path}/shares/#{@channels_name[channel]}.csv")
15
- t = CSV.table("#{config.path}/shares/#{@channels_name[channel]}.csv", headers: ['share_id', 'user_deleted', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
15
+ t = CSV.table("#{config.path}/shares/#{@channels_name[channel]}.csv", headers: ['share_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
16
16
  message =[]
17
17
  t.each do |m|
18
18
  if m[:user_deleted] == ''