slack-smart-bot 1.15.1 → 1.15.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -1
- data/img/chat_gpt_attach_image.png +0 -0
- data/lib/slack/smart-bot/ai/open_ai/models.rb +19 -10
- data/lib/slack/smart-bot/ai/open_ai/send_gpt_chat.rb +14 -10
- data/lib/slack/smart-bot/comm/dont_understand.rb +23 -6
- data/lib/slack/smart-bot/comm/get_user_info.rb +9 -10
- data/lib/slack/smart-bot/comm/respond.rb +56 -28
- data/lib/slack/smart-bot/comm/send_file.rb +17 -6
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat.rb +885 -129
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_add_collaborator.rb +3 -3
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_copy_session.rb +132 -15
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_delete_session.rb +1 -1
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_get_prompts.rb +50 -12
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_list_sessions.rb +99 -34
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_share_session.rb +12 -2
- data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_use_model.rb +36 -25
- data/lib/slack/smart-bot/commands/general/bot_help.rb +29 -24
- data/lib/slack/smart-bot/commands/general/poster.rb +0 -1
- data/lib/slack/smart-bot/commands/general/see_announcements.rb +1 -1
- data/lib/slack/smart-bot/commands/general/summarize.rb +22 -8
- data/lib/slack/smart-bot/commands/general_bot_commands.rb +156 -55
- data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +13 -11
- data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +21 -17
- data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +2 -2
- data/lib/slack/smart-bot/commands.rb +19 -19
- data/lib/slack/smart-bot/process_first.rb +38 -35
- data/lib/slack/smart-bot/treat_message.rb +57 -56
- data/lib/slack/smart-bot/utils/download_http_content.rb +91 -0
- data/lib/slack/smart-bot/utils/get_authorizations.rb +41 -0
- data/lib/slack/smart-bot/utils/get_keywords.rb +33 -0
- data/lib/slack/smart-bot/utils/get_openai_sessions.rb +46 -6
- data/lib/slack/smart-bot/utils/get_teams.rb +9 -1
- data/lib/slack/smart-bot/utils/save_stats.rb +13 -5
- data/lib/slack/smart-bot/utils/transform_to_slack_markdown.rb +36 -0
- data/lib/slack/smart-bot/utils/update_openai_sessions.rb +9 -4
- data/lib/slack/smart-bot/utils.rb +48 -44
- data/lib/slack-smart-bot.rb +10 -9
- data/whats_new.txt +27 -1
- metadata +63 -2
@@ -7,42 +7,42 @@ class SlackSmartBot
|
|
7
7
|
unless data.text.to_s.match(/\A\s*\z/)
|
8
8
|
#to remove italic, bold... from data.text since there is no method on slack api
|
9
9
|
if remove_blocks and !data.blocks.nil? and data.blocks.size > 0
|
10
|
-
data_text =
|
10
|
+
data_text = ""
|
11
11
|
data.blocks.each do |b|
|
12
|
-
if b.type ==
|
12
|
+
if b.type == "rich_text"
|
13
13
|
if b.elements.size > 0
|
14
14
|
b.elements.each do |e|
|
15
|
-
if e.type ==
|
16
|
-
if e.elements.size > 0 and (e.elements.type.uniq - [
|
17
|
-
data_text +=
|
15
|
+
if e.type == "rich_text_section" or e.type == "rich_text_preformatted"
|
16
|
+
if e.elements.size > 0 and (e.elements.type.uniq - ["link", "text", "user", "channel"]) == []
|
17
|
+
data_text += "```" if e.type == "rich_text_preformatted"
|
18
18
|
e.elements.each do |el|
|
19
|
-
if el.type ==
|
19
|
+
if el.type == "text"
|
20
20
|
data_text += el.text
|
21
|
-
elsif el.type ==
|
21
|
+
elsif el.type == "user"
|
22
22
|
data_text += "<@#{el.user_id}>"
|
23
|
-
elsif el.type ==
|
23
|
+
elsif el.type == "channel"
|
24
24
|
tch = data.text.scan(/(<##{el.channel_id}\|[^\>]*>)/).flatten.first
|
25
25
|
data_text += tch.to_s
|
26
26
|
else
|
27
27
|
data_text += el.url
|
28
28
|
end
|
29
29
|
end
|
30
|
-
data_text +=
|
30
|
+
data_text += "```" if e.type == "rich_text_preformatted"
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
|
-
data.text = data_text unless data_text ==
|
37
|
+
data.text = data_text unless data_text == ""
|
38
38
|
end
|
39
39
|
data.text = CGI.unescapeHTML(data.text)
|
40
40
|
data.text.gsub!("\u00A0", " ") #to change (asc char 160) into blank space
|
41
41
|
end
|
42
|
-
data.text.gsub!(
|
43
|
-
data.text.gsub!(
|
44
|
-
data.text.gsub!(
|
45
|
-
data.text.gsub!(
|
42
|
+
data.text.gsub!("‘", "'")
|
43
|
+
data.text.gsub!("’", "'")
|
44
|
+
data.text.gsub!("“", '"')
|
45
|
+
data.text.gsub!("”", '"')
|
46
46
|
rescue Exception => exc
|
47
47
|
@logger.warn "Impossible to unescape or clean format for data.text:#{data.text}"
|
48
48
|
@logger.warn exc.inspect
|
@@ -50,8 +50,8 @@ class SlackSmartBot
|
|
50
50
|
|
51
51
|
unless data.key?(:routine)
|
52
52
|
data.routine = false
|
53
|
-
data.routine_name =
|
54
|
-
data.routine_type =
|
53
|
+
data.routine_name = ""
|
54
|
+
data.routine_type = ""
|
55
55
|
end
|
56
56
|
if config[:testing] and config.on_master_bot and !@buffered
|
57
57
|
@buffered = true
|
@@ -59,7 +59,8 @@ class SlackSmartBot
|
|
59
59
|
f.puts "|#{data.channel}|#{data.thread_ts}|#{data.user}|#{data.user_name}|#{data.text}"
|
60
60
|
}
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
|
+
if data.key?(:dest) and data.dest.to_s != "" # for run routines and publish on different channels
|
63
64
|
dest = data.dest
|
64
65
|
elsif data.channel[0] == "D" or data.channel[0] == "C" or data.channel[0] == "G" #Direct message or Channel or Private Channel
|
65
66
|
dest = data.channel
|
@@ -73,9 +74,9 @@ class SlackSmartBot
|
|
73
74
|
end
|
74
75
|
|
75
76
|
#open ai chat gpt and shared messages as an input
|
76
|
-
if data.text.match?(/\A\s*(^|!!|!)?\s*(\?|\?\?)\s*/im) and !data.attachments.nil? and data.attachments.size > 0 and !data.attachments[0].text.nil? and data.attachments[0].text !=
|
77
|
+
if data.text.match?(/\A\s*(^|!!|!)?\s*(\?|\?\?)\s*/im) and !data.attachments.nil? and data.attachments.size > 0 and !data.attachments[0].text.nil? and data.attachments[0].text != ""
|
77
78
|
data.attachments.each_with_index do |att, i|
|
78
|
-
if !att.text.nil? and att.text !=
|
79
|
+
if !att.text.nil? and att.text != ""
|
79
80
|
data.text += "\n#{att.text}"
|
80
81
|
end
|
81
82
|
end
|
@@ -83,6 +84,7 @@ class SlackSmartBot
|
|
83
84
|
if !dest.nil? and config.on_master_bot and !data.text.nil? and data.text.match(/^ping from (.+)\s*$/) and data.user == config[:nick_id]
|
84
85
|
@pings << $1
|
85
86
|
end
|
87
|
+
|
86
88
|
if config.on_master_bot and @vacations_check != Time.now.strftime("%Y%m%d%H") #every hour since depends on user's time zone
|
87
89
|
@vacations_check = Time.now.strftime("%Y%m%d%H")
|
88
90
|
t = Thread.new do
|
@@ -92,19 +94,22 @@ class SlackSmartBot
|
|
92
94
|
typem = :dont_treat
|
93
95
|
|
94
96
|
@users = get_users() if @users.empty?
|
95
|
-
if data.key?(:bot_id) and data.bot_id.to_s!=
|
97
|
+
if data.key?(:bot_id) and data.bot_id.to_s != ""
|
96
98
|
if @slack_bots.key?(data.bot_id) #bot or workflow
|
97
99
|
data.user = @slack_bots[data.bot_id]
|
98
100
|
else
|
99
101
|
bot_info = get_user_info(data.bot_id, is_bot: true)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
102
|
+
if bot_info.nil? or bot_info.empty?
|
103
|
+
@logger.warn "Bot not found on users with data: #{data.inspect}"
|
104
|
+
else
|
105
|
+
@slack_bots[data.bot_id] = bot_info.user.id
|
106
|
+
data.user = bot_info.user.id
|
107
|
+
@users << bot_info.user unless bot_info.nil? or bot_info.empty?
|
104
108
|
end
|
105
109
|
end
|
106
110
|
end
|
107
|
-
|
111
|
+
|
112
|
+
if data.nil? or data.user.nil? or data.user.to_s == ""
|
108
113
|
user_info = nil
|
109
114
|
else
|
110
115
|
user_info = find_user(data.user, get_sso_user_name: true)
|
@@ -115,27 +120,26 @@ class SlackSmartBot
|
|
115
120
|
end
|
116
121
|
end
|
117
122
|
if !dest.nil? and !data.text.nil? and !data.text.to_s.match?(/\A\s*\z/)
|
118
|
-
|
119
123
|
get_bots_created()
|
120
124
|
if data.channel[0] == "D" and !data.text.to_s.match?(/^\s*<@#{config[:nick_id]}>\s+/) and
|
121
|
-
|
125
|
+
(data.text.to_s.match?(/^\s*(on)?\s*<#\w+\|[^>]*>/i) or data.text.to_s.match?(/^\s*(on)?\s*#\w+/i))
|
122
126
|
data.text = "<@#{config[:nick_id]}> " + data.text.to_s
|
123
127
|
end
|
124
128
|
#todo: we need to add mixed channels: @smart-bot on private1 #bot1cm <#CXDDFRDDF|bot2cu>: echo A
|
125
129
|
if data.text.match(/\A\^\^+/) # to open a thread it will be only when starting by single ^
|
126
130
|
typem = :dont_treat
|
127
131
|
elsif data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((<#\w+\|[^>]*>\s*)+)\s*:?\s*(.*)/im) or
|
128
|
-
|
129
|
-
|
132
|
+
data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((#[a-zA-Z0-9\-\_]+\s*)+)\s*:?\s*(.*)/im) or
|
133
|
+
data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?(([a-zA-Z0-9\-\_]+\s*)+)\s*:\s*(.*)/im)
|
130
134
|
channels_rules = $2 #multiple channels @smart-bot on #channel1 #channel2 echo AAA
|
131
135
|
data_text = $4
|
132
|
-
channel_rules_name =
|
133
|
-
channel_rules =
|
136
|
+
channel_rules_name = ""
|
137
|
+
channel_rules = ""
|
134
138
|
channels_arr = channels_rules.scan(/<#(\w+)\|([^>]*)>/)
|
135
139
|
if channels_arr.size == 0
|
136
140
|
channels_arr = []
|
137
141
|
channels_rules.scan(/([^\s]+)/).each do |cn|
|
138
|
-
cna = cn.join.gsub(
|
142
|
+
cna = cn.join.gsub("#", "")
|
139
143
|
if @channels_name.key?(cna)
|
140
144
|
channels_arr << [cna, @channels_name[cna]]
|
141
145
|
else
|
@@ -144,8 +148,8 @@ class SlackSmartBot
|
|
144
148
|
end
|
145
149
|
else
|
146
150
|
channels_arr.each do |row|
|
147
|
-
row[0] = @channels_id[row[1]] if row[0] ==
|
148
|
-
row[1] = @channels_name[row[0]] if row[1] ==
|
151
|
+
row[0] = @channels_id[row[1]] if row[0] == ""
|
152
|
+
row[1] = @channels_name[row[0]] if row[1] == ""
|
149
153
|
end
|
150
154
|
end
|
151
155
|
|
@@ -165,7 +169,6 @@ class SlackSmartBot
|
|
165
169
|
break
|
166
170
|
end
|
167
171
|
end
|
168
|
-
|
169
172
|
elsif data.channel == @master_bot_id
|
170
173
|
if config.on_master_bot #only to be treated on master bot channel
|
171
174
|
typem = :on_master
|
@@ -177,7 +180,7 @@ class SlackSmartBot
|
|
177
180
|
elsif data.channel[0] == "D" #Direct message
|
178
181
|
get_rules_imported()
|
179
182
|
if @rules_imported.key?("#{user_info.team_id}_#{user_info.name}") && @rules_imported["#{user_info.team_id}_#{user_info.name}"].key?(user_info.name) and
|
180
|
-
|
183
|
+
@bots_created.key?(@rules_imported["#{user_info.team_id}_#{user_info.name}"][user_info.name])
|
181
184
|
if @channel_id == @rules_imported["#{user_info.team_id}_#{user_info.name}"][user_info.name]
|
182
185
|
#only to be treated by the channel we are 'using'
|
183
186
|
typem = :on_dm
|
@@ -189,9 +192,9 @@ class SlackSmartBot
|
|
189
192
|
elsif data.channel[0] == "C" or data.channel[0] == "G"
|
190
193
|
#only to be treated on the channel of the bot. excluding running ruby
|
191
194
|
if !config.on_master_bot and @bots_created.key?(@channel_id) and @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
|
192
|
-
|
195
|
+
!data.text.match?(/^!?\s*(ruby|code)\s+/) and !data.text.match?(/^!?!?\s*(ruby|code)\s+/) and !data.text.match?(/^\^?\s*(ruby|code)\s+/)
|
193
196
|
typem = :on_extended
|
194
|
-
elsif config.on_master_bot and (data.text.match?(/^!?\s*(ruby|code)\s+/) or data.text.match?(/^!?!?\s*(ruby|code)\s+/) or data.text.match?(/^\^?\s*(ruby|code)\s+/)
|
197
|
+
elsif config.on_master_bot and (data.text.match?(/^!?\s*(ruby|code)\s+/) or data.text.match?(/^!?!?\s*(ruby|code)\s+/) or data.text.match?(/^\^?\s*(ruby|code)\s+/))
|
195
198
|
#or in case of running ruby, the master bot
|
196
199
|
@bots_created.each do |k, v|
|
197
200
|
if v.key?(:extended) and v[:extended].include?(@channels_name[data.channel])
|
@@ -209,25 +212,25 @@ class SlackSmartBot
|
|
209
212
|
end
|
210
213
|
if data.channel[0] == "G" and config.on_master_bot and !extended #private group
|
211
214
|
typem = :on_pg
|
212
|
-
elsif data.channel[0] ==
|
215
|
+
elsif data.channel[0] == "C" and config.on_master_bot and !extended #public group
|
213
216
|
typem = :on_pub
|
214
217
|
end
|
215
218
|
end
|
216
219
|
end
|
217
220
|
load "#{config.path}/rules/general_commands.rb" if File.exist?("#{config.path}/rules/general_commands.rb") and @datetime_general_commands != File.mtime("#{config.path}/rules/general_commands.rb")
|
218
|
-
eval(File.new(config.path + config.rules_file).read) if !defined?(rules) and File.exist?(config.path+config.rules_file) and !config.rules_file.empty?
|
221
|
+
eval(File.new(config.path + config.rules_file).read) if !defined?(rules) and File.exist?(config.path + config.rules_file) and !config.rules_file.empty?
|
219
222
|
unless typem == :dont_treat or user_info.nil?
|
220
223
|
if (Time.now - @last_activity_check) > TIMEOUT_LISTENING #every 30 minutes
|
221
224
|
@last_activity_check = Time.now
|
222
|
-
@listening.each do |k,v|
|
225
|
+
@listening.each do |k, v|
|
223
226
|
unless k == :threads
|
224
227
|
v.each do |kk, vv|
|
225
228
|
if (Time.now - vv) > TIMEOUT_LISTENING
|
226
229
|
if @listening[:threads].key?(kk) && @active_chat_gpt_sessions.key?(k) &&
|
227
|
-
|
230
|
+
@active_chat_gpt_sessions[k].key?(kk)
|
228
231
|
unreact :running, kk, channel: @listening[:threads][kk]
|
229
232
|
session_name = @active_chat_gpt_sessions[k][kk]
|
230
|
-
chatgpt_message = "ChatGPT session has been terminated due to inactivity."
|
233
|
+
chatgpt_message = ":information_source: ChatGPT session has been terminated due to inactivity."
|
231
234
|
if !session_name.to_s.empty?
|
232
235
|
chatgpt_message += "\n\nIf you want to start it again on this thread call `chatgpt #{session_name}`"
|
233
236
|
end
|
@@ -259,16 +262,15 @@ class SlackSmartBot
|
|
259
262
|
@answer[team_id_user][qdest] = data.text
|
260
263
|
@questions[team_id_user] = data.text # to be backwards compatible #todo remove it when 2.0
|
261
264
|
end
|
262
|
-
elsif @repl_sessions.key?(team_id_user) and data.channel
|
263
|
-
|
264
|
-
|
265
|
-
|
265
|
+
elsif @repl_sessions.key?(team_id_user) and data.channel == @repl_sessions[team_id_user][:dest] and
|
266
|
+
((@repl_sessions[team_id_user][:on_thread] and data.thread_ts == @repl_sessions[team_id_user][:thread_ts]) or
|
267
|
+
(!@repl_sessions[team_id_user][:on_thread] and data.thread_ts.to_s == ""))
|
266
268
|
if data.text.match(/^\s*```(.*)```\s*$/im)
|
267
|
-
|
269
|
+
@repl_sessions[team_id_user][:command] = $1
|
268
270
|
else
|
269
271
|
@repl_sessions[team_id_user][:command] = data.text
|
270
272
|
end
|
271
|
-
command =
|
273
|
+
command = "repl"
|
272
274
|
else
|
273
275
|
command = data.text
|
274
276
|
end
|
@@ -276,13 +278,13 @@ class SlackSmartBot
|
|
276
278
|
if command.match(/\A\s*```(.*)```\s*\z/im)
|
277
279
|
command = $1
|
278
280
|
elsif command.size >= 2 and
|
279
|
-
|
281
|
+
((command[0] == "`" and command[-1] == "`") or (command[0] == "*" and command[-1] == "*") or (command[0] == "_" and command[-1] == "_"))
|
280
282
|
command = command[1..-2]
|
281
283
|
end
|
282
284
|
|
283
285
|
#ruby file attached
|
284
286
|
if !data.files.nil? and data.files.size == 1 and
|
285
|
-
|
287
|
+
(command.match?(/^(ruby|code)\s*$/) or (command.match?(/^\s*$/) and data.files[0].filetype == "ruby") or
|
286
288
|
(typem == :on_call and data.files[0].filetype == "ruby"))
|
287
289
|
res = Faraday.new("https://files.slack.com", headers: { "Authorization" => "Bearer #{config[:token]}" }).get(data.files[0].url_private)
|
288
290
|
command += " ruby" if command != "ruby"
|
@@ -326,11 +328,11 @@ class SlackSmartBot
|
|
326
328
|
@logger.fatal stack
|
327
329
|
end
|
328
330
|
else
|
329
|
-
if user_info.nil? and data.user.to_s!=
|
331
|
+
if user_info.nil? and data.user.to_s != ""
|
330
332
|
@logger.warn "Pay attention there is no user on users with id #{data.user}"
|
331
333
|
end
|
332
334
|
if !config.on_master_bot and !dest.nil? and (data.channel == @master_bot_id or dest[0] == "D") and
|
333
|
-
|
335
|
+
data.text.match?(/^\s*(!|!!|\^)?\s*bot\s+status\s*$/i) and @admin_users_id.include?(data.user)
|
334
336
|
respond "ping from #{config.channel}", dest
|
335
337
|
elsif !config.on_master_bot and !dest.nil? and data.user == config[:nick_id] and dest == @master_bot_id
|
336
338
|
# to treat on other bots the status messages populated on master bot
|
@@ -370,7 +372,6 @@ class SlackSmartBot
|
|
370
372
|
end
|
371
373
|
end
|
372
374
|
end
|
373
|
-
|
374
375
|
when /^Bot has been (closed|killed) by/i
|
375
376
|
sleep 2
|
376
377
|
get_bots_created()
|
@@ -409,9 +410,9 @@ class SlackSmartBot
|
|
409
410
|
if @status == :exit
|
410
411
|
@listening[:threads].each do |thread_ts, channel_thread|
|
411
412
|
unreact :running, thread_ts, channel: channel_thread
|
412
|
-
respond "ChatGPT session closed since SmartBot is going to be closed", channel_thread, thread_ts: thread_ts
|
413
|
+
respond "ChatGPT session closed since SmartBot is going to be closed.\nCheck <##{@channels_id[config.status_channel]}>", channel_thread, thread_ts: thread_ts
|
413
414
|
end
|
414
|
-
@logger.info
|
415
|
+
@logger.info "Game over!"
|
415
416
|
sleep 3
|
416
417
|
exit!
|
417
418
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
def download_http_content(url, authorizations, team_id_user_creator = nil, session_name = nil)
|
3
|
+
begin
|
4
|
+
url_message = ""
|
5
|
+
parsed_url = URI.parse(url)
|
6
|
+
|
7
|
+
headers = {}
|
8
|
+
|
9
|
+
authorizations.each do |key, value|
|
10
|
+
if key.match?(/^(https:\/\/|http:\/\/)?#{parsed_url.host.gsub(".", '\.')}/)
|
11
|
+
value.each do |k, v|
|
12
|
+
headers[k.to_sym] = v unless k == :host
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
extra_message = ""
|
18
|
+
domain = "#{parsed_url.scheme}://#{parsed_url.host}"
|
19
|
+
if parsed_url.host.match?(/(drive|docs)\.google\.com/) #download the file
|
20
|
+
if url.include?("/file/d/")
|
21
|
+
gdrive_id = url.split("/d/")[1].split("/")[0]
|
22
|
+
url = "https://drive.google.com/uc?id=#{gdrive_id}&export=download"
|
23
|
+
end
|
24
|
+
io = URI.open(url, headers)
|
25
|
+
is_pdf = io.meta["content-type"].to_s == "application/pdf" || io.meta["content-disposition"].to_s.include?("pdf")
|
26
|
+
if is_pdf
|
27
|
+
require "pdf-reader"
|
28
|
+
if io.meta["content-disposition"].to_s.include?("pdf")
|
29
|
+
pdf_filename = io.meta["content-disposition"].split("filename=")[1].strip
|
30
|
+
extra_message = " PDF file: #{pdf_filename}"
|
31
|
+
end
|
32
|
+
reader = PDF::Reader.new(io)
|
33
|
+
text = reader.pages.map(&:text).join("\n")
|
34
|
+
else
|
35
|
+
is_docx = io.meta["content-type"].to_s == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || io.meta["content-disposition"].to_s.include?("docx")
|
36
|
+
if is_docx
|
37
|
+
require "docx"
|
38
|
+
if io.meta["content-disposition"].to_s.include?("docx")
|
39
|
+
docx_filename = io.meta["content-disposition"].split("filename=")[1].strip
|
40
|
+
extra_message = " DOCX file: #{docx_filename}"
|
41
|
+
end
|
42
|
+
doc = Docx::Document.open(io)
|
43
|
+
text = doc.paragraphs.map(&:to_s).join("\n")
|
44
|
+
else #text
|
45
|
+
text = io.read
|
46
|
+
text_filename = io.meta["content-disposition"].split("filename=")[1].strip if io.meta["content-disposition"].to_s.include?("filename=")
|
47
|
+
extra_message = " Text file: #{text_filename}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
io.close
|
51
|
+
elsif parsed_url.path.match?(/\.pdf$/)
|
52
|
+
require "pdf-reader"
|
53
|
+
io = URI.open(url, headers)
|
54
|
+
reader = PDF::Reader.new(io)
|
55
|
+
text = reader.pages.map(&:text).join("\n")
|
56
|
+
io.close
|
57
|
+
elsif parsed_url.path.match?(/\.docx?$/)
|
58
|
+
require "docx"
|
59
|
+
io = URI.open(url, headers)
|
60
|
+
doc = Docx::Document.open(io)
|
61
|
+
text = doc.paragraphs.map(&:to_s).join("\n")
|
62
|
+
io.close
|
63
|
+
else
|
64
|
+
parsed_url += "/" if parsed_url.path == ""
|
65
|
+
http = NiceHttp.new(host: domain, headers: headers, log: :no)
|
66
|
+
path = parsed_url.path
|
67
|
+
path += "?#{parsed_url.query}" if parsed_url.query
|
68
|
+
response = http.get(path)
|
69
|
+
html_doc = Nokogiri::HTML(response.body)
|
70
|
+
html_doc.search("script, style").remove
|
71
|
+
text = html_doc.text.strip
|
72
|
+
text.gsub!(/^\s*$/m, "")
|
73
|
+
http.close
|
74
|
+
end
|
75
|
+
if !session_name.nil? and !team_id_user_creator.nil?
|
76
|
+
if (!@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name].key?(:live_content) or
|
77
|
+
@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:live_content].nil? or
|
78
|
+
!@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:live_content].include?(url)) and
|
79
|
+
(!@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name].key?(:static_content) or
|
80
|
+
@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:static_content].nil? or
|
81
|
+
!@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:static_content].include?(url))
|
82
|
+
url_message = "> #{url}#{extra_message}: content extracted and added to prompt"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
rescue Exception => e
|
86
|
+
text = "Error: #{e.message}"
|
87
|
+
url_message = "> #{url}: #{text}\n"
|
88
|
+
end
|
89
|
+
return text, url_message
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
def get_authorizations(session_name, team_id_user_creator)
|
3
|
+
team_id_user = Thread.current[:team_id_user]
|
4
|
+
|
5
|
+
authorizations = {}
|
6
|
+
|
7
|
+
if config.key?(:authorizations)
|
8
|
+
config[:authorizations].each do |key, value|
|
9
|
+
if value.key?(:host)
|
10
|
+
authorizations[value[:host]] = value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if @personal_settings_hash.key?(team_id_user) and @personal_settings_hash[team_id_user].key?(:authorizations)
|
16
|
+
@personal_settings_hash[team_id_user][:authorizations].each do |key, value|
|
17
|
+
if value.key?(:host)
|
18
|
+
authorizations[value[:host]] = value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if !session_name.nil?
|
24
|
+
if team_id_user != team_id_user_creator
|
25
|
+
team_id_to_use = team_id_user_creator
|
26
|
+
else
|
27
|
+
team_id_to_use = team_id_user
|
28
|
+
end
|
29
|
+
if @open_ai[team_id_to_use][:chat_gpt][:sessions].key?(session_name) and
|
30
|
+
@open_ai[team_id_to_use][:chat_gpt][:sessions][session_name].key?(:authorizations) and
|
31
|
+
!@open_ai[team_id_to_use][:chat_gpt][:sessions][session_name][:authorizations].nil?
|
32
|
+
@open_ai[team_id_to_use][:chat_gpt][:sessions][session_name][:authorizations].each do |host, header|
|
33
|
+
authorizations[host] ||= {}
|
34
|
+
authorizations[host].merge!(header)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return authorizations
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
def get_keywords(sentence, list_avoid: [])
|
3
|
+
require "engtagger"
|
4
|
+
keywords = []
|
5
|
+
unless sentence.to_s.strip.empty?
|
6
|
+
# Initialize the POS tagger
|
7
|
+
tagger = EngTagger.new
|
8
|
+
tagged_sentence = tagger.add_tags(sentence)
|
9
|
+
unless tagged_sentence.nil?
|
10
|
+
|
11
|
+
# Extract nouns and proper nouns from the sentence
|
12
|
+
nouns = tagger.get_nouns(tagged_sentence).keys
|
13
|
+
proper_nouns = tagger.get_proper_nouns(tagged_sentence).keys
|
14
|
+
adjectives = tagger.get_adjectives(tagged_sentence).keys
|
15
|
+
ids = sentence.scan(/([\w]+\-[\w\-]+)/)
|
16
|
+
|
17
|
+
# Combine nouns and proper nouns to create the list of keywords
|
18
|
+
keywords = (nouns + proper_nouns + adjectives + ids.flatten).uniq
|
19
|
+
|
20
|
+
#delete all keywords that are one or two characters long
|
21
|
+
keywords.delete_if { |keyword| keyword.length < 3 }
|
22
|
+
# delete all keywords that are in the list_avoid /word/i
|
23
|
+
if !list_avoid.empty?
|
24
|
+
keywords.delete_if { |keyword| list_avoid.any? { |avoid| keyword.match?(/#{avoid}/i) } }
|
25
|
+
end
|
26
|
+
|
27
|
+
#remove special characters from the keywords
|
28
|
+
keywords.map! { |keyword| keyword.gsub(/[^\w\-_]/i, "") }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
return keywords
|
32
|
+
end
|
33
|
+
end
|
@@ -15,21 +15,21 @@ class SlackSmartBot
|
|
15
15
|
files += Dir.glob(File.join(folder, "o_*.yaml"))
|
16
16
|
end
|
17
17
|
@datetime_open_ai_file ||= {}
|
18
|
-
|
18
|
+
|
19
19
|
files.each do |file|
|
20
20
|
if !defined?(@datetime_open_ai_file) or !@datetime_open_ai_file.key?(file) or @datetime_open_ai_file[file] != File.mtime(file)
|
21
|
-
|
21
|
+
open_ai_user = YAML.load(Utils::Encryption.decrypt(File.read(file), config))
|
22
22
|
#user_file will be the team_id _ + the user name
|
23
23
|
user_team_id = File.basename(File.dirname(file))
|
24
24
|
user_file = user_team_id + "_" + File.basename(file).gsub("o_","").gsub(".yaml","")
|
25
|
-
|
25
|
+
|
26
26
|
if @open_ai.key?(user_file) and @open_ai[user_file].key?(:chat_gpt) and @open_ai[user_file][:chat_gpt].key?(:sessions) and
|
27
27
|
@open_ai[user_file][:chat_gpt][:sessions].key?("")
|
28
|
-
temp_session = @open_ai[user_file][:chat_gpt][:sessions][""].deep_copy
|
28
|
+
temp_session = @open_ai[user_file][:chat_gpt][:sessions][""].deep_copy
|
29
29
|
else
|
30
30
|
temp_session = nil
|
31
31
|
end
|
32
|
-
@open_ai[user_file] =
|
32
|
+
@open_ai[user_file] = open_ai_user
|
33
33
|
@open_ai[user_file][:chat_gpt][:sessions][""] = temp_session unless temp_session.nil?
|
34
34
|
@datetime_open_ai_file[file] = File.mtime(file)
|
35
35
|
end
|
@@ -40,7 +40,47 @@ class SlackSmartBot
|
|
40
40
|
@ai_gpt ||= {}
|
41
41
|
@ai_gpt[team_id_user] ||= {}
|
42
42
|
content = File.read(file_name)
|
43
|
-
|
43
|
+
#The file contains an array of hashes with the messages
|
44
|
+
#read it and store it as ruby code
|
45
|
+
session = Utils::Encryption.decrypt(content, config).force_encoding("UTF-8")
|
46
|
+
session_new_format = []
|
47
|
+
if session.to_s.match?(/^Me>/) #old format to be backwards compatible
|
48
|
+
session = session.split("\n")
|
49
|
+
new_value = ""
|
50
|
+
type = ""
|
51
|
+
session.each do |s|
|
52
|
+
s.gsub!('"', '\"')
|
53
|
+
if s.match?(/^(Me|chatGPT)> /)
|
54
|
+
if new_value != ""
|
55
|
+
#escape new_value to be able to store it as json
|
56
|
+
session_new_format << "{\"role\": \"#{type}\", \"content\": [{\"type\": \"text\", \"text\": #{new_value.to_json}}]}"
|
57
|
+
new_value = ""
|
58
|
+
end
|
59
|
+
if s.match?(/^Me> /)
|
60
|
+
type = "user"
|
61
|
+
else
|
62
|
+
type = "assistant"
|
63
|
+
end
|
64
|
+
s.gsub!(/^(Me|chatGPT)> /, "")
|
65
|
+
#s.gsub!("'", "\\'")
|
66
|
+
new_value += s + "\n"
|
67
|
+
else
|
68
|
+
#s.gsub!("'", "\\'")
|
69
|
+
new_value += s + "\n"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
session_new_format << "{\"role\": \"#{type}\", \"content\": [{\"type\": \"text\", \"text\": #{new_value.to_json}}]}"
|
73
|
+
end
|
74
|
+
if session_new_format.empty?
|
75
|
+
#each line is json, so we need to convert it to ruby using json parser
|
76
|
+
session = session.split("\n").map{|s| JSON.parse(s, symbolize_names: true)}
|
77
|
+
else
|
78
|
+
session = []
|
79
|
+
session_new_format.each do |s|
|
80
|
+
session << JSON.parse(s, symbolize_names: true)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@ai_gpt[team_id_user][session_name] = session.deep_copy
|
44
84
|
end
|
45
85
|
end
|
46
86
|
end
|
@@ -24,5 +24,13 @@ class SlackSmartBot
|
|
24
24
|
@datetime_teams_file[file] = File.mtime(file)
|
25
25
|
end
|
26
26
|
end
|
27
|
+
#remove from @teams and @datetime_teams_file the keys that are not in the files
|
28
|
+
#this is to avoid having old data in memory
|
29
|
+
@teams.keys.each do |key|
|
30
|
+
unless files.include?(File.join(config.path, "teams", "t_#{key}.yaml"))
|
31
|
+
@teams.delete(key)
|
32
|
+
@datetime_teams_file.delete(File.join(config.path, "teams", "t_#{key}.yaml"))
|
33
|
+
end
|
34
|
+
end
|
27
35
|
end
|
28
|
-
end
|
36
|
+
end
|
@@ -1,9 +1,17 @@
|
|
1
1
|
class SlackSmartBot
|
2
2
|
def save_stats(method, data: {}, forced: false)
|
3
|
-
if
|
3
|
+
if Thread.current[:user].nil? and !data[:user].to_s.empty?
|
4
|
+
user_stats = data[:user]
|
5
|
+
else
|
6
|
+
user_stats = Thread.current[:user]
|
7
|
+
end
|
8
|
+
if has_access?(method, user_stats) or forced
|
4
9
|
if config.stats
|
5
10
|
begin
|
6
|
-
command_ids_not_to_log = [
|
11
|
+
command_ids_not_to_log = [
|
12
|
+
"add_vacation", "remove_vacation", "add_memo_team", "set_personal_settings", "open_ai_chat",
|
13
|
+
"open_ai_chat_add_authorization", "open_ai_chat_copy_session_from_user",
|
14
|
+
]
|
7
15
|
Thread.current[:command_id] = method.to_s
|
8
16
|
require "csv"
|
9
17
|
if !File.exist?("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log")
|
@@ -38,9 +46,9 @@ class SlackSmartBot
|
|
38
46
|
end
|
39
47
|
user_info = find_user(data.user.id)
|
40
48
|
if user_info.nil? or user_info.is_app_user or user_info.is_bot
|
41
|
-
time_zone =
|
42
|
-
job_title =
|
43
|
-
team_id =
|
49
|
+
time_zone = ""
|
50
|
+
job_title = ""
|
51
|
+
team_id = ""
|
44
52
|
else
|
45
53
|
time_zone = user_info.tz_label
|
46
54
|
job_title = user_info.profile.title
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class SlackSmartBot
|
2
|
+
def transform_to_slack_markdown(markdown_text)
|
3
|
+
# Regular expressions to match code blocks
|
4
|
+
code_block_regex = /```(.*?)```/m
|
5
|
+
|
6
|
+
# Extract code blocks to preserve them
|
7
|
+
preserved_code_blocks = markdown_text.scan(code_block_regex).map(&:first)
|
8
|
+
|
9
|
+
# Replace code blocks and inline code with placeholders
|
10
|
+
code_block_placeholder_text = ""
|
11
|
+
while markdown_text.include?(code_block_placeholder_text)
|
12
|
+
code_block_placeholder_text = "CODE_BLOCK_PLACEHOLDER_#{"6:x".gen}"
|
13
|
+
end
|
14
|
+
transformed_text = markdown_text.gsub(code_block_regex, code_block_placeholder_text)
|
15
|
+
|
16
|
+
# Transform general Markdown to Slack Markdown
|
17
|
+
transformed_text.gsub!(/^\* (.*)$/, '• \1') # Unordered list
|
18
|
+
transformed_text.gsub!(/^\s*d+. (.*)$/, '\1.') # Ordered list
|
19
|
+
transformed_text.gsub!(/!\[(.*?)\]\((.*?)\)/, '\1') # Images to alt text
|
20
|
+
transformed_text.gsub!(/\[(.*?)\]\((.*?)\)/, '<\2|\1>') # Links
|
21
|
+
transformed_text.gsub!(/\*\*(.*?)\*\*/, '*\1*') # Bold
|
22
|
+
transformed_text.gsub!(/__(.*?)__/, '*\1*') # Bold
|
23
|
+
# delete any * character in any position if it is a header
|
24
|
+
transformed_text.gsub!(/^\s*#+.*\*/) { |match| match.gsub("*", "") }
|
25
|
+
# add for more than 4 # the same than for #### but one :black_small_square: per extra #
|
26
|
+
transformed_text.gsub!(/^\s*####(#+) (.*)$/) { "\n" + "#{":black_small_square:" * ($1.length + 1)} *#{$2}*" }
|
27
|
+
transformed_text.gsub!(/^\s*#### (.*)$/, "\n" + ':black_small_square: *\1*') # Header level 4 to bold
|
28
|
+
transformed_text.gsub!(/^\s*### (.*)$/, "\n" + ':small_orange_diamond: *\1*') # Header level 3 to bold
|
29
|
+
transformed_text.gsub!(/^\s*## (.*)$/, "\n" + ':small_blue_diamond: *\1*') # Header level 2 to bold
|
30
|
+
transformed_text.gsub!(/^\s*# (.*)$/, "\n" + ':small_red_triangle: *\1*') # Header level 1 to bold
|
31
|
+
|
32
|
+
# Reinsert preserved code blocks and inline code
|
33
|
+
preserved_code_blocks.each { |block| transformed_text.sub!(code_block_placeholder_text, "```#{block}```") }
|
34
|
+
return transformed_text
|
35
|
+
end
|
36
|
+
end
|