ipapi-rb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f349f84933eea258dba2b8e92e17f5ed04ac800998135182da92c17d6222724c
4
+ data.tar.gz: cd78e6d317b289099ce5cfd806eb94cda1f4a10d4cfc8071e15c0c0ab06b92f3
5
+ SHA512:
6
+ metadata.gz: 5ab6b4f1d3d25bd53fde29cfa798e32b8fc22c88c0cf07ee5285440ad7dfe631811116da8be4fdc5042c09b77eddce0743293897e1d82cbe45a9359aef63efc9
7
+ data.tar.gz: 536ce8dee48bacda8d431fd8dcdda67d21edd0330bf0d54f1a376fa8384e76b0077ba1a929143bf8226045c0f6572f114037928d56d6450717ee06c9d918ef82
data/README.md ADDED
@@ -0,0 +1,325 @@
1
+ # IpApi
2
+
3
+ The **IpApi** gem provides a Ruby interface to the [IP-API.com](https://ip-api.com) geolocation API, enabling IP address and domain lookups to retrieve location, network, and connection information.
4
+
5
+ ```ruby
6
+ require 'ip_api'
7
+
8
+ response = IpApi.query( '8.8.8.8' )
9
+ if response.success?
10
+ result = response.result
11
+ puts "#{ result.city }, #{ result.country }" # => "Ashburn, United States"
12
+ puts result.isp # => "Google LLC"
13
+ puts result.coordinates # => [39.03, -77.5]
14
+ end
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Table of Contents
20
+
21
+ - [Installation](#installation)
22
+ - [Command Line](#command-line)
23
+ - [Quick Start](#quick-start)
24
+ - [Free vs Pro](#free-vs-pro)
25
+ - [Options](#options)
26
+ - [Fields](#fields)
27
+ - [Language](#language)
28
+ - [Response](#response)
29
+ - [Location Fields](#location-fields)
30
+ - [Network Fields](#network-fields)
31
+ - [Connection Fields](#connection-fields)
32
+ - [Helper Methods](#helper-methods)
33
+ - [Error Handling](#error-handling)
34
+ - [Connections](#connections)
35
+ - [License](#license)
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ Add this line to your application's Gemfile:
42
+
43
+ ```ruby
44
+ gem 'ipapi-rb'
45
+ ```
46
+
47
+ Then execute:
48
+
49
+ ```bash
50
+ bundle install
51
+ ```
52
+
53
+ Or install it directly:
54
+
55
+ ```bash
56
+ gem install ipapi-rb
57
+ ```
58
+
59
+ ## Command Line
60
+
61
+ The gem includes an `ipapi` command for quick lookups from the terminal:
62
+
63
+ ```bash
64
+ ipapi 8.8.8.8
65
+ ipapi google.com
66
+ ipapi # Look up your own IP
67
+ ipapi -f country,city 8.8.8.8 # Specific fields only
68
+ ipapi -l de 8.8.8.8 # German localization
69
+ ipapi -j 8.8.8.8 # Raw JSON output
70
+ ```
71
+
72
+ Set your Pro API key via environment variable for HTTPS and unlimited requests:
73
+
74
+ ```bash
75
+ export IPAPI_API_KEY=your_api_key
76
+ ```
77
+
78
+ ## Quick Start
79
+
80
+ The simplest way to use the gem is through the module-level convenience method:
81
+
82
+ ```ruby
83
+ require 'ip_api'
84
+
85
+ # Query an IP address
86
+ response = IpApi.query( '8.8.8.8' )
87
+ if response.success?
88
+ result = response.result
89
+ puts result.country # => "United States"
90
+ puts result.city # => "Ashburn"
91
+ puts result.isp # => "Google LLC"
92
+ puts result.hosting? # => true
93
+ end
94
+
95
+ # Query a domain
96
+ response = IpApi.query( 'google.com' )
97
+
98
+ # Query your own IP (no argument)
99
+ response = IpApi.query
100
+ ```
101
+
102
+ For more control, instantiate request objects directly:
103
+
104
+ ```ruby
105
+ request = IpApi::QueryRequest.new( api_key: ENV[ 'IPAPI_API_KEY' ] )
106
+
107
+ options = IpApi::QueryOptions.build do
108
+ fields [ :country, :city, :isp, :proxy, :hosting ]
109
+ lang :de
110
+ end
111
+
112
+ response = request.submit( '8.8.8.8', options )
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Free vs Pro
118
+
119
+ The gem automatically selects the appropriate endpoint based on whether an API key is configured:
120
+
121
+ | Feature | Free | Pro |
122
+ |---------|------|-----|
123
+ | **Endpoint** | `http://ip-api.com` | `https://pro.ip-api.com` |
124
+ | **SSL/HTTPS** | No | Yes |
125
+ | **Rate Limit** | 45 requests/minute | Unlimited |
126
+ | **Commercial Use** | No | Yes |
127
+
128
+ ```ruby
129
+ # Free (no API key)
130
+ response = IpApi.query( '8.8.8.8' )
131
+
132
+ # Pro (with API key)
133
+ IpApi.api_key ENV[ 'IPAPI_API_KEY' ]
134
+ response = IpApi.query( '8.8.8.8' )
135
+
136
+ # Or pass directly to request
137
+ request = IpApi::QueryRequest.new( api_key: 'your-key' )
138
+ response = request.submit( '8.8.8.8' )
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Options
144
+
145
+ ### Fields
146
+
147
+ Control which fields are returned to reduce bandwidth:
148
+
149
+ ```ruby
150
+ options = IpApi::QueryOptions.build do
151
+ fields [ :status, :country, :city, :lat, :lon ]
152
+ end
153
+
154
+ response = IpApi.query( '8.8.8.8', options )
155
+ ```
156
+
157
+ Available fields (use snake_case in Ruby):
158
+
159
+ | Field | Description |
160
+ |-------|-------------|
161
+ | `:status` | Request status (`success` or `fail`) |
162
+ | `:message` | Error message (when status is `fail`) |
163
+ | `:query` | IP address used for the query |
164
+ | `:continent` | Continent name |
165
+ | `:continent_code` | Two-letter continent code |
166
+ | `:country` | Country name |
167
+ | `:country_code` | ISO 3166-1 alpha-2 country code |
168
+ | `:region` | Region/state short code |
169
+ | `:region_name` | Region/state full name |
170
+ | `:city` | City name |
171
+ | `:district` | District (subdivision of city) |
172
+ | `:zip` | Zip/postal code |
173
+ | `:lat` | Latitude |
174
+ | `:lon` | Longitude |
175
+ | `:timezone` | Timezone identifier |
176
+ | `:offset` | UTC offset in seconds |
177
+ | `:currency` | National currency code |
178
+ | `:isp` | ISP name |
179
+ | `:org` | Organization name |
180
+ | `:as` | AS number and organization |
181
+ | `:as_name` | AS name |
182
+ | `:reverse` | Reverse DNS (can delay response) |
183
+ | `:mobile` | Mobile connection flag |
184
+ | `:proxy` | Proxy/VPN/Tor flag |
185
+ | `:hosting` | Hosting/datacenter flag |
186
+
187
+ ### Language
188
+
189
+ Localize the `country`, `region_name`, and `city` fields:
190
+
191
+ ```ruby
192
+ options = IpApi::QueryOptions.build do
193
+ lang :de # German
194
+ end
195
+
196
+ response = IpApi.query( '8.8.8.8', options )
197
+ puts response.result.country # => "Vereinigte Staaten"
198
+ ```
199
+
200
+ Supported languages: `:en`, `:de`, `:es`, `:fr`, `:ja`, `:ru`, `:'pt-BR'`, `:'zh-CN'`
201
+
202
+ ---
203
+
204
+ ## Response
205
+
206
+ ### Location Fields
207
+
208
+ ```ruby
209
+ result = response.result
210
+
211
+ result.continent # => "North America"
212
+ result.continent_code # => "NA"
213
+ result.country # => "United States"
214
+ result.country_code # => "US"
215
+ result.region # => "VA"
216
+ result.region_name # => "Virginia"
217
+ result.city # => "Ashburn"
218
+ result.district # => nil
219
+ result.zip # => "20149"
220
+ result.lat # => 39.03
221
+ result.lon # => -77.5
222
+ result.timezone # => "America/New_York"
223
+ result.offset # => -18000
224
+ result.currency # => "USD"
225
+ ```
226
+
227
+ ### Network Fields
228
+
229
+ ```ruby
230
+ result.isp # => "Google LLC"
231
+ result.org # => "Google Public DNS"
232
+ result.as_number # => "AS15169 Google LLC"
233
+ result.as_name # => "GOOGLE"
234
+ result.reverse # => "dns.google"
235
+ ```
236
+
237
+ ### Connection Fields
238
+
239
+ ```ruby
240
+ result.mobile # => false
241
+ result.proxy # => false
242
+ result.hosting # => true
243
+ ```
244
+
245
+ ### Helper Methods
246
+
247
+ ```ruby
248
+ result.success? # => true (status == 'success')
249
+ result.failed? # => false (status == 'fail')
250
+
251
+ result.mobile? # => false
252
+ result.proxy? # => false
253
+ result.hosting? # => true
254
+
255
+ result.coordinates # => [39.03, -77.5]
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Error Handling
261
+
262
+ The API returns errors in two ways:
263
+
264
+ **HTTP errors** (rate limiting):
265
+
266
+ ```ruby
267
+ response = IpApi.query( '8.8.8.8' )
268
+
269
+ unless response.success?
270
+ error = response.result
271
+ puts error.error_type # => :rate_limit_error
272
+ puts error.error_description # => "The rate limit has been exceeded..."
273
+ end
274
+ ```
275
+
276
+ **API errors** (invalid query, private IP):
277
+
278
+ ```ruby
279
+ response = IpApi.query( '192.168.1.1' )
280
+
281
+ if response.success?
282
+ result = response.result
283
+ if result.failed?
284
+ puts result.message # => "private range"
285
+ end
286
+ end
287
+ ```
288
+
289
+ Common API error messages:
290
+
291
+ | Message | Description |
292
+ |---------|-------------|
293
+ | `private range` | IP is in a private network range |
294
+ | `reserved range` | IP is in a reserved range |
295
+ | `invalid query` | Invalid IP or domain |
296
+
297
+ ---
298
+
299
+ ## Connections
300
+
301
+ The gem uses Faraday for HTTP requests. To customize the connection:
302
+
303
+ ```ruby
304
+ connection = Faraday.new do | faraday |
305
+ faraday.response :logger
306
+ faraday.adapter :net_http
307
+ end
308
+
309
+ IpApi.connection connection
310
+ ```
311
+
312
+ Or pass it directly to a request:
313
+
314
+ ```ruby
315
+ request = IpApi::QueryRequest.new(
316
+ api_key: ENV[ 'IPAPI_API_KEY' ],
317
+ connection: connection
318
+ )
319
+ ```
320
+
321
+ ---
322
+
323
+ ## License
324
+
325
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/bin/ipapi ADDED
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ip_api'
4
+
5
+ def usage
6
+ puts "Usage: ipapi [options] [ip_or_domain]"
7
+ puts
8
+ puts "Look up geolocation and network information for an IP address or domain."
9
+ puts "If no IP/domain is provided, looks up the current machine's public IP."
10
+ puts
11
+ puts "Options:"
12
+ puts " -f, --fields <list> Comma-separated fields to return"
13
+ puts " -l, --lang <code> Language for localized fields (en, de, es, fr, ja, ru, pt-BR, zh-CN)"
14
+ puts " -j, --json Output raw JSON"
15
+ puts " -h, --help Show this help message"
16
+ puts
17
+ puts "Environment:"
18
+ puts " IPAPI_API_KEY Your IP-API Pro key (optional, enables HTTPS and unlimited requests)"
19
+ puts
20
+ puts "Examples:"
21
+ puts " ipapi 8.8.8.8"
22
+ puts " ipapi google.com"
23
+ puts " ipapi # Look up your own IP"
24
+ puts " ipapi -f country,city 8.8.8.8"
25
+ puts " ipapi -l de 8.8.8.8 # German localization"
26
+ puts " ipapi -j 8.8.8.8 # Raw JSON output"
27
+ puts
28
+ puts "Available fields:"
29
+ puts " status, message, query, continent, continent_code, country, country_code,"
30
+ puts " region, region_name, city, district, zip, lat, lon, timezone, offset,"
31
+ puts " currency, isp, org, as, as_name, reverse, mobile, proxy, hosting"
32
+ end
33
+
34
+ def parse_options( args )
35
+ options = {}
36
+ query = nil
37
+
38
+ while args.any?
39
+ arg = args.shift
40
+ case arg
41
+ when '-f', '--fields'
42
+ fields_str = args.shift
43
+ if fields_str
44
+ options[ :fields ] = fields_str.split( ',' ).map { | f | f.strip.to_sym }
45
+ end
46
+ when '-l', '--lang'
47
+ lang = args.shift
48
+ options[ :lang ] = lang.to_sym if lang
49
+ when '-j', '--json'
50
+ options[ :json ] = true
51
+ when '-h', '--help'
52
+ usage
53
+ exit 0
54
+ when /^-/
55
+ $stderr.puts "Unknown option: #{ arg }"
56
+ $stderr.puts "Run 'ipapi --help' for usage"
57
+ exit 1
58
+ else
59
+ query = arg
60
+ end
61
+ end
62
+
63
+ [ query, options ]
64
+ end
65
+
66
+ def format_result( result )
67
+ output = []
68
+
69
+ output << [ 'Query', result.query ] if result.query
70
+ output << [ 'Status', result.status ] if result.status
71
+
72
+ if result.failed?
73
+ output << [ 'Message', result.message ] if result.message
74
+ else
75
+ # Location
76
+ output << [ 'Continent', "#{ result.continent } (#{ result.continent_code })" ] if result.continent
77
+ output << [ 'Country', "#{ result.country } (#{ result.country_code })" ] if result.country
78
+ output << [ 'Region', "#{ result.region_name } (#{ result.region })" ] if result.region_name
79
+ output << [ 'City', result.city ] if result.city
80
+ output << [ 'District', result.district ] if result.district
81
+ output << [ 'ZIP', result.zip ] if result.zip
82
+ output << [ 'Coordinates', "#{ result.lat }, #{ result.lon }" ] if result.lat && result.lon
83
+ output << [ 'Timezone', result.timezone ] if result.timezone
84
+ output << [ 'UTC Offset', result.offset ] if result.offset
85
+ output << [ 'Currency', result.currency ] if result.currency
86
+
87
+ # Network
88
+ output << [ 'ISP', result.isp ] if result.isp
89
+ output << [ 'Organization', result.org ] if result.org
90
+ output << [ 'AS', result.as_number ] if result.as_number
91
+ output << [ 'AS Name', result.as_name ] if result.as_name
92
+ output << [ 'Reverse DNS', result.reverse ] if result.reverse
93
+
94
+ # Flags
95
+ flags = []
96
+ flags << 'Mobile' if result.mobile?
97
+ flags << 'Proxy/VPN' if result.proxy?
98
+ flags << 'Hosting' if result.hosting?
99
+ output << [ 'Flags', flags.join( ', ' ) ] unless flags.empty?
100
+ end
101
+
102
+ max_label = output.map { | label, _ | label.length }.max || 0
103
+ output.each do | label, value |
104
+ puts "#{ label.ljust( max_label ) } #{ value }"
105
+ end
106
+ end
107
+
108
+ # Main
109
+ query, options = parse_options( ARGV.dup )
110
+
111
+ # Set API key from environment
112
+ IpApi.api_key ENV[ 'IPAPI_API_KEY' ] if ENV[ 'IPAPI_API_KEY' ]
113
+
114
+ # Build request options
115
+ request_options = nil
116
+ if options[ :fields ] || options[ :lang ]
117
+ request_options = IpApi::QueryOptions.build do
118
+ fields options[ :fields ] if options[ :fields ]
119
+ lang options[ :lang ] if options[ :lang ]
120
+ end
121
+ end
122
+
123
+ # Make request
124
+ response = IpApi.query( query, request_options )
125
+
126
+ unless response.success?
127
+ $stderr.puts "HTTP Error: #{ response.result.error_description }"
128
+ exit 1
129
+ end
130
+
131
+ result = response.result
132
+
133
+ if options[ :json ]
134
+ require 'json'
135
+ puts JSON.pretty_generate( result.to_h )
136
+ else
137
+ format_result( result )
138
+ end
139
+
140
+ exit( result.success? ? 0 : 1 )
data/ipapi-rb.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ require_relative 'lib/ip_api/version'
2
+
3
+ Gem::Specification.new do | spec |
4
+
5
+ spec.name = 'ipapi-rb'
6
+ spec.version = IpApi::VERSION
7
+ spec.authors = [ 'Kristoph Cichocki-Romanov' ]
8
+ spec.email = [ 'rubygems.org@kristoph.net' ]
9
+
10
+ spec.summary =
11
+ "The IpApi gem implements a lightweight interface to the IP-API.com geolocation " \
12
+ "API for IP address and domain lookups."
13
+ spec.description =
14
+ "The IpApi gem implements a lightweight interface to the IP-API.com geolocation " \
15
+ "API. IP-API provides location and network information for any IP address or domain " \
16
+ "name in JSON format.\n" \
17
+ "\n" \
18
+ "This gem supports both the free endpoint (rate-limited, HTTP only) and the Pro " \
19
+ "endpoint (unlimited, HTTPS) with automatic endpoint selection based on API key " \
20
+ "presence."
21
+ spec.license = 'MIT'
22
+ spec.homepage = 'https://github.com/EndlessInternational/ipapi-rb'
23
+ spec.metadata = {
24
+ 'source_code_uri' => 'https://github.com/EndlessInternational/ipapi-rb',
25
+ 'bug_tracker_uri' => 'https://github.com/EndlessInternational/ipapi-rb/issues',
26
+ }
27
+
28
+ spec.required_ruby_version = '>= 3.0'
29
+ spec.files = Dir[ "lib/**/*.rb", "bin/ipapi", "LICENSE", "README.md", "ipapi-rb.gemspec" ]
30
+ spec.bindir = 'bin'
31
+ spec.executables = [ 'ipapi' ]
32
+ spec.require_paths = [ "lib" ]
33
+
34
+ spec.add_runtime_dependency 'faraday', '~> 2'
35
+ spec.add_runtime_dependency 'dynamicschema', '~> 2'
36
+
37
+ spec.add_development_dependency 'minitest', '~> 5.25'
38
+ spec.add_development_dependency 'debug', '~> 1.9'
39
+ spec.add_development_dependency 'vcr', '~> 6.3'
40
+
41
+ end
@@ -0,0 +1,40 @@
1
+ module IpApi
2
+ class ErrorResult
3
+
4
+ attr_reader :error_type, :error_description
5
+
6
+ def initialize( status_code, attributes = nil )
7
+ @status_code = status_code
8
+ @error_type, @error_description = status_code_to_error( status_code )
9
+ if attributes&.respond_to?( :[] )
10
+ @error_description = attributes[ :message ] if attributes[ :message ]
11
+ end
12
+ end
13
+
14
+ def success?
15
+ false
16
+ end
17
+
18
+ private
19
+
20
+ # ip-api returns HTTP 200 for most errors with status: "fail" in the body
21
+ # only rate limiting returns HTTP 429
22
+ def status_code_to_error( status_code )
23
+ case status_code
24
+ when 200
25
+ [ :unexpected_error,
26
+ "The response was successful but it did not include a valid payload." ]
27
+ when 429
28
+ [ :rate_limit_error,
29
+ "The rate limit has been exceeded. Free tier allows 45 requests per minute." ]
30
+ when 500..599
31
+ [ :server_error,
32
+ "The IP-API server encountered an error while processing the request." ]
33
+ else
34
+ [ :http_error,
35
+ "The IP-API service returned HTTP status code: '#{ status_code }'." ]
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ module IpApi
2
+ module Helpers
3
+ def string_camelize( string )
4
+ words = string.split( /[\s_\-]/ )
5
+ words.map.with_index { | word, index | index.zero? ? word.downcase : word.capitalize }.join
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module IpApi
2
+ module ModuleMethods
3
+
4
+ def connection( connection = nil )
5
+ @connection = connection if connection
6
+ @connection ||= Faraday.new { | builder | builder.adapter Faraday.default_adapter }
7
+ end
8
+
9
+ def api_key( api_key = nil )
10
+ @api_key = api_key || @api_key
11
+ @api_key
12
+ end
13
+
14
+ def query( query = nil, options = nil, &block )
15
+ IpApi::QueryRequest.new.submit( query, options, &block )
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ module IpApi
2
+ class QueryOptions
3
+ include DynamicSchema::Definable
4
+ include Helpers
5
+
6
+ LANGUAGES = [ :en, :de, :es, :'pt-BR', :fr, :ja, :'zh-CN', :ru ]
7
+
8
+ FIELDS = [
9
+ :status, :message, :query,
10
+ :continent, :continent_code, :country, :country_code, :region, :region_name,
11
+ :city, :district, :zip, :lat, :lon, :timezone, :offset, :currency,
12
+ :isp, :org, :as, :as_name, :reverse,
13
+ :mobile, :proxy, :hosting
14
+ ]
15
+
16
+ schema do
17
+ fields Symbol, array: true, in: FIELDS
18
+ lang Symbol, in: LANGUAGES
19
+ end
20
+
21
+ def self.build( options = nil, &block )
22
+ new( api_options: builder.build( options, &block ) )
23
+ end
24
+
25
+ def self.build!( options = nil, &block )
26
+ new( api_options: builder.build!( options, &block ) )
27
+ end
28
+
29
+ def initialize( options = {}, api_options: nil )
30
+ @options = self.class.builder.build( options || {} )
31
+ @options = api_options.merge( @options ) if api_options
32
+ end
33
+
34
+ def to_h
35
+ @options.to_h
36
+ end
37
+
38
+ def to_params
39
+ params = {}
40
+ hash = to_h
41
+
42
+ if hash[ :fields ] && !hash[ :fields ].empty?
43
+ params[ :fields ] = hash[ :fields ].map { | f | _snake_to_camel( f ) }.join( ',' )
44
+ end
45
+
46
+ if hash[ :lang ]
47
+ params[ :lang ] = hash[ :lang ].to_s
48
+ end
49
+
50
+ params
51
+ end
52
+
53
+ private
54
+
55
+ def _snake_to_camel( value )
56
+ value.to_s.gsub( /_([a-z])/ ) { $1.upcase }
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,26 @@
1
+ module IpApi
2
+ class QueryRequest < Request
3
+
4
+ def submit( query = nil, options = nil, &block )
5
+ if options
6
+ options = options.is_a?( QueryOptions ) ? options : QueryOptions.build!( options.to_h )
7
+ params = options.to_params
8
+ else
9
+ params = {}
10
+ end
11
+
12
+ path = query ? "/json/#{ query }" : "/json/"
13
+ response = get( "#{ base_uri }#{ path }", params, &block )
14
+ attributes = ( JSON.parse( response.body, symbolize_names: true ) rescue nil )
15
+
16
+ result = if response.success? && attributes
17
+ QueryResult.new( attributes )
18
+ else
19
+ ErrorResult.new( response.status, attributes )
20
+ end
21
+
22
+ ResponseMethods.install( response, result )
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ require 'forwardable'
2
+
3
+ module IpApi
4
+
5
+ QueryResultSchema = DynamicSchema::Struct.define do
6
+ status String
7
+ message String
8
+ query String
9
+
10
+ continent String
11
+ continent_code String, as: :continentCode
12
+ country String
13
+ country_code String, as: :countryCode
14
+ region String
15
+ region_name String, as: :regionName
16
+ city String
17
+ district String
18
+ zip String
19
+ lat Float
20
+ lon Float
21
+ timezone String
22
+ offset Integer
23
+ currency String
24
+
25
+ isp String
26
+ org String
27
+ as_number String, as: :as
28
+ as_name String, as: :asname
29
+ reverse String
30
+
31
+ mobile [ TrueClass, FalseClass ]
32
+ proxy [ TrueClass, FalseClass ]
33
+ hosting [ TrueClass, FalseClass ]
34
+ end
35
+
36
+ class QueryResult < QueryResultSchema
37
+
38
+ def success?
39
+ status == 'success'
40
+ end
41
+
42
+ def failed?
43
+ status == 'fail'
44
+ end
45
+
46
+ def mobile?
47
+ mobile == true
48
+ end
49
+
50
+ def proxy?
51
+ proxy == true
52
+ end
53
+
54
+ def hosting?
55
+ hosting == true
56
+ end
57
+
58
+ def coordinates
59
+ [ lat, lon ] if lat && lon
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,32 @@
1
+ module IpApi
2
+ class Request
3
+
4
+ FREE_BASE_URI = 'http://ip-api.com'.freeze
5
+ PRO_BASE_URI = 'https://pro.ip-api.com'.freeze
6
+
7
+ def initialize( connection: nil, api_key: nil )
8
+ @connection = connection || IpApi.connection
9
+ @api_key = api_key || IpApi.api_key
10
+ end
11
+
12
+ def pro?
13
+ !@api_key.nil? && !@api_key.empty?
14
+ end
15
+
16
+ def base_uri
17
+ pro? ? PRO_BASE_URI : FREE_BASE_URI
18
+ end
19
+
20
+ protected
21
+
22
+ def get( uri, params = {}, &block )
23
+ params[ :key ] = @api_key if pro?
24
+
25
+ @connection.get( uri ) do | request |
26
+ request.params = params unless params.empty?
27
+ block.call( request ) if block
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module IpApi
2
+ module ResponseMethods
3
+ def self.install( response, result )
4
+ response.instance_variable_set( "@_ip_api_result", result )
5
+ response.extend( ResponseMethods )
6
+ end
7
+
8
+ def result
9
+ @_ip_api_result
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module IpApi
2
+ VERSION = '1.0.0'
3
+ end
data/lib/ip_api.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'json'
2
+ require 'uri'
3
+
4
+ require 'faraday'
5
+ require 'dynamic_schema'
6
+
7
+ require_relative 'ip_api/version'
8
+
9
+ require_relative 'ip_api/helpers'
10
+ require_relative 'ip_api/error_result'
11
+ require_relative 'ip_api/request'
12
+ require_relative 'ip_api/response_methods'
13
+
14
+ require_relative 'ip_api/query_options'
15
+ require_relative 'ip_api/query_result'
16
+ require_relative 'ip_api/query_request'
17
+
18
+ require_relative 'ip_api/module_methods'
19
+
20
+ module IpApi
21
+ extend ModuleMethods
22
+ end
data/lib/ipapi-rb.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'ip_api'
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ipapi-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Kristoph Cichocki-Romanov
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: faraday
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dynamicschema
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.25'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.25'
54
+ - !ruby/object:Gem::Dependency
55
+ name: debug
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.9'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.9'
68
+ - !ruby/object:Gem::Dependency
69
+ name: vcr
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '6.3'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '6.3'
82
+ description: |-
83
+ The IpApi gem implements a lightweight interface to the IP-API.com geolocation API. IP-API provides location and network information for any IP address or domain name in JSON format.
84
+
85
+ This gem supports both the free endpoint (rate-limited, HTTP only) and the Pro endpoint (unlimited, HTTPS) with automatic endpoint selection based on API key presence.
86
+ email:
87
+ - rubygems.org@kristoph.net
88
+ executables:
89
+ - ipapi
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - README.md
94
+ - bin/ipapi
95
+ - ipapi-rb.gemspec
96
+ - lib/ip_api.rb
97
+ - lib/ip_api/error_result.rb
98
+ - lib/ip_api/helpers.rb
99
+ - lib/ip_api/module_methods.rb
100
+ - lib/ip_api/query_options.rb
101
+ - lib/ip_api/query_request.rb
102
+ - lib/ip_api/query_result.rb
103
+ - lib/ip_api/request.rb
104
+ - lib/ip_api/response_methods.rb
105
+ - lib/ip_api/version.rb
106
+ - lib/ipapi-rb.rb
107
+ homepage: https://github.com/EndlessInternational/ipapi-rb
108
+ licenses:
109
+ - MIT
110
+ metadata:
111
+ source_code_uri: https://github.com/EndlessInternational/ipapi-rb
112
+ bug_tracker_uri: https://github.com/EndlessInternational/ipapi-rb/issues
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '3.0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.6.7
128
+ specification_version: 4
129
+ summary: The IpApi gem implements a lightweight interface to the IP-API.com geolocation
130
+ API for IP address and domain lookups.
131
+ test_files: []