-- | This modules exports everything needed for retrieving global
-- IP addresses.
--
-- @since 0.1
module Pythia.Services.GlobalIp
  ( -- * Queries
    queryGlobalIp,
    queryGlobalIpv4,
    queryGlobalIpv6,

    -- * Types
    IpType (..),
    IpAddress (..),

    -- ** Configuration
    GlobalIpv4Config,
    GlobalIpv6Config,
    GlobalIpBothConfig,
    GlobalIpConfig (..),
    GlobalIpApp (..),
    UrlSource (..),
  )
where

import Data.Char qualified as Char
import Data.Text qualified as T
import Pythia.Data.Command (Command)
import Pythia.Internal.ShellApp qualified as ShellApp
import Pythia.Prelude
import Pythia.Services.GlobalIp.Types
  ( GlobalIpApp (GlobalIpAppCurl, GlobalIpAppDig),
    GlobalIpBothConfig,
    GlobalIpConfig (MkGlobalIpConfig, app, sources),
    GlobalIpv4Config,
    GlobalIpv6Config,
    UrlSource (MkUrlSource, unUrlSource),
  )
import Pythia.Services.Types.Network
  ( IpAddress (MkIpAddress),
    IpRefinement,
    IpType (Ipv4, Ipv6),
  )
import Refined (Predicate, Refined)
import Refined qualified as R

-- | Queries for IPv4 and IPv6 global IP address based on the configuration.
--
-- @since 0.1
queryGlobalIp ::
  ( MonadCatch m,
    MonadTypedProcess m
  ) =>
  GlobalIpBothConfig ->
  m (IpAddress Ipv4, IpAddress Ipv6)
queryGlobalIp :: forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpBothConfig -> m (IpAddress 'Ipv4, IpAddress 'Ipv6)
queryGlobalIp = Lens' GlobalIpBothConfig GlobalIpApp
-> Lens' GlobalIpBothConfig ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> (GlobalIpApp
    -> ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
    -> m (IpAddress 'Ipv4, IpAddress 'Ipv6))
