├── .github └── workflows │ └── cd_cpan.yml ├── .gitignore ├── Geo-IPinfo ├── Changes ├── MANIFEST ├── Makefile.PL ├── ignore.txt ├── lib │ └── Geo │ │ ├── Details.pm │ │ └── IPinfo.pm ├── t │ ├── 00-load.t │ ├── 01-usage.t │ ├── manifest.t │ ├── pod-coverage.t │ └── pod.t └── xt │ └── boilerplate.t ├── README.md ├── example.pl └── scripts └── fmt.sh /.github/workflows/cd_cpan.yml: -------------------------------------------------------------------------------- 1 | name: Release package to CPAN via Pause 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Set up Perl environment 16 | uses: shogo82148/actions-setup-perl@v1 17 | with: 18 | perl-version: '5.34' 19 | install-modules: 'CPAN::Uploader' 20 | 21 | - name: Test and build 22 | run: | 23 | cd Geo-IPinfo 24 | cpanm ExtUtils::MakeMaker LWP::UserAgent JSON Cache::LRU Net::CIDR Net::CIDR::Set 25 | perl Makefile.PL && RELEASE_TESTING=TRUE make test && make distcheck && make dist 26 | 27 | - name: Upload to CPAN 28 | run: | 29 | cd Geo-IPinfo 30 | cpan-upload -v -u $USERNAME -p $PASSWORD *.tar.gz 31 | env: 32 | USERNAME: ${{ secrets.PAUSE_USERNAME }} 33 | PASSWORD: ${{ secrets.PAUSE_PASSWORD }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./t 2 | .vscode 3 | -------------------------------------------------------------------------------- /Geo-IPinfo/Changes: -------------------------------------------------------------------------------- 1 | Revision history for Geo-IPinfo 2 | 3 | 1.0 Nov 07, 2017 4 | Initial release 5 | 6 | 2.0 Nov 29, 2018 7 | Add Caching, breaking changes 8 | 9 | 2.1 Aug 03, 2023 10 | Matching to API response structure 11 | 12 | 2.1.1 Aug 03, 2023 13 | Fixing Testcases and formatting codebase 14 | 15 | 2.1.2 Aug 04, 2023 16 | Making release ready 17 | 18 | 2.1.3 Aug 09, 2023 19 | Enabled JSON encoding and updated docs 20 | 21 | 3.0.0 Nov 22, 2023 22 | Inlined data files 23 | -------------------------------------------------------------------------------- /Geo-IPinfo/MANIFEST: -------------------------------------------------------------------------------- 1 | Changes 2 | lib/Geo/IPinfo.pm 3 | lib/Geo/Details.pm 4 | Makefile.PL 5 | MANIFEST This list of files 6 | ignore.txt 7 | t/00-load.t 8 | t/01-usage.t 9 | t/manifest.t 10 | t/pod-coverage.t 11 | t/pod.t 12 | xt/boilerplate.t 13 | -------------------------------------------------------------------------------- /Geo-IPinfo/Makefile.PL: -------------------------------------------------------------------------------- 1 | use 5.006; 2 | use strict; 3 | use warnings; 4 | use ExtUtils::MakeMaker; 5 | 6 | WriteMakefile( 7 | NAME => 'Geo::IPinfo', 8 | AUTHOR => q{ipinfo.io }, 9 | VERSION_FROM => 'lib/Geo/IPinfo.pm', 10 | ABSTRACT_FROM => 'lib/Geo/IPinfo.pm', 11 | LICENSE => 'apache_2_0', 12 | PL_FILES => {}, 13 | MIN_PERL_VERSION => '5.006', 14 | CONFIGURE_REQUIRES => { 15 | 'ExtUtils::MakeMaker' => '0', 16 | }, 17 | BUILD_REQUIRES => { 18 | 'Test::More' => '0', 19 | }, 20 | PREREQ_PM => { 21 | 'LWP::UserAgent' => '0', 22 | 'JSON' => '0', 23 | 'Cache::LRU' => '0', 24 | 'Net::CIDR' => '0', 25 | 'Net::CIDR::Set' => '0', 26 | }, 27 | dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, 28 | clean => { FILES => 'Geo-IPinfo-*' }, 29 | ); 30 | 31 | package MY; 32 | -------------------------------------------------------------------------------- /Geo-IPinfo/ignore.txt: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.old 3 | Build 4 | Build.bat 5 | META.* 6 | MYMETA.* 7 | .build/ 8 | _build/ 9 | cover_db/ 10 | blib/ 11 | inc/ 12 | .lwpcookies 13 | .last_cover_stats 14 | nytprof.out 15 | pod2htm*.tmp 16 | pm_to_blib 17 | Geo-IPinfo-* 18 | Geo-IPinfo-*.tar.gz 19 | -------------------------------------------------------------------------------- /Geo-IPinfo/lib/Geo/Details.pm: -------------------------------------------------------------------------------- 1 | package Geo::Details; 2 | 3 | use 5.006; 4 | use strict; 5 | use warnings; 6 | 7 | sub new { 8 | my $class = shift; 9 | my $data = shift; 10 | my $key = shift // ''; 11 | 12 | # If $data is a hash reference, directly bless it into the class and return. 13 | if ( ref($data) eq 'HASH' ) { 14 | bless $data, $class; 15 | return $data; 16 | } 17 | 18 | # If $data is a plain string, create a new hash reference and set the specified key to the string value. 19 | # Use the provided key or default to ''. 20 | my $self = { $key => $data }; 21 | bless $self, $class; 22 | return $self; 23 | } 24 | 25 | sub TO_JSON { 26 | my ($self) = @_; 27 | 28 | # Return a copy of the object as a hash reference for JSON encoding 29 | return {%$self}; 30 | } 31 | 32 | sub abuse { 33 | return $_[0]->{abuse}; 34 | } 35 | 36 | sub ip { 37 | return $_[0]->{ip}; 38 | } 39 | 40 | sub org { 41 | return $_[0]->{org}; 42 | } 43 | 44 | sub domains { 45 | return $_[0]->{domains}; 46 | } 47 | 48 | sub privacy { 49 | return $_[0]->{privacy}; 50 | } 51 | 52 | sub timezone { 53 | return $_[0]->{timezone}; 54 | } 55 | 56 | sub hostname { 57 | return $_[0]->{hostname}; 58 | } 59 | 60 | sub city { 61 | return $_[0]->{city}; 62 | } 63 | 64 | sub region { 65 | return $_[0]->{region}; 66 | } 67 | 68 | sub country { 69 | return $_[0]->{country}; 70 | } 71 | 72 | sub country_name { 73 | return $_[0]->{country_name}; 74 | } 75 | 76 | sub country_flag { 77 | return $_[0]->{country_flag}; 78 | } 79 | 80 | sub country_flag_url { 81 | return $_[0]->{country_flag_url}; 82 | } 83 | 84 | sub country_currency { 85 | return $_[0]->{country_currency}; 86 | } 87 | 88 | sub continent { 89 | return $_[0]->{continent}; 90 | } 91 | 92 | sub is_eu { 93 | return $_[0]->{is_eu}; 94 | } 95 | 96 | sub loc { 97 | return $_[0]->{loc}; 98 | } 99 | 100 | sub latitude { 101 | return $_[0]->{latitude}; 102 | } 103 | 104 | sub longitude { 105 | return $_[0]->{longitude}; 106 | } 107 | 108 | sub postal { 109 | return $_[0]->{postal}; 110 | } 111 | 112 | sub asn { 113 | return $_[0]->{asn}; 114 | } 115 | 116 | sub company { 117 | return $_[0]->{company}; 118 | } 119 | 120 | sub carrier { 121 | return $_[0]->{carrier}; 122 | } 123 | 124 | sub meta { 125 | return $_[0]->{meta}; 126 | } 127 | 128 | sub all { 129 | return $_[0]; 130 | } 131 | 132 | #------------------------------------------------------------------------------- 133 | 134 | 1; 135 | __END__ 136 | 137 | 138 | =head1 NAME 139 | 140 | Geo::Details - Module to represent details of a geographical location 141 | 142 | =head1 SYNOPSIS 143 | 144 | use Geo::Details; 145 | 146 | my $data = { 147 | ip => '169.48.204.140', 148 | city => 'Dallas', 149 | country => 'US', 150 | country_name => 'United States', 151 | hostname => '8c.cc.30a9.ip4.static.sl-reverse.com', 152 | country_flag_url => 'https://example.com/us.png', # URL to the country flag image 153 | # ... (other attributes) 154 | }; 155 | 156 | my $geo_details = Geo::Details->new($data); 157 | 158 | print $geo_details->ip; # Output: 192.168.1.1 159 | print $geo_details->city; # Output: New York 160 | print $geo_details->country_name; # Output: United States 161 | 162 | =head1 DESCRIPTION 163 | 164 | Geo::Details is a simple module that represents details of a geographical location. 165 | 166 | =head1 METHODS 167 | 168 | =head2 new 169 | 170 | my $geo_details = Geo::Details->new($data, $key); 171 | 172 | Creates a new Geo::Details object. If C<$data> is a hash reference, it directly blesses it into the class and returns the object. If C<$data> is a plain string, it creates a new hash reference with the specified key and sets the string value. 173 | 174 | C<$key> is an optional parameter used when C<$data> is a plain string. It defaults to an empty string if not provided. 175 | 176 | =head2 TO_JSON 177 | 178 | This method is used to convert the object to a JSON representation. 179 | 180 | =head2 abuse 181 | 182 | my $abuse_email = $geo_details->abuse(); 183 | 184 | Returns the abuse contact email address associated with the geographical location. 185 | 186 | =head2 ip 187 | 188 | my $ip_address = $geo_details->ip(); 189 | 190 | Returns the IP address associated with the geographical location. 191 | 192 | =head2 org 193 | 194 | my $organization = $geo_details->org(); 195 | 196 | Returns the organization associated with the geographical location. 197 | 198 | =head2 domains 199 | 200 | my $domains_ref = $geo_details->domains(); 201 | 202 | Returns a reference to an array containing the domain names associated with the IP address. 203 | 204 | =head2 privacy 205 | 206 | my $privacy_policy = $geo_details->privacy(); 207 | 208 | Returns the privacy policy related to the geographical location. 209 | 210 | =head2 timezone 211 | 212 | my $timezone = $geo_details->timezone(); 213 | 214 | Returns the timezone information of the geographical location. 215 | 216 | =head2 hostname 217 | 218 | my $hostname = $geo_details->hostname(); 219 | 220 | Returns the hostname associated with the IP address. 221 | 222 | =head2 city 223 | 224 | my $city_name = $geo_details->city(); 225 | 226 | Returns the city name of the geographical location. 227 | 228 | =head2 region 229 | 230 | my $region_name = $geo_details->region(); 231 | 232 | Returns the region or state name of the geographical location. 233 | 234 | =head2 country 235 | 236 | my $country_code = $geo_details->country(); 237 | 238 | Returns the ISO 3166-1 alpha-2 code of the country associated with the geographical location. 239 | 240 | =head2 country_name 241 | 242 | my $country_name = $geo_details->country_name(); 243 | 244 | Returns the full name of the country associated with the geographical location. 245 | 246 | =head2 country_flag 247 | 248 | my $country_flag_code = $geo_details->country_flag(); 249 | 250 | Returns the ISO 3166-1 alpha-2 code for the country flag associated with the geographical location. 251 | 252 | =head2 country_flag_url 253 | 254 | my $flag_url = $geo_details->country_flag_url(); 255 | 256 | Returns the URL to the country flag image associated with the geographical location. 257 | 258 | =head2 country_currency 259 | 260 | my $currency_code = $geo_details->country_currency(); 261 | 262 | Returns the currency code used in the country associated with the geographical location. 263 | 264 | =head2 continent 265 | 266 | my $continent_code = $geo_details->continent(); 267 | 268 | Returns the continent code of the geographical location. 269 | 270 | =head2 is_eu 271 | 272 | my $is_eu_country = $geo_details->is_eu(); 273 | 274 | Returns true if the country associated with the geographical location is in the European Union (EU). 275 | 276 | =head2 loc 277 | 278 | my $location_string = $geo_details->loc(); 279 | 280 | Returns a string representing the latitude and longitude of the geographical location. 281 | 282 | =head2 latitude 283 | 284 | my $latitude = $geo_details->latitude(); 285 | 286 | Returns the latitude coordinate of the geographical location. 287 | 288 | =head2 longitude 289 | 290 | my $longitude = $geo_details->longitude(); 291 | 292 | Returns the longitude coordinate of the geographical location. 293 | 294 | =head2 postal 295 | 296 | my $postal_code = $geo_details->postal(); 297 | 298 | Returns the postal or ZIP code associated with the geographical location. 299 | 300 | =head2 asn 301 | 302 | my $asn_number = $geo_details->asn(); 303 | 304 | Returns the Autonomous System Number (ASN) associated with the IP address. 305 | 306 | =head2 company 307 | 308 | my $company_name = $geo_details->company(); 309 | 310 | Returns the name of the company or organization associated with the geographical location. 311 | 312 | =head2 carrier 313 | 314 | my $carrier_name = $geo_details->carrier(); 315 | 316 | Returns the name of the carrier or internet service provider (ISP) associated with the IP address. 317 | 318 | =head2 meta 319 | 320 | my $meta_data_ref = $geo_details->meta(); 321 | 322 | Returns a reference to the meta-data hash containing additional information about the geographical location. 323 | 324 | =head2 all 325 | 326 | my $all_details_ref = $geo_details->all(); 327 | 328 | Returns a reference to the hash containing all the details of the geographical location. 329 | 330 | =head1 AUTHOR 331 | 332 | Your Name 333 | 334 | =head1 LICENSE 335 | 336 | This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. 337 | 338 | =cut 339 | 340 | # End of Geo::Details 341 | -------------------------------------------------------------------------------- /Geo-IPinfo/lib/Geo/IPinfo.pm: -------------------------------------------------------------------------------- 1 | package Geo::IPinfo; 2 | 3 | use 5.006; 4 | use strict; 5 | use warnings; 6 | use Cache::LRU; 7 | use LWP::UserAgent; 8 | use HTTP::Headers; 9 | use JSON; 10 | use Geo::Details; 11 | use Net::CIDR; 12 | use Net::CIDR::Set; 13 | 14 | our $VERSION = '3.0.1'; 15 | use constant DEFAULT_CACHE_MAX_SIZE => 4096; 16 | use constant DEFAULT_CACHE_TTL => 86_400; 17 | use constant DEFAULT_TIMEOUT => 2; 18 | use constant HTTP_TOO_MANY_REQUEST => 429; 19 | 20 | my %valid_fields = ( 21 | ip => 1, 22 | hostname => 1, 23 | city => 1, 24 | region => 1, 25 | country => 1, 26 | loc => 1, 27 | org => 1, 28 | postal => 1, 29 | timezone => 1, 30 | geo => 1, 31 | asn => 1, 32 | company => 1, 33 | privacy => 1, 34 | abuse => 1, 35 | domains => 1, 36 | ); 37 | my $base_url = 'https://ipinfo.io/'; 38 | my $base_url_ipv6 = 'https://v6.ipinfo.io/'; 39 | my $country_flag_url = 'https://cdn.ipinfo.io/static/images/countries-flags/'; 40 | my $cache_ttl = 0; 41 | my $custom_cache = 0; 42 | my %default_countries = ( 43 | "BD" => "Bangladesh", 44 | "BE" => "Belgium", 45 | "BF" => "Burkina Faso", 46 | "BG" => "Bulgaria", 47 | "BA" => "Bosnia and Herzegovina", 48 | "BB" => "Barbados", 49 | "WF" => "Wallis and Futuna", 50 | "BL" => "Saint Barthelemy", 51 | "BM" => "Bermuda", 52 | "BN" => "Brunei", 53 | "BO" => "Bolivia", 54 | "BH" => "Bahrain", 55 | "BI" => "Burundi", 56 | "BJ" => "Benin", 57 | "BT" => "Bhutan", 58 | "JM" => "Jamaica", 59 | "BV" => "Bouvet Island", 60 | "BW" => "Botswana", 61 | "WS" => "Samoa", 62 | "BQ" => "Bonaire, Saint Eustatius and Saba ", 63 | "BR" => "Brazil", 64 | "BS" => "Bahamas", 65 | "JE" => "Jersey", 66 | "BY" => "Belarus", 67 | "BZ" => "Belize", 68 | "RU" => "Russia", 69 | "RW" => "Rwanda", 70 | "RS" => "Serbia", 71 | "TL" => "East Timor", 72 | "RE" => "Reunion", 73 | "TM" => "Turkmenistan", 74 | "TJ" => "Tajikistan", 75 | "RO" => "Romania", 76 | "TK" => "Tokelau", 77 | "GW" => "Guinea-Bissau", 78 | "GU" => "Guam", 79 | "GT" => "Guatemala", 80 | "GS" => "South Georgia and the South Sandwich Islands", 81 | "GR" => "Greece", 82 | "GQ" => "Equatorial Guinea", 83 | "GP" => "Guadeloupe", 84 | "JP" => "Japan", 85 | "GY" => "Guyana", 86 | "GG" => "Guernsey", 87 | "GF" => "French Guiana", 88 | "GE" => "Georgia", 89 | "GD" => "Grenada", 90 | "GB" => "United Kingdom", 91 | "GA" => "Gabon", 92 | "SV" => "El Salvador", 93 | "GN" => "Guinea", 94 | "GM" => "Gambia", 95 | "GL" => "Greenland", 96 | "GI" => "Gibraltar", 97 | "GH" => "Ghana", 98 | "OM" => "Oman", 99 | "TN" => "Tunisia", 100 | "JO" => "Jordan", 101 | "HR" => "Croatia", 102 | "HT" => "Haiti", 103 | "HU" => "Hungary", 104 | "HK" => "Hong Kong", 105 | "HN" => "Honduras", 106 | "HM" => "Heard Island and McDonald Islands", 107 | "VE" => "Venezuela", 108 | "PR" => "Puerto Rico", 109 | "PS" => "Palestinian Territory", 110 | "PW" => "Palau", 111 | "PT" => "Portugal", 112 | "SJ" => "Svalbard and Jan Mayen", 113 | "PY" => "Paraguay", 114 | "IQ" => "Iraq", 115 | "PA" => "Panama", 116 | "PF" => "French Polynesia", 117 | "PG" => "Papua New Guinea", 118 | "PE" => "Peru", 119 | "PK" => "Pakistan", 120 | "PH" => "Philippines", 121 | "PN" => "Pitcairn", 122 | "PL" => "Poland", 123 | "PM" => "Saint Pierre and Miquelon", 124 | "ZM" => "Zambia", 125 | "EH" => "Western Sahara", 126 | "EE" => "Estonia", 127 | "EG" => "Egypt", 128 | "ZA" => "South Africa", 129 | "EC" => "Ecuador", 130 | "IT" => "Italy", 131 | "VN" => "Vietnam", 132 | "SB" => "Solomon Islands", 133 | "ET" => "Ethiopia", 134 | "SO" => "Somalia", 135 | "ZW" => "Zimbabwe", 136 | "SA" => "Saudi Arabia", 137 | "ES" => "Spain", 138 | "ER" => "Eritrea", 139 | "ME" => "Montenegro", 140 | "MD" => "Moldova", 141 | "MG" => "Madagascar", 142 | "MF" => "Saint Martin", 143 | "MA" => "Morocco", 144 | "MC" => "Monaco", 145 | "UZ" => "Uzbekistan", 146 | "MM" => "Myanmar", 147 | "ML" => "Mali", 148 | "MO" => "Macao", 149 | "MN" => "Mongolia", 150 | "MH" => "Marshall Islands", 151 | "MK" => "Macedonia", 152 | "MU" => "Mauritius", 153 | "MT" => "Malta", 154 | "MW" => "Malawi", 155 | "MV" => "Maldives", 156 | "MQ" => "Martinique", 157 | "MP" => "Northern Mariana Islands", 158 | "MS" => "Montserrat", 159 | "MR" => "Mauritania", 160 | "IM" => "Isle of Man", 161 | "UG" => "Uganda", 162 | "TZ" => "Tanzania", 163 | "MY" => "Malaysia", 164 | "MX" => "Mexico", 165 | "IL" => "Israel", 166 | "FR" => "France", 167 | "IO" => "British Indian Ocean Territory", 168 | "SH" => "Saint Helena", 169 | "FI" => "Finland", 170 | "FJ" => "Fiji", 171 | "FK" => "Falkland Islands", 172 | "FM" => "Micronesia", 173 | "FO" => "Faroe Islands", 174 | "NI" => "Nicaragua", 175 | "NL" => "Netherlands", 176 | "NO" => "Norway", 177 | "NA" => "Namibia", 178 | "VU" => "Vanuatu", 179 | "NC" => "New Caledonia", 180 | "NE" => "Niger", 181 | "NF" => "Norfolk Island", 182 | "NG" => "Nigeria", 183 | "NZ" => "New Zealand", 184 | "NP" => "Nepal", 185 | "NR" => "Nauru", 186 | "NU" => "Niue", 187 | "CK" => "Cook Islands", 188 | "XK" => "Kosovo", 189 | "CI" => "Ivory Coast", 190 | "CH" => "Switzerland", 191 | "CO" => "Colombia", 192 | "CN" => "China", 193 | "CM" => "Cameroon", 194 | "CL" => "Chile", 195 | "CC" => "Cocos Islands", 196 | "CA" => "Canada", 197 | "CG" => "Republic of the Congo", 198 | "CF" => "Central African Republic", 199 | "CD" => "Democratic Republic of the Congo", 200 | "CZ" => "Czech Republic", 201 | "CY" => "Cyprus", 202 | "CX" => "Christmas Island", 203 | "CR" => "Costa Rica", 204 | "CW" => "Curacao", 205 | "CV" => "Cape Verde", 206 | "CU" => "Cuba", 207 | "SZ" => "Swaziland", 208 | "SY" => "Syria", 209 | "SX" => "Sint Maarten", 210 | "KG" => "Kyrgyzstan", 211 | "KE" => "Kenya", 212 | "SS" => "South Sudan", 213 | "SR" => "Suriname", 214 | "KI" => "Kiribati", 215 | "KH" => "Cambodia", 216 | "KN" => "Saint Kitts and Nevis", 217 | "KM" => "Comoros", 218 | "ST" => "Sao Tome and Principe", 219 | "SK" => "Slovakia", 220 | "KR" => "South Korea", 221 | "SI" => "Slovenia", 222 | "KP" => "North Korea", 223 | "KW" => "Kuwait", 224 | "SN" => "Senegal", 225 | "SM" => "San Marino", 226 | "SL" => "Sierra Leone", 227 | "SC" => "Seychelles", 228 | "KZ" => "Kazakhstan", 229 | "KY" => "Cayman Islands", 230 | "SG" => "Singapore", 231 | "SE" => "Sweden", 232 | "SD" => "Sudan", 233 | "DO" => "Dominican Republic", 234 | "DM" => "Dominica", 235 | "DJ" => "Djibouti", 236 | "DK" => "Denmark", 237 | "VG" => "British Virgin Islands", 238 | "DE" => "Germany", 239 | "YE" => "Yemen", 240 | "DZ" => "Algeria", 241 | "US" => "United States", 242 | "UY" => "Uruguay", 243 | "YT" => "Mayotte", 244 | "UM" => "United States Minor Outlying Islands", 245 | "LB" => "Lebanon", 246 | "LC" => "Saint Lucia", 247 | "LA" => "Laos", 248 | "TV" => "Tuvalu", 249 | "TW" => "Taiwan", 250 | "TT" => "Trinidad and Tobago", 251 | "TR" => "Turkey", 252 | "LK" => "Sri Lanka", 253 | "LI" => "Liechtenstein", 254 | "LV" => "Latvia", 255 | "TO" => "Tonga", 256 | "LT" => "Lithuania", 257 | "LU" => "Luxembourg", 258 | "LR" => "Liberia", 259 | "LS" => "Lesotho", 260 | "TH" => "Thailand", 261 | "TF" => "French Southern Territories", 262 | "TG" => "Togo", 263 | "TD" => "Chad", 264 | "TC" => "Turks and Caicos Islands", 265 | "LY" => "Libya", 266 | "VA" => "Vatican", 267 | "VC" => "Saint Vincent and the Grenadines", 268 | "AE" => "United Arab Emirates", 269 | "AD" => "Andorra", 270 | "AG" => "Antigua and Barbuda", 271 | "AF" => "Afghanistan", 272 | "AI" => "Anguilla", 273 | "VI" => "U.S. Virgin Islands", 274 | "IS" => "Iceland", 275 | "IR" => "Iran", 276 | "AM" => "Armenia", 277 | "AL" => "Albania", 278 | "AO" => "Angola", 279 | "AQ" => "Antarctica", 280 | "AS" => "American Samoa", 281 | "AR" => "Argentina", 282 | "AU" => "Australia", 283 | "AT" => "Austria", 284 | "AW" => "Aruba", 285 | "IN" => "India", 286 | "AX" => "Aland Islands", 287 | "AZ" => "Azerbaijan", 288 | "IE" => "Ireland", 289 | "ID" => "Indonesia", 290 | "UA" => "Ukraine", 291 | "QA" => "Qatar", 292 | "MZ" => "Mozambique" 293 | ); 294 | my %default_countries_flags = ( 295 | 'AD' => { 'emoji' => '🇦🇩', 'unicode' => 'U+1F1E6 U+1F1E9' }, 296 | 'AE' => { 'emoji' => '🇦🇪', 'unicode' => 'U+1F1E6 U+1F1EA' }, 297 | 'AF' => { 'emoji' => '🇦🇫', 'unicode' => 'U+1F1E6 U+1F1EB' }, 298 | 'AG' => { 'emoji' => '🇦🇬', 'unicode' => 'U+1F1E6 U+1F1EC' }, 299 | 'AI' => { 'emoji' => '🇦🇮', 'unicode' => 'U+1F1E6 U+1F1EE' }, 300 | 'AL' => { 'emoji' => '🇦🇱', 'unicode' => 'U+1F1E6 U+1F1F1' }, 301 | 'AM' => { 'emoji' => '🇦🇲', 'unicode' => 'U+1F1E6 U+1F1F2' }, 302 | 'AO' => { 'emoji' => '🇦🇴', 'unicode' => 'U+1F1E6 U+1F1F4' }, 303 | 'AQ' => { 'emoji' => '🇦🇶', 'unicode' => 'U+1F1E6 U+1F1F6' }, 304 | 'AR' => { 'emoji' => '🇦🇷', 'unicode' => 'U+1F1E6 U+1F1F7' }, 305 | 'AS' => { 'emoji' => '🇦🇸', 'unicode' => 'U+1F1E6 U+1F1F8' }, 306 | 'AT' => { 'emoji' => '🇦🇹', 'unicode' => 'U+1F1E6 U+1F1F9' }, 307 | 'AU' => { 'emoji' => '🇦🇺', 'unicode' => 'U+1F1E6 U+1F1FA' }, 308 | 'AW' => { 'emoji' => '🇦🇼', 'unicode' => 'U+1F1E6 U+1F1FC' }, 309 | 'AX' => { 'emoji' => '🇦🇽', 'unicode' => 'U+1F1E6 U+1F1FD' }, 310 | 'AZ' => { 'emoji' => '🇦🇿', 'unicode' => 'U+1F1E6 U+1F1FF' }, 311 | 'BA' => { 'emoji' => '🇧🇦', 'unicode' => 'U+1F1E7 U+1F1E6' }, 312 | 'BB' => { 'emoji' => '🇧🇧', 'unicode' => 'U+1F1E7 U+1F1E7' }, 313 | 'BD' => { 'emoji' => '🇧🇩', 'unicode' => 'U+1F1E7 U+1F1E9' }, 314 | 'BE' => { 'emoji' => '🇧🇪', 'unicode' => 'U+1F1E7 U+1F1EA' }, 315 | 'BF' => { 'emoji' => '🇧🇫', 'unicode' => 'U+1F1E7 U+1F1EB' }, 316 | 'BG' => { 'emoji' => '🇧🇬', 'unicode' => 'U+1F1E7 U+1F1EC' }, 317 | 'BH' => { 'emoji' => '🇧🇭', 'unicode' => 'U+1F1E7 U+1F1ED' }, 318 | 'BI' => { 'emoji' => '🇧🇮', 'unicode' => 'U+1F1E7 U+1F1EE' }, 319 | 'BJ' => { 'emoji' => '🇧🇯', 'unicode' => 'U+1F1E7 U+1F1EF' }, 320 | 'BL' => { 'emoji' => '🇧🇱', 'unicode' => 'U+1F1E7 U+1F1F1' }, 321 | 'BM' => { 'emoji' => '🇧🇲', 'unicode' => 'U+1F1E7 U+1F1F2' }, 322 | 'BN' => { 'emoji' => '🇧🇳', 'unicode' => 'U+1F1E7 U+1F1F3' }, 323 | 'BO' => { 'emoji' => '🇧🇴', 'unicode' => 'U+1F1E7 U+1F1F4' }, 324 | 'BQ' => { 'emoji' => '🇧🇶', 'unicode' => 'U+1F1E7 U+1F1F6' }, 325 | 'BR' => { 'emoji' => '🇧🇷', 'unicode' => 'U+1F1E7 U+1F1F7' }, 326 | 'BS' => { 'emoji' => '🇧🇸', 'unicode' => 'U+1F1E7 U+1F1F8' }, 327 | 'BT' => { 'emoji' => '🇧🇹', 'unicode' => 'U+1F1E7 U+1F1F9' }, 328 | 'BV' => { 'emoji' => '🇧🇻', 'unicode' => 'U+1F1E7 U+1F1FB' }, 329 | 'BW' => { 'emoji' => '🇧🇼', 'unicode' => 'U+1F1E7 U+1F1FC' }, 330 | 'BY' => { 'emoji' => '🇧🇾', 'unicode' => 'U+1F1E7 U+1F1FE' }, 331 | 'BZ' => { 'emoji' => '🇧🇿', 'unicode' => 'U+1F1E7 U+1F1FF' }, 332 | 'CA' => { 'emoji' => '🇨🇦', 'unicode' => 'U+1F1E8 U+1F1E6' }, 333 | 'CC' => { 'emoji' => '🇨🇨', 'unicode' => 'U+1F1E8 U+1F1E8' }, 334 | 'CD' => { 'emoji' => '🇨🇩', 'unicode' => 'U+1F1E8 U+1F1E9' }, 335 | 'CF' => { 'emoji' => '🇨🇫', 'unicode' => 'U+1F1E8 U+1F1EB' }, 336 | 'CG' => { 'emoji' => '🇨🇬', 'unicode' => 'U+1F1E8 U+1F1EC' }, 337 | 'CH' => { 'emoji' => '🇨🇭', 'unicode' => 'U+1F1E8 U+1F1ED' }, 338 | 'CI' => { 'emoji' => '🇨🇮', 'unicode' => 'U+1F1E8 U+1F1EE' }, 339 | 'CK' => { 'emoji' => '🇨🇰', 'unicode' => 'U+1F1E8 U+1F1F0' }, 340 | 'CL' => { 'emoji' => '🇨🇱', 'unicode' => 'U+1F1E8 U+1F1F1' }, 341 | 'CM' => { 'emoji' => '🇨🇲', 'unicode' => 'U+1F1E8 U+1F1F2' }, 342 | 'CN' => { 'emoji' => '🇨🇳', 'unicode' => 'U+1F1E8 U+1F1F3' }, 343 | 'CO' => { 'emoji' => '🇨🇴', 'unicode' => 'U+1F1E8 U+1F1F4' }, 344 | 'CR' => { 'emoji' => '🇨🇷', 'unicode' => 'U+1F1E8 U+1F1F7' }, 345 | 'CU' => { 'emoji' => '🇨🇺', 'unicode' => 'U+1F1E8 U+1F1FA' }, 346 | 'CV' => { 'emoji' => '🇨🇻', 'unicode' => 'U+1F1E8 U+1F1FB' }, 347 | 'CW' => { 'emoji' => '🇨🇼', 'unicode' => 'U+1F1E8 U+1F1FC' }, 348 | 'CX' => { 'emoji' => '🇨🇽', 'unicode' => 'U+1F1E8 U+1F1FD' }, 349 | 'CY' => { 'emoji' => '🇨🇾', 'unicode' => 'U+1F1E8 U+1F1FE' }, 350 | 'CZ' => { 'emoji' => '🇨🇿', 'unicode' => 'U+1F1E8 U+1F1FF' }, 351 | 'DE' => { 'emoji' => '🇩🇪', 'unicode' => 'U+1F1E9 U+1F1EA' }, 352 | 'DJ' => { 'emoji' => '🇩🇯', 'unicode' => 'U+1F1E9 U+1F1EF' }, 353 | 'DK' => { 'emoji' => '🇩🇰', 'unicode' => 'U+1F1E9 U+1F1F0' }, 354 | 'DM' => { 'emoji' => '🇩🇲', 'unicode' => 'U+1F1E9 U+1F1F2' }, 355 | 'DO' => { 'emoji' => '🇩🇴', 'unicode' => 'U+1F1E9 U+1F1F4' }, 356 | 'DZ' => { 'emoji' => '🇩🇿', 'unicode' => 'U+1F1E9 U+1F1FF' }, 357 | 'EC' => { 'emoji' => '🇪🇨', 'unicode' => 'U+1F1EA U+1F1E8' }, 358 | 'EE' => { 'emoji' => '🇪🇪', 'unicode' => 'U+1F1EA U+1F1EA' }, 359 | 'EG' => { 'emoji' => '🇪🇬', 'unicode' => 'U+1F1EA U+1F1EC' }, 360 | 'EH' => { 'emoji' => '🇪🇭', 'unicode' => 'U+1F1EA U+1F1ED' }, 361 | 'ER' => { 'emoji' => '🇪🇷', 'unicode' => 'U+1F1EA U+1F1F7' }, 362 | 'ES' => { 'emoji' => '🇪🇸', 'unicode' => 'U+1F1EA U+1F1F8' }, 363 | 'ET' => { 'emoji' => '🇪🇹', 'unicode' => 'U+1F1EA U+1F1F9' }, 364 | 'FI' => { 'emoji' => '🇫🇮', 'unicode' => 'U+1F1EB U+1F1EE' }, 365 | 'FJ' => { 'emoji' => '🇫🇯', 'unicode' => 'U+1F1EB U+1F1EF' }, 366 | 'FK' => { 'emoji' => '🇫🇰', 'unicode' => 'U+1F1EB U+1F1F0' }, 367 | 'FM' => { 'emoji' => '🇫🇲', 'unicode' => 'U+1F1EB U+1F1F2' }, 368 | 'FO' => { 'emoji' => '🇫🇴', 'unicode' => 'U+1F1EB U+1F1F4' }, 369 | 'FR' => { 'emoji' => '🇫🇷', 'unicode' => 'U+1F1EB U+1F1F7' }, 370 | 'GA' => { 'emoji' => '🇬🇦', 'unicode' => 'U+1F1EC U+1F1E6' }, 371 | 'GB' => { 'emoji' => '🇬🇧', 'unicode' => 'U+1F1EC U+1F1E7' }, 372 | 'GD' => { 'emoji' => '🇬🇩', 'unicode' => 'U+1F1EC U+1F1E9' }, 373 | 'GE' => { 'emoji' => '🇬🇪', 'unicode' => 'U+1F1EC U+1F1EA' }, 374 | 'GF' => { 'emoji' => '🇬🇫', 'unicode' => 'U+1F1EC U+1F1EB' }, 375 | 'GG' => { 'emoji' => '🇬🇬', 'unicode' => 'U+1F1EC U+1F1EC' }, 376 | 'GH' => { 'emoji' => '🇬🇭', 'unicode' => 'U+1F1EC U+1F1ED' }, 377 | 'GI' => { 'emoji' => '🇬🇮', 'unicode' => 'U+1F1EC U+1F1EE' }, 378 | 'GL' => { 'emoji' => '🇬🇱', 'unicode' => 'U+1F1EC U+1F1F1' }, 379 | 'GM' => { 'emoji' => '🇬🇲', 'unicode' => 'U+1F1EC U+1F1F2' }, 380 | 'GN' => { 'emoji' => '🇬🇳', 'unicode' => 'U+1F1EC U+1F1F3' }, 381 | 'GP' => { 'emoji' => '🇬🇵', 'unicode' => 'U+1F1EC U+1F1F5' }, 382 | 'GQ' => { 'emoji' => '🇬🇶', 'unicode' => 'U+1F1EC U+1F1F6' }, 383 | 'GR' => { 'emoji' => '🇬🇷', 'unicode' => 'U+1F1EC U+1F1F7' }, 384 | 'GS' => { 'emoji' => '🇬🇸', 'unicode' => 'U+1F1EC U+1F1F8' }, 385 | 'GT' => { 'emoji' => '🇬🇹', 'unicode' => 'U+1F1EC U+1F1F9' }, 386 | 'GU' => { 'emoji' => '🇬🇺', 'unicode' => 'U+1F1EC U+1F1FA' }, 387 | 'GW' => { 'emoji' => '🇬🇼', 'unicode' => 'U+1F1EC U+1F1FC' }, 388 | 'GY' => { 'emoji' => '🇬🇾', 'unicode' => 'U+1F1EC U+1F1FE' }, 389 | 'HK' => { 'emoji' => '🇭🇰', 'unicode' => 'U+1F1ED U+1F1F0' }, 390 | 'HM' => { 'emoji' => '🇭🇲', 'unicode' => 'U+1F1ED U+1F1F2' }, 391 | 'HN' => { 'emoji' => '🇭🇳', 'unicode' => 'U+1F1ED U+1F1F3' }, 392 | 'HR' => { 'emoji' => '🇭🇷', 'unicode' => 'U+1F1ED U+1F1F7' }, 393 | 'HT' => { 'emoji' => '🇭🇹', 'unicode' => 'U+1F1ED U+1F1F9' }, 394 | 'HU' => { 'emoji' => '🇭🇺', 'unicode' => 'U+1F1ED U+1F1FA' }, 395 | 'ID' => { 'emoji' => '🇮🇩', 'unicode' => 'U+1F1EE U+1F1E9' }, 396 | 'IE' => { 'emoji' => '🇮🇪', 'unicode' => 'U+1F1EE U+1F1EA' }, 397 | 'IL' => { 'emoji' => '🇮🇱', 'unicode' => 'U+1F1EE U+1F1F1' }, 398 | 'IM' => { 'emoji' => '🇮🇲', 'unicode' => 'U+1F1EE U+1F1F2' }, 399 | 'IN' => { 'emoji' => '🇮🇳', 'unicode' => 'U+1F1EE U+1F1F3' }, 400 | 'IO' => { 'emoji' => '🇮🇴', 'unicode' => 'U+1F1EE U+1F1F4' }, 401 | 'IQ' => { 'emoji' => '🇮🇶', 'unicode' => 'U+1F1EE U+1F1F6' }, 402 | 'IR' => { 'emoji' => '🇮🇷', 'unicode' => 'U+1F1EE U+1F1F7' }, 403 | 'IS' => { 'emoji' => '🇮🇸', 'unicode' => 'U+1F1EE U+1F1F8' }, 404 | 'IT' => { 'emoji' => '🇮🇹', 'unicode' => 'U+1F1EE U+1F1F9' }, 405 | 'JE' => { 'emoji' => '🇯🇪', 'unicode' => 'U+1F1EF U+1F1EA' }, 406 | 'JM' => { 'emoji' => '🇯🇲', 'unicode' => 'U+1F1EF U+1F1F2' }, 407 | 'JO' => { 'emoji' => '🇯🇴', 'unicode' => 'U+1F1EF U+1F1F4' }, 408 | 'JP' => { 'emoji' => '🇯🇵', 'unicode' => 'U+1F1EF U+1F1F5' }, 409 | 'KE' => { 'emoji' => '🇰🇪', 'unicode' => 'U+1F1F0 U+1F1EA' }, 410 | 'KG' => { 'emoji' => '🇰🇬', 'unicode' => 'U+1F1F0 U+1F1EC' }, 411 | 'KH' => { 'emoji' => '🇰🇭', 'unicode' => 'U+1F1F0 U+1F1ED' }, 412 | 'KI' => { 'emoji' => '🇰🇮', 'unicode' => 'U+1F1F0 U+1F1EE' }, 413 | 'KM' => { 'emoji' => '🇰🇲', 'unicode' => 'U+1F1F0 U+1F1F2' }, 414 | 'KN' => { 'emoji' => '🇰🇳', 'unicode' => 'U+1F1F0 U+1F1F3' }, 415 | 'KP' => { 'emoji' => '🇰🇵', 'unicode' => 'U+1F1F0 U+1F1F5' }, 416 | 'KR' => { 'emoji' => '🇰🇷', 'unicode' => 'U+1F1F0 U+1F1F7' }, 417 | 'KW' => { 'emoji' => '🇰🇼', 'unicode' => 'U+1F1F0 U+1F1FC' }, 418 | 'KY' => { 'emoji' => '🇰🇾', 'unicode' => 'U+1F1F0 U+1F1FE' }, 419 | 'KZ' => { 'emoji' => '🇰🇿', 'unicode' => 'U+1F1F0 U+1F1FF' }, 420 | 'LA' => { 'emoji' => '🇱🇦', 'unicode' => 'U+1F1F1 U+1F1E6' }, 421 | 'LB' => { 'emoji' => '🇱🇧', 'unicode' => 'U+1F1F1 U+1F1E7' }, 422 | 'LC' => { 'emoji' => '🇱🇨', 'unicode' => 'U+1F1F1 U+1F1E8' }, 423 | 'LI' => { 'emoji' => '🇱🇮', 'unicode' => 'U+1F1F1 U+1F1EE' }, 424 | 'LK' => { 'emoji' => '🇱🇰', 'unicode' => 'U+1F1F1 U+1F1F0' }, 425 | 'LR' => { 'emoji' => '🇱🇷', 'unicode' => 'U+1F1F1 U+1F1F7' }, 426 | 'LS' => { 'emoji' => '🇱🇸', 'unicode' => 'U+1F1F1 U+1F1F8' }, 427 | 'LT' => { 'emoji' => '🇱🇹', 'unicode' => 'U+1F1F1 U+1F1F9' }, 428 | 'LU' => { 'emoji' => '🇱🇺', 'unicode' => 'U+1F1F1 U+1F1FA' }, 429 | 'LV' => { 'emoji' => '🇱🇻', 'unicode' => 'U+1F1F1 U+1F1FB' }, 430 | 'LY' => { 'emoji' => '🇱🇾', 'unicode' => 'U+1F1F1 U+1F1FE' }, 431 | 'MA' => { 'emoji' => '🇲🇦', 'unicode' => 'U+1F1F2 U+1F1E6' }, 432 | 'MC' => { 'emoji' => '🇲🇨', 'unicode' => 'U+1F1F2 U+1F1E8' }, 433 | 'MD' => { 'emoji' => '🇲🇩', 'unicode' => 'U+1F1F2 U+1F1E9' }, 434 | 'ME' => { 'emoji' => '🇲🇪', 'unicode' => 'U+1F1F2 U+1F1EA' }, 435 | 'MF' => { 'emoji' => '🇲🇫', 'unicode' => 'U+1F1F2 U+1F1EB' }, 436 | 'MG' => { 'emoji' => '🇲🇬', 'unicode' => 'U+1F1F2 U+1F1EC' }, 437 | 'MH' => { 'emoji' => '🇲🇭', 'unicode' => 'U+1F1F2 U+1F1ED' }, 438 | 'MK' => { 'emoji' => '🇲🇰', 'unicode' => 'U+1F1F2 U+1F1F0' }, 439 | 'ML' => { 'emoji' => '🇲🇱', 'unicode' => 'U+1F1F2 U+1F1F1' }, 440 | 'MM' => { 'emoji' => '🇲🇲', 'unicode' => 'U+1F1F2 U+1F1F2' }, 441 | 'MN' => { 'emoji' => '🇲🇳', 'unicode' => 'U+1F1F2 U+1F1F3' }, 442 | 'MO' => { 'emoji' => '🇲🇴', 'unicode' => 'U+1F1F2 U+1F1F4' }, 443 | 'MP' => { 'emoji' => '🇲🇵', 'unicode' => 'U+1F1F2 U+1F1F5' }, 444 | 'MQ' => { 'emoji' => '🇲🇶', 'unicode' => 'U+1F1F2 U+1F1F6' }, 445 | 'MR' => { 'emoji' => '🇲🇷', 'unicode' => 'U+1F1F2 U+1F1F7' }, 446 | 'MS' => { 'emoji' => '🇲🇸', 'unicode' => 'U+1F1F2 U+1F1F8' }, 447 | 'MT' => { 'emoji' => '🇲🇹', 'unicode' => 'U+1F1F2 U+1F1F9' }, 448 | 'MU' => { 'emoji' => '🇲🇺', 'unicode' => 'U+1F1F2 U+1F1FA' }, 449 | 'MV' => { 'emoji' => '🇲🇻', 'unicode' => 'U+1F1F2 U+1F1FB' }, 450 | 'MW' => { 'emoji' => '🇲🇼', 'unicode' => 'U+1F1F2 U+1F1FC' }, 451 | 'MX' => { 'emoji' => '🇲🇽', 'unicode' => 'U+1F1F2 U+1F1FD' }, 452 | 'MY' => { 'emoji' => '🇲🇾', 'unicode' => 'U+1F1F2 U+1F1FE' }, 453 | 'MZ' => { 'emoji' => '🇲🇿', 'unicode' => 'U+1F1F2 U+1F1FF' }, 454 | 'NA' => { 'emoji' => '🇳🇦', 'unicode' => 'U+1F1F3 U+1F1E6' }, 455 | 'NC' => { 'emoji' => '🇳🇨', 'unicode' => 'U+1F1F3 U+1F1E8' }, 456 | 'NE' => { 'emoji' => '🇳🇪', 'unicode' => 'U+1F1F3 U+1F1EA' }, 457 | 'NF' => { 'emoji' => '🇳🇫', 'unicode' => 'U+1F1F3 U+1F1EB' }, 458 | 'NG' => { 'emoji' => '🇳🇬', 'unicode' => 'U+1F1F3 U+1F1EC' }, 459 | 'NI' => { 'emoji' => '🇳🇮', 'unicode' => 'U+1F1F3 U+1F1EE' }, 460 | 'NL' => { 'emoji' => '🇳🇱', 'unicode' => 'U+1F1F3 U+1F1F1' }, 461 | 'NO' => { 'emoji' => '🇳🇴', 'unicode' => 'U+1F1F3 U+1F1F4' }, 462 | 'NP' => { 'emoji' => '🇳🇵', 'unicode' => 'U+1F1F3 U+1F1F5' }, 463 | 'NR' => { 'emoji' => '🇳🇷', 'unicode' => 'U+1F1F3 U+1F1F7' }, 464 | 'NU' => { 'emoji' => '🇳🇺', 'unicode' => 'U+1F1F3 U+1F1FA' }, 465 | 'NZ' => { 'emoji' => '🇳🇿', 'unicode' => 'U+1F1F3 U+1F1FF' }, 466 | 'OM' => { 'emoji' => '🇴🇲', 'unicode' => 'U+1F1F4 U+1F1F2' }, 467 | 'PA' => { 'emoji' => '🇵🇦', 'unicode' => 'U+1F1F5 U+1F1E6' }, 468 | 'PE' => { 'emoji' => '🇵🇪', 'unicode' => 'U+1F1F5 U+1F1EA' }, 469 | 'PF' => { 'emoji' => '🇵🇫', 'unicode' => 'U+1F1F5 U+1F1EB' }, 470 | 'PG' => { 'emoji' => '🇵🇬', 'unicode' => 'U+1F1F5 U+1F1EC' }, 471 | 'PH' => { 'emoji' => '🇵🇭', 'unicode' => 'U+1F1F5 U+1F1ED' }, 472 | 'PK' => { 'emoji' => '🇵🇰', 'unicode' => 'U+1F1F5 U+1F1F0' }, 473 | 'PL' => { 'emoji' => '🇵🇱', 'unicode' => 'U+1F1F5 U+1F1F1' }, 474 | 'PM' => { 'emoji' => '🇵🇲', 'unicode' => 'U+1F1F5 U+1F1F2' }, 475 | 'PN' => { 'emoji' => '🇵🇳', 'unicode' => 'U+1F1F5 U+1F1F3' }, 476 | 'PR' => { 'emoji' => '🇵🇷', 'unicode' => 'U+1F1F5 U+1F1F7' }, 477 | 'PS' => { 'emoji' => '🇵🇸', 'unicode' => 'U+1F1F5 U+1F1F8' }, 478 | 'PT' => { 'emoji' => '🇵🇹', 'unicode' => 'U+1F1F5 U+1F1F9' }, 479 | 'PW' => { 'emoji' => '🇵🇼', 'unicode' => 'U+1F1F5 U+1F1FC' }, 480 | 'PY' => { 'emoji' => '🇵🇾', 'unicode' => 'U+1F1F5 U+1F1FE' }, 481 | 'QA' => { 'emoji' => '🇶🇦', 'unicode' => 'U+1F1F6 U+1F1E6' }, 482 | 'RE' => { 'emoji' => '🇷🇪', 'unicode' => 'U+1F1F7 U+1F1EA' }, 483 | 'RO' => { 'emoji' => '🇷🇴', 'unicode' => 'U+1F1F7 U+1F1F4' }, 484 | 'RS' => { 'emoji' => '🇷🇸', 'unicode' => 'U+1F1F7 U+1F1F8' }, 485 | 'RU' => { 'emoji' => '🇷🇺', 'unicode' => 'U+1F1F7 U+1F1FA' }, 486 | 'RW' => { 'emoji' => '🇷🇼', 'unicode' => 'U+1F1F7 U+1F1FC' }, 487 | 'SA' => { 'emoji' => '🇸🇦', 'unicode' => 'U+1F1F8 U+1F1E6' }, 488 | 'SB' => { 'emoji' => '🇸🇧', 'unicode' => 'U+1F1F8 U+1F1E7' }, 489 | 'SC' => { 'emoji' => '🇸🇨', 'unicode' => 'U+1F1F8 U+1F1E8' }, 490 | 'SD' => { 'emoji' => '🇸🇩', 'unicode' => 'U+1F1F8 U+1F1E9' }, 491 | 'SE' => { 'emoji' => '🇸🇪', 'unicode' => 'U+1F1F8 U+1F1EA' }, 492 | 'SG' => { 'emoji' => '🇸🇬', 'unicode' => 'U+1F1F8 U+1F1EC' }, 493 | 'SH' => { 'emoji' => '🇸🇭', 'unicode' => 'U+1F1F8 U+1F1ED' }, 494 | 'SI' => { 'emoji' => '🇸🇮', 'unicode' => 'U+1F1F8 U+1F1EE' }, 495 | 'SJ' => { 'emoji' => '🇸🇯', 'unicode' => 'U+1F1F8 U+1F1EF' }, 496 | 'SK' => { 'emoji' => '🇸🇰', 'unicode' => 'U+1F1F8 U+1F1F0' }, 497 | 'SL' => { 'emoji' => '🇸🇱', 'unicode' => 'U+1F1F8 U+1F1F1' }, 498 | 'SM' => { 'emoji' => '🇸🇲', 'unicode' => 'U+1F1F8 U+1F1F2' }, 499 | 'SN' => { 'emoji' => '🇸🇳', 'unicode' => 'U+1F1F8 U+1F1F3' }, 500 | 'SO' => { 'emoji' => '🇸🇴', 'unicode' => 'U+1F1F8 U+1F1F4' }, 501 | 'SR' => { 'emoji' => '🇸🇷', 'unicode' => 'U+1F1F8 U+1F1F7' }, 502 | 'SS' => { 'emoji' => '🇸🇸', 'unicode' => 'U+1F1F8 U+1F1F8' }, 503 | 'ST' => { 'emoji' => '🇸🇹', 'unicode' => 'U+1F1F8 U+1F1F9' }, 504 | 'SV' => { 'emoji' => '🇸🇻', 'unicode' => 'U+1F1F8 U+1F1FB' }, 505 | 'SX' => { 'emoji' => '🇸🇽', 'unicode' => 'U+1F1F8 U+1F1FD' }, 506 | 'SY' => { 'emoji' => '🇸🇾', 'unicode' => 'U+1F1F8 U+1F1FE' }, 507 | 'SZ' => { 'emoji' => '🇸🇿', 'unicode' => 'U+1F1F8 U+1F1FF' }, 508 | 'TC' => { 'emoji' => '🇹🇨', 'unicode' => 'U+1F1F9 U+1F1E8' }, 509 | 'TD' => { 'emoji' => '🇹🇩', 'unicode' => 'U+1F1F9 U+1F1E9' }, 510 | 'TF' => { 'emoji' => '🇹🇫', 'unicode' => 'U+1F1F9 U+1F1EB' }, 511 | 'TG' => { 'emoji' => '🇹🇬', 'unicode' => 'U+1F1F9 U+1F1EC' }, 512 | 'TH' => { 'emoji' => '🇹🇭', 'unicode' => 'U+1F1F9 U+1F1ED' }, 513 | 'TJ' => { 'emoji' => '🇹🇯', 'unicode' => 'U+1F1F9 U+1F1EF' }, 514 | 'TK' => { 'emoji' => '🇹🇰', 'unicode' => 'U+1F1F9 U+1F1F0' }, 515 | 'TL' => { 'emoji' => '🇹🇱', 'unicode' => 'U+1F1F9 U+1F1F1' }, 516 | 'TM' => { 'emoji' => '🇹🇲', 'unicode' => 'U+1F1F9 U+1F1F2' }, 517 | 'TN' => { 'emoji' => '🇹🇳', 'unicode' => 'U+1F1F9 U+1F1F3' }, 518 | 'TO' => { 'emoji' => '🇹🇴', 'unicode' => 'U+1F1F9 U+1F1F4' }, 519 | 'TR' => { 'emoji' => '🇹🇷', 'unicode' => 'U+1F1F9 U+1F1F7' }, 520 | 'TT' => { 'emoji' => '🇹🇹', 'unicode' => 'U+1F1F9 U+1F1F9' }, 521 | 'TV' => { 'emoji' => '🇹🇻', 'unicode' => 'U+1F1F9 U+1F1FB' }, 522 | 'TW' => { 'emoji' => '🇹🇼', 'unicode' => 'U+1F1F9 U+1F1FC' }, 523 | 'TZ' => { 'emoji' => '🇹🇿', 'unicode' => 'U+1F1F9 U+1F1FF' }, 524 | 'UA' => { 'emoji' => '🇺🇦', 'unicode' => 'U+1F1FA U+1F1E6' }, 525 | 'UG' => { 'emoji' => '🇺🇬', 'unicode' => 'U+1F1FA U+1F1EC' }, 526 | 'UM' => { 'emoji' => '🇺🇲', 'unicode' => 'U+1F1FA U+1F1F2' }, 527 | 'US' => { 'emoji' => '🇺🇸', 'unicode' => 'U+1F1FA U+1F1F8' }, 528 | 'UY' => { 'emoji' => '🇺🇾', 'unicode' => 'U+1F1FA U+1F1FE' }, 529 | 'UZ' => { 'emoji' => '🇺🇿', 'unicode' => 'U+1F1FA U+1F1FF' }, 530 | 'VA' => { 'emoji' => '🇻🇦', 'unicode' => 'U+1F1FB U+1F1E6' }, 531 | 'VC' => { 'emoji' => '🇻🇨', 'unicode' => 'U+1F1FB U+1F1E8' }, 532 | 'VE' => { 'emoji' => '🇻🇪', 'unicode' => 'U+1F1FB U+1F1EA' }, 533 | 'VG' => { 'emoji' => '🇻🇬', 'unicode' => 'U+1F1FB U+1F1EC' }, 534 | 'VI' => { 'emoji' => '🇻🇮', 'unicode' => 'U+1F1FB U+1F1EE' }, 535 | 'VN' => { 'emoji' => '🇻🇳', 'unicode' => 'U+1F1FB U+1F1F3' }, 536 | 'VU' => { 'emoji' => '🇻🇺', 'unicode' => 'U+1F1FB U+1F1FA' }, 537 | 'WF' => { 'emoji' => '🇼🇫', 'unicode' => 'U+1F1FC U+1F1EB' }, 538 | 'WS' => { 'emoji' => '🇼🇸', 'unicode' => 'U+1F1FC U+1F1F8' }, 539 | 'XK' => { 'emoji' => '🇽🇰', 'unicode' => 'U+1F1FD U+1F1F0' }, 540 | 'YE' => { 'emoji' => '🇾🇪', 'unicode' => 'U+1F1FE U+1F1EA' }, 541 | 'YT' => { 'emoji' => '🇾🇹', 'unicode' => 'U+1F1FE U+1F1F9' }, 542 | 'ZA' => { 'emoji' => '🇿🇦', 'unicode' => 'U+1F1FF U+1F1E6' }, 543 | 'ZM' => { 'emoji' => '🇿🇲', 'unicode' => 'U+1F1FF U+1F1F2' }, 544 | 'ZW' => { 'emoji' => '🇿🇼', 'unicode' => 'U+1F1FF U+1F1FC' } 545 | ); 546 | my @default_eu_countries = ( 547 | "IE", "AT", "LT", "LU", "LV", "DE", "DK", "SE", "SI", "SK", 548 | "CZ", "CY", "NL", "FI", "FR", "MT", "ES", "IT", "EE", "PL", 549 | "PT", "HU", "HR", "GR", "RO", "BG", "BE" 550 | ); 551 | my %default_countries_currencies = ( 552 | 'AD' => { 'code' => 'EUR', 'symbol' => '€' }, 553 | 'AE' => { 'code' => 'AED', 'symbol' => 'د.إ' }, 554 | 'AF' => { 'code' => 'AFN', 'symbol' => '؋' }, 555 | 'AG' => { 'code' => 'XCD', 'symbol' => '$' }, 556 | 'AI' => { 'code' => 'XCD', 'symbol' => '$' }, 557 | 'AL' => { 'code' => 'ALL', 'symbol' => 'L' }, 558 | 'AM' => { 'code' => 'AMD', 'symbol' => '֏' }, 559 | 'AO' => { 'code' => 'AOA', 'symbol' => 'Kz' }, 560 | 'AQ' => { 'code' => '', 'symbol' => '$' }, 561 | 'AR' => { 'code' => 'ARS', 'symbol' => '$' }, 562 | 'AS' => { 'code' => 'USD', 'symbol' => '$' }, 563 | 'AT' => { 'code' => 'EUR', 'symbol' => '€' }, 564 | 'AU' => { 'code' => 'AUD', 'symbol' => '$' }, 565 | 'AW' => { 'code' => 'AWG', 'symbol' => 'ƒ' }, 566 | 'AX' => { 'code' => 'EUR', 'symbol' => '€' }, 567 | 'AZ' => { 'code' => 'AZN', 'symbol' => '₼' }, 568 | 'BA' => { 'code' => 'BAM', 'symbol' => 'KM' }, 569 | 'BB' => { 'code' => 'BBD', 'symbol' => '$' }, 570 | 'BD' => { 'code' => 'BDT', 'symbol' => '৳' }, 571 | 'BE' => { 'code' => 'EUR', 'symbol' => '€' }, 572 | 'BF' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 573 | 'BG' => { 'code' => 'BGN', 'symbol' => 'лв' }, 574 | 'BH' => { 'code' => 'BHD', 'symbol' => '.د.ب' }, 575 | 'BI' => { 'code' => 'BIF', 'symbol' => 'FBu' }, 576 | 'BJ' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 577 | 'BL' => { 'code' => 'EUR', 'symbol' => '€' }, 578 | 'BM' => { 'code' => 'BMD', 'symbol' => '$' }, 579 | 'BN' => { 'code' => 'BND', 'symbol' => '$' }, 580 | 'BO' => { 'code' => 'BOB', 'symbol' => '$b' }, 581 | 'BQ' => { 'code' => 'USD', 'symbol' => '$' }, 582 | 'BR' => { 'code' => 'BRL', 'symbol' => 'R$' }, 583 | 'BS' => { 'code' => 'BSD', 'symbol' => '$' }, 584 | 'BT' => { 'code' => 'BTN', 'symbol' => 'Nu.' }, 585 | 'BV' => { 'code' => 'NOK', 'symbol' => 'kr' }, 586 | 'BW' => { 'code' => 'BWP', 'symbol' => 'P' }, 587 | 'BY' => { 'code' => 'BYR', 'symbol' => 'Br' }, 588 | 'BZ' => { 'code' => 'BZD', 'symbol' => 'BZ$' }, 589 | 'CA' => { 'code' => 'CAD', 'symbol' => '$' }, 590 | 'CC' => { 'code' => 'AUD', 'symbol' => '$' }, 591 | 'CD' => { 'code' => 'CDF', 'symbol' => 'FC' }, 592 | 'CF' => { 'code' => 'XAF', 'symbol' => 'FCFA' }, 593 | 'CG' => { 'code' => 'XAF', 'symbol' => 'FCFA' }, 594 | 'CH' => { 'code' => 'CHF', 'symbol' => 'CHF' }, 595 | 'CI' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 596 | 'CK' => { 'code' => 'NZD', 'symbol' => '$' }, 597 | 'CL' => { 'code' => 'CLP', 'symbol' => '$' }, 598 | 'CM' => { 'code' => 'XAF', 'symbol' => 'FCFA' }, 599 | 'CN' => { 'code' => 'CNY', 'symbol' => '¥' }, 600 | 'CO' => { 'code' => 'COP', 'symbol' => '$' }, 601 | 'CR' => { 'code' => 'CRC', 'symbol' => '₡' }, 602 | 'CU' => { 'code' => 'CUP', 'symbol' => '₱' }, 603 | 'CV' => { 'code' => 'CVE', 'symbol' => '$' }, 604 | 'CW' => { 'code' => 'ANG', 'symbol' => 'ƒ' }, 605 | 'CX' => { 'code' => 'AUD', 'symbol' => '$' }, 606 | 'CY' => { 'code' => 'EUR', 'symbol' => '€' }, 607 | 'CZ' => { 'code' => 'CZK', 'symbol' => 'Kč' }, 608 | 'DE' => { 'code' => 'EUR', 'symbol' => '€' }, 609 | 'DJ' => { 'code' => 'DJF', 'symbol' => 'Fdj' }, 610 | 'DK' => { 'code' => 'DKK', 'symbol' => 'kr' }, 611 | 'DM' => { 'code' => 'XCD', 'symbol' => '$' }, 612 | 'DO' => { 'code' => 'DOP', 'symbol' => 'RD$' }, 613 | 'DZ' => { 'code' => 'DZD', 'symbol' => 'دج' }, 614 | 'EC' => { 'code' => 'USD', 'symbol' => '$' }, 615 | 'EE' => { 'code' => 'EUR', 'symbol' => '€' }, 616 | 'EG' => { 'code' => 'EGP', 'symbol' => '£' }, 617 | 'EH' => { 'code' => 'MAD', 'symbol' => 'MAD' }, 618 | 'ER' => { 'code' => 'ERN', 'symbol' => 'Nfk' }, 619 | 'ES' => { 'code' => 'EUR', 'symbol' => '€' }, 620 | 'ET' => { 'code' => 'ETB', 'symbol' => 'Br' }, 621 | 'FI' => { 'code' => 'EUR', 'symbol' => '€' }, 622 | 'FJ' => { 'code' => 'FJD', 'symbol' => '$' }, 623 | 'FK' => { 'code' => 'FKP', 'symbol' => '£' }, 624 | 'FM' => { 'code' => 'USD', 'symbol' => '$' }, 625 | 'FO' => { 'code' => 'DKK', 'symbol' => 'kr' }, 626 | 'FR' => { 'code' => 'EUR', 'symbol' => '€' }, 627 | 'GA' => { 'code' => 'XAF', 'symbol' => 'FCFA' }, 628 | 'GB' => { 'code' => 'GBP', 'symbol' => '£' }, 629 | 'GD' => { 'code' => 'XCD', 'symbol' => '$' }, 630 | 'GE' => { 'code' => 'GEL', 'symbol' => 'ლ' }, 631 | 'GF' => { 'code' => 'EUR', 'symbol' => '€' }, 632 | 'GG' => { 'code' => 'GBP', 'symbol' => '£' }, 633 | 'GH' => { 'code' => 'GHS', 'symbol' => 'GH₵' }, 634 | 'GI' => { 'code' => 'GIP', 'symbol' => '£' }, 635 | 'GL' => { 'code' => 'DKK', 'symbol' => 'kr' }, 636 | 'GM' => { 'code' => 'GMD', 'symbol' => 'D' }, 637 | 'GN' => { 'code' => 'GNF', 'symbol' => 'FG' }, 638 | 'GP' => { 'code' => 'EUR', 'symbol' => '€' }, 639 | 'GQ' => { 'code' => 'XAF', 'symbol' => 'FCFA' }, 640 | 'GR' => { 'code' => 'EUR', 'symbol' => '€' }, 641 | 'GS' => { 'code' => 'GBP', 'symbol' => '£' }, 642 | 'GT' => { 'code' => 'GTQ', 'symbol' => 'Q' }, 643 | 'GU' => { 'code' => 'USD', 'symbol' => '$' }, 644 | 'GW' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 645 | 'GY' => { 'code' => 'GYD', 'symbol' => '$' }, 646 | 'HK' => { 'code' => 'HKD', 'symbol' => '$' }, 647 | 'HM' => { 'code' => 'AUD', 'symbol' => '$' }, 648 | 'HN' => { 'code' => 'HNL', 'symbol' => 'L' }, 649 | 'HR' => { 'code' => 'HRK', 'symbol' => 'kn' }, 650 | 'HT' => { 'code' => 'HTG', 'symbol' => 'G' }, 651 | 'HU' => { 'code' => 'HUF', 'symbol' => 'Ft' }, 652 | 'ID' => { 'code' => 'IDR', 'symbol' => 'Rp' }, 653 | 'IE' => { 'code' => 'EUR', 'symbol' => '€' }, 654 | 'IL' => { 'code' => 'ILS', 'symbol' => '₪' }, 655 | 'IM' => { 'code' => 'GBP', 'symbol' => '£' }, 656 | 'IN' => { 'code' => 'INR', 'symbol' => '₹' }, 657 | 'IO' => { 'code' => 'USD', 'symbol' => '$' }, 658 | 'IQ' => { 'code' => 'IQD', 'symbol' => 'ع.د' }, 659 | 'IR' => { 'code' => 'IRR', 'symbol' => '﷼' }, 660 | 'IS' => { 'code' => 'ISK', 'symbol' => 'kr' }, 661 | 'IT' => { 'code' => 'EUR', 'symbol' => '€' }, 662 | 'JE' => { 'code' => 'GBP', 'symbol' => '£' }, 663 | 'JM' => { 'code' => 'JMD', 'symbol' => 'J$' }, 664 | 'JO' => { 'code' => 'JOD', 'symbol' => 'JD' }, 665 | 'JP' => { 'code' => 'JPY', 'symbol' => '¥' }, 666 | 'KE' => { 'code' => 'KES', 'symbol' => 'KSh' }, 667 | 'KG' => { 'code' => 'KGS', 'symbol' => 'лв' }, 668 | 'KH' => { 'code' => 'KHR', 'symbol' => '៛' }, 669 | 'KI' => { 'code' => 'AUD', 'symbol' => '$' }, 670 | 'KM' => { 'code' => 'KMF', 'symbol' => 'CF' }, 671 | 'KN' => { 'code' => 'XCD', 'symbol' => '$' }, 672 | 'KP' => { 'code' => 'KPW', 'symbol' => '₩' }, 673 | 'KR' => { 'code' => 'KRW', 'symbol' => '₩' }, 674 | 'KW' => { 'code' => 'KWD', 'symbol' => 'KD' }, 675 | 'KY' => { 'code' => 'KYD', 'symbol' => '$' }, 676 | 'KZ' => { 'code' => 'KZT', 'symbol' => '₸' }, 677 | 'LA' => { 'code' => 'LAK', 'symbol' => '₭' }, 678 | 'LB' => { 'code' => 'LBP', 'symbol' => '£' }, 679 | 'LC' => { 'code' => 'XCD', 'symbol' => '$' }, 680 | 'LI' => { 'code' => 'CHF', 'symbol' => 'CHF' }, 681 | 'LK' => { 'code' => 'LKR', 'symbol' => '₨' }, 682 | 'LR' => { 'code' => 'LRD', 'symbol' => '$' }, 683 | 'LS' => { 'code' => 'LSL', 'symbol' => 'M' }, 684 | 'LT' => { 'code' => 'LTL', 'symbol' => 'Lt' }, 685 | 'LU' => { 'code' => 'EUR', 'symbol' => '€' }, 686 | 'LV' => { 'code' => 'EUR', 'symbol' => '€' }, 687 | 'LY' => { 'code' => 'LYD', 'symbol' => 'LD' }, 688 | 'MA' => { 'code' => 'MAD', 'symbol' => 'MAD' }, 689 | 'MC' => { 'code' => 'EUR', 'symbol' => '€' }, 690 | 'MD' => { 'code' => 'MDL', 'symbol' => 'lei' }, 691 | 'ME' => { 'code' => 'EUR', 'symbol' => '€' }, 692 | 'MF' => { 'code' => 'EUR', 'symbol' => '€' }, 693 | 'MG' => { 'code' => 'MGA', 'symbol' => 'Ar' }, 694 | 'MH' => { 'code' => 'USD', 'symbol' => '$' }, 695 | 'MK' => { 'code' => 'MKD', 'symbol' => 'ден' }, 696 | 'ML' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 697 | 'MM' => { 'code' => 'MMK', 'symbol' => 'K' }, 698 | 'MN' => { 'code' => 'MNT', 'symbol' => '₮' }, 699 | 'MO' => { 'code' => 'MOP', 'symbol' => 'MOP$' }, 700 | 'MP' => { 'code' => 'USD', 'symbol' => '$' }, 701 | 'MQ' => { 'code' => 'EUR', 'symbol' => '€' }, 702 | 'MR' => { 'code' => 'MRO', 'symbol' => 'UM' }, 703 | 'MS' => { 'code' => 'XCD', 'symbol' => '$' }, 704 | 'MT' => { 'code' => 'EUR', 'symbol' => '€' }, 705 | 'MU' => { 'code' => 'MUR', 'symbol' => '₨' }, 706 | 'MV' => { 'code' => 'MVR', 'symbol' => 'Rf' }, 707 | 'MW' => { 'code' => 'MWK', 'symbol' => 'MK' }, 708 | 'MX' => { 'code' => 'MXN', 'symbol' => '$' }, 709 | 'MY' => { 'code' => 'MYR', 'symbol' => 'RM' }, 710 | 'MZ' => { 'code' => 'MZN', 'symbol' => 'MT' }, 711 | 'NA' => { 'code' => 'NAD', 'symbol' => '$' }, 712 | 'NC' => { 'code' => 'XPF', 'symbol' => '₣' }, 713 | 'NE' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 714 | 'NF' => { 'code' => 'AUD', 'symbol' => '$' }, 715 | 'NG' => { 'code' => 'NGN', 'symbol' => '₦' }, 716 | 'NI' => { 'code' => 'NIO', 'symbol' => 'C$' }, 717 | 'NL' => { 'code' => 'EUR', 'symbol' => '€' }, 718 | 'NO' => { 'code' => 'NOK', 'symbol' => 'kr' }, 719 | 'NP' => { 'code' => 'NPR', 'symbol' => '₨' }, 720 | 'NR' => { 'code' => 'AUD', 'symbol' => '$' }, 721 | 'NU' => { 'code' => 'NZD', 'symbol' => '$' }, 722 | 'NZ' => { 'code' => 'NZD', 'symbol' => '$' }, 723 | 'OM' => { 'code' => 'OMR', 'symbol' => '﷼' }, 724 | 'PA' => { 'code' => 'PAB', 'symbol' => 'B/.' }, 725 | 'PE' => { 'code' => 'PEN', 'symbol' => 'S/.' }, 726 | 'PF' => { 'code' => 'XPF', 'symbol' => '₣' }, 727 | 'PG' => { 'code' => 'PGK', 'symbol' => 'K' }, 728 | 'PH' => { 'code' => 'PHP', 'symbol' => '₱' }, 729 | 'PK' => { 'code' => 'PKR', 'symbol' => '₨' }, 730 | 'PL' => { 'code' => 'PLN', 'symbol' => 'zł' }, 731 | 'PM' => { 'code' => 'EUR', 'symbol' => '€' }, 732 | 'PN' => { 'code' => 'NZD', 'symbol' => '$' }, 733 | 'PR' => { 'code' => 'USD', 'symbol' => '$' }, 734 | 'PS' => { 'code' => 'ILS', 'symbol' => '₪' }, 735 | 'PT' => { 'code' => 'EUR', 'symbol' => '€' }, 736 | 'PW' => { 'code' => 'USD', 'symbol' => '$' }, 737 | 'PY' => { 'code' => 'PYG', 'symbol' => 'Gs' }, 738 | 'QA' => { 'code' => 'QAR', 'symbol' => '﷼' }, 739 | 'RE' => { 'code' => 'EUR', 'symbol' => '€' }, 740 | 'RO' => { 'code' => 'RON', 'symbol' => 'lei' }, 741 | 'RS' => { 'code' => 'RSD', 'symbol' => 'Дин.' }, 742 | 'RU' => { 'code' => 'RUB', 'symbol' => '₽' }, 743 | 'RW' => { 'code' => 'RWF', 'symbol' => 'R₣' }, 744 | 'SA' => { 'code' => 'SAR', 'symbol' => '﷼' }, 745 | 'SB' => { 'code' => 'SBD', 'symbol' => '$' }, 746 | 'SC' => { 'code' => 'SCR', 'symbol' => '₨' }, 747 | 'SD' => { 'code' => 'SDG', 'symbol' => 'ج.س.' }, 748 | 'SE' => { 'code' => 'SEK', 'symbol' => 'kr' }, 749 | 'SG' => { 'code' => 'SGD', 'symbol' => 'S$' }, 750 | 'SH' => { 'code' => 'SHP', 'symbol' => '£' }, 751 | 'SI' => { 'code' => 'EUR', 'symbol' => '€' }, 752 | 'SJ' => { 'code' => 'NOK', 'symbol' => 'kr' }, 753 | 'SK' => { 'code' => 'EUR', 'symbol' => '€' }, 754 | 'SL' => { 'code' => 'SLL', 'symbol' => 'Le' }, 755 | 'SM' => { 'code' => 'EUR', 'symbol' => '€' }, 756 | 'SN' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 757 | 'SO' => { 'code' => 'SOS', 'symbol' => 'S' }, 758 | 'SR' => { 'code' => 'SRD', 'symbol' => '$' }, 759 | 'SS' => { 'code' => 'SSP', 'symbol' => '£' }, 760 | 'ST' => { 'code' => 'STD', 'symbol' => 'Db' }, 761 | 'SV' => { 'code' => 'USD', 'symbol' => '$' }, 762 | 'SX' => { 'code' => 'ANG', 'symbol' => 'ƒ' }, 763 | 'SY' => { 'code' => 'SYP', 'symbol' => '£' }, 764 | 'SZ' => { 'code' => 'SZL', 'symbol' => 'E' }, 765 | 'TC' => { 'code' => 'USD', 'symbol' => '$' }, 766 | 'TD' => { 'code' => 'XAF', 'symbol' => 'FCFA' }, 767 | 'TF' => { 'code' => 'EUR', 'symbol' => '€' }, 768 | 'TG' => { 'code' => 'XOF', 'symbol' => 'CFA' }, 769 | 'TH' => { 'code' => 'THB', 'symbol' => '฿' }, 770 | 'TJ' => { 'code' => 'TJS', 'symbol' => 'SM' }, 771 | 'TK' => { 'code' => 'NZD', 'symbol' => '$' }, 772 | 'TL' => { 'code' => 'USD', 'symbol' => '$' }, 773 | 'TM' => { 'code' => 'TMT', 'symbol' => 'T' }, 774 | 'TN' => { 'code' => 'TND', 'symbol' => 'د.ت' }, 775 | 'TO' => { 'code' => 'TOP', 'symbol' => 'T$' }, 776 | 'TR' => { 'code' => 'TRY', 'symbol' => '₺' }, 777 | 'TT' => { 'code' => 'TTD', 'symbol' => 'TT$' }, 778 | 'TV' => { 'code' => 'AUD', 'symbol' => '$' }, 779 | 'TW' => { 'code' => 'TWD', 'symbol' => 'NT$' }, 780 | 'TZ' => { 'code' => 'TZS', 'symbol' => 'TSh' }, 781 | 'UA' => { 'code' => 'UAH', 'symbol' => '₴' }, 782 | 'UG' => { 'code' => 'UGX', 'symbol' => 'USh' }, 783 | 'UM' => { 'code' => 'USD', 'symbol' => '$' }, 784 | 'US' => { 'code' => 'USD', 'symbol' => '$' }, 785 | 'UY' => { 'code' => 'UYU', 'symbol' => '$U' }, 786 | 'UZ' => { 'code' => 'UZS', 'symbol' => 'лв' }, 787 | 'VA' => { 'code' => 'EUR', 'symbol' => '€' }, 788 | 'VC' => { 'code' => 'XCD', 'symbol' => '$' }, 789 | 'VE' => { 'code' => 'VEF', 'symbol' => 'Bs' }, 790 | 'VG' => { 'code' => 'USD', 'symbol' => '$' }, 791 | 'VI' => { 'code' => 'USD', 'symbol' => '$' }, 792 | 'VN' => { 'code' => 'VND', 'symbol' => '₫' }, 793 | 'VU' => { 'code' => 'VUV', 'symbol' => 'VT' }, 794 | 'WF' => { 'code' => 'XPF', 'symbol' => '₣' }, 795 | 'WS' => { 'code' => 'WST', 'symbol' => 'WS$' }, 796 | 'XK' => { 'code' => 'EUR', 'symbol' => '€' }, 797 | 'YE' => { 'code' => 'YER', 'symbol' => '﷼' }, 798 | 'YT' => { 'code' => 'EUR', 'symbol' => '€' }, 799 | 'ZA' => { 'code' => 'ZAR', 'symbol' => 'R' }, 800 | 'ZM' => { 'code' => 'ZMK', 'symbol' => 'ZK' }, 801 | 'ZW' => { 'code' => 'ZWL', 'symbol' => '$' } 802 | ); 803 | my %default_continents = ( 804 | "BD" => { "code" => "AS", "name" => "Asia" }, 805 | "BE" => { "code" => "EU", "name" => "Europe" }, 806 | "BF" => { "code" => "AF", "name" => "Africa" }, 807 | "BG" => { "code" => "EU", "name" => "Europe" }, 808 | "BA" => { "code" => "EU", "name" => "Europe" }, 809 | "BB" => { "code" => "NA", "name" => "North America" }, 810 | "WF" => { "code" => "OC", "name" => "Oceania" }, 811 | "BL" => { "code" => "NA", "name" => "North America" }, 812 | "BM" => { "code" => "NA", "name" => "North America" }, 813 | "BN" => { "code" => "AS", "name" => "Asia" }, 814 | "BO" => { "code" => "SA", "name" => "South America" }, 815 | "BH" => { "code" => "AS", "name" => "Asia" }, 816 | "BI" => { "code" => "AF", "name" => "Africa" }, 817 | "BJ" => { "code" => "AF", "name" => "Africa" }, 818 | "BT" => { "code" => "AS", "name" => "Asia" }, 819 | "JM" => { "code" => "NA", "name" => "North America" }, 820 | "BV" => { "code" => "AN", "name" => "Antarctica" }, 821 | "BW" => { "code" => "AF", "name" => "Africa" }, 822 | "WS" => { "code" => "OC", "name" => "Oceania" }, 823 | "BQ" => { "code" => "NA", "name" => "North America" }, 824 | "BR" => { "code" => "SA", "name" => "South America" }, 825 | "BS" => { "code" => "NA", "name" => "North America" }, 826 | "JE" => { "code" => "EU", "name" => "Europe" }, 827 | "BY" => { "code" => "EU", "name" => "Europe" }, 828 | "BZ" => { "code" => "NA", "name" => "North America" }, 829 | "RU" => { "code" => "EU", "name" => "Europe" }, 830 | "RW" => { "code" => "AF", "name" => "Africa" }, 831 | "RS" => { "code" => "EU", "name" => "Europe" }, 832 | "TL" => { "code" => "OC", "name" => "Oceania" }, 833 | "RE" => { "code" => "AF", "name" => "Africa" }, 834 | "TM" => { "code" => "AS", "name" => "Asia" }, 835 | "TJ" => { "code" => "AS", "name" => "Asia" }, 836 | "RO" => { "code" => "EU", "name" => "Europe" }, 837 | "TK" => { "code" => "OC", "name" => "Oceania" }, 838 | "GW" => { "code" => "AF", "name" => "Africa" }, 839 | "GU" => { "code" => "OC", "name" => "Oceania" }, 840 | "GT" => { "code" => "NA", "name" => "North America" }, 841 | "GS" => { "code" => "AN", "name" => "Antarctica" }, 842 | "GR" => { "code" => "EU", "name" => "Europe" }, 843 | "GQ" => { "code" => "AF", "name" => "Africa" }, 844 | "GP" => { "code" => "NA", "name" => "North America" }, 845 | "JP" => { "code" => "AS", "name" => "Asia" }, 846 | "GY" => { "code" => "SA", "name" => "South America" }, 847 | "GG" => { "code" => "EU", "name" => "Europe" }, 848 | "GF" => { "code" => "SA", "name" => "South America" }, 849 | "GE" => { "code" => "AS", "name" => "Asia" }, 850 | "GD" => { "code" => "NA", "name" => "North America" }, 851 | "GB" => { "code" => "EU", "name" => "Europe" }, 852 | "GA" => { "code" => "AF", "name" => "Africa" }, 853 | "SV" => { "code" => "NA", "name" => "North America" }, 854 | "GN" => { "code" => "AF", "name" => "Africa" }, 855 | "GM" => { "code" => "AF", "name" => "Africa" }, 856 | "GL" => { "code" => "NA", "name" => "North America" }, 857 | "GI" => { "code" => "EU", "name" => "Europe" }, 858 | "GH" => { "code" => "AF", "name" => "Africa" }, 859 | "OM" => { "code" => "AS", "name" => "Asia" }, 860 | "TN" => { "code" => "AF", "name" => "Africa" }, 861 | "JO" => { "code" => "AS", "name" => "Asia" }, 862 | "HR" => { "code" => "EU", "name" => "Europe" }, 863 | "HT" => { "code" => "NA", "name" => "North America" }, 864 | "HU" => { "code" => "EU", "name" => "Europe" }, 865 | "HK" => { "code" => "AS", "name" => "Asia" }, 866 | "HN" => { "code" => "NA", "name" => "North America" }, 867 | "HM" => { "code" => "AN", "name" => "Antarctica" }, 868 | "VE" => { "code" => "SA", "name" => "South America" }, 869 | "PR" => { "code" => "NA", "name" => "North America" }, 870 | "PS" => { "code" => "AS", "name" => "Asia" }, 871 | "PW" => { "code" => "OC", "name" => "Oceania" }, 872 | "PT" => { "code" => "EU", "name" => "Europe" }, 873 | "SJ" => { "code" => "EU", "name" => "Europe" }, 874 | "PY" => { "code" => "SA", "name" => "South America" }, 875 | "IQ" => { "code" => "AS", "name" => "Asia" }, 876 | "PA" => { "code" => "NA", "name" => "North America" }, 877 | "PF" => { "code" => "OC", "name" => "Oceania" }, 878 | "PG" => { "code" => "OC", "name" => "Oceania" }, 879 | "PE" => { "code" => "SA", "name" => "South America" }, 880 | "PK" => { "code" => "AS", "name" => "Asia" }, 881 | "PH" => { "code" => "AS", "name" => "Asia" }, 882 | "PN" => { "code" => "OC", "name" => "Oceania" }, 883 | "PL" => { "code" => "EU", "name" => "Europe" }, 884 | "PM" => { "code" => "NA", "name" => "North America" }, 885 | "ZM" => { "code" => "AF", "name" => "Africa" }, 886 | "EH" => { "code" => "AF", "name" => "Africa" }, 887 | "EE" => { "code" => "EU", "name" => "Europe" }, 888 | "EG" => { "code" => "AF", "name" => "Africa" }, 889 | "ZA" => { "code" => "AF", "name" => "Africa" }, 890 | "EC" => { "code" => "SA", "name" => "South America" }, 891 | "IT" => { "code" => "EU", "name" => "Europe" }, 892 | "VN" => { "code" => "AS", "name" => "Asia" }, 893 | "SB" => { "code" => "OC", "name" => "Oceania" }, 894 | "ET" => { "code" => "AF", "name" => "Africa" }, 895 | "SO" => { "code" => "AF", "name" => "Africa" }, 896 | "ZW" => { "code" => "AF", "name" => "Africa" }, 897 | "SA" => { "code" => "AS", "name" => "Asia" }, 898 | "ES" => { "code" => "EU", "name" => "Europe" }, 899 | "ER" => { "code" => "AF", "name" => "Africa" }, 900 | "ME" => { "code" => "EU", "name" => "Europe" }, 901 | "MD" => { "code" => "EU", "name" => "Europe" }, 902 | "MG" => { "code" => "AF", "name" => "Africa" }, 903 | "MF" => { "code" => "NA", "name" => "North America" }, 904 | "MA" => { "code" => "AF", "name" => "Africa" }, 905 | "MC" => { "code" => "EU", "name" => "Europe" }, 906 | "UZ" => { "code" => "AS", "name" => "Asia" }, 907 | "MM" => { "code" => "AS", "name" => "Asia" }, 908 | "ML" => { "code" => "AF", "name" => "Africa" }, 909 | "MO" => { "code" => "AS", "name" => "Asia" }, 910 | "MN" => { "code" => "AS", "name" => "Asia" }, 911 | "MH" => { "code" => "OC", "name" => "Oceania" }, 912 | "MK" => { "code" => "EU", "name" => "Europe" }, 913 | "MU" => { "code" => "AF", "name" => "Africa" }, 914 | "MT" => { "code" => "EU", "name" => "Europe" }, 915 | "MW" => { "code" => "AF", "name" => "Africa" }, 916 | "MV" => { "code" => "AS", "name" => "Asia" }, 917 | "MQ" => { "code" => "NA", "name" => "North America" }, 918 | "MP" => { "code" => "OC", "name" => "Oceania" }, 919 | "MS" => { "code" => "NA", "name" => "North America" }, 920 | "MR" => { "code" => "AF", "name" => "Africa" }, 921 | "IM" => { "code" => "EU", "name" => "Europe" }, 922 | "UG" => { "code" => "AF", "name" => "Africa" }, 923 | "TZ" => { "code" => "AF", "name" => "Africa" }, 924 | "MY" => { "code" => "AS", "name" => "Asia" }, 925 | "MX" => { "code" => "NA", "name" => "North America" }, 926 | "IL" => { "code" => "AS", "name" => "Asia" }, 927 | "FR" => { "code" => "EU", "name" => "Europe" }, 928 | "IO" => { "code" => "AS", "name" => "Asia" }, 929 | "SH" => { "code" => "AF", "name" => "Africa" }, 930 | "FI" => { "code" => "EU", "name" => "Europe" }, 931 | "FJ" => { "code" => "OC", "name" => "Oceania" }, 932 | "FK" => { "code" => "SA", "name" => "South America" }, 933 | "FM" => { "code" => "OC", "name" => "Oceania" }, 934 | "FO" => { "code" => "EU", "name" => "Europe" }, 935 | "NI" => { "code" => "NA", "name" => "North America" }, 936 | "NL" => { "code" => "EU", "name" => "Europe" }, 937 | "NO" => { "code" => "EU", "name" => "Europe" }, 938 | "NA" => { "code" => "AF", "name" => "Africa" }, 939 | "VU" => { "code" => "OC", "name" => "Oceania" }, 940 | "NC" => { "code" => "OC", "name" => "Oceania" }, 941 | "NE" => { "code" => "AF", "name" => "Africa" }, 942 | "NF" => { "code" => "OC", "name" => "Oceania" }, 943 | "NG" => { "code" => "AF", "name" => "Africa" }, 944 | "NZ" => { "code" => "OC", "name" => "Oceania" }, 945 | "NP" => { "code" => "AS", "name" => "Asia" }, 946 | "NR" => { "code" => "OC", "name" => "Oceania" }, 947 | "NU" => { "code" => "OC", "name" => "Oceania" }, 948 | "CK" => { "code" => "OC", "name" => "Oceania" }, 949 | "XK" => { "code" => "EU", "name" => "Europe" }, 950 | "CI" => { "code" => "AF", "name" => "Africa" }, 951 | "CH" => { "code" => "EU", "name" => "Europe" }, 952 | "CO" => { "code" => "SA", "name" => "South America" }, 953 | "CN" => { "code" => "AS", "name" => "Asia" }, 954 | "CM" => { "code" => "AF", "name" => "Africa" }, 955 | "CL" => { "code" => "SA", "name" => "South America" }, 956 | "CC" => { "code" => "AS", "name" => "Asia" }, 957 | "CA" => { "code" => "NA", "name" => "North America" }, 958 | "CG" => { "code" => "AF", "name" => "Africa" }, 959 | "CF" => { "code" => "AF", "name" => "Africa" }, 960 | "CD" => { "code" => "AF", "name" => "Africa" }, 961 | "CZ" => { "code" => "EU", "name" => "Europe" }, 962 | "CY" => { "code" => "EU", "name" => "Europe" }, 963 | "CX" => { "code" => "AS", "name" => "Asia" }, 964 | "CR" => { "code" => "NA", "name" => "North America" }, 965 | "CW" => { "code" => "NA", "name" => "North America" }, 966 | "CV" => { "code" => "AF", "name" => "Africa" }, 967 | "CU" => { "code" => "NA", "name" => "North America" }, 968 | "SZ" => { "code" => "AF", "name" => "Africa" }, 969 | "SY" => { "code" => "AS", "name" => "Asia" }, 970 | "SX" => { "code" => "NA", "name" => "North America" }, 971 | "KG" => { "code" => "AS", "name" => "Asia" }, 972 | "KE" => { "code" => "AF", "name" => "Africa" }, 973 | "SS" => { "code" => "AF", "name" => "Africa" }, 974 | "SR" => { "code" => "SA", "name" => "South America" }, 975 | "KI" => { "code" => "OC", "name" => "Oceania" }, 976 | "KH" => { "code" => "AS", "name" => "Asia" }, 977 | "KN" => { "code" => "NA", "name" => "North America" }, 978 | "KM" => { "code" => "AF", "name" => "Africa" }, 979 | "ST" => { "code" => "AF", "name" => "Africa" }, 980 | "SK" => { "code" => "EU", "name" => "Europe" }, 981 | "KR" => { "code" => "AS", "name" => "Asia" }, 982 | "SI" => { "code" => "EU", "name" => "Europe" }, 983 | "KP" => { "code" => "AS", "name" => "Asia" }, 984 | "KW" => { "code" => "AS", "name" => "Asia" }, 985 | "SN" => { "code" => "AF", "name" => "Africa" }, 986 | "SM" => { "code" => "EU", "name" => "Europe" }, 987 | "SL" => { "code" => "AF", "name" => "Africa" }, 988 | "SC" => { "code" => "AF", "name" => "Africa" }, 989 | "KZ" => { "code" => "AS", "name" => "Asia" }, 990 | "KY" => { "code" => "NA", "name" => "North America" }, 991 | "SG" => { "code" => "AS", "name" => "Asia" }, 992 | "SE" => { "code" => "EU", "name" => "Europe" }, 993 | "SD" => { "code" => "AF", "name" => "Africa" }, 994 | "DO" => { "code" => "NA", "name" => "North America" }, 995 | "DM" => { "code" => "NA", "name" => "North America" }, 996 | "DJ" => { "code" => "AF", "name" => "Africa" }, 997 | "DK" => { "code" => "EU", "name" => "Europe" }, 998 | "VG" => { "code" => "NA", "name" => "North America" }, 999 | "DE" => { "code" => "EU", "name" => "Europe" }, 1000 | "YE" => { "code" => "AS", "name" => "Asia" }, 1001 | "DZ" => { "code" => "AF", "name" => "Africa" }, 1002 | "US" => { "code" => "NA", "name" => "North America" }, 1003 | "UY" => { "code" => "SA", "name" => "South America" }, 1004 | "YT" => { "code" => "AF", "name" => "Africa" }, 1005 | "UM" => { "code" => "OC", "name" => "Oceania" }, 1006 | "LB" => { "code" => "AS", "name" => "Asia" }, 1007 | "LC" => { "code" => "NA", "name" => "North America" }, 1008 | "LA" => { "code" => "AS", "name" => "Asia" }, 1009 | "TV" => { "code" => "OC", "name" => "Oceania" }, 1010 | "TW" => { "code" => "AS", "name" => "Asia" }, 1011 | "TT" => { "code" => "NA", "name" => "North America" }, 1012 | "TR" => { "code" => "AS", "name" => "Asia" }, 1013 | "LK" => { "code" => "AS", "name" => "Asia" }, 1014 | "LI" => { "code" => "EU", "name" => "Europe" }, 1015 | "LV" => { "code" => "EU", "name" => "Europe" }, 1016 | "TO" => { "code" => "OC", "name" => "Oceania" }, 1017 | "LT" => { "code" => "EU", "name" => "Europe" }, 1018 | "LU" => { "code" => "EU", "name" => "Europe" }, 1019 | "LR" => { "code" => "AF", "name" => "Africa" }, 1020 | "LS" => { "code" => "AF", "name" => "Africa" }, 1021 | "TH" => { "code" => "AS", "name" => "Asia" }, 1022 | "TF" => { "code" => "AN", "name" => "Antarctica" }, 1023 | "TG" => { "code" => "AF", "name" => "Africa" }, 1024 | "TD" => { "code" => "AF", "name" => "Africa" }, 1025 | "TC" => { "code" => "NA", "name" => "North America" }, 1026 | "LY" => { "code" => "AF", "name" => "Africa" }, 1027 | "VA" => { "code" => "EU", "name" => "Europe" }, 1028 | "VC" => { "code" => "NA", "name" => "North America" }, 1029 | "AE" => { "code" => "AS", "name" => "Asia" }, 1030 | "AD" => { "code" => "EU", "name" => "Europe" }, 1031 | "AG" => { "code" => "NA", "name" => "North America" }, 1032 | "AF" => { "code" => "AS", "name" => "Asia" }, 1033 | "AI" => { "code" => "NA", "name" => "North America" }, 1034 | "VI" => { "code" => "NA", "name" => "North America" }, 1035 | "IS" => { "code" => "EU", "name" => "Europe" }, 1036 | "IR" => { "code" => "AS", "name" => "Asia" }, 1037 | "AM" => { "code" => "AS", "name" => "Asia" }, 1038 | "AL" => { "code" => "EU", "name" => "Europe" }, 1039 | "AO" => { "code" => "AF", "name" => "Africa" }, 1040 | "AQ" => { "code" => "AN", "name" => "Antarctica" }, 1041 | "AS" => { "code" => "OC", "name" => "Oceania" }, 1042 | "AR" => { "code" => "SA", "name" => "South America" }, 1043 | "AU" => { "code" => "OC", "name" => "Oceania" }, 1044 | "AT" => { "code" => "EU", "name" => "Europe" }, 1045 | "AW" => { "code" => "NA", "name" => "North America" }, 1046 | "IN" => { "code" => "AS", "name" => "Asia" }, 1047 | "AX" => { "code" => "EU", "name" => "Europe" }, 1048 | "AZ" => { "code" => "AS", "name" => "Asia" }, 1049 | "IE" => { "code" => "EU", "name" => "Europe" }, 1050 | "ID" => { "code" => "AS", "name" => "Asia" }, 1051 | "UA" => { "code" => "EU", "name" => "Europe" }, 1052 | "QA" => { "code" => "AS", "name" => "Asia" }, 1053 | "MZ" => { "code" => "AF", "name" => "Africa" } 1054 | ); 1055 | 1056 | #------------------------------------------------------------------------------- 1057 | 1058 | sub new { 1059 | my ( $pkg, $token, %options ) = @_; 1060 | 1061 | my $self = {}; 1062 | $token = defined $token ? $token : ''; 1063 | 1064 | $self->{base_url} = $base_url; 1065 | $self->{base_url_ipv6} = $base_url_ipv6; 1066 | $self->{ua} = LWP::UserAgent->new; 1067 | $self->{ua}->ssl_opts( 'verify_hostname' => 0 ); 1068 | $self->{ua}->default_headers( 1069 | HTTP::Headers->new( 1070 | Accept => 'application/json', 1071 | Authorization => 'Bearer ' . $token 1072 | ) 1073 | ); 1074 | $self->{ua}->agent("IPinfoClient/Perl/$VERSION"); 1075 | 1076 | my $timeout = 1077 | defined $options{timeout} ? $options{timeout} : DEFAULT_TIMEOUT; 1078 | $self->{ua}->timeout($timeout); 1079 | 1080 | $self->{message} = ''; 1081 | 1082 | bless $self, $pkg; 1083 | 1084 | my $countries = 1085 | exists $options{countries} ? $options{countries} : \%default_countries; 1086 | my $eu_countries = 1087 | exists $options{eu_countries} 1088 | ? $options{eu_countries} 1089 | : \@default_eu_countries; 1090 | my $countries_flags = 1091 | exists $options{countries_flags} 1092 | ? $options{countries_flags} 1093 | : \%default_countries_flags; 1094 | my $countries_currencies = 1095 | exists $options{countries_currencies} 1096 | ? $options{countries_currencies} 1097 | : \%default_countries_currencies; 1098 | my $continents = 1099 | exists $options{continents} 1100 | ? $options{continents} 1101 | : \%default_continents; 1102 | 1103 | $self->{countries} = $countries; 1104 | $self->{eu_countries} = $eu_countries; 1105 | $self->{countries_flags} = $countries_flags; 1106 | $self->{countries_currencies} = $countries_currencies; 1107 | $self->{continents} = $continents; 1108 | $self->{cache} = $self->_build_cache(%options); 1109 | 1110 | return $self; 1111 | } 1112 | 1113 | #------------------------------------------------------------------------------- 1114 | 1115 | sub info { 1116 | my ( $self, $ip ) = @_; 1117 | 1118 | return $self->_get_info( $ip, '', 0 ); 1119 | } 1120 | 1121 | #------------------------------------------------------------------------------- 1122 | 1123 | sub info_v6 { 1124 | my ( $self, $ip ) = @_; 1125 | 1126 | return $self->_get_info( $ip, '', 1 ); 1127 | } 1128 | 1129 | #------------------------------------------------------------------------------- 1130 | 1131 | sub geo { 1132 | my ( $self, $ip ) = @_; 1133 | 1134 | return $self->_get_info( $ip, 'geo', 0 ); 1135 | } 1136 | 1137 | #------------------------------------------------------------------------------- 1138 | 1139 | sub field { 1140 | my ( $self, $ip, $field ) = @_; 1141 | 1142 | if ( not defined $field ) { 1143 | $self->{message} = 'Field must be defined.'; 1144 | return; 1145 | } 1146 | 1147 | if ( not defined $valid_fields{$field} ) { 1148 | $self->{message} = "Invalid field: $field"; 1149 | return; 1150 | } 1151 | 1152 | return $self->_get_info( $ip, $field, 0 ); 1153 | } 1154 | 1155 | #------------------------------------------------------------------------------- 1156 | 1157 | sub error_msg { 1158 | my $self = shift; 1159 | 1160 | return $self->{message}; 1161 | } 1162 | 1163 | #------------------------------------------------------------------------------- 1164 | #-- private method(s) below, don't call them directly ------------------------- 1165 | 1166 | sub _get_info { 1167 | my ( $self, $ip, $field, $ipv6_lookup ) = @_; 1168 | 1169 | $ip = defined $ip ? $ip : ''; 1170 | $field = defined $field ? $field : ''; 1171 | 1172 | if ( $ip ne '' ) { 1173 | my $validated_ip = Net::CIDR::cidrvalidate($ip); 1174 | if ( !defined $validated_ip ) { 1175 | $self->{message} = 'Invalid IP address'; 1176 | return undef; 1177 | } 1178 | } 1179 | 1180 | my ( $info, $message ) = $self->_lookup_info( $ip, $field, $ipv6_lookup ); 1181 | $self->{message} = $message; 1182 | 1183 | if ( $field ne '' && ref($info) eq 'HASH' ) { 1184 | if ( exists $info->{'bogon'} ) { 1185 | $self->{message} = 'Field info not available for bogon IPs'; 1186 | return undef; 1187 | } 1188 | } 1189 | 1190 | return defined $info ? Geo::Details->new( $info, $field ) : undef; 1191 | } 1192 | 1193 | sub _lookup_info { 1194 | my ( $self, $ip, $field, $ipv6_lookup ) = @_; 1195 | 1196 | # checking bogon IP and returning response locally. 1197 | if ( $ip ne '' ) { 1198 | if ( _is_bogon($ip) ) { 1199 | my $details = {}; 1200 | $details->{ip} = $ip; 1201 | $details->{bogon} = "True"; 1202 | return ( $details, '' ); 1203 | } 1204 | } 1205 | 1206 | my $key = $ip . '/' . $field; 1207 | my $cached_info = $self->_lookup_info_from_cache($key); 1208 | 1209 | if ( defined $cached_info ) { 1210 | return ( $cached_info, '' ); 1211 | } 1212 | 1213 | my ( $source_info, $message ) = $self->_lookup_info_from_source($ipv6_lookup, $key); 1214 | if ( not defined $source_info ) { 1215 | return ( $source_info, $message ); 1216 | } 1217 | 1218 | if ( ref($source_info) eq '' ) { 1219 | return ( $source_info, $message ); 1220 | } 1221 | 1222 | my $country = $source_info->{country}; 1223 | if ( defined $country ) { 1224 | $source_info->{country_name} = $self->{countries}->{$country}; 1225 | $source_info->{country_flag} = $self->{countries_flags}->{$country}; 1226 | $source_info->{country_flag_url} = 1227 | $country_flag_url . $country . ".svg"; 1228 | $source_info->{country_currency} = 1229 | $self->{countries_currencies}->{$country}; 1230 | $source_info->{continent} = $self->{continents}->{$country}; 1231 | if ( grep { $_ eq $country } @{ $self->{eu_countries} } ) { 1232 | $source_info->{is_eu} = "True"; 1233 | } 1234 | else { 1235 | $source_info->{is_eu} = undef; 1236 | } 1237 | } 1238 | 1239 | if ( defined $source_info->{'loc'} ) { 1240 | my ( $lat, $lon ) = split /,/, $source_info->{loc}; 1241 | $source_info->{latitude} = $lat; 1242 | $source_info->{longitude} = $lon; 1243 | } 1244 | 1245 | $source_info->{meta} = { time => time(), from_cache => 0 }; 1246 | $self->{cache}->set( $key, $source_info ); 1247 | 1248 | return ( $source_info, $message ); 1249 | } 1250 | 1251 | sub _lookup_info_from_cache { 1252 | my ( $self, $cache_key ) = @_; 1253 | 1254 | my $cached_info = $self->{cache}->get($cache_key); 1255 | if ( defined $cached_info ) { 1256 | my $timedelta = time() - $cached_info->{meta}->{time}; 1257 | if ( $timedelta <= $cache_ttl || $custom_cache == 1 ) { 1258 | $cached_info->{meta}->{from_cache} = 1; 1259 | 1260 | return $cached_info; 1261 | } 1262 | } 1263 | 1264 | return; 1265 | } 1266 | 1267 | sub _lookup_info_from_source { 1268 | my ( $self, $is_ipv6, $key ) = @_; 1269 | 1270 | my $url = ''; 1271 | if ( $is_ipv6 ) { 1272 | $url = $self->{base_url_ipv6} . $key; 1273 | } else { 1274 | $url = $self->{base_url} . $key; 1275 | } 1276 | 1277 | my $response = $self->{ua}->get($url); 1278 | 1279 | if ( $response->is_success ) { 1280 | 1281 | my $content_type = $response->header('Content-Type') || ''; 1282 | my $info; 1283 | 1284 | if ( $content_type =~ m{application/json}i ) { 1285 | eval { $info = from_json( $response->decoded_content ); }; 1286 | if ($@) { 1287 | return ( undef, 'Error parsing JSON response.' ); 1288 | } 1289 | } 1290 | else { 1291 | $info = $response->decoded_content; 1292 | chomp($info); 1293 | } 1294 | 1295 | return ( $info, '' ); 1296 | } 1297 | if ( $response->code == HTTP_TOO_MANY_REQUEST ) { 1298 | return ( undef, 'Your monthly request quota has been exceeded.' ); 1299 | } 1300 | 1301 | return ( undef, $response->status_line ); 1302 | } 1303 | 1304 | sub _build_cache { 1305 | my ( $pkg, %options ) = @_; 1306 | 1307 | if ( defined $options{cache} ) { 1308 | $custom_cache = 1; 1309 | 1310 | return $options{cache}; 1311 | } 1312 | 1313 | $cache_ttl = DEFAULT_CACHE_TTL; 1314 | if ( defined $options{cache_ttl} ) { 1315 | $cache_ttl = $options{cache_ttl}; 1316 | } 1317 | 1318 | return Cache::LRU->new( 1319 | size => defined $options{cache_max_size} 1320 | ? $options{cache_max_size} 1321 | : DEFAULT_CACHE_MAX_SIZE 1322 | ); 1323 | } 1324 | 1325 | # Lists of bogon CIDRs. 1326 | my @ip4_bogon_networks = ( 1327 | "0.0.0.0/8", "10.0.0.0/8", 1328 | "100.64.0.0/10", "127.0.0.0/8", 1329 | "169.254.0.0/16", "172.16.0.0/12", 1330 | "192.0.0.0/24", "192.0.2.0/24", 1331 | "192.168.0.0/16", "198.18.0.0/15", 1332 | "198.51.100.0/24", "203.0.113.0/24", 1333 | "224.0.0.0/4", "240.0.0.0/4", 1334 | "255.255.255.255/32" 1335 | ); 1336 | my @ip6_bogon_networks = ( 1337 | "0:0:0:0:0:0:0:0/128", "0:0:0:0:0:0:0:1/128", 1338 | "0:0:0:0:0:ffff:0:0/96", "0:0:0:0:0:0:0:0/96", 1339 | "100::/64", "2001:10::/28", 1340 | "2001:db8::/32", "fc00::/7", 1341 | "fe80::/10", "fec0::/10", 1342 | "ff00::/8", "2002::/24", 1343 | "2002:a00::/24", "2002:7f00::/24", 1344 | "2002:a9fe::/32", "2002:ac10::/28", 1345 | "2002:c000::/40", "2002:c000:200::/40", 1346 | "2002:c0a8::/32", "2002:c612::/31", 1347 | "2002:c633:6400::/40", "2002:cb00:7100::/40", 1348 | "2002:e000::/20", "2002:f000::/20", 1349 | "2002:ffff:ffff::/48", "2001::/40", 1350 | "2001:0:a00::/40", "2001:0:7f00::/40", 1351 | "2001:0:a9fe::/48", "2001:0:ac10::/44", 1352 | "2001:0:c000::/56", "2001:0:c000:200::/56", 1353 | "2001:0:c0a8::/48", "2001:0:c612::/47", 1354 | "2001:0:c633:6400::/56", "2001:0:cb00:7100::/56", 1355 | "2001:0:e000::/36", "2001:0:f000::/36", 1356 | "2001:0:ffff:ffff::/64" 1357 | ); 1358 | 1359 | # Check if an IP address is a bogon. 1360 | sub _is_bogon { 1361 | my $ip = shift; 1362 | 1363 | my $ip_is_bogon = 0; 1364 | 1365 | if ( $ip =~ /:/ ) { # IPv6 address 1366 | my $ip6_bogon_cidr_set = Net::CIDR::Set->new(); 1367 | $ip6_bogon_cidr_set->add($_) foreach (@ip6_bogon_networks); 1368 | $ip_is_bogon = $ip6_bogon_cidr_set->contains($ip); 1369 | } 1370 | else { # IPv4 address 1371 | my $ip4_bogon_cidr_set = Net::CIDR::Set->new(); 1372 | $ip4_bogon_cidr_set->add($_) foreach (@ip4_bogon_networks); 1373 | $ip_is_bogon = $ip4_bogon_cidr_set->contains($ip); 1374 | } 1375 | 1376 | return $ip_is_bogon; 1377 | } 1378 | 1379 | #------------------------------------------------------------------------------- 1380 | 1381 | 1; 1382 | __END__ 1383 | 1384 | 1385 | =head1 NAME 1386 | 1387 | Geo::IPinfo - The official Perl library for IPinfo. 1388 | 1389 | =head1 VERSION 1390 | 1391 | Version 3.0.1 1392 | 1393 | =cut 1394 | 1395 | =head1 SYNOPSIS 1396 | 1397 | Geo::IP The official Perl library for IPinfo. IPinfo prides itself on being the most reliable, accurate, and in-depth source of IP address data available anywhere. We process terabytes of data to produce our custom IP geolocation, company, carrier and IP type data sets. You can visit our developer docs at https://ipinfo.io/developers. 1398 | 1399 | A quick usage example: 1400 | 1401 | use Geo::IPinfo; 1402 | 1403 | $access_token = '123456789abc'; 1404 | $ipinfo = Geo::IPinfo->new($access_token); 1405 | 1406 | $ip_address = '216.239.36.21'; 1407 | $details = $ipinfo->info($ip_address); 1408 | $city = $details->city; # Mountain View 1409 | $loc = $details->loc; # 37.4056,-122.0775 1410 | 1411 | $ip_address = '2001:4860:4860::8888'; 1412 | $details = $ipinfo->info_v6($ip_address); 1413 | $city = $details->city; # Mountain View 1414 | $loc = $details->loc; # 37.4056,-122.0775 1415 | 1416 | =head1 SUBROUTINES/METHODS 1417 | 1418 | =head2 new([token], [options]) 1419 | 1420 | Create an ipinfo object. The 'token' (string value) and 'options' (hash value) arguments are optional. 1421 | 1422 | If 'token' is specified, then it's used to overcome the default 1423 | non-commercial limitation of 1,000 request/day (For more details, see L) 1424 | 1425 | if 'options' is specfied, the included values will allow control over cache policies and country name localization (For more details, see L). 1426 | 1427 | =cut 1428 | 1429 | =head2 info(ip_address) 1430 | 1431 | Returns a reference to a Details object containing all information related to the IPv4 address. In case 1432 | of errors, returns undef, the error message can be retrieved with the function 'error_msg()' 1433 | 1434 | The values can be accessed with the named methods: ip, org, domains, privacy, abuse, timezone, hostname, city, country, country_name, country_flag, 1435 | country_flag_url, country_currency, continent, is_eu, loc, latitude, longitude, postal, asn, company, meta, carrier, and all. 1436 | 1437 | =head2 info_v6(ip_address) 1438 | 1439 | Returns a reference to a Details object containing all information related to the IPv6 address. In case 1440 | of errors, returns undef, the error message can be retrieved with the function 'error_msg()' 1441 | 1442 | The values can be accessed with the named methods: ip, org, domains, privacy, abuse, timezone, hostname, city, country, country_name, country_flag, 1443 | country_flag_url, country_currency, continent, is_eu, loc, latitude, longitude, postal, asn, company, meta, carrier, and all. 1444 | 1445 | =head2 geo(ip_address) 1446 | 1447 | Returns a reference to an object containing only the geolocation related data. Returns undef 1448 | in case of errors, the error message can be retrieved with the function 'error_msg' 1449 | 1450 | It's usually faster than getting the full response using 'info()' 1451 | 1452 | The values returned are: ip, city, org, loc, latitude, longitude, hostname, is_eu, country, country_name, country_flag, 1453 | country_flag_url, country_currency, meta, continent, postal, region, and timezone. 1454 | 1455 | =head2 field(ip_address, field_name) 1456 | 1457 | Returns a reference to an object containing only the field related data. Returns undef 1458 | if the field is invalid 1459 | 1460 | The possible values of 'field_name' are: ip, hostname, city, region, country, loc, org, postal, timezone, geo, asn, 1461 | company, privacy, abuse, and domains. 1462 | 1463 | =head2 error_msg( ) 1464 | 1465 | Returns a string containing the error message of the last operation, it returns an empty 1466 | string if the last operation was successful 1467 | 1468 | =cut 1469 | 1470 | =head1 AUTHOR 1471 | 1472 | Ben Dowling, C<< >> 1473 | 1474 | =head1 BUGS 1475 | 1476 | Please report any bugs or feature requests to C, or through 1477 | the web interface at L. I will be notified, and then you'll 1478 | automatically be notified of progress on your bug as I make changes. 1479 | 1480 | 1481 | 1482 | 1483 | =head1 SUPPORT 1484 | 1485 | You can find documentation for this module with the perldoc command. 1486 | 1487 | perldoc Geo::IPinfo 1488 | 1489 | 1490 | You can also look for information at: 1491 | 1492 | =over 4 1493 | 1494 | =item * RT: CPAN's request tracker (report bugs here) 1495 | 1496 | L 1497 | 1498 | =item * AnnoCPAN: Annotated CPAN documentation 1499 | 1500 | L 1501 | 1502 | =item * CPAN Ratings 1503 | 1504 | L 1505 | 1506 | =item * Search CPAN 1507 | 1508 | L 1509 | 1510 | =item * GitHub 1511 | 1512 | L 1513 | 1514 | =back 1515 | 1516 | 1517 | =head1 ACKNOWLEDGEMENTS 1518 | 1519 | 1520 | =head1 LICENSE AND COPYRIGHT 1521 | 1522 | Copyright 2019 ipinfo.io. 1523 | 1524 | Licensed under the Apache License, Version 2.0 (the "License"); 1525 | you may not use this file except in compliance with the License. 1526 | You may obtain a copy of the License at 1527 | 1528 | L 1529 | 1530 | Unless required by applicable law or agreed to in writing, software 1531 | distributed under the License is distributed on an "AS IS" BASIS, 1532 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1533 | See the License for the specific language governing permissions and 1534 | limitations under the License. 1535 | 1536 | 1537 | =cut 1538 | 1539 | # End of Geo::IPinfo 1540 | -------------------------------------------------------------------------------- /Geo-IPinfo/t/00-load.t: -------------------------------------------------------------------------------- 1 | #!perl -T 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | 6 | use Test::More; 7 | 8 | plan tests => 1; 9 | 10 | BEGIN { 11 | use_ok('Geo::IPinfo') || print "Bail out!\n"; 12 | } 13 | 14 | diag("Testing Geo::IPinfo $Geo::IPinfo::VERSION, Perl $], $^X"); 15 | -------------------------------------------------------------------------------- /Geo-IPinfo/t/01-usage.t: -------------------------------------------------------------------------------- 1 | #!perl -T 2 | 3 | use strict; 4 | use warnings; 5 | use Test::More; 6 | 7 | if ( $ENV{RELEASE_TESTING} ) { 8 | plan tests => 7; 9 | } 10 | else { 11 | plan( skip_all => "Basic usage tests not required for installation" ); 12 | } 13 | 14 | use_ok('Geo::IPinfo'); 15 | 16 | my $ip; 17 | 18 | $ip = Geo::IPinfo->new(); 19 | isa_ok( $ip, "Geo::IPinfo", '$ip' ); 20 | 21 | ok( $ip->info("8.8.8.8"), "info() return a hash when querying a valid IP" ); 22 | 23 | is( $ip->info("1000.1000.1.1"), 24 | undef, "info() return undef when querying an invalid IP" ); 25 | 26 | is( $ip->field("8.8.8.8"), 27 | undef, "field() return undef if 'field' is missing" ); 28 | 29 | my $details = $ip->field( '8.8.8.8', 'city' ); 30 | my $city = $details->city; 31 | is( 32 | $city, 33 | "Mountain View", 34 | "field() return a valid string when querying a valid IP" 35 | ); 36 | is( $ip->field( "192.168.0.1", "city" ), 37 | undef, "field() return 'undef' when getting fields of private IPs" ); 38 | -------------------------------------------------------------------------------- /Geo-IPinfo/t/manifest.t: -------------------------------------------------------------------------------- 1 | #!perl -T 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use Test::More; 6 | 7 | unless ( $ENV{RELEASE_TESTING} ) { 8 | plan( skip_all => "Author tests not required for installation" ); 9 | } 10 | 11 | my $min_tcm = 0.9; 12 | eval "use Test::CheckManifest $min_tcm"; 13 | plan skip_all => "Test::CheckManifest $min_tcm required" if $@; 14 | 15 | ok_manifest(); 16 | -------------------------------------------------------------------------------- /Geo-IPinfo/t/pod-coverage.t: -------------------------------------------------------------------------------- 1 | #!perl -T 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use Test::More; 6 | 7 | unless ( $ENV{RELEASE_TESTING} ) { 8 | plan( skip_all => "Author tests not required for installation" ); 9 | } 10 | 11 | # Ensure a recent version of Test::Pod::Coverage 12 | my $min_tpc = 1.08; 13 | eval "use Test::Pod::Coverage $min_tpc"; 14 | plan skip_all => 15 | "Test::Pod::Coverage $min_tpc required for testing POD coverage" 16 | if $@; 17 | 18 | # Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, 19 | # but older versions don't recognize some common documentation styles 20 | my $min_pc = 0.18; 21 | eval "use Pod::Coverage $min_pc"; 22 | plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" 23 | if $@; 24 | 25 | all_pod_coverage_ok(); 26 | -------------------------------------------------------------------------------- /Geo-IPinfo/t/pod.t: -------------------------------------------------------------------------------- 1 | #!perl -T 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use Test::More; 6 | 7 | unless ( $ENV{RELEASE_TESTING} ) { 8 | plan( skip_all => "Author tests not required for installation" ); 9 | } 10 | 11 | # Ensure a recent version of Test::Pod 12 | my $min_tp = 1.22; 13 | eval "use Test::Pod $min_tp"; 14 | plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; 15 | 16 | all_pod_files_ok(); 17 | -------------------------------------------------------------------------------- /Geo-IPinfo/xt/boilerplate.t: -------------------------------------------------------------------------------- 1 | #!perl -T 2 | use 5.006; 3 | use strict; 4 | use warnings; 5 | use Test::More; 6 | 7 | plan tests => 3; 8 | 9 | sub not_in_file_ok { 10 | my ($filename, %regex) = @_; 11 | open( my $fh, '<', $filename ) 12 | or die "couldn't open $filename for reading: $!"; 13 | 14 | my %violated; 15 | 16 | while (my $line = <$fh>) { 17 | while (my ($desc, $regex) = each %regex) { 18 | if ($line =~ $regex) { 19 | push @{$violated{$desc}||=[]}, $.; 20 | } 21 | } 22 | } 23 | 24 | if (%violated) { 25 | fail("$filename contains boilerplate text"); 26 | diag "$_ appears on lines @{$violated{$_}}" for keys %violated; 27 | } else { 28 | pass("$filename contains no boilerplate text"); 29 | } 30 | } 31 | 32 | sub module_boilerplate_ok { 33 | my ($module) = @_; 34 | not_in_file_ok($module => 35 | 'the great new $MODULENAME' => qr/ - The great new /, 36 | 'boilerplate description' => qr/Quick summary of what the module/, 37 | 'stub function definition' => qr/function[12]/, 38 | ); 39 | } 40 | 41 | TODO: { 42 | local $TODO = "Need to replace the boilerplate text"; 43 | 44 | not_in_file_ok(README => 45 | "The README is used..." => qr/The README is used/, 46 | "'version information here'" => qr/to provide version information/, 47 | ); 48 | 49 | not_in_file_ok(Changes => 50 | "placeholder date/time" => qr(Date/time) 51 | ); 52 | 53 | module_boilerplate_ok('lib/Geo/IPinfo.pm'); 54 | 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [IPinfo](https://ipinfo.io/) IPinfo Perl Client Library 2 | 3 | This is the official Perl client library for the [IPinfo.io](https://ipinfo.io) IP address API, allowing you to look up your own IP address, or get any of the following details for an IP: 4 | 5 | - [IP to Geolocation](https://ipinfo.io/ip-geolocation-api) (city, region, country, postal code, latitude, and longitude) 6 | - [IP to ASN](https://ipinfo.io/asn-api) (ISP or network operator, associated domain name, and type, such as business, hosting, or company) 7 | - [IP to Company](https://ipinfo.io/ip-company-api) (the name and domain of the business that uses the IP address) 8 | - [IP to Carrier](https://ipinfo.io/ip-carrier-api) (the name of the mobile carrier and MNC and MCC for that carrier if the IP is used exclusively for mobile traffic) 9 | 10 | Check all the data we have for your IP address [here](https://ipinfo.io/what-is-my-ip). 11 | 12 | ### Getting Started 13 | 14 | You'll need an IPinfo API access token, which you can get by signing up for a free account at [https://ipinfo.io/signup](https://ipinfo.io/signup). 15 | 16 | The free plan is limited to 50,000 requests per month, and doesn't include some of the data fields such as IP type and company data. To enable all the data fields and additional request volumes see [https://ipinfo.io/pricing](https://ipinfo.io/pricing) 17 | 18 | ⚠️ Note: This library does not currently support our newest free API https://ipinfo.io/lite. If you’d like to use IPinfo Lite, you can call the [endpoint directly](https://ipinfo.io/developers/lite-api) using your preferred HTTP client. Developers are also welcome to contribute support for Lite by submitting a pull request. 19 | 20 | #### Installation 21 | 22 | Using `cpanm` install the `Geo::IPinfo` module: 23 | 24 | $ cpanm Geo::IPinfo 25 | 26 | Add this line to your application code: 27 | 28 | ```perl 29 | use Geo::IPinfo; 30 | ``` 31 | 32 | If you'd like to install from source (not necessary for use in your application), download the source and run the following commands: 33 | 34 | perl Makefile.PL 35 | make 36 | make test 37 | make install 38 | 39 | #### Quick Start 40 | 41 | ```perl 42 | use Geo::IPinfo; 43 | 44 | $access_token = '123456789abc'; 45 | $ipinfo = Geo::IPinfo->new($access_token); 46 | 47 | $ip_address = '216.239.36.21'; 48 | $details = $ipinfo->info($ip_address); 49 | $city = $details->city; # Emeryville 50 | $loc = $details->loc; # 37.8342,-122.2900 51 | ``` 52 | 53 | #### Dependencies 54 | 55 | - Cache::LRU 56 | - JSON 57 | - LWP::UserAgent 58 | - HTTP::Headers 59 | - Net::CIDR 60 | - Net::CIDR::Set 61 | 62 | #### Usage 63 | 64 | The `Geo::IPinfo->info()` method accepts an IPv4 address as an optional, positional argument. If no IP address is specified, the API will return data for the IP address from which it receives the request. The `Geo::IPinfo->info_v6()` method works in a similar fashion but for IPv6 addresses. 65 | 66 | ```perl 67 | use Geo::IPinfo; 68 | 69 | $access_token = '123456789abc'; 70 | $ipinfo = Geo::IPinfo->new($access_token); 71 | $details = $ipinfo->info($ip_address); 72 | # for IPv6 73 | # $details = $ipinfo->info_v6($ip_address); 74 | 75 | if (defined $details) # valid data returned 76 | { 77 | $city = $details->city; # Emeryville 78 | $loc = $details->loc; # 37.8342,-122.2900 79 | } 80 | else # invalid data obtained, show error message 81 | { 82 | print $ipinfo->error_msg . "\n"; 83 | } 84 | ``` 85 | 86 | If the `Details` object is empty the error message can be accessed via `Geo::IPinfo->error_msg`. 87 | 88 | #### Authentication 89 | 90 | The IPinfo library can be authenticated with your IPinfo API token, which is passed in as a positional argument. It also works without an authentication token, but in a more limited capacity. 91 | 92 | ```perl 93 | $access_token = '123456789abc'; 94 | $ipinfo = Geo::IPinfo->new($access_token); 95 | ``` 96 | 97 | #### Details Data 98 | 99 | `Geo::IPinfo->info()` and `Geo::IPinfo->info_v6()` will return a `Details` object that contains all fields listed in the [IPinfo developer docs](https://ipinfo.io/developers/responses#full-response) with a few minor additions. Properties can be accessed through methods of the same name. 100 | 101 | ```perl 102 | $hostname = $details->hostname; # cpe-104-175-221-247.socal.res.rr.com 103 | ``` 104 | 105 | ##### Country Name 106 | 107 | `Details->country_name` will return the country name. See below for instructions on changing these country names for use with non-English languages. `Details->country` will still return the country code. 108 | 109 | ```perl 110 | $country = $details->country; # US 111 | $country_name = $details->country_name; # United States 112 | ``` 113 | 114 | #### IP Address 115 | 116 | `Details->ip_address` will return the `IPAddr` object from the [Perl Standard Library](https://perl-doc.org/stdlib-2.5.1/libdoc/ipaddr/rdoc/IPAddr.html). `Details->ip` will still return a string. 117 | 118 | ```perl 119 | $ip = $details->ip; # 104.175.221.247 120 | $ip_addr = $details->ip_address; # 121 | ``` 122 | 123 | ##### Longitude and Latitude 124 | 125 | `Details->latitude` and `Details->longitude` will return latitude and longitude, respectively, as strings. `Details->loc` will still return a composite string of both values. 126 | 127 | ```perl 128 | $loc = $details->loc; # 34.0293,-118.3570 129 | $lat = $details->latitude; # 34.0293 130 | $lon = $details->longitude; # -118.3570 131 | ``` 132 | 133 | ##### Accessing all properties 134 | 135 | `Details->all` will return all details data as a dictionary. 136 | 137 | ```perl 138 | $details->all = { 139 | "ip": "104.175.221.247", 140 | "hostname": "cpe-104-175-221-247.socal.res.rr.com", 141 | "city": "Los Angeles", 142 | "region": "California", 143 | "country": "US", 144 | "loc": "34.0290,-118.4000", 145 | "postal": "90034", 146 | "asn": { 147 | "asn": "AS20001", 148 | "name": "Time Warner Cable Internet LLC", 149 | "domain": "twcable.com", 150 | "route": "104.172.0.0/14", 151 | "type": "isp" 152 | }, 153 | "company": { 154 | "name": "Time Warner Cable Internet LLC", 155 | "domain": "twcable.com", 156 | "type": "isp" 157 | } 158 | } 159 | ``` 160 | 161 | #### Caching 162 | 163 | In-memory caching of `Details` data is provided by default via the [Cache::LRU](https://metacpan.org/pod/Cache::LRU) package. This uses an LRU (least recently used) cache with a TTL (time to live) by default. This means that values will be cached for the specified duration; if the cache's max size is reached, cache values will be invalidated as necessary, starting with the oldest cached value. 164 | 165 | ##### Modifying cache options 166 | 167 | Cache behavior can be modified with the `%options` argument. 168 | 169 | - Default maximum cache size: 4096 (multiples of 2 are recommended to increase efficiency) 170 | - Default TTL: 24 hours (in seconds) 171 | 172 | ```perl 173 | $token = '1234'; 174 | $ipinfo = Geo::IPinfo->new($token, ("cache_ttl" => 100, "cache_max_size" => 1000)); 175 | ``` 176 | 177 | ##### Using a different cache 178 | 179 | It's possible to use a custom cache by passing this into the handler object with the `cache` option. A custom cache must include the following methods: 180 | 181 | - $custom_cache->get($key); 182 | - $custom_cache->set($key, $value); 183 | 184 | If a custom cache is used then the `cache_ttl` and `cache_max_size` options will not be used. 185 | 186 | ```perl 187 | $ipinfo = Geo::IPinfo->new($token, ("cache" => $my_custom_cache)); 188 | ``` 189 | 190 | ### Request options 191 | 192 | The request timeout period can be set in the `%options` parameter. 193 | 194 | - Default request timeout: 2 seconds 195 | 196 | ```perl 197 | $ipinfo = Geo::IPinfo->new($token, ("timeout" => 5)); 198 | ``` 199 | 200 | #### Internationalization 201 | 202 | When looking up an IP address, the `$details` object includes a `$details->country_name` method which includes the country name based on American English, `$details->is_eu` method which returns `true` if the country is a member of the European Union (EU) else `undef`, `$details->country_flag` method which returns a dictionary of emoji and Unicode of the country's flag, `$details->country_flag_url` will return a public link to the country's flag image as an SVG which can be used anywhere and `$details->country_currency` method which returns a dictionary of code, the symbol of a country's currency and `$details->continent` which includes code and name of the continent. It is possible to return the country name in other languages, change the EU countries, countries flags, countries' currencies, and continents by setting the `countries`, `eu_countries`, `countries_flags`, `countries_currencies` and `continents` settings when creating the `IPinfo` object. The `countries`, `countries_flags`, `countries_currencies`, and `continents` are hashes while `eu_countries` is an array. 203 | 204 | ```perl 205 | my %custom_countries = ( 206 | "US" => "Custom United States", 207 | "DE" => "Custom Germany" 208 | ); 209 | my @custom_eu_countries = ( "FR", "DE" ); 210 | my %custom_countries_flags = ( 211 | 'AD' => { 'emoji' => '🇦🇩', 'unicode' => 'U+1F1E6 U+1F1E9' }, 212 | 'AE' => { 'emoji' => '🇦🇪', 'unicode' => 'U+1F1E6 U+1F1EA' } 213 | ); 214 | my %custom_countries_currencies = ( 215 | 'AD' => { 'code' => 'EUR', 'symbol' => '€' }, 216 | 'AE' => { 'code' => 'AED', 'symbol' => 'د.إ' } 217 | ); 218 | my %custom_continents = ( 219 | "BE" => { "code" => "EU", "name" => "Europe" }, 220 | "BF" => { "code" => "AF", "name" => "Africa" } 221 | ); 222 | 223 | $ipinfo = Geo::IPinfo->new($token, countries => \%custom_countries); 224 | $ipinfo = Geo::IPinfo->new($token, eu_countries => \@custom_eu_countries); 225 | $ipinfo = Geo::IPinfo->new($token, countries_flags => \%custom_countries_flags); 226 | $ipinfo = Geo::IPinfo->new($token, countries_currencies => \%custom_countries_currencies); 227 | $ipinfo = Geo::IPinfo->new($token, continents => \%custom_continents); 228 | ``` 229 | 230 | ### Additional Information 231 | 232 | Additional package information can be found at the following locations: 233 | 234 | RT, CPAN's request tracker (report bugs here) 235 | http://rt.cpan.org/NoAuth/Bugs.html?Dist=Geo-IPinfo 236 | 237 | AnnoCPAN, Annotated CPAN documentation 238 | http://annocpan.org/dist/Geo-IPinfo 239 | 240 | CPAN Ratings 241 | http://cpanratings.perl.org/d/Geo-IPinfo 242 | 243 | Search CPAN 244 | http://search.cpan.org/dist/Geo-IPinfo/ 245 | 246 | ### Other Libraries 247 | 248 | There are [official IPinfo client libraries](https://ipinfo.io/developers/libraries) available for many languages including PHP, Go, Java, Ruby, and many popular frameworks such as Django, Rails, and Laravel. There are also many third-party libraries and integrations available for our API. 249 | 250 | ### About IPinfo 251 | 252 | Founded in 2013, IPinfo prides itself on being the most reliable, accurate, and in-depth source of IP address data available anywhere. We process terabytes of data to produce our custom IP geolocation, company, carrier, privacy detection, Reverse IP, hosted domains, and IP type data sets. Our API handles over 40 billion requests a month for 100,000 businesses and developers. 253 | 254 | [![image](https://avatars3.githubusercontent.com/u/15721521?s=128&u=7bb7dde5c4991335fb234e68a30971944abc6bf3&v=4)](https://ipinfo.io/) 255 | 256 | SUPPORT AND DOCUMENTATION 257 | 258 | After installing, you can find documentation for this module with the 259 | perldoc command. 260 | 261 | perldoc Geo::IPinfo 262 | -------------------------------------------------------------------------------- /example.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | use strict; 4 | use warnings; 5 | 6 | use lib 'Geo-IPinfo/lib'; 7 | 8 | use Geo::IPinfo; 9 | use Data::Dumper; 10 | use JSON; 11 | 12 | my $token = '1234567'; 13 | my %custom_countries = ( 14 | "US" => "Custom United States", 15 | "DE" => "Custom Germany" 16 | ); 17 | my @custom_eu_countries = ( "FR", "DE" ); 18 | 19 | # if you have a valid token, use it 20 | my $ipinfo = Geo::IPinfo->new($token); 21 | 22 | # or, if you don't have a token, use this: 23 | # my $ipinfo = Geo::IPinfo->new(); 24 | 25 | # provide your own countries and eu countries 26 | # my $ipinfo = Geo::IPinfo->new($token, countries => \%custom_countries, eu_countries => \@custom_eu_countries); 27 | 28 | # return a hash reference containing all IP related information 29 | my $data = $ipinfo->info('8.8.8.8'); 30 | 31 | if ( defined $data ) # valid data returned 32 | { 33 | # use Data::Dumper to see the contents of the hash reference (useful for debugging) 34 | print Dumper($data); 35 | 36 | # loop and print key-value paris 37 | print "\nInformation about IP 8.8.8.8:\n"; 38 | for my $key ( sort keys %{$data} ) { 39 | printf "%10s : %s\n", $key, 40 | defined $data->{$key} ? $data->{$key} : "N/A"; 41 | } 42 | print "\n"; 43 | 44 | # print JSON string 45 | my $json = JSON->new->allow_blessed->convert_blessed; 46 | my $json_string = $json->utf8->pretty->encode($data); 47 | print $json_string . "\n"; 48 | } 49 | else # invalid data obtained, show error message 50 | { 51 | print $ipinfo->error_msg . "\n"; 52 | } 53 | 54 | # you can also retrieve information for IPv6 addresses in a similar fashion 55 | my $ipv6_data = $ipinfo->info_v6('2001:4860:4860::8888'); 56 | if ( defined $ipv6_data ) # valid data returned 57 | { 58 | # print JSON string 59 | my $json = JSON->new->allow_blessed->convert_blessed; 60 | my $json_string = $json->utf8->pretty->encode($ipv6_data); 61 | print $json_string . "\n"; 62 | } 63 | else # invalid data obtained, show error message 64 | { 65 | print $ipinfo->error_msg . "\n"; 66 | } 67 | 68 | # retrieve only city information of the IP address 69 | my $details = $ipinfo->field( '8.8.8.8', 'city' ); 70 | print "The city of 8.8.8.8 is " . $details->city . "\n"; 71 | -------------------------------------------------------------------------------- /scripts/fmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR=`dirname $0` 4 | ROOT=$DIR/.. 5 | 6 | # Format code in project. 7 | 8 | perltidy \ 9 | -l=79 \ 10 | -b -bext='/' \ 11 | $ROOT/example.pl \ 12 | $ROOT/Geo-IPinfo/t/* \ 13 | $ROOT/Geo-IPinfo/lib/Geo/* 14 | --------------------------------------------------------------------------------