From 6eeaddfd372202a600baf9c057fbab7f2bb735d9 Mon Sep 17 00:00:00 2001 From: Vlad Rud Date: Wed, 2 Feb 2022 00:37:58 +0300 Subject: [PATCH] Added actual captcha testing --- captchagen/captchagen.go | 23 ++++++++++++-- db/db.go | 61 ++----------------------------------- db/entry.go | 37 +++++++++++++++++++++++ db/methods.go | 28 +++++++++++++++++ db/structs.go | 1 + db/user.go | 50 +++++++++++++++++++++++++++++++ go.mod | 4 ++- go.sum | 6 ++++ kicker/handlers.go | 65 +++++++++++++++++++++++++++------------- 9 files changed, 192 insertions(+), 83 deletions(-) create mode 100644 db/entry.go create mode 100644 db/methods.go create mode 100644 db/user.go diff --git a/captchagen/captchagen.go b/captchagen/captchagen.go index 34b13ea..12a0276 100644 --- a/captchagen/captchagen.go +++ b/captchagen/captchagen.go @@ -1,9 +1,11 @@ package captchagen import ( + "bytes" "fmt" "image" "image/color" + "image/png" "io/ioutil" "log" "math/rand" @@ -24,6 +26,7 @@ type Captcha struct { } var Logos []Logo = []Logo{} +var XPositions []int = []int{} // Создаём пустое изображение с серым градиентом, грузим шрифт из /assets и возвращаем контекст вызвавшей функции func initImage() *gg.Context { @@ -49,11 +52,11 @@ func initImage() *gg.Context { func GenCaptcha() Captcha { dc := initImage() rand.Seed(time.Now().UnixNano()) - rand.Shuffle(len(Logos), func(i, j int) { Logos[i], Logos[j] = Logos[j], Logos[i] }) + rand.Shuffle(len(Logos), func(i, j int) { Logos[i], Logos[j] = Logos[j], Logos[i] }) // Перемешиваем логотипы + rand.Shuffle(len(XPositions), func(i, j int) { XPositions[i], XPositions[j] = XPositions[j], XPositions[i] }) // И позиции correct_answer := 0 - count := len(Logos) for i, logo := range Logos { - x := i*600/count + 50 + x := XPositions[i] y := rand.Intn(400 - logo.Image.Bounds().Dy()) dc.DrawImage(logo.Image, x, y) if logo.IsCorrect { @@ -74,6 +77,16 @@ func GenCaptcha() Captcha { return captcha } +func (captcha *Captcha) ToReader() *bytes.Reader { + buff := new(bytes.Buffer) + err := png.Encode(buff, captcha.Image) + if err != nil { + fmt.Println("failed to create buffer", err) + } + reader := bytes.NewReader(buff.Bytes()) + return reader +} + // Инициализация списка логотипов. // // Логотипы читаются из папки /assets рядом с исполняемым файлом. @@ -100,5 +113,9 @@ func Init() error { logo := Logo{Image: im, IsCorrect: is_correct} // Создаём в памяти структуру для капчи Logos = append(Logos, logo) // Заносим логотип в общий список } + + for i := range Logos { + XPositions = append(XPositions, 50+i*600/len(Logos)) // Горизонтальное расположение не рандомно: чтобы логотипы не перемешались. + } return nil } diff --git a/db/db.go b/db/db.go index 06fe68c..2ac46a1 100644 --- a/db/db.go +++ b/db/db.go @@ -22,6 +22,9 @@ type DB struct { Client *mongo.Client } +type EmptyStruct struct { +} + var database DB = DB{} func Init(URI string) (DB, error) { @@ -70,61 +73,3 @@ func (d *DB) Stop() { panic(err) } } - -func (d *DB) NewEntry(ctx context.Context, collectionName string, entry interface{}) error { - collection := d.Database.Collection(collectionName) - res, err := collection.InsertOne(ctx, entry) - if err != nil { - return err - } - log.Printf("New entry: %v\nDB ID: %v\n", entry, res.InsertedID) - return nil -} - -func (d *DB) EntryExists(ctx context.Context, collectionName string, filter interface{}) bool { - collection := d.Database.Collection(collectionName) - var result bson.D - err := collection.FindOne(ctx, filter).Decode(&result) - if err != nil { - log.Printf("EntryExists error: %v", err) - } - return err != mongo.ErrNoDocuments -} - -func (d *DB) ChatExists(ctx context.Context, chat Chat) bool { - filter := bson.D{primitive.E{Key: "id", Value: chat.Id}} - return d.EntryExists(ctx, "chats", filter) -} - -func (d *DB) UserExists(ctx context.Context, user User) bool { - filter := bson.D{ - primitive.E{Key: "id", Value: user.Id}, - primitive.E{Key: "chat_id", Value: user.ChatId}, - } - return d.EntryExists(ctx, "users", filter) -} - -func (d *DB) NewChat(ctx context.Context, chat Chat) error { - if d.ChatExists(ctx, chat) { - return errors.New("chat entry already exists") - } - err := d.NewEntry(ctx, "chats", chat) - if err != nil { - log.Fatal(err) - return err - } - log.Printf("New chat added: %s (%d)\n", chat.Title, chat.Id) - return nil -} - -func (d *DB) NewUser(ctx context.Context, user User) error { - if d.UserExists(ctx, user) { - return errors.New("user entry already exists") - } - err := d.NewEntry(ctx, "users", user) - if err != nil { - return err - } - log.Printf("New user: %v\n", user) - return nil -} diff --git a/db/entry.go b/db/entry.go new file mode 100644 index 0000000..f047bcb --- /dev/null +++ b/db/entry.go @@ -0,0 +1,37 @@ +package db + +import ( + "context" + "log" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +func (d *DB) GetEntry(ctx context.Context, collectionName string, filter interface{}) (interface{}, error) { + collection := d.Database.Collection(collectionName) + var result bson.D + err := collection.FindOne(ctx, filter).Decode(&result) + if err != nil { + return EmptyStruct{}, err + } + return result, nil +} + +func (d *DB) NewEntry(ctx context.Context, collectionName string, entry interface{}) error { + collection := d.Database.Collection(collectionName) + res, err := collection.InsertOne(ctx, entry) + if err != nil { + return err + } + log.Printf("New entry: %v\nDB ID: %v\n", entry, res.InsertedID) + return nil +} + +func (d *DB) EntryExists(ctx context.Context, collectionName string, filter interface{}) bool { + _, err := d.GetEntry(ctx, collectionName, filter) + if err != nil { + log.Printf("EntryExists error: %v", err) + } + return err != mongo.ErrNoDocuments +} diff --git a/db/methods.go b/db/methods.go new file mode 100644 index 0000000..1250ec9 --- /dev/null +++ b/db/methods.go @@ -0,0 +1,28 @@ +package db + +import ( + "context" + "errors" + "log" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func (d *DB) ChatExists(ctx context.Context, chat Chat) bool { + filter := bson.D{primitive.E{Key: "id", Value: chat.Id}} + return d.EntryExists(ctx, "chats", filter) +} + +func (d *DB) NewChat(ctx context.Context, chat Chat) error { + if d.ChatExists(ctx, chat) { + return errors.New("chat entry already exists") + } + err := d.NewEntry(ctx, "chats", chat) + if err != nil { + log.Fatal(err) + return err + } + log.Printf("New chat added: %s (%d)\n", chat.Title, chat.Id) + return nil +} diff --git a/db/structs.go b/db/structs.go index fe7c150..84fa9ab 100644 --- a/db/structs.go +++ b/db/structs.go @@ -14,4 +14,5 @@ type User struct { CorrectAnswer int8 `bson:"correct_answer"` CaptchaMessage int `bson:"captcha_message"` IsBanned bool `bson:"is_banned"` + DateJoined int64 `bson:"date_joined"` } diff --git a/db/user.go b/db/user.go new file mode 100644 index 0000000..6fa832a --- /dev/null +++ b/db/user.go @@ -0,0 +1,50 @@ +package db + +import ( + "context" + "errors" + "log" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func makeIDFilter(user User) bson.D { + return bson.D{ + primitive.E{Key: "id", Value: user.Id}, + primitive.E{Key: "chat_id", Value: user.ChatId}, + } +} + +func (d *DB) UserExists(ctx context.Context, user User) bool { + filter := makeIDFilter(user) + return d.EntryExists(ctx, "users", filter) +} + +func (d *DB) NewUser(ctx context.Context, user User) error { + if d.UserExists(ctx, user) { + return errors.New("user entry already exists") + } + err := d.NewEntry(ctx, "users", user) + if err != nil { + return err + } + log.Printf("New user: %v\n", user) + return nil +} + +func (d *DB) GetUser(ctx context.Context, user User) (User, error) { + filter := makeIDFilter(user) + var result User + err := d.Database.Collection("users").FindOne(ctx, filter).Decode(&result) + if err != nil { + return User{}, err + } + return result, nil +} + +func (d *DB) RemoveUser(ctx context.Context, user User) error { + filter := makeIDFilter(user) + _, err := d.Database.Collection("users").DeleteOne(ctx, filter) + return err +} diff --git a/go.mod b/go.mod index fe6efa9..86b1350 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,14 @@ go 1.17 require ( github.com/fogleman/gg v1.3.0 // indirect + github.com/go-co-op/gocron v1.11.0 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/joho/godotenv v1.4.0 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect @@ -17,7 +19,7 @@ require ( go.mongodb.org/mongo-driver v1.8.0 // indirect golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/text v0.3.6 // indirect gopkg.in/tucnak/telebot.v3 v3.0.0-20211126232936-7f936709f3ee // indirect ) diff --git a/go.sum b/go.sum index b26337d..e8680a1 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-co-op/gocron v1.11.0 h1:ujOMubCpGcTxnnR/9vJIPIEpgwuAjbueAYqJRNr+nHg= +github.com/go-co-op/gocron v1.11.0/go.mod h1:qtlsoMpHlSdIZ3E/xuZzrrAbeX3u5JtPvWf2TcdutU0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -33,6 +35,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -61,6 +65,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/kicker/handlers.go b/kicker/handlers.go index 6590b52..5f782e9 100644 --- a/kicker/handlers.go +++ b/kicker/handlers.go @@ -1,39 +1,48 @@ package kicker import ( - "bytes" "context" "fmt" - "image/png" "kickerbot/captchagen" "kickerbot/db" "log" + "strconv" "time" tb "gopkg.in/tucnak/telebot.v3" ) func userJoined(c tb.Context) error { - m := c.Message() + bot := c.Bot() + captcha := captchagen.GenCaptcha() + reader := captcha.ToReader() + message := c.Message() user := db.User{ - Id: m.Sender.ID, - Username: m.Sender.Username, - FirstName: m.Sender.FirstName, - LastName: m.Sender.LastName, + Id: message.Sender.ID, + Username: message.Sender.Username, + FirstName: message.Sender.FirstName, + LastName: message.Sender.LastName, IsBanned: false, - ChatId: m.Chat.ID, - CorrectAnswer: 0, + ChatId: message.Chat.ID, + CorrectAnswer: int8(captcha.CorrectAnswer), + DateJoined: time.Now().Unix(), } + user.CorrectAnswer = int8(captcha.CorrectAnswer) d := db.GetDatabase() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - d.NewUser(ctx, user) log.Print(user) str := fmt.Sprintf("%v", user) - c.Bot().Send(&tb.User{ID: 60441930}, str) msg := fmt.Sprintf("Приветствую, %v!\nПеред тем, как дать тебе что-то здесь писать, я задам тебе один вопрос:\nКакой из этих движков самый лучший? Подумай хорошенько, и дай ответ цифрой.", user.FirstName) - c.Reply(msg) + photo := tb.Photo{File: tb.FromReader(reader), Caption: msg} + result, err := bot.Send(tb.ChatID(message.Chat.ID), &photo, &tb.SendOptions{ReplyTo: message}) + if err != nil { + return err + } + user.CaptchaMessage = result.ID db.Log("new user", str) + + d.NewUser(ctx, user) return nil } @@ -41,7 +50,27 @@ var HandlersV1 = []Handler{ { Endpoint: tb.OnText, Handler: func(c tb.Context) error { - db.Log("message", c.Message()) + sender := c.Sender() + message := c.Message() + d := db.GetDatabase() + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + if user, err := d.GetUser(ctx, db.User{Id: sender.ID, ChatId: message.Chat.ID, IsBanned: false}); err == nil { + text := message.Text + if num, err := strconv.Atoi(text); err == nil { + if num == int(user.CorrectAnswer) { + _ = d.RemoveUser(ctx, user) + c.Reply("Капча пройдена!") + c.Bot().Delete(&tb.Message{Chat: message.Chat, ID: user.CaptchaMessage}) + } else { + c.Reply("Ещё разочек") + } + } else { + log.Print(err) + } + } else { + c.Bot().Ban(message.Chat, &tb.ChatMember{User: sender}) + } return nil }, }, @@ -49,13 +78,7 @@ var HandlersV1 = []Handler{ Endpoint: "/gen", Handler: func(c tb.Context) error { captcha := captchagen.GenCaptcha() - buff := new(bytes.Buffer) - err := png.Encode(buff, captcha.Image) - if err != nil { - fmt.Println("failed to create buffer", err) - } - reader := bytes.NewReader(buff.Bytes()) - // log.Print(reader) + reader := captcha.ToReader() caption := fmt.Sprintf("Правильный ответ: %d", captcha.CorrectAnswer) c.Reply(&tb.Photo{File: tb.FromReader(reader), Caption: caption}) return nil