IPinfo 1.0.1 → 2.2.4
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 +4 -4
- data/.github/workflows/cd_rubygems.yaml +37 -0
- data/.github/workflows/unittest.yaml +35 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -1
- data/README.md +88 -20
- data/ipinfo.gemspec +8 -10
- data/lib/ipinfo/adapter.rb +15 -5
- data/lib/ipinfo/countriesData.rb +1045 -0
- data/lib/ipinfo/ipAddressMatcher.rb +92 -0
- data/lib/ipinfo/version.rb +1 -1
- data/lib/ipinfo.rb +123 -32
- metadata +92 -6
- data/.travis.yml +0 -11
- data/lib/ipinfo/countries.json +0 -1
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'resolv'
|
4
|
+
require 'ipaddr'
|
5
|
+
|
6
|
+
class IPinfo::IpAddressMatcher
|
7
|
+
attr_accessor :required_address
|
8
|
+
|
9
|
+
@@bogon_list = [
|
10
|
+
"0.0.0.0/8",
|
11
|
+
"10.0.0.0/8",
|
12
|
+
"100.64.0.0/10",
|
13
|
+
"127.0.0.0/8",
|
14
|
+
"169.254.0.0/16",
|
15
|
+
"172.16.0.0/12",
|
16
|
+
"192.0.0.0/24",
|
17
|
+
"192.0.2.0/24",
|
18
|
+
"192.168.0.0/16",
|
19
|
+
"198.18.0.0/15",
|
20
|
+
"198.51.100.0/24",
|
21
|
+
"203.0.113.0/24",
|
22
|
+
"224.0.0.0/4",
|
23
|
+
"240.0.0.0/4",
|
24
|
+
"255.255.255.255/32",
|
25
|
+
"::/128",
|
26
|
+
"::1/128",
|
27
|
+
"::ffff:0:0/96",
|
28
|
+
"::/96",
|
29
|
+
"100::/64",
|
30
|
+
"2001:10::/28",
|
31
|
+
"2001:db8::/32",
|
32
|
+
"fc00::/7",
|
33
|
+
"fe80::/10",
|
34
|
+
"fec0::/10",
|
35
|
+
"ff00::/8",
|
36
|
+
"2002::/24",
|
37
|
+
"2002:a00::/24",
|
38
|
+
"2002:7f00::/24",
|
39
|
+
"2002:a9fe::/32",
|
40
|
+
"2002:ac10::/28",
|
41
|
+
"2002:c000::/40",
|
42
|
+
"2002:c000:200::/40",
|
43
|
+
"2002:c0a8::/32",
|
44
|
+
"2002:c612::/31",
|
45
|
+
"2002:c633:6400::/40",
|
46
|
+
"2002:cb00:7100::/40",
|
47
|
+
"2002:e000::/20",
|
48
|
+
"2002:f000::/20",
|
49
|
+
"2002:ffff:ffff::/48",
|
50
|
+
"2001::/40",
|
51
|
+
"2001:0:a00::/40",
|
52
|
+
"2001:0:7f00::/40",
|
53
|
+
"2001:0:a9fe::/48",
|
54
|
+
"2001:0:ac10::/44",
|
55
|
+
"2001:0:c000::/56",
|
56
|
+
"2001:0:c000:200::/56",
|
57
|
+
"2001:0:c0a8::/48",
|
58
|
+
"2001:0:c612::/47",
|
59
|
+
"2001:0:c633:6400::/56",
|
60
|
+
"2001:0:cb00:7100::/56",
|
61
|
+
"2001:0:e000::/36",
|
62
|
+
"2001:0:f000::/36",
|
63
|
+
"2001:0:ffff:ffff::/64"
|
64
|
+
]
|
65
|
+
|
66
|
+
def initialize(ip)
|
67
|
+
if !ip.index('/').nil? and ip.index('/') > 0
|
68
|
+
addr_mask = ip.split('/')
|
69
|
+
ip = addr_mask[0]
|
70
|
+
end
|
71
|
+
|
72
|
+
@required_address = parseAddress(ip)
|
73
|
+
end
|
74
|
+
|
75
|
+
def parseAddress(ip)
|
76
|
+
begin
|
77
|
+
Resolv.getaddress ip
|
78
|
+
rescue Resolv::ResolvError => e
|
79
|
+
pp e
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def matches
|
84
|
+
for bogon in @@bogon_list do
|
85
|
+
if bogon.include? required_address
|
86
|
+
return true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
false
|
91
|
+
end
|
92
|
+
end
|
data/lib/ipinfo/version.rb
CHANGED
data/lib/ipinfo.rb
CHANGED
@@ -8,14 +8,18 @@ require 'ipinfo/errors'
|
|
8
8
|
require 'ipinfo/response'
|
9
9
|
require 'ipinfo/version'
|
10
10
|
require 'json'
|
11
|
+
require_relative 'ipinfo/ipAddressMatcher'
|
12
|
+
require_relative 'ipinfo/countriesData'
|
11
13
|
|
12
14
|
module IPinfo
|
15
|
+
include CountriesData
|
13
16
|
DEFAULT_CACHE_MAXSIZE = 4096
|
14
17
|
DEFAULT_CACHE_TTL = 60 * 60 * 24
|
15
|
-
DEFAULT_COUNTRY_FILE = File.join(File.dirname(__FILE__),
|
16
|
-
'ipinfo/countries.json')
|
17
18
|
RATE_LIMIT_MESSAGE = 'To increase your limits, please review our ' \
|
18
19
|
'paid plans at https://ipinfo.io/pricing'
|
20
|
+
# Base URL to get country flag image link.
|
21
|
+
# "PK" -> "https://cdn.ipinfo.io/static/images/countries-flags/PK.svg"
|
22
|
+
COUNTRY_FLAGS_URL = "https://cdn.ipinfo.io/static/images/countries-flags/"
|
19
23
|
|
20
24
|
class << self
|
21
25
|
def create(access_token = nil, settings = {})
|
@@ -30,43 +34,102 @@ class IPinfo::IPinfo
|
|
30
34
|
|
31
35
|
def initialize(access_token = nil, settings = {})
|
32
36
|
@access_token = access_token
|
33
|
-
|
37
|
+
prepare_http_client(settings.fetch('http_client', nil))
|
34
38
|
|
35
39
|
maxsize = settings.fetch('maxsize', DEFAULT_CACHE_MAXSIZE)
|
36
40
|
ttl = settings.fetch('ttl', DEFAULT_CACHE_TTL)
|
37
41
|
@cache = settings.fetch('cache', DefaultCache.new(ttl, maxsize))
|
38
|
-
@countries =
|
39
|
-
|
42
|
+
@countries = settings.fetch('countries', DEFAULT_COUNTRY_LIST)
|
43
|
+
@eu_countries = settings.fetch('eu_countries', DEFAULT_EU_COUNTRIES_LIST)
|
44
|
+
@countries_flags = settings.fetch('countries_flags', DEFAULT_COUNTRIES_FLAG_LIST)
|
45
|
+
@countries_currencies = settings.fetch('countries_currencies', DEFAULT_COUNTRIES_CURRENCIES_LIST)
|
46
|
+
@continents = settings.fetch('continents', DEFAULT_CONTINENT_LIST)
|
40
47
|
end
|
41
48
|
|
42
49
|
def details(ip_address = nil)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
50
|
+
details_base(ip_address, :v4)
|
51
|
+
end
|
52
|
+
|
53
|
+
def details_v6(ip_address = nil)
|
54
|
+
details_base(ip_address, :v6)
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_map_url(ips)
|
58
|
+
if !ips.kind_of?(Array)
|
59
|
+
return JSON.generate({:error => 'Invalid input. Array required!'})
|
60
|
+
end
|
61
|
+
if ips.length > 500000
|
62
|
+
return JSON.generate({:error => 'No more than 500,000 ips allowed!'})
|
47
63
|
end
|
48
64
|
|
49
|
-
|
50
|
-
|
51
|
-
|
65
|
+
json_ips = JSON.generate({:ips => ips})
|
66
|
+
res = @httpc.post('/tools/map', json_ips)
|
67
|
+
|
68
|
+
obj = JSON.parse(res.body)
|
69
|
+
obj['reportUrl']
|
70
|
+
end
|
71
|
+
|
72
|
+
def batch_requests(url_array, api_token)
|
73
|
+
result = Hash.new
|
74
|
+
lookup_ips = []
|
75
|
+
|
76
|
+
url_array.each { |url|
|
77
|
+
ip = @cache.get(cache_key(url))
|
78
|
+
|
79
|
+
unless ip.nil?
|
80
|
+
result.store(url, ip)
|
81
|
+
else
|
82
|
+
lookup_ips << url
|
83
|
+
end
|
84
|
+
}
|
85
|
+
|
86
|
+
if lookup_ips.empty?
|
87
|
+
return result
|
52
88
|
end
|
53
89
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
90
|
+
begin
|
91
|
+
lookup_ips.each_slice(1000){ |ips|
|
92
|
+
json_arr = JSON.generate(lookup_ips)
|
93
|
+
res = @httpc.post("/batch?token=#{api_token}", json_arr, 5)
|
94
|
+
|
95
|
+
raise StandardError, "Request Quota Exceeded" if res.status == 429
|
96
|
+
|
97
|
+
data = JSON.parse(res.body)
|
98
|
+
data.each { |key, val|
|
99
|
+
@cache.set(cache_key(key), val)
|
100
|
+
}
|
101
|
+
|
102
|
+
result.merge!(data)
|
103
|
+
}
|
104
|
+
|
105
|
+
rescue StandardError => e
|
106
|
+
return e.message
|
58
107
|
end
|
59
108
|
|
60
|
-
|
109
|
+
result
|
61
110
|
end
|
62
111
|
|
63
112
|
protected
|
64
113
|
|
65
|
-
def
|
66
|
-
|
114
|
+
def prepare_http_client(httpc = nil)
|
115
|
+
@httpc = Adapter.new(access_token, httpc || :net_http)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def request_details(ip_address = nil, host_type)
|
121
|
+
if isBogon(ip_address)
|
122
|
+
details[:ip] = ip_address
|
123
|
+
details[:bogon] = true
|
124
|
+
details[:ip_address] = IPAddr.new(ip_address)
|
125
|
+
|
126
|
+
return details
|
127
|
+
end
|
128
|
+
|
129
|
+
res = @cache.get(cache_key(ip_address))
|
67
130
|
return res unless res.nil?
|
68
131
|
|
69
|
-
response = @httpc.get(escape_path(ip_address))
|
132
|
+
response = @httpc.get(escape_path(ip_address), host_type)
|
70
133
|
|
71
134
|
if response.status.eql?(429)
|
72
135
|
raise RateLimitError,
|
@@ -74,26 +137,54 @@ class IPinfo::IPinfo
|
|
74
137
|
end
|
75
138
|
|
76
139
|
details = JSON.parse(response.body, symbolize_names: true)
|
77
|
-
@cache.set(ip_address, details)
|
140
|
+
@cache.set(cache_key(ip_address), details)
|
78
141
|
details
|
79
142
|
end
|
80
143
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
144
|
+
def details_base(ip_address, host_type)
|
145
|
+
details = request_details(ip_address, host_type)
|
146
|
+
if details.key? :country
|
147
|
+
details[:country_name] =
|
148
|
+
@countries.fetch(details.fetch(:country), nil)
|
149
|
+
details[:is_eu] =
|
150
|
+
@eu_countries.include?(details.fetch(:country))
|
151
|
+
details[:country_flag] =
|
152
|
+
@countries_flags.fetch(details.fetch(:country), nil)
|
153
|
+
details[:country_currency] =
|
154
|
+
@countries_currencies.fetch(details.fetch(:country), nil)
|
155
|
+
details[:continent] =
|
156
|
+
@continents.fetch(details.fetch(:country), nil)
|
157
|
+
details[:country_flag_url] = COUNTRY_FLAGS_URL + details.fetch(:country) + ".svg"
|
158
|
+
end
|
88
159
|
|
89
|
-
|
90
|
-
|
91
|
-
|
160
|
+
if details.key? :ip
|
161
|
+
details[:ip_address] =
|
162
|
+
IPAddr.new(details.fetch(:ip))
|
163
|
+
end
|
164
|
+
|
165
|
+
if details.key? :loc
|
166
|
+
loc = details.fetch(:loc).split(',')
|
167
|
+
details[:latitude] = loc[0]
|
168
|
+
details[:longitude] = loc[1]
|
169
|
+
end
|
170
|
+
|
171
|
+
Response.new(details)
|
92
172
|
end
|
93
173
|
|
94
|
-
|
174
|
+
def isBogon(ip)
|
175
|
+
if ip.nil?
|
176
|
+
return false
|
177
|
+
end
|
178
|
+
|
179
|
+
matcher_object = IpAddressMatcher.new(ip)
|
180
|
+
matcher_object.matches
|
181
|
+
end
|
95
182
|
|
96
183
|
def escape_path(ip)
|
97
184
|
ip ? "/#{CGI.escape(ip)}" : '/'
|
98
185
|
end
|
186
|
+
|
187
|
+
def cache_key(ip)
|
188
|
+
"1:#{ip}"
|
189
|
+
end
|
99
190
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: IPinfo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stanislav K, James Timmins
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-09-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: faraday
|
@@ -17,14 +17,98 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '2.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '2.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: async-http-faraday
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: faraday-net_http_persistent
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '2.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '2.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: faraday-typhoeus
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
24
59
|
requirements:
|
25
60
|
- - "~>"
|
26
61
|
- !ruby/object:Gem::Version
|
27
62
|
version: '1.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: faraday-patron
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: faraday-httpclient
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '2.0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '2.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: faraday-excon
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '2.1'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '2.1'
|
28
112
|
- !ruby/object:Gem::Dependency
|
29
113
|
name: json
|
30
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,10 +146,11 @@ extensions: []
|
|
62
146
|
extra_rdoc_files: []
|
63
147
|
files:
|
64
148
|
- ".editorconfig"
|
149
|
+
- ".github/workflows/cd_rubygems.yaml"
|
150
|
+
- ".github/workflows/unittest.yaml"
|
65
151
|
- ".gitignore"
|
66
152
|
- ".rubocop.yml"
|
67
153
|
- ".ruby-version"
|
68
|
-
- ".travis.yml"
|
69
154
|
- Gemfile
|
70
155
|
- LICENSE
|
71
156
|
- README.md
|
@@ -77,8 +162,9 @@ files:
|
|
77
162
|
- lib/ipinfo/adapter.rb
|
78
163
|
- lib/ipinfo/cache/cache_interface.rb
|
79
164
|
- lib/ipinfo/cache/default_cache.rb
|
80
|
-
- lib/ipinfo/
|
165
|
+
- lib/ipinfo/countriesData.rb
|
81
166
|
- lib/ipinfo/errors.rb
|
167
|
+
- lib/ipinfo/ipAddressMatcher.rb
|
82
168
|
- lib/ipinfo/mod.rb
|
83
169
|
- lib/ipinfo/response.rb
|
84
170
|
- lib/ipinfo/version.rb
|
@@ -100,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
186
|
- !ruby/object:Gem::Version
|
101
187
|
version: '0'
|
102
188
|
requirements: []
|
103
|
-
rubygems_version: 3.
|
189
|
+
rubygems_version: 3.4.10
|
104
190
|
signing_key:
|
105
191
|
specification_version: 4
|
106
192
|
summary: This is a ruby wrapper for http://ipinfo.io.
|
data/.travis.yml
DELETED
data/lib/ipinfo/countries.json
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
{"BD": "Bangladesh", "BE": "Belgium", "BF": "Burkina Faso", "BG": "Bulgaria", "BA": "Bosnia and Herzegovina", "BB": "Barbados", "WF": "Wallis and Futuna", "BL": "Saint Barthelemy", "BM": "Bermuda", "BN": "Brunei", "BO": "Bolivia", "BH": "Bahrain", "BI": "Burundi", "BJ": "Benin", "BT": "Bhutan", "JM": "Jamaica", "BV": "Bouvet Island", "BW": "Botswana", "WS": "Samoa", "BQ": "Bonaire, Saint Eustatius and Saba ", "BR": "Brazil", "BS": "Bahamas", "JE": "Jersey", "BY": "Belarus", "BZ": "Belize", "RU": "Russia", "RW": "Rwanda", "RS": "Serbia", "TL": "East Timor", "RE": "Reunion", "TM": "Turkmenistan", "TJ": "Tajikistan", "RO": "Romania", "TK": "Tokelau", "GW": "Guinea-Bissau", "GU": "Guam", "GT": "Guatemala", "GS": "South Georgia and the South Sandwich Islands", "GR": "Greece", "GQ": "Equatorial Guinea", "GP": "Guadeloupe", "JP": "Japan", "GY": "Guyana", "GG": "Guernsey", "GF": "French Guiana", "GE": "Georgia", "GD": "Grenada", "GB": "United Kingdom", "GA": "Gabon", "SV": "El Salvador", "GN": "Guinea", "GM": "Gambia", "GL": "Greenland", "GI": "Gibraltar", "GH": "Ghana", "OM": "Oman", "TN": "Tunisia", "JO": "Jordan", "HR": "Croatia", "HT": "Haiti", "HU": "Hungary", "HK": "Hong Kong", "HN": "Honduras", "HM": "Heard Island and McDonald Islands", "VE": "Venezuela", "PR": "Puerto Rico", "PS": "Palestinian Territory", "PW": "Palau", "PT": "Portugal", "SJ": "Svalbard and Jan Mayen", "PY": "Paraguay", "IQ": "Iraq", "PA": "Panama", "PF": "French Polynesia", "PG": "Papua New Guinea", "PE": "Peru", "PK": "Pakistan", "PH": "Philippines", "PN": "Pitcairn", "PL": "Poland", "PM": "Saint Pierre and Miquelon", "ZM": "Zambia", "EH": "Western Sahara", "EE": "Estonia", "EG": "Egypt", "ZA": "South Africa", "EC": "Ecuador", "IT": "Italy", "VN": "Vietnam", "SB": "Solomon Islands", "ET": "Ethiopia", "SO": "Somalia", "ZW": "Zimbabwe", "SA": "Saudi Arabia", "ES": "Spain", "ER": "Eritrea", "ME": "Montenegro", "MD": "Moldova", "MG": "Madagascar", "MF": "Saint Martin", "MA": "Morocco", "MC": "Monaco", "UZ": "Uzbekistan", "MM": "Myanmar", "ML": "Mali", "MO": "Macao", "MN": "Mongolia", "MH": "Marshall Islands", "MK": "Macedonia", "MU": "Mauritius", "MT": "Malta", "MW": "Malawi", "MV": "Maldives", "MQ": "Martinique", "MP": "Northern Mariana Islands", "MS": "Montserrat", "MR": "Mauritania", "IM": "Isle of Man", "UG": "Uganda", "TZ": "Tanzania", "MY": "Malaysia", "MX": "Mexico", "IL": "Israel", "FR": "France", "IO": "British Indian Ocean Territory", "SH": "Saint Helena", "FI": "Finland", "FJ": "Fiji", "FK": "Falkland Islands", "FM": "Micronesia", "FO": "Faroe Islands", "NI": "Nicaragua", "NL": "Netherlands", "NO": "Norway", "NA": "Namibia", "VU": "Vanuatu", "NC": "New Caledonia", "NE": "Niger", "NF": "Norfolk Island", "NG": "Nigeria", "NZ": "New Zealand", "NP": "Nepal", "NR": "Nauru", "NU": "Niue", "CK": "Cook Islands", "XK": "Kosovo", "CI": "Ivory Coast", "CH": "Switzerland", "CO": "Colombia", "CN": "China", "CM": "Cameroon", "CL": "Chile", "CC": "Cocos Islands", "CA": "Canada", "CG": "Republic of the Congo", "CF": "Central African Republic", "CD": "Democratic Republic of the Congo", "CZ": "Czech Republic", "CY": "Cyprus", "CX": "Christmas Island", "CR": "Costa Rica", "CW": "Curacao", "CV": "Cape Verde", "CU": "Cuba", "SZ": "Swaziland", "SY": "Syria", "SX": "Sint Maarten", "KG": "Kyrgyzstan", "KE": "Kenya", "SS": "South Sudan", "SR": "Suriname", "KI": "Kiribati", "KH": "Cambodia", "KN": "Saint Kitts and Nevis", "KM": "Comoros", "ST": "Sao Tome and Principe", "SK": "Slovakia", "KR": "South Korea", "SI": "Slovenia", "KP": "North Korea", "KW": "Kuwait", "SN": "Senegal", "SM": "San Marino", "SL": "Sierra Leone", "SC": "Seychelles", "KZ": "Kazakhstan", "KY": "Cayman Islands", "SG": "Singapore", "SE": "Sweden", "SD": "Sudan", "DO": "Dominican Republic", "DM": "Dominica", "DJ": "Djibouti", "DK": "Denmark", "VG": "British Virgin Islands", "DE": "Germany", "YE": "Yemen", "DZ": "Algeria", "US": "United States", "UY": "Uruguay", "YT": "Mayotte", "UM": "United States Minor Outlying Islands", "LB": "Lebanon", "LC": "Saint Lucia", "LA": "Laos", "TV": "Tuvalu", "TW": "Taiwan", "TT": "Trinidad and Tobago", "TR": "Turkey", "LK": "Sri Lanka", "LI": "Liechtenstein", "LV": "Latvia", "TO": "Tonga", "LT": "Lithuania", "LU": "Luxembourg", "LR": "Liberia", "LS": "Lesotho", "TH": "Thailand", "TF": "French Southern Territories", "TG": "Togo", "TD": "Chad", "TC": "Turks and Caicos Islands", "LY": "Libya", "VA": "Vatican", "VC": "Saint Vincent and the Grenadines", "AE": "United Arab Emirates", "AD": "Andorra", "AG": "Antigua and Barbuda", "AF": "Afghanistan", "AI": "Anguilla", "VI": "U.S. Virgin Islands", "IS": "Iceland", "IR": "Iran", "AM": "Armenia", "AL": "Albania", "AO": "Angola", "AQ": "Antarctica", "AS": "American Samoa", "AR": "Argentina", "AU": "Australia", "AT": "Austria", "AW": "Aruba", "IN": "India", "AX": "Aland Islands", "AZ": "Azerbaijan", "IE": "Ireland", "ID": "Indonesia", "UA": "Ukraine", "QA": "Qatar", "MZ": "Mozambique"}
|