{-# language OverloadedStrings, DeriveGeneric #-}

module BinancePriceFetcher ( fetchADADetails
                           , fetchTicker
                           , receivers
                           ) where

import           Data.Aeson
import qualified Data.ByteString.Lazy as B
import           GHC.Generics
import           Network.HTTP.Conduit   ( simpleHttp )
import qualified Data.Text as T         ( pack
                                        , unpack )
import           Discord                ( DiscordHandler )
import           Discord.Types          ( Message
                                        , messageChannel )
import           UnliftIO               ( liftIO )

import           Command
import           Owoifier               ( owoify )

receivers :: [Message -> DiscordHandler ()]
receivers :: [Message -> DiscordHandler ()]
receivers = (Command DiscordHandler -> Message -> DiscordHandler ())
-> [Command DiscordHandler] -> [Message -> DiscordHandler ()]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Command DiscordHandler -> Message -> DiscordHandler ()
forall (m :: * -> *).
MonadDiscord m =>
Command m -> Message -> m ()
runCommand
    [ Command DiscordHandler
handleTicker
    , Command DiscordHandler
handleAda24h
    ]

data Ticker = Ticker {
    Ticker -> String
symbol              :: String,
    Ticker -> String
priceChange         :: String,
    Ticker -> String
priceChangePercent  :: String,
    Ticker -> String
weightedAvgPrice    :: String,
    Ticker -> String
prevClosePrice      :: String,
    Ticker -> String
lastPrice           :: String,
    Ticker -> String
lastQty             :: String,
    Ticker -> String
bidPrice            :: String,
    Ticker -> String
bidQty              :: String,
    Ticker -> String
askPrice            :: String,
    Ticker -> String
askQty              :: String,
    Ticker -> String
openPrice           :: String,
    Ticker -> String
highPrice           :: String,
    Ticker -> String
lowPrice            :: String,
    Ticker -> String
volume              :: String,
    Ticker -> String
quoteVolume         :: String,
    Ticker -> Integer
openTime            :: Integer,
    Ticker -> Integer
closeTime           :: Integer,
    Ticker -> Integer
firstId             :: Integer,
    Ticker -> Integer
lastId              :: Integer,
    Ticker -> Integer
count               :: Integer
} deriving (Int -> Ticker -> ShowS
[Ticker] -> ShowS
Ticker -> String
(Int -> Ticker -> ShowS)
-> (Ticker -> String) -> ([Ticker] -> ShowS) -> Show Ticker
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Ticker] -> ShowS
$cshowList :: [Ticker] -> ShowS
show :: Ticker -> String
$cshow :: Ticker -> String
showsPrec :: Int -> Ticker -> ShowS
$cshowsPrec :: Int -> Ticker -> ShowS
Show, (forall x. Ticker -> Rep Ticker x)
-> (forall x. Rep Ticker x -> Ticker) -> Generic Ticker
forall x. Rep Ticker x -> Ticker
forall x. Ticker -> Rep Ticker x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Ticker x -> Ticker
$cfrom :: forall x. Ticker -> Rep Ticker x
Generic)

instance FromJSON Ticker
instance ToJSON Ticker

adaEmoji :: String
adaEmoji :: String
adaEmoji = String
"<:ada:805934431071371305>"
-- TODO: allow server to choose emoji through :config

jsonURL :: String -> String -> String
jsonURL :: String -> ShowS
jsonURL String
base String
quote = String
"https://api.binance.com/api/v3/ticker/24hr?symbol=" String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
base String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
quote

sign :: String -> String
sign :: ShowS
sign String
"BUSD"  = String
"$"
sign String
"TUSD"  = String
"$"
sign String
"USDT"  = String
"$"
sign String
"AUD"   = String
"$"
sign String
"CAD"   = String
"$"
sign String
"EUR"   = String
"€"
sign String
"GBP"   = String
"£"
sign String
"JPY"   = String
"¥"

