1 {-# LANGUAGE DeriveDataTypeable, DeriveGeneric #-}
5 import Control
.Applicative
((<$>))
6 import Control
.Monad
((<=<))
7 import Data
.Data
(Data
)
8 import Data
.Maybe (mapMaybe)
9 import Data
.Monoid
(Monoid
(..))
10 import Data
.String (IsString
(..))
11 import Data
.Typeable
(Typeable
)
12 import GHC
.Generics
(Generic
)
13 import System
.IO (IOMode(ReadMode
), withFile
)
16 import Data
.Aeson
(ToJSON
(..), FromJSON
(..))
19 import qualified Data
.ByteString
.Base16
as B16
22 import Data
.ByteString
(ByteString
)
23 -- import qualified Data.ByteString as BS
24 import Data
.ByteString
.Builder
(toLazyByteString
, intDec
)
25 import qualified Data
.ByteString
.Char8
as CS
26 import Data
.ByteString
.Lazy
(fromChunks
, toStrict
)
27 import qualified Data
.ByteString
.Lazy
.Char8
as LCS
30 import qualified Data
.CaseInsensitive
as CI
33 import qualified Crypto
.Hash
.SHA512
as SHA512
34 import Crypto
.MAC
.HMAC
(hmac
)
37 import Control
.DeepSeq
(NFData
(..))
40 import Network
.HTTP
.Client
41 ( RequestBody
(RequestBodyBS
)
42 , parseUrl
, method
, secure
, requestHeaders
, requestBody
, cookieJar
44 , withResponse
, responseVersion
, responseStatus
, responseHeaders
45 , responseCookieJar
, responseBody
, brConsume
49 import Network
.HTTP
.Client
.TLS
(tlsManagerSettings
)
52 import Network
.HTTP
.Types
(SimpleQuery
, renderSimpleQuery
)
53 import Network
.HTTP
.Types
.Method
(methodPost
)
56 import Pipes
.Aeson
(DecodingError
)
57 import qualified Pipes
.Aeson
as PA
60 import Pipes
.ByteString
(fromHandle
)
63 import Data
.Text
(Text
)
64 import qualified Data
.Text
as T
65 import Data
.Text
.Encoding
(decodeLatin1
)
68 import Data
.Time
.Clock
.POSIX
(getPOSIXTime
)
71 import Control
.Monad
.Trans
.State
.Strict
(evalStateT
)
74 -- import Cryptsy.API.Public
77 url
= "https://api.cryptsy.com/api"
79 apiKeyFilename
:: FilePath
80 apiKeyFilename
= "apiKey.json"
82 cryptsyMethod
:: String
83 cryptsyMethod
= "getinfo"
85 data Latin1String
= Latin1
{ toBytes
:: ByteString
} deriving (Eq
, Ord
, Data
, Typeable
)
87 instance Read Latin1String
where
88 readsPrec n
= mapMaybe canDecode
. readsPrec n
90 canDecode
(txt
, r
) = do
91 thext
<- completeDecode txt
94 instance Show Latin1String
where
95 showsPrec n
= showsPrec n
. toBytes
98 instance IsString Latin1String
where
99 fromString str
= case completeDecode
$ fromString str
of
101 Nothing
-> error "Incomplete decode"
103 instance Monoid Latin1String
where
104 mempty
= Latin1
{ toBytes
= mempty
}
105 mappend l r
= Latin1
{ toBytes
= toBytes l `mappend` toBytes r
}
106 mconcat ths
= Latin1
{ toBytes
= mconcat
$ map toBytes ths
}
108 instance NFData Latin1String
where
111 decode
:: Text
-> (Latin1String
, Text
)
112 decode txt
= (Latin1
. CS
.pack
$ T
.unpack l1c
, uc
)
114 (l1c
, uc
) = T
.span
(< toEnum 256) txt
116 completeDecode
:: Monad m
=> Text
-> m Latin1String
117 completeDecode txt
= if T
.null remainder
119 else fail "Incomplete decode."
120 where (thext
, remainder
) = decode txt
122 encode
:: Latin1String
-> Text
123 encode
= decodeLatin1
. toBytes
125 instance ToJSON Latin1String
where
126 toJSON
= toJSON
. encode
128 instance FromJSON Latin1String
where
129 parseJSON
= completeDecode
<=< parseJSON
131 data APIKey
= APIKey
{ public
:: Latin1String
, private
:: Latin1String
}
132 deriving (Eq
, Ord
, Show, Read, Data
, Typeable
, Generic
)
134 instance ToJSON APIKey
where
136 instance FromJSON APIKey
where
138 -- TODO: Error on incomplete parse
139 fromJSONFile
:: FromJSON a
=> FilePath -> IO (Either DecodingError a
)
140 fromJSONFile f
= withFile f ReadMode
$ evalStateT PA
.decode
. fromHandle
142 -- Seconds since POSIX epoch.
144 mkNonce
= floor <$> getPOSIXTime
148 query
= [ ( CS
.pack
"method", CS
.pack
"getinfo" ) ]
151 sha512_blocksize_bytes
:: Int
152 sha512_blocksize_bytes
= 128
154 hmac_sha512
:: ByteString
-> ByteString
-> ByteString
155 hmac_sha512
= hmac SHA512
.hash sha512_blocksize_bytes
159 Right apiKey
<- fromJSONFile apiKeyFilename
160 plainReq
<- parseUrl url
163 postData
= renderSimpleQuery
False $ query
++ [ ( CS
.pack
"nonce", toStrict
. toLazyByteString
$ intDec nonce
) ]
165 { method
= methodPost
168 [ ( CI
.mk
$ CS
.pack
"Content-Type", CS
.pack
"application/x-www-form-urlencoded" )
169 , ( CI
.mk
$ CS
.pack
"Key", toBytes
$ public apiKey
)
170 , ( CI
.mk
$ CS
.pack
"Sign", B16
.encode
$ hmac_sha512
(toBytes
$ private apiKey
) postData
)
171 , ( CI
.mk
$ CS
.pack
"User-Agent", CS
.pack
"Network.HTTP.Client.TLS" )
173 , requestBody
= RequestBodyBS postData
174 , cookieJar
= Just mempty
178 withManager tlsManagerSettings
$ \manager
->
179 withResponse req manager
$ \response
-> do
180 print $ responseStatus response
181 print $ responseVersion response
182 print $ responseHeaders response
183 brConsume
(responseBody response
) >>= putStrLn . LCS
.unpack
. fromChunks
184 print $ responseCookieJar response