diff --git a/.gitignore b/.gitignore index 609bc4c..d57b4db 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ server_log.txt # *.sublime-workspace + +iphub_key.inc diff --git a/README.md b/README.md index 0ced505..f7d6bf5 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,47 @@ Include in your code and begin using the library: ## Usage When a player connects, their details are automatically requested. Once the -response has arrived, the functions `GetPlayerCountryCode` and -`GetPlayerCountryName` can be used. +response has arrived, the API can be used. + +`bool:IsGeoDataReady(playerid)` Checks if the data is ready, requests should +only take milliseconds but this ensures your code doesn't attempt to request +data that isn't ready yet. + +The rest of the API corresponds directly to the +[IPHub documentation](https://docs.iphub.info/documentation/json-keys/): + +* `GetPlayerCountryCode(playerid, output[], len = sizeof output)` +* `GetPlayerCountryName(playerid, output[], len = sizeof output)` +* `GetPlayerASN(playerid, &asn)` +* `GetPlayerISP(playerid, output[], len = sizeof output)` +* `GetPlayerIPBlock(playerid, &block)` ## Testing -(There are no unit tests yet) +To test, first you must build build the package with the file `iphub_key.inc` +which should consist of: + +```pawn +#define IPHUB_KEY your_api_key +``` + +You can get a free API key from [this page](https://iphub.info/pricing) by +clicking `Looking for the free plan (2000 req/day)? Here!` + +Then just run as normal and connect to `localhost:7777`: + +```bash +sampctl package run +``` + +If you connect locally, you'll see no useful data because you'll be sending the +loopback interface address, but a response will still appear: + +```bash +text="requesting ip data" addr="127.0.0.1" playerid=0 +text="received ip data" request=0 status=200 +text="determined target player for ip data" playerid=0 +text="extracted ip data for player" code="ZZ" name="Unknown" asn=0 isp="Private/local IP" block=0 +``` + +You can also type `/ip` in-game to see a message containing the data. diff --git a/geoip.inc b/geoip.inc index fb4cf44..a33d37f 100644 --- a/geoip.inc +++ b/geoip.inc @@ -22,14 +22,27 @@ #endif +#if !defined MAX_GEOIP_COUNTRY_CODE + #define MAX_GEOIP_COUNTRY_CODE (4) +#endif + +#if !defined MAX_GEOIP_COUNTRY_NAME + #define MAX_GEOIP_COUNTRY_NAME (128) +#endif + +#if !defined MAX_GEOIP_ISP_NAME + #define MAX_GEOIP_ISP_NAME (128) +#endif + + // E_GEO_DATA matches the iphub response object, minus the IP address itself enum E_GEO_DATA { - bool:E_GEO_READY, // false if response hasn't arrived yet - E_GEO_CODE[4], // "countryCode": "US", - E_GEO_NAME[128], // "countryName": "United States", - E_GEO_ASN, // "asn": 15169, - E_GEO_ISP[128], // "isp": "GOOGLE - Google Inc.", - E_GEO_BLOCK, // "block": 1 + bool:E_GEO_READY, // false if response hasn't arrived yet + E_GEO_CODE[MAX_GEOIP_COUNTRY_CODE], // "countryCode": "US", + E_GEO_NAME[MAX_GEOIP_COUNTRY_NAME], // "countryName": "United States", + E_GEO_ASN, // "asn": 15169, + E_GEO_ISP[MAX_GEOIP_ISP_NAME], // "isp": "GOOGLE - Google Inc.", + E_GEO_BLOCK, // "block": 1 } static GeoData[MAX_PLAYERS][E_GEO_DATA]; @@ -38,23 +51,119 @@ static Map:RequestToPlayer; forward OnGeoResponse(Request:id, E_HTTP_STATUS:status, Node:node); -stock GetPlayerCountryCode(playerid, output[], len) { - // todo + +// - +// API +// - + + +// IsGeoDataReady checks if geo IP data for a player is ready to read yet. +stock bool:IsGeoDataReady(playerid) { + if(!IsPlayerConnected(playerid)) { + return false; + } + return GeoData[playerid][E_GEO_READY]; +} + +stock GetPlayerCountryCode(playerid, output[], len = sizeof output) { + if(!IsPlayerConnected(playerid)) { + return 1; + } + + if(!GeoData[playerid][E_GEO_READY]) { + return 2; + } + + strcat(output, GeoData[playerid][E_GEO_CODE], len); return 0; } -stock GetPlayerCountryName(playerid, output[], len) { - // todo +stock GetPlayerCountryName(playerid, output[], len = sizeof output) { + if(!IsPlayerConnected(playerid)) { + return 1; + } + + if(!GeoData[playerid][E_GEO_READY]) { + return 2; + } + + strcat(output, GeoData[playerid][E_GEO_NAME], len); return 0; } +stock GetPlayerASN(playerid, &asn) { + if(!IsPlayerConnected(playerid)) { + return 1; + } + + if(!GeoData[playerid][E_GEO_READY]) { + return 2; + } + + asn = GeoData[playerid][E_GEO_ASN]; + return 0; +} + +stock GetPlayerISP(playerid, output[], len = sizeof output) { + if(!IsPlayerConnected(playerid)) { + return 1; + } + + if(!GeoData[playerid][E_GEO_READY]) { + return 2; + } + + strcat(output, GeoData[playerid][E_GEO_ISP], len); + return 0; +} + +stock GetPlayerIPBlock(playerid, &block) { + if(!IsPlayerConnected(playerid)) { + return 1; + } + + if(!GeoData[playerid][E_GEO_READY]) { + return 2; + } + + block = GeoData[playerid][E_GEO_BLOCK]; + return 0; +} + + +// - +// Internal +// - + + hook OnScriptInit() { - Client = RequestsClient("http://v2.api.iphub.info/ip/", RequestHeaders("X-Key", IPHUB_KEY)); + new key[] = IPHUB_KEY; + if(strlen(key) == 0) { + fatal("IPHUB_KEY must be set for geoip library"); + } + + Client = RequestsClient("http://v2.api.iphub.info/ip/", RequestHeaders("X-Key", key)); } hook OnPlayerConnect(playerid) { - new addr[18]; + new addr[16]; GetPlayerIp(playerid, addr, sizeof addr); + _geoip_doRequest(addr, playerid); +} + +hook OnPlayerDisconnect(playerid, reason) { + GeoData[playerid][E_GEO_READY] = false; + GeoData[playerid][E_GEO_CODE][0] = EOS; + GeoData[playerid][E_GEO_NAME][0] = EOS; + GeoData[playerid][E_GEO_ASN] = 0; + GeoData[playerid][E_GEO_ISP][0] = EOS; + GeoData[playerid][E_GEO_BLOCK] = 0; +} + +_geoip_doRequest(addr[16], playerid) { + dbg("geoip", "requesting ip data", + _s("addr", addr), + _i("playerid", playerid)); new Request:r = RequestJSON( Client, @@ -67,6 +176,10 @@ hook OnPlayerConnect(playerid) { } public OnGeoResponse(Request:id, E_HTTP_STATUS:status, Node:node) { + dbg("geoip", "received ip data", + _i("request", _:id), + _i("status", _:status)); + if(status != HTTP_STATUS_OK) { err("iphub response status code was not OK", _i("status", _:status)); @@ -78,6 +191,9 @@ public OnGeoResponse(Request:id, E_HTTP_STATUS:status, Node:node) { return; } + dbg("geoip", "determined target player for ip data", + _i("playerid", playerid)); + // { // "ip": "8.8.8.8", // "countryCode": "US", @@ -93,4 +209,12 @@ public OnGeoResponse(Request:id, E_HTTP_STATUS:status, Node:node) { JsonGetInt(node, "asn", GeoData[playerid][E_GEO_ASN]); JsonGetString(node, "isp", GeoData[playerid][E_GEO_ISP], 128); JsonGetInt(node, "block", GeoData[playerid][E_GEO_BLOCK]); + + dbg("geoip", "extracted ip data for player", + _s("code", GeoData[playerid][E_GEO_CODE]), + _s("name", GeoData[playerid][E_GEO_NAME]), + _i("asn", GeoData[playerid][E_GEO_ASN]), + _s("isp", GeoData[playerid][E_GEO_ISP]), + _i("block", GeoData[playerid][E_GEO_BLOCK]) + ); } diff --git a/test.pwn b/test.pwn index 5c52198..9552a3f 100644 --- a/test.pwn +++ b/test.pwn @@ -1,7 +1,39 @@ -#define IPHUB_KEY "123" - +#include "iphub_key" #include "geoip" main() { - // + logger_debug("geoip", true); +} + +public OnPlayerCommandText(playerid, cmdtext[]) { + if(!strcmp(cmdtext, "/ip")) { + if(!IsGeoDataReady(playerid)) { + SendClientMessage(playerid, -1, "Data not ready yet..."); + return 1; + } + + new + code[MAX_GEOIP_COUNTRY_CODE], + name[MAX_GEOIP_COUNTRY_NAME], + asn, + isp[MAX_GEOIP_ISP_NAME], + block, + str[128]; + + GetPlayerCountryCode(playerid, code); + GetPlayerCountryName(playerid, name); + GetPlayerASN(playerid, asn); + GetPlayerISP(playerid, isp); + GetPlayerIPBlock(playerid, block); + + format(str, sizeof str, + "Country: %s (%s), ASN: %d, ISP: '%s', Block: %d", + name, code, asn, isp, block); + + SendClientMessage(playerid, -1, str); + + return 1; + } + + return 0; }