sign String
"ADA"   = String
"₳"
sign String
"BCH"   = String
"Ƀ"
sign String
"BSV"   = String
"Ɓ"
sign String
"BTC"   = String
"₿"
sign String
"DAI"   = String
"◈"
sign String
"DOGE"  = String
"Ð"
sign String
"EOS"   = String
"ε"
sign String
"ETC"   = String
"ξ"
sign String
"ETH"   = String
"Ξ"
sign String
"LTC"   = String
"Ł"
sign String
"MKR"   = String
"Μ"
sign String
"REP"   = String
"Ɍ"
sign String
"STEEM" = String
"ȿ"
sign String
"XMR"   = String
"ɱ"
sign String
"XRP"   = String
"✕"
sign String
"XTZ"   = String
"ꜩ"
sign String
"ZEC"   = String
"ⓩ"

sign String
x       = String
x

getJSON :: String -> String -> IO B.ByteString
getJSON :: String -> String -> IO ByteString
getJSON String
a String
b = String -> IO ByteString
forall (m :: * -> *). MonadIO m => String -> m ByteString
simpleHttp (String -> IO ByteString) -> String -> IO ByteString
forall a b. (a -> b) -> a -> b
$ String -> ShowS
jsonURL String
a String
b

fetchADADetails :: IO (Either String String)
fetchADADetails :: IO (Either String String)
fetchADADetails = do
    Either String String
ticker <- String -> String -> IO (Either String String)
fetchTicker String
"ADA" String
"BUSD"
    Either String String -> IO (Either String String)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String String -> IO (Either String String))
-> Either String String -> IO (Either String String)
forall a b. (a -> b) -> a -> b
$ case Either String String
ticker of
        Left  String
err -> String -> Either String String
forall a b. a -> Either a b
Left String
err
        Right String
str -> String -> Either String String
forall a b. b -> Either a b
Right (String -> Either String String) -> String -> Either String String
forall a b. (a -> b) -> a -> b
$ String
adaEmoji String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" (philcoin) is " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
str

fetchTicker :: String -> String -> IO (Either String String)
fetchTicker :: String -> String -> IO (Either String String)
fetchTicker String
base String
quote = do
    Either String Ticker
detailsM <- (ByteString -> Either String Ticker
forall a. FromJSON a => ByteString -> Either String a
eitherDecode (ByteString -> Either String Ticker)
-> IO ByteString -> IO (Either String Ticker)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> String -> IO ByteString
getJSON String
base String
quote) :: IO (Either String Ticker)
    Either String String -> IO (Either String String)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String String -> IO (Either String String))
-> Either String String -> IO (Either String String)
forall a b. (a -> b) -> a -> b
$ case Either String Ticker
detailsM of
        Left String
err       -> String -> Either String String
forall a b. a -> Either a b
Left String
err
        Right Ticker
details -> do
            let percentChangeD :: Double
percentChangeD = String -> Double
forall a. Read a => String -> a
read (Ticker -> String
priceChangePercent Ticker
details) :: Double
                curPriceD :: Double
curPriceD      = String -> Double
forall a. Read a => String -> a
read (Ticker -> String
lastPrice          Ticker
details) :: Double
                lowPriceD :: Double
lowPriceD      = String -> Double
forall a. Read a => String -> a
read (Ticker -> String
lowPrice           Ticker
details) :: Double
                highPriceD :: Double
highPriceD     = String -> Double
forall a. Read a => String -> a
read (Ticker -> String
highPrice          Ticker
details) :: Double
            String -> Either String String
forall a b. b -> Either a b
Right (String -> Either String String) -> String -> Either String String
forall a b. (a -> b) -> a -> b
$ String -> String -> Double -> Double -> Double -> Double -> String
tickerAnnounce String
base String
quote Double
percentChangeD Double
curPriceD Double
lowPriceD Double
highPriceD

tickerAnnounce :: String -> String -> Double -> Double -> Double -> Double -> String
tickerAnnounce :: String -> String -> Double -> Double -> Double -> Double -> String
tickerAnnounce String
base String
quote Double
percentChange Double
curPrice Double
lowPrice Double
highPrice = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [
      String
"**", if Double
percentChange Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
< Double
0 then String
"down 💢" else String
"up 🚀🚀", String
"** "
    , String
"**", Double -> String
forall a. Show a => a -> String
show (Double -> Double
forall a. Num a => a -> a
abs Double
percentChange), String
"%** in the past 24 hours, "
    , String
"currently sitting at **", ShowS
sign String
base, String
"1** = **"
    , ShowS
sign String
quote, Double -> String
forall a. Show a => a -> String
show Double
curPrice, String
"** per unit.\n"

    , String
"Lowest price in the past 24h: **", ShowS
sign String
quote, Double -> String
forall a. Show a => a -> String
show Double
lowPrice, String
"**.\n"

    , String
"Highest price in the past 24h: **", ShowS
sign String
quote, Double -> String
forall a. Show a => a -> String
show Double
highPrice, String
"**."
    ]


