class SlackSmartBot # help: ---------------------------------------------- # help: `run repl SESSION_NAME` # help: `run repl SESSION_NAME ENV_VAR=VALUE ENV_VAR=VALUE` # help: `run repl SESSION_NAME PARAMS` # help: `run live SESSION_NAME` # help: `run irb SESSION_NAME` # help: Will run the repl session specified and return the output. # help: You can supply the Environmental Variables you need for the Session # help: PARAMS: Also it is possible to supply code that will be run before the repl code on the same session. # help: It will return only the values that were print out on the repl with puts, print, p or pp # help: Example: # help: _run repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_ # help: # help: command_id: :run_repl # help: def run_repl(dest, user, session_name, env_vars, prerun, rules_file) #todo: add tests from = user.name if has_access?(__method__, user) 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}") code = prerun.join("\n") if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run") if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and (@repls[session_name][:creator_name] != user.name or @repls[session_name][:creator_team_id] != user.team_id) and !is_admin?(user) respond "The REPL with session name: #{session_name} is private", dest elsif !prerun.empty? and (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/) or code.match?(/=?\s*(require|load)(\(|\s)/i)) respond "Sorry I cannot run this due security reasons", dest else if @repls.key?(session_name) #not temp @repls[session_name][:accessed] = Time.now.to_s if @repls[session_name].creator_name == user.name and @repls[session_name].creator_team_id == user.team_id @repls[session_name][:runs_by_creator] += 1 else @repls[session_name][:runs_by_others] += 1 end update_repls() end content = env_vars.join("\n") content += "\nrequire 'nice_http'\n" unless rules_file.empty? # to get the project_folder begin eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file) end end if File.exist?("#{project_folder}/.smart-bot-repl") and ((@repls.key?(session_name) and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean) or !@repls.key?(session_name)) content += File.read("#{project_folder}/.smart-bot-repl") content += "\n" end unless prerun.empty? content += prerun.join("\n") content += "\n" end content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.run").gsub(/^(quit|exit|bye)$/i, "") #todo: remove this gsub, it will never contain it Dir.mkdir("#{project_folder}/tmp") unless Dir.exist?("#{project_folder}/tmp") Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl") if Thread.current[:on_thread] # to force stdout.each to be performed every 3 seconds content = "Thread.new do while true do puts '' sleep 3 end end #{content} " end random = "5:LN&".gen File.write("#{project_folder}/tmp/repl/#{session_name}_#{user.name}_#{random}.rb", content, mode: "w+") process_to_run = "ruby ./tmp/repl/#{session_name}_#{user.name}_#{random}.rb" process_to_run = ("cd #{project_folder} && #{process_to_run}") if defined?(project_folder) respond "Running REPL #{session_name} (id: #{random})" @run_repls[random] = { team_id: user.team_id, user: user.name, name: session_name, pid: '' } react :running require "pty" timeout = 60 * 60 * 4 # 4 hours started = Time.now results = [] begin PTY.spawn(process_to_run) do |stdout, stdin, pid| last_result = -1 last_time = Time.now @run_repls[random].pid = pid begin stdout.each do |line| if (Time.now - started) > timeout respond "run REPL session finished. Max time reached: #{session_name} (id: #{random})", dest pids = `pgrep -P #{pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows pids.each do |pd| begin Process.kill("KILL", pd) rescue end end break else results << line if Thread.current[:on_thread] if (Time.now - last_time) > 2 if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500 output = "" results[(last_result + 1)..-1].each do |li| if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/) output += "```\n#{li}```\n" else output += li end end respond output else send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join) end last_result = results.size - 1 last_time = Time.now end end end end rescue Errno::EIO @logger.warn "run_repl PTY Errno:EIO error" end if results.empty? respond "*#{session_name}* (id: #{random}): Nothing returned." else if last_result != (results.size - 1) if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500 output = "" results[(last_result + 1)..-1].each do |li| if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/) output += "```\n#{li}```\n" else output += li end end if Thread.current[:on_thread] respond output else respond "*#{session_name}* (id: #{random}):\n#{output}" end else send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join) end end end end rescue PTY::ChildExited @logger.warn "run_repl PTY The child process exited!" end @run_repls.delete(random) if @run_repls.key?(random) unreact :running end else respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest end end end end