Compare commits

...

32 Commits

Author SHA1 Message Date
VLADIMIR 4e9e9702d5 fix
continuous-integration/drone/push Build encountered an error
2023-04-30 01:08:28 +07:00
VLADIMIR 6a6aff0274 add тяга
continuous-integration/drone/push Build is passing
2023-04-28 20:00:10 +07:00
VLADIMIR 615c479179 fix
continuous-integration/drone/push Build is passing
2023-04-28 16:56:05 +07:00
VLADIMIR 2e1dbbc6be add бицепц
continuous-integration/drone/push Build is passing
2023-04-28 13:27:32 +07:00
VLADIMIR 7b892730f2 fix massage
continuous-integration/drone/push Build is passing
2023-04-22 15:58:23 +07:00
VLADIMIR edfdf4affb clear
continuous-integration/drone/push Build is passing
2023-04-16 17:17:41 +07:00
VLADIMIR ddca8c456f add weight delta to stat
continuous-integration/drone/push Build is passing
2023-04-16 17:15:59 +07:00
VLADIMIR 09fe5f2324 update db and added weight
continuous-integration/drone/push Build is passing
2023-04-16 17:08:40 +07:00
VLADIMIR 3f9447f0b8 update calories message
continuous-integration/drone/push Build is passing
2023-04-15 19:13:10 +07:00
VLADIMIR a35a5c8efc add weight
continuous-integration/drone/push Build is passing
2023-04-09 15:48:31 +07:00
VLADIMIR ada1a97999 add last_time
continuous-integration/drone/push Build is passing
2023-04-09 14:47:38 +07:00
VLADIMIR d1f2c359a2 add week stat
continuous-integration/drone/push Build is passing
2023-04-09 14:15:53 +07:00
VLADIMIR c3d00b5dec add products
continuous-integration/drone/push Build is passing
2023-04-09 13:31:01 +07:00
VLADIMIR 12ffc3461f clear
continuous-integration/drone/push Build is passing
2023-04-08 19:24:03 +07:00
VLADIMIR f1f2ae721b mv to framework
continuous-integration/drone/push Build is failing
2023-04-08 19:23:42 +07:00
VLADIMIR 4509aae6a8 add framework
continuous-integration/drone/push Build is passing
2023-04-08 18:55:16 +07:00
VLADIMIR 11d3adf8db add port to config
continuous-integration/drone/push Build is passing
2023-04-07 15:11:19 +07:00
VLADIMIR 92634ce744 add app config
continuous-integration/drone/push Build is passing
2023-04-07 15:01:17 +07:00
VLADIMIR 4ceb63a307 move stat to 23:00
continuous-integration/drone/push Build is passing
2023-04-06 17:56:04 +07:00
VLADIMIR 64345c9350 add struct
continuous-integration/drone/push Build is passing
2023-04-06 17:36:57 +07:00
VLADIMIR b771745c47 add pause
continuous-integration/drone/push Build is passing
2023-04-06 01:18:25 +07:00
user-penguin 9e6681da30 add приседания
continuous-integration/drone/push Build is passing
2023-03-20 03:01:09 +07:00
VLADIMIR bce61e698a update calories
continuous-integration/drone/push Build is passing
2023-03-18 19:02:32 +07:00
VLADIMIR 823786c3ea fix test step
continuous-integration/drone/push Build is passing
2023-03-18 14:47:45 +07:00
VLADIMIR e3d2ba38a8 add test step
continuous-integration/drone/push Build is passing
2023-03-18 14:39:02 +07:00
VLADIMIR e24c6813a7 fix
continuous-integration/drone/push Build is passing
2023-03-18 14:36:41 +07:00
VLADIMIR 871b6c6dcd add calories
continuous-integration/drone/push Build is passing
2023-03-18 14:35:05 +07:00
VLADIMIR c52a1f7f40 add vscode
continuous-integration/drone/push Build is passing
2023-03-18 14:03:58 +07:00
VLADIMIR ff9a9ca154 fix mongo, add workout and update help
continuous-integration/drone/push Build is passing
2023-03-15 02:09:08 +07:00
VLADIMIR d6d2e06a72 fix
continuous-integration/drone/push Build is passing
2023-03-14 01:50:44 +07:00
VLADIMIR 87157b2081 update stat
continuous-integration/drone/push Build is failing
2023-03-14 01:49:54 +07:00
VLADIMIR 01e132628c update calories
continuous-integration/drone/push Build is passing
2023-03-14 01:15:11 +07:00
30 changed files with 1319 additions and 571 deletions
+6 -1
View File
@@ -6,7 +6,12 @@ steps:
- name: build - name: build
image: golang image: golang
commands: commands:
- go build -o valera_tg_bot main.go - go build -o valera_tg_bot cmd/valera/main.go
- name: test
image: golang
commands:
- go test ./...
- name: scp - name: scp
image: appleboy/drone-scp image: appleboy/drone-scp
-12
View File
@@ -1,12 +0,0 @@
{
"configurations": [
{
"type": "go",
"name": "run",
"goExecPath": "/usr/local/go/bin/go",
"buildParams": [
"$PROJECT_DIR$/main.go",
],
},
]
}
+88
View File
@@ -0,0 +1,88 @@
package main
import (
"log"
"net/http"
"valera/internal/config"
"valera/internal/db"
"valera/internal/states"
"valera/internal/states/clear_bot_state"
"valera/internal/states/eat_bot_state"
"valera/internal/states/go_bot_state"
"valera/internal/states/help_bot_state"
"valera/internal/states/pause_bot_state"
"valera/internal/states/ping_bot_state"
"valera/internal/states/start_bot_state"
"valera/internal/states/stat_bot_state"
"valera/internal/states/weight_bot_state"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
)
const (
version = "v1.9.0"
)
func main() {
cfg := config.NewAppConfig()
bot, err := tgbot.NewBotAPI(cfg.TgConfig.Token)
if err != nil {
panic(err)
}
dataBase, err := db.NewDB(cfg)
if err != nil {
panic(err)
}
u := tgbot.NewUpdate(0)
u.Timeout = 60
updates, err := bot.GetUpdatesChan(u)
if err != nil {
panic(err)
}
botStates := []states.BotState{
clear_bot_state.NewClearBotState(bot, dataBase),
go_bot_state.NewGoBotState(bot, dataBase),
stat_bot_state.NewStatBotState(bot, dataBase),
start_bot_state.NewStartBotState(bot, dataBase, version),
help_bot_state.NewHelpBotState(bot, dataBase),
ping_bot_state.NewPingBotState(bot, dataBase),
pause_bot_state.NewPauseBotState(bot, dataBase),
eat_bot_state.NewEatBotState(bot, dataBase),
weight_bot_state.NewWeightBotState(bot, dataBase),
}
go func() {
for _, s := range botStates {
http.HandleFunc(s.GetHandler())
}
log.Println("Server is start up! port", cfg.Port)
log.Fatal(http.ListenAndServe(cfg.Port, nil))
}()
for update := range updates {
if update.Message == nil {
continue
}
text := update.Message.Text
chatID := update.Message.Chat.ID
username := update.Message.From.UserName
userInfoDTO, err := dataBase.GetChatInfo(chatID, username)
if err != nil {
log.Println(err)
continue
}
for _, s := range botStates {
if err := s.Do(text, userInfoDTO); err != nil {
log.Println(err)
}
}
}
}
-12
View File
@@ -1,12 +0,0 @@
package commands
type Command string
const (
Start = Command("/start")
Help = Command("/help")
Ping = Command("/ping")
Go = Command("/go")
Stat = Command("/stat")
Eat = Command("/eat")
)
-19
View File
@@ -1,19 +0,0 @@
package config
type Config struct {
MongoURL string
DBName string
ChatsCollectionName string
WorkoutsCollectionName string
CaloriesCollectionName string
}
func NewConfig(mongoURl string, dbName string) *Config {
return &Config{
MongoURL: mongoURl,
DBName: dbName,
ChatsCollectionName: "chats",
WorkoutsCollectionName: "workouts",
CaloriesCollectionName: "calories",
}
}
-22
View File
@@ -1,22 +0,0 @@
package db
const (
UserStateNone = UserState("")
UserStateGo = UserState("Go")
UserStateEat = UserState("Eat")
)
type UserState string
type Chat struct {
ChatID int64 `bson:"chat_id"`
}
type ChatInfo struct {
ChatID int64 `bson:"chat_id"`
Status string `bson:"status"`
}
func (c *ChatInfo) GetStatus() UserState {
return UserState(c.Status)
}
-225
View File
@@ -1,225 +0,0 @@
package db
import (
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
"time"
"valera/config"
)
type DB struct {
cfg *config.Config
}
func NewDB(
mongoURL string,
dbName string,
) *DB {
return &DB{
cfg: config.NewConfig(mongoURL, dbName),
}
}
func (db *DB) AddChat(chatID int64) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(db.cfg.MongoURL))
if err != nil {
return err
}
collection := client.Database(db.cfg.DBName).Collection(db.cfg.ChatsCollectionName)
result := collection.FindOne(ctx, bson.M{"chat_id": chatID})
if result.Err() == mongo.ErrNoDocuments {
if _, err := collection.InsertOne(ctx, &Chat{ChatID: chatID}); err != nil {
return err
}
return nil
}
return result.Err()
}
func (db *DB) AddWorkout(chatID int64, workout *Workout) error {
if workout.Count <= 0 {
return nil
}
if err := db.AddChat(chatID); err != nil {
return err
}
workout.ChatID = chatID
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(db.cfg.MongoURL))
if err != nil {
return err
}
collection := client.Database(db.cfg.DBName).Collection(db.cfg.WorkoutsCollectionName)
_, err = collection.InsertOne(
ctx,
workout,
)
return err
}
func (db *DB) AddCalories(chatID int64, calories *Calories) error {
if calories.Count <= 0 {
return nil
}
if err := db.AddChat(chatID); err != nil {
return err
}
calories.ChatID = chatID
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(db.cfg.MongoURL))
if err != nil {
return err
}
collection := client.Database(db.cfg.DBName).Collection(db.cfg.CaloriesCollectionName)
_, err = collection.InsertOne(
ctx,
calories,
)
return err
}
func (db *DB) GetChatInfo(chatID int64) (*ChatInfo, error) {
if err := db.AddChat(chatID); err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(db.cfg.MongoURL))
if err != nil {
return nil, err
}
collection := client.Database(db.cfg.DBName).Collection(db.cfg.ChatsCollectionName)
chatInfoDTO := &ChatInfo{}
if err := collection.FindOne(ctx, bson.M{"chat_id": chatID}).Decode(chatInfoDTO); err != nil {
return nil, err
}
return chatInfoDTO, err
}
func (db *DB) SetStatusToChat(chatID int64, status UserState) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(db.cfg.MongoURL))
if err != nil {
return err
}
collection := client.Database(db.cfg.DBName).Collection(db.cfg.ChatsCollectionName)
_, err = collection.UpdateOne(
ctx,
bson.M{"chat_id": chatID},
bson.M{"$set": bson.M{"status": status}},
)
return err
}
func (db *DB) GetStat(chatID int64) (map[string]int, error) {
if err := db.AddChat(chatID); err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(db.cfg.MongoURL))
if err != nil {
return nil, err
}
loc, _ := time.LoadLocation("Asia/Novosibirsk")
t := time.Now().In(loc).Add(-24 * time.Hour)
res := map[string]int{}
collection := client.Database(db.cfg.DBName).Collection(db.cfg.WorkoutsCollectionName)
cursor, err := collection.Find(
ctx,
bson.M{
"chat_id": chatID,
"created_at": bson.M{"$gt": t},
},
)
if err != nil {
return nil, err
}
for cursor.Next(context.Background()) {
var result Workout
if err := cursor.Decode(&result); err != nil {
log.Fatal(err)
}
res[result.Name] += result.Count
}
if err := cursor.Err(); err != nil {
log.Fatal(err)
}
caloriesCollection := client.Database(db.cfg.DBName).Collection(db.cfg.CaloriesCollectionName)
caloriesCursor, err := caloriesCollection.Find(
ctx,
bson.M{
"chat_id": chatID,
"created_at": bson.M{"$gt": t},
},
)
if err != nil {
return nil, err
}
for caloriesCursor.Next(context.Background()) {
var result Calories
if err := caloriesCursor.Decode(&result); err != nil {
log.Fatal(err)
}
res["Калории"] += result.Count
}
if err := caloriesCursor.Err(); err != nil {
log.Fatal(err)
}
return res, err
}
func (db *DB) GetAllChats() ([]int64, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(db.cfg.MongoURL))
if err != nil {
return nil, err
}
collection := client.Database(db.cfg.DBName).Collection(db.cfg.ChatsCollectionName)
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
return nil, err
}
var res []int64
for cursor.Next(context.Background()) {
var result ChatInfo
if err := cursor.Decode(&result); err != nil {
log.Fatal(err)
}
res = append(res, result.ChatID)
}
if err := cursor.Err(); err != nil {
log.Fatal(err)
}
return res, err
}
+2 -2
View File
@@ -1,10 +1,9 @@
module valera module valera
go 1.17 go 1.20
require ( require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/umputun/go-flags v1.5.1
go.mongodb.org/mongo-driver v1.11.2 go.mongodb.org/mongo-driver v1.11.2
) )
@@ -19,6 +18,7 @@ require (
github.com/xdg-go/stringprep v1.0.3 // indirect github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
) )
+3 -4
View File
@@ -5,8 +5,8 @@ github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaEL
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -27,8 +27,6 @@ github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQ
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/umputun/go-flags v1.5.1 h1:vRauoXV3Ultt1HrxivSxowbintgZLJE+EcBy5ta3/mY=
github.com/umputun/go-flags v1.5.1/go.mod h1:nTbvsO/hKqe7Utri/NoyN18GR3+EWf+9RrmsdwdhrEc=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
@@ -41,6 +39,8 @@ go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNH
go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -52,7 +52,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+69
View File
@@ -0,0 +1,69 @@
package config
import (
"io/ioutil"
"strings"
)
type AppConfig struct {
Port string
MongoConfig *MongoConfig
TgConfig *TgConfig
}
type MongoConfig struct {
URL string
DBName string
ChatsCollectionName string
WorkoutsCollectionName string
CaloriesCollectionName string
WeightCollectionName string
}
type TgConfig struct {
Token string
}
func NewAppConfig() *AppConfig {
mongoURL, err := readFile("mongo_url.txt")
if err != nil {
panic(err)
}
mongoURL = strings.ReplaceAll(mongoURL, "\n", "")
dbName, err := readFile("db_name.txt")
if err != nil {
panic(err)
}
dbName = strings.ReplaceAll(dbName, "\n", "")
token, err := readFile("token.txt")
if err != nil {
panic(err)
}
token = strings.ReplaceAll(token, "\n", "")
return &AppConfig{
Port: ":10002",
MongoConfig: &MongoConfig{
URL: mongoURL,
DBName: dbName,
ChatsCollectionName: "chats",
WorkoutsCollectionName: "workouts",
CaloriesCollectionName: "calories",
WeightCollectionName: "weights",
},
TgConfig: &TgConfig{
Token: token,
},
}
}
func readFile(filename string) (string, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
str := string(b)
return str, nil
}
+25
View File
@@ -0,0 +1,25 @@
package db
const (
UserStateNone = UserState("")
UserStateGo = UserState("Go")
UserStateEat = UserState("Eat")
UserStatePause = UserState("Pause")
UserStateWeight = UserState("Weight")
)
type UserState string
type Chat struct {
ChatID int64 `bson:"chat_id"`
}
type ChatInfo struct {
ChatID int64 `bson:"chat_id"`
Status string `bson:"status"`
Username string `bson:"username"`
}
func (c *ChatInfo) GetStatus() UserState {
return UserState(c.Status)
}
+298
View File
@@ -0,0 +1,298 @@
package db
import (
"context"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"valera/internal/config"
)
type DB struct {
chatsColection *mongo.Collection
workoutsColection *mongo.Collection
caloriesColection *mongo.Collection
weightColection *mongo.Collection
}
func NewDB(
cfg *config.AppConfig,
) (*DB, error) {
ctx := context.Background()
opt := options.Client().
ApplyURI(cfg.MongoConfig.URL).
SetMinPoolSize(10)
client, err := mongo.Connect(ctx, opt)
if err != nil {
return nil, err
}
return &DB{
chatsColection: client.Database(cfg.MongoConfig.DBName).Collection(cfg.MongoConfig.ChatsCollectionName),
workoutsColection: client.Database(cfg.MongoConfig.DBName).Collection(cfg.MongoConfig.WorkoutsCollectionName),
caloriesColection: client.Database(cfg.MongoConfig.DBName).Collection(cfg.MongoConfig.CaloriesCollectionName),
weightColection: client.Database(cfg.MongoConfig.DBName).Collection(cfg.MongoConfig.WeightCollectionName),
}, nil
}
func (db *DB) AddChat(chatID int64) error {
ctx := context.Background()
result := db.chatsColection.FindOne(ctx, bson.M{"chat_id": chatID})
if result.Err() == mongo.ErrNoDocuments {
if _, err := db.chatsColection.InsertOne(ctx, &Chat{ChatID: chatID}); err != nil {
return err
}
return nil
}
if result.Err() != nil {
return result.Err()
}
loc, _ := time.LoadLocation("Asia/Novosibirsk")
t := time.Now().In(loc)
_, err := db.chatsColection.UpdateOne(
ctx,
bson.M{"chat_id": chatID},
bson.M{"$set": bson.M{"last_time": t}},
)
return err
}
func (db *DB) AddWorkout(chatID int64, workout *Workout) error {
ctx := context.Background()
if workout.Count <= 0 {
return nil
}
if err := db.AddChat(chatID); err != nil {
return err
}
workout.ChatID = chatID
_, err := db.workoutsColection.InsertOne(
ctx,
workout,
)
return err
}
func (db *DB) AddCalories(chatID int64, calories *Calories) error {
ctx := context.Background()
if err := db.AddChat(chatID); err != nil {
return err
}
calories.ChatID = chatID
_, err := db.caloriesColection.InsertOne(
ctx,
calories,
)
return err
}
func (db *DB) AddWeight(chatID int64, weight *Weight) error {
ctx := context.Background()
if weight.Weight <= 0 {
return nil
}
if err := db.AddChat(chatID); err != nil {
return err
}
weight.ChatID = chatID
_, err := db.weightColection.InsertOne(
ctx,
weight,
)
return err
}
func (db *DB) GetChatInfo(chatID int64, username string) (*ChatInfo, error) {
ctx := context.Background()
if err := db.AddChat(chatID); err != nil {
return nil, err
}
chatInfoDTO := &ChatInfo{Username: username}
if err := db.chatsColection.FindOne(ctx, bson.M{"chat_id": chatID}).Decode(chatInfoDTO); err != nil {
return nil, err
}
return chatInfoDTO, nil
}
func (db *DB) SetStatusToChat(chatID int64, status UserState) error {
ctx := context.Background()
_, err := db.chatsColection.UpdateOne(
ctx,
bson.M{"chat_id": chatID},
bson.M{"$set": bson.M{"status": status}},
)
return err
}
func (db *DB) SetPause(chatID int64, duration time.Duration) error {
ctx := context.Background()
loc, _ := time.LoadLocation("Asia/Novosibirsk")
t := time.Now().In(loc).Add(+duration)
_, err := db.chatsColection.UpdateOne(
ctx,
bson.M{"chat_id": chatID},
bson.M{"$set": bson.M{"pause_until": t}},
)
return err
}
func (db *DB) GetStatBetween(chatID int64, startTime time.Time, endTime time.Time) (map[string]float64, error) {
ctx := context.Background()
if err := db.AddChat(chatID); err != nil {
return nil, err
}
res := map[string]float64{}
cursor, err := db.workoutsColection.Find(
ctx,
bson.M{
"chat_id": chatID,
"created_at": bson.M{
"$gte": startTime,
"$lt": endTime,
},
},
options.Find().SetSort(
bson.M{
"created_at": 1,
},
),
)
if err != nil {
return nil, err
}
for cursor.Next(context.Background()) {
var result Workout
if err := cursor.Decode(&result); err != nil {
log.Fatal(err)
}
res[result.Name] += float64(result.Count)
}
if err := cursor.Err(); err != nil {
log.Fatal(err)
}
caloriesCursor, err := db.caloriesColection.Find(
ctx,
bson.M{
"chat_id": chatID,
"created_at": bson.M{
"$gte": startTime,
"$lt": endTime,
},
},
options.Find().SetSort(
bson.M{
"created_at": 1,
},
),
)
if err != nil {
return nil, err
}
for caloriesCursor.Next(context.Background()) {
var result Calories
if err := caloriesCursor.Decode(&result); err != nil {
log.Fatal(err)
}
res["Калории"] += float64(result.Count)
}
if err := caloriesCursor.Err(); err != nil {
log.Fatal(err)
}
weightCursor, err := db.weightColection.Find(
ctx,
bson.M{
"chat_id": chatID,
"created_at": bson.M{
"$gte": startTime,
"$lt": endTime,
},
},
options.Find().SetSort(
bson.M{
"created_at": 1,
},
),
)
if err != nil {
return nil, err
}
firstWeight := 0.0
lastWeight := 0.0
countWeights := 0
for weightCursor.Next(context.Background()) {
var result Weight
if err := weightCursor.Decode(&result); err != nil {
log.Fatal(err)
}
res["Вес кг"] = result.Weight
if firstWeight == 0 {
firstWeight = result.Weight
}
lastWeight = result.Weight
countWeights++
}
if err := weightCursor.Err(); err != nil {
log.Fatal(err)
}
if countWeights > 0 {
res["Изменение веса"] = lastWeight - firstWeight
}
return res, err
}
func (db *DB) GetAllChats() ([]int64, error) {
ctx := context.Background()
loc, _ := time.LoadLocation("Asia/Novosibirsk")
t := time.Now().In(loc)
cursor, err := db.chatsColection.Find(
ctx,
bson.M{
"$or": bson.A{
bson.M{
"pause_until": bson.M{"$lt": t},
},
bson.M{
"pause_until": bson.M{"$exists": false},
},
},
},
)
if err != nil {
return nil, err
}
var res []int64
for cursor.Next(context.Background()) {
var result ChatInfo
if err := cursor.Decode(&result); err != nil {
log.Fatal(err)
}
res = append(res, result.ChatID)
}
if err := cursor.Err(); err != nil {
log.Fatal(err)
}
return res, err
}
+22
View File
@@ -0,0 +1,22 @@
package db
import "time"
type Weight struct {
ChatID int64 `bson:"chat_id"`
Weight float64 `bson:"weight"`
CreatedAt time.Time `bson:"created_at"`
Username string `bson:"username"`
}
func NewWeight(
weight float64,
username string,
) *Weight {
loc, _ := time.LoadLocation("Asia/Novosibirsk")
return &Weight{
Weight: weight,
Username: username,
CreatedAt: time.Now().In(loc),
}
}
+38
View File
@@ -0,0 +1,38 @@
package times
import (
"time"
)
var (
loc *time.Location
)
func init() {
loc, _ = time.LoadLocation("Asia/Novosibirsk")
}
func GetNow() time.Time {
t := time.Now().In(loc)
return t
}
func GetStartDay() time.Time {
t := GetNow()
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc)
return t
}
func GetStartDayMinus(days int) time.Time {
t := GetStartDay()
for i := 0; i < days; i++ {
t = t.Add(-24 * time.Hour)
}
return t
}
func GetStartWeek() time.Time {
t := GetStartDay()
t = t.Add(-6 * 24 * time.Hour)
return t
}
@@ -0,0 +1,45 @@
package clear_bot_state
import (
"net/http"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
"valera/internal/db"
"valera/internal/states"
)
var names = []string{
"/c", // en
"/с", // ru
}
type clearBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewClearBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &clearBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *clearBotState) Do(text string, chatInfo *db.ChatInfo) error {
if !slices.Contains(names, text) {
return nil
}
msg := tgbot.NewMessage(chatInfo.ChatID, "Чистка")
msg.ReplyMarkup = tgbot.NewRemoveKeyboard(false)
_, _ = s.bot.Send(msg)
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone)
}
func (s *clearBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}
}
+52
View File
@@ -0,0 +1,52 @@
package eat_bot_state
import (
"strconv"
"strings"
)
var (
productsMap = map[string]struct {
caloriesIn100G int
awgWeightG int
}{
"чай": {caloriesIn100G: 65, awgWeightG: 200},
"яблоко": {caloriesIn100G: 52, awgWeightG: 242},
"хлеб": {caloriesIn100G: 245, awgWeightG: 35},
"огурец": {caloriesIn100G: 15, awgWeightG: 20},
"помидор": {caloriesIn100G: 18, awgWeightG: 18},
"слива": {caloriesIn100G: 50, awgWeightG: 50},
}
)
func calcCalories(text string) (int, error) {
product, ok := productsMap[strings.ToLower(text)]
if ok {
return product.caloriesIn100G * product.awgWeightG / 100, nil
}
arr := strings.Split(text, " ")
if len(arr) == 2 {
product, ok := productsMap[strings.ToLower(arr[0])]
if ok {
count, err := strconv.Atoi(arr[1])
if err != nil {
return 0, nil
}
return product.caloriesIn100G * count / 100, nil
}
count1, err := strconv.Atoi(arr[0])
if err != nil {
return 0, nil
}
count2, err := strconv.Atoi(arr[1])
if err != nil {
return 0, nil
}
return count1 * count2 / 100, nil
}
count, err := strconv.Atoi(text)
if err != nil {
return 0, nil
}
return count, nil
}
@@ -0,0 +1,65 @@
package eat_bot_state
import (
"fmt"
"testing"
)
func TestCalcCalories(t *testing.T) {
t.Parallel()
tests := []struct {
name string
text string
want int
wantErr bool
}{
{
"empty text",
"",
0,
false,
},
{
"1 text",
"1",
1,
false,
},
{
"2 5 text",
"20 50",
10,
false,
},
{
"чай text",
"чай",
130,
false,
},
{
"чай 250 text",
"чай 250",
162,
false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
count, err := calcCalories(tt.text)
if (err != nil) != tt.wantErr {
t.Errorf("error calc calories: %v", err)
return
}
if count != tt.want {
fmt.Println(count, tt.want)
fmt.Println(count, tt.want)
t.Errorf("error count: %v", err)
return
}
})
}
}
@@ -0,0 +1,64 @@
package eat_bot_state
import (
"fmt"
"log"
"net/http"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
"valera/internal/db"
"valera/internal/modules/times"
"valera/internal/states"
)
var names = []string{
"/eat",
}
type eatBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewEatBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &eatBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *eatBotState) Do(text string, chatInfo *db.ChatInfo) error {
if chatInfo.Status == string(db.UserStateEat) {
count, err := calcCalories(text)
if err != nil {
log.Println(err)
return nil
}
if err := s.dataBase.AddCalories(chatInfo.ChatID, db.NewCalories(count, chatInfo.Username)); err != nil {
log.Println(err)
return nil
}
stat, err := s.dataBase.GetStatBetween(chatInfo.ChatID, times.GetStartDay(), times.GetNow())
if err != nil {
log.Println(err)
return nil
}
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, fmt.Sprintf("Калории, фу, %d, записал.\nКалорий сегодня: %v", count, stat["Калории"])))
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone)
}
if !slices.Contains(names, text) {
return nil
}
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, "Вижу ты поел, отпишись сколько калорий было"))
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateEat)
}
func (s *eatBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}
}
@@ -0,0 +1,121 @@
package go_bot_state
import (
"fmt"
"log"
"net/http"
"strconv"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
"valera/internal/db"
"valera/internal/states"
)
var names = []string{
"/go",
"/го",
}
var (
workoutTypes = []string{
"Приседания",
"Тяга",
"Бицепс",
"Пресс",
"Отжимания",
"Подтягивания",
}
)
type goBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewGoBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &goBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *goBotState) Do(text string, chatInfo *db.ChatInfo) error {
if chatInfo.Status == string(db.UserStateGo) {
if err := s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserState(text)); err != nil {
log.Println(err)
}
msg := tgbot.NewMessage(chatInfo.ChatID, fmt.Sprintf("%s, отпишись сколько раз ты выполнил упражнение", text))
msg.ReplyMarkup = tgbot.NewRemoveKeyboard(false)
_, _ = s.bot.Send(msg)
return nil
}
isWorkout := false
for _, workoutType := range workoutTypes {
if chatInfo.GetStatus() == db.UserState(workoutType) {
count, err := strconv.Atoi(text)
if err != nil {
log.Println(err)
continue
}
if err := s.dataBase.AddWorkout(chatInfo.ChatID, db.NewWorkout(workoutType, count, chatInfo.Username)); err != nil {
log.Println(err)
continue
}
msgText := fmt.Sprintf("Отлично, %s, записал.", text)
if count <= 0 {
msgText = "Плохо, хочешь быть толстым и не красивым?"
}
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, msgText))
if err := s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone); err != nil {
log.Println(err)
}
isWorkout = true
break
}
}
if isWorkout {
return nil
}
if !slices.Contains(names, text) {
return nil
}
s.sendGoToChat(chatInfo.ChatID)
return nil
}
func (s *goBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
chats, err := s.dataBase.GetAllChats()
if err != nil {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
for _, chatID := range chats {
s.sendGoToChat(chatID)
}
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, `{"result":"ok"}`)
}
}
func (s *goBotState) sendGoToChat(chatID int64) {
msg := tgbot.NewMessage(chatID, "Давай немного разомнемся, выбирай:")
rows := make([][]tgbot.KeyboardButton, 0, len(workoutTypes))
for _, workoutType := range workoutTypes {
rows = append(rows, tgbot.NewKeyboardButtonRow(tgbot.NewKeyboardButton(workoutType)))
}
msg.ReplyMarkup = tgbot.NewReplyKeyboard(rows...)
if _, err := s.bot.Send(msg); err == nil {
if err := s.dataBase.SetStatusToChat(chatID, db.UserStateGo); err != nil {
log.Println(err)
}
}
}
@@ -0,0 +1,42 @@
package help_bot_state
import (
"net/http"
"valera/internal/db"
"valera/internal/states"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
)
var names = []string{
"/help",
}
type helpBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewHelpBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &helpBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *helpBotState) Do(text string, chatInfo *db.ChatInfo) error {
if !slices.Contains(names, text) {
return nil
}
msg := "Вот что я умею:\n\n1) Предлагать размяться\n - несколько видов упражнений 6 раз в день\n2) Показывать статистику за день\n3) Считать калории\n - введи одно число, я запишу калории\n - введи 2 числа через пробел одно количество грамм другое количество калорий в 100 граммах (порядок не важен), я посчитаю\n - введи название того что ты съел, я посчитаю (если знаю)"
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, msg))
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone)
}
func (s *helpBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}
}
+11
View File
@@ -0,0 +1,11 @@
package states
import (
"net/http"
"valera/internal/db"
)
type BotState interface {
Do(text string, chatInfo *db.ChatInfo) error
GetHandler() (string, func(http.ResponseWriter, *http.Request))
}
+21
View File
@@ -0,0 +1,21 @@
package pause_bot_state
import (
"strconv"
"strings"
"time"
)
func getDuration(text string) (time.Duration, error) {
d := strings.TrimSuffix(text, "ч")
d = strings.TrimSuffix(d, "д")
count, err := strconv.Atoi(d)
if err != nil {
return 0, err
}
res := time.Duration(count) * time.Hour
if strings.HasSuffix(text, "д") {
res *= 24
}
return res, nil
}
@@ -0,0 +1,66 @@
package pause_bot_state
import (
"fmt"
"log"
"net/http"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
"valera/internal/db"
"valera/internal/states"
)
var names = []string{
"/pause",
}
type pauseBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewPauseBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &pauseBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *pauseBotState) Do(text string, chatInfo *db.ChatInfo) error {
if chatInfo.Status == string(db.UserStatePause) {
duration, err := getDuration(text)
if err != nil {
log.Println(err)
return nil
}
if err := s.dataBase.SetPause(chatInfo.ChatID, duration); err != nil {
return nil
}
msg := tgbot.NewMessage(chatInfo.ChatID, fmt.Sprintf("Поставил паузу %v, отдыхай", duration))
msg.ReplyMarkup = tgbot.NewRemoveKeyboard(false)
_, _ = s.bot.Send(msg)
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone)
}
if !slices.Contains(names, text) {
return nil
}
msg := tgbot.NewMessage(chatInfo.ChatID, "Хочешь отдохнуть? Сколько времени тебе нужно?")
msg.ReplyMarkup = tgbot.NewReplyKeyboard([][]tgbot.KeyboardButton{
tgbot.NewKeyboardButtonRow(
tgbot.NewKeyboardButton("8ч"),
tgbot.NewKeyboardButton("32ч"),
),
}...)
_, _ = s.bot.Send(msg)
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStatePause)
}
func (s *pauseBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}
}
@@ -0,0 +1,43 @@
package ping_bot_state
import (
"net/http"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
"valera/internal/db"
"valera/internal/states"
)
var names = []string{
"/ping",
}
type pingBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewPingBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &pingBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *pingBotState) Do(text string, chatInfo *db.ChatInfo) error {
if !slices.Contains(names, text) {
return nil
}
msg := "Понг"
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, msg))
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone)
}
func (s *pingBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}
}
@@ -0,0 +1,45 @@
package start_bot_state
import (
"fmt"
"net/http"
"valera/internal/db"
"valera/internal/states"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
)
var names = []string{
"/start",
}
type startBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
version string
}
func NewStartBotState(bot *tgbot.BotAPI, dataBase *db.DB, version string) states.BotState {
return &startBotState{
bot: bot,
dataBase: dataBase,
version: version,
}
}
func (s *startBotState) Do(text string, chatInfo *db.ChatInfo) error {
if !slices.Contains(names, text) {
return nil
}
msg := fmt.Sprintf("Здорова, я Валера (%s), твой тренер (%d).", s.version, chatInfo.ChatID)
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, msg))
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone)
}
func (s *startBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}
}
@@ -0,0 +1,93 @@
package stat_bot_state
import (
"fmt"
"net/http"
"time"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
"valera/internal/db"
"valera/internal/modules/times"
"valera/internal/states"
)
var names = []string{
"/stat",
"/stat_week",
}
type statBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewStatBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &statBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *statBotState) Do(text string, chatInfo *db.ChatInfo) error {
if !slices.Contains(names, text) {
return nil
}
if text == "/stat" {
s.sendStatToChatAfter(chatInfo.ChatID, "Результаты за сегодня:\n", times.GetStartDay())
}
if text == "/stat_week" {
s.sendStatToChatAfter(chatInfo.ChatID, "Результаты за неделю:\n", times.GetStartWeek())
}
return nil
}
func (s *statBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
chats, err := s.dataBase.GetAllChats()
if err != nil {
fmt.Println(err)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
for _, chatID := range chats {
t := times.GetStartDay()
if err := s.sendStatToChatAfter(chatID, "Напоминаю:\n- Cегодня больше не жрем!\n\nРезультаты за сегодня:\n", times.GetStartDay()); err != nil {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
if t.Weekday() == time.Sunday {
if err := s.sendStatToChatAfter(chatID, "Результаты за неделю:\n", times.GetStartWeek()); err != nil {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
}
}
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, `{"result":"ok"}`)
}
}
func (s *statBotState) sendStatToChatAfter(chatID int64, prefix string, t time.Time) error {
msgText := prefix
stat, err := s.dataBase.GetStatBetween(chatID, t, times.GetNow())
if err != nil {
return err
}
for k, v := range stat {
msgText += fmt.Sprintf("- %s: %v\n", k, v)
}
_, _ = s.bot.Send(tgbot.NewMessage(chatID, msgText))
return nil
}
@@ -0,0 +1,100 @@
package weight_bot_state
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"golang.org/x/exp/slices"
"valera/internal/db"
"valera/internal/modules/times"
"valera/internal/states"
)
var names = []string{
"/weight",
}
type weightBotState struct {
bot *tgbot.BotAPI
dataBase *db.DB
}
func NewWeightBotState(bot *tgbot.BotAPI, dataBase *db.DB) states.BotState {
return &weightBotState{
bot: bot,
dataBase: dataBase,
}
}
func (s *weightBotState) Do(text string, chatInfo *db.ChatInfo) error {
if chatInfo.Status == string(db.UserStateWeight) {
weight, err := getWeight(text)
if err != nil {
log.Println(err)
return nil
}
if weight <= 0 {
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, "Все фигня, давай по новой"))
return nil
}
if err := s.dataBase.AddWeight(chatInfo.ChatID, db.NewWeight(weight, chatInfo.Username)); err != nil {
log.Println(err)
return nil
}
stat, err := s.dataBase.GetStatBetween(chatInfo.ChatID, times.GetStartDayMinus(1), times.GetStartDay())
if err != nil {
log.Println(err)
return nil
}
dWeight := 0.0
if stat["Вес кг"] != 0 {
dWeight = weight - stat["Вес кг"]
}
_, _ = s.bot.Send(tgbot.NewMessage(chatInfo.ChatID, fmt.Sprintf("%vкг, записал.\nИзменение веса: %vкг", weight, dWeight)))
return s.dataBase.SetStatusToChat(chatInfo.ChatID, db.UserStateNone)
}
if !slices.Contains(names, text) {
return nil
}
s.sendWeightToChat(chatInfo.ChatID)
return nil
}
func (s *weightBotState) GetHandler() (string, func(http.ResponseWriter, *http.Request)) {
return names[0], func(w http.ResponseWriter, r *http.Request) {
chats, err := s.dataBase.GetAllChats()
if err != nil {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
for _, chatID := range chats {
s.sendWeightToChat(chatID)
}
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, `{"result":"ok"}`)
}
}
func (s *weightBotState) sendWeightToChat(chatID int64) {
msg := tgbot.NewMessage(chatID, "Давай взвешивайся, отпишись сколько кг ты весишь")
if _, err := s.bot.Send(msg); err == nil {
if err := s.dataBase.SetStatusToChat(chatID, db.UserStateWeight); err != nil {
log.Println(err)
}
}
}
func getWeight(text string) (float64, error) {
number := strings.ReplaceAll(text, ",", ".")
return strconv.ParseFloat(number, 64)
}
-274
View File
@@ -1,274 +0,0 @@
package main
import (
"errors"
"fmt"
tgbot "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/umputun/go-flags"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
"valera/commands"
"valera/db"
)
var (
workoutTypes = []string{
"Отжимания",
"Пресс",
}
)
const (
version = "v1.4.0"
)
type Opts struct {
Token string `short:"t" long:"token" description:"Telegram api token"`
Name string `short:"n" long:"name" description:"Telegram bot name" default:"@body_weight_loss_bot"`
}
var opts Opts
func readFile(filename string) (string, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
str := string(b)
return str, nil
}
func main() {
run()
}
func run() {
p := flags.NewParser(&opts, flags.PrintErrors|flags.PassDoubleDash|flags.HelpFlag)
p.SubcommandsOptional = true
if _, err := p.Parse(); err != nil {
if err.(*flags.Error).Type != flags.ErrHelp {
log.Println(errors.New("[ERROR] cli error: " + err.Error()))
}
os.Exit(2)
}
if opts.Token == "" {
token, err := readFile("token.txt")
if err != nil {
panic(err)
}
opts.Token = strings.ReplaceAll(token, "\n", "")
}
fmt.Println(opts.Token)
bot, err := tgbot.NewBotAPI(opts.Token)
if err != nil {
panic(err)
}
mongoURL, err := readFile("mongo_url.txt")
if err != nil {
panic(err)
}
mongoURL = strings.ReplaceAll(mongoURL, "\n", "")
fmt.Println(mongoURL)
dbName, err := readFile("db_name.txt")
if err != nil {
panic(err)
}
dbName = strings.ReplaceAll(dbName, "\n", "")
fmt.Println(dbName)
dataBase := db.NewDB(
mongoURL,
dbName,
)
u := tgbot.NewUpdate(0)
u.Timeout = 60
updates, err := bot.GetUpdatesChan(u)
if err != nil {
panic(err)
}
go func() {
http.HandleFunc("/go", func(w http.ResponseWriter, r *http.Request) {
chats, err := dataBase.GetAllChats()
if err != nil {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
for _, chatID := range chats {
sendGoToChat(bot, dataBase, chatID)
}
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, `{"result":"ok"}`)
})
http.HandleFunc("/stat", func(w http.ResponseWriter, r *http.Request) {
chats, err := dataBase.GetAllChats()
if err != nil {
fmt.Println(err)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
for _, chatID := range chats {
if err := sendStatToChat(bot, dataBase, chatID, "Напоминаю:\n- Cегодня больше не жрем!\n\n"); err != nil {
fmt.Println(err)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = fmt.Fprintf(w, `{"result":"error"}`)
return
}
}
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, `{"result":"ok"}`)
})
port := ":10002"
log.Println("Server is start up! port", port)
log.Fatal(http.ListenAndServe(port, nil))
}()
log.Println("Run", opts.Name)
for update := range updates {
if update.Message == nil {
continue
}
text := update.Message.Text
chatID := update.Message.Chat.ID
username := update.Message.From.UserName
userInfoDTO, err := dataBase.GetChatInfo(chatID)
if err != nil {
log.Println(err)
continue
}
isWorkout := false
for _, workoutType := range workoutTypes {
if userInfoDTO.GetStatus() == db.UserState(workoutType) {
count, err := strconv.Atoi(text)
if err != nil {
log.Println(err)
continue
}
if err := dataBase.AddWorkout(chatID, db.NewWorkout(workoutType, count, username)); err != nil {
log.Println(err)
continue
}
msgText := fmt.Sprintf("Отлично, %s, записал.", text)
if count <= 0 {
msgText = "Плохо, хочешь быть толстым и не красивым?"
}
_, _ = bot.Send(tgbot.NewMessage(chatID, msgText))
if err := dataBase.SetStatusToChat(chatID, db.UserStateNone); err != nil {
log.Println(err)
}
isWorkout = true
break
}
}
if isWorkout {
continue
}
switch userInfoDTO.GetStatus() {
case db.UserStateGo:
if err := dataBase.SetStatusToChat(chatID, db.UserState(text)); err != nil {
log.Println(err)
}
msg := tgbot.NewMessage(chatID, fmt.Sprintf("%s, отпишись сколько раз ты выполнил упражнение", text))
msg.ReplyMarkup = tgbot.NewRemoveKeyboard(false)
_, _ = bot.Send(msg)
continue
case db.UserStateEat:
count, err := strconv.Atoi(text)
if err != nil {
log.Println(err)
continue
}
if err := dataBase.AddCalories(chatID, db.NewCalories(count, username)); err != nil {
log.Println(err)
continue
}
if count <= 0 {
_, _ = bot.Send(tgbot.NewMessage(chatID, "Все фигня, давай по новой"))
continue
}
_, _ = bot.Send(tgbot.NewMessage(chatID, fmt.Sprintf("Калории, фу, %s, записал.", text)))
if err := dataBase.SetStatusToChat(chatID, db.UserStateNone); err != nil {
log.Println(err)
}
continue
}
command := commands.Command(strings.Replace(text, opts.Name, "", 1))
switch command {
case commands.Start:
_, _ = bot.Send(tgbot.NewMessage(chatID, fmt.Sprintf("Здорова, я Валера (%s), твой тренер (%d).", version, chatID)))
if err := dataBase.SetStatusToChat(chatID, db.UserStateNone); err != nil {
log.Println(err)
}
case commands.Help:
_, _ = bot.Send(tgbot.NewMessage(chatID, "Вот что я умею:\n\n1) Предлагать размяться\n2) Показывать статистику\n3) Считать калории"))
case commands.Ping:
_, _ = bot.Send(tgbot.NewMessage(chatID, "pong"))
case commands.Go:
sendGoToChat(bot, dataBase, chatID)
case commands.Stat:
if err := sendStatToChat(bot, dataBase, chatID, ""); err != nil {
fmt.Println(err)
continue
}
case commands.Eat:
if _, err := bot.Send(tgbot.NewMessage(chatID, "Вижу ты поел, отпишись сколько калорий было")); err == nil {
if err := dataBase.SetStatusToChat(chatID, db.UserStateEat); err != nil {
log.Println(err)
}
}
}
}
}
func sendGoToChat(bot *tgbot.BotAPI, dataBase *db.DB, chatID int64) {
msg := tgbot.NewMessage(chatID, "Давай немного разомнемся, выбирай:")
row := tgbot.NewKeyboardButtonRow()
for _, workoutType := range workoutTypes {
row = append(row, tgbot.NewKeyboardButton(workoutType))
}
msg.ReplyMarkup = tgbot.NewReplyKeyboard(row)
if _, err := bot.Send(msg); err == nil {
if err := dataBase.SetStatusToChat(chatID, db.UserStateGo); err != nil {
log.Println(err)
}
}
}
func sendStatToChat(bot *tgbot.BotAPI, dataBase *db.DB, chatID int64, prefix string) error {
stat, err := dataBase.GetStat(chatID)
if err != nil {
return err
}
msgText := prefix + "Результаты за сегодня:\n"
for k, v := range stat {
msgText += fmt.Sprintf("- %s: %d\n", k, v)
}
_, _ = bot.Send(tgbot.NewMessage(chatID, msgText))
return nil
}