module AiUrlContext
Constants
- LOCALHOST_HOSTNAMES
- MAX_CONTENT_CHARS
- PASTEBIN_PAGE_PATTERN
- USER_AGENT
Public Instance Methods
Source
# File lib/ai/url_context.rb, line 117 def build_context_lines(question, fetch: nil) url = extract_url(question) return [] unless url return [] unless safe_url?(url) return [] if skip_url?(url) result = fetch_link(url, fetch: fetch) return [] unless result[:found] lines = ["URL: #{result[:url]}"] lines << "Title: #{result[:title]}" if result[:title] lines << "Content: #{result[:text]}" lines end
Source
# File lib/ai/url_context.rb, line 85 def clean_plain_text(body) body.to_s.strip.gsub(/\r\n?/, "\n").squeeze(" ") end
Source
# File lib/ai/url_context.rb, line 132 def default_fetch ->(url) { RabbotHttp.fetch(url, user_agent: USER_AGENT) } end
Source
# File lib/ai/url_context.rb, line 63 def extract_content(body, url: nil) _ = url if html_body?(body) metadata = UrlPreview.parse_metadata(body) paragraphs = UrlPreview.extract_paragraphs(body, limit: 10, offset: 0) if paragraphs.empty? paragraphs = UrlPreview.fallback_paragraphs(body, metadata: metadata, offset: 0, limit: 3) end parts = [] title = metadata[:title].to_s.strip parts << title unless title.empty? paragraphs.each do |paragraph| line = paragraph.to_s.strip parts << line unless line.empty? end parts.join("\n\n") else clean_plain_text(body) end end
Source
# File lib/ai/url_context.rb, line 24 def extract_url(question) UrlLink.extract_first_url(question) end
Source
# File lib/ai/url_context.rb, line 96 def fetch_link(url, fetch: nil) fetch ||= default_fetch resolved = resolve_fetch_url(url) body = fetch.call(resolved) return { found: false, url: url, title: nil, text: nil, error: "empty" } if body.to_s.strip.empty? title = nil if html_body?(body) metadata = UrlPreview.parse_metadata(body) title = metadata[:title].to_s.strip title = nil if title.empty? end text = truncate_content(extract_content(body, url: resolved)) return { found: false, url: url, title: title, text: nil, error: "no content" } if text.empty? { found: true, url: resolved, title: title, text: text, error: nil } rescue StandardError => e { found: false, url: url, title: nil, text: nil, error: e.message } end
Source
# File lib/ai/url_context.rb, line 58 def html_body?(body) text = body.to_s.lstrip text.start_with?("<") || text.match?(/\A(?:<!DOCTYPE|<!--)/i) end
Source
# File lib/ai/url_context.rb, line 42 def localhost_hostname?(host) LOCALHOST_HOSTNAMES.include?(host.to_s.strip.downcase) end
Source
# File lib/ai/url_context.rb, line 50 def resolve_fetch_url(url) text = url.to_s.strip match = text.match(PASTEBIN_PAGE_PATTERN) return text.sub(%r{pastebin\.com/\K[a-zA-Z0-9]+}i, "raw/#{match[1]}") if match text end
Source
# File lib/ai/url_context.rb, line 28 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/ai/url_context.rb, line 46 def skip_url?(url) UrlLink.reserved_url?(url) end
Source
# File lib/ai/url_context.rb, line 89 def truncate_content(text) line = text.to_s.strip return line if line.length <= MAX_CONTENT_CHARS "#{line[0, MAX_CONTENT_CHARS - 3]}..." end