class SlackSmartBot
def repl(dest, user, session_name, env_vars, rules_file, command, description, type)
from = user.name
if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and
(!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
else
if !@repl_sessions.key?(from)
save_stats(__method__)
Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
serialt = Time.now.strftime("%Y%m%d%H%M%S%N")
if session_name.to_s==''
session_name = "#{from}_#{serialt}"
temp_repl = true
else
temp_repl = false
i = 0
name = session_name
while File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.input")
i+=1
session_name = "#{name}#{i}"
end
end
@repl_sessions[from] = {
name: session_name,
dest: dest,
started: Time.now,
finished: Time.now,
input: [],
on_thread: Thread.current[:on_thread],
thread_ts: Thread.current[:thread_ts]
}
unless temp_repl
@repls[session_name] = {
created: @repl_sessions[from][:started].to_s,
accessed: @repl_sessions[from][:started].to_s,
creator_name: user.name,
creator_id: user.id,
description: description,
type: type,
runs_by_creator: 0,
runs_by_others: 0,
gets: 0
}
update_repls()
end
react :running
if Thread.current[:ts].to_s == ''
@ts_react = Thread.current[:thread_ts]
else
@ts_react = Thread.current[:ts]
end
message = "Session name: *#{session_name}*
From now on I will execute all you write as a Ruby command and I will keep the session open until you send `quit` or `bye` or `exit`.
I will respond with the result so it is not necessary you send `print`, `puts`, `p` or `pp` unless you want it as the output when calling `run repl`.
Use `p` to print a message raw, exacly like it is returned.
If you want to avoid a message to be treated by me, start the message with '-'.
After 30 minutes of no communication with the Smart Bot the session will be dismissed.
If you want to see the methods of a class or module you created use _ls TheModuleOrClass_
You can supply the Environmental Variables you need for the Session
Example:
_repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_
"
respond message, dest
File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", "", mode: "a+")
File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.output", "", mode: "a+")
File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.run", "", mode: "a+")
if type != :private_clean and type != :public_clean
pre_execute = '
if File.exist?(\"./.smart-bot-repl\")
begin
eval(File.read(\"./.smart-bot-repl\"), bindme' + serialt + ')
rescue Exception => resp_repl
end
end
'
else
pre_execute = ''
end
process_to_run = '
ruby -e "' + env_vars.join("\n") + '
require \"amazing_print\"
bindme' + serialt + ' = binding
eval(\"require \'nice_http\'\" , bindme' + serialt + ')
def ls(obj)
(obj.methods - Object.methods)
end
file_input_repl = File.open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.input\", \"r\")
' + pre_execute + '
while true do
sleep 0.2
code_to_run_repl = file_input_repl.read
if code_to_run_repl.to_s!=\"\"
add_to_run_repl = true
if code_to_run_repl.to_s.match?(/^quit$/i) or
code_to_run_repl.to_s.match?(/^exit$/i) or
code_to_run_repl.to_s.match?(/^bye bot$/i) or
code_to_run_repl.to_s.match?(/^bye$/i)
exit
else
if code_to_run_repl.match?(/^\s*ls\s+(.+)/)
add_to_run_repl = false
end
error = false
begin
resp_repl = eval(code_to_run_repl.gsub(/^\s*(puts|print|p|pp)\s/, \"\"), bindme' + serialt + ')
rescue Exception => resp_repl
error = true
end
if resp_repl.to_s != \"\"
if code_to_run_repl.match?(/^\s*p\s+/i)
open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
f.puts \"\`\`\`\n#{resp_repl.inspect}\n\`\`\`\"
}
else
open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
f.puts \"\`\`\`\n#{resp_repl.ai}\n\`\`\`\"
}
end
unless error or !add_to_run_repl
open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.run\", \"a+\") {|f|
f.puts code_to_run_repl
}
end
end
end
end
end"
'
unless rules_file.empty? begin
eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
end
end
started = Time.now
process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)
stdin, stdout, stderr, wait_thr = Open3.popen3(process_to_run)
timeout = 30 * 60
file_output_repl = File.open("#{config.path}/repl/#{@channel_id}/#{session_name}.output", "r")
@repl_sessions[from][:pid] = wait_thr.pid
while (wait_thr.status == 'run' or wait_thr.status == 'sleep') and @repl_sessions.key?(from)
begin
if (Time.now-@repl_sessions[from][:finished]) > timeout
open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
f.puts 'quit'
}
respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
unreact :running, @ts_react
pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) pids.each do |pid|
begin
Process.kill("KILL", pid)
rescue
end
end
@repl_sessions.delete(from)
break
end
sleep 0.2
resp_repl = file_output_repl.read
if resp_repl.to_s!=''
if resp_repl.to_s.lines.count < 60 and resp_repl.to_s.size < 3500
respond resp_repl, dest
else
resp_repl.gsub!(/^\s*```/,'')
resp_repl.gsub!(/```\s*$/,'')
send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: resp_repl)
end
end
rescue Exception => excp
@logger.fatal excp
end
end
else
@repl_sessions[from][:finished] = Time.now
code = @repl_sessions[from][:command]
@repl_sessions[from][:command] = ''
code.gsub!("\\n", "\n")
code.gsub!("\\r", "\r")
if code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/)
respond "Sorry I cannot run this due security reasons", dest
else
@repl_sessions[from][:input]<<code
case code
when /^\s*(quit|exit|bye|bye bot)\s*$/i
open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
f.puts code
}
respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
unreact :running, @ts_react
pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) pids.each do |pid|
begin
Process.kill("KILL", pid)
rescue
end
end
@repl_sessions.delete(from)
when /^\s*-/i
else
open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
f.puts code
}
end
end
end
end
end
end