├── data └── README ├── plugins └── rails │ └── through │ ├── init.rb │ └── lib │ └── through.rb ├── spec └── locate_test.rb ├── README.textile └── locate.rb /data/README: -------------------------------------------------------------------------------- 1 | http://www.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz 2 | http://www.maxmind.com/timezone.txt 3 | -------------------------------------------------------------------------------- /plugins/rails/through/init.rb: -------------------------------------------------------------------------------- 1 | # Through.locate :ip => '212.58.224.131' 2 | # Through.time_zone :ip => '212.58.224.131' 3 | 4 | require 'through' -------------------------------------------------------------------------------- /plugins/rails/through/lib/through.rb: -------------------------------------------------------------------------------- 1 | require 'open-uri' 2 | 3 | module Through 4 | SERVER = 'http://through.helicoid.net' 5 | 6 | def self.locate(options = {}) 7 | request "/location/#{options[:ip]}" 8 | end 9 | 10 | def self.time_zone(options = {}) 11 | time_zone = request "/time_zone/#{options[:ip]}" 12 | if time_zone 13 | time_zone['rails'] = ActiveSupport::TimeZone::MAPPING.index(time_zone['country_name']) 14 | end 15 | time_zone 16 | end 17 | 18 | def self.request(url) 19 | Timeout::timeout(3) do 20 | response = open(SERVER + url).read 21 | ActiveSupport::JSON.decode response 22 | end 23 | rescue Timeout::Error 24 | nil 25 | end 26 | end -------------------------------------------------------------------------------- /spec/locate_test.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'spec/interop/test' 3 | require 'sinatra/test/unit' 4 | 5 | include Sinatra::Test::Methods 6 | 7 | Sinatra::Application.default_options.merge!( 8 | :env => :test, 9 | :run => false, 10 | :raise_errors => true, 11 | :logging => false 12 | ) 13 | 14 | describe 'Locate' do 15 | require 'locate' 16 | 17 | it 'should look up IP addresses' do 18 | get_it '/location/80.68.87.32' 19 | @response.should be_ok 20 | @response.body.should match(/United Kingdom/) 21 | end 22 | 23 | it 'should look up time zones' do 24 | get_it '/time_zone/80.68.87.32' 25 | 26 | @response.should be_ok 27 | @response.body.should match(%r{Europe/London}) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h2. Overview 2 | 3 | Through is a small geolocation web API. You can use it to: 4 | 5 | * Look up the location of an IP address 6 | * Find out the time zone for a location 7 | * Get responses in JSON 8 | * Centralise your geographical data so each of your apps don't need to store it 9 | 10 | I didn't want to reproduce the same functionality across my web apps, so I thought an API would be a good solution. 11 | 12 | h2. Installation 13 | 14 | # Install libgeoip (sudo port install libgeoip) 15 | # Install the ruby binding (sudo gem install geoip_city) 16 | # Get the data: (curl -O http://www.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz) 17 | # Get some time zone mapping data (curl http://www.maxmind.com/timezone.txt > data/timezone.txt) -------------------------------------------------------------------------------- /locate.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'geoip_city' 4 | require 'fastercsv' 5 | 6 | # For to_json 7 | require 'active_support' 8 | 9 | class Locate 10 | CITY_DATA_FILE = 'data/GeoLiteCity.dat' 11 | TIME_ZONE_FILE = 'data/timezone.txt' 12 | 13 | def initialize 14 | @db = GeoIPCity::Database.new CITY_DATA_FILE 15 | @time_zones = FCSV.parse(File.read(TIME_ZONE_FILE), :col_sep => "\t") 16 | end 17 | 18 | def from_ip(remote_ip) 19 | @db.look_up remote_ip 20 | end 21 | 22 | # This returns a TZInfo-compatible identifier 23 | def time_zone_for_location(location) 24 | time_zone = @time_zones.find do |country, region, zone| 25 | if location[:country_code] == 'US' and country == 'US' and region == location[:region] 26 | true 27 | elsif country != 'US' and location[:country_code] == country 28 | true 29 | end 30 | end 31 | 32 | { :country_code => time_zone[0], :region => time_zone[1], :country_name => time_zone[2] } 33 | end 34 | end 35 | 36 | locator = Locate.new 37 | 38 | # http://localhost:4567/location/80.68.87.32 39 | get '/location/*' do 40 | location = locator.from_ip params[:splat].first 41 | location.to_json 42 | end 43 | 44 | # Return the time zone for a given IP 45 | # http://localhost:4567/time_zone/80.68.87.32 46 | get '/time_zone/*' do 47 | location = locator.from_ip params[:splat].first 48 | locator.time_zone_for_location(location).to_json 49 | end --------------------------------------------------------------------------------