-> GlobalIpBothConfig
-> m (IpAddress 'Ipv4, IpAddress 'Ipv6)
forall config sources (m :: Type -> Type) result.
Lens' config GlobalIpApp
-> Lens' config sources
-> (GlobalIpApp -> sources -> m result)
-> config
-> m result
queryGlobalIp' Lens' GlobalIpBothConfig GlobalIpApp
#app Lens' GlobalIpBothConfig ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
#sources GlobalIpApp
-> ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> m (IpAddress 'Ipv4, IpAddress 'Ipv6)
forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp
-> ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> m (IpAddress 'Ipv4, IpAddress 'Ipv6)
getBothIps
{-# INLINEABLE queryGlobalIp #-}

-- | 'queryGlobalIp' restricted to IPv4 address only.
--
-- @since 0.1
queryGlobalIpv4 ::
  ( MonadCatch m,
    MonadTypedProcess m
  ) =>
  GlobalIpv4Config ->
  m (IpAddress Ipv4)
queryGlobalIpv4 :: forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpv4Config -> m (IpAddress 'Ipv4)
queryGlobalIpv4 = Lens' GlobalIpv4Config GlobalIpApp
-> Lens' GlobalIpv4Config [UrlSource 'Ipv4]
-> (GlobalIpApp -> [UrlSource 'Ipv4] -> m (IpAddress 'Ipv4))
-> GlobalIpv4Config
-> m (IpAddress 'Ipv4)
forall config sources (m :: Type -> Type) result.
Lens' config GlobalIpApp
-> Lens' config sources
-> (GlobalIpApp -> sources -> m result)
-> config
-> m result
queryGlobalIp' Lens' GlobalIpv4Config GlobalIpApp
#app Lens' GlobalIpv4Config [UrlSource 'Ipv4]
#sources GlobalIpApp -> [UrlSource 'Ipv4] -> m (IpAddress 'Ipv4)
forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp -> [UrlSource 'Ipv4] -> m (IpAddress 'Ipv4)
getIpv4s
{-# INLINEABLE queryGlobalIpv4 #-}

-- | 'queryGlobalIp' restricted to IPv6 address only.
--
-- @since 0.1
queryGlobalIpv6 ::
  ( MonadCatch m,
    MonadTypedProcess m
  ) =>
  GlobalIpv6Config ->
  m (IpAddress Ipv6)
queryGlobalIpv6 :: forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpv6Config -> m (IpAddress 'Ipv6)
queryGlobalIpv6 = Lens' GlobalIpv6Config GlobalIpApp
-> Lens' GlobalIpv6Config [UrlSource 'Ipv6]
-> (GlobalIpApp -> [UrlSource 'Ipv6] -> m (IpAddress 'Ipv6))
-> GlobalIpv6Config
-> m (IpAddress 'Ipv6)
forall config sources (m :: Type -> Type) result.
Lens' config GlobalIpApp
-> Lens' config sources
-> (GlobalIpApp -> sources -> m result)
-> config
-> m result
queryGlobalIp' Lens' GlobalIpv6Config GlobalIpApp
#app Lens' GlobalIpv6Config [UrlSource 'Ipv6]
#sources GlobalIpApp -> [UrlSource 'Ipv6] -> m (IpAddress 'Ipv6)
forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp -> [UrlSource 'Ipv6] -> m (IpAddress 'Ipv6)
getIpv6s
{-# INLINEABLE queryGlobalIpv6 #-}

queryGlobalIp' ::
  Lens' config GlobalIpApp ->
  Lens' config sources ->
  (GlobalIpApp -> sources -> m result) ->
  config ->
  m result
queryGlobalIp' :: forall config sources (m :: Type -> Type) result.
Lens' config GlobalIpApp
-> Lens' config sources
-> (GlobalIpApp -> sources -> m result)
-> config
-> m result
queryGlobalIp' Lens' config GlobalIpApp
appLens Lens' config sources
sourceLens GlobalIpApp -> sources -> m result
getIpFn config
config =
  GlobalIpApp -> sources -> m result
getIpFn (config
config config -> Lens' config GlobalIpApp -> GlobalIpApp
forall k s (is :: IxList) a.
Is k A_Getter =>
s -> Optic' k is s a -> a
^. Lens' config GlobalIpApp
appLens) (config
config config -> Lens' config sources -> sources
forall k s (is :: IxList) a.
Is k A_Getter =>
s -> Optic' k is s a -> a
^. Lens' config sources
sourceLens)
{-# INLINEABLE queryGlobalIp' #-}

getBothIps ::
  ( MonadCatch m,
    MonadTypedProcess m
  ) =>
  GlobalIpApp ->
  ([UrlSource Ipv4], [UrlSource Ipv6]) ->
  m (IpAddress Ipv4, IpAddress Ipv6)
getBothIps :: forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp
-> ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> m (IpAddress 'Ipv4, IpAddress 'Ipv6)
getBothIps GlobalIpApp
app ([UrlSource 'Ipv4]
ipv4Srcs, [UrlSource 'Ipv6]
ipv6Srcs) =
  (,)
    (IpAddress 'Ipv4
 -> IpAddress 'Ipv6 -> (IpAddress 'Ipv4, IpAddress 'Ipv6))
-> m (IpAddress 'Ipv4)
-> m (IpAddress 'Ipv6 -> (IpAddress 'Ipv4, IpAddress 'Ipv6))
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
<$> GlobalIpApp -> [UrlSource 'Ipv4] -> m (IpAddress 'Ipv4)
forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp -> [UrlSource 'Ipv4] -> m (IpAddress 'Ipv4)
getIpv4s GlobalIpApp
app [UrlSource 'Ipv4]
ipv4Srcs
    m (IpAddress 'Ipv6 -> (IpAddress 'Ipv4, IpAddress 'Ipv6))
-> m (IpAddress 'Ipv6) -> m (IpAddress 'Ipv4, IpAddress 'Ipv6)
forall a b. m (a -> b) -> m a -> m b
forall (f :: Type -> Type) a b.
Applicative f =>
f (a -> b) -> f a -> f b
<*> GlobalIpApp -> [UrlSource 'Ipv6] -> m (IpAddress 'Ipv6)
forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp -> [UrlSource 'Ipv6] -> m (IpAddress 'Ipv6)
getIpv6s GlobalIpApp
app [UrlSource 'Ipv6]
ipv6Srcs
{-# INLINEABLE getBothIps #-}

getIpv4s ::
  ( MonadCatch m,
    MonadTypedProcess m
  ) =>
  GlobalIpApp ->
  [UrlSource Ipv4] ->
  m (IpAddress Ipv4)
getIpv4s :: forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp -> [UrlSource 'Ipv4] -> m (IpAddress 'Ipv4)
getIpv4s GlobalIpApp
app [UrlSource 'Ipv4]
extraSrcs = do
  let sources :: [UrlSource 'Ipv4]
sources = case [UrlSource 'Ipv4]
extraSrcs of
        [] -> GlobalIpApp -> [UrlSource 'Ipv4]
ipv4Defaults GlobalIpApp
app
        [UrlSource 'Ipv4]
_ -> GlobalIpApp -> [UrlSource 'Ipv4] -> [UrlSource 'Ipv4]
forall (a :: IpType). GlobalIpApp -> [UrlSource a] -> [UrlSource a]
prependApp GlobalIpApp
app [UrlSource 'Ipv4]
extraSrcs
  [UrlSource 'Ipv4] -> m (IpAddress 'Ipv4)
forall (m :: Type -> Type) (a :: IpType).
(MonadCatch m, MonadTypedProcess m,
 Predicate (IpRefinement a) Text) =>
[UrlSource a] -> m (IpAddress a)
getIpFromSources [UrlSource 'Ipv4]
sources
{-# INLINEABLE getIpv4s #-}

getIpv6s ::
  ( MonadCatch m,
    MonadTypedProcess m
  ) =>
  GlobalIpApp ->
  [UrlSource Ipv6] ->
  m (IpAddress Ipv6)
getIpv6s :: forall (m :: Type -> Type).
(MonadCatch m, MonadTypedProcess m) =>
GlobalIpApp -> [UrlSource 'Ipv6] -> m (IpAddress 'Ipv6)
getIpv6s GlobalIpApp
app [UrlSource 'Ipv6]
extraSrcs = do
  let sources :: [UrlSource 'Ipv6]
sources = case [UrlSource 'Ipv6]
extraSrcs of
        [] -> GlobalIpApp -> [UrlSource 'Ipv6]
ipv6Defaults GlobalIpApp
app
        [UrlSource 'Ipv6]
_ -> GlobalIpApp -> [UrlSource 'Ipv6] -> [UrlSource 'Ipv6]
forall (a :: IpType). GlobalIpApp -> [UrlSource a] -> [UrlSource a]
prependApp GlobalIpApp
app [UrlSource 'Ipv6]
extraSrcs
  [UrlSource 'Ipv6] -> m (IpAddress 'Ipv6)
forall (m :: Type -> Type) (a :: IpType).
(MonadCatch m, MonadTypedProcess m,
 Predicate (IpRefinement a) Text) =>
[UrlSource a] -> m (IpAddress a)
getIpFromSources [UrlSource 'Ipv6]
sources
{-# INLINEABLE getIpv6s #-}

prependApp :: GlobalIpApp -> [UrlSource a] -> [UrlSource a]
prependApp :: forall (a :: IpType). GlobalIpApp -> [UrlSource a] -> [UrlSource a]
prependApp GlobalIpApp
GlobalIpAppCurl [UrlSource a]
srcs = (UrlSource a -> UrlSource a) -> [UrlSource a] -> [UrlSource a]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap (Optic An_Iso '[] (UrlSource a) (UrlSource a) Text Text
#unUrlSource Optic An_Iso '[] (UrlSource a) (UrlSource a) Text Text
-> (Text -> Text) -> UrlSource a -> UrlSource a
forall k (is :: IxList) s t a b.
Is k A_Setter =>
Optic k is s t a b -> (a -> b) -> s -> t
%~ (Text
"curl " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>)) [UrlSource a]
srcs
prependApp GlobalIpApp
GlobalIpAppDig [UrlSource a]
srcs = (UrlSource a -> UrlSource a) -> [UrlSource a] -> [UrlSource a]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap (Optic An_Iso '[] (UrlSource a) (UrlSource a) Text Text
#unUrlSource Optic An_Iso '[] (UrlSource a) (UrlSource a) Text Text
-> (Text -> Text) -> UrlSource a -> UrlSource a
forall k (is :: IxList) s t a b.
Is k A_Setter =>
Optic k is s t a b -> (a -> b) -> s -> t
%~ (\Text
s -> Text
"dig " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
s Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" +short")) [UrlSource a]
srcs
{-# INLINEABLE prependApp #-}

ipv4Defaults :: GlobalIpApp -> [UrlSource Ipv4]
ipv4Defaults :: GlobalIpApp -> [UrlSource 'Ipv4]
ipv4Defaults GlobalIpApp
GlobalIpAppCurl = ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
curlDefaults ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> Optic'
     A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv4]
-> [UrlSource 'Ipv4]
forall k s (is :: IxList) a.
Is k A_Getter =>
s -> Optic' k is s a -> a
^. Optic'
  A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv4]
forall s t a b. Field1 s t a b => Lens s t a b
_1
ipv4Defaults GlobalIpApp
GlobalIpAppDig = ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
digDefaults ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> Optic'
     A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv4]
-> [UrlSource 'Ipv4]
forall k s (is :: IxList) a.
Is k A_Getter =>
s -> Optic' k is s a -> a
^. Optic'
  A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv4]
forall s t a b. Field1 s t a b => Lens s t a b
_1
{-# INLINEABLE ipv4Defaults #-}

ipv6Defaults :: GlobalIpApp -> [UrlSource Ipv6]
ipv6Defaults :: GlobalIpApp -> [UrlSource 'Ipv6]
ipv6Defaults GlobalIpApp
GlobalIpAppCurl = ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
curlDefaults ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> Optic'
     A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv6]
-> [UrlSource 'Ipv6]
forall k s (is :: IxList) a.
Is k A_Getter =>
s -> Optic' k is s a -> a
^. Optic'
  A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv6]
forall s t a b. Field2 s t a b => Lens s t a b
_2
ipv6Defaults GlobalIpApp
GlobalIpAppDig = ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
digDefaults ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
-> Optic'
     A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv6]
-> [UrlSource 'Ipv6]
forall k s (is :: IxList) a.
Is k A_Getter =>
s -> Optic' k is s a -> a
^. Optic'
  A_Lens '[] ([UrlSource 'Ipv4], [UrlSource 'Ipv6]) [UrlSource 'Ipv6]
forall s t a b. Field2 s t a b => Lens s t a b
_2
{-# INLINEABLE ipv6Defaults #-}

curlDefaults :: ([UrlSource Ipv4], [UrlSource Ipv6])
curlDefaults :: ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
curlDefaults = ([UrlSource 'Ipv4]
ipv4s, [UrlSource 'Ipv6]
forall {a}. [a]
ipv6s)
  where
    ipv4s :: [UrlSource 'Ipv4]
ipv4s =
      [ UrlSource 'Ipv4
"curl http://whatismyip.akamai.com/",
        UrlSource 'Ipv4
"curl http://ifconfig.me/ip",
        UrlSource 'Ipv4
"curl http://myexternalip.com/raw",
        UrlSource 'Ipv4
"curl http://checkip.amazonaws.com/"
      ]
    ipv6s :: [a]
ipv6s = []
{-# INLINEABLE curlDefaults #-}

digDefaults :: ([UrlSource Ipv4], [UrlSource Ipv6])
digDefaults :: ([UrlSource 'Ipv4], [UrlSource 'Ipv6])
digDefaults = ([UrlSource 'Ipv4]
ipv4s, [UrlSource 'Ipv6]
forall {a}. [a]
ipv6s)
  where
    ipv4s :: [UrlSource 'Ipv4]
ipv4s =
      [ UrlSource 'Ipv4
"dig @resolver1.opendns.com myip.opendns.com +short",
        UrlSource 'Ipv4
"dig @resolver2.opendns.com myip.opendns.com +short",
        UrlSource 'Ipv4
"dig @resolver3.opendns.com myip.opendns.com +short",
        UrlSource 'Ipv4
"dig @resolver4.opendns.com myip.opendns.com +short",
        UrlSource 'Ipv4
"dig @ns1-1.akamaitech.net ANY whoami.akamai.net +short",
        UrlSource 'Ipv4
"dig -4 TXT o-o.myaddr.l.google.com @ns1.google.com +short"
      ]
    ipv6s :: [a]
ipv6s = []
{-# INLINEABLE digDefaults #-}

getIpFromSources ::
  ( MonadCatch m,
    MonadTypedProcess m,
    Predicate (IpRefinement a) Text
  ) =>
  [UrlSource a] ->
  m (IpAddress a)
getIpFromSources :: forall (m :: Type -> Type) (a :: IpType).
(MonadCatch m, MonadTypedProcess m,
 Predicate (IpRefinement a) Text) =>
[UrlSource a] -> m (IpAddress a)
getIpFromSources = (Refined (IpRefinement a) Text -> IpAddress a)
-> m (Refined (IpRefinement a) Text) -> m (IpAddress a)
forall a b. (a -> b) -> m a -> m b
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap Refined (IpRefinement a) Text -> IpAddress a
forall (a :: IpType). Refined (IpRefinement a) Text -> IpAddress a
MkIpAddress (m (Refined (IpRefinement a) Text) -> m (IpAddress a))
-> ([UrlSource a] -> m (Refined (IpRefinement a) Text))
-> [UrlSource a]
-> m (IpAddress a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Iso' (UrlSource a) Command
-> [UrlSource a] -> m (Refined (IpRefinement a) Text)
forall (m :: Type -> Type) p a.
(MonadCatch m, MonadTypedProcess m, Predicate p Text) =>
Iso' a Command -> [a] -> m (Refined p Text)
getIp (Optic An_Iso '[] (UrlSource a) (UrlSource a) Text Text
#unUrlSource Optic An_Iso '[] (UrlSource a) (UrlSource a) Text Text
-> Optic (ReversedOptic An_Iso) '[] Text Text Command Command
-> Iso' (UrlSource a) Command
forall k l m (is :: IxList) (js :: IxList) (ks :: IxList) s t u v a
       b.
(JoinKinds k l m, AppendIndices is js ks) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Optic An_Iso '[] Command Command Text Text
-> Optic (ReversedOptic An_Iso) '[] Text Text Command Command
forall (is :: IxList) s t a b.
AcceptsEmptyIndices "re" is =>
Optic An_Iso is s t a b -> Optic (ReversedOptic An_Iso) is b a t s
forall k (is :: IxList) s t a b.
(ReversibleOptic k, AcceptsEmptyIndices "re" is) =>
Optic k is s t a b -> Optic (ReversedOptic k) is b a t s
re Optic An_Iso '[] Command Command Text Text
#unCommand)
{-# INLINEABLE getIpFromSources #-}

getIp ::
  forall m p a.
  ( MonadCatch m,
    MonadTypedProcess m,
    Predicate p Text
  ) =>
  Iso' a Command ->
  [a] ->
  m (Refined p Text)
getIp :: forall (m :: Type -> Type) p a.
(MonadCatch m, MonadTypedProcess m, Predicate p Text) =>
Iso' a Command -> [a] -> m (Refined p Text)
getIp Iso' a Command
cmdIso [a]
cmds = [m (Refined p Text)] -> m (Refined p Text)
forall (m :: Type -> Type) result.
MonadCatch m =>
[m result] -> m result
ShellApp.tryIOs ((a -> m (Refined p Text)) -> [a] -> [m (Refined p Text)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> m (Refined p Text)
go [a]
cmds)
  where
    go :: a -> m (Refined p Text)
go a
cmd = do
      Text
txt <- Command -> m Text
forall (m :: Type -> Type).
(MonadThrow m, MonadTypedProcess m) =>
Command -> m Text
ShellApp.runCommand (Command -> m Text) -> Command -> m Text
forall a b. (a -> b) -> a -> b
$ a
cmd a -> Iso' a Command -> Command
forall k s (is :: IxList) a.
Is k A_Getter =>
s -> Optic' k is s a -> a
^. Iso' a Command
cmdIso
      Text -> m (Refined p Text)
forall {k} (p :: k) x (m :: Type -> Type).
(Predicate p x, MonadThrow m) =>
x -> m (Refined p x)
R.refineThrow (Text -> Text
trim Text
txt)
{-# INLINEABLE getIp #-}

trim :: Text -> Text
trim :: Text -> Text
trim = (Char -> Bool) -> Text -> Text
T.dropAround Char -> Bool
Char.isSpace
{-# INLINEABLE trim #-}