handleTicker :: Command DiscordHandler
handleTicker :: Command DiscordHandler
handleTicker = Text
-> (Message -> String -> String -> DiscordHandler ())
-> Command DiscordHandler
forall (m :: * -> *) h.
(CommandHandlerType m h, MonadDiscord m) =>
Text -> h -> Command m
command Text
"binance" ((Message -> String -> String -> DiscordHandler ())
 -> Command DiscordHandler)
-> (Message -> String -> String -> DiscordHandler ())
-> Command DiscordHandler
forall a b. (a -> b) -> a -> b
$ \Message
m String
base String
quote -> do
    Either String String
announcementM <- IO (Either String String)
-> ReaderT DiscordHandle IO (Either String String)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either String String)
 -> ReaderT DiscordHandle IO (Either String String))
-> IO (Either String String)
-> ReaderT DiscordHandle IO (Either String String)
forall a b. (a -> b) -> a -> b
$ String -> String -> IO (Either String String)
fetchTicker String
base String
quote
    case Either String String
announcementM of
         Left String
err -> do
            IO () -> DiscordHandler ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> DiscordHandler ()) -> IO () -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"[DEBUG] Cannot get ticker from Binance: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
err
            Message -> Text -> DiscordHandler ()
forall (m :: * -> *). MonadDiscord m => Message -> Text -> m ()
respond Message
m (Text -> DiscordHandler ()) -> Text -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ Text -> Text
owoify Text
"Couldn't get the data! Sorry!"
         Right String
announcement ->
            Message -> Text -> DiscordHandler ()
forall (m :: * -> *). MonadDiscord m => Message -> Text -> m ()
respond Message
m (Text -> DiscordHandler ()) -> Text -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ Text -> Text
owoify (Text -> Text) -> (String -> Text) -> String -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ String
base String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"/" String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
quote String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" is "
                                 String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
announcement

handleAda24h :: Command DiscordHandler
handleAda24h :: Command DiscordHandler
handleAda24h =
    Text -> Command DiscordHandler -> Command DiscordHandler
forall (m :: * -> *). Text -> Command m -> Command m
alias Text
"ada24h"
    (Command DiscordHandler -> Command DiscordHandler)
-> ((Message -> DiscordHandler ()) -> Command DiscordHandler)
-> (Message -> DiscordHandler ())
-> Command DiscordHandler
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> (Message -> DiscordHandler ()) -> Command DiscordHandler
forall (m :: * -> *) h.
(CommandHandlerType m h, MonadDiscord m) =>
Text -> h -> Command m
command Text
"ada" ((Message -> DiscordHandler ()) -> Command DiscordHandler)
-> (Message -> DiscordHandler ()) -> Command DiscordHandler
forall a b. (a -> b) -> a -> b
$ \Message
m -> do
    -- owo no indent here needed??? owo???
    Either String String
adaAnnouncementM <- IO (Either String String)
-> ReaderT DiscordHandle IO (Either String String)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO (Either String String)
fetchADADetails
    case Either String String
adaAnnouncementM of
        Left String
err -> do
            IO () -> DiscordHandler ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> DiscordHandler ()) -> IO () -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"[DEBUG] Cannot fetch ADA details from Binance: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
err
            Message -> Text -> DiscordHandler ()
forall (m :: * -> *). MonadDiscord m => Message -> Text -> m ()
respond Message
m (Text -> DiscordHandler ()) -> Text -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ Text -> Text
owoify Text
"Couldn't get the data! Sorry!"
        Right String
announcement ->
            Message -> Text -> DiscordHandler ()
forall (m :: * -> *). MonadDiscord m => Message -> Text -> m ()
respond Message
m (Text -> DiscordHandler ()) -> Text -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ Text -> Text
owoify (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack String
announcement