From 2a29f0b89d30d79c585cdc6fc49819f1368b6c43 Mon Sep 17 00:00:00 2001 From: nefrace Date: Tue, 24 Jan 2023 23:17:30 +0300 Subject: [PATCH] KarmaShots, TimeLogger --- commands.go | 8 ++++++ filters.go | 19 ++++++++++++++ handlers.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++--- karma.go | 6 +++++ main.go | 16 +++++++++-- middleware.go | 35 ++++++++++++++---------- states.go | 9 +++++-- types.go | 55 +++++++++++++++++++++++++++++++++----- utils.go | 29 ++++++++++++++++++++ 9 files changed, 222 insertions(+), 28 deletions(-) create mode 100644 filters.go create mode 100644 karma.go create mode 100644 utils.go diff --git a/commands.go b/commands.go index c4208e2..08fbffc 100644 --- a/commands.go +++ b/commands.go @@ -9,6 +9,7 @@ import ( var commandMe = neco.NewCommand("me", "Пишу ваш текст о вас в третьем лице", false) func handleMe(u *neco.Update) error { + u.DeleteMessage() param := commandMe.Param(u.Text()) _, err := u.AnswerMarkdown(fmt.Sprintf("_*%s* %s_", neco.EscapeMd2(u.From().FirstName), neco.EscapeMd2(param))) return err @@ -31,9 +32,16 @@ func handleHelp(u *neco.Update) error { var commandSay = neco.NewCommand("say", "Пишу ваш текст от своего имени.", true) func handleSay(u *neco.Update) error { + u.DeleteMessage() param := commandSay.Param(u.Text()) _, err := u.AnswerMarkdown(fmt.Sprintf("*_%s_*", neco.EscapeMd2(param))) return err } var commandWarn = neco.NewCommand("warn", "Делаю предупреждение пользователю", true) + +func handleWarn(u *neco.Update) error { + param := commandWarn.Param(u.Text()) + _, err := u.AnswerMarkdown(fmt.Sprintf("*_%s_*", neco.EscapeMd2(param))) + return err +} diff --git a/filters.go b/filters.go new file mode 100644 index 0000000..ae2aff4 --- /dev/null +++ b/filters.go @@ -0,0 +1,19 @@ +package main + +import ( + "strings" + + neco "git.nefrace.ru/nefrace/nechotron" +) + +func karmaTriggers(u *neco.Update) bool { + good, bad := GetTriggers() + all := append(good, bad...) + text := u.Text() + for _, t := range all { + if strings.HasPrefix(text, t) { + return true + } + } + return false +} diff --git a/handlers.go b/handlers.go index 9805156..ad2414b 100644 --- a/handlers.go +++ b/handlers.go @@ -1,8 +1,73 @@ package main -import "git.nefrace.ru/nefrace/nechotron" +import ( + "fmt" + "time" + + "git.nefrace.ru/nefrace/nechotron" + "git.nefrace.ru/nefrace/tongo" + "github.com/NicoNex/echotron/v3" +) + +func handleKarma(u *nechotron.Update) error { + from, _ := u.Ctx.Value("userfrom").(*User) + to, _ := u.Ctx.Value("userto").(*User) + mentionFrom := nechotron.UserMention(u.Message.From) + mentionTo := nechotron.UserMention(u.Message.ReplyToMessage.From) + good, bad := GetTriggers() + value := 0 + text := u.Text() + if StringHasPrefix(text, good...) { + value = 1 + } + if StringHasPrefix(text, bad...) { + value = -1 + } + store := tongo.NewStore[KarmaShot](db) + fromKarma, _ := store.Count(u.Ctx, tongo.E("to", from.ID)) + totalFromKarma := from.KarmaOffset + fromKarma + if totalFromKarma < 0 { + _, err := u.AnswerText( + fmt.Sprintf("У тебя слишком маленькая карма *\\(%d\\), чтобы менять её другим\\.", totalFromKarma), + &echotron.MessageOptions{ParseMode: echotron.MarkdownV2, ReplyToMessageID: u.MessageID()}) + return err + } + timeThreshold := time.Now().Add(1 * -time.Minute) + recentShots, err := store.Count( + u.Ctx, + tongo.E("from", from.ID), + tongo.E("to", to.ID), + tongo.E("when", tongo.D(tongo.E("$gte", timeThreshold)))) + if err != nil { + return err + } + if recentShots > 0 { + u.DeleteMessage() + res, err := u.AnswerMarkdown( + fmt.Sprintf("*%s*, ты только недавно менял карму *%s*\\. Подожди минуту\\.", mentionFrom, mentionTo)) + go func() { + time.Sleep(10 * time.Second) + u.Bot.DeleteMessage(u.ChatID(), res.Result.ID) + }() + return err + } + newShot := KarmaShot{ + Item: tongo.NewID(), + From: from.ID, + To: to.ID, + MessageText: nechotron.GetText(u.Message.ReplyToMessage), + When: time.Now(), + Count: value, + } + store.InsertOne(u.Ctx, &newShot) + newKarma, _ := store.Count(u.Ctx, tongo.E("to", to.ID)) + totalToKarma := to.KarmaOffset + newKarma + changeText := "повысил" + if value < 0 { + changeText = "понизил" + } + _, err = u.AnswerMarkdown( + fmt.Sprintf("*%s \\(%d\\)* только что %s карму *%s \\(%d\\)*", mentionFrom, totalFromKarma, changeText, mentionTo, totalToKarma)) + return err -func handleAdmin(u *nechotron.Update) error { - u.AnswerPlain("Вы админ!") - return nil } diff --git a/karma.go b/karma.go new file mode 100644 index 0000000..d673cd4 --- /dev/null +++ b/karma.go @@ -0,0 +1,6 @@ +package main + +// Returns slices of good and bad triggers +func GetTriggers() ([]string, []string) { + return []string{"+", "спасибо", "благодарю", "👍"}, []string{"-", "👎"} +} diff --git a/main.go b/main.go index da97f9a..1c53723 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "log" "os" @@ -8,21 +9,26 @@ import ( "git.nefrace.ru/nefrace/tongo" "github.com/NicoNex/echotron/v3" "github.com/joho/godotenv" + "go.mongodb.org/mongo-driver/mongo/options" ) var BuildTime string +var db *tongo.Database func main() { godotenv.Load() - db, err := tongo.NewConnection(os.Getenv("MONGO_URI"), "godette") + var err error + db, err = tongo.NewConnection(os.Getenv("MONGO_URI"), "godette") if err != nil { log.Fatalf("Can't connect to database: %v", err) } + createIndexes(db) token := os.Getenv("TELEGRAM_TOKEN") neco := nechotron.NewTron(token, &MainState) neco. Use(UserDBUpdater(db)). - Use(ErrorLogger) + Use(ErrorLogger). + Use(ExecTimeLogger) api := echotron.NewAPI(token) defaultCommands := []*nechotron.Command{commandHelp, commandMe} adminCommands := []*nechotron.Command{commandSay, commandWarn} @@ -31,3 +37,9 @@ func main() { nechotron.SetMyCommands(api, "", echotron.BotCommandScope{Type: echotron.BCSTAllChatAdministrators}, allCommands...) log.Fatal(neco.DispatchPoll()) } + +func createIndexes(db *tongo.Database) error { + userStore := tongo.NewStore[User](db) + _, err := userStore.Coll.Indexes().CreateOne(context.Background(), userIndex, options.CreateIndexes()) + return err +} diff --git a/middleware.go b/middleware.go index 2d6517a..e18f30a 100644 --- a/middleware.go +++ b/middleware.go @@ -9,6 +9,16 @@ import ( "git.nefrace.ru/nefrace/tongo" ) +func ExecTimeLogger(next nechotron.UpdateHandler) nechotron.UpdateHandler { + return func(u *nechotron.Update) error { + start := time.Now() + err := next(u) + t := time.Since(start) + log.Println("Update was handled in %d microseconds", t.Microseconds()) + return err + } +} + func UserLogger(next nechotron.UpdateHandler) nechotron.UpdateHandler { return func(u *nechotron.Update) error { log.Println(u.From().FirstName) @@ -20,24 +30,21 @@ func UserLogger(next nechotron.UpdateHandler) nechotron.UpdateHandler { func UserDBUpdater(db *tongo.Database) nechotron.Middleware { return func(next nechotron.UpdateHandler) nechotron.UpdateHandler { return func(u *nechotron.Update) error { - store := tongo.NewStore[User](db) from := u.From() - user, err := store.GetOne(u.Ctx, tongo.E("id", from.ID)) + userFrom, err := UpdateUser(u.Ctx, db, from, true) if err != nil { - user = &User{ - Item: tongo.NewID(), - ID: from.ID, + return err + } + u.Ctx = context.WithValue(u.Ctx, "userfrom", userFrom) + if u.IsMessage() && u.Message.ReplyToMessage != nil { + to := u.Message.ReplyToMessage.From + userTo, err := UpdateUser(u.Ctx, db, to, false) + if err != nil { + return err } + u.Ctx = context.WithValue(u.Ctx, "userto", userTo) } - user.FirstName = from.FirstName - user.LastName = from.LastName - user.Username = from.Username - user.LastMessage = time.Now() - err = store.ReplaceItem(u.Ctx, *user, true) - if err != nil { - log.Println("Cant replace user: ", err) - } - u.Ctx = context.WithValue(u.Ctx, "dbuser", user) + return next(u) } } diff --git a/states.go b/states.go index ebb19a9..87631dc 100644 --- a/states.go +++ b/states.go @@ -6,11 +6,16 @@ import ( var MainState = neco.State{ Fn: func(u *neco.Update) error { - disp := neco.NewDispatcher(). + mainCommands := neco.NewDispatcher(). HandleCommand(commandMe, handleMe). HandleCommand(commandHelp, handleHelp). HandleCommand(commandSay, handleSay) + replyDispatcher := neco.NewDispatcher(). + HandleCommand(commandWarn, handleWarn). + HandleFilter(karmaTriggers, handleKarma) + replies := neco.NewDispatcher(). + HandleFilter(neco.IsReply, replyDispatcher.Run) - return disp.Run(u) + return neco.ChainRun(u, mainCommands, replies) }, } diff --git a/types.go b/types.go index 9343927..0790a14 100644 --- a/types.go +++ b/types.go @@ -1,9 +1,12 @@ package main import ( + "context" "time" "git.nefrace.ru/nefrace/tongo" + "github.com/NicoNex/echotron/v3" + "go.mongodb.org/mongo-driver/mongo" ) var _ tongo.Collectable = &User{} @@ -14,20 +17,60 @@ type User struct { FirstName string LastName string ID int64 - KarmaOffset int + KarmaOffset int64 LastMessage time.Time } +func UpdateUser(ctx context.Context, db *tongo.Database, user *echotron.User, updateLastMessage bool) (*User, error) { + store := tongo.NewStore[User](db) + u, err := store.GetOne(ctx, tongo.E("id", user.ID)) + if err != nil { + u = &User{ + Item: tongo.NewID(), + ID: user.ID, + } + } + u.FirstName = user.FirstName + u.LastName = user.LastName + u.Username = user.Username + if updateLastMessage { + u.LastMessage = time.Now() + } + err = store.ReplaceItem(ctx, *u, true) + if err != nil { + // log.Println("Cant replace user: ", err) + return nil, err + } + return u, nil +} + +var userIndex = mongo.IndexModel{ + Keys: tongo.D(tongo.E("id", 1)), +} + func (User) Coll() string { return "users" } var _ tongo.Collectable = &KarmaShot{} type KarmaShot struct { - tongo.Item - From tongo.OID - To tongo.OID - Count int - When time.Time + tongo.Item `bson:",inline"` + From int64 + To int64 + Count int + MessageText string + When time.Time } func (KarmaShot) Coll() string { return "karma" } + +type Warn struct { + tongo.Item `bson:",inline"` + From int64 + To int64 + MessageText string + WarnText string + When time.Time + Active bool +} + +func (Warn) Coll() string { return "warns" } diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..445f003 --- /dev/null +++ b/utils.go @@ -0,0 +1,29 @@ +package main + +import "strings" + +func StringHasAny(s string, subs ...string) bool { + for _, sub := range subs { + if strings.Contains(s, sub) { + return true + } + } + return false +} + +func StringHasPrefix(s string, subs ...string) bool { + for _, sub := range subs { + if strings.HasPrefix(s, sub) { + return true + } + } + return false +} +func StringHasSuffix(s string, subs ...string) bool { + for _, sub := range subs { + if strings.HasSuffix(s, sub) { + return true + } + } + return false +}