IPinfo 2.3.1 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d123a1cdf07b3693cc28ea77d42da4d9984cbdc8676ab235c5a837d8cfb2d272
4
- data.tar.gz: e322a8feefc0bb770b0318ecb66967c0ab50e0171bf5ca5d1921deb4ab79bdd3
3
+ metadata.gz: 908f27e7bc10bccd5d91c5697e53ef43c3399f497a797596cba28403e5fb7769
4
+ data.tar.gz: 93805a37bd2528f8f335bdfacb7c8367a8c95f4cf6728f5f9a7fbb216daeb831
5
5
  SHA512:
6
- metadata.gz: 18d5583c9b890e82dd229ee7138a7f956ed4ce8e32267cf0b0b2eb61417f85792963a83711e7756f572868cfbeefdf828460c9c547c27578d9d5d47bef909ecf
7
- data.tar.gz: d404fa4474c572d6c0e1076ecd4c460224db7393ebb9e4c1344f73ecbe4b96d92fdcd0d4f7ce2d8b5f51372c85ad2859ca5cd3328cba1b0d2070808dd17f0cfa
6
+ metadata.gz: f5178a2fee807837d3385b3e3fb00098aac28b54480d6492e11fc5e30de7ec92cd16d0c3bfb4685be15c3dfa18b2b6db44f8bc01568a63ba3fd24375421d6c11
7
+ data.tar.gz: 811a19e8a55e9a06b84e075c36d4ce52e8d127f0d5bacf944b142b0318c934d39229261e22e96a271ecf2c816175262c48e35cc593d93429880ed485e6f0c3cd
@@ -91,3 +91,81 @@ class IPinfo::AdapterLite
91
91
  headers
92
92
  end
93
93
  end
