{-# LANGUAGE DeriveGeneric,OverloadedStrings,DuplicateRecordFields #-} {-| Module : IPGeolocation Description : IP2Location.io Haskell package Copyright : (c) IP2Location, 2025 License : MIT Maintainer : sales@ip2location.com Stability : experimental This module allows users to query an IP address to get geolocation & proxy info. IP2Location.io API subscription at https://www.ip2location.io -} module IPGeolocation (IPResult(..), ResponseObj(..), ErrorObj(..), ErrorInfo(..), ASInfo(..), Continent(..), Translation(..), Country(..), Currency(..), Language(..), Region(..), City(..), TimeZoneInfo(..), GeoTargeting(..), ProxyObj(..), lookUpIP) where import Control.Exception import System.Exit import Data.Aeson as DA import Data.Aeson.Types (Result(..), Parser) import Network.HTTP.Client import Network.HTTP.Client.TLS (tlsManagerSettings) import Network.HTTP.Types.Status (statusCode) import Network.URI.Encode as URIE import Configuration import GHC.Generics (Generic) import Control.Applicative ((<|>)) -- | AS info data ASInfo = ASInfo { as_number :: String, as_name :: String, as_domain :: String, as_usage_type :: String, as_cidr :: String } deriving (Show, Generic) -- | Translation data Translation = Translation { lang :: Maybe String, value :: Maybe String } deriving (Show, Generic) -- | Continent data Continent = Continent { name :: String, code :: String, hemisphere :: [String], translation :: Translation } deriving (Show, Generic) -- | Currency data Currency = Currency { code :: String, name :: String, symbol :: String } deriving (Show, Generic) -- | Language data Language = Language { code :: String, name :: String } deriving (Show, Generic) -- | Country data Country = Country { name :: String, alpha3_code :: String, numeric_code :: Int, demonym :: String, flag :: String, capital :: String, total_area :: Int, population :: Int, currency :: Currency, language :: Language, tld :: String, translation :: Translation } deriving (Show, Generic) -- | Region data Region = Region { name :: String, code :: String, translation :: Translation } deriving (Show, Generic) -- | City data City = City { name :: String, translation :: Translation } deriving (Show, Generic) -- | Time zone info data TimeZoneInfo = TimeZoneInfo { olson :: String, current_time :: String, gmt_offset :: Int, is_dst :: Bool, abbreviation :: String, dst_start_date :: String, dst_end_date :: String, sunrise :: String, sunset :: String } deriving (Show, Generic) -- | Geotargeting data GeoTargeting = GeoTargeting { metro :: Maybe String } deriving (Show, Generic) -- | Proxy data ProxyObj = ProxyObj { last_seen :: Int, proxy_type :: String, threat :: String, provider :: String, is_vpn :: Bool, is_tor :: Bool, is_data_center :: Bool, is_public_proxy :: Bool, is_web_proxy :: Bool, is_web_crawler :: Bool, is_residential_proxy :: Bool, is_consumer_privacy_network :: Bool, is_enterprise_private_network :: Bool, is_spammer :: Bool, is_scanner :: Bool, is_botnet :: Bool, is_bogon :: Bool } deriving (Show, Generic) -- | Main response type data ResponseObj = ResponseObj { ip :: String, country_code :: String, country_name :: String, region_name :: String, city_name :: String, latitude :: Float, longitude :: Float, zip_code :: String, time_zone :: String, asn :: String, as :: String, as_info :: Maybe ASInfo, isp :: Maybe String, domain :: Maybe String, net_speed :: Maybe String, idd_code :: Maybe String, area_code :: Maybe String, weather_station_code :: Maybe String, weather_station_name :: Maybe String, mcc :: Maybe String, mnc :: Maybe String, mobile_brand :: Maybe String, elevation :: Maybe Int, usage_type :: Maybe String, address_type :: Maybe String, continent :: Maybe Continent, country :: Maybe Country, region :: Maybe Region, city :: Maybe City, time_zone_info :: Maybe TimeZoneInfo, geotargeting :: Maybe GeoTargeting, ads_category :: Maybe String, ads_category_name :: Maybe String, district :: Maybe String, is_proxy :: Bool, fraud_score :: Maybe Int, proxy :: Maybe ProxyObj } deriving (Show, Generic) -- | Define the error detail structure data ErrorInfo = ErrorInfo { error_code :: Int, error_message :: String } deriving (Show, Generic) -- | Define the error structure data ErrorObj = ErrorObj { error :: ErrorInfo } deriving (Show, Generic) -- | Define the wrapper type for Response or Error data IPResult = IPResponse ResponseObj | IPError ErrorObj deriving (Show, Generic) -- Derive FromJSON instances instance FromJSON ASInfo instance FromJSON Translation instance FromJSON Continent instance FromJSON Currency instance FromJSON Language instance FromJSON Country instance FromJSON Region instance FromJSON City instance FromJSON TimeZoneInfo instance FromJSON GeoTargeting instance FromJSON ProxyObj instance FromJSON ResponseObj instance FromJSON ErrorInfo instance FromJSON ErrorObj instance FromJSON IPResult where parseJSON v = (IPResponse <$> parseJSON v) <|> (IPError <$> parseJSON v) {-| The 'lookUpIP' function returns an IPResult containing geolocation & proxy data for an IP address It takes 3 arguments; the API configuration, either IPv4 or IPv6 address (String), lang -} lookUpIP :: Config -> String -> String -> IO IPResult lookUpIP myconfig ip lang = do let format = "json" let mySource = source myconfig let myVersion = version myconfig let myKey = apiKey myconfig let langStr = if lang == "" then "" else "&lang=" ++ (URIE.encode lang) let url = "https://api.ip2location.io/?key=" ++ (URIE.encode myKey) ++ "&source=" ++ mySource ++ "&source-version=" ++ myVersion ++ "&format=" ++ format ++ "&ip=" ++ (URIE.encode ip) ++ langStr manager <- newManager tlsManagerSettings httprequest <- parseRequest $ url httpresponse <- httpLbs httprequest manager let json = responseBody httpresponse case eitherDecode json of Right result -> return result Left err -> die("ERROR: " ++ err)