Skip to content

Commit c2bed4b

Browse files
committed
[report]: add daily report
1 parent d6bb567 commit c2bed4b

19 files changed

+573
-134
lines changed

.env.dev

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ TELEGRAM_TOKEN=
77
MONO_TOKENS=
88
TELEGRAM_ADMINS=
99
TELEGRAM_CHATS=
10+
# <minute> <hour> <day> <month> <weekday>
11+
SCHEDULE_TIME= 0 21 * * *
1012

1113
# More info https://github.com/rs/zerolog#leveled-logging
1214
LOG_LEVEL=info

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
steps:
1818
- uses: actions/setup-go@v3
1919
with:
20-
go-version: 1.18.x
20+
go-version: 1.21.x
2121

2222
- uses: actions/checkout@v3
2323

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
steps:
99
- uses: actions/setup-go@v3
1010
with:
11-
go-version: 1.18.x
11+
go-version: 1.21.x
1212
- uses: actions/checkout@v3
1313

1414
- uses: actions/cache@v3

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# builder
2-
FROM golang:1.18-alpine as builder
2+
FROM golang:1.21-alpine as builder
33

44
WORKDIR /
55

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ A simple telegram bot, written in Go with the [telegram-bot-api](https://github.
1010

1111
![mono_personal_tgbot](Resources/screenshot.png)
1212

13+
![mono_personal_tgbot](Resources/screenshot1.png)
14+
1315
## Usage
1416

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

2427
### Telegram commands

Resources/screenshot1.png

33 KB
Loading

app.go

+14
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,23 @@ func main() {
3232
log.Panic().Err(err)
3333
}
3434

35+
// init Schedule Report
36+
isScheduleReportEnabled := os.Getenv("SCHEDULE_TIME") != ""
37+
var scheduleReport *ScheduleReport
38+
if isScheduleReportEnabled {
39+
scheduleReport, err = NewScheduleReport(os.Getenv("SCHEDULE_TIME"))
40+
if err != nil {
41+
log.Panic().Err(err)
42+
}
43+
}
44+
3545
go bot.TelegramStart(os.Getenv("TELEGRAM_TOKEN"))
3646
go bot.ProcessingStart()
3747

48+
if isScheduleReportEnabled {
49+
go scheduleReport.Start(bot.ScheduleReport)
50+
}
51+
3852
// run http server
3953
bot.WebhookStart()
4054
}

bot.go

+34-23
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bytes"
5+
"context"
56
"encoding/json"
67
"errors"
78
"fmt"
@@ -11,6 +12,7 @@ import (
1112
"reflect"
1213
"strconv"
1314
"strings"
15+
"time"
1416

1517
"github.com/rs/zerolog/log"
1618

@@ -32,6 +34,7 @@ type Bot interface {
3234
TelegramStart(token string)
3335
WebhookStart()
3436
ProcessingStart()
37+
ScheduleReport(ctx context.Context) (int, error)
3538
}
3639

3740
// bot is implementation the Bot interface
@@ -47,6 +50,8 @@ type bot struct {
4750
statementTmpl *template.Template
4851
balanceTmpl *template.Template
4952
webhookTmpl *template.Template
53+
54+
mono *Mono
5055
}
5156

5257
// New returns a bot object.
@@ -76,6 +81,7 @@ func New(telegramAdmins, telegramChats string) Bot {
7681
statementTmpl: statementTmpl,
7782
balanceTmpl: balanceTmpl,
7883
webhookTmpl: webhookTmpl,
84+
mono: NewMono(),
7985
}
8086

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

93-
client := NewClient(monoToken)
99+
client := NewClient(monoToken, b.mono)
94100
if err := client.Init(); err != nil {
95101
return err
96102
}
@@ -448,7 +454,12 @@ func (b *bot) WebhookStart() {
448454
fmt.Fprintf(w, "Ok!")
449455
})
450456

