module BotSites
BotSites — per-bot web site catalog and public URL resolution for !web. Public: normalize, handle, resolve_public_base, parse_nick_web_prompt, nick_candidates Depends: BotIdentity, GeoLookup, RabbotDb Tests: test/lib/bot_sites_test.rb
Constants
- ENV_PUBLIC_BASE
- IPIFY_URL
Public Instance Methods
Source
# File lib/bot_sites.rb, line 151 def default_fetch(url) require_relative "rabbot_http" RabbotHttp.fetch(url).to_s.strip end
Source
# File lib/bot_sites.rb, line 62 def default_owner_sites(bot_id) { "docs" => { key: "docs", label: "API documentation", path: "/#{bot_id}/docs/", url: nil } } end
Source
# File lib/bot_sites.rb, line 142 def fetch_public_ip(fetch: method(:default_fetch)) ip = fetch.call(IPIFY_URL).to_s.strip return nil if ip.empty? ip rescue StandardError nil end
Source
# File lib/bot_sites.rb, line 77 def handle(sites:, bot_id:, key: nil, public_base: "", shared: {}, fetch: method(:default_fetch), ptr_resolve: nil) ptr_resolve ||= ->(addr) { GeoLookup.reverse_ptr(addr) } catalog = sites || {} return [:empty, nil] if catalog.empty? selected = select_sites(catalog, key) return [:not_found, key.to_s.strip] if selected.empty? && !key.to_s.strip.empty? base = resolve_public_base(public_base, shared: shared, fetch: fetch, ptr_resolve: ptr_resolve) entries = selected.map { |site| resolve_site_entry(site, base) } [:ok, { entries: entries, bot_id: bot_id, key: key.to_s.strip.empty? ? nil : key.to_s.strip }] end
Source
# File lib/bot_sites.rb, line 114 def join_base_path(base, path) root = base.to_s.strip.chomp("/") suffix = path.to_s.strip suffix = "/#{suffix}" unless suffix.start_with?("/") "#{root}#{suffix}" end
Source
# File lib/bot_sites.rb, line 165 def nick_candidates(bot) ([bot.nick, bot.config.nick] + Array(bot.config.nicks)).compact.map(&:to_s).reject(&:empty?).uniq end
Source
# File lib/bot_sites.rb, line 20 def normalize(raw, bot_id:, bot_role:, level:) sites = parse_sites(raw) return sites unless sites.empty? return {} unless owner_level?(bot_role: bot_role, level: level) default_owner_sites(bot_id) end
Source
# File lib/bot_sites.rb, line 73 def owner_level?(bot_role:, level:) BotIdentity.resolve_level(bot_role: bot_role, level: level) == RabbotDb::LEVEL_OWNER end
Source
# File lib/bot_sites.rb, line 169 def parse_nick_web_prompt(message, bot:) text = message.to_s.lstrip return nil if text.empty? nick_candidates(bot).each do |nick| match = text.match(/\A!#{Regexp.escape(nick)}\s+web(?:\s+(.+))?\z/i) next unless match return match[1].to_s.strip end nil end
Source
# File lib/bot_sites.rb, line 43 def parse_site_entry(key, value) key = key.to_s.strip return nil if key.empty? case value when Hash data = value.transform_keys(&:to_s) label = data.fetch("label", key).to_s.strip label = key if label.empty? path = data["path"].to_s.strip path = nil if path.empty? url = data["url"].to_s.strip url = nil if url.empty? { key: key, label: label, path: path, url: url } else raise ConfigError, "web.sites.#{key} must be a mapping" end end
Source
# File lib/bot_sites.rb, line 29 def parse_sites(raw) return {} if raw.nil? case raw when Hash raw.each_with_object({}) do |(key, value), result| site = parse_site_entry(key, value) result[site[:key]] = site if site end else raise ConfigError, "web.sites must be a YAML mapping" end end
Source
# File lib/bot_sites.rb, line 121 def resolve_public_base(configured, shared:, fetch: method(:default_fetch), ptr_resolve: nil) ptr_resolve ||= ->(addr) { GeoLookup.reverse_ptr(addr) } explicit = configured.to_s.strip return explicit unless explicit.empty? env = ENV[ENV_PUBLIC_BASE].to_s.strip return env unless env.empty? cached = shared[:web_public_base].to_s.strip return cached unless cached.empty? ip = fetch_public_ip(fetch: fetch) return nil if ip.nil? || ip.empty? ptr = ptr_resolve.call(ip) host = usable_hostname?(ptr) ? ptr : ip base = "http://#{host}" shared[:web_public_base] = base base end
Source
# File lib/bot_sites.rb, line 100 def resolve_site_entry(site, base) url = site[:url] if url && !url.empty? return site.merge(url: url, complete: true) end path = site[:path] if base && path && !path.empty? return site.merge(url: join_base_path(base, path), complete: true) end site.merge(url: path || "", complete: false) end
Source
# File lib/bot_sites.rb, line 91 def select_sites(catalog, key) filter = key.to_s.strip values = catalog.values return values.sort_by { |site| site[:key] } if filter.empty? site = catalog[filter] site ? [site] : [] end
Source
# File lib/bot_sites.rb, line 156 def usable_hostname?(name) text = name.to_s.strip return false if text.empty? return false if text.match?(/\A[\d.]+\z/) return false if text.match?(/\A[\da-f:]+(\%.*)?\z/i) true end