Skip to content

Commit

Permalink
[report]: add daily report
Browse files Browse the repository at this point in the history
  • Loading branch information
vkopitsa committed Aug 13, 2023
1 parent d6bb567 commit c2bed4b
Show file tree
Hide file tree
Showing 19 changed files with 573 additions and 134 deletions.
2 changes: 2 additions & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ TELEGRAM_TOKEN=
MONO_TOKENS=
TELEGRAM_ADMINS=
TELEGRAM_CHATS=
# <minute> <hour> <day> <month> <weekday>
SCHEDULE_TIME= 0 21 * * *

# More info https://github.com/rs/zerolog#leveled-logging
LOG_LEVEL=info
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.18.x
go-version: 1.21.x

- uses: actions/checkout@v3

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.18.x
go-version: 1.21.x
- uses: actions/checkout@v3

- uses: actions/cache@v3
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# builder
FROM golang:1.18-alpine as builder
FROM golang:1.21-alpine as builder

WORKDIR /

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ A simple telegram bot, written in Go with the [telegram-bot-api](https://github.

![mono_personal_tgbot](Resources/screenshot.png)

![mono_personal_tgbot](Resources/screenshot1.png)

## Usage

Run `mono_personal_tgbot` execution file in your terminal with following env variables
Expand All @@ -19,6 +21,7 @@ Run `mono_personal_tgbot` execution file in your terminal with following env var
`TELEGRAM_TOKEN` | [How to get telegram bot token](https://core.telegram.org/bots#3-how-do-i-create-a-bot)
`TELEGRAM_ADMINS` | ids of the trusted user, example: `1234567,1234567`
`TELEGRAM_CHATS` | ids of the trusted chats, example: `-1234567,-1234567`
`SCHEDULE_TIME` | set time for daily report, example: `0 21 * * *`
`MONO_TOKENS` | [How to get monobank token](https://api.monobank.ua/)

### Telegram commands
Expand Down
Binary file added Resources/screenshot1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,23 @@ func main() {
log.Panic().Err(err)
}

// init Schedule Report
isScheduleReportEnabled := os.Getenv("SCHEDULE_TIME") != ""
var scheduleReport *ScheduleReport
if isScheduleReportEnabled {
scheduleReport, err = NewScheduleReport(os.Getenv("SCHEDULE_TIME"))
if err != nil {
log.Panic().Err(err)
}
}

go bot.TelegramStart(os.Getenv("TELEGRAM_TOKEN"))
go bot.ProcessingStart()

if isScheduleReportEnabled {
go scheduleReport.Start(bot.ScheduleReport)
}

// run http server
bot.WebhookStart()
}
57 changes: 34 additions & 23 deletions bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -11,6 +12,7 @@ import (
"reflect"
"strconv"
"strings"
"time"

"github.com/rs/zerolog/log"

Expand All @@ -32,6 +34,7 @@ type Bot interface {
TelegramStart(token string)
WebhookStart()
ProcessingStart()
ScheduleReport(ctx context.Context) (int, error)
}

// bot is implementation the Bot interface
Expand All @@ -47,6 +50,8 @@ type bot struct {
statementTmpl *template.Template
balanceTmpl *template.Template
webhookTmpl *template.Template

mono *Mono
}

// New returns a bot object.
Expand Down Expand Up @@ -76,6 +81,7 @@ func New(telegramAdmins, telegramChats string) Bot {
statementTmpl: statementTmpl,
balanceTmpl: balanceTmpl,
webhookTmpl: webhookTmpl,
mono: NewMono(),
}

return &b
Expand All @@ -90,7 +96,7 @@ func (b *bot) InitMonoClients(monoTokens string) error {
clients := make([]Client, 0, len(monoTokensArr))
for _, monoToken := range monoTokensArr {

client := NewClient(monoToken)
client := NewClient(monoToken, b.mono)
if err := client.Init(); err != nil {
return err
}
Expand Down Expand Up @@ -448,7 +454,12 @@ func (b *bot) WebhookStart() {
fmt.Fprintf(w, "Ok!")
})

err := http.ListenAndServe(":8080", nil)
server := &http.Server{
Addr: ":8080",
ReadHeaderTimeout: 5 * time.Minute,
}

err := server.ListenAndServe()
if err != nil {
log.Panic().Err(err).Msg("[webhook] serve")
}
Expand All @@ -457,23 +468,6 @@ func (b *bot) WebhookStart() {
// ProcessingStart starts processing data that received from chennal.
func (b *bot) ProcessingStart() {

sendTo := func(chatIds, message string) error {
ids := strings.Split(strings.Trim(chatIds, " "), ",")
for _, id := range ids {
chatID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return err
}

_, err = b.BotAPI.Send(tgbotapi.NewMessage(chatID, message))
if err != nil {
return err
}
}

return nil
}

for {
statementItemData := <-b.ch

Expand Down Expand Up @@ -508,14 +502,14 @@ func (b *bot) ProcessingStart() {
message := tpl.String()

// to chat
err = sendTo(b.telegramChats, message)
err = b.sendTo(b.telegramChats, message)
if err != nil {
log.Error().Err(err).Msg("[processing] send to chat")
continue
}

// to admin
err = sendTo(b.telegramAdmins, message)
err = b.sendTo(b.telegramAdmins, message)
if err != nil {
log.Error().Err(err).Msg("[processing] send to admin")
continue
Expand Down Expand Up @@ -603,7 +597,7 @@ func (b bot) getClientByAccountID(id string) (Client, error) {
}

func (b *bot) buildBalanceByClient(client Client) (string, error) {
clientInfo, err := client.GetInfo()
clientInfo, err := client.Clear().GetInfo()
if err != nil {
return "", err
}
Expand Down Expand Up @@ -634,6 +628,23 @@ func (b *bot) sendBalanceByClient(client Client, tgMessage *tgbotapi.Message) er
return err
}

func (b *bot) sendTo(chatIds, message string) error {
ids := strings.Split(strings.Trim(chatIds, " "), ",")
for _, id := range ids {
chatID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return err
}

_, err = b.BotAPI.Send(tgbotapi.NewMessage(chatID, message))
if err != nil {
return err
}
}

return nil
}

func sendAccountButtonsEditMessage(prefix string, client Client, message tgbotapi.Message) (*tgbotapi.EditMessageTextConfig, error) {
messageConfig, inlineKeyboardMarkup, _ := buildAccountButtons[tgbotapi.EditMessageTextConfig](prefix, client, message)
messageConfig.Text = fmt.Sprintf("%s\nВиберіть рахунок:", client.GetName())
Expand Down Expand Up @@ -674,7 +685,7 @@ func buildAccountButtons[V tgbotapi.EditMessageTextConfig | tgbotapi.MessageConf
})

buttons = append(buttons, tgbotapi.InlineKeyboardButton{
Text: fmt.Sprintf("%s%s", NormalizePrice(account.Balance), GetCurrencySymbol(account.CurrencyCode)),
Text: account.GetName(),
CallbackData: &callbackData,
})
}
Expand Down
106 changes: 26 additions & 80 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ import (
"errors"
"fmt"
"hash/fnv"
"net/http"
"strings"
"time"

"github.com/rs/zerolog/log"

"golang.org/x/time/rate"
)

// StatementItem is a statement data
Expand Down Expand Up @@ -64,6 +59,7 @@ type Client interface {
GetStatement(command, accountId string) ([]StatementItem, error)
SetWebHook(url string) (WebHookResponse, error)
GetName() string
Clear() Client

ResetReport(accountId string)
GetAccountByID(id string) (*Account, error)
Expand All @@ -73,26 +69,27 @@ type client struct {
Info *ClientInfo
id uint32
token string
limiter *rate.Limiter
reports map[string]Report
mono *Mono
}

// NewClient returns a client object.
func NewClient(token string) Client {
func NewClient(token string, mono *Mono) Client {

h := fnv.New32a()
h.Write([]byte(token))

return &client{
limiter: rate.NewLimiter(rate.Every(time.Second*30), 1),
token: token,
id: h.Sum32(),
reports: make(map[string]Report),
mono: mono,
}
}

func (c *client) Init() error {
_, err := c.GetInfo()
info, err := c.GetInfo()
c.Info = &info
return err
}

Expand All @@ -102,26 +99,25 @@ func (c client) GetID() uint32 {

func (c *client) GetReport(accountId string) Report {
if _, ok := c.reports[accountId]; !ok {
c.reports[accountId] = NewReport(accountId, c.id)
account, err := c.GetAccountByID(accountId)
if err != nil {
return nil
}
c.reports[accountId] = NewReport(account, c.id)
}

return c.reports[accountId]
}

func (c *client) GetInfo() (ClientInfo, error) {
if c.limiter.Allow() {
log.Debug().Msg("[monoapi] get info")
info, err := c.getClientInfo()
c.Info = &info
return info, err
}

if c.Info != nil {
return *c.Info, nil
}

log.Warn().Msg("[monoapi] get info, waiting")
return ClientInfo{}, errors.New("please waiting and then try again")
log.Debug().Msg("[monoapi] get info")
info, err := c.mono.GetClientInfo(c.token)
c.Info = &info
return *c.Info, err
}

// GetName return name of the client
Expand All @@ -132,22 +128,16 @@ func (c client) GetName() string {
return c.Info.Name
}

// SetWebHook is a function set up the monobank webhook.
func (c client) SetWebHook(url string) (WebHookResponse, error) {
response := WebHookResponse{}
// Clear clear vars of the client
func (c *client) Clear() Client {
c.Info = nil

payload := strings.NewReader(fmt.Sprintf("{\"webHookUrl\": \"%s\"}", url))

req, err := http.NewRequest("POST", "https://api.monobank.ua/personal/webhook", payload)
if err != nil {
log.Error().Err(err).Msg("[monoapi] webhook, NewRequest")
return response, err
}

req.Header.Add("X-Token", c.token)
req.Header.Add("content-type", "application/json")
return c
}

return DoRequest(response, req)
// SetWebHook is a function set up the monobank webhook.
func (c client) SetWebHook(url string) (WebHookResponse, error) {
return c.mono.SetWebHook(url, c.token)
}

func (c *client) GetAccountByID(id string) (*Account, error) {
Expand All @@ -167,53 +157,9 @@ func (c *client) ResetReport(accountId string) {
}

func (c client) GetStatement(command string, accountId string) ([]StatementItem, error) {
if c.limiter.Allow() {
return c.getStatement(command, accountId)
}

log.Warn().Msg("[monoapi] statement, waiting")
return []StatementItem{}, errors.New("please waiting and then try again")
}

func (c client) getStatement(command, account string) ([]StatementItem, error) {

statementItems := []StatementItem{}

from, to, err := getTimeRangeByPeriod(command)
if err != nil {
log.Error().Err(err).Msg("[monoapi] statements, range")
return statementItems, err
}

log.Debug().Msgf("[monoapi] statements, range from: %d, to: %d", from, to)

url := fmt.Sprintf("https://api.monobank.ua/personal/statement/%s/%d", account, from)
if to > 0 {
url = fmt.Sprintf("%s/%d", url, to)
}

req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Error().Err(err).Msg("[monoapi] statements, NewRequest")
return statementItems, err
}

req.Header.Add("x-token", c.token)

return DoRequest(statementItems, req)
return c.mono.GetStatement(command, accountId, c.token)
}

func (c client) getClientInfo() (ClientInfo, error) {
var clientInfo ClientInfo

url := "https://api.monobank.ua/personal/client-info"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Error().Err(err).Msg("[monoapi] client info, create request")
return clientInfo, err
}

req.Header.Add("x-token", c.token)

return DoRequest(clientInfo, req)
func (c Account) GetName() string {
return fmt.Sprintf("%s %s", c.Type, GetCurrencySymbol(c.CurrencyCode))
}
Loading

0 comments on commit c2bed4b

Please sign in to comment.