451-
err := http.ListenAndServe(":8080", nil)
457+
server := &http.Server{
458+
Addr: ":8080",
459+
ReadHeaderTimeout: 5 * time.Minute,
460+
}
461+
462+
err := server.ListenAndServe()
452463
if err != nil {
453464
log.Panic().Err(err).Msg("[webhook] serve")
454465
}
@@ -457,23 +468,6 @@ func (b *bot) WebhookStart() {
457468
// ProcessingStart starts processing data that received from chennal.
458469
func (b *bot) ProcessingStart() {
459470

460-
sendTo := func(chatIds, message string) error {
461-
ids := strings.Split(strings.Trim(chatIds, " "), ",")
462-
for _, id := range ids {
463-
chatID, err := strconv.ParseInt(id, 10, 64)
464-
if err != nil {
465-
return err
466-
}
467-
468-
_, err = b.BotAPI.Send(tgbotapi.NewMessage(chatID, message))
469-
if err != nil {
470-
return err
471-
}
472-
}
473-
474-
return nil
475-
}
476-
477471
for {
478472
statementItemData := <-b.ch
479473

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

510504
// to chat
511-
err = sendTo(b.telegramChats, message)
505+
err = b.sendTo(b.telegramChats, message)
512506
if err != nil {
513507
log.Error().Err(err).Msg("[processing] send to chat")
514508
continue
515509
}
516510

517511
// to admin
518-
err = sendTo(b.telegramAdmins, message)
512+
err = b.sendTo(b.telegramAdmins, message)
519513
if err != nil {
520514
log.Error().Err(err).Msg("[processing] send to admin")
521515
continue
@@ -603,7 +597,7 @@ func (b bot) getClientByAccountID(id string) (Client, error) {
603597
}
604598

605599
func (b *bot) buildBalanceByClient(client Client) (string, error) {
606-
clientInfo, err := client.GetInfo()
600+
clientInfo, err := client.Clear().GetInfo()
607601
if err != nil {
608602
return "", err
609603
}
@@ -634,6 +628,23 @@ func (b *bot) sendBalanceByClient(client Client, tgMessage *tgbotapi.Message) er
634628
return err
635629
}
636630

631+
func (b *bot) sendTo(chatIds, message string) error {
632+
ids := strings.Split(strings.Trim(chatIds, " "), ",")
633+
for _, id := range ids {
634+
chatID, err := strconv.ParseInt(id, 10, 64)
635+
if err != nil {
636+
return err
637+
}
638+
639+
_, err = b.BotAPI.Send(tgbotapi.NewMessage(chatID, message))
640+
if err != nil {
641+
return err
642+
}
643+
}
644+
645+
return nil
646+
}
647+
637648
func sendAccountButtonsEditMessage(prefix string, client Client, message tgbotapi.Message) (*tgbotapi.EditMessageTextConfig, error) {
638649
messageConfig, inlineKeyboardMarkup, _ := buildAccountButtons[tgbotapi.EditMessageTextConfig](prefix, client, message)
639650
messageConfig.Text = fmt.Sprintf("%s\nВиберіть рахунок:", client.GetName())
@@ -674,7 +685,7 @@ func buildAccountButtons[V tgbotapi.EditMessageTextConfig | tgbotapi.MessageConf
674685
})
675686

676687
buttons = append(buttons, tgbotapi.InlineKeyboardButton{
677-
Text: fmt.Sprintf("%s%s", NormalizePrice(account.Balance), GetCurrencySymbol(account.CurrencyCode)),
688+
Text: account.GetName(),
678689
CallbackData: &callbackData,
679690
})
680691
}

client.go

+26-80
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,8 @@ import (
44
"errors"
55
"fmt"
66
"hash/fnv"
7-
"net/http"
8-
"strings"
9-
"time"
107

118
"github.com/rs/zerolog/log"
12-
13-
"golang.org/x/time/rate"
149
)
1510

1611
// StatementItem is a statement data
@@ -64,6 +59,7 @@ type Client interface {
6459
GetStatement(command, accountId string) ([]StatementItem, error)
6560
SetWebHook(url string) (WebHookResponse, error)
6661
GetName() string
62+
Clear() Client
6763

6864
ResetReport(accountId string)
6965
GetAccountByID(id string) (*Account, error)
@@ -73,26 +69,27 @@ type client struct {
7369
Info *ClientInfo
7470
id uint32
7571
token string
76-
limiter *rate.Limiter
7772
reports map[string]Report
73+
mono *Mono
7874
}
7975

8076
// NewClient returns a client object.
81-
func NewClient(token string) Client {
77+
func NewClient(token string, mono *Mono) Client {
8278

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

8682
return &client{
87-
limiter: rate.NewLimiter(rate.Every(time.Second*30), 1),
8883
token: token,
8984
id: h.Sum32(),
9085
reports: make(map[string]Report),
86+
mono: mono,
9187
}
9288
}
9389

9490
func (c *client) Init() error {
95-
_, err := c.GetInfo()
91+
info, err := c.GetInfo()
92+
c.Info = &info
9693
return err
9794
}
9895

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

103100
func (c *client) GetReport(accountId string) Report {
104101
if _, ok := c.reports[accountId]; !ok {
105-
c.reports[accountId] = NewReport(accountId, c.id)
102+
account, err := c.GetAccountByID(accountId)
103+
if err != nil {
104+
return nil
105+
}
106+
c.reports[accountId] = NewReport(account, c.id)
106107
}
107108

108109
return c.reports[accountId]
109110
}
110111

111112
func (c *client) GetInfo() (ClientInfo, error) {
112-
if c.limiter.Allow() {
113-
log.Debug().Msg("[monoapi] get info")
114-
info, err := c.getClientInfo()
115-
c.Info = &info
116-
return info, err
117-
}
118-
119113
if c.Info != nil {
120114
return *c.Info, nil
121115
}
122116

123-
log.Warn().Msg("[monoapi] get info, waiting")
124-
return ClientInfo{}, errors.New("please waiting and then try again")
117+
log.Debug().Msg("[monoapi] get info")
118+
info, err := c.mono.GetClientInfo(c.token)
119+
c.Info = &info
120+
return *c.Info, err
125121
}
126122

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

135-
// SetWebHook is a function set up the monobank webhook.
136-
func (c client) SetWebHook(url string) (WebHookResponse, error) {
137-
response := WebHookResponse{}
131+
// Clear clear vars of the client
132+
func (c *client) Clear() Client {
133+
c.Info = nil
138134

139-
payload := strings.NewReader(fmt.Sprintf("{\"webHookUrl\": \"%s\"}", url))
140-
141-
req, err := http.NewRequest("POST", "https://api.monobank.ua/personal/webhook", payload)
142-
if err != nil {
143-
log.Error().Err(err).Msg("[monoapi] webhook, NewRequest")
144-
return response, err
145-
}
146-
147-
req.Header.Add("X-Token", c.token)
148-
req.Header.Add("content-type", "application/json")
135+
return c
136+
}
149137

150-
return DoRequest(response, req)
138+
// SetWebHook is a function set up the monobank webhook.
139+
func (c client) SetWebHook(url string) (WebHookResponse, error) {
140+
return c.mono.SetWebHook(url, c.token)
151141
}
152142

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

169159
func (c client) GetStatement(command string, accountId string) ([]StatementItem, error) {
170-
if c.limiter.Allow() {
171-
return c.getStatement(command, accountId)
172-
}
173-
174-
log.Warn().Msg("[monoapi] statement, waiting")
175-
return []StatementItem{}, errors.New("please waiting and then try again")
176-
}
177-
178-
func (c client) getStatement(command, account string) ([]StatementItem, error) {
179-
180-
statementItems := []StatementItem{}
181-
182-
from, to, err := getTimeRangeByPeriod(command)
183-
if err != nil {
184-
log.Error().Err(err).Msg("[monoapi] statements, range")
185-
return statementItems, err
186-
}
187-
188-
log.Debug().Msgf("[monoapi] statements, range from: %d, to: %d", from, to)
189-
190-
url := fmt.Sprintf("https://api.monobank.ua/personal/statement/%s/%d", account, from)
191-
if to > 0 {
192-
url = fmt.Sprintf("%s/%d", url, to)
193-
}
194-
195-
req, err := http.NewRequest("GET", url, nil)
196-
if err != nil {
197-
log.Error().Err(err).Msg("[monoapi] statements, NewRequest")
198-
return statementItems, err
199-
}
200-
201-
req.Header.Add("x-token", c.token)
202-
203-
return DoRequest(statementItems, req)
160+
return c.mono.GetStatement(command, accountId, c.token)
204161
}
205162

206-
func (c client) getClientInfo() (ClientInfo, error) {
207-
var clientInfo ClientInfo
208-
209-
url := "https://api.monobank.ua/personal/client-info"
210-
req, err := http.NewRequest("GET", url, nil)
211-
if err != nil {
212-
log.Error().Err(err).Msg("[monoapi] client info, create request")
213-
return clientInfo, err
214-
}
215-
216-
req.Header.Add("x-token", c.token)
217-
218-
return DoRequest(clientInfo, req)
163+
func (c Account) GetName() string {
164+
return fmt.Sprintf("%s %s", c.Type, GetCurrencySymbol(c.CurrencyCode))
219165
}

0 commit comments

Comments
 (0)