diff --git a/db/structs.go b/db/structs.go index 683728c..baa6308 100644 --- a/db/structs.go +++ b/db/structs.go @@ -1,20 +1,33 @@ package db +import ( + "time" + + "git.nefrace.ru/nefrace/tongo" +) + type Chat struct { - Id int64 - Title string - TopicId int64 `bson:"topic_id"` + tongo.Item `bson:",inline"` + ChatId int64 + Title string + TopicId int64 `bson:"topic_id"` } +func (Chat) Coll() string { return "chats" } + type User struct { - Id int64 - ChatId int64 `bson:"chat_id"` - Username string `bson:"username"` - FirstName string `bson:"first_name"` - LastName string `bson:"last_name"` - CorrectAnswer int8 `bson:"correct_answer"` - CaptchaMessage int `bson:"captcha_message"` - IsBanned bool `bson:"is_banned"` - DateJoined int64 `bson:"date_joined"` - JoinedMessage int `bson:"joined_message"` + tongo.Item `bson:",inline"` + UserId int64 `bson:"user_id"` + ChatId int64 `bson:"chat_id"` + Username string `bson:"username"` + FirstName string `bson:"first_name"` + LastName string `bson:"last_name"` + CorrectAnswer int8 `bson:"correct_answer"` + CaptchaMessage int `bson:"captcha_message"` + IsBanned bool `bson:"is_banned"` + DateJoined time.Time `bson:"date_joined"` + JoinedMessage int `bson:"joined_message"` + LastNotification time.Time `bson:"last_notification"` } + +func (User) Coll() string { return "users" } diff --git a/go.mod b/go.mod index 5103c96..20a9c4c 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( ) require ( + git.nefrace.ru/nefrace/tongo v0.0.0-20230124201743-e58da1b7d030 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/klauspost/compress v1.15.13 // indirect diff --git a/go.sum b/go.sum index 9d3c077..a73ddf2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +git.nefrace.ru/nefrace/tongo v0.0.0-20230124201743-e58da1b7d030 h1:s/T9bu8B/JQihx7HVj0Z7MG+veTAV97vEvNGfak+Wxo= +git.nefrace.ru/nefrace/tongo v0.0.0-20230124201743-e58da1b7d030/go.mod h1:I6AwXVmI+xC2kZ4TriUXK2Jchni1MZfiQQ4IWQBwC88= github.com/NicoNex/echotron/v3 v3.21.0 h1:6YDSYA/AGlPxWoUdOIRrBvUZ0RhRa2W4taS4eSrbdXw= github.com/NicoNex/echotron/v3 v3.21.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/kicker/handlers.go b/kicker/handlers.go index f87250b..8de4654 100644 --- a/kicker/handlers.go +++ b/kicker/handlers.go @@ -9,11 +9,11 @@ import ( "strconv" "time" - tb "github.com/NicoNex/echotron/v3" - "go.mongodb.org/mongo-driver/bson" + "git.nefrace.ru/nefrace/tongo" + "github.com/NicoNex/echotron/v3" ) -func userJoined(b *bot, update *tb.Update) error { +func userJoined(b *bot, update *echotron.Update) error { captcha := captchagen.GenCaptcha() _, err := b.DeleteMessage(update.Message.Chat.ID, update.Message.ID) if err != nil { @@ -22,11 +22,13 @@ func userJoined(b *bot, update *tb.Update) error { bytes, err := captcha.ToBytes() if err != nil { log.Printf("Error creating captcha bytes: %v", bytes) - b.SendMessage("Не могу создать капчу, @nefrace, проверь логи.", update.Message.From.ID, &tb.MessageOptions{MessageThreadID: update.Message.ThreadID}) + b.SendMessage("Не могу создать капчу, @nefrace, проверь логи.", update.Message.From.ID, &echotron.MessageOptions{MessageThreadID: update.Message.ThreadID}) } message := update.Message + store := tongo.NewStore[db.User](Client) user := db.User{ - Id: message.From.ID, + Item: tongo.NewID(), + UserId: message.From.ID, Username: message.From.Username, FirstName: message.From.FirstName, LastName: message.From.LastName, @@ -34,52 +36,51 @@ func userJoined(b *bot, update *tb.Update) error { ChatId: message.Chat.ID, JoinedMessage: message.ID, CorrectAnswer: int8(captcha.CorrectAnswer), - DateJoined: time.Now().Unix(), + DateJoined: time.Now(), } - user.CorrectAnswer = int8(captcha.CorrectAnswer) - d := db.GetDatabase() + // user.CorrectAnswer = int8(captcha.CorrectAnswer) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() log.Print(user) - msg := fmt.Sprintf("Приветствую тебя, *[%s](tg://user?id=%d)*\\!\nДля подтверждения, что ты человек, выбери логотип движка, которому посвящен данный чат, и отправь его номер сюда\\.\n*_Я дам тебе две минуты на это\\._*", EscapeText(tb.MarkdownV2, user.FirstName), user.Id) - options := tb.PhotoOptions{ + msg := fmt.Sprintf("Приветствую тебя, *[%s](tg://user?id=%d)*\\!\nДля подтверждения, что ты человек, выбери логотип движка, которому посвящен данный чат, и отправь его номер сюда\\.\n*_Я дам тебе десять минут на это\\._*", EscapeText(echotron.MarkdownV2, user.FirstName), user.Id) + options := echotron.PhotoOptions{ Caption: msg, - ParseMode: tb.MarkdownV2, + ParseMode: echotron.MarkdownV2, } if message.Chat.IsForum { options.MessageThreadID = int(b.CaptchaTopic) } - result, err := b.SendPhoto(tb.NewInputFileBytes("logos.png", *bytes), message.Chat.ID, &options) + result, err := b.SendPhoto(echotron.NewInputFileBytes("logos.png", *bytes), message.Chat.ID, &options) if err != nil { return err } user.CaptchaMessage = result.Result.ID - - d.NewUser(ctx, user) + store.InsertOne(ctx, &user) return nil } -func userLeft(b *bot, update *tb.Update) error { +func userLeft(b *bot, update *echotron.Update) error { message := update.Message sender := message.From - d := db.GetDatabase() + store := tongo.NewStore[db.User](Client) 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}); err == nil { - d.RemoveUser(ctx, user) + if user, err := store.GetOne(ctx, tongo.E("user_id", sender.ID), tongo.E("chat_id", message.Chat.ID)); err == nil { //d.GetUser(ctx, db.User{UserId: sender.ID, ChatId: message.Chat.ID}); err == nil { + store.DeleteByID(ctx, user.Id) b.DeleteMessage(message.Chat.ID, message.ID) b.DeleteMessage(message.Chat.ID, user.CaptchaMessage) } return nil } -func checkCaptcha(b *bot, update *tb.Update) error { +func checkCaptcha(b *bot, update *echotron.Update) error { message := update.Message sender := message.From + store := tongo.NewStore[db.User](Client) 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}); err == nil { + if user, err := store.GetOne(ctx, tongo.E("user_id", sender.ID), tongo.E("chat_id", message.Chat.ID)); err == nil { //d.GetUser(ctx, db.User{UserId: sender.ID, ChatId: message.Chat.ID}); err == nil { if message.Chat.IsForum { chat, err := d.GetChat(ctx, message.Chat.ID) if err != nil { @@ -87,6 +88,9 @@ func checkCaptcha(b *bot, update *tb.Update) error { } if message.ThreadID != int(chat.TopicId) { b.DeleteMessage(message.Chat.ID, message.ID) + text := fmt.Sprintf("*[%s](tg://user?id=%d)*, сначала пройди капчу\\!", user.FirstName, user.UserId) + res, _ := b.SendMessage(text, message.Chat.ID, &echotron.MessageOptions{ParseMode: echotron.MarkdownV2, MessageThreadID: message.ThreadID}) + go waitAndDelete(&b.API, res.Result, 30*time.Second) return nil } } @@ -95,13 +99,13 @@ func checkCaptcha(b *bot, update *tb.Update) error { solved := false if num, err := strconv.Atoi(guess); err == nil { if num == int(user.CorrectAnswer) { - _ = d.RemoveUser(ctx, user) + _ = store.DeleteByID(ctx, user.Id) solved = true b.DeleteMessage(message.Chat.ID, message.ID) b.DeleteMessage(message.Chat.ID, user.CaptchaMessage) - msg := fmt.Sprintf("*[%s](tg://user?id=%d)* только что успешно прошёл капчу\\!", EscapeText(tb.MarkdownV2, user.FirstName), user.Id) - options := tb.MessageOptions{ - ParseMode: tb.MarkdownV2, + msg := fmt.Sprintf("*[%s](tg://user?id=%d)* только что успешно прошёл капчу\\!", EscapeText(echotron.MarkdownV2, user.FirstName), user.Id) + options := echotron.MessageOptions{ + ParseMode: echotron.MarkdownV2, } if message.Chat.IsForum { options.MessageThreadID = int(b.CaptchaTopic) @@ -110,8 +114,9 @@ func checkCaptcha(b *bot, update *tb.Update) error { if err != nil { log.Printf("Can't send welcome message: %s", err) } - time.Sleep(time.Second * 10) - _, err = b.DeleteMessage(message.Chat.ID, res.Result.ID) + go waitAndDelete(&b.API, res.Result, 10*time.Second) + // time.Sleep(time.Second * 10) + // _, err = b.DeleteMessage(message.Chat.ID, res.Result.ID) if err != nil { log.Printf("Can't delete welcome message: %s", err) } @@ -122,16 +127,16 @@ func checkCaptcha(b *bot, update *tb.Update) error { b.DeleteMessage(message.Chat.ID, user.CaptchaMessage) b.DeleteMessage(message.Chat.ID, user.JoinedMessage) b.BanChatMember(message.Chat.ID, sender.ID, nil) - _ = d.RemoveUser(ctx, user) + _ = store.DeleteByID(ctx, user.Id) } } return nil } -func botAdded(b *bot, update *tb.Update) error { +func botAdded(b *bot, update *echotron.Update) error { m := update.Message chat := db.Chat{ - Id: m.Chat.ID, + ChatId: m.Chat.ID, Title: m.Chat.Title, TopicId: 0, } @@ -145,22 +150,23 @@ func botAdded(b *bot, update *tb.Update) error { return nil } -func setTopic(b *bot, update *tb.Update) error { +func setTopic(b *bot, update *echotron.Update) error { m := update.Message - d := db.GetDatabase() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - chat, err := d.GetChat(ctx, m.Chat.ID) + store := tongo.NewStore[db.Chat](Client) + chat, err := store.GetOne(ctx, tongo.E("chat_id", m.Chat.ID)) if err != nil { return err } - upd := bson.D{{Key: "$set", Value: bson.D{{Key: "topic_id", Value: m.ThreadID}}}} + chat.TopicId = int64(m.ThreadID) + // upd := bson.D{{Key: "$set", Value: bson.D{{Key: "topic_id", Value: m.ThreadID}}}} b.CaptchaTopic = int64(m.ThreadID) - err = d.UpdateChat(ctx, chat, upd) + err = store.ReplaceItem(ctx, *chat, false) if err != nil { return err } b.DeleteMessage(m.Chat.ID, m.ID) - b.SendMessage("Данный топик выбран в качестве проверочного для пользователей", m.Chat.ID, &tb.MessageOptions{MessageThreadID: m.ThreadID}) + b.SendMessage("Данный топик выбран в качестве проверочного для пользователей", m.Chat.ID, &echotron.MessageOptions{MessageThreadID: m.ThreadID}) return nil } diff --git a/kicker/kicker.go b/kicker/kicker.go index 10d5452..915d074 100644 --- a/kicker/kicker.go +++ b/kicker/kicker.go @@ -7,17 +7,20 @@ import ( "strings" "time" - tb "github.com/NicoNex/echotron/v3" + "git.nefrace.ru/nefrace/tongo" + "github.com/NicoNex/echotron/v3" ) +var Client *tongo.Database + type bot struct { chatID int64 CaptchaTopic int64 - Me *tb.User - tb.API + Me *echotron.User + echotron.API } -func (b *bot) Update(update *tb.Update) { +func (b *bot) Update(update *echotron.Update) { if update.Message != nil { if len(update.Message.NewChatMembers) != 0 { for _, user := range update.Message.NewChatMembers { @@ -45,15 +48,15 @@ func (b *bot) Update(update *tb.Update) { // Базовая структура для бота type Kicker struct { Token string - Dispatcher *tb.Dispatcher + Dispatcher *echotron.Dispatcher } -func (b *Kicker) NewBot(chatID int64) tb.Bot { +func (b *Kicker) NewBot(chatID int64) echotron.Bot { d := db.GetDatabase() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() chat := db.Chat{ - Id: chatID, + ChatId: chatID, Title: "", TopicId: 0, } @@ -68,7 +71,7 @@ func (b *Kicker) NewBot(chatID int64) tb.Bot { chatID, CaptchaTopic, nil, - tb.NewAPI(b.Token), + echotron.NewAPI(b.Token), } me, err := result.GetMe() if err != nil { @@ -81,7 +84,7 @@ func (b *Kicker) NewBot(chatID int64) tb.Bot { // Initialize bot with token func (b *Kicker) Init() error { - dsp := tb.NewDispatcher(b.Token, b.NewBot) + dsp := echotron.NewDispatcher(b.Token, b.NewBot) b.Dispatcher = dsp return nil } @@ -90,14 +93,14 @@ func (b *Kicker) Start() error { return b.Dispatcher.Poll() } -func EscapeText(parseMode tb.ParseMode, text string) string { +func EscapeText(parseMode echotron.ParseMode, text string) string { var replacer *strings.Replacer - if parseMode == tb.HTML { + if parseMode == echotron.HTML { replacer = strings.NewReplacer("<", "<", ">", ">", "&", "&") - } else if parseMode == tb.Markdown { + } else if parseMode == echotron.Markdown { replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[") - } else if parseMode == tb.MarkdownV2 { + } else if parseMode == echotron.MarkdownV2 { replacer = strings.NewReplacer( "_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(", "\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>", @@ -110,3 +113,8 @@ func EscapeText(parseMode tb.ParseMode, text string) string { return replacer.Replace(text) } + +func waitAndDelete(b *echotron.API, message *echotron.Message, t time.Duration) { + time.Sleep(t) + b.DeleteMessage(message.Chat.ID, message.ID) +} diff --git a/kicker/tasks.go b/kicker/tasks.go index 305d6a2..f84dab1 100644 --- a/kicker/tasks.go +++ b/kicker/tasks.go @@ -2,37 +2,32 @@ package kicker import ( "context" + "fmt" "kickerbot/db" "log" "time" - tb "github.com/NicoNex/echotron/v3" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "git.nefrace.ru/nefrace/tongo" + "github.com/NicoNex/echotron/v3" ) type TaskBot struct { Token string - tb.API + echotron.API } -func TaskKickOldUsers(b *tb.API) { - d := db.GetDatabase() -// log.Print("STARTING KICKING TASK") +func TaskKickOldUsers(b *echotron.API) { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - now := time.Now().Unix() - old := now - 120 - filter := bson.D{ - primitive.E{Key: "date_joined", Value: bson.D{bson.E{Key: "$lt", Value: old}}}, - } - users, err := d.GetUsers(ctx, filter) + now := time.Now() + old := now.Add(-10 * time.Minute) + store := tongo.NewStore[db.User](Client) + users, err := store.GetMany(ctx, tongo.E("date_joined", tongo.D(tongo.E("$lt", old)))) if err != nil { log.Printf("Error in deleting task: %v", err) } for _, user := range users { - - _, err := b.BanChatMember(user.ChatId, user.Id, &tb.BanOptions{RevokeMessages: true}) + _, err := b.BanChatMember(user.ChatId, user.UserId, &echotron.BanOptions{RevokeMessages: true}) if err != nil { log.Println("User was not banned: ", err) continue @@ -40,6 +35,30 @@ func TaskKickOldUsers(b *tb.API) { log.Printf("User %s was banned", user.Id) b.DeleteMessage(user.ChatId, user.CaptchaMessage) b.DeleteMessage(user.ChatId, user.JoinedMessage) - d.RemoveUser(ctx, user) + store.DeleteByID(ctx, user.Id) + } +} + +func TaskNotifyUsers(b *echotron.API) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + store := tongo.NewStore[db.User](Client) + chatStore := tongo.NewStore[db.Chat](Client) + users, _ := store.GetMany(ctx) + for _, user := range users { + if time.Since(user.LastNotification) > 2*time.Minute { + user.LastNotification = time.Now() + text := fmt.Sprintf("*[%s](tg://user?id=%d)*, напоминаю, что тебе необходимо пройти капчу\\!", EscapeText(echotron.MarkdownV2, user.FirstName), user.Id) + store.ReplaceItem(ctx, *user, false) + chat, err := chatStore.GetOne(ctx, tongo.E("chat_id", user.ChatId)) + if err != nil { + log.Printf("Can't get chat from user: %s", err) + } + res, err := b.SendMessage(text, user.ChatId, &echotron.MessageOptions{MessageThreadID: int(chat.TopicId), ParseMode: echotron.MarkdownV2}) + if err != nil { + log.Printf("Can't send notification to user: %s", err) + } + go waitAndDelete(b, res.Result, 2*time.Minute) + } } } diff --git a/main.go b/main.go index 6e01275..fb56ec0 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,19 @@ package main import ( "kickerbot/captchagen" - "kickerbot/db" "kickerbot/kicker" "log" "os" "time" + "git.nefrace.ru/nefrace/tongo" "github.com/NicoNex/echotron/v3" "github.com/go-co-op/gocron" "github.com/joho/godotenv" ) +var client *tongo.Database + func main() { err := godotenv.Load() captchagen.Init() @@ -23,10 +25,12 @@ func main() { if !exists { log.Fatal("no token specified") } - _, dberr := db.Init(os.Getenv("MONGO_URI")) - if dberr != nil { + client, err = tongo.NewConnection(os.Getenv("MONGO_URI"), "godotkicker") + // _, dberr := db.Init(os.Getenv("MONGO_URI")) + if err != nil { log.Fatal(err) } + kicker.Client = client Bot := kicker.Kicker{Token: token} Bot.Init()