From c6abbbef1db964974ef9b0528c7b50728b3bebf5 Mon Sep 17 00:00:00 2001 From: Nefrace Date: Wed, 13 Sep 2023 22:33:53 +0300 Subject: [PATCH] Users mute, admin topics --- db/structs.go | 20 ++++++ kicker/handlers.go | 151 ++++++++++++++++++++++++++++++++++++++++----- kicker/kicker.go | 47 +++++++++++++- kicker/tasks.go | 14 ++++- main.go | 1 + 5 files changed, 214 insertions(+), 19 deletions(-) diff --git a/db/structs.go b/db/structs.go index 1e6f9c4..dd92798 100644 --- a/db/structs.go +++ b/db/structs.go @@ -16,6 +16,14 @@ type Chat struct { func (Chat) Coll() string { return "chats" } +type AdminTopic struct { + tongo.Item `bson:",inline"` + ChatId int64 `bson:"chat_id"` + TopicId int64 `bson:"topic_id"` +} + +func (AdminTopic) Coll() string { return "admin_topics" } + type User struct { tongo.Item `bson:",inline"` UserId int64 `bson:"user_id"` @@ -33,3 +41,15 @@ type User struct { } func (User) Coll() string { return "users" } + +type Mute struct { + tongo.Item `bson:",inline"` + UserId int64 `bson:"user_id"` + ChatId int64 `bson:"chat_id"` + Message string `bson:"message"` + Date time.Time `bson:"date"` + Until time.Time `bson:"until"` + MessageLink string `bson:"link"` //https://t.me/c/1402723647/279354/363305 +} + +func (Mute) Coll() string { return "mutes" } diff --git a/kicker/handlers.go b/kicker/handlers.go index 8965e3d..8149706 100644 --- a/kicker/handlers.go +++ b/kicker/handlers.go @@ -8,6 +8,7 @@ import ( "kickerbot/db" "log" "strconv" + "strings" "time" "git.nefrace.ru/nefrace/tongo" @@ -46,10 +47,10 @@ func userJoined(b *bot, update *echotron.Update) error { if captcha == nil { return nil } - // _, err := b.DeleteMessage(update.Message.Chat.ID, update.Message.ID) - // if err != nil { - // log.Printf("Can't delete message: %v", err) - // } + _, err = b.DeleteMessage(message.Chat.ID, message.ID) + if err != nil { + log.Printf("Can't delete message: %v", err) + } bytes, err := captcha.ToBytes() if err != nil { log.Printf("Error creating captcha bytes: %v", bytes) @@ -86,6 +87,68 @@ func userLeft(b *bot, update *echotron.Update) error { return nil } +var muted = echotron.ChatPermissions{ + CanSendMessages: false, + CanSendAudios: false, + CanSendDocuments: false, + CanSendPhotos: false, + CanSendVideos: false, + CanSendVideoNotes: false, + CanSendVoiceNotes: false, + CanSendPolls: false, + CanSendOtherMessages: false, +} + +func muteUser(b *bot, update *echotron.Update) error { + message := update.Message + + if message.ReplyToMessage == nil { + return nil + } + reply := message.ReplyToMessage + items := strings.SplitN(message.Text, " ", 3) + days, msg := 1, reply.Text + var err error + if len(items) > 1 { + days, err = strconv.Atoi(items[1]) + if err != nil || days < 1 { + res, _ := b.SendMessage("Неверно указано количество дней", message.Chat.ID, &echotron.MessageOptions{ReplyToMessageID: message.ID}) + go waitAndDelete(&b.API, res.Result, 10*time.Second) + return err + } + } + if len(items) > 2 { + msg = fmt.Sprintf("%s: \"%s\"", reply.From.FirstName, items[2]) + } + store := tongo.NewStore[db.Mute](Client) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + until := time.Now().AddDate(0, 0, days) + _, err = b.RestrictChatMember(message.Chat.ID, reply.From.ID, muted, &echotron.RestrictOptions{UntilDate: int(until.Unix())}) + if err != nil { + b.SendMessage(fmt.Sprintf("Не могу дать молчанку юзеру: %v", err), b.chatID, &echotron.MessageOptions{ReplyToMessageID: message.ID}) + return err + } + b.SendMessage( + fmt.Sprintf("Пользователю *%s* выдана \"молчанка\" на %d %s\\.", UserMention(reply.From), days, pluralRu(days, "день", "дня", "дней")), + message.Chat.ID, + &echotron.MessageOptions{ + ParseMode: echotron.MarkdownV2, + MessageThreadID: int64(message.ThreadID), + }, + ) + store.InsertOne(ctx, &db.Mute{ + Item: tongo.NewID(), + UserId: reply.From.ID, + ChatId: message.Chat.ID, + Message: msg, + Date: time.Now(), + Until: until, + MessageLink: fmt.Sprintf("https://t.me/c/%d/%d/%d", transformChatID(update.ChatID()), message.ThreadID, message.ID), + }) + return nil +} + func userBanned(b *bot, update *echotron.Update) error { m := update.ChatMember c := m.Chat @@ -107,20 +170,19 @@ func checkCaptcha(b *bot, update *echotron.Update) error { // d := db.GetDatabase() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - + chat, err := chatStore.GetOne(ctx, tongo.E("chat_id", message.Chat.ID)) + if err != nil { + return err + } if user, err := store.GetOne(ctx, tongo.E("user_id", sender.ID), tongo.E("chat_id", message.Chat.ID), tongo.E("is_joined", false), ); err == nil { //d.GetUser(ctx, db.User{UserId: sender.ID, ChatId: message.Chat.ID}); err == nil { - chat, err := chatStore.GetOne(ctx, tongo.E("chat_id", message.Chat.ID)) - if err != nil { - return err - } if message.Chat.IsForum { if message.ThreadID != int(chat.TopicId) { b.DeleteMessage(message.Chat.ID, message.ID) - text := fmt.Sprintf("*%s*, сначала пройди капчу\\!", UserMention(sender)) + text := fmt.Sprintf("*%s*, сначала пройди [капчу](https://t.me/c/%d/%d/%d)\\!", UserMention(sender), transformChatID(b.chatID), chat.TopicId, user.CaptchaMessage) res, _ := b.SendMessage(text, message.Chat.ID, &echotron.MessageOptions{ParseMode: echotron.MarkdownV2, MessageThreadID: int64(message.ThreadID)}) go waitAndDelete(&b.API, res.Result, 10*time.Second) return nil @@ -167,6 +229,69 @@ func checkCaptcha(b *bot, update *echotron.Update) error { b.BanChatMember(message.Chat.ID, sender.ID, nil) store.DeleteByID(ctx, user.Id) } + } else if message.Chat.IsForum && message.ThreadID == int(chat.TopicId) { + res, err := b.GetChatMember(update.ChatID(), sender.ID) + if err != nil { + return err + } + if res.Result.Status == "administrator" || res.Result.Status == "creator" { + return nil + } + b.DeleteMessage(b.chatID, message.ID) + } + return nil +} + +func setAdminTopic(b *bot, update *echotron.Update, set bool) error { + message := update.Message + chatID := update.ChatID() + store := tongo.NewStore[db.AdminTopic](Client) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + topic, err := store.GetOne(ctx, tongo.E("chat_id", chatID), tongo.E("topic_id", message.ThreadID)) + if set { + if err != nil || topic == nil { + _, err := store.InsertOne(ctx, &db.AdminTopic{ + Item: tongo.NewID(), + ChatId: chatID, + TopicId: int64(message.ThreadID), + }) + if err != nil { + log.Println("Can't set admintopic: ", err) + return err + } + b.SendMessage("Данный топик теперь только для админов.", chatID, &echotron.MessageOptions{MessageThreadID: int64(message.ThreadID)}) + } + return nil + } else { + if err == nil { + if err := store.DeleteByID(ctx, topic.Id); err != nil { + log.Println("Can't unset admintopic: ", err) + return err + } + b.SendMessage("Данный топик теперь доступен всем!", chatID, &echotron.MessageOptions{MessageThreadID: int64(message.ThreadID)}) + } + } + return nil +} + +func checkAdminTopics(b *bot, update *echotron.Update) error { + message := update.Message + chatID := update.ChatID() + sender := message.From + res, err := b.GetChatMember(message.Chat.ID, sender.ID) + if err != nil { + log.Println("muteUser: Can't get member: ", err) + return err + } + if res.Result.Status == "administrator" || res.Result.Status == "creator" { + return nil + } + store := tongo.NewStore[db.AdminTopic](Client) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if _, err := store.GetOne(ctx, tongo.E("chat_id", chatID), tongo.E("topic_id", message.ThreadID)); err == nil { + b.DeleteMessage(chatID, message.ID) } return nil } @@ -195,12 +320,6 @@ func botAdded(b *bot, update *echotron.Update) error { func setTopic(b *bot, update *echotron.Update) error { m := update.Message - if res, err := b.GetChatMember(m.Chat.ID, m.From.ID); err == nil { - m := res.Result - if !(m.Status == "administrator" || m.Status == "creator") { - return nil - } - } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() store := tongo.NewStore[db.Chat](Client) diff --git a/kicker/kicker.go b/kicker/kicker.go index 78379ff..e62b991 100644 --- a/kicker/kicker.go +++ b/kicker/kicker.go @@ -6,6 +6,7 @@ import ( "kickerbot/db" "log" "regexp" + "strconv" "strings" "time" @@ -38,12 +39,32 @@ func (b *bot) Update(update *echotron.Update) { return } if update.Message.Text != "" { - if update.Message.Text == "/settopic" { - setTopic(b, update) + res, err := b.GetChatMember(update.ChatID(), update.Message.From.ID) + if err != nil { + log.Println("Kicker 44: can't get user member: ", err) return } + if res.Result.Status == "administrator" || res.Result.Status == "creator" { + if update.Message.Text == "/settopic" { + setTopic(b, update) + return + } + if update.Message.Text == "/admin" { + setAdminTopic(b, update, true) + return + } + if update.Message.Text == "/unadmin" { + setAdminTopic(b, update, false) + return + } + if strings.HasPrefix(update.Message.Text, "/mute") { + muteUser(b, update) + return + } + } checkCaptcha(b, update) } + checkAdminTopics(b, update) } if update.ChatMember != nil { m := update.ChatMember.NewChatMember @@ -164,3 +185,25 @@ func UserMention(u *echotron.User) string { func UserMentionDB(u *db.User) string { return Mention(u.FirstName, u.UserId) } + +func pluralRu(n int, single string, double string, five string) string { + switch n { + case 10, 11, 12, 13, 14, 15, 16, 17, 18, 19: + return five + default: + s := []rune(strconv.Itoa(n)) + switch s[len(s)-1] { + case '1': + return single + case '2', '3', '4': + return double + default: + return five + } + } +} + +func transformChatID(id int64) int64 { + return -id - 1000000000000 + +} diff --git a/kicker/tasks.go b/kicker/tasks.go index 1790934..80ce13e 100644 --- a/kicker/tasks.go +++ b/kicker/tasks.go @@ -50,7 +50,6 @@ func TaskNotifyUsers(b *echotron.API) { for _, user := range users { if time.Since(user.LastNotification) > 2*time.Minute { user.LastNotification = time.Now() - text := fmt.Sprintf("*%s*, напоминаю, что тебе необходимо пройти капчу\\!", UserMentionDB(user)) store.ReplaceItem(ctx, *user, false) chat, err := chatStore.GetOne(ctx, tongo.E("chat_id", user.ChatId)) var topic int64 = 0 @@ -60,6 +59,7 @@ func TaskNotifyUsers(b *echotron.API) { } else { topic = chat.TopicId } + text := fmt.Sprintf("*%s*, напоминаю, что тебе необходимо пройти [капчу](https://t.me/c/%d/%d/%d)\\!", UserMentionDB(user), transformChatID(user.ChatId), chat.TopicId, user.CaptchaMessage) res, err := b.SendMessage(text, user.ChatId, &echotron.MessageOptions{MessageThreadID: topic, ParseMode: echotron.MarkdownV2, ReplyToMessageID: user.CaptchaMessage}) if err != nil { log.Printf("Can't send notification to user: %s", err) @@ -68,3 +68,15 @@ func TaskNotifyUsers(b *echotron.API) { } } } + +func TaskRemoveOldMutes(b *echotron.API) { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + store := tongo.NewStore[db.Mute](Client) + mutes, _ := store.GetMany(ctx) + for _, mute := range mutes { + if time.Now().After(mute.Until) { + store.DeleteByID(ctx, mute.Id) + } + } +} diff --git a/main.go b/main.go index 8378b84..8c50d10 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ func main() { tasker := echotron.NewAPI(token) scheduler.Every(30).Seconds().Do(func() { kicker.TaskKickOldUsers(&tasker) }) scheduler.Every(30).Seconds().Do(func() { kicker.TaskNotifyUsers(&tasker) }) + scheduler.Every(2).Minutes().Do(func() { kicker.TaskRemoveOldMutes(&tasker) }) scheduler.StartAsync() Bot.Start() }