Use monobank api package

This commit is contained in:
Illya Marchenko 2024-04-10 16:21:55 +03:00
parent 476191a828
commit 0f2a98ed07
Signed by: stuzer05
GPG Key ID: A6ABAAA9268F9F4F
14 changed files with 60 additions and 209 deletions

10
go.mod

@ -3,9 +3,15 @@ module stuzer.link/monobank-firefly3-bot
go 1.22.2 go 1.22.2
require ( require (
gitea.stuzer.link/stuzer05/go-firefly3.git v0.0.0-20240410091455-7a9f9e825950 gitea.stuzer.link/stuzer05/go-firefly3 v0.0.0-20240410091657-a7ef9d02ecd9
github.com/antihax/optional v1.0.0 github.com/antihax/optional v1.0.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/vtopc/go-monobank v0.21.0
) )
require golang.org/x/oauth2 v0.19.0 // indirect require (
github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 // indirect
github.com/vtopc/epoch v1.3.0 // indirect
github.com/vtopc/go-rest v0.3.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
)

28
go.sum

@ -1,10 +1,34 @@
gitea.stuzer.link/stuzer05/go-firefly3.git v0.0.0-20240410091455-7a9f9e825950 h1:pVK3w7DcomY/twRVAgXors2XgiPqOM/1iudMRbDLPfs= gitea.stuzer.link/stuzer05/go-firefly3 v0.0.0-20240410091657-a7ef9d02ecd9 h1:YudeO7GocpdRDagqvVjbYEPXK3X2JSwYp2ZCnKyo5jQ=
gitea.stuzer.link/stuzer05/go-firefly3.git v0.0.0-20240410091455-7a9f9e825950/go.mod h1:r5Ijsq//fNw+3RqJ9XSZLtHkWxrm04G3qwR/SAjbpdY= gitea.stuzer.link/stuzer05/go-firefly3 v0.0.0-20240410091657-a7ef9d02ecd9/go.mod h1:t1fQbrfn3dmJPLVsoX3Pof9/3xcSBIYpYl/Iz6qBH5E=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU=
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY=
github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vtopc/epoch v1.3.0 h1:grB1J6FJVfIMbK6GY0LpOO86R4rQZ4oVwHa08USBPKA=
github.com/vtopc/epoch v1.3.0/go.mod h1:oQJwg81RJ2bCV01D4ImQ+cwpcRDUNc66x/9A/6I2RYU=
github.com/vtopc/go-monobank v0.21.0 h1:pW3mX7XvhJIZjJEGEzVFcPI+cD3yAIir/fFA5ei4Zlo=
github.com/vtopc/go-monobank v0.21.0/go.mod h1:gTC/lAouMpz9WfHnXF3RC0JdEPZwnl7P4vBG+htKT3c=
github.com/vtopc/go-rest v0.3.0 h1:kP+5UNeTVQxwRX3tXe/XByuntwZmKArv5/uMdEf1WKo=
github.com/vtopc/go-rest v0.3.0/go.mod h1:t7XPUz57Z+U66vsVIjEm89Uw/NZf486JVAFhq6+mZps=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

12
http.go

@ -3,30 +3,30 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
monobank "github.com/vtopc/go-monobank"
"io" "io"
"net/http" "net/http"
monobank "stuzer.link/monobank-firefly3-bot/monobank/api/webhook/models"
) )
func readRequestBody(r *http.Request) (monobank.Transaction, error) { func readRequestBody(r *http.Request) (monobank.WebHookResponse, error) {
// read body bytes // read body bytes
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
return monobank.Transaction{}, err return monobank.WebHookResponse{}, err
} }
LogString(string(body)) LogString(string(body))
// check empty body // check empty body
if len(string(body)) == 0 { if len(string(body)) == 0 {
return monobank.Transaction{}, errors.New("empty body") return monobank.WebHookResponse{}, errors.New("empty body")
} }
// parse body // parse body
var transaction monobank.Transaction var transaction monobank.WebHookResponse
err = json.Unmarshal(body, &transaction) err = json.Unmarshal(body, &transaction)
if err != nil { if err != nil {
return monobank.Transaction{}, err return monobank.WebHookResponse{}, err
} }
return transaction, nil return transaction, nil

23
main.go

