Added actual captcha testing

This commit is contained in:
Vlad Rud 2022-02-02 00:37:58 +03:00
parent 2ca9ac5145
commit 6eeaddfd37
9 changed files with 192 additions and 83 deletions

View File

@ -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
}

View File

@ -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
}

37
db/entry.go Normal file
View File

@ -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
}

28
db/methods.go Normal file
View File

@ -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
}

View File

@ -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"`
}

50
db/user.go Normal file
View File

@ -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
}

4
go.mod
View File

@ -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
)

6
go.sum
View File

@ -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=

View File

@ -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