94
+
95
+ class IPinfo::AdapterCore
96
+ HOST = 'https://api.ipinfo.io/lookup'
97
+
98
+ attr_reader :conn
99
+
100
+ def initialize(token = nil, adapter = :net_http)
101
+ @token = token
102
+ @conn = connection(adapter)
103
+ end
104
+
105
+ def get(uri)
106
+ @conn.get(HOST + uri) do |req|
107
+ default_headers.each_pair do |key, value|
108
+ req.headers[key] = value
109
+ end
110
+ req.params['token'] = CGI.escape(token) if token
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ attr_reader :token
117
+
118
+ def connection(adapter)
119
+ Faraday.new() do |conn|
120
+ conn.adapter(adapter)
121
+ end
122
+ end
123
+
124
+ def default_headers
125
+ headers = {
126
+ 'User-Agent' => "IPinfoClient/Ruby/#{IPinfo::VERSION}",
127
+ 'Accept' => 'application/json'
128
+ }
129
+ headers['Authorization'] = "Bearer #{CGI.escape(token)}" if token
130
+ headers
131
+ end
132
+ end
133
+
134
+ class IPinfo::AdapterPlus
135
+ HOST = 'https://api.ipinfo.io/lookup'
136
+
137
+ attr_reader :conn
138
+
139
+ def initialize(token = nil, adapter = :net_http)
140
+ @token = token
141
+ @conn = connection(adapter)
142
+ end
143
+
144
+ def get(uri)
145
+ @conn.get(HOST + uri) do |req|
146
+ default_headers.each_pair do |key, value|
147
+ req.headers[key] = value
148
+ end
149
+ req.params['token'] = CGI.escape(token) if token
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ attr_reader :token
156
+
157
+ def connection(adapter)
158
+ Faraday.new() do |conn|
159
+ conn.adapter(adapter)
160
+ end
161
+ end
162
+
163
+ def default_headers
164
+ headers = {
165
+ 'User-Agent' => "IPinfoClient/Ruby/#{IPinfo::VERSION}",
166
+ 'Accept' => 'application/json'
167
+ }
168
+ headers['Authorization'] = "Bearer #{CGI.escape(token)}" if token
169
+ headers
170
+ end
171
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IPinfo
4
- VERSION = '2.3.1'
4
+ VERSION = '2.4.0'
5
5
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipinfo/adapter'
4
+ require 'ipinfo/cache/default_cache'
5
+ require 'ipinfo/errors'
6
+ require 'ipinfo/response'
7
+ require_relative 'ipinfo/ipAddressMatcher'
8
+ require_relative 'ipinfo/countriesData'
9
+ require 'ipaddr'
10
+ require 'cgi'
11
+
12
+ module IPinfoCore
13
+ include CountriesData
14
+ DEFAULT_CACHE_MAXSIZE = 4096
15
+ DEFAULT_CACHE_TTL = 60 * 60 * 24
16
+ RATE_LIMIT_MESSAGE = 'To increase your limits, please review our ' \
17
+ 'paid plans at https://ipinfo.io/pricing'
18
+ # Base URL to get country flag image link.
19
+ # "PK" -> "https://cdn.ipinfo.io/static/images/countries-flags/PK.svg"
20
+ COUNTRY_FLAGS_URL = 'https://cdn.ipinfo.io/static/images/countries-flags/'
21
+
22
+ class << self
23
+ def create(access_token = nil, settings = {})
24
+ IPinfo::IPinfoCore.new(access_token, settings)
25
+ end
26
+ end
27
+ end
28
+
29
+ class IPinfo::IPinfoCore
30
+ include IPinfoCore
31
+ attr_accessor :access_token, :countries, :httpc
32
+
33
+ def initialize(access_token = nil, settings = {})
34
+ @access_token = access_token
35
+ @httpc = IPinfo::AdapterCore.new(access_token, httpc || :net_http)
36
+
37
+ maxsize = settings.fetch('maxsize', DEFAULT_CACHE_MAXSIZE)
38
+ ttl = settings.fetch('ttl', DEFAULT_CACHE_TTL)
39
+ @cache = settings.fetch('cache', IPinfo::DefaultCache.new(ttl, maxsize))
40
+ @countries = settings.fetch('countries', DEFAULT_COUNTRY_LIST)
41
+ @eu_countries = settings.fetch('eu_countries', DEFAULT_EU_COUNTRIES_LIST)
42
+ @countries_flags = settings.fetch('countries_flags', DEFAULT_COUNTRIES_FLAG_LIST)
43
+ @countries_currencies = settings.fetch('countries_currencies', DEFAULT_COUNTRIES_CURRENCIES_LIST)
44
+ @continents = settings.fetch('continents', DEFAULT_CONTINENT_LIST)
45
+ end
46
+
47
+ def details(ip_address = nil)
48
+ details_base(ip_address)
49
+ end
50
+
51
+ def request_details(ip_address = nil)
52
+ if ip_address && isBogon(ip_address)
53
+ details = {}
54
+ details[:ip] = ip_address
55
+ details[:bogon] = true
56
+ details[:ip_address] = IPAddr.new(ip_address)
57
+ return details
58
+ end
59
+
60
+ res = @cache.get(cache_key(ip_address))
61
+ return res unless res.nil?
62
+
63
+ response = @httpc.get(escape_path(ip_address))
64
+
65
+ if response.status.eql?(429)
66
+ raise RateLimitError,
67
+ RATE_LIMIT_MESSAGE
68
+ end
69
+
70
+ details = JSON.parse(response.body, symbolize_names: true)
71
+ @cache.set(cache_key(ip_address), details)
72
+ details
73
+ end
74
+
75
+ def details_base(ip_address)
76
+ details = request_details(ip_address)
77
+
78
+ # Core response has nested geo object
79
+ if details.key?(:geo) && details[:geo].is_a?(Hash) && details[:geo].key?(:country_code)
80
+ country_code = details[:geo][:country_code]
81
+ details[:geo][:country_name] = @countries.fetch(country_code, nil)
82
+ details[:geo][:is_eu] = @eu_countries.include?(country_code)
83
+ details[:geo][:country_flag] = @countries_flags.fetch(country_code, nil)
84
+ details[:geo][:country_currency] = @countries_currencies.fetch(country_code, nil)
85
+ details[:geo][:continent] = @continents.fetch(country_code, nil)
86
+ details[:geo][:country_flag_url] = "#{COUNTRY_FLAGS_URL}#{country_code}.svg"
87
+ end
88
+
89
+ # Handle top-level country_code if present (for certain edge cases)
90
+ if details.key?(:country_code)
91
+ country_code = details[:country_code]
92
+ details[:country_name] = @countries.fetch(country_code, nil)
93
+ details[:is_eu] = @eu_countries.include?(country_code)
94
+ details[:country_flag] = @countries_flags.fetch(country_code, nil)
95
+ details[:country_currency] = @countries_currencies.fetch(country_code, nil)
96
+ details[:continent] = @continents.fetch(country_code, nil)
97
+ details[:country_flag_url] = "#{COUNTRY_FLAGS_URL}#{country_code}.svg"
98
+ end
99
+
100
+ if details.key? :ip
101
+ details[:ip_address] =
102
+ IPAddr.new(details.fetch(:ip))
103
+ end
104
+
105
+ IPinfo::Response.new(details)
106
+ end
107
+
108
+ def isBogon(ip)
109
+ if ip.nil?
110
+ return false
111
+ end
112
+
113
+ matcher_object = IPinfo::IpAddressMatcher.new(ip)
114
+ matcher_object.matches
115
+ end
116
+
117
+ def escape_path(ip)
118
+ ip ? "/#{CGI.escape(ip)}" : '/'
119
+ end
120
+
121
+ def cache_key(ip)
122
+ "1:#{ip}"
123
+ end
124
+ end
data/lib/ipinfo_lite.rb CHANGED
@@ -50,10 +50,10 @@ class IPinfo::IPinfoLite
50
50
 