@ -1,10 +1,11 @@
package main package main
import ( import (
"bytes" "context"
"flag" "flag"
"fmt" "fmt"
"github.com/joho/godotenv" "github.com/joho/godotenv"
monobank "github.com/vtopc/go-monobank"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -61,24 +62,12 @@ func main() {
webhookUrl := `https://` + os.Getenv("MONOBANK_WEBHOOK_DOMAIN") + webhookLocalUrl webhookUrl := `https://` + os.Getenv("MONOBANK_WEBHOOK_DOMAIN") + webhookLocalUrl
// register monobank webhook // register monobank webhook
req, err := http.NewRequest("POST", "https://api.monobank.ua/personal/webhook", bytes.NewBuffer([]byte(`{"webHookUrl":"`+webhookUrl+`"}`))) monobankClient := monobank.NewPersonalClient(nil).WithAuth(monobank.NewPersonalAuthorizer(os.Getenv("MONOBANK_TOKEN")))
err := monobankClient.SetWebHook(context.Background(), webhookUrl)
if err != nil { if err != nil {
log.Fatalf(err.Error()) log.Fatalln("failed to register monobank webhook")
} }
req.Header.Add("Content-Type", "application/json")
req.Header.Add("X-Token", os.Getenv("MONOBANK_TOKEN"))
res, err := (&http.Client{}).Do(req)
if err != nil {
log.Fatalf(err.Error())
}
if res.StatusCode != http.StatusOK {
log.Fatalf("failed to register monobank webhook")
}
res.Body.Close()
// set webhook // set webhook
http.HandleFunc(webhookLocalUrl, handleWebhook) http.HandleFunc(webhookLocalUrl, handleWebhook)
@ -87,7 +76,7 @@ func main() {
fmt.Println("webhook url " + webhookUrl) fmt.Println("webhook url " + webhookUrl)
err = http.ListenAndServe(os.Getenv("LISTEN"), nil) err = http.ListenAndServe(os.Getenv("LISTEN"), nil)
if err != nil { if err != nil {
log.Fatalf(err.Error()) log.Fatalln(err.Error())
} }
} }
} }

@ -1,32 +0,0 @@
package models
type ClientInfo struct {
ClientID string `json:"clientId"`
Name string `json:"name"`
WebHookURL string `json:"webHookUrl"`
Permissions string `json:"permissions"`
Accounts []Account `json:"accounts_list"`
Jars []Jar `json:"jars"`
}
type Account struct {
ID string `json:"id"`
SendID string `json:"sendId"`
Balance int `json:"balance"`
CreditLimit int `json:"creditLimit"`
Type string `json:"type"`
CurrencyCode int `json:"currencyCode"`
CashbackType string `json:"cashbackType"`
MaskedPan []string `json:"maskedPan"`
Iban string `json:"iban"`
}
type Jar struct {
ID string `json:"id"`
SendID string `json:"sendId"`
Title string `json:"title"`
Description string `json:"description"`
CurrencyCode int `json:"currencyCode"`
Balance int `json:"balance"`
Goal int `json:"goal"`
}

@ -1,19 +0,0 @@
package requests
import (
"encoding/json"
"main/monobank"
models2 "main/monobank/api/client_info/models"
)
func ClientInfo() (models2.ClientInfo, error) {
data := models2.ClientInfo{}
responseJson, err := monobank.Request("GET", "https://api.monobank.ua/personal/client-info", struct{}{})
if err != nil {
return data, err
}
json.Unmarshal([]byte(responseJson), &data)
return data, nil
}

@ -1,7 +0,0 @@
package models
type StatementRequest struct {
Account string `json:"account"`
From int `json:"from"`
To int `json:"to"`
}

@ -1,22 +0,0 @@
package models
type Statement struct {
ID string `json:"id"`
Time int `json:"time"`
Description string `json:"description"`
Mcc int `json:"mcc"`
OriginalMcc int `json:"originalMcc"`
Hold bool `json:"hold"`
Amount int `json:"amount"`
OperationAmount int `json:"operationAmount"`
CurrencyCode int `json:"currencyCode"`
CommissionRate int `json:"commissionRate"`
CashbackAmount int `json:"cashbackAmount"`
Balance int `json:"balance"`
Comment string `json:"comment"`
ReceiptID string `json:"receiptId"`
InvoiceID string `json:"invoiceId"`
CounterEdrpou string `json:"counterEdrpou"`
CounterIban string `json:"counterIban"`
CounterName string `json:"counterName"`
}

@ -1,20 +0,0 @@
package requests
import (
"encoding/json"
"main/monobank"
models2 "main/monobank/api/statement/models"
"strconv"
)
func Statement(request models2.StatementRequest) ([]models2.Statement, error) {
data := []models2.Statement{}
responseJson, err := monobank.Request("GET", "https://api.monobank.ua/personal/statement/"+request.Account+"/"+strconv.Itoa(request.From)+"/"+strconv.Itoa(request.To), struct{}{})
if err != nil {
return data, err
}
json.Unmarshal([]byte(responseJson), &data)
return data, nil
}

