├── .gitignore ├── scripts ├── deploy.sh ├── deploy_docs.sh └── ctags.sh ├── src ├── main │ └── java │ │ └── io │ │ └── ipinfo │ │ └── api │ │ ├── errors │ │ ├── ErrorResponseException.java │ │ └── RateLimitedException.java │ │ ├── model │ │ ├── Mobile.java │ │ ├── Continent.java │ │ ├── CountryCurrency.java │ │ ├── CountryFlag.java │ │ ├── MapResponse.java │ │ ├── Domains.java │ │ ├── Carrier.java │ │ ├── Company.java │ │ ├── Anonymous.java │ │ ├── ASN.java │ │ ├── ASNPlus.java │ │ ├── Privacy.java │ │ ├── Abuse.java │ │ ├── Prefix.java │ │ ├── Geo.java │ │ ├── GeoPlus.java │ │ ├── ASNResponse.java │ │ ├── IPResponseLite.java │ │ ├── IPResponseCore.java │ │ ├── IPResponsePlus.java │ │ └── IPResponse.java │ │ ├── cache │ │ ├── NoCache.java │ │ ├── Cache.java │ │ └── SimpleCache.java │ │ ├── request │ │ ├── ASNRequest.java │ │ ├── MapRequest.java │ │ ├── BaseRequest.java │ │ ├── IPRequestLite.java │ │ ├── IPRequestCore.java │ │ ├── IPRequestPlus.java │ │ └── IPRequest.java │ │ ├── IPinfoCore.java │ │ ├── IPinfoPlus.java │ │ ├── IPinfoLite.java │ │ └── IPinfo.java └── test │ └── java │ └── io │ └── ipinfo │ ├── IPinfoCoreTest.java │ ├── IPinfoPlusTest.java │ ├── IPinfoLiteTest.java │ └── IPinfoTest.java ├── .github └── workflows │ └── test.yml ├── CHANGELOG.md ├── pom.xml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | .vim/ 4 | out/ 5 | 6 | *.gpg 7 | settings.xml 8 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | echo "Publishing Maven snapshot..." 6 | 7 | mvn clean source:jar javadoc:jar deploy 8 | 9 | echo "Maven snapshot published." 10 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/errors/ErrorResponseException.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.errors; 2 | 3 | public class ErrorResponseException extends RuntimeException { 4 | public ErrorResponseException() {} 5 | 6 | public ErrorResponseException(Exception ex) { 7 | super(ex); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/errors/RateLimitedException.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.errors; 2 | 3 | public class RateLimitedException extends Exception { 4 | public RateLimitedException() { 5 | super("You have been sending too many requests. Visit https://ipinfo.io/account to see your API limits."); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Mobile.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class Mobile { 4 | // Mobile can be empty object {} so all fields are optional 5 | 6 | public Mobile() { 7 | } 8 | 9 | @Override 10 | public String toString() { 11 | return "Mobile{}"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/deploy_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | echo "Publishing Javadoc and JDiff..." 6 | 7 | cd ./target 8 | git clone -b gh-pages git@github.com:ipinfo/java gh-pages 9 | cd gh-pages 10 | cp -rf ../apidocs/* . 11 | git add --all 12 | git commit -m "Deploy javadocs to GitHub Pages." 13 | git push -f origin gh-pages 14 | 15 | echo "Javadoc and JDiff published to gh-pages." 16 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/cache/NoCache.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.cache; 2 | 3 | public class NoCache implements Cache { 4 | @Override 5 | public Object get(String key) { 6 | return null; 7 | } 8 | 9 | @Override 10 | public boolean set(String key, Object val) { 11 | return false; 12 | } 13 | 14 | @Override 15 | public boolean clear() { 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | java-version: ["8", "11", "16", "17", "21"] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Set up JDK 22 | uses: actions/setup-java@v3 23 | with: 24 | java-version: ${{ matrix.java-version }} 25 | distribution: "temurin" 26 | cache: maven 27 | 28 | - name: Run tests 29 | run: mvn test 30 | env: 31 | IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }} 32 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Continent.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class Continent { 4 | private final String code; 5 | private final String name; 6 | 7 | public Continent( 8 | String code, 9 | String name 10 | ) { 11 | this.code = code; 12 | this.name = name; 13 | } 14 | 15 | public String getCode() { 16 | return code; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "Continent{" + 26 | "code='" + code + '\'' + 27 | ",name='" + name + '\'' + 28 | '}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/CountryCurrency.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class CountryCurrency { 4 | private final String code; 5 | private final String symbol; 6 | 7 | public CountryCurrency( 8 | String code, 9 | String symbol 10 | ) { 11 | this.code = code; 12 | this.symbol = symbol; 13 | } 14 | 15 | public String getCode() { 16 | return code; 17 | } 18 | 19 | public String getSymbol() { 20 | return symbol; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "CountryCurrency{" + 26 | "code='" + code + '\'' + 27 | ",symbol='" + symbol + '\'' + 28 | '}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/CountryFlag.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class CountryFlag { 4 | private final String emoji; 5 | private final String unicode; 6 | 7 | public CountryFlag( 8 | String emoji, 9 | String unicode 10 | ) { 11 | this.emoji = emoji; 12 | this.unicode = unicode; 13 | } 14 | 15 | public String getEmoji() { 16 | return emoji; 17 | } 18 | 19 | public String getUnicode() { 20 | return unicode; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "CountryFlag{" + 26 | "emoji='" + emoji + '\'' + 27 | ",unicode='" + unicode + '\'' + 28 | '}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/MapResponse.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class MapResponse { 4 | private final String reportUrl; 5 | private final String status; 6 | 7 | public MapResponse( 8 | String reportUrl, 9 | String status 10 | ) { 11 | this.reportUrl = reportUrl; 12 | this.status = status; 13 | } 14 | 15 | public String getReportUrl() { 16 | return reportUrl; 17 | } 18 | 19 | public String getStatus() { 20 | return status; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "MapResponse{" + 26 | "reportUrl='" + reportUrl + '\'' + 27 | ", status=" + status + 28 | '}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Domains.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | import java.util.List; 4 | 5 | public class Domains { 6 | private final String total; 7 | private final List domains; 8 | 9 | public Domains( 10 | String total, 11 | List domains 12 | ) { 13 | this.total = total; 14 | this.domains = domains; 15 | } 16 | 17 | public String getTotal() { 18 | return total; 19 | } 20 | 21 | public List getDomains() { 22 | return domains; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "Domains{" + 28 | "total='" + total + '\'' + 29 | ",domains='" + domains + '\'' + 30 | '}'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scripts/ctags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Regenerate ctags. 4 | 5 | ctags \ 6 | --recurse=yes \ 7 | --exclude=node_modules \ 8 | --exclude=dist \ 9 | --exclude=build \ 10 | --exclude=target \ 11 | -f .vim/tags \ 12 | --tag-relative=never \ 13 | --totals=yes \ 14 | ./src 15 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/cache/Cache.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.cache; 2 | 3 | import io.ipinfo.api.model.ASNResponse; 4 | import io.ipinfo.api.model.IPResponse; 5 | 6 | public interface Cache { 7 | /** 8 | * Gets an arbitrary object stored in cache. 9 | * 10 | * @param key the key that maps to the object. 11 | * @return the value mapped to from `key`. 12 | */ 13 | Object get(String key); 14 | 15 | /** 16 | * Sets a key/value pair in the cache. 17 | * 18 | * @param key the key. 19 | * @param val the value. 20 | * @return if caching was successful. 21 | */ 22 | boolean set(String key, Object val); 23 | 24 | /** 25 | * Clears all entries in the cache. 26 | * 27 | * @return if clear was successful. 28 | */ 29 | boolean clear(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Carrier.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | 4 | public class Carrier { 5 | private final String name; 6 | private final String mcc; 7 | private final String mnc; 8 | 9 | public Carrier( 10 | String name, 11 | String mcc, 12 | String mnc 13 | ) { 14 | this.name = name; 15 | this.mcc = mcc; 16 | this.mnc = mnc; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public String getMcc() { 24 | return mcc; 25 | } 26 | 27 | public String getMnc() { 28 | return mnc; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "Carrier{" + 34 | "name='" + name + '\'' + 35 | ", mcc='" + mcc + '\'' + 36 | ", mnc='" + mnc + '\'' + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Company.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | 4 | public class Company { 5 | private final String name; 6 | private final String domain; 7 | private final String type; 8 | 9 | public Company( 10 | String name, 11 | String domain, 12 | String type 13 | ) { 14 | this.name = name; 15 | this.domain = domain; 16 | this.type = type; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public String getDomain() { 24 | return domain; 25 | } 26 | 27 | public String getType() { 28 | return type; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "Company{" + 34 | "name='" + name + '\'' + 35 | ", domain='" + domain + '\'' + 36 | ", type='" + type + '\'' + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Anonymous.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class Anonymous { 4 | private final Boolean is_proxy; 5 | private final Boolean is_relay; 6 | private final Boolean is_tor; 7 | private final Boolean is_vpn; 8 | 9 | public Anonymous( 10 | Boolean is_proxy, 11 | Boolean is_relay, 12 | Boolean is_tor, 13 | Boolean is_vpn 14 | ) { 15 | this.is_proxy = is_proxy; 16 | this.is_relay = is_relay; 17 | this.is_tor = is_tor; 18 | this.is_vpn = is_vpn; 19 | } 20 | 21 | public Boolean getIsProxy() { 22 | return is_proxy; 23 | } 24 | 25 | public Boolean getIsRelay() { 26 | return is_relay; 27 | } 28 | 29 | public Boolean getIsTor() { 30 | return is_tor; 31 | } 32 | 33 | public Boolean getIsVpn() { 34 | return is_vpn; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "Anonymous{" + 40 | "is_proxy=" + is_proxy + 41 | ", is_relay=" + is_relay + 42 | ", is_tor=" + is_tor + 43 | ", is_vpn=" + is_vpn + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/request/ASNRequest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.request; 2 | 3 | import io.ipinfo.api.errors.ErrorResponseException; 4 | import io.ipinfo.api.errors.RateLimitedException; 5 | import io.ipinfo.api.model.ASNResponse; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | 10 | 11 | public class ASNRequest extends BaseRequest { 12 | private final static String URL_FORMAT = "https://ipinfo.io/%s"; 13 | private final String asn; 14 | 15 | public ASNRequest(OkHttpClient client, String token, String asn) { 16 | super(client, token); 17 | this.asn = asn; 18 | } 19 | 20 | @Override 21 | public ASNResponse handle() throws RateLimitedException { 22 | String url = String.format(URL_FORMAT, asn); 23 | Request.Builder request = new Request.Builder().url(url).get(); 24 | 25 | try (Response response = handleRequest(request)) { 26 | if (response == null || response.body() == null) { 27 | return null; 28 | } 29 | 30 | try { 31 | return gson.fromJson(response.body().string(), ASNResponse.class); 32 | } catch (Exception ex) { 33 | throw new ErrorResponseException(ex); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/request/MapRequest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.request; 2 | 3 | import io.ipinfo.api.errors.ErrorResponseException; 4 | import io.ipinfo.api.errors.RateLimitedException; 5 | import io.ipinfo.api.model.MapResponse; 6 | import okhttp3.*; 7 | 8 | import java.util.List; 9 | 10 | public class MapRequest extends BaseRequest { 11 | private final static String URL = "https://ipinfo.io/tools/map"; 12 | private final List ips; 13 | 14 | public MapRequest(OkHttpClient client, String token, List ips) { 15 | super(client, token); 16 | this.ips = ips; 17 | } 18 | 19 | @Override 20 | public MapResponse handle() throws RateLimitedException { 21 | String jsonIpList = gson.toJson(ips); 22 | RequestBody requestBody = RequestBody.create(null, jsonIpList); 23 | Request.Builder request = new Request.Builder().url(URL).post(requestBody); 24 | 25 | try (Response response = handleRequest(request)) { 26 | if (response == null || response.body() == null) { 27 | return null; 28 | } 29 | 30 | try { 31 | return gson.fromJson(response.body().string(), MapResponse.class); 32 | } catch (Exception ex) { 33 | throw new ErrorResponseException(ex); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/ASN.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | 4 | public class ASN { 5 | private final String asn; 6 | private final String name; 7 | private final String domain; 8 | private final String route; 9 | private final String type; 10 | 11 | public ASN( 12 | String asn, 13 | String name, 14 | String domain, 15 | String route, 16 | String type 17 | ) { 18 | this.asn = asn; 19 | this.name = name; 20 | this.domain = domain; 21 | this.route = route; 22 | this.type = type; 23 | } 24 | 25 | public String getAsn() { 26 | return asn; 27 | } 28 | 29 | public String getName() { 30 | 31 | return name; 32 | } 33 | 34 | public String getDomain() { 35 | return domain; 36 | } 37 | 38 | public String getRoute() { 39 | return route; 40 | } 41 | 42 | public String getType() { 43 | return type; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "ASN{" + 49 | "asn='" + asn + '\'' + 50 | ", name='" + name + '\'' + 51 | ", domain='" + domain + '\'' + 52 | ", route='" + route + '\'' + 53 | ", type='" + type + '\'' + 54 | '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/ASNPlus.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class ASNPlus { 4 | private final String asn; 5 | private final String name; 6 | private final String domain; 7 | private final String type; 8 | private final String last_changed; 9 | 10 | public ASNPlus( 11 | String asn, 12 | String name, 13 | String domain, 14 | String type, 15 | String last_changed 16 | ) { 17 | this.asn = asn; 18 | this.name = name; 19 | this.domain = domain; 20 | this.type = type; 21 | this.last_changed = last_changed; 22 | } 23 | 24 | public String getAsn() { 25 | return asn; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public String getDomain() { 33 | return domain; 34 | } 35 | 36 | public String getType() { 37 | return type; 38 | } 39 | 40 | public String getLastChanged() { 41 | return last_changed; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "ASNPlus{" + 47 | "asn='" + asn + '\'' + 48 | ", name='" + name + '\'' + 49 | ", domain='" + domain + '\'' + 50 | ", type='" + type + '\'' + 51 | ", last_changed='" + last_changed + '\'' + 52 | '}'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/request/BaseRequest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.request; 2 | 3 | import com.google.gson.Gson; 4 | import io.ipinfo.api.errors.ErrorResponseException; 5 | import io.ipinfo.api.errors.RateLimitedException; 6 | import okhttp3.Credentials; 7 | import okhttp3.OkHttpClient; 8 | import okhttp3.Request; 9 | import okhttp3.Response; 10 | 11 | public abstract class BaseRequest { 12 | protected final static Gson gson = new Gson(); 13 | private final OkHttpClient client; 14 | private final String token; 15 | 16 | protected BaseRequest(OkHttpClient client, String token) { 17 | this.client = client; 18 | this.token = token; 19 | } 20 | 21 | public abstract T handle() throws RateLimitedException; 22 | 23 | public Response handleRequest(Request.Builder request) throws RateLimitedException { 24 | request 25 | .addHeader("Authorization", Credentials.basic(token, "")) 26 | .addHeader("user-agent", "IPinfoClient/Java/3.2.0") 27 | .addHeader("Content-Type", "application/json"); 28 | 29 | Response response; 30 | 31 | try { 32 | response = client.newCall(request.build()).execute(); 33 | } catch (Exception e) { 34 | throw new ErrorResponseException(e); 35 | } 36 | 37 | // Sanity check 38 | if (response == null) { 39 | return null; 40 | } 41 | 42 | if (response.code() == 429) { 43 | throw new RateLimitedException(); 44 | } 45 | 46 | return response; 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Privacy.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class Privacy { 4 | private final boolean vpn; 5 | private final boolean proxy; 6 | private final boolean tor; 7 | private final boolean relay; 8 | private final boolean hosting; 9 | private final String service; 10 | 11 | public Privacy( 12 | boolean vpn, 13 | boolean proxy, 14 | boolean tor, 15 | boolean relay, 16 | boolean hosting, 17 | String service 18 | ) { 19 | this.vpn = vpn; 20 | this.proxy = proxy; 21 | this.tor = tor; 22 | this.relay = relay; 23 | this.hosting = hosting; 24 | this.service = service; 25 | } 26 | 27 | public boolean getVpn() { 28 | return vpn; 29 | } 30 | 31 | public boolean getProxy() { 32 | return proxy; 33 | } 34 | 35 | public boolean getTor() { 36 | return tor; 37 | } 38 | 39 | public boolean getRelay() { 40 | return relay; 41 | } 42 | 43 | public boolean getHosting() { 44 | return hosting; 45 | } 46 | 47 | public String getService() { 48 | return service; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "Privacy{" + 54 | "vpn=" + vpn + 55 | ", proxy=" + proxy + 56 | ", tor=" + tor + 57 | ", relay=" + relay + 58 | ", hosting=" + hosting + 59 | ", service=" + service + 60 | '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/request/IPRequestLite.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.request; 2 | 3 | import io.ipinfo.api.errors.ErrorResponseException; 4 | import io.ipinfo.api.errors.RateLimitedException; 5 | import io.ipinfo.api.model.IPResponseLite; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | 10 | public class IPRequestLite extends BaseRequest { 11 | private final static String URL_FORMAT = "https://api.ipinfo.io/lite/%s"; 12 | private final String ip; 13 | 14 | public IPRequestLite(OkHttpClient client, String token, String ip) { 15 | super(client, token); 16 | this.ip = ip; 17 | } 18 | 19 | @Override 20 | public IPResponseLite handle() throws RateLimitedException { 21 | if (IPRequest.isBogon(ip)) { 22 | try { 23 | return new IPResponseLite(ip, true); 24 | } catch (Exception ex) { 25 | throw new ErrorResponseException(ex); 26 | } 27 | } 28 | 29 | String url = String.format(URL_FORMAT, ip); 30 | Request.Builder request = new Request.Builder().url(url).get(); 31 | 32 | try (Response response = handleRequest(request)) { 33 | if (response == null || response.body() == null) { 34 | return null; 35 | } 36 | 37 | try { 38 | return gson.fromJson(response.body().string(), IPResponseLite.class); 39 | } catch (Exception ex) { 40 | throw new ErrorResponseException(ex); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/request/IPRequestCore.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.request; 2 | 3 | import io.ipinfo.api.errors.ErrorResponseException; 4 | import io.ipinfo.api.errors.RateLimitedException; 5 | import io.ipinfo.api.model.IPResponseCore; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | 10 | public class IPRequestCore extends BaseRequest { 11 | private final static String URL_FORMAT = "https://api.ipinfo.io/lookup/%s"; 12 | private final String ip; 13 | 14 | public IPRequestCore(OkHttpClient client, String token, String ip) { 15 | super(client, token); 16 | this.ip = ip; 17 | } 18 | 19 | @Override 20 | public IPResponseCore handle() throws RateLimitedException { 21 | if (IPRequest.isBogon(ip)) { 22 | try { 23 | return new IPResponseCore(ip, true); 24 | } catch (Exception ex) { 25 | throw new ErrorResponseException(ex); 26 | } 27 | } 28 | 29 | String url = String.format(URL_FORMAT, ip); 30 | Request.Builder request = new Request.Builder().url(url).get(); 31 | 32 | try (Response response = handleRequest(request)) { 33 | if (response == null || response.body() == null) { 34 | return null; 35 | } 36 | 37 | try { 38 | return gson.fromJson(response.body().string(), IPResponseCore.class); 39 | } catch (Exception ex) { 40 | throw new ErrorResponseException(ex); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/request/IPRequestPlus.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.request; 2 | 3 | import io.ipinfo.api.errors.ErrorResponseException; 4 | import io.ipinfo.api.errors.RateLimitedException; 5 | import io.ipinfo.api.model.IPResponsePlus; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | 10 | public class IPRequestPlus extends BaseRequest { 11 | private final static String URL_FORMAT = "https://api.ipinfo.io/lookup/%s"; 12 | private final String ip; 13 | 14 | public IPRequestPlus(OkHttpClient client, String token, String ip) { 15 | super(client, token); 16 | this.ip = ip; 17 | } 18 | 19 | @Override 20 | public IPResponsePlus handle() throws RateLimitedException { 21 | if (IPRequest.isBogon(ip)) { 22 | try { 23 | return new IPResponsePlus(ip, true); 24 | } catch (Exception ex) { 25 | throw new ErrorResponseException(ex); 26 | } 27 | } 28 | 29 | String url = String.format(URL_FORMAT, ip); 30 | Request.Builder request = new Request.Builder().url(url).get(); 31 | 32 | try (Response response = handleRequest(request)) { 33 | if (response == null || response.body() == null) { 34 | return null; 35 | } 36 | 37 | try { 38 | return gson.fromJson(response.body().string(), IPResponsePlus.class); 39 | } catch (Exception ex) { 40 | throw new ErrorResponseException(ex); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Abuse.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class Abuse { 4 | private final String address; 5 | private final String country; 6 | private final String email; 7 | private final String name; 8 | private final String network; 9 | private final String phone; 10 | 11 | public Abuse( 12 | String address, 13 | String country, 14 | String email, 15 | String name, 16 | String network, 17 | String phone 18 | ) { 19 | this.address = address; 20 | this.country = country; 21 | this.email = email; 22 | this.name = name; 23 | this.network = network; 24 | this.phone = phone; 25 | } 26 | 27 | public String getAddress() { 28 | return address; 29 | } 30 | 31 | public String getCountry() { 32 | return country; 33 | } 34 | 35 | public String getEmail() { 36 | return email; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public String getNetwork() { 44 | return network; 45 | } 46 | 47 | public String getPhone() { 48 | return phone; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "Abuse{" + 54 | "address='" + address + '\'' + 55 | ", country='" + country + '\'' + 56 | ", email='" + email + '\'' + 57 | ", name='" + name + '\'' + 58 | ", network='" + network + '\'' + 59 | ", phone='" + phone + '\'' + 60 | '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/cache/SimpleCache.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.cache; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.time.temporal.ChronoField; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class SimpleCache implements Cache { 10 | private final Duration duration; 11 | private final Map cache = new HashMap<>(); 12 | 13 | public SimpleCache(Duration duration) { 14 | this.duration = duration; 15 | } 16 | 17 | @Override 18 | public Object get(String key) { 19 | Payload payload = cache.get(key); 20 | if (payload == null || payload.hasExpired()) { 21 | return null; 22 | } 23 | 24 | return payload.data; 25 | } 26 | 27 | @Override 28 | public boolean set(String key, Object val) { 29 | cache.put(key, new Payload(val, duration)); 30 | return true; 31 | } 32 | 33 | @Override 34 | public boolean clear() { 35 | cache.clear(); 36 | return true; 37 | } 38 | 39 | private static class Payload { 40 | final Object data; 41 | final Instant creation; 42 | final Duration expiration; 43 | 44 | Payload(Object data, Duration duration) { 45 | this.data = data; 46 | creation = Instant.now(); 47 | this.expiration = duration; 48 | } 49 | 50 | public boolean hasExpired() { 51 | long time = expiration 52 | .addTo(creation) 53 | .getLong(ChronoField.INSTANT_SECONDS); 54 | long now = System.currentTimeMillis(); 55 | return now <= time; 56 | } 57 | 58 | public Object getData() { 59 | return data; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Prefix.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class Prefix { 4 | private final String netblock; 5 | private final String id; 6 | private final String name; 7 | private final String country; 8 | private final String size; 9 | private final String status; 10 | private final String domain; 11 | 12 | public Prefix( 13 | String netblock, 14 | String id, 15 | String name, 16 | String country, 17 | String size, 18 | String status, 19 | String domain 20 | ) { 21 | this.netblock = netblock; 22 | this.id = id; 23 | this.name = name; 24 | this.country = country; 25 | this.size = size; 26 | this.status = status; 27 | this.domain = domain; 28 | } 29 | 30 | public String getNetblock() { 31 | return netblock; 32 | } 33 | 34 | public String getId() { 35 | return id; 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public String getCountry() { 43 | return country; 44 | } 45 | 46 | public String getSize() { 47 | return size; 48 | } 49 | 50 | public String getStatus() { 51 | return status; 52 | } 53 | 54 | public String getDomain() { 55 | return domain; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "Prefix{" + 61 | "netblock='" + netblock + '\'' + 62 | ", id='" + id + '\'' + 63 | ", name='" + name + '\'' + 64 | ", country='" + country + '\'' + 65 | ", size='" + size + '\'' + 66 | ", status='" + status + '\'' + 67 | ", domain='" + domain + '\'' + 68 | '}'; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.2.0 2 | 3 | - Add support for IPinfo Core API 4 | - Add support for IPinfo Plus API 5 | 6 | # 3.1.0 7 | 8 | - Add support for IPinfo Lite API 9 | 10 | # 3.0.2 11 | 12 | - `IPResponse`: `GetAnycast` returns whether an IP is anycast, independent of underlying response field (`anycast` or `is_anycast`) 13 | 14 | # 3.0.1 15 | 16 | - `IPResponse`: reflect `anycast` response field being renamed to `is_anycast` 17 | - fix and improve tests 18 | 19 | # 3.0.0 20 | 21 | - Removed loading of country/continent/currency/EU-related data via files. This 22 | is done fully statically now. 23 | - IPinfo builder no longer supports functions `setContinentFile`, 24 | `setCountryCurrencyFile`, `setCountryFlagFile`, `setEUCountryFile`, 25 | `setCountryFile`. 26 | - IPinfo `Context` object no longer supports being initialized via input maps. 27 | 28 | # 2.2.2 29 | 30 | - Updated guava to vsn 32.1.2 31 | 32 | # 2.2.1 33 | 34 | - Updated vulnerable dependency; `com.google.guava` 35 | 36 | # 2.2.0 37 | 38 | - Added `isEU`, `CountryFlag`, `CountryCurrency` and `Continent` fields. 39 | - Checking bogon IP locally. 40 | - Upgraded `okhttp` to `4.10.0`. 41 | 42 | # 2.1.0 43 | 44 | - Added `Relay` and `Service` fields to `io.ipinfo.api.model.Privacy`. 45 | 46 | # 2.0.0 47 | 48 | Breaking changes: 49 | 50 | - `IPInfo` is renamed to `IPinfo`. 51 | - `IPInfoBuilder` is moved into `IPinfo.Builder`. 52 | - `ASNResponse.numIps` is now an `Integer` instead of a `String`. 53 | - The cache implementation now only uses `get` and `set`, which accept 54 | arbitrary strings, which may not necessarily be IP or ASN strings like 55 | "1.2.3.4" and "AS123". 56 | 57 | Additions: 58 | 59 | - `getBatch`, `getBatchIps` and `getBatchAsns` allow you to do lookups of 60 | multiple entities at once. There is no limit to the size of inputs on these 61 | library calls as long as your token has quota. 62 | - `getMap` will give you a map URL for https://ipinfo.io/tools/map given a list 63 | of IPs. 64 | - Many new pieces of data have been added that were previously missing. The new 65 | dataset reflects all the new data available via raw API calls. 66 | - The keys given to cache functions will now be versioned. `IPinfo.cacheKey` 67 | must be used to derive the correct key if doing manual lookups. 68 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/IPinfoCore.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api; 2 | 3 | import io.ipinfo.api.cache.Cache; 4 | import io.ipinfo.api.cache.SimpleCache; 5 | import io.ipinfo.api.context.Context; 6 | import io.ipinfo.api.errors.RateLimitedException; 7 | import io.ipinfo.api.model.IPResponseCore; 8 | import io.ipinfo.api.request.IPRequestCore; 9 | import java.time.Duration; 10 | import okhttp3.OkHttpClient; 11 | 12 | public class IPinfoCore { 13 | 14 | private final OkHttpClient client; 15 | private final Context context; 16 | private final String token; 17 | private final Cache cache; 18 | 19 | IPinfoCore( 20 | OkHttpClient client, 21 | Context context, 22 | String token, 23 | Cache cache 24 | ) { 25 | this.client = client; 26 | this.context = context; 27 | this.token = token; 28 | this.cache = cache; 29 | } 30 | 31 | public static void main(String[] args) throws RateLimitedException { 32 | System.out.println("Running IPinfo Core client"); 33 | } 34 | 35 | /** 36 | * Lookup IP information using the IP. This is a blocking call. 37 | * 38 | * @param ip IP address to query information for. 39 | * @return Response containing IP information. 40 | * @throws RateLimitedException if the user has exceeded the rate limit. 41 | */ 42 | public IPResponseCore lookupIP(String ip) throws RateLimitedException { 43 | IPRequestCore request = new IPRequestCore(client, token, ip); 44 | IPResponseCore response = request.handle(); 45 | 46 | if (response != null) { 47 | response.setContext(context); 48 | if (!response.getBogon()) { 49 | cache.set(cacheKey(ip), response); 50 | } 51 | } 52 | 53 | return response; 54 | } 55 | 56 | public static String cacheKey(String k) { 57 | return "core_" + k; 58 | } 59 | 60 | public static class Builder { 61 | 62 | private OkHttpClient client; 63 | private String token; 64 | private Cache cache; 65 | 66 | public Builder setClient(OkHttpClient client) { 67 | this.client = client; 68 | return this; 69 | } 70 | 71 | public Builder setToken(String token) { 72 | this.token = token; 73 | return this; 74 | } 75 | 76 | public Builder setCache(Cache cache) { 77 | this.cache = cache; 78 | return this; 79 | } 80 | 81 | public IPinfoCore build() { 82 | if (client == null) { 83 | client = new OkHttpClient(); 84 | } 85 | if (cache == null) { 86 | cache = new SimpleCache(Duration.ofDays(1)); 87 | } 88 | return new IPinfoCore(client, new Context(), token, cache); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/IPinfoPlus.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api; 2 | 3 | import io.ipinfo.api.cache.Cache; 4 | import io.ipinfo.api.cache.SimpleCache; 5 | import io.ipinfo.api.context.Context; 6 | import io.ipinfo.api.errors.RateLimitedException; 7 | import io.ipinfo.api.model.IPResponsePlus; 8 | import io.ipinfo.api.request.IPRequestPlus; 9 | import java.time.Duration; 10 | import okhttp3.OkHttpClient; 11 | 12 | public class IPinfoPlus { 13 | 14 | private final OkHttpClient client; 15 | private final Context context; 16 | private final String token; 17 | private final Cache cache; 18 | 19 | IPinfoPlus( 20 | OkHttpClient client, 21 | Context context, 22 | String token, 23 | Cache cache 24 | ) { 25 | this.client = client; 26 | this.context = context; 27 | this.token = token; 28 | this.cache = cache; 29 | } 30 | 31 | public static void main(String[] args) throws RateLimitedException { 32 | System.out.println("Running IPinfo Plus client"); 33 | } 34 | 35 | /** 36 | * Lookup IP information using the IP. This is a blocking call. 37 | * 38 | * @param ip IP address to query information for. 39 | * @return Response containing IP information. 40 | * @throws RateLimitedException if the user has exceeded the rate limit. 41 | */ 42 | public IPResponsePlus lookupIP(String ip) throws RateLimitedException { 43 | IPRequestPlus request = new IPRequestPlus(client, token, ip); 44 | IPResponsePlus response = request.handle(); 45 | 46 | if (response != null) { 47 | response.setContext(context); 48 | if (!response.getBogon()) { 49 | cache.set(cacheKey(ip), response); 50 | } 51 | } 52 | 53 | return response; 54 | } 55 | 56 | public static String cacheKey(String k) { 57 | return "plus_" + k; 58 | } 59 | 60 | public static class Builder { 61 | 62 | private OkHttpClient client; 63 | private String token; 64 | private Cache cache; 65 | 66 | public Builder setClient(OkHttpClient client) { 67 | this.client = client; 68 | return this; 69 | } 70 | 71 | public Builder setToken(String token) { 72 | this.token = token; 73 | return this; 74 | } 75 | 76 | public Builder setCache(Cache cache) { 77 | this.cache = cache; 78 | return this; 79 | } 80 | 81 | public IPinfoPlus build() { 82 | if (client == null) { 83 | client = new OkHttpClient(); 84 | } 85 | if (cache == null) { 86 | cache = new SimpleCache(Duration.ofDays(1)); 87 | } 88 | return new IPinfoPlus(client, new Context(), token, cache); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/Geo.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class Geo { 4 | private final String city; 5 | private final String region; 6 | private final String region_code; 7 | private final String country; 8 | private final String country_code; 9 | private final String continent; 10 | private final String continent_code; 11 | private final Double latitude; 12 | private final Double longitude; 13 | private final String timezone; 14 | private final String postal_code; 15 | 16 | public Geo( 17 | String city, 18 | String region, 19 | String region_code, 20 | String country, 21 | String country_code, 22 | String continent, 23 | String continent_code, 24 | Double latitude, 25 | Double longitude, 26 | String timezone, 27 | String postal_code 28 | ) { 29 | this.city = city; 30 | this.region = region; 31 | this.region_code = region_code; 32 | this.country = country; 33 | this.country_code = country_code; 34 | this.continent = continent; 35 | this.continent_code = continent_code; 36 | this.latitude = latitude; 37 | this.longitude = longitude; 38 | this.timezone = timezone; 39 | this.postal_code = postal_code; 40 | } 41 | 42 | public String getCity() { 43 | return city; 44 | } 45 | 46 | public String getRegion() { 47 | return region; 48 | } 49 | 50 | public String getRegionCode() { 51 | return region_code; 52 | } 53 | 54 | public String getCountry() { 55 | return country; 56 | } 57 | 58 | public String getCountryCode() { 59 | return country_code; 60 | } 61 | 62 | public String getContinent() { 63 | return continent; 64 | } 65 | 66 | public String getContinentCode() { 67 | return continent_code; 68 | } 69 | 70 | public Double getLatitude() { 71 | return latitude; 72 | } 73 | 74 | public Double getLongitude() { 75 | return longitude; 76 | } 77 | 78 | public String getTimezone() { 79 | return timezone; 80 | } 81 | 82 | public String getPostalCode() { 83 | return postal_code; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return "Geo{" + 89 | "city='" + city + '\'' + 90 | ", region='" + region + '\'' + 91 | ", region_code='" + region_code + '\'' + 92 | ", country='" + country + '\'' + 93 | ", country_code='" + country_code + '\'' + 94 | ", continent='" + continent + '\'' + 95 | ", continent_code='" + continent_code + '\'' + 96 | ", latitude=" + latitude + 97 | ", longitude=" + longitude + 98 | ", timezone='" + timezone + '\'' + 99 | ", postal_code='" + postal_code + '\'' + 100 | '}'; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/IPinfoLite.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api; 2 | 3 | import com.google.common.net.InetAddresses; 4 | import io.ipinfo.api.cache.Cache; 5 | import io.ipinfo.api.cache.SimpleCache; 6 | import io.ipinfo.api.context.Context; 7 | import io.ipinfo.api.errors.RateLimitedException; 8 | import io.ipinfo.api.model.IPResponseLite; 9 | import io.ipinfo.api.request.IPRequestLite; 10 | import java.io.IOException; 11 | import java.lang.reflect.Type; 12 | import java.time.Duration; 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.function.BiConsumer; 20 | import javax.annotation.ParametersAreNonnullByDefault; 21 | import okhttp3.*; 22 | 23 | public class IPinfoLite { 24 | 25 | private final OkHttpClient client; 26 | private final Context context; 27 | private final String token; 28 | private final Cache cache; 29 | 30 | IPinfoLite( 31 | OkHttpClient client, 32 | Context context, 33 | String token, 34 | Cache cache 35 | ) { 36 | this.client = client; 37 | this.context = context; 38 | this.token = token; 39 | this.cache = cache; 40 | } 41 | 42 | public static void main(String[] args) throws RateLimitedException { 43 | System.out.println("Running IPinfo Lite client"); 44 | } 45 | 46 | /** 47 | * Lookup IP information using the IP. This is a blocking call. 48 | * 49 | * @param ip IP address to query information for. 50 | * @return Response containing IP information. 51 | * @throws RateLimitedException if the user has exceeded the rate limit. 52 | */ 53 | public IPResponseLite lookupIP(String ip) throws RateLimitedException { 54 | IPRequestLite request = new IPRequestLite(client, token, ip); 55 | IPResponseLite response = request.handle(); 56 | 57 | if (response != null) { 58 | response.setContext(context); 59 | if (!response.getBogon()) { 60 | cache.set(cacheKey(ip), response); 61 | } 62 | } 63 | 64 | return response; 65 | } 66 | 67 | public static String cacheKey(String k) { 68 | return "lite_" + k; 69 | } 70 | 71 | public static class Builder { 72 | 73 | private OkHttpClient client; 74 | private String token; 75 | private Cache cache; 76 | 77 | public Builder setClient(OkHttpClient client) { 78 | this.client = client; 79 | return this; 80 | } 81 | 82 | public Builder setToken(String token) { 83 | this.token = token; 84 | return this; 85 | } 86 | 87 | public Builder setCache(Cache cache) { 88 | this.cache = cache; 89 | return this; 90 | } 91 | 92 | public IPinfoLite build() { 93 | if (client == null) { 94 | client = new OkHttpClient(); 95 | } 96 | if (cache == null) { 97 | cache = new SimpleCache(Duration.ofDays(1)); 98 | } 99 | return new IPinfoLite(client, new Context(), token, cache); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/GeoPlus.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | public class GeoPlus { 4 | private final String city; 5 | private final String region; 6 | private final String region_code; 7 | private final String country; 8 | private final String country_code; 9 | private final String continent; 10 | private final String continent_code; 11 | private final Double latitude; 12 | private final Double longitude; 13 | private final String timezone; 14 | private final String postal_code; 15 | private final String dma_code; 16 | private final String geoname_id; 17 | private final Integer radius; 18 | private final String last_changed; 19 | 20 | public GeoPlus( 21 | String city, 22 | String region, 23 | String region_code, 24 | String country, 25 | String country_code, 26 | String continent, 27 | String continent_code, 28 | Double latitude, 29 | Double longitude, 30 | String timezone, 31 | String postal_code, 32 | String dma_code, 33 | String geoname_id, 34 | Integer radius, 35 | String last_changed 36 | ) { 37 | this.city = city; 38 | this.region = region; 39 | this.region_code = region_code; 40 | this.country = country; 41 | this.country_code = country_code; 42 | this.continent = continent; 43 | this.continent_code = continent_code; 44 | this.latitude = latitude; 45 | this.longitude = longitude; 46 | this.timezone = timezone; 47 | this.postal_code = postal_code; 48 | this.dma_code = dma_code; 49 | this.geoname_id = geoname_id; 50 | this.radius = radius; 51 | this.last_changed = last_changed; 52 | } 53 | 54 | public String getCity() { 55 | return city; 56 | } 57 | 58 | public String getRegion() { 59 | return region; 60 | } 61 | 62 | public String getRegionCode() { 63 | return region_code; 64 | } 65 | 66 | public String getCountry() { 67 | return country; 68 | } 69 | 70 | public String getCountryCode() { 71 | return country_code; 72 | } 73 | 74 | public String getContinent() { 75 | return continent; 76 | } 77 | 78 | public String getContinentCode() { 79 | return continent_code; 80 | } 81 | 82 | public Double getLatitude() { 83 | return latitude; 84 | } 85 | 86 | public Double getLongitude() { 87 | return longitude; 88 | } 89 | 90 | public String getTimezone() { 91 | return timezone; 92 | } 93 | 94 | public String getPostalCode() { 95 | return postal_code; 96 | } 97 | 98 | public String getDmaCode() { 99 | return dma_code; 100 | } 101 | 102 | public String getGeonameId() { 103 | return geoname_id; 104 | } 105 | 106 | public Integer getRadius() { 107 | return radius; 108 | } 109 | 110 | public String getLastChanged() { 111 | return last_changed; 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return "GeoPlus{" + 117 | "city='" + city + '\'' + 118 | ", region='" + region + '\'' + 119 | ", region_code='" + region_code + '\'' + 120 | ", country='" + country + '\'' + 121 | ", country_code='" + country_code + '\'' + 122 | ", continent='" + continent + '\'' + 123 | ", continent_code='" + continent_code + '\'' + 124 | ", latitude=" + latitude + 125 | ", longitude=" + longitude + 126 | ", timezone='" + timezone + '\'' + 127 | ", postal_code='" + postal_code + '\'' + 128 | ", dma_code='" + dma_code + '\'' + 129 | ", geoname_id='" + geoname_id + '\'' + 130 | ", radius=" + radius + 131 | ", last_changed='" + last_changed + '\'' + 132 | '}'; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/ASNResponse.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import io.ipinfo.api.context.Context; 5 | 6 | import java.util.List; 7 | 8 | public class ASNResponse { 9 | private final String asn; 10 | private final String name; 11 | private final String country; 12 | private final String allocated; 13 | private final String registry; 14 | private final String domain; 15 | @SerializedName("num_ips") 16 | private final Integer numIps; 17 | private final String type; 18 | private final List prefixes; 19 | private final List prefixes6; 20 | private final List peers; 21 | private final List upstreams; 22 | private final List downstreams; 23 | private transient Context context; 24 | 25 | public ASNResponse( 26 | String asn, 27 | String name, 28 | String country, 29 | String allocated, 30 | String registry, 31 | String domain, 32 | Integer numIps, 33 | String type, 34 | List prefixes, 35 | List prefixes6, 36 | List peers, 37 | List upstreams, 38 | List downstreams 39 | ) { 40 | this.asn = asn; 41 | this.name = name; 42 | this.country = country; 43 | this.allocated = allocated; 44 | this.registry = registry; 45 | this.domain = domain; 46 | this.numIps = numIps; 47 | this.type = type; 48 | this.prefixes = prefixes; 49 | this.prefixes6 = prefixes6; 50 | this.peers = peers; 51 | this.upstreams = upstreams; 52 | this.downstreams = downstreams; 53 | } 54 | 55 | /** 56 | * Set by the library for extra utility functions 57 | * 58 | * @param context for country information 59 | */ 60 | public void setContext(Context context) { 61 | this.context = context; 62 | } 63 | 64 | public String getAsn() { 65 | return asn; 66 | } 67 | 68 | public String getName() { 69 | return name; 70 | } 71 | 72 | public String getCountry() { 73 | return country; 74 | } 75 | 76 | public String getCountryCode() { 77 | return country; 78 | } 79 | 80 | public String getCountryName() { 81 | return context.getCountryName(getCountryCode()); 82 | } 83 | 84 | public String getAllocated() { 85 | return allocated; 86 | } 87 | 88 | public String getRegistry() { 89 | return registry; 90 | } 91 | 92 | public String getDomain() { 93 | return domain; 94 | } 95 | 96 | public Integer getNumIps() { 97 | return numIps; 98 | } 99 | 100 | public String getType() { 101 | return type; 102 | } 103 | 104 | public List getPrefixes() { 105 | return prefixes; 106 | } 107 | 108 | public List getPrefixes6() { 109 | return prefixes6; 110 | } 111 | 112 | public List getPeers() { 113 | return peers; 114 | } 115 | 116 | public List getUpstreams() { 117 | return upstreams; 118 | } 119 | 120 | public List getDownstreams() { 121 | return downstreams; 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return "ASNResponse{" + 127 | "asn='" + asn + '\'' + 128 | ", name='" + name + '\'' + 129 | ", country='" + country + '\'' + 130 | ", allocated='" + allocated + '\'' + 131 | ", registry='" + registry + '\'' + 132 | ", domain='" + domain + '\'' + 133 | ", numIps='" + numIps + '\'' + 134 | ", type='" + type + '\'' + 135 | ", prefixes=" + prefixes + 136 | ", prefixes6=" + prefixes6 + 137 | ", peers=" + peers + 138 | ", upstreams=" + upstreams + 139 | ", downstreams=" + downstreams + 140 | '}'; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/IPResponseLite.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | import io.ipinfo.api.context.Context; 4 | 5 | public class IPResponseLite { 6 | private final String ip; 7 | private final String asn; 8 | private final String as_name; 9 | private final String as_domain; 10 | private final String country_code; 11 | private final String country; 12 | private final String continent_code; 13 | private final String continent; 14 | private final boolean bogon; 15 | private transient Context context; 16 | 17 | public IPResponseLite( 18 | String ip, 19 | String asn, 20 | String as_name, 21 | String as_domain, 22 | String country_code, 23 | String country, 24 | String continent_code, 25 | String continent 26 | ) { 27 | this.ip = ip; 28 | this.asn = asn; 29 | this.as_name = as_name; 30 | this.as_domain = as_domain; 31 | this.country_code = country_code; 32 | this.country = country; 33 | this.continent_code = continent_code; 34 | this.continent = continent; 35 | this.bogon = false; 36 | } 37 | 38 | public IPResponseLite(String ip, boolean bogon) { 39 | this.ip = ip; 40 | this.bogon = bogon; 41 | this.asn = null; 42 | this.as_name = null; 43 | this.as_domain = null; 44 | this.country_code = null; 45 | this.country = null; 46 | this.continent_code = null; 47 | this.continent = null; 48 | } 49 | 50 | /** 51 | * Set by the library for extra utility functions 52 | * 53 | * @param context for country information 54 | */ 55 | public void setContext(Context context) { 56 | this.context = context; 57 | } 58 | 59 | public String getIp() { 60 | return ip; 61 | } 62 | 63 | public String getAsn() { 64 | return asn; 65 | } 66 | 67 | public String getAsName() { 68 | return as_name; 69 | } 70 | 71 | public String getAsDomain() { 72 | return as_domain; 73 | } 74 | 75 | public String getCountryCode() { 76 | return country_code; 77 | } 78 | 79 | public String getCountry() { 80 | return country; 81 | } 82 | 83 | public String getCountryName() { 84 | return context != null ? context.getCountryName(getCountryCode()) : country; 85 | } 86 | 87 | public String getContinentCode() { 88 | return continent_code; 89 | } 90 | 91 | public String getContinent() { 92 | return continent; 93 | } 94 | 95 | public boolean getBogon() { 96 | return bogon; 97 | } 98 | 99 | public Boolean isEU() { 100 | return context != null ? context.isEU(getCountryCode()) : null; 101 | } 102 | 103 | public CountryFlag getCountryFlag() { 104 | return context != null ? context.getCountryFlag(getCountryCode()) : null; 105 | } 106 | 107 | public String getCountryFlagURL() { 108 | return context != null ? context.getCountryFlagURL(getCountryCode()) : null; 109 | } 110 | 111 | public CountryCurrency getCountryCurrency() { 112 | return context != null ? context.getCountryCurrency(getCountryCode()) : null; 113 | } 114 | 115 | public Continent getContinentInfo() { 116 | return context != null ? context.getContinent(getCountryCode()) : null; 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | return (bogon ? 122 | "IPResponseLite{" + 123 | "ip='" + ip + '\'' + 124 | ", bogon=" + bogon + 125 | "}" 126 | : 127 | "IPResponseLite{" + 128 | "ip='" + ip + '\'' + 129 | ", asn='" + asn + '\'' + 130 | ", as_name='" + as_name + '\'' + 131 | ", as_domain='" + as_domain + '\'' + 132 | ", country_code='" + country_code + '\'' + 133 | ", country='" + country + '\'' + 134 | ", continent_code='" + continent_code + '\'' + 135 | ", continent='" + continent + '\'' + 136 | '}'); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/IPResponseCore.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import io.ipinfo.api.context.Context; 5 | 6 | public class IPResponseCore { 7 | private final String ip; 8 | private final Geo geo; 9 | @SerializedName("as") 10 | private final ASN asn; 11 | private final Boolean is_anonymous; 12 | private final Boolean is_anycast; 13 | private final Boolean is_hosting; 14 | private final Boolean is_mobile; 15 | private final Boolean is_satellite; 16 | private final boolean bogon; 17 | private transient Context context; 18 | 19 | public IPResponseCore( 20 | String ip, 21 | Geo geo, 22 | ASN asn, 23 | Boolean is_anonymous, 24 | Boolean is_anycast, 25 | Boolean is_hosting, 26 | Boolean is_mobile, 27 | Boolean is_satellite 28 | ) { 29 | this.ip = ip; 30 | this.geo = geo; 31 | this.asn = asn; 32 | this.is_anonymous = is_anonymous; 33 | this.is_anycast = is_anycast; 34 | this.is_hosting = is_hosting; 35 | this.is_mobile = is_mobile; 36 | this.is_satellite = is_satellite; 37 | this.bogon = false; 38 | } 39 | 40 | public IPResponseCore(String ip, boolean bogon) { 41 | this.ip = ip; 42 | this.bogon = bogon; 43 | this.geo = null; 44 | this.asn = null; 45 | this.is_anonymous = null; 46 | this.is_anycast = null; 47 | this.is_hosting = null; 48 | this.is_mobile = null; 49 | this.is_satellite = null; 50 | } 51 | 52 | /** 53 | * Set by the library for extra utility functions 54 | * 55 | * @param context for country information 56 | */ 57 | public void setContext(Context context) { 58 | this.context = context; 59 | } 60 | 61 | public String getIp() { 62 | return ip; 63 | } 64 | 65 | public Geo getGeo() { 66 | return geo; 67 | } 68 | 69 | public ASN getAsn() { 70 | return asn; 71 | } 72 | 73 | public Boolean getIsAnonymous() { 74 | return is_anonymous; 75 | } 76 | 77 | public Boolean getIsAnycast() { 78 | return is_anycast; 79 | } 80 | 81 | public Boolean getIsHosting() { 82 | return is_hosting; 83 | } 84 | 85 | public Boolean getIsMobile() { 86 | return is_mobile; 87 | } 88 | 89 | public Boolean getIsSatellite() { 90 | return is_satellite; 91 | } 92 | 93 | public boolean getBogon() { 94 | return bogon; 95 | } 96 | 97 | public String getCountryName() { 98 | return context != null && geo != null ? context.getCountryName(geo.getCountryCode()) : (geo != null ? geo.getCountry() : null); 99 | } 100 | 101 | public Boolean isEU() { 102 | return context != null && geo != null ? context.isEU(geo.getCountryCode()) : null; 103 | } 104 | 105 | public CountryFlag getCountryFlag() { 106 | return context != null && geo != null ? context.getCountryFlag(geo.getCountryCode()) : null; 107 | } 108 | 109 | public String getCountryFlagURL() { 110 | return context != null && geo != null ? context.getCountryFlagURL(geo.getCountryCode()) : null; 111 | } 112 | 113 | public CountryCurrency getCountryCurrency() { 114 | return context != null && geo != null ? context.getCountryCurrency(geo.getCountryCode()) : null; 115 | } 116 | 117 | public Continent getContinentInfo() { 118 | return context != null && geo != null ? context.getContinent(geo.getCountryCode()) : null; 119 | } 120 | 121 | @Override 122 | public String toString() { 123 | if (bogon) { 124 | return "IPResponseCore{" + 125 | "ip='" + ip + '\'' + 126 | ", bogon=" + bogon + 127 | '}'; 128 | } 129 | return "IPResponseCore{" + 130 | "ip='" + ip + '\'' + 131 | ", geo=" + geo + 132 | ", asn=" + asn + 133 | ", is_anonymous=" + is_anonymous + 134 | ", is_anycast=" + is_anycast + 135 | ", is_hosting=" + is_hosting + 136 | ", is_mobile=" + is_mobile + 137 | ", is_satellite=" + is_satellite + 138 | '}'; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | io.ipinfo 9 | ipinfo-api 10 | 3.2.0 11 | jar 12 | 13 | 14 | IPinfo 15 | 16 | 17 | 18 | UTF-8 19 | 20 | 21 | IPinfo-API 22 | 23 | https://github.com/ipinfo/java 24 | Java wrapper for the IPinfo API 25 | 26 | 27 | GitHub 28 | https://github.com/ipinfo/java/issues 29 | 30 | 31 | 32 | 33 | Apache 2.0 34 | https://www.apache.org/licenses/LICENSE-2.0 35 | repo 36 | 37 | 38 | 39 | 40 | git@github.com:ipinfo/java.git 41 | scm:git:git@github.com:ipinfo/java.git 42 | scm:git:git@github.com:ipinfo/java.git 43 | HEAD 44 | 45 | 46 | 47 | 48 | ipinfo 49 | IPinfo 50 | support@ipinfo.io 51 | IPinfo 52 | 53 | 54 | 55 | 56 | 57 | com.google.code.gson 58 | gson 59 | 2.9.0 60 | compile 61 | 62 | 63 | com.squareup.okhttp3 64 | okhttp 65 | 4.10.0 66 | compile 67 | 68 | 69 | com.google.guava 70 | guava 71 | 32.1.2-jre 72 | compile 73 | 74 | 75 | org.junit.jupiter 76 | junit-jupiter-api 77 | 5.2.0 78 | test 79 | 80 | 81 | org.junit.jupiter 82 | junit-jupiter-engine 83 | 5.2.0 84 | test 85 | 86 | 87 | 88 | 89 | 90 | release 91 | 92 | 93 | 94 | org.sonatype.central 95 | central-publishing-maven-plugin 96 | 0.7.0 97 | true 98 | 99 | central 100 | true 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-source-plugin 107 | 3.3.1 108 | 109 | 110 | attach-sources 111 | 112 | jar-no-fork 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-javadoc-plugin 121 | 3.11.2 122 | 123 | 124 | attach-javadocs 125 | 126 | jar 127 | 128 | 129 | 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-gpg-plugin 135 | 3.2.7 136 | 137 | 138 | sign-artifacts 139 | verify 140 | 141 | sign 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/IPResponsePlus.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import io.ipinfo.api.context.Context; 5 | 6 | public class IPResponsePlus { 7 | private final String ip; 8 | private final String hostname; 9 | private final GeoPlus geo; 10 | @SerializedName("as") 11 | private final ASNPlus asn; 12 | private final Mobile mobile; 13 | private final Anonymous anonymous; 14 | private final Abuse abuse; 15 | private final Company company; 16 | private final Privacy privacy; 17 | private final Domains domains; 18 | private final Boolean is_anonymous; 19 | private final Boolean is_anycast; 20 | private final Boolean is_hosting; 21 | private final Boolean is_mobile; 22 | private final Boolean is_satellite; 23 | private final boolean bogon; 24 | private transient Context context; 25 | 26 | public IPResponsePlus( 27 | String ip, 28 | String hostname, 29 | GeoPlus geo, 30 | ASNPlus asn, 31 | Mobile mobile, 32 | Anonymous anonymous, 33 | Abuse abuse, 34 | Company company, 35 | Privacy privacy, 36 | Domains domains, 37 | Boolean is_anonymous, 38 | Boolean is_anycast, 39 | Boolean is_hosting, 40 | Boolean is_mobile, 41 | Boolean is_satellite 42 | ) { 43 | this.ip = ip; 44 | this.hostname = hostname; 45 | this.geo = geo; 46 | this.asn = asn; 47 | this.mobile = mobile; 48 | this.anonymous = anonymous; 49 | this.abuse = abuse; 50 | this.company = company; 51 | this.privacy = privacy; 52 | this.domains = domains; 53 | this.is_anonymous = is_anonymous; 54 | this.is_anycast = is_anycast; 55 | this.is_hosting = is_hosting; 56 | this.is_mobile = is_mobile; 57 | this.is_satellite = is_satellite; 58 | this.bogon = false; 59 | } 60 | 61 | public IPResponsePlus(String ip, boolean bogon) { 62 | this.ip = ip; 63 | this.bogon = bogon; 64 | this.hostname = null; 65 | this.geo = null; 66 | this.asn = null; 67 | this.mobile = null; 68 | this.anonymous = null; 69 | this.abuse = null; 70 | this.company = null; 71 | this.privacy = null; 72 | this.domains = null; 73 | this.is_anonymous = null; 74 | this.is_anycast = null; 75 | this.is_hosting = null; 76 | this.is_mobile = null; 77 | this.is_satellite = null; 78 | } 79 | 80 | /** 81 | * Set by the library for extra utility functions 82 | * 83 | * @param context for country information 84 | */ 85 | public void setContext(Context context) { 86 | this.context = context; 87 | } 88 | 89 | public String getIp() { 90 | return ip; 91 | } 92 | 93 | public String getHostname() { 94 | return hostname; 95 | } 96 | 97 | public GeoPlus getGeo() { 98 | return geo; 99 | } 100 | 101 | public ASNPlus getAsn() { 102 | return asn; 103 | } 104 | 105 | public Mobile getMobile() { 106 | return mobile; 107 | } 108 | 109 | public Anonymous getAnonymous() { 110 | return anonymous; 111 | } 112 | 113 | public Abuse getAbuse() { 114 | return abuse; 115 | } 116 | 117 | public Company getCompany() { 118 | return company; 119 | } 120 | 121 | public Privacy getPrivacy() { 122 | return privacy; 123 | } 124 | 125 | public Domains getDomains() { 126 | return domains; 127 | } 128 | 129 | public Boolean getIsAnonymous() { 130 | return is_anonymous; 131 | } 132 | 133 | public Boolean getIsAnycast() { 134 | return is_anycast; 135 | } 136 | 137 | public Boolean getIsHosting() { 138 | return is_hosting; 139 | } 140 | 141 | public Boolean getIsMobile() { 142 | return is_mobile; 143 | } 144 | 145 | public Boolean getIsSatellite() { 146 | return is_satellite; 147 | } 148 | 149 | public boolean getBogon() { 150 | return bogon; 151 | } 152 | 153 | public String getCountryName() { 154 | return context != null && geo != null ? context.getCountryName(geo.getCountryCode()) : (geo != null ? geo.getCountry() : null); 155 | } 156 | 157 | public Boolean isEU() { 158 | return context != null && geo != null ? context.isEU(geo.getCountryCode()) : null; 159 | } 160 | 161 | public CountryFlag getCountryFlag() { 162 | return context != null && geo != null ? context.getCountryFlag(geo.getCountryCode()) : null; 163 | } 164 | 165 | public String getCountryFlagURL() { 166 | return context != null && geo != null ? context.getCountryFlagURL(geo.getCountryCode()) : null; 167 | } 168 | 169 | public CountryCurrency getCountryCurrency() { 170 | return context != null && geo != null ? context.getCountryCurrency(geo.getCountryCode()) : null; 171 | } 172 | 173 | public Continent getContinentInfo() { 174 | return context != null && geo != null ? context.getContinent(geo.getCountryCode()) : null; 175 | } 176 | 177 | @Override 178 | public String toString() { 179 | if (bogon) { 180 | return "IPResponsePlus{" + 181 | "ip='" + ip + '\'' + 182 | ", bogon=" + bogon + 183 | '}'; 184 | } 185 | return "IPResponsePlus{" + 186 | "ip='" + ip + '\'' + 187 | ", hostname='" + hostname + '\'' + 188 | ", geo=" + geo + 189 | ", asn=" + asn + 190 | ", mobile=" + mobile + 191 | ", anonymous=" + anonymous + 192 | ", abuse=" + abuse + 193 | ", company=" + company + 194 | ", privacy=" + privacy + 195 | ", domains=" + domains + 196 | ", is_anonymous=" + is_anonymous + 197 | ", is_anycast=" + is_anycast + 198 | ", is_hosting=" + is_hosting + 199 | ", is_mobile=" + is_mobile + 200 | ", is_satellite=" + is_satellite + 201 | '}'; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/model/IPResponse.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.model; 2 | 3 | import io.ipinfo.api.context.Context; 4 | 5 | public class IPResponse { 6 | private final String ip; 7 | private final String hostname; 8 | private final boolean bogon; 9 | 10 | // IPinfo's IP response indicates whether an IP is anycast, using either 11 | // `anycast` or `is_anycast` fields, depending on the token's capabilities. 12 | private final boolean anycast; 13 | private final boolean is_anycast; 14 | 15 | private final String city; 16 | private final String region; 17 | private final String country; 18 | private final String loc; 19 | private final String org; 20 | private final String postal; 21 | private final String timezone; 22 | private final ASN asn; 23 | private final Company company; 24 | private final Carrier carrier; 25 | private final Privacy privacy; 26 | private final Abuse abuse; 27 | private final Domains domains; 28 | private transient Context context; 29 | 30 | public IPResponse( 31 | String ip, 32 | String hostname, 33 | boolean bogon, 34 | boolean anycast, 35 | boolean is_anycast, 36 | String city, 37 | String region, 38 | String country, 39 | String loc, 40 | String org, 41 | String postal, 42 | String timezone, 43 | ASN asn, 44 | Company company, 45 | Carrier carrier, 46 | Privacy privacy, 47 | Abuse abuse, 48 | Domains domains 49 | ) { 50 | this.ip = ip; 51 | this.hostname = hostname; 52 | this.bogon = bogon; 53 | this.anycast = anycast; 54 | this.is_anycast = is_anycast; 55 | this.city = city; 56 | this.region = region; 57 | this.country = country; 58 | this.loc = loc; 59 | this.postal = postal; 60 | this.timezone = timezone; 61 | this.org = org; 62 | this.asn = asn; 63 | this.company = company; 64 | this.carrier = carrier; 65 | this.privacy = privacy; 66 | this.abuse = abuse; 67 | this.domains = domains; 68 | } 69 | 70 | public IPResponse( 71 | String ip, 72 | boolean bogon 73 | ) { 74 | this(ip, null, bogon, false, false, null, null, null, null, null, null, null, null, null, null, null, null, null); 75 | } 76 | 77 | /** 78 | * Set by the library for extra utility functions 79 | * 80 | * @param context for country information 81 | */ 82 | public void setContext(Context context) { 83 | this.context = context; 84 | } 85 | 86 | public String getIp() { 87 | return ip; 88 | } 89 | 90 | public String getHostname() { 91 | return hostname; 92 | } 93 | 94 | public boolean getBogon() { 95 | return bogon; 96 | } 97 | 98 | public boolean getAnycast() { 99 | return anycast || is_anycast; 100 | } 101 | 102 | public String getCity() { 103 | return city; 104 | } 105 | 106 | public String getRegion() { 107 | return region; 108 | } 109 | 110 | public String getCountryCode() { 111 | return country; 112 | } 113 | 114 | public String getCountryName() { 115 | return context.getCountryName(getCountryCode()); 116 | } 117 | 118 | public Boolean isEU() { 119 | return context.isEU(getCountryCode()); 120 | } 121 | 122 | public CountryFlag getCountryFlag() { 123 | return context.getCountryFlag(getCountryCode()); 124 | } 125 | 126 | public String getCountryFlagURL() { 127 | return context.getCountryFlagURL(getCountryCode()); 128 | } 129 | 130 | public CountryCurrency getCountryCurrency() { 131 | return context.getCountryCurrency(getCountryCode()); 132 | } 133 | 134 | public Continent getContinent() { 135 | return context.getContinent(getCountryCode()); 136 | } 137 | 138 | public String getLocation() { 139 | return loc; 140 | } 141 | 142 | public String getLatitude() { 143 | try { 144 | return loc.split(",")[0]; 145 | } catch (Exception ex) { 146 | return null; 147 | } 148 | } 149 | 150 | public String getLongitude() { 151 | try { 152 | return loc.split(",")[1]; 153 | } catch (Exception ex) { 154 | return null; 155 | } 156 | } 157 | 158 | public String getOrg() { 159 | return org; 160 | } 161 | 162 | public String getPostal() { 163 | return postal; 164 | } 165 | 166 | public String getTimezone() { 167 | return timezone; 168 | } 169 | 170 | public ASN getAsn() { 171 | return asn; 172 | } 173 | 174 | public Company getCompany() { 175 | return company; 176 | } 177 | 178 | public Carrier getCarrier() { 179 | return carrier; 180 | } 181 | 182 | public Privacy getPrivacy() { 183 | return privacy; 184 | } 185 | 186 | public Abuse getAbuse() { 187 | return abuse; 188 | } 189 | 190 | public Domains getDomains() { 191 | return domains; 192 | } 193 | 194 | @Override 195 | public String toString() { 196 | return (bogon ? 197 | "IPResponse{" + 198 | "ip='" + ip + '\'' + 199 | ", bogon='" + bogon + '\'' + 200 | "}" 201 | : 202 | "IPResponse{" + 203 | "ip='" + ip + '\'' + 204 | ", hostname='" + hostname + '\'' + 205 | ", anycast=" + anycast + 206 | ", is_anycast=" + is_anycast + 207 | ", city='" + city + '\'' + 208 | ", region='" + region + '\'' + 209 | ", country='" + country + '\'' + 210 | ", loc='" + loc + '\'' + 211 | ", org='" + org + '\'' + 212 | ", postal='" + postal + '\'' + 213 | ", timezone='" + timezone + '\'' + 214 | ", asn=" + asn + 215 | ", company=" + company + 216 | ", carrier=" + carrier + 217 | ", privacy=" + privacy + 218 | ", abuse=" + abuse + 219 | ", domains=" + domains + 220 | '}'); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/test/java/io/ipinfo/IPinfoCoreTest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import io.ipinfo.api.IPinfoCore; 6 | import io.ipinfo.api.errors.RateLimitedException; 7 | import io.ipinfo.api.model.IPResponseCore; 8 | import org.junit.jupiter.api.Test; 9 | 10 | public class IPinfoCoreTest { 11 | 12 | @Test 13 | public void testAccessToken() { 14 | String token = "test_token"; 15 | IPinfoCore client = new IPinfoCore.Builder() 16 | .setToken(token) 17 | .build(); 18 | assertNotNull(client); 19 | } 20 | 21 | @Test 22 | public void testGoogleDNS() { 23 | IPinfoCore client = new IPinfoCore.Builder() 24 | .setToken(System.getenv("IPINFO_TOKEN")) 25 | .build(); 26 | 27 | try { 28 | IPResponseCore response = client.lookupIP("8.8.8.8"); 29 | assertAll( 30 | "8.8.8.8", 31 | () -> assertEquals("8.8.8.8", response.getIp(), "IP mismatch"), 32 | () -> assertNotNull(response.getGeo(), "geo should be set"), 33 | () -> assertEquals("Mountain View", response.getGeo().getCity(), "city mismatch"), 34 | () -> assertEquals("California", response.getGeo().getRegion(), "region mismatch"), 35 | () -> assertEquals("CA", response.getGeo().getRegionCode(), "region code mismatch"), 36 | () -> assertEquals("United States", response.getGeo().getCountry(), "country mismatch"), 37 | () -> assertEquals("US", response.getGeo().getCountryCode(), "country code mismatch"), 38 | () -> assertEquals("North America", response.getGeo().getContinent(), "continent mismatch"), 39 | () -> assertEquals("NA", response.getGeo().getContinentCode(), "continent code mismatch"), 40 | () -> assertNotNull(response.getGeo().getLatitude(), "latitude should be set"), 41 | () -> assertNotNull(response.getGeo().getLongitude(), "longitude should be set"), 42 | () -> assertEquals("America/Los_Angeles", response.getGeo().getTimezone(), "timezone mismatch"), 43 | () -> assertEquals("94043", response.getGeo().getPostalCode(), "postal code mismatch"), 44 | // Enriched fields 45 | () -> assertEquals("United States", response.getCountryName(), "country name mismatch"), 46 | () -> assertFalse(response.isEU(), "isEU mismatch"), 47 | () -> assertEquals("🇺🇸", response.getCountryFlag().getEmoji(), "emoji mismatch"), 48 | () -> assertEquals("U+1F1FA U+1F1F8", response.getCountryFlag().getUnicode(), "unicode mismatch"), 49 | () -> assertEquals("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", response.getCountryFlagURL(), "flag URL mismatch"), 50 | () -> assertEquals("USD", response.getCountryCurrency().getCode(), "currency code mismatch"), 51 | () -> assertEquals("$", response.getCountryCurrency().getSymbol(), "currency symbol mismatch"), 52 | () -> assertEquals("NA", response.getContinentInfo().getCode(), "continent info code mismatch"), 53 | () -> assertEquals("North America", response.getContinentInfo().getName(), "continent info name mismatch"), 54 | // AS fields 55 | () -> assertNotNull(response.getAsn(), "asn should be set"), 56 | () -> assertEquals("AS15169", response.getAsn().getAsn(), "ASN mismatch"), 57 | () -> assertEquals("Google LLC", response.getAsn().getName(), "AS name mismatch"), 58 | () -> assertEquals("google.com", response.getAsn().getDomain(), "AS domain mismatch"), 59 | () -> assertEquals("hosting", response.getAsn().getType(), "AS type mismatch"), 60 | // Network flags 61 | () -> assertFalse(response.getIsAnonymous(), "is_anonymous mismatch"), 62 | () -> assertTrue(response.getIsAnycast(), "is_anycast mismatch"), 63 | () -> assertTrue(response.getIsHosting(), "is_hosting mismatch"), 64 | () -> assertFalse(response.getIsMobile(), "is_mobile mismatch"), 65 | () -> assertFalse(response.getIsSatellite(), "is_satellite mismatch"), 66 | () -> assertFalse(response.getBogon(), "bogon mismatch") 67 | ); 68 | } catch (RateLimitedException e) { 69 | fail(e); 70 | } 71 | } 72 | 73 | @Test 74 | public void testBogon() { 75 | IPinfoCore client = new IPinfoCore.Builder() 76 | .setToken(System.getenv("IPINFO_TOKEN")) 77 | .build(); 78 | 79 | try { 80 | IPResponseCore response = client.lookupIP("127.0.0.1"); 81 | assertAll( 82 | "127.0.0.1", 83 | () -> assertEquals("127.0.0.1", response.getIp(), "IP mismatch"), 84 | () -> assertTrue(response.getBogon(), "bogon mismatch") 85 | ); 86 | } catch (RateLimitedException e) { 87 | fail(e); 88 | } 89 | } 90 | 91 | @Test 92 | public void testCloudFlareDNS() { 93 | IPinfoCore client = new IPinfoCore.Builder() 94 | .setToken(System.getenv("IPINFO_TOKEN")) 95 | .build(); 96 | 97 | try { 98 | IPResponseCore response = client.lookupIP("1.1.1.1"); 99 | assertAll( 100 | "1.1.1.1", 101 | () -> assertEquals("1.1.1.1", response.getIp(), "IP mismatch"), 102 | () -> assertEquals("AS13335", response.getAsn().getAsn(), "ASN mismatch"), 103 | () -> assertEquals("Cloudflare, Inc.", response.getAsn().getName(), "AS name mismatch"), 104 | () -> assertEquals("cloudflare.com", response.getAsn().getDomain(), "AS domain mismatch"), 105 | () -> assertNotNull(response.getGeo().getCountryCode(), "country code should be set"), 106 | () -> assertNotNull(response.getGeo().getCountry(), "country should be set"), 107 | () -> assertNotNull(response.getCountryName(), "country name should be set"), 108 | () -> assertNotNull(response.getGeo().getContinentCode(), "continent code should be set"), 109 | () -> assertNotNull(response.getGeo().getContinent(), "continent should be set"), 110 | () -> assertNotNull(response.isEU(), "isEU should be set"), 111 | () -> assertNotNull(response.getCountryFlag().getEmoji(), "emoji should be set"), 112 | () -> assertNotNull(response.getCountryFlag().getUnicode(), "unicode should be set"), 113 | () -> assertNotNull(response.getCountryFlagURL(), "flag URL should be set"), 114 | () -> assertNotNull(response.getCountryCurrency().getCode(), "currency code should be set"), 115 | () -> assertNotNull(response.getCountryCurrency().getSymbol(), "currency symbol should be set"), 116 | () -> assertNotNull(response.getContinentInfo().getCode(), "continent info code should be set"), 117 | () -> assertNotNull(response.getContinentInfo().getName(), "continent info name should be set"), 118 | () -> assertFalse(response.getBogon(), "bogon mismatch") 119 | ); 120 | } catch (RateLimitedException e) { 121 | fail(e); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/request/IPRequest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api.request; 2 | 3 | import io.ipinfo.api.errors.ErrorResponseException; 4 | import io.ipinfo.api.errors.RateLimitedException; 5 | import io.ipinfo.api.model.IPResponse; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | import java.net.InetAddress; 10 | import java.net.UnknownHostException; 11 | 12 | public class IPRequest extends BaseRequest { 13 | private final static String URL_FORMAT = "https://ipinfo.io/%s"; 14 | private final String ip; 15 | 16 | public IPRequest(OkHttpClient client, String token, String ip) { 17 | super(client, token); 18 | this.ip = ip; 19 | } 20 | 21 | @Override 22 | public IPResponse handle() throws RateLimitedException { 23 | if (isBogon(ip)) { 24 | try { 25 | return new IPResponse(ip, true); 26 | } catch (Exception ex) { 27 | throw new ErrorResponseException(ex); 28 | } 29 | } 30 | 31 | String url = String.format(URL_FORMAT, ip); 32 | Request.Builder request = new Request.Builder().url(url).get(); 33 | 34 | try (Response response = handleRequest(request)) { 35 | if (response == null || response.body() == null) { 36 | return null; 37 | } 38 | 39 | try { 40 | return gson.fromJson(response.body().string(), IPResponse.class); 41 | } catch (Exception ex) { 42 | throw new ErrorResponseException(ex); 43 | } 44 | } 45 | } 46 | 47 | static IpAddressMatcher[] IpAddressMatcherList = { 48 | // IPv4 49 | new IpAddressMatcher("0.0.0.0/8"), 50 | new IpAddressMatcher("10.0.0.0/8"), 51 | new IpAddressMatcher("100.64.0.0/10"), 52 | new IpAddressMatcher("127.0.0.0/8"), 53 | new IpAddressMatcher("169.254.0.0/16"), 54 | new IpAddressMatcher("172.16.0.0/12"), 55 | new IpAddressMatcher("192.0.0.0/24"), 56 | new IpAddressMatcher("192.0.2.0/24"), 57 | new IpAddressMatcher("192.168.0.0/16"), 58 | new IpAddressMatcher("198.18.0.0/15"), 59 | new IpAddressMatcher("198.51.100.0/24"), 60 | new IpAddressMatcher("203.0.113.0/24"), 61 | new IpAddressMatcher("224.0.0.0/4"), 62 | new IpAddressMatcher("240.0.0.0/4"), 63 | new IpAddressMatcher("255.255.255.255/32"), 64 | // IPv6 65 | new IpAddressMatcher("::/128"), 66 | new IpAddressMatcher("::1/128"), 67 | new IpAddressMatcher("::ffff:0:0/96"), 68 | new IpAddressMatcher("::/96"), 69 | new IpAddressMatcher("100::/64"), 70 | new IpAddressMatcher("2001:10::/28"), 71 | new IpAddressMatcher("2001:db8::/32"), 72 | new IpAddressMatcher("fc00::/7"), 73 | new IpAddressMatcher("fe80::/10"), 74 | new IpAddressMatcher("fec0::/10"), 75 | new IpAddressMatcher("ff00::/8"), 76 | // 6to4 77 | new IpAddressMatcher("2002::/24"), 78 | new IpAddressMatcher("2002:a00::/24"), 79 | new IpAddressMatcher("2002:7f00::/24"), 80 | new IpAddressMatcher("2002:a9fe::/32"), 81 | new IpAddressMatcher("2002:ac10::/28"), 82 | new IpAddressMatcher("2002:c000::/40"), 83 | new IpAddressMatcher("2002:c000:200::/40"), 84 | new IpAddressMatcher("2002:c0a8::/32"), 85 | new IpAddressMatcher("2002:c612::/31"), 86 | new IpAddressMatcher("2002:c633:6400::/40"), 87 | new IpAddressMatcher("2002:cb00:7100::/40"), 88 | new IpAddressMatcher("2002:e000::/20"), 89 | new IpAddressMatcher("2002:f000::/20"), 90 | new IpAddressMatcher("2002:ffff:ffff::/48"), 91 | // Teredo 92 | new IpAddressMatcher("2001::/40"), 93 | new IpAddressMatcher("2001:0:a00::/40"), 94 | new IpAddressMatcher("2001:0:7f00::/40"), 95 | new IpAddressMatcher("2001:0:a9fe::/48"), 96 | new IpAddressMatcher("2001:0:ac10::/44"), 97 | new IpAddressMatcher("2001:0:c000::/56"), 98 | new IpAddressMatcher("2001:0:c000:200::/56"), 99 | new IpAddressMatcher("2001:0:c0a8::/48"), 100 | new IpAddressMatcher("2001:0:c612::/47"), 101 | new IpAddressMatcher("2001:0:c633:6400::/56"), 102 | new IpAddressMatcher("2001:0:cb00:7100::/56"), 103 | new IpAddressMatcher("2001:0:e000::/36"), 104 | new IpAddressMatcher("2001:0:f000::/36"), 105 | new IpAddressMatcher("2001:0:ffff:ffff::/64") 106 | }; 107 | 108 | static boolean isBogon(String ip) { 109 | for (int i = 0; i < IpAddressMatcherList.length; i++) { 110 | IpAddressMatcher ipAddressMatcher = IpAddressMatcherList[i]; 111 | if (ipAddressMatcher.matches(ip)) { 112 | return true; 113 | } 114 | } 115 | return false; 116 | } 117 | 118 | static InetAddress parseAddress(String address) { 119 | try { 120 | return InetAddress.getByName(address); 121 | } 122 | catch (UnknownHostException e) { 123 | throw new IllegalArgumentException("Failed to parse address: " + address, e); 124 | } 125 | } 126 | 127 | /* 128 | * Copyright 2002-2019 the original author or authors. 129 | * 130 | * Licensed under the Apache License, Version 2.0 (the "License"); 131 | * you may not use this file except in compliance with the License. 132 | * You may obtain a copy of the License at 133 | * 134 | * https://www.apache.org/licenses/LICENSE-2.0 135 | * 136 | * Unless required by applicable law or agreed to in writing, software 137 | * distributed under the License is distributed on an "AS IS" BASIS, 138 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139 | * See the License for the specific language governing permissions and 140 | * limitations under the License. 141 | */ 142 | public static class IpAddressMatcher { 143 | private final int nMaskBits; 144 | private final InetAddress requiredAddress; 145 | 146 | public IpAddressMatcher(String ipAddress) { 147 | 148 | if (ipAddress.indexOf('/') > 0) { 149 | String[] addressAndMask = ipAddress.split("/"); 150 | ipAddress = addressAndMask[0]; 151 | nMaskBits = Integer.parseInt(addressAndMask[1]); 152 | } 153 | else { 154 | nMaskBits = -1; 155 | } 156 | requiredAddress = parseAddress(ipAddress); 157 | } 158 | 159 | public boolean matches(String address) { 160 | InetAddress remoteAddress = parseAddress(address); 161 | 162 | if (!requiredAddress.getClass().equals(remoteAddress.getClass())) { 163 | return false; 164 | } 165 | 166 | if (nMaskBits < 0) { 167 | return remoteAddress.equals(requiredAddress); 168 | } 169 | 170 | byte[] remAddr = remoteAddress.getAddress(); 171 | byte[] reqAddr = requiredAddress.getAddress(); 172 | 173 | int nMaskFullBytes = nMaskBits / 8; 174 | byte finalByte = (byte) (0xFF00 >> (nMaskBits & 0x07)); 175 | 176 | for (int i = 0; i < nMaskFullBytes; i++) { 177 | if (remAddr[i] != reqAddr[i]) { 178 | return false; 179 | } 180 | } 181 | 182 | if (finalByte != 0) { 183 | return (remAddr[nMaskFullBytes] & finalByte) == (reqAddr[nMaskFullBytes] & finalByte); 184 | } 185 | 186 | return true; 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/test/java/io/ipinfo/IPinfoPlusTest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import io.ipinfo.api.IPinfoPlus; 6 | import io.ipinfo.api.errors.RateLimitedException; 7 | import io.ipinfo.api.model.IPResponsePlus; 8 | import org.junit.jupiter.api.Test; 9 | 10 | public class IPinfoPlusTest { 11 | 12 | @Test 13 | public void testAccessToken() { 14 | String token = "test_token"; 15 | IPinfoPlus client = new IPinfoPlus.Builder() 16 | .setToken(token) 17 | .build(); 18 | assertNotNull(client); 19 | } 20 | 21 | @Test 22 | public void testGoogleDNS() { 23 | IPinfoPlus client = new IPinfoPlus.Builder() 24 | .setToken(System.getenv("IPINFO_TOKEN")) 25 | .build(); 26 | 27 | try { 28 | IPResponsePlus response = client.lookupIP("8.8.8.8"); 29 | assertAll( 30 | "8.8.8.8", 31 | () -> assertEquals("8.8.8.8", response.getIp(), "IP mismatch"), 32 | () -> assertEquals("dns.google", response.getHostname(), "hostname mismatch"), 33 | () -> assertNotNull(response.getGeo(), "geo should be set"), 34 | () -> assertEquals("Mountain View", response.getGeo().getCity(), "city mismatch"), 35 | () -> assertEquals("California", response.getGeo().getRegion(), "region mismatch"), 36 | () -> assertEquals("CA", response.getGeo().getRegionCode(), "region code mismatch"), 37 | () -> assertEquals("United States", response.getGeo().getCountry(), "country mismatch"), 38 | () -> assertEquals("US", response.getGeo().getCountryCode(), "country code mismatch"), 39 | () -> assertEquals("North America", response.getGeo().getContinent(), "continent mismatch"), 40 | () -> assertEquals("NA", response.getGeo().getContinentCode(), "continent code mismatch"), 41 | () -> assertNotNull(response.getGeo().getLatitude(), "latitude should be set"), 42 | () -> assertNotNull(response.getGeo().getLongitude(), "longitude should be set"), 43 | () -> assertEquals("America/Los_Angeles", response.getGeo().getTimezone(), "timezone mismatch"), 44 | () -> assertEquals("94043", response.getGeo().getPostalCode(), "postal code mismatch"), 45 | // Enriched fields 46 | () -> assertEquals("United States", response.getCountryName(), "country name mismatch"), 47 | () -> assertFalse(response.isEU(), "isEU mismatch"), 48 | () -> assertEquals("🇺🇸", response.getCountryFlag().getEmoji(), "emoji mismatch"), 49 | () -> assertEquals("U+1F1FA U+1F1F8", response.getCountryFlag().getUnicode(), "unicode mismatch"), 50 | () -> assertEquals("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", response.getCountryFlagURL(), "flag URL mismatch"), 51 | () -> assertEquals("USD", response.getCountryCurrency().getCode(), "currency code mismatch"), 52 | () -> assertEquals("$", response.getCountryCurrency().getSymbol(), "currency symbol mismatch"), 53 | () -> assertEquals("NA", response.getContinentInfo().getCode(), "continent info code mismatch"), 54 | () -> assertEquals("North America", response.getContinentInfo().getName(), "continent info name mismatch"), 55 | // AS fields 56 | () -> assertNotNull(response.getAsn(), "asn should be set"), 57 | () -> assertEquals("AS15169", response.getAsn().getAsn(), "ASN mismatch"), 58 | () -> assertEquals("Google LLC", response.getAsn().getName(), "AS name mismatch"), 59 | () -> assertEquals("google.com", response.getAsn().getDomain(), "AS domain mismatch"), 60 | () -> assertEquals("hosting", response.getAsn().getType(), "AS type mismatch"), 61 | // Network flags 62 | () -> assertFalse(response.getIsAnonymous(), "is_anonymous mismatch"), 63 | () -> assertTrue(response.getIsAnycast(), "is_anycast mismatch"), 64 | () -> assertTrue(response.getIsHosting(), "is_hosting mismatch"), 65 | () -> assertFalse(response.getIsMobile(), "is_mobile mismatch"), 66 | () -> assertFalse(response.getIsSatellite(), "is_satellite mismatch"), 67 | // Plus-specific fields (may not be present depending on token tier) 68 | () -> assertFalse(response.getBogon(), "bogon mismatch") 69 | ); 70 | } catch (RateLimitedException e) { 71 | fail(e); 72 | } 73 | } 74 | 75 | @Test 76 | public void testBogon() { 77 | IPinfoPlus client = new IPinfoPlus.Builder() 78 | .setToken(System.getenv("IPINFO_TOKEN")) 79 | .build(); 80 | 81 | try { 82 | IPResponsePlus response = client.lookupIP("127.0.0.1"); 83 | assertAll( 84 | "127.0.0.1", 85 | () -> assertEquals("127.0.0.1", response.getIp(), "IP mismatch"), 86 | () -> assertTrue(response.getBogon(), "bogon mismatch") 87 | ); 88 | } catch (RateLimitedException e) { 89 | fail(e); 90 | } 91 | } 92 | 93 | @Test 94 | public void testCloudFlareDNS() { 95 | IPinfoPlus client = new IPinfoPlus.Builder() 96 | .setToken(System.getenv("IPINFO_TOKEN")) 97 | .build(); 98 | 99 | try { 100 | IPResponsePlus response = client.lookupIP("1.1.1.1"); 101 | assertAll( 102 | "1.1.1.1", 103 | () -> assertEquals("1.1.1.1", response.getIp(), "IP mismatch"), 104 | () -> assertEquals("one.one.one.one", response.getHostname(), "hostname mismatch"), 105 | () -> assertEquals("AS13335", response.getAsn().getAsn(), "ASN mismatch"), 106 | () -> assertEquals("Cloudflare, Inc.", response.getAsn().getName(), "AS name mismatch"), 107 | () -> assertEquals("cloudflare.com", response.getAsn().getDomain(), "AS domain mismatch"), 108 | () -> assertNotNull(response.getGeo().getCountryCode(), "country code should be set"), 109 | () -> assertNotNull(response.getGeo().getCountry(), "country should be set"), 110 | () -> assertNotNull(response.getCountryName(), "country name should be set"), 111 | () -> assertNotNull(response.getGeo().getContinentCode(), "continent code should be set"), 112 | () -> assertNotNull(response.getGeo().getContinent(), "continent should be set"), 113 | () -> assertNotNull(response.isEU(), "isEU should be set"), 114 | () -> assertNotNull(response.getCountryFlag().getEmoji(), "emoji should be set"), 115 | () -> assertNotNull(response.getCountryFlag().getUnicode(), "unicode should be set"), 116 | () -> assertNotNull(response.getCountryFlagURL(), "flag URL should be set"), 117 | () -> assertNotNull(response.getCountryCurrency().getCode(), "currency code should be set"), 118 | () -> assertNotNull(response.getCountryCurrency().getSymbol(), "currency symbol should be set"), 119 | () -> assertNotNull(response.getContinentInfo().getCode(), "continent info code should be set"), 120 | () -> assertNotNull(response.getContinentInfo().getName(), "continent info name should be set"), 121 | // Plus-specific fields (may not be present depending on token tier) 122 | () -> assertFalse(response.getBogon(), "bogon mismatch") 123 | ); 124 | } catch (RateLimitedException e) { 125 | fail(e); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/io/ipinfo/IPinfoLiteTest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import io.ipinfo.api.IPinfoLite; 6 | import io.ipinfo.api.errors.ErrorResponseException; 7 | import io.ipinfo.api.errors.RateLimitedException; 8 | import io.ipinfo.api.model.IPResponseLite; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | 16 | public class IPinfoLiteTest { 17 | 18 | @Test 19 | public void testGoogleDNSLite() { 20 | IPinfoLite ii = new IPinfoLite.Builder() 21 | .setToken(System.getenv("IPINFO_TOKEN")) 22 | .build(); 23 | 24 | try { 25 | IPResponseLite response = ii.lookupIP("8.8.8.8"); 26 | assertAll( 27 | "8.8.8.8", 28 | () -> assertEquals("8.8.8.8", response.getIp(), "IP mismatch"), 29 | () -> 30 | assertEquals("AS15169", response.getAsn(), "ASN mismatch"), 31 | () -> 32 | assertEquals( 33 | "Google LLC", 34 | response.getAsName(), 35 | "AS name mismatch" 36 | ), 37 | () -> 38 | assertEquals( 39 | "google.com", 40 | response.getAsDomain(), 41 | "AS domain mismatch" 42 | ), 43 | () -> 44 | assertEquals( 45 | "US", 46 | response.getCountryCode(), 47 | "country code mismatch" 48 | ), 49 | () -> 50 | assertEquals( 51 | "United States", 52 | response.getCountry(), 53 | "country mismatch" 54 | ), 55 | () -> 56 | assertEquals( 57 | "United States", 58 | response.getCountryName(), 59 | "country name mismatch" 60 | ), 61 | () -> 62 | assertEquals( 63 | "NA", 64 | response.getContinentCode(), 65 | "continent code mismatch" 66 | ), 67 | () -> 68 | assertEquals( 69 | "North America", 70 | response.getContinent(), 71 | "continent mismatch" 72 | ), 73 | () -> assertFalse(response.isEU(), "isEU mismatch"), 74 | () -> 75 | assertEquals( 76 | "🇺🇸", 77 | response.getCountryFlag().getEmoji(), 78 | "emoji mismatch" 79 | ), 80 | () -> 81 | assertEquals( 82 | "U+1F1FA U+1F1F8", 83 | response.getCountryFlag().getUnicode(), 84 | "country flag unicode mismatch" 85 | ), 86 | () -> 87 | assertEquals( 88 | "https://cdn.ipinfo.io/static/images/countries-flags/US.svg", 89 | response.getCountryFlagURL(), 90 | "country flag URL mismatch" 91 | ), 92 | () -> 93 | assertEquals( 94 | "USD", 95 | response.getCountryCurrency().getCode(), 96 | "country currency code mismatch" 97 | ), 98 | () -> 99 | assertEquals( 100 | "$", 101 | response.getCountryCurrency().getSymbol(), 102 | "country currency symbol mismatch" 103 | ), 104 | () -> 105 | assertEquals( 106 | "NA", 107 | response.getContinentInfo().getCode(), 108 | "continent info code mismatch" 109 | ), 110 | () -> 111 | assertEquals( 112 | "North America", 113 | response.getContinentInfo().getName(), 114 | "continent info name mismatch" 115 | ), 116 | () -> assertFalse(response.getBogon(), "bogon mismatch") 117 | ); 118 | 119 | IPResponseLite bogonResp = ii.lookupIP("2001:0:c000:200::0:255:1"); 120 | assertAll( 121 | "bogon response", 122 | () -> 123 | assertEquals( 124 | "2001:0:c000:200::0:255:1", 125 | bogonResp.getIp(), 126 | "IP mismatch" 127 | ), 128 | () -> assertTrue(bogonResp.getBogon(), "bogon mismatch") 129 | ); 130 | } catch (RateLimitedException e) { 131 | fail(e); 132 | } 133 | } 134 | 135 | @Test 136 | public void testCloudFlareDNSLite() { 137 | IPinfoLite ii = new IPinfoLite.Builder() 138 | .setToken(System.getenv("IPINFO_TOKEN")) 139 | .build(); 140 | 141 | try { 142 | IPResponseLite response = ii.lookupIP("1.1.1.1"); 143 | assertAll( 144 | "1.1.1.1", 145 | () -> assertEquals("1.1.1.1", response.getIp(), "IP mismatch"), 146 | () -> 147 | assertEquals("AS13335", response.getAsn(), "ASN mismatch"), 148 | () -> 149 | assertEquals( 150 | "Cloudflare, Inc.", 151 | response.getAsName(), 152 | "AS name mismatch" 153 | ), 154 | () -> 155 | assertEquals( 156 | "cloudflare.com", 157 | response.getAsDomain(), 158 | "AS domain mismatch" 159 | ), 160 | () -> assertNotNull(response.getCountryCode(), "country code should be set"), 161 | () -> assertNotNull(response.getCountry(), "country should be set"), 162 | () -> assertNotNull(response.getCountryName(), "country name should be set"), 163 | () -> assertNotNull(response.getContinentCode(), "continent code should be set"), 164 | () -> assertNotNull(response.getContinent(), "continent should be set"), 165 | () -> assertNotNull(response.isEU(), "isEU should be set"), 166 | () -> assertNotNull(response.getCountryFlag().getEmoji(), "emoji should be set"), 167 | () -> assertNotNull(response.getCountryFlag().getUnicode(), "country flag unicode should be set"), 168 | () -> assertNotNull(response.getCountryFlagURL(), "country flag URL should be set"), 169 | () -> assertNotNull(response.getCountryCurrency().getCode(), "country currency code should be set"), 170 | () -> assertNotNull(response.getCountryCurrency().getSymbol(), "country currency symbol should be set"), 171 | () -> assertNotNull(response.getContinentInfo().getCode(), "continent info code should be set"), 172 | () -> assertNotNull(response.getContinentInfo().getName(), "continent info name should be set"), 173 | () -> assertFalse(response.getBogon(), "bogon mismatch") 174 | ); 175 | } catch (RateLimitedException e) { 176 | fail(e); 177 | } 178 | } 179 | 180 | @Test 181 | public void testBogonIPsLite() { 182 | IPinfoLite ii = new IPinfoLite.Builder() 183 | .setToken(System.getenv("IPINFO_TOKEN")) 184 | .build(); 185 | 186 | try { 187 | // Test various bogon IPs 188 | String[] bogonIps = { 189 | "10.0.0.1", // Private network 190 | "192.168.1.1", // Private network 191 | "127.0.0.1", // Loopback 192 | "169.254.1.1", // Link-local 193 | "224.0.0.1", // Multicast 194 | "255.255.255.255", // Broadcast 195 | }; 196 | 197 | for (String ip : bogonIps) { 198 | IPResponseLite response = ii.lookupIP(ip); 199 | assertAll( 200 | "bogon IP " + ip, 201 | () -> assertEquals(ip, response.getIp(), "IP mismatch"), 202 | () -> assertTrue(response.getBogon(), "should be bogon") 203 | ); 204 | } 205 | } catch (RateLimitedException e) { 206 | fail(e); 207 | } 208 | } 209 | 210 | @Test 211 | public void testEUCountryLite() { 212 | IPinfoLite ii = new IPinfoLite.Builder() 213 | .setToken(System.getenv("IPINFO_TOKEN")) 214 | .build(); 215 | 216 | try { 217 | // Test with a German IP (should be EU) 218 | IPResponseLite response = ii.lookupIP("89.200.139.6"); // A German IP 219 | if ( 220 | response != null && 221 | !response.getBogon() && 222 | "DE".equals(response.getCountryCode()) 223 | ) { 224 | assertAll( 225 | "EU country test", 226 | () -> 227 | assertEquals( 228 | "DE", 229 | response.getCountryCode(), 230 | "country code mismatch" 231 | ), 232 | () -> 233 | assertTrue(response.isEU(), "Germany should be in EU"), 234 | () -> 235 | assertEquals( 236 | "🇩🇪", 237 | response.getCountryFlag().getEmoji(), 238 | "German flag emoji mismatch" 239 | ), 240 | () -> 241 | assertEquals( 242 | "EUR", 243 | response.getCountryCurrency().getCode(), 244 | "Euro currency mismatch" 245 | ) 246 | ); 247 | } 248 | } catch (RateLimitedException e) { 249 | fail(e); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [IPinfo](https://ipinfo.io/) IPinfo Java Client Library 2 | 3 | [![License](http://img.shields.io/:license-apache-blue.svg)](LICENSE) 4 | 5 | This is the official Java 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: 6 | 7 | - [IP geolocation data](https://ipinfo.io/ip-geolocation-api) (city, region, country, postal code, latitude, and longitude) 8 | - [ASN information](https://ipinfo.io/asn-api) (ISP or network operator, associated domain name, and type, such as business, hosting, or company) 9 | - [Company data](https://ipinfo.io/ip-company-api) (the name and domain of the business that uses the IP address) 10 | - [Carrier details](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) 11 | 12 | Check all the data we have for your IP address [here](https://ipinfo.io/what-is-my-ip). 13 | 14 | ### Getting Started 15 | 16 | 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). 17 | 18 | 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) 19 | 20 | [Click here to view the Java SDK's API documentation](https://ipinfo.github.io/java/). 21 | 22 | The library also supports the Lite API, see the [Lite API section](#lite-api) for more info. 23 | 24 | #### Installation 25 | 26 | ##### Maven 27 | 28 | Add these values to your pom.xml file: 29 | 30 | Dependency: 31 | 32 | ```xml 33 | 34 | 35 | io.ipinfo 36 | ipinfo-api 37 | 3.2.0 38 | compile 39 | 40 | 41 | ``` 42 | 43 | #### Quick Start 44 | 45 | ##### IP Information 46 | 47 | ```java 48 | import io.ipinfo.api.IPinfo; 49 | import io.ipinfo.api.errors.RateLimitedException; 50 | import io.ipinfo.api.model.IPResponse; 51 | 52 | public class Main { 53 | public static void main(String... args) { 54 | IPinfo ipInfo = new IPinfo.Builder() 55 | .setToken("YOUR TOKEN") 56 | .build(); 57 | 58 | try { 59 | IPResponse response = ipInfo.lookupIP("8.8.8.8"); 60 | 61 | // Print out the hostname 62 | System.out.println(response.getHostname()); 63 | } catch (RateLimitedException ex) { 64 | // Handle rate limits here. 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | ##### ASN Information 71 | 72 | ```java 73 | import io.ipinfo.api.IPinfo; 74 | import io.ipinfo.api.errors.RateLimitedException; 75 | import io.ipinfo.api.model.IPResponse; 76 | 77 | public class Main { 78 | public static void main(String... args) { 79 | IPinfo ipInfo = new IPinfo.Builder() 80 | .setToken("YOUR TOKEN") 81 | .build(); 82 | 83 | try { 84 | ASNResponse response = ipInfo.lookupASN("AS7922"); 85 | 86 | // Print out country name 87 | System.out.println(response.getCountry()); 88 | } catch (RateLimitedException ex) { 89 | // Handle rate limits here. 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | #### Errors 96 | 97 | - `ErrorResponseException`: A runtime exception accessible through the 98 | `ExecutionException` of a future. This exception signals that something went 99 | wrong when mapping the API response to the wrapper. You probably can't 100 | recover from this exception. 101 | - `RateLimitedException` An exception signaling that you've been rate limited. 102 | 103 | ##### Lite API 104 | 105 | The library gives the possibility to use the [Lite API](https://ipinfo.io/developers/lite-api) too, authentication with your token is still required. 106 | 107 | The returned details are slightly different from the Core API. 108 | 109 | ```java 110 | import io.ipinfo.api.IPinfoLite; 111 | import io.ipinfo.api.errors.RateLimitedException; 112 | import io.ipinfo.api.model.IPResponseLite; 113 | 114 | public class Main { 115 | public static void main(String... args) { 116 | IPinfoLite ipInfoLite = new IPinfoLite.Builder() 117 | .setToken("YOUR TOKEN") 118 | .build(); 119 | 120 | try { 121 | IPResponseLite response = ipInfoLite.lookupIP("8.8.8.8"); 122 | 123 | // Print out the ASN 124 | System.out.println(response.getAsn()); // AS15169 125 | 126 | // Print out the country code 127 | System.out.println(response.getCountryCode()); // US 128 | 129 | // Print out the country name 130 | System.out.println(response.getCountry()); // United States 131 | } catch (RateLimitedException ex) { 132 | // Handle rate limits here. 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | #### Caching 139 | 140 | This library provides a very simple caching system accessible in `SimpleCache`. 141 | Simple cache is an in-memory caching system that resets every time you restart 142 | your code. 143 | 144 | If you prefer a different caching methodology, you may use the `Cache` 145 | interface and implement your own caching system around your own infrastructure. 146 | 147 | The default cache length is 1 day, this can be changed by calling the 148 | SimpleCache constructor yourself. 149 | 150 | ```java 151 | import io.ipinfo.api.IPinfo; 152 | import io.ipinfo.api.errors.RateLimitedException; 153 | import io.ipinfo.api.model.IPResponse; 154 | 155 | public class Main { 156 | public static void main(String... args) { 157 | // 5 Day Cache 158 | IPinfo ipInfo = new IPinfo.Builder() 159 | .setToken("YOUR TOKEN") 160 | .setCache(new SimpleCache(Duration.ofDays(5))) 161 | .build(); 162 | 163 | try { 164 | IPResponse response = ipInfo.lookupIP("8.8.8.8"); 165 | 166 | // Print out the hostname 167 | System.out.println(response.getHostname()); 168 | } catch (RateLimitedException ex) { 169 | // Handle rate limits here. 170 | } 171 | } 172 | } 173 | ``` 174 | 175 | #### Country Name Lookup 176 | 177 | This library provides a system to lookup country names through ISO2 country 178 | codes. 179 | 180 | ```java 181 | import io.ipinfo.api.IPinfo; 182 | import io.ipinfo.api.errors.RateLimitedException; 183 | import io.ipinfo.api.model.IPResponse; 184 | 185 | public class Main { 186 | public static void main(String... args) { 187 | IPinfo ipInfo = new IPinfo.Builder() 188 | .setToken("YOUR TOKEN") 189 | .build(); 190 | 191 | try { 192 | IPResponse response = ipInfo.lookupIP("8.8.8.8"); 193 | 194 | // Print out the country code 195 | System.out.println(response.getCountryCode()); 196 | 197 | // Print out the country name 198 | System.out.println(response.getCountryName()); 199 | } catch (RateLimitedException ex) { 200 | // Handle rate limits here. 201 | } 202 | } 203 | } 204 | ``` 205 | 206 | #### EU Country Lookup 207 | 208 | This library provides a system to lookup if a country is a member of the European Union (EU) through 209 | ISO2 country codes. 210 | 211 | ```java 212 | import io.ipinfo.api.IPinfo; 213 | import io.ipinfo.api.errors.RateLimitedException; 214 | import io.ipinfo.api.model.IPResponse; 215 | 216 | public class Main { 217 | public static void main(String... args) { 218 | IPinfo ipInfo = new IPinfo.Builder() 219 | .setToken("YOUR TOKEN") 220 | .build(); 221 | 222 | try { 223 | IPResponse response = ipInfo.lookupIP("8.8.8.8"); 224 | 225 | // Print out whether the country is a member of the EU 226 | System.out.println(response.isEU()); 227 | } catch (RateLimitedException ex) { 228 | // Handle rate limits here. 229 | } 230 | } 231 | } 232 | ``` 233 | 234 | #### Internationalization 235 | 236 | This library provides a system to lookup if a country is a member of the European Union (EU), emoji and unicode of the country's flag, code and symbol of the country's currency, and public link to the country's flag image as an SVG and continent code and name through ISO2 country codes. 237 | 238 | ```java 239 | import io.ipinfo.api.IPinfo; 240 | import io.ipinfo.api.errors.RateLimitedException; 241 | import io.ipinfo.api.model.IPResponse; 242 | 243 | public class Main { 244 | public static void main(String... args) { 245 | IPinfo ipInfo = new IPinfo.Builder() 246 | .setToken("YOUR TOKEN") 247 | .build(); 248 | 249 | try { 250 | IPResponse response = ipInfo.lookupIP("8.8.8.8"); 251 | 252 | // Print out whether the country is a member of the EU 253 | System.out.println(response.isEU()); 254 | 255 | // CountryFlag{emoji='🇺🇸',unicode='U+1F1FA U+1F1F8'} 256 | System.out.println(response.getCountryFlag()); 257 | 258 | // https://cdn.ipinfo.io/static/images/countries-flags/US.svg 259 | System.out.println(response.getCountryFlagURL()); 260 | 261 | // CountryCurrency{code='USD',symbol='$'} 262 | System.out.println(response.getCountryCurrency()); 263 | 264 | // Continent{code='NA',name='North America'} 265 | System.out.println(response.getContinent()); 266 | } catch (RateLimitedException ex) { 267 | // Handle rate limits here. 268 | } 269 | } 270 | } 271 | ``` 272 | 273 | #### Location Information 274 | 275 | This library provides an easy way to get the latitude and longitude of an IP Address: 276 | 277 | ```java 278 | import io.ipinfo.api.IPinfo; 279 | import io.ipinfo.api.errors.RateLimitedException; 280 | import io.ipinfo.api.model.IPResponse; 281 | 282 | public class Main { 283 | public static void main(String... args) { 284 | IPinfo ipInfo = new IPinfo.Builder() 285 | .setToken("YOUR TOKEN") 286 | .build(); 287 | 288 | try { 289 | IPResponse response = ipInfo.lookupIP("8.8.8.8"); 290 | 291 | // Print out the Latitude and Longitude 292 | System.out.println(response.getLatitude()); 293 | System.out.println(response.getLongitude()); 294 | 295 | } catch (RateLimitedException ex) { 296 | // Handle rate limits here. 297 | } 298 | } 299 | } 300 | ``` 301 | 302 | #### Extra Information 303 | 304 | - This library is thread-safe. Feel free to call the different endpoints from 305 | different threads. 306 | - This library uses Square's http client. Please refer to their documentation 307 | to get information on more functionality you can use. 308 | 309 | ### Other Libraries 310 | 311 | There are official [IPinfo client libraries](https://ipinfo.io/developers/libraries) available for many languages including PHP, Python, 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. 312 | 313 | ### About IPinfo 314 | 315 | 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, VPN detection, hosted domains, and IP type data sets. Our API handles over 40 billion requests a month for 100,000 businesses and developers. 316 | 317 | [![image](https://avatars3.githubusercontent.com/u/15721521?s=128&u=7bb7dde5c4991335fb234e68a30971944abc6bf3&v=4)](https://ipinfo.io/) 318 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /src/test/java/io/ipinfo/IPinfoTest.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo; 2 | 3 | import io.ipinfo.api.IPinfo; 4 | import io.ipinfo.api.errors.ErrorResponseException; 5 | import io.ipinfo.api.errors.RateLimitedException; 6 | import io.ipinfo.api.model.ASNResponse; 7 | import io.ipinfo.api.model.IPResponse; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Disabled; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.lang.reflect.Array; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | 18 | import static org.junit.jupiter.api.Assertions.*; 19 | 20 | public class IPinfoTest { 21 | @Test 22 | public void testGoogleDNS() { 23 | IPinfo ii = new IPinfo.Builder() 24 | .setToken(System.getenv("IPINFO_TOKEN")) 25 | .build(); 26 | 27 | try { 28 | IPResponse response = ii.lookupIP("8.8.8.8"); 29 | assertAll("8.8.8.8", 30 | () -> assertEquals("8.8.8.8", response.getIp(), "IP mismatch"), 31 | () -> assertEquals("dns.google", response.getHostname(), "hostname mismatch"), 32 | () -> assertTrue(response.getAnycast(), "anycast mismatch"), 33 | () -> assertEquals("Mountain View", response.getCity(), "city mismatch"), 34 | () -> assertEquals("California", response.getRegion(), "region mismatch"), 35 | () -> assertEquals("US", response.getCountryCode(), "country code mismatch"), 36 | () -> assertEquals("United States", response.getCountryName(), "country name mismatch"), 37 | () -> assertFalse(response.isEU(), "isEU mismatch"), 38 | () -> assertEquals("🇺🇸", response.getCountryFlag().getEmoji(), "emoji mismatch"), 39 | () -> assertEquals("U+1F1FA U+1F1F8", response.getCountryFlag().getUnicode(), "country flag unicode mismatch"), 40 | () -> assertEquals("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", response.getCountryFlagURL(), "country flag mismatch"), 41 | () -> assertEquals("USD", response.getCountryCurrency().getCode(), "country currency code mismatch"), 42 | () -> assertEquals("$", response.getCountryCurrency().getSymbol(), "country currency symbo mismatch"), 43 | () -> assertEquals("NA", response.getContinent().getCode(), "continent code mismatch"), 44 | () -> assertEquals("North America", response.getContinent().getName(), "continent name mismatch"), 45 | () -> assertEquals("America/Los_Angeles", response.getTimezone(), "timezone mismatch"), 46 | () -> assertFalse(response.getPrivacy().getProxy(), "proxy mismatch"), 47 | () -> assertFalse(response.getPrivacy().getVpn(), "VPN mismatch"), 48 | () -> assertFalse(response.getPrivacy().getTor(), "Tor mismatch"), 49 | () -> assertFalse(response.getPrivacy().getRelay(), "relay mismatch"), 50 | () -> assertTrue(response.getPrivacy().getHosting(), "hosting mismatch"), 51 | () -> assertEquals("", response.getPrivacy().getService(), "service mismatch"), 52 | () -> assertEquals(5, response.getDomains().getDomains().size(), "domains size mismatch") 53 | ); 54 | 55 | IPResponse bogonResp = ii.lookupIP("2001:0:c000:200::0:255:1"); 56 | assertAll("bogon response", 57 | () -> assertEquals("2001:0:c000:200::0:255:1", bogonResp.getIp(), "IP mismatch"), 58 | () -> assertTrue(bogonResp.getBogon(), "bogon mismatch") 59 | ); 60 | } catch (RateLimitedException e) { 61 | fail(e); 62 | } 63 | } 64 | 65 | @Disabled 66 | @Test 67 | public void testGetMap() { 68 | IPinfo ii = new IPinfo.Builder() 69 | .setToken(System.getenv("IPINFO_TOKEN")) 70 | .build(); 71 | 72 | try { 73 | String mapUrl = ii.getMap(Arrays.asList("1.1.1.1", "2.2.2.2", "8.8.8.8")); 74 | } catch (RateLimitedException e) { 75 | fail(e); 76 | } 77 | } 78 | 79 | @Test 80 | public void testGetBatch() { 81 | IPinfo ii = new IPinfo.Builder() 82 | .setToken(System.getenv("IPINFO_TOKEN")) 83 | .build(); 84 | 85 | try { 86 | List urls = new ArrayList(10); 87 | urls.add("AS123"); 88 | urls.add("8.8.8.8"); 89 | urls.add("9.9.9.9/hostname"); 90 | urls.add("239.0.0.0"); 91 | ConcurrentHashMap result = ii.getBatch(urls); 92 | 93 | assertAll("keys exist", 94 | () -> assertTrue(result.containsKey("AS123"), "does not contain AS123"), 95 | () -> assertTrue(result.containsKey("8.8.8.8"), "does not contain 8.8.8.8"), 96 | () -> assertTrue(result.containsKey("9.9.9.9/hostname"), "does not contain 9.9.9.9/hostname") 97 | ); 98 | 99 | ASNResponse asnResp = (ASNResponse)result.get("AS123"); 100 | assertAll("AS123", 101 | () -> assertEquals("AS123", asnResp.getAsn(), "ASN mismatch"), 102 | () -> assertEquals("Air Force Systems Networking", asnResp.getName(), "name mismatch"), 103 | () -> assertEquals("US", asnResp.getCountryCode(), "country code mismatch"), 104 | () -> assertEquals("United States", asnResp.getCountryName(), "country name mismatch"), 105 | () -> assertEquals("1987-08-24", asnResp.getAllocated(), "allocated mismatch"), 106 | () -> assertEquals("arin", asnResp.getRegistry(), "registry mismatch"), 107 | () -> assertEquals("af.mil", asnResp.getDomain(), "domain mismatch"), 108 | () -> assertEquals(new Integer(0), asnResp.getNumIps(), "num IPs mismatch"), 109 | () -> assertEquals("inactive", asnResp.getType(), "type mismatch") 110 | ); 111 | 112 | IPResponse ipResp = (IPResponse)result.get("8.8.8.8"); 113 | assertAll("8.8.8.8", 114 | () -> assertEquals("8.8.8.8", ipResp.getIp(), "IP mismatch"), 115 | () -> assertEquals("dns.google", ipResp.getHostname(), "hostname mismatch"), 116 | () -> assertTrue(ipResp.getAnycast(), "anycast mismatch"), 117 | () -> assertEquals("Mountain View", ipResp.getCity(), "city mismatch"), 118 | () -> assertEquals("California", ipResp.getRegion(), "region mismatch"), 119 | () -> assertEquals("US", ipResp.getCountryCode(), "country code mismatch"), 120 | () -> assertEquals("United States", ipResp.getCountryName(), "country name mismatch"), 121 | () -> assertEquals("America/Los_Angeles", ipResp.getTimezone(), "timezone mismatch"), 122 | () -> assertFalse(ipResp.getPrivacy().getProxy(), "proxy mismatch"), 123 | () -> assertFalse(ipResp.getPrivacy().getVpn(), "VPN mismatch"), 124 | () -> assertFalse(ipResp.getPrivacy().getTor(), "Tor mismatch"), 125 | () -> assertFalse(ipResp.getPrivacy().getRelay(), "relay mismatch"), 126 | () -> assertTrue(ipResp.getPrivacy().getHosting(), "hosting mismatch"), 127 | () -> assertEquals("", ipResp.getPrivacy().getService(), "service mismatch"), 128 | () -> assertEquals(5, ipResp.getDomains().getDomains().size(), "domains size mismatch") 129 | ); 130 | 131 | String hostname = (String)result.get("9.9.9.9/hostname"); 132 | assertEquals("dns9.quad9.net", hostname, "hostname mismatch"); 133 | 134 | IPResponse bogonResp = (IPResponse)result.get("239.0.0.0"); 135 | assertAll("239.0.0.0", 136 | () -> assertEquals("239.0.0.0", bogonResp.getIp(), "IP mismatch"), 137 | () -> assertTrue(bogonResp.getBogon(), "bogon mismatch") 138 | ); 139 | } catch (RateLimitedException e) { 140 | fail(e); 141 | } 142 | } 143 | 144 | @Test 145 | public void testGetBatchIps() { 146 | IPinfo ii = new IPinfo.Builder() 147 | .setToken(System.getenv("IPINFO_TOKEN")) 148 | .build(); 149 | 150 | try { 151 | List ips = new ArrayList(10); 152 | ips.add("1.1.1.1"); 153 | ips.add("8.8.8.8"); 154 | ips.add("9.9.9.9"); 155 | ConcurrentHashMap result = ii.getBatchIps(ips); 156 | 157 | assertAll("keys exist", 158 | () -> assertTrue(result.containsKey("1.1.1.1"), "does not contain 1.1.1.1"), 159 | () -> assertTrue(result.containsKey("8.8.8.8"), "does not contain 8.8.8.8"), 160 | () -> assertTrue(result.containsKey("9.9.9.9"), "does not contain 9.9.9.9") 161 | ); 162 | 163 | IPResponse res1 = result.get("1.1.1.1"); 164 | assertAll("1.1.1.1", 165 | () -> assertEquals("1.1.1.1", res1.getIp(), "IP mismatch"), 166 | () -> assertEquals("one.one.one.one", res1.getHostname(), "hostname mismatch"), 167 | () -> assertTrue(res1.getAnycast(), "anycast mismatch"), 168 | () -> assertNotNull(res1.getCity(), "city should be set"), 169 | () -> assertNotNull(res1.getRegion(), "region should be set"), 170 | () -> assertNotNull(res1.getCountryCode(), "country code should be set"), 171 | () -> assertNotNull(res1.getCountryName(), "country name should be set"), 172 | () -> assertNotNull(res1.getTimezone(), "timezone should be set"), 173 | () -> assertFalse(res1.getPrivacy().getProxy(), "proxy mismatch"), 174 | () -> assertFalse(res1.getPrivacy().getVpn(), "VPN mismatch"), 175 | () -> assertFalse(res1.getPrivacy().getTor(), "Tor mismatch"), 176 | () -> assertFalse(res1.getPrivacy().getRelay(), "relay mismatch"), 177 | () -> assertTrue(res1.getPrivacy().getHosting(), "hosting mismatch"), 178 | () -> assertEquals("", res1.getPrivacy().getService(), "service mismatch"), 179 | () -> assertEquals(5, res1.getDomains().getDomains().size(), "domains size mismatch") 180 | ); 181 | 182 | IPResponse res2 = result.get("8.8.8.8"); 183 | assertAll("8.8.8.8", 184 | () -> assertEquals("8.8.8.8", res2.getIp(), "IP mismatch"), 185 | () -> assertEquals("dns.google", res2.getHostname(), "hostname mismatch"), 186 | () -> assertTrue(res2.getAnycast(), "anycast mismatch"), 187 | () -> assertEquals("Mountain View", res2.getCity(), "city mismatch"), 188 | () -> assertEquals("California", res2.getRegion(), "region mismatch"), 189 | () -> assertEquals("US", res2.getCountryCode(), "country code mismatch"), 190 | () -> assertEquals("United States", res2.getCountryName(), "country name mismatch"), 191 | () -> assertEquals("America/Los_Angeles", res2.getTimezone(), "timezone mismatch"), 192 | () -> assertFalse(res2.getPrivacy().getProxy(), "proxy mismatch"), 193 | () -> assertFalse(res2.getPrivacy().getVpn(), "VPN mismatch"), 194 | () -> assertFalse(res2.getPrivacy().getTor(), "Tor mismatch"), 195 | () -> assertFalse(res2.getPrivacy().getRelay(), "relay mismatch"), 196 | () -> assertTrue(res2.getPrivacy().getHosting(), "hosting mismatch"), 197 | () -> assertEquals("", res2.getPrivacy().getService(), "service mismatch"), 198 | () -> assertEquals(5, res2.getDomains().getDomains().size(), "domains size mismatch") 199 | ); 200 | 201 | IPResponse res3 = result.get("9.9.9.9"); 202 | assertAll("9.9.9.9", 203 | () -> assertEquals("9.9.9.9", res3.getIp(), "IP mismatch"), 204 | () -> assertEquals("dns9.quad9.net", res3.getHostname(), "hostname mismatch"), 205 | () -> assertTrue(res3.getAnycast(), "anycast mismatch"), 206 | () -> assertEquals("Ashburn", res3.getCity(), "city mismatch"), 207 | () -> assertEquals("Virginia", res3.getRegion(), "region mismatch"), 208 | () -> assertEquals("US", res3.getCountryCode(), "country code mismatch"), 209 | () -> assertEquals("United States", res3.getCountryName(), "country name mismatch"), 210 | () -> assertEquals("America/New_York", res3.getTimezone(), "timezone mismatch"), 211 | () -> assertFalse(res3.getPrivacy().getProxy(), "proxy mismatch"), 212 | () -> assertFalse(res3.getPrivacy().getVpn(), "VPN mismatch"), 213 | () -> assertFalse(res3.getPrivacy().getTor(), "Tor mismatch"), 214 | () -> assertFalse(res3.getPrivacy().getRelay(), "relay mismatch"), 215 | () -> assertFalse(res3.getPrivacy().getHosting(), "hosting mismatch"), 216 | () -> assertEquals("", res3.getPrivacy().getService(), "service mismatch"), 217 | () -> assertEquals(5, res3.getDomains().getDomains().size(), "domains size mismatch") 218 | ); 219 | } catch (RateLimitedException e) { 220 | fail(e); 221 | } 222 | } 223 | 224 | @Test 225 | public void testGetBatchAsns() { 226 | IPinfo ii = new IPinfo.Builder() 227 | .setToken(System.getenv("IPINFO_TOKEN")) 228 | .build(); 229 | 230 | try { 231 | List asns = new ArrayList(10); 232 | asns.add("AS123"); 233 | asns.add("AS321"); 234 | ConcurrentHashMap result = ii.getBatchAsns(asns); 235 | 236 | assertAll("keys exist", 237 | () -> assertTrue(result.containsKey("AS123"), "does not contain AS123"), 238 | () -> assertTrue(result.containsKey("AS321"), "does not contain AS321") 239 | ); 240 | 241 | ASNResponse res1 = result.get("AS123"); 242 | assertAll("AS123", 243 | () -> assertEquals("AS123", res1.getAsn(), "ASN mismatch"), 244 | () -> assertEquals("Air Force Systems Networking", res1.getName(), "name mismatch"), 245 | () -> assertEquals("US", res1.getCountryCode(), "country code mismatch"), 246 | () -> assertEquals("United States", res1.getCountryName(), "country name mismatch"), 247 | () -> assertEquals("1987-08-24", res1.getAllocated(), "allocated mismatch"), 248 | () -> assertEquals("arin", res1.getRegistry(), "registry mismatch"), 249 | () -> assertEquals("af.mil", res1.getDomain(), "domain mismatch"), 250 | () -> assertEquals(new Integer(0), res1.getNumIps(), "num IPs mismatch"), 251 | () -> assertEquals("inactive", res1.getType(), "type mismatch") 252 | ); 253 | 254 | ASNResponse res2 = result.get("AS321"); 255 | assertAll("AS321", 256 | () -> assertEquals("AS321", res2.getAsn(), "ASN mismatch"), 257 | () -> assertNotNull(res2.getName(), "name should be set"), 258 | () -> assertEquals("US", res2.getCountryCode(), "country code mismatch"), 259 | () -> assertEquals("United States", res2.getCountryName(), "country name mismatch"), 260 | () -> assertEquals("1989-06-30", res2.getAllocated(), "allocated mismatch"), 261 | () -> assertEquals("arin", res2.getRegistry(), "registry mismatch"), 262 | () -> assertEquals("mail.mil", res2.getDomain(), "domain mismatch"), 263 | () -> assertNotNull(res2.getNumIps(), "num IPs should be set"), 264 | () -> assertEquals("government", res2.getType(), "type mismatch") 265 | ); 266 | } catch (RateLimitedException e) { 267 | fail(e); 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/main/java/io/ipinfo/api/IPinfo.java: -------------------------------------------------------------------------------- 1 | package io.ipinfo.api; 2 | 3 | import com.google.common.net.InetAddresses; 4 | import com.google.gson.Gson; 5 | import com.google.gson.reflect.TypeToken; 6 | import io.ipinfo.api.cache.Cache; 7 | import io.ipinfo.api.cache.SimpleCache; 8 | import io.ipinfo.api.context.Context; 9 | import io.ipinfo.api.errors.RateLimitedException; 10 | import io.ipinfo.api.model.ASNResponse; 11 | import io.ipinfo.api.model.IPResponse; 12 | import io.ipinfo.api.model.MapResponse; 13 | import io.ipinfo.api.request.ASNRequest; 14 | import io.ipinfo.api.request.IPRequest; 15 | import io.ipinfo.api.request.MapRequest; 16 | import okhttp3.*; 17 | 18 | import javax.annotation.ParametersAreNonnullByDefault; 19 | import java.io.IOException; 20 | import java.lang.reflect.Type; 21 | import java.time.Duration; 22 | import java.util.ArrayList; 23 | import java.util.HashMap; 24 | import java.util.List; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import java.util.concurrent.CountDownLatch; 27 | import java.util.concurrent.TimeUnit; 28 | import java.util.function.BiConsumer; 29 | 30 | public class IPinfo { 31 | private static final int batchMaxSize = 1000; 32 | private static final int batchReqTimeoutDefault = 5; 33 | private static final BatchReqOpts defaultBatchReqOpts = new BatchReqOpts.Builder() 34 | .setBatchSize(batchMaxSize) 35 | .setTimeoutPerBatch(batchReqTimeoutDefault) 36 | .build(); 37 | private final static Gson gson = new Gson(); 38 | 39 | private final OkHttpClient client; 40 | private final Context context; 41 | private final String token; 42 | private final Cache cache; 43 | 44 | IPinfo(OkHttpClient client, Context context, String token, Cache cache) { 45 | this.client = client; 46 | this.context = context; 47 | this.token = token; 48 | this.cache = cache; 49 | } 50 | 51 | public static void main(String... args) { 52 | System.out.println("This library is not meant to be run as a standalone jar."); 53 | System.exit(0); 54 | } 55 | 56 | /** 57 | * Lookup IP information using the IP. 58 | * 59 | * @param ip the ip string to lookup - accepts both ipv4 and ipv6. 60 | * @return IPResponse response from the api. 61 | * @throws RateLimitedException an exception when your api key has been rate limited. 62 | */ 63 | public IPResponse lookupIP(String ip) throws RateLimitedException { 64 | IPResponse response = (IPResponse)cache.get(cacheKey(ip)); 65 | if (response != null) { 66 | return response; 67 | } 68 | 69 | response = new IPRequest(client, token, ip).handle(); 70 | response.setContext(context); 71 | 72 | cache.set(cacheKey(ip), response); 73 | return response; 74 | } 75 | 76 | /** 77 | * Lookup ASN information using the AS number. 78 | * 79 | * @param asn the asn string to lookup. 80 | * @return ASNResponse response from the api. 81 | * @throws RateLimitedException an exception when your api key has been rate limited. 82 | */ 83 | public ASNResponse lookupASN(String asn) throws RateLimitedException { 84 | ASNResponse response = (ASNResponse)cache.get(cacheKey(asn)); 85 | if (response != null) { 86 | return response; 87 | } 88 | 89 | response = new ASNRequest(client, token, asn).handle(); 90 | response.setContext(context); 91 | 92 | cache.set(cacheKey(asn), response); 93 | return response; 94 | } 95 | 96 | /** 97 | * Get a map of a list of IPs. 98 | * 99 | * @param ips the list of IPs to map. 100 | * @return String the URL to the map. 101 | * @throws RateLimitedException an exception when your API key has been rate limited. 102 | */ 103 | public String getMap(List ips) throws RateLimitedException { 104 | MapResponse response = new MapRequest(client, token, ips).handle(); 105 | return response.getReportUrl(); 106 | } 107 | 108 | /** 109 | * Get the result of a list of URLs in bulk. 110 | * 111 | * @param urls the list of URLs. 112 | * @return the result where each URL is the key and the value is the data for that URL. 113 | * @throws RateLimitedException an exception when your API key has been rate limited. 114 | */ 115 | public ConcurrentHashMap getBatch( 116 | List urls 117 | ) throws RateLimitedException { 118 | return this.getBatchGeneric(urls, defaultBatchReqOpts); 119 | } 120 | 121 | /** 122 | * Get the result of a list of URLs in bulk. 123 | * 124 | * @param urls the list of URLs. 125 | * @param opts options to modify the behavior of the batch operation. 126 | * @return the result where each URL is the key and the value is the data for that URL. 127 | * @throws RateLimitedException an exception when your API key has been rate limited. 128 | */ 129 | public ConcurrentHashMap getBatch( 130 | List urls, 131 | BatchReqOpts opts 132 | ) throws RateLimitedException { 133 | return this.getBatchGeneric(urls, opts); 134 | } 135 | 136 | /** 137 | * Get the result of a list of IPs in bulk. 138 | * 139 | * @param ips the list of IPs. 140 | * @return the result where each IP is the key and the value is the data for that IP. 141 | * @throws RateLimitedException an exception when your API key has been rate limited. 142 | */ 143 | public ConcurrentHashMap getBatchIps( 144 | List ips 145 | ) throws RateLimitedException { 146 | return new ConcurrentHashMap(this.getBatchGeneric(ips, defaultBatchReqOpts)); 147 | } 148 | 149 | /** 150 | * Get the result of a list of IPs in bulk. 151 | * 152 | * @param ips the list of IPs. 153 | * @param opts options to modify the behavior of the batch operation. 154 | * @return the result where each IP is the key and the value is the data for that IP. 155 | * @throws RateLimitedException an exception when your API key has been rate limited. 156 | */ 157 | public ConcurrentHashMap getBatchIps( 158 | List ips, 159 | BatchReqOpts opts 160 | ) throws RateLimitedException { 161 | return new ConcurrentHashMap(this.getBatchGeneric(ips, opts)); 162 | } 163 | 164 | /** 165 | * Get the result of a list of ASNs in bulk. 166 | * 167 | * @param asns the list of ASNs. 168 | * @return the result where each ASN is the key and the value is the data for that ASN. 169 | * @throws RateLimitedException an exception when your API key has been rate limited. 170 | */ 171 | public ConcurrentHashMap getBatchAsns( 172 | List asns 173 | ) throws RateLimitedException { 174 | return new ConcurrentHashMap(this.getBatchGeneric(asns, defaultBatchReqOpts)); 175 | } 176 | 177 | /** 178 | * Get the result of a list of ASNs in bulk. 179 | * 180 | * @param asns the list of ASNs. 181 | * @param opts options to modify the behavior of the batch operation. 182 | * @return the result where each ASN is the key and the value is the data for that ASN. 183 | * @throws RateLimitedException an exception when your API key has been rate limited. 184 | */ 185 | public ConcurrentHashMap getBatchAsns( 186 | List asns, 187 | BatchReqOpts opts 188 | ) throws RateLimitedException { 189 | return new ConcurrentHashMap(this.getBatchGeneric(asns, opts)); 190 | } 191 | 192 | private ConcurrentHashMap getBatchGeneric( 193 | List urls, 194 | BatchReqOpts opts 195 | ) throws RateLimitedException { 196 | int batchSize; 197 | int timeoutPerBatch; 198 | List lookupUrls; 199 | ConcurrentHashMap result; 200 | 201 | // if the cache is available, filter out URLs already cached. 202 | result = new ConcurrentHashMap<>(urls.size()); 203 | if (this.cache != null) { 204 | lookupUrls = new ArrayList<>(urls.size()/2); 205 | for (String url : urls) { 206 | Object val = cache.get(cacheKey(url)); 207 | if (val != null) { 208 | result.put(url, val); 209 | } else { 210 | lookupUrls.add(url); 211 | } 212 | } 213 | } else { 214 | lookupUrls = urls; 215 | } 216 | 217 | // everything cached; exit early. 218 | if (lookupUrls.size() == 0) { 219 | return result; 220 | } 221 | 222 | // use correct batch size; default/clip to `batchMaxSize`. 223 | if (opts.batchSize == 0 || opts.batchSize > batchMaxSize) { 224 | batchSize = batchMaxSize; 225 | } else { 226 | batchSize = opts.batchSize; 227 | } 228 | 229 | // use correct timeout per batch; either default or user-provided. 230 | if (opts.timeoutPerBatch == 0) { 231 | timeoutPerBatch = batchReqTimeoutDefault; 232 | } else { 233 | timeoutPerBatch = opts.timeoutPerBatch; 234 | } 235 | 236 | // prep URL we'll target. 237 | // add `filter=1` as qparam for filtering out empty results on server. 238 | String postUrl; 239 | if (opts.filter) { 240 | postUrl = "https://ipinfo.io/batch?filter=1"; 241 | } else { 242 | postUrl = "https://ipinfo.io/batch"; 243 | } 244 | 245 | // prepare latch & common request. 246 | // each request, when complete, will countdown the latch. 247 | CountDownLatch latch = new CountDownLatch((int)Math.ceil(lookupUrls.size()/1000.0)); 248 | Request.Builder reqCommon = new Request.Builder() 249 | .url(postUrl) 250 | .addHeader("Content-Type", "application/json") 251 | .addHeader("Authorization", Credentials.basic(token, "")) 252 | .addHeader("User-Agent", "IPinfoClient/Java/3.2.0"); 253 | 254 | for (int i = 0; i < lookupUrls.size(); i += batchSize) { 255 | // create chunk. 256 | int end = i + batchSize; 257 | if (end > lookupUrls.size()) { 258 | end = lookupUrls.size(); 259 | } 260 | List urlsChunk = lookupUrls.subList(i, end); 261 | 262 | // prepare & queue up request. 263 | String urlListJson = gson.toJson(urlsChunk); 264 | RequestBody requestBody = RequestBody.create(null, urlListJson); 265 | Request req = reqCommon.post(requestBody).build(); 266 | OkHttpClient chunkClient = client.newBuilder() 267 | .connectTimeout(timeoutPerBatch, TimeUnit.SECONDS) 268 | .readTimeout(timeoutPerBatch, TimeUnit.SECONDS) 269 | .build(); 270 | chunkClient.newCall(req).enqueue(new Callback() { 271 | @Override 272 | @ParametersAreNonnullByDefault 273 | public void onFailure(Call call, IOException e) { 274 | latch.countDown(); 275 | } 276 | 277 | @Override 278 | @ParametersAreNonnullByDefault 279 | public void onResponse(Call call, Response response) throws IOException { 280 | if (response.body() == null || response.code() == 429) { 281 | return; 282 | } 283 | 284 | Type respType = new TypeToken>() {}.getType(); 285 | HashMap localResult 286 | = gson.fromJson(response.body().string(), respType); 287 | localResult.forEach(new BiConsumer() { 288 | @Override 289 | public void accept(String k, Object v) { 290 | if (k.startsWith("AS")) { 291 | String vStr = gson.toJson(v); 292 | ASNResponse vCasted = gson.fromJson(vStr, ASNResponse.class); 293 | vCasted.setContext(context); 294 | result.put(k, vCasted); 295 | } else if (InetAddresses.isInetAddress(k)) { 296 | String vStr = gson.toJson(v); 297 | IPResponse vCasted = gson.fromJson(vStr, IPResponse.class); 298 | vCasted.setContext(context); 299 | result.put(k, vCasted); 300 | } else { 301 | result.put(k, v); 302 | } 303 | } 304 | }); 305 | 306 | latch.countDown(); 307 | } 308 | }); 309 | } 310 | 311 | // wait for all requests to finish. 312 | try { 313 | if (opts.timeoutTotal == 0) { 314 | latch.await(); 315 | } else { 316 | boolean success = latch.await(opts.timeoutTotal, TimeUnit.SECONDS); 317 | if (!success) { 318 | if (result.size() == 0) { 319 | return null; 320 | } else { 321 | return result; 322 | } 323 | } 324 | } 325 | } catch (InterruptedException e) { 326 | if (result.size() == 0) { 327 | return null; 328 | } else { 329 | return result; 330 | } 331 | } 332 | 333 | // insert any new lookups into the cache: 334 | if (cache != null) { 335 | for (String url : lookupUrls) { 336 | Object v = result.get(url); 337 | if (v != null) { 338 | cache.set(cacheKey(url), v); 339 | } 340 | } 341 | } 342 | 343 | return result; 344 | } 345 | 346 | /** 347 | * Converts a normal key into a versioned cache key. 348 | * 349 | * @param k the key to convert into a versioned cache key. 350 | * @return the versioned cache key. 351 | */ 352 | public static String cacheKey(String k) { 353 | return k+":1"; 354 | } 355 | 356 | public static class Builder { 357 | private OkHttpClient client = new OkHttpClient.Builder().build(); 358 | private String token = ""; 359 | private Cache cache = new SimpleCache(Duration.ofDays(1)); 360 | 361 | public Builder setClient(OkHttpClient client) { 362 | this.client = client; 363 | return this; 364 | } 365 | 366 | public Builder setToken(String token) { 367 | this.token = token; 368 | return this; 369 | } 370 | 371 | public Builder setCache(Cache cache) { 372 | this.cache = cache; 373 | return this; 374 | } 375 | 376 | public IPinfo build() { 377 | return new IPinfo(client, new Context(), token, cache); 378 | } 379 | } 380 | 381 | public static class BatchReqOpts { 382 | public final int batchSize; 383 | public final int timeoutPerBatch; 384 | public final int timeoutTotal; 385 | public final boolean filter; 386 | 387 | public BatchReqOpts( 388 | int batchSize, 389 | int timeoutPerBatch, 390 | int timeoutTotal, 391 | boolean filter 392 | ) { 393 | this.batchSize = batchSize; 394 | this.timeoutPerBatch = timeoutPerBatch; 395 | this.timeoutTotal = timeoutTotal; 396 | this.filter = filter; 397 | } 398 | 399 | public static class Builder { 400 | private int batchSize = 1000; 401 | private int timeoutPerBatch = 5; 402 | private int timeoutTotal = 0; 403 | private boolean filter = false; 404 | 405 | /** 406 | * batchSize is the internal batch size used per API request; the IPinfo 407 | * API has a maximum batch size, but the batch request functions available 408 | * in this library do not. Therefore the library chunks the input slices 409 | * internally into chunks of size `batchSize`, clipping to the maximum 410 | * allowed by the IPinfo API. 411 | * 412 | * 0 means to use the default batch size which is the max allowed by the 413 | * IPinfo API. 414 | * 415 | * @param batchSize see description. 416 | * @return the builder. 417 | */ 418 | public Builder setBatchSize(int batchSize) { 419 | this.batchSize = batchSize; 420 | return this; 421 | } 422 | 423 | /** 424 | * timeoutPerBatch is the timeout in seconds that each batch of size 425 | * `BatchSize` will have for its own request. 426 | * 427 | * 0 means to use a default of 5 seconds; any negative number will turn it 428 | * off; turning it off does _not_ disable the effects of `timeoutTotal`. 429 | * 430 | * @param timeoutPerBatch see description. 431 | * @return the builder. 432 | */ 433 | public Builder setTimeoutPerBatch(int timeoutPerBatch) { 434 | this.timeoutPerBatch = timeoutPerBatch; 435 | return this; 436 | } 437 | 438 | /** 439 | * timeoutTotal is the total timeout in seconds for all batch requests in a 440 | * batch request function to complete. 441 | * 442 | * 0 means no total timeout; `timeoutPerBatch` will still apply. 443 | * 444 | * @param timeoutTotal see description. 445 | * @return the builder. 446 | */ 447 | public Builder setTimeoutTotal(int timeoutTotal) { 448 | this.timeoutTotal = timeoutTotal; 449 | return this; 450 | } 451 | 452 | /** 453 | * filter, if turned on, will filter out a URL whose value was deemed empty 454 | * on the server. 455 | * 456 | * @param filter see description. 457 | * @return the builder. 458 | */ 459 | public Builder setFilter(boolean filter) { 460 | this.filter = filter; 461 | return this; 462 | } 463 | 464 | public IPinfo.BatchReqOpts build() { 465 | return new IPinfo.BatchReqOpts(batchSize, timeoutPerBatch, timeoutTotal, filter); 466 | } 467 | } 468 | } 469 | } 470 | --------------------------------------------------------------------------------