class Geo
Constants
- RESERVED
Public Class Methods
Source
# File plugins/geo.rb, line 104 def self.channel_users_from(channel) return [] unless channel raw = if channel.respond_to?(:user_list) channel.user_list else channel.users end normalize_channel_users(raw) end
Source
# File plugins/geo.rb, line 263 def self.far_report(users:, requester:, nicks:, search: Geocoder.method(:search)) if nicks.length == 1 from_user = requester to_resolved = resolve_user(users, nicks[0]) return to_resolved[:error] if to_resolved[:error] to_user = to_resolved[:user] else from_resolved = resolve_user(users, nicks[0]) return from_resolved[:error] if from_resolved[:error] to_resolved = resolve_user(users, nicks[1]) return to_resolved[:error] if to_resolved[:error] from_user = from_resolved[:user] to_user = to_resolved[:user] end from_geo = user_geo(from_user, search: search) return from_geo[:error] if from_geo[:error] to_geo = user_geo(to_user, search: search) return to_geo[:error] if to_geo[:error] from_coords = GeoLookup.coordinates_for(from_geo[:result]) to_coords = GeoLookup.coordinates_for(to_geo[:result]) return "Could not resolve coordinates for distance check" unless from_coords && to_coords km = GeoLookup.distance_km(from_coords, to_coords) GeoLookup.format_distance_report( from_label: from_user.nick, to_label: to_user.nick, km: km, between: nicks.length > 1 ) end
Source
# File plugins/geo.rb, line 90 def self.find_user_by_nick(users, nick) target = Normalize.nick(nick) users.find { |user| user.nick.casecmp?(target) } end
Source
# File plugins/geo.rb, line 207 def self.info_report(users:, requester:, nick:, search: Geocoder.method(:search), resolve: GeoLookup.method(:resolve_hostname_to_ip), ptr_resolve: ->(addr) { Resolv.getname(addr) }) user = if nick resolved = resolve_user(users, nick) return resolved[:error] if resolved[:error] resolved[:user] else requester end host = user.host.to_s ip = GeoLookup.extract_ip(host, resolve: resolve) unless ip return GeoLookup.host_error(nick: user.nick, host: host) end if GeoLookup.private_ip?(ip) return GeoLookup.host_error(nick: user.nick, host: host) end layers = GeoLookup.build_layer_info( ip: ip, host: host, search: search, ptr_resolve: ptr_resolve ) GeoLookup.format_info_report(nick: user.nick, layers: layers) end
Source
# File plugins/geo.rb, line 193 def self.isp_report(users:, requester:, nick:, search: Geocoder.method(:search), resolve: GeoLookup.method(:resolve_hostname_to_ip)) resolved = resolve_user_for_optional_nick( users: users, requester: requester, nick: nick, search: search, resolve: resolve ) return resolved[:error] if resolved[:error] GeoLookup.format_isp_report( nick: resolved[:user].nick, result: resolved[:result], host: resolved[:user].host ) end
Source
# File plugins/geo.rb, line 152 def self.location_report(users:, requester:, nick:, search: Geocoder.method(:search), resolve: GeoLookup.method(:resolve_hostname_to_ip)) resolved = resolve_user_for_optional_nick( users: users, requester: requester, nick: nick, search: search, resolve: resolve ) return resolved[:error] if resolved[:error] GeoLookup.format_location_report(nick: resolved[:user].nick, result: resolved[:result]) end
Source
# File plugins/geo.rb, line 258 def self.map_report(users:, bot_nick:, search: Geocoder.method(:search)) atlas = GeoLookup.channel_atlas(users, bot_nick: bot_nick, search: search) GeoLookup.format_map_report(atlas: atlas) end
Source
# File plugins/geo.rb, line 238 def self.meet_report(users:, requester:, nick:, search: Geocoder.method(:search)) resolved = resolve_user(users, nick) return resolved[:error] if resolved[:error] from_geo = user_geo(requester, search: search) return from_geo[:error] if from_geo[:error] to_geo = user_geo(resolved[:user], search: search) return to_geo[:error] if to_geo[:error] from_coords = GeoLookup.coordinates_for(from_geo[:result]) to_coords = GeoLookup.coordinates_for(to_geo[:result]) return "Could not resolve coordinates" unless from_coords && to_coords midpoint = GeoLookup.midpoint_coords(from_coords, to_coords) label = GeoLookup.reverse_label(midpoint, search: search) km = GeoLookup.distance_km(from_coords, midpoint) GeoLookup.format_meet_report(from_nick: requester.nick, to_nick: resolved[:user].nick, midpoint_label: label, km: km) end
Source
# File plugins/geo.rb, line 308 def self.near_report(users:, place:, bot_nick:, search: Geocoder.method(:search)) place_result = GeoLookup.lookup_place(place, search: search) return "Could not find #{place}" unless place_result place_coords = GeoLookup.coordinates_for(place_result) return "Could not find coordinates for #{place}" unless place_coords candidates = users .reject { |user| user.nick.to_s.casecmp?(bot_nick.to_s) } .sort_by { |user| user.nick.to_s.downcase } .take(GeoLookup::NEAR_USER_LIMIT) entries = GeoLookup.near_entries(users, place_coords, bot_nick: bot_nick, search: search) GeoLookup.format_near_report( place: place, entries: entries, scanned: candidates.length, located: entries.length ) end
Source
# File plugins/geo.rb, line 95 def self.normalize_channel_users(channel_users) case channel_users when Hash channel_users.keys else Array(channel_users) end end
Source
# File plugins/geo.rb, line 29 def self.parse_command(text) stripped = text.to_s.strip return { action: :location, nick: nil } if stripped.empty? %w[far near].each do |word| return { error: usage_message } if stripped.match?(/\A#{word}\z/i) end if (match = stripped.match(/\Afar\s+(\S+)(?:\s+(\S+))?\z/i)) return { action: :far, nicks: [match[1], match[2]].compact } end if (match = stripped.match(/\Atime(?:\s+(\S+))?\z/i)) return { action: :time, nick: match[1] } end if (match = stripped.match(/\Aweather(?:\s+(\S+))?\z/i)) return { action: :weather, nick: match[1] } end if (match = stripped.match(/\Asun(?:\s+(.+))?\z/i)) arg = match[1].to_s.strip return { action: :sun, nick: nil, place: nil } if arg.empty? return { action: :sun, nick: arg, place: nil } if arg !~ /\s/ && !RESERVED.include?(arg.downcase) return { action: :sun, nick: nil, place: arg } end if (match = stripped.match(/\Ameet\s+(\S+)\z/i)) return { action: :meet, nick: match[1] } end if (match = stripped.match(/\Aisp(?:\s+(\S+))?\z/i)) return { action: :isp, nick: match[1] } end if (match = stripped.match(/\Ainfo(?:\s+(\S+))?\z/i)) return { action: :info, nick: match[1] } end if stripped.match?(/\Amap\z/i) return { action: :map } end if (match = stripped.match(/\Anear\s+(.+)\z/i)) place = match[1].to_s.strip return { error: usage_message } if place.empty? return { action: :near, place: place } end if (match = stripped.match(/\A(\S+)\z/i)) nick = match[1] return { error: usage_message } if RESERVED.include?(nick.downcase) return { action: :location, nick: nick } end { error: usage_message } end
Source
# File plugins/geo.rb, line 115 def self.resolve_user(users, nick) user = find_user_by_nick(users, nick) return { error: "#{nick} is not in this channel" } unless user { user: user } end
Source
# File plugins/geo.rb, line 135 def self.resolve_user_for_optional_nick(users:, requester:, nick:, search: Geocoder.method(:search), resolve: GeoLookup.method(:resolve_hostname_to_ip)) user = if nick resolved = resolve_user(users, nick) return resolved if resolved[:error] resolved[:user] else requester end geo = user_geo(user, search: search, resolve: resolve) return geo if geo[:error] { user: user, result: geo[:result] } end
Source
# File plugins/geo.rb, line 172 def self.sun_report(users:, requester:, nick:, place:, search: Geocoder.method(:search), fetch_json: RabbotHttp.method(:fetch_json)) if place place_result = GeoLookup.lookup_place(place, search: search) return "Could not find #{place}" unless place_result coords = GeoLookup.coordinates_for(place_result) return "Could not find coordinates for #{place}" unless coords return SunTimes.report_for_coords(coords[0], coords[1], place: place, fetch_json: fetch_json) end resolved = resolve_user_for_optional_nick(users: users, requester: requester, nick: nick, search: search) return resolved[:error] if resolved[:error] coords = GeoLookup.coordinates_for(resolved[:result]) return "Could not determine coordinates for #{resolved[:user].nick}" unless coords label = GeoLookup.location_label(resolved[:result]) SunTimes.report_for_coords(coords[0], coords[1], place: label, fetch_json: fetch_json) end
Source
# File plugins/geo.rb, line 300 def self.time_report(users:, requester:, nick:, search: Geocoder.method(:search), clock: -> { Time.now.utc }) resolved = resolve_user_for_optional_nick(users: users, requester: requester, nick: nick, search: search) return resolved[:error] if resolved[:error] report = GeoLookup.format_time_report(nick: resolved[:user].nick, result: resolved[:result], clock: clock) report || "Could not determine local time for #{resolved[:user].nick}" end
Source
# File plugins/geo.rb, line 24 def self.usage_message "Usage: !geo [nick] | !geo far <nick> [nick] | !geo time [nick] | !geo weather [nick] | " \ "!geo sun [nick|place] | !geo meet <nick> | !geo isp [nick] | !geo info [nick] | !geo map | !geo near <place>" end
Source
# File plugins/geo.rb, line 122 def self.user_geo(user, search: Geocoder.method(:search), resolve: GeoLookup.method(:resolve_hostname_to_ip)) host = user.host.to_s ip = GeoLookup.extract_ip(host, resolve: resolve) unless ip return { error: GeoLookup.host_error(nick: user.nick, host: host) } end result = GeoLookup.lookup_ip(ip, search: search) return { error: "Could not geolocate #{user.nick}" } unless result { result: result } end
Source
# File plugins/geo.rb, line 162 def self.weather_report(users:, requester:, nick:, search: Geocoder.method(:search), bom_lookup: Bom.method(:lookup)) resolved = resolve_user_for_optional_nick(users: users, requester: requester, nick: nick, search: search) return resolved[:error] if resolved[:error] city = GeoLookup.bom_city_from_result(resolved[:result]) return "Could not determine city for #{resolved[:user].nick}" if city.empty? bom_lookup.call(city, days: 1) end
Public Instance Methods
Source
# File plugins/geo.rb, line 329 def deliver_reply(m, text) body = text.to_s if body.strip.empty? m.reply "Geo lookup produced no output" return end # Keep short multi-line reports on one PRIVMSG. flood_safe_reply splits on # every newline, which turns a 4-line GEO banner into four delayed chunks. if body.bytesize <= outgoing_line_limit(m) m.reply(body) else themed_flood_safe_reply(m, body) end end
Source
# File plugins/geo.rb, line 345 def execute(m, args_text = nil) unless m.channel m.reply "Geo commands must be used in a channel" return end parsed = self.class.parse_command(args_text) if parsed[:error] m.reply parsed[:error] return end users = self.class.channel_users_from(m.channel) search = Geocoder.method(:search) message = case parsed[:action] when :location self.class.location_report(users: users, requester: m.user, nick: parsed[:nick], search: search) when :far self.class.far_report(users: users, requester: m.user, nicks: parsed[:nicks], search: search) when :time self.class.time_report(users: users, requester: m.user, nick: parsed[:nick], search: search) when :weather self.class.weather_report(users: users, requester: m.user, nick: parsed[:nick], search: search) when :sun self.class.sun_report( users: users, requester: m.user, nick: parsed[:nick], place: parsed[:place], search: search ) when :meet self.class.meet_report(users: users, requester: m.user, nick: parsed[:nick], search: search) when :isp self.class.isp_report(users: users, requester: m.user, nick: parsed[:nick], search: search) when :info self.class.info_report(users: users, requester: m.user, nick: parsed[:nick], search: search) when :map self.class.map_report(users: users, bot_nick: bot.nick, search: search) when :near self.class.near_report(users: users, place: parsed[:place], bot_nick: bot.nick, search: search) else "Unknown geo command" end deliver_reply(m, message) rescue StandardError => e warn "[geo] #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}" if ENV["RABBOT_DEBUG"] m.reply "Geo error: #{e.message}" end