@ -1,6 +0,0 @@
package models
type Data struct {
Account string `json:"account"`
StatementItem StatementItem `json:"statementItem"`
}

@ -1,16 +0,0 @@
package models
type StatementItem struct {
ID string `json:"id"`
Time int `json:"time"`
Description string `json:"description"`
Mcc int `json:"mcc"`
OriginalMcc int `json:"originalMcc"`
Amount int `json:"amount"`
OperationAmount int `json:"operationAmount"`
CurrencyCode int `json:"currencyCode"`
CommissionRate int `json:"commissionRate"`
CashbackAmount int `json:"cashbackAmount"`
Balance int `json:"balance"`
Hold bool `json:"hold"`
}

@ -1,6 +0,0 @@
package models
type Transaction struct {
Type string `json:"type"`
Data Data `json:"data"`
}

@ -1,37 +0,0 @@
package monobank
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
)
func Request(method string, url string, data interface{}) (string, error) {
apiMonabankToken := os.Getenv("MONOBANK_TOKEN")
json, err := json.Marshal(data)
if err != nil {
return "", err
}
r, err := http.NewRequest(method, url, bytes.NewBuffer(json))
if err != nil {
return "", err
}
r.Header.Add("Accept", "application/vnd.api+json")
r.Header.Add("Content-Type", "application/json")
r.Header.Add("X-Token", apiMonabankToken)
client := &http.Client{}
res, err := client.Do(r)
if err != nil {
return "", err
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
return string(body), nil
}

@ -5,27 +5,24 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"gitea.stuzer.link/stuzer05/go-firefly3.git" firefly3 "gitea.stuzer.link/stuzer05/go-firefly3"
"github.com/antihax/optional" "github.com/antihax/optional"
monobank "github.com/vtopc/go-monobank"
"math" "math"
"net/http" "net/http"
"os" "os"
"slices" "slices"
"strconv" "strconv"
monobank "stuzer.link/monobank-firefly3-bot/monobank/api/webhook/models"
"time" "time"
) )
func handleWebhook(w http.ResponseWriter, r *http.Request) { func handleWebhook(w http.ResponseWriter, r *http.Request) {
var err error
firefly3TransactionTypeWithdrawal := firefly3.WITHDRAWAL_TransactionTypeProperty firefly3TransactionTypeWithdrawal := firefly3.WITHDRAWAL_TransactionTypeProperty
firefly3TransactionTypeDeposit := firefly3.DEPOSIT_TransactionTypeProperty firefly3TransactionTypeDeposit := firefly3.DEPOSIT_TransactionTypeProperty
firefly3TransactionTypeTransfer := firefly3.TRANSFER_TransactionTypeProperty firefly3TransactionTypeTransfer := firefly3.TRANSFER_TransactionTypeProperty
// read request // read request
var monobankTransaction monobank.Transaction monobankTransaction, err := readRequestBody(r)
monobankTransaction, err = readRequestBody(r)
if err != nil { if err != nil {
LogString(err.Error()) LogString(err.Error())
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@ -50,11 +47,11 @@ func handleWebhook(w http.ResponseWriter, r *http.Request) {
} }
// find accounts // find accounts
account := ConfigGetAccountByMonobankId(config, monobankTransaction.Data.Account) account := ConfigGetAccountByMonobankId(config, monobankTransaction.Data.AccountID)
// cancel if one of account ids is empty // cancel if one of account ids is empty
if len(account.Firefly3Name) == 0 || len(account.MonobankId) == 0 { if len(account.Firefly3Name) == 0 || len(account.MonobankId) == 0 {
LogString("cannot find firefly3 or monobank ids (" + monobankTransaction.Data.Account + ")") LogString("cannot find firefly3 or monobank ids (" + monobankTransaction.Data.AccountID + ")")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
return return
} }
@ -72,7 +69,7 @@ func handleWebhook(w http.ResponseWriter, r *http.Request) {
for _, row := range config.TransactionTypes { for _, row := range config.TransactionTypes {
// is refund // is refund
if slices.Contains(row.NamesRefund, monobankTransaction.Data.StatementItem.Description) { if slices.Contains(row.NamesRefund, monobankTransaction.Data.Transaction.Description) {
opts := firefly3.TransactionsApiListTransactionOpts{ opts := firefly3.TransactionsApiListTransactionOpts{
Limit: optional.NewInt32(999), Limit: optional.NewInt32(999),
Type_: optional.NewInterface("withdrawal"), Type_: optional.NewInterface("withdrawal"),
@ -107,9 +104,9 @@ func handleWebhook(w http.ResponseWriter, r *http.Request) {
} }
// find transaction // find transaction
sum := int(math.Abs(math.Round(float64(monobankTransaction.Data.StatementItem.Amount/100)))) - int(math.Abs(math.Round(float64(monobankTransaction.Data.StatementItem.CommissionRate/100)))) sum := int(math.Abs(math.Round(float64(monobankTransaction.Amount/100)))) - int(math.Abs(math.Round(float64(monobankTransaction.CommissionRate/100))))
sum2, _ := strconv.ParseFloat(tRow.Amount, 64) sum2, _ := strconv.ParseFloat(tRow.Amount, 64)
if slices.Contains(row.Names, monobankTransaction.Data.StatementItem.Description) && sum == int(sum2) { if slices.Contains(row.Names, monobankTransaction.Description) && sum == int(sum2) {
// delete transaction // delete transaction
opts := firefly3.TransactionsApiDeleteTransactionOpts{} opts := firefly3.TransactionsApiDeleteTransactionOpts{}
firefly3Client.TransactionsApi.DeleteTransaction(context.Background(), tRows.Id, &opts) firefly3Client.TransactionsApi.DeleteTransaction(context.Background(), tRows.Id, &opts)
@ -121,15 +118,15 @@ func handleWebhook(w http.ResponseWriter, r *http.Request) {
break break
} else { } else {
// check name & mcc // check name & mcc
if !(slices.Contains(row.Names, monobankTransaction.Data.StatementItem.Description) || slices.Contains(row.MccCodes, monobankTransaction.Data.StatementItem.Mcc)) { if !(slices.Contains(row.Names, monobankTransaction.Data.Transaction.Description) || slices.Contains(row.MccCodes, int(monobankTransaction.Data.Transaction.MCC))) {
continue continue
} }
// create firefly3 transaction // create firefly3 transaction
firefly3Transaction := firefly3.TransactionSplitStore{ firefly3Transaction := firefly3.TransactionSplitStore{
Date: time.Unix(int64(monobankTransaction.Data.StatementItem.Time), 0).Add(time.Hour * 2), Date: time.Unix(monobankTransaction.Data.Transaction.Time.Unix(), 0).Add(time.Hour * 2),
Notes: string(monobankTransactionJson), Notes: string(monobankTransactionJson),
Amount: strconv.Itoa(int(math.Abs(math.Round(float64(monobankTransaction.Data.StatementItem.Amount/100)))) - int(math.Abs(math.Round(float64(monobankTransaction.Data.StatementItem.CommissionRate/100))))), Amount: strconv.Itoa(int(math.Abs(math.Round(float64(monobankTransaction.Data.Transaction.Amount/100)))) - int(math.Abs(math.Round(float64(monobankTransaction.Data.Transaction.CommissionRate/100))))),
SourceName: account.Firefly3Name, SourceName: account.Firefly3Name,
} }
@ -166,13 +163,13 @@ func handleWebhook(w http.ResponseWriter, r *http.Request) {
break break
} }
} }
if monobankTransaction.Data.StatementItem.CommissionRate > 0 { if monobankTransaction.Data.Transaction.CommissionRate > 0 {
firefly3Transactions = append(firefly3Transactions, firefly3.TransactionSplitStore{ firefly3Transactions = append(firefly3Transactions, firefly3.TransactionSplitStore{
Type_: &firefly3TransactionTypeWithdrawal, Type_: &firefly3TransactionTypeWithdrawal,
Date: time.Unix(int64(monobankTransaction.Data.StatementItem.Time), 0).Add(time.Hour * 2), Date: time.Unix(monobankTransaction.Data.Transaction.Time.Unix(), 0).Add(time.Hour * 2),
Notes: string(monobankTransactionJson), Notes: string(monobankTransactionJson),
Description: "Transfer fee", Description: "Transfer fee",
Amount: strconv.Itoa(int(math.Abs(math.Round(float64(monobankTransaction.Data.StatementItem.CommissionRate / 100))))), Amount: strconv.Itoa(int(math.Abs(math.Round(float64(monobankTransaction.Data.Transaction.CommissionRate / 100))))),
SourceName: account.Firefly3Name, SourceName: account.Firefly3Name,
}) })
} }