api-monobank (empty) → 0.1.0.0
raw patch · 9 files changed
+452/−0 lines, 9 filesdep +aesondep +api-monobankdep +basesetup-changed
Dependencies added: aeson, api-monobank, base, bytestring, hs-coindesk-api, http-conduit, stm, text, time
Files
- LICENSE +30/−0
- README.md +127/−0
- Setup.hs +2/−0
- api-monobank.cabal +56/−0
- app/Main.hs +33/−0
- src/Api.hs +57/−0
- src/Types.hs +135/−0
- src/Utils.hs +12/−0
- tests/Spec.hs +0/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Sergey Bushnyak (c) 2019++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Sergey Bushnyak nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,127 @@+# Monobank Api Client++Haskell client for MonoBank API services (https://api.monobank.ua/docs/)++++1. [Introduction](#introduction)+2. [Public API](#public)+3. [Private API](#private)+4. [Building library](#building)++## Introduction++API for extracting information and personal account status. To grant access, you must pass the authorization in your personal office at https://api.monobank.ua/ and get a token for personal use.++If you have questions about the operation of the API, we invite to the community in the [Telegram channel](https://t.me/joinchat/FiAEWhDf-QzTqM4wzEtffw).++If you have a service or application and you want to centrally join the API for customer service, you need to connect to a [corporate](https://api.monobank.ua/docs/corporate.html) API that has more features.++This will allow monobank clients to log in to your service (for example, in a financial manager) to provide information about the status of an account or statements.++In the event of the exploitation of this API as corporate, the bank reserves the right to impose sanctions on the company.++## Public+General information provided without authorization.++| # |Path | Type | Params | Description | Response | Notes|+|---|---------------|------|--------|----------------------|----------|------|+| 1 | /bank/currency| GET | | Get a basic list of monobank currency rates. Information is cached and updated no more than once every 5 minutes. | JSON | Response will be a json array with objects of 2 types+++```json+ {+ "currencyCodeA": 978,+ "currencyCodeB": 840,+ "date": 1561426807,+ "rateBuy": 1.1249,+ "rateSell": 1.1368+ },+ {+ "currencyCodeA": 826,+ "currencyCodeB": 980,+ "date": 1561461569,+ "rateCross": 33.2857+ },+```+note the absence of `rateBuy`, `rateSell` in a second example.Inside client system automatically convert `int` base date into normalized `UTCTime` format.+++## Private+Information provided with authorization.++| # |Path | Type | Params | Description | Response | Notes|+|---|----------------------|------|-------------------------------| ---------------------|----------|------|+| 1 | /personal/client-info| GET | | Obtaining information about the client and the list of his accounts. Limit on the use of the function no more than 1 time in 60 seconds.| JSON |+| | | | `X-Token` string; `in header` | Token for personal access to the API | |+| 2 | /personal/statement | GET | /{account}/{from}/{to} | Receive an extract for the time from {to} to {to} time in seconds Unix time format. The maximum time for which it is possible to extract an extract is 31 days (2678400 seconds) Limit on the use of the function no more than 1 time in 60 seconds. | JSON |+| | | | `X-Token` string; in `header` | Token for personal access to the API | |+| | | | `account` string; in `path` | Account ID from the Statement list list or 0 is a default account.+| | | | `from` string; in `path` | Start of the excerpt time+| | | | `to` string; in `path` | End time of the excerpt (if not, the current time will be used)++## Building++- `src` - contains actual API that can be used in 3rd part apps+- `app` - executable for CLI app that can be used to view current value or run continuosly++Just build repository with `stack build` and use following++```+$ stack exec -- mnb-app+```++or simply `mnb-app` if you used `stack install` command++```+$ mnb-app+```+that will bring following output if runned without keys, currency info++```+Monobank | Not tokenized API access available for currencies only+Monobank | Getting last currency prices++USD/UAH+ - Buy: 25.961 ₴+ - Sell: 26.2357₴++EUR/UAH+ - Buy: 29.161 ₴+ - Sell: 29.9294₴++RUB/UAH+ - Buy: 0.378 ₴+ - Sell: 0.418₴++EUR/USD+ - Buy: 1.1249 $+ - Sell: 1.1368$++GBP/UAH+ - Cross: 33.32₴++CHF/UAH+ - Cross: 26.9263₴++BYN/UAH+ - Cross: 12.8902₴++CAD/UAH+ - Cross: 19.8825₴++CZK/UAH+ - Cross: 1.1669₴++DKK/UAH+ - Cross: 4.0001₴++HUF/UAH+ - Cross: 9.22e-2₴++PLN/UAH+ - Cross: 7.012₴++TRY/UAH+ - Cross: 4.5473₴+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ api-monobank.cabal view
@@ -0,0 +1,56 @@+name: api-monobank+version: 0.1.0.0+-- synopsis:+description: Api client for popular Ukrainian bank - Monobank+homepage: https://github.com/sigrlami/api-monobank#readme+license: MIT+license-file: LICENSE+author: Sergey Bushnyak+maintainer: sergey.bushnyak@sigrlami.eu+copyright: Copyright: (c) 2019 Sergey Bushnyak+category: Web+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Api+ , Types+ , Utils+ build-depends: base >= 4.7 && < 5+ , aeson+ , text+ , time+ , http-conduit+ , bytestring+ default-language: Haskell2010++executable mnb-app+ hs-source-dirs: app+ main-is: Main.hs+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends: base+ , bytestring+ , http-conduit+ , aeson+ , stm+ , text+ , time+ , api-monobank+ default-language: Haskell2010++test-suite mnb-app-test+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: Spec.hs+ build-depends: base+ , bytestring+ , hs-coindesk-api+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010+++source-repository head+ type: git+ location: https://github.com/sigrlami/api-monobank
+ app/Main.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Concurrent (forkIO, threadDelay)+import Control.Concurrent.STM.TVar+import Control.Monad (forever)+import qualified Data.Text as T+import Data.Time.Clock+import System.Environment++import qualified Api as MBApi+import qualified Types as MBApi++--------------------------------------------------------------------------------++main :: IO ()+main =+ do+ let delay = 60000 -- 1 request per minute available+ a <- getArgs+ timeVar <- newTVarIO (delay*1000)+ case a of+ ["-t"] -> do+ -- read token from file+ return ()+ otherwise -> do+ putStrLn $ "Monobank | Not tokenized API access available for currencies only"+ putStrLn $ "Monobank | Getting last currency prices\n"+ mcurrs <- MBApi.getCurrencies+ --putStrLn $ show $ mcurrs+ mapM_ MBApi.showCurrencyPair mcurrs+ return ()
+ src/Api.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE OverloadedStrings #-}++module Api+ ( getCurrencies+ , getPersonalInfo+ , getPersonalStatement+ , getPersonalStatementFull+ ) where++import Data.Aeson+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.Text as T+import Network.HTTP.Conduit (simpleHttp)++import Types+import Utils+++--------------------------------------------------------------------------------++endpoint :: T.Text+endpoint = "https://api.monobank.ua"++-- Get a basic list of monobank currency rates.+-- Information is cached and updated no more than once every 5 minutes.+--+-- GET /bank/currency+getCurrencies :: IO [CurrencyPair]+getCurrencies = do+ let path = T.concat [endpoint, "/bank/currency"]+ -- results <- fmap decodeEither $ simpleHttp (T.unpack path)+ resp <- simpleHttp (T.unpack path)+ --putStrLn $ show $ resp+ let res = eitherDecode resp+ case res of+ Left err -> do+ putStrLn err+ return $ []+ Right val -> do+ return $ val++-- GET /personal/client-info+getPersonalInfo = undefined+++-- Receive an extract for the time from {to} to {to} time in seconds Unix time format.+-- The maximum time for which it is possible to extract an extract is 31 days+-- (2678400 seconds) Limit on the use of the function no more than 1 time in 60 seconds.+--+-- GET /personal/statement/{account}/{from}/{to}+getPersonalStatement = undefined+++-- Get Personal Statement from beginning+-- 12 months will take 12 minutes+getPersonalStatementFull = undefined
+ src/Types.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeSynonymInstances #-}++module Types where++import Data.Aeson+import Data.Aeson.TH+import Data.Maybe+import qualified Data.Text as T+import Data.Time+import GHC.Generics++import Utils++-----------------------------------------------------------------++-- | Wrapper for user token from Developer's Dashboard, https://api.monobank.ua/+type Token = T.Text++-----------------------------------------------------------------++data Currency =+ Currency+ { isoCode :: String+ , isoNumericCode :: Int+ , decimalDigits :: Int+ , symbol :: String+ , name :: String+ } deriving (Eq, Show)++convertCurrencyFromCode :: Int -> Currency+convertCurrencyFromCode val =+ case val of+ 840 -> Currency { isoCode = "USD", isoNumericCode= 840, decimalDigits= 2, symbol= "$", name="Dollar"}+ 978 -> Currency { isoCode = "EUR", isoNumericCode= 978, decimalDigits= 2, symbol= "€", name="Euro"}+ 980 -> Currency { isoCode = "UAH", isoNumericCode= 980, decimalDigits= 2, symbol= "₴", name="Hrivnya"}+ 643 -> Currency { isoCode = "RUB", isoNumericCode= 643, decimalDigits= 2, symbol= "", name="Ruble"}+ 826 -> Currency { isoCode = "GBP", isoNumericCode= 826, decimalDigits= 2, symbol= "", name="Pound Sterling"}+ 949 -> Currency { isoCode = "TRY", isoNumericCode= 949, decimalDigits= 2, symbol= "", name="Lira"}+ 985 -> Currency { isoCode = "PLN", isoNumericCode= 985, decimalDigits= 2, symbol= "", name="Zloty"}+ 348 -> Currency { isoCode = "HUF", isoNumericCode= 348, decimalDigits= 2, symbol= "", name="Forint"}+ 208 -> Currency { isoCode = "DKK", isoNumericCode= 208, decimalDigits= 2, symbol= "", name="Danish Krone"}+ 203 -> Currency { isoCode = "CZK", isoNumericCode= 203, decimalDigits= 2, symbol= "", name="Czech Koruna"}+ 124 -> Currency { isoCode = "CAD", isoNumericCode= 124, decimalDigits= 2, symbol= "", name="Canadian Dollar"}+ 933 -> Currency { isoCode = "BYN", isoNumericCode= 933, decimalDigits= 2, symbol= "", name="Belarussian Ruble"}+ 756 -> Currency { isoCode = "CHF", isoNumericCode= 756, decimalDigits= 2, symbol= "", name="Swiss Franc"}+ otherwise -> Currency { isoCode = "NA" , isoNumericCode= 0 , decimalDigits= 2, symbol= "", name="Uknown"}++-------------------------------------------------------------------------------------------++-- | Data type that represents currency pair from Monobank+-- at spicific time+data CurrencyPair =+ CurrencyPair+ { cpCurrencyCodeA :: Currency -- ^ Currency derived from international identificator+ , cpCurrencyCodeB :: Currency -- ^ Currency derived from international identificator+ , cpDate :: Int -- ^ Timestamp for the currency pair information+ , cpRateSell :: Maybe Float -- ^ Rate to sell currency+ , cpRateBuy :: Maybe Float -- ^ Rate to buy currency+ , cpRateCross :: Maybe Float -- ^ Rate+ } deriving (Show)++instance FromJSON CurrencyPair where+ parseJSON =+ withObject "object" $ \o -> do+ currA' <- o .: "currencyCodeA"+ currB' <- o .: "currencyCodeB"+ date' <- o .: "date" -- TODO: convert to UTCTime+ rateSell' <- o .:? "rateSell"+ rateBuy' <- o .:? "rateBuy"+ rateCross' <- o .:? "rateCross"++ let currA = convertCurrencyFromCode currA'+ currB = convertCurrencyFromCode currB'+ --date =++ return $+ CurrencyPair currA currB date' rateSell' rateBuy' rateCross'++showCurrencyPair :: CurrencyPair -> IO ()+showCurrencyPair cp = do+ putStrLn $ (isoCode $ cpCurrencyCodeA cp) ++ "/" ++ (isoCode $ cpCurrencyCodeB cp)+ case (cpRateBuy cp) of+ Nothing -> do+ putStrLn $ " - Cross: " ++ (show $ fromMaybe 0 $ cpRateCross cp) ++ (symbol $ cpCurrencyCodeB cp)+ putStrLn $ ""+ Just buy -> do+ putStrLn $ " - Buy: " ++ (show $ buy) ++ " " ++ (symbol $ cpCurrencyCodeB cp)+ putStrLn $ " - Sell: " ++ (show $ fromMaybe 0 $ cpRateSell cp) ++ (symbol $ cpCurrencyCodeB cp)+ putStrLn $ ""++-- Simplified version for one-to-one conversion+--+-- instance FromJSON CurrencyPair where+-- parseJSON = genericParseJSON opts+-- where+-- opts = defaultOptions { fieldLabelModifier = uncapFst . drop 2}++--------------------------------------------------------------------------------++data Account =+ Account+ { acId :: T.Text -- ^+ , acBalance :: Int -- ^ Current balance+ , acCreditLimit :: Int -- ^ Current credit limit available to user+ , acCurrency :: Currency -- ^ Currency of the account+ , acCashbackType :: T.Text -- ^+ } deriving (Eq, Show)++data User =+ User+ { uName :: String -- ^ User's full name+ , uAccounts :: [Account] -- ^ List of user accounts, by currency type+ } deriving (Eq, Show)++data Statement =+ Statement+ { stId :: String -- ^+ , stTime :: String -- ^+ , stDescription :: String -- ^+ , stMCC :: String -- ^+ , stHold :: Bool -- ^+ , stAmount :: Int -- ^+ , stOperationAmount :: Int -- ^+ , stCurrency :: Currency -- ^+ , stComissionRate :: Int -- ^+ , stCashbackAmount :: Int -- ^+ , balance :: Int -- ^+ } deriving (Eq, Show)
+ src/Utils.hs view
@@ -0,0 +1,12 @@+module Utils where+++import qualified Data.Char as Char+import Data.Text as T++-------------------------------------------------------------------+++uncapFst :: String -> String+uncapFst (head:tail) = Char.toLower head : tail+uncapFst [] = []
+ tests/Spec.hs view