51
51
  def request_details(ip_address = nil)
52
52
  if ip_address && ip_address != 'me' && isBogon(ip_address)
53
+ details = {}
53
54
  details[:ip] = ip_address
54
55
  details[:bogon] = true
55
56
  details[:ip_address] = IPAddr.new(ip_address)
56
-
57
57
  return details
58
58
  end
59
59
 
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipinfo/adapter'
4
+ require 'ipinfo/cache/default_cache'
5
+ require 'ipinfo/errors'
6
+ require 'ipinfo/response'
7
+ require_relative 'ipinfo/ipAddressMatcher'
8
+ require_relative 'ipinfo/countriesData'
9
+ require 'ipaddr'
10
+ require 'cgi'
11
+
12
+ module IPinfoPlus
13
+ include CountriesData
14
+ DEFAULT_CACHE_MAXSIZE = 4096
15
+ DEFAULT_CACHE_TTL = 60 * 60 * 24
16
+ RATE_LIMIT_MESSAGE = 'To increase your limits, please review our ' \
17
+ 'paid plans at https://ipinfo.io/pricing'
18
+ # Base URL to get country flag image link.
19
+ # "PK" -> "https://cdn.ipinfo.io/static/images/countries-flags/PK.svg"
20
+ COUNTRY_FLAGS_URL = 'https://cdn.ipinfo.io/static/images/countries-flags/'
21
+
22
+ class << self
23
+ def create(access_token = nil, settings = {})
24
+ IPinfo::IPinfoPlus.new(access_token, settings)
25
+ end
26
+ end
27
+ end
28
+
29
+ class IPinfo::IPinfoPlus
30
+ include IPinfoPlus
31
+ attr_accessor :access_token, :countries, :httpc
32
+
33
+ def initialize(access_token = nil, settings = {})
34
+ @access_token = access_token
35
+ @httpc = IPinfo::AdapterPlus.new(access_token, httpc || :net_http)
36
+
37
+ maxsize = settings.fetch('maxsize', DEFAULT_CACHE_MAXSIZE)
38
+ ttl = settings.fetch('ttl', DEFAULT_CACHE_TTL)
39
+ @cache = settings.fetch('cache', IPinfo::DefaultCache.new(ttl, maxsize))
40
+ @countries = settings.fetch('countries', DEFAULT_COUNTRY_LIST)
41
+ @eu_countries = settings.fetch('eu_countries', DEFAULT_EU_COUNTRIES_LIST)
42
+ @countries_flags = settings.fetch('countries_flags', DEFAULT_COUNTRIES_FLAG_LIST)
43
+ @countries_currencies = settings.fetch('countries_currencies', DEFAULT_COUNTRIES_CURRENCIES_LIST)
44
+ @continents = settings.fetch('continents', DEFAULT_CONTINENT_LIST)
45
+ end
46
+
47
+ def details(ip_address = nil)
48
+ details_base(ip_address)
49
+ end
50
+
51
+ def request_details(ip_address = nil)
52
+ if ip_address && isBogon(ip_address)
53
+ details = {}
54
+ details[:ip] = ip_address
55
+ details[:bogon] = true
56
+ details[:ip_address] = IPAddr.new(ip_address)
57
+ return details
58
+ end
59
+
60
+ res = @cache.get(cache_key(ip_address))
61
+ return res unless res.nil?
62
+
63
+ response = @httpc.get(escape_path(ip_address))
64
+
65
+ if response.status.eql?(429)
66
+ raise RateLimitError,
67
+ RATE_LIMIT_MESSAGE
68
+ end
69
+
70
+ details = JSON.parse(response.body, symbolize_names: true)
71
+ @cache.set(cache_key(ip_address), details)
72
+ details
73
+ end
74
+
75
+ def details_base(ip_address)
76
+ details = request_details(ip_address)
77
+
78
+ # Plus response has nested geo object (same structure as Core)
79
+ if details.key?(:geo) && details[:geo].is_a?(Hash) && details[:geo].key?(:country_code)
80
+ country_code = details[:geo][:country_code]
81
+ details[:geo][:country_name] = @countries.fetch(country_code, nil)
82
+ details[:geo][:is_eu] = @eu_countries.include?(country_code)
83
+ details[:geo][:country_flag] = @countries_flags.fetch(country_code, nil)
84
+ details[:geo][:country_currency] = @countries_currencies.fetch(country_code, nil)
85
+ details[:geo][:continent] = @continents.fetch(country_code, nil)
86
+ details[:geo][:country_flag_url] = "#{COUNTRY_FLAGS_URL}#{country_code}.svg"
87
+ end
88
+
89
+ # Handle top-level country_code if present (for certain edge cases)
90
+ if details.key?(:country_code)
91
+ country_code = details[:country_code]
92
+ details[:country_name] = @countries.fetch(country_code, nil)
93
+ details[:is_eu] = @eu_countries.include?(country_code)
94
+ details[:country_flag] = @countries_flags.fetch(country_code, nil)
95
+ details[:country_currency] = @countries_currencies.fetch(country_code, nil)
96
+ details[:continent] = @continents.fetch(country_code, nil)
97
+ details[:country_flag_url] = "#{COUNTRY_FLAGS_URL}#{country_code}.svg"
98
+ end
99
+
100
+ if details.key? :ip
101
+ details[:ip_address] =
102
+ IPAddr.new(details.fetch(:ip))
103
+ end
104
+
105
+ IPinfo::Response.new(details)
106
+ end
107
+
108
+ def isBogon(ip)
109
+ if ip.nil?
110
+ return false
111
+ end
112
+
113
+ matcher_object = IPinfo::IpAddressMatcher.new(ip)
114
+ matcher_object.matches
115
+ end
116
+
117
+ def escape_path(ip)
118
+ ip ? "/#{CGI.escape(ip)}" : '/'
119
+ end
120
+
121
+ def cache_key(ip)
122
+ "1:#{ip}"
123
+ end
124
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: IPinfo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - IPinfo releases
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-04 00:00:00.000000000 Z
11
+ date: 2025-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -166,7 +166,9 @@ files:
166
166
  - lib/ipinfo/mod.rb
167
167
  - lib/ipinfo/response.rb
168
168
  - lib/ipinfo/version.rb
169
+ - lib/ipinfo_core.rb
169
170
  - lib/ipinfo_lite.rb
171
+ - lib/ipinfo_plus.rb
170
172
  homepage: https://ipinfo.io
171
173
  licenses: []
172
174
  metadata: {}