module ImageWget
Constants
- AGENT_MODEL
- DEFAULT_QUESTION
- LOCALHOST_HOSTNAMES
- MAX_BODY_CHARS
- MAX_BODY_LINES
- STYLE_TITLE
Public Instance Methods
Source
# File lib/image_wget.rb, line 169 def build_analysis_prompt(image_path:, question:, nick:) prompt_question = question.to_s.strip prompt_question = DEFAULT_QUESTION if prompt_question.empty? parts = [ "@#{image_path}", "", "View the attached image and answer for IRC chat.", "Question: #{prompt_question}", "Requested by IRC nick: #{nick}", "", "Requirements:", "- Plain text only, no markdown.", "- At most #{MAX_BODY_LINES} short lines.", "- No file paths, no preamble โ just the answer." ] AiGuard.guard_prompt(parts.join("\n")) end
Source
# File lib/image_wget.rb, line 266 def dispatch(network:, channel:, nick:, url:, question:, ai_allowed:, fetch:, runner: nil, storage_root: Dcc::STORAGE_ROOT, store: RabbotDb, model: AGENT_MODEL, google_drive_api_key: nil) return { success: false, reply: AiAccess.denial_message } unless ai_allowed run_wget( network: network, nick: nick, url: url, question: question, fetch: fetch, runner: runner, storage_root: storage_root, store: store, model: model, google_drive_api_key: google_drive_api_key ) end
Source
# File lib/image_wget.rb, line 133 def download_failure(message, url:, trace:) result = { error: message } result[:trace] = trace if trace&.any? result end
Source
# File lib/image_wget.rb, line 108 def download_image(url, fetch:, max_size: Dcc::MAX_FILE_SIZE, google_drive_api_key: nil) trace = nil if GoogleDriveDownload.applicable?(url) drive_result = GoogleDriveDownload.fetch_with_trace(url, fetch: fetch, api_key: google_drive_api_key) body = drive_result[:body] trace = drive_result[:steps] else body = fetch.call(url) end return download_failure("Could not download that URL.", url: url, trace: trace) if body.nil? || body.to_s.empty? data = body.to_s return download_failure("File too large.", url: url, trace: trace) if data.bytesize > max_size return download_failure("URL does not look like an image.", url: url, trace: trace) unless image_content?(data) { data: data, filename: guess_filename(url, data), size: data.bytesize } rescue StandardError download_failure("Could not download that URL.", url: url, trace: trace) end
Source
# File lib/image_wget.rb, line 93 def extension_for_content(content) bytes = content.to_s.b.byteslice(0, 4) return ".png" if bytes&.start_with?("\x89PNG".b) return ".jpg" if bytes&.start_with?("\xFF\xD8".b) return ".gif" if bytes&.start_with?("GIF8".b) return ".webp" if bytes&.start_with?("RIFF".b) return ".bmp" if bytes&.start_with?("BM".b) ".img" end
Source
# File lib/image_wget.rb, line 188 def extract_body_lines(text) body = text.to_s.gsub(/\A```[^\n]*\n/m, "").gsub(/```\z/, "") body.lines.map(&:rstrip).reject(&:empty?).map do |line| line.length > MAX_BODY_CHARS ? "#{line[0, MAX_BODY_CHARS - 3]}..." : line end.first(MAX_BODY_LINES) end
Source
# File lib/image_wget.rb, line 104 def format_download_error(url:, reason:, trace: nil) ImageWgetErrors.format_download_error(url: url, reason: reason, trace: trace) end
Source
# File lib/image_wget.rb, line 195 def format_reply(entry:, url:, body_lines:, model:, duration_s:) title = "#{entry['filename']} ยท #{Normalize.nick(entry['nick'])}" lines = [ IrcFormat.report_header(tag: "WGET", title: title, style: STYLE_TITLE), url.to_s.strip ] lines.concat(body_lines) lines << IrcFormat.report_footer(entry["filename"], "cursor", model, "#{duration_s}s") lines.join("\n") end
Source
# File lib/image_wget.rb, line 82 def guess_filename(url, content) path = URI.parse(url.to_s).path.to_s base = File.basename(path) return base if Dcc.image_filename?(base) ext = extension_for_content(content) "wget#{ext}" rescue URI::InvalidURIError "wget#{extension_for_content(content)}" end
Source
# File lib/image_wget.rb, line 69 def image_content?(data) bytes = data.to_s.b.byteslice(0, 12) return false if bytes.nil? || bytes.empty? return true if bytes.start_with?("\x89PNG".b) return true if bytes.start_with?("\xFF\xD8\xFF".b) return true if bytes.start_with?("GIF8".b) return true if bytes.start_with?("RIFF".b) && bytes.byteslice(8, 4) == "WEBP".b return true if bytes.start_with?("BM".b) false end
Source
# File lib/image_wget.rb, line 65 def localhost_hostname?(host) LOCALHOST_HOSTNAMES.include?(host.to_s.strip.downcase) end
Source
# File lib/image_wget.rb, line 159 def locate_image(network:, nick:, storage_root: Dcc::STORAGE_ROOT, store: RabbotDb) entry = Dcc.last_image_for(network: network, nick: nick, store: store) return { error: "No image on file for #{Normalize.nick(nick)}." } unless entry path = Dcc.absolute_path(entry, storage_root: storage_root) return { error: "That image file is no longer available." } unless File.file?(path) { entry: entry, path: path } end
Source
# File lib/image_wget.rb, line 34 def parse_command(text) stripped = text.to_s.strip return { error: usage_message } if stripped.empty? unless (match = stripped.match(%r{\A(https?://\S+)(?:\s+(.+))?\z}i)) return { error: usage_message } end url = match[1].gsub(/[.,;:!?)]+$/, "") return { error: usage_message } unless safe_url?(url) question = match[2]&.strip question = nil if question&.empty? { url: url, question: question } end
Source
# File lib/image_wget.rb, line 206 def run_wget(network:, nick:, url:, question:, fetch:, runner: nil, storage_root: Dcc::STORAGE_ROOT, store: RabbotDb, model: AGENT_MODEL, google_drive_api_key: nil) return { success: false, reply: usage_message } unless safe_url?(url) if question && AiGuard.system_request?(question) return { success: false, reply: AiGuard.rejection_message } end downloaded = download_image(url, fetch: fetch, google_drive_api_key: google_drive_api_key) if downloaded[:error] reply = if downloaded[:trace] format_download_error( url: url, reason: downloaded[:error], trace: downloaded[:trace] ) else downloaded[:error] end return { success: false, reply: reply } end entry = store_downloaded( network: network, nick: nick, url: url, download: downloaded, storage_root: storage_root, store: store ) path = Dcc.absolute_path(entry, storage_root: storage_root) prompt = build_analysis_prompt(image_path: path, question: question, nick: nick) result = AiAgent.run_agent(prompt, model: model, runner: runner || AiAgent.method(:default_runner)) unless result.success? message = result.text.to_s.strip return { success: false, reply: message.empty? ? "Image analysis failed." : message } end filtered = AiGuard.filter_output(result.text) if filtered == AiGuard.rejection_message return { success: false, reply: filtered } end body_lines = extract_body_lines(filtered) return { success: false, reply: "Could not parse analysis from the AI response." } if body_lines.empty? { success: true, reply: format_reply( entry: entry, url: url, body_lines: body_lines, model: result.model || model, duration_s: result.duration_s ) } end
Source
# File lib/image_wget.rb, line 51 def safe_url?(url) uri = URI.parse(url.to_s) return false unless %w[http https].include?(uri.scheme) return false if uri.host.to_s.strip.empty? return false if localhost_hostname?(uri.host) ip = GeoLookup.extract_ip(uri.host) return false if ip && GeoLookup.private_ip?(ip) true rescue URI::InvalidURIError, ArgumentError false end
Source
# File lib/image_wget.rb, line 139 def store_downloaded(network:, nick:, url:, download:, storage_root: Dcc::STORAGE_ROOT, store: RabbotDb) temp = Tempfile.new(["wget", File.extname(download[:filename].to_s)]) temp.binmode temp.write(download[:data]) temp.flush temp.close Dcc.register_file( network: network, nick: nick, filename: download[:filename], size: download[:size], source_path: temp.path, storage_root: storage_root, store: store ) ensure temp&.unlink end
Source
# File lib/image_wget.rb, line 30 def usage_message "Usage: !wget <image-url> [question]" end