Users mute, admin topics

This commit is contained in:
Nefrace 2023-09-13 22:33:53 +03:00
parent 4afbc2564b
commit c6abbbef1d
5 changed files with 214 additions and 19 deletions

View File

@ -16,6 +16,14 @@ type Chat struct {
func (Chat) Coll() string { return "chats" } 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 { type User struct {
tongo.Item `bson:",inline"` tongo.Item `bson:",inline"`
UserId int64 `bson:"user_id"` UserId int64 `bson:"user_id"`
@ -33,3 +41,15 @@ type User struct {
} }
func (User) Coll() string { return "users" } 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" }

View File

@ -8,6 +8,7 @@ import (
"kickerbot/db" "kickerbot/db"
"log" "log"
"strconv" "strconv"
"strings"
"time" "time"
"git.nefrace.ru/nefrace/tongo" "git.nefrace.ru/nefrace/tongo"
@ -46,10 +47,10 @@ func userJoined(b *bot, update *echotron.Update) error {
if captcha == nil { if captcha == nil {
return nil return nil
} }
// _, err := b.DeleteMessage(update.Message.Chat.ID, update.Message.ID) _, err = b.DeleteMessage(message.Chat.ID, message.ID)
// if err != nil { if err != nil {
// log.Printf("Can't delete message: %v", err) log.Printf("Can't delete message: %v", err)
// } }
bytes, err := captcha.ToBytes() bytes, err := captcha.ToBytes()
if err != nil { if err != nil {
log.Printf("Error creating captcha bytes: %v", bytes) log.Printf("Error creating captcha bytes: %v", bytes)
@ -86,6 +87,68 @@ func userLeft(b *bot, update *echotron.Update) error {
return nil 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 { func userBanned(b *bot, update *echotron.Update) error {
m := update.ChatMember m := update.ChatMember
c := m.Chat c := m.Chat
@ -107,20 +170,19 @@ func checkCaptcha(b *bot, update *echotron.Update) error {
// d := db.GetDatabase() // d := db.GetDatabase()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel() 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, if user, err := store.GetOne(ctx,
tongo.E("user_id", sender.ID), tongo.E("user_id", sender.ID),
tongo.E("chat_id", message.Chat.ID), tongo.E("chat_id", message.Chat.ID),
tongo.E("is_joined", false), tongo.E("is_joined", false),
); err == nil { //d.GetUser(ctx, db.User{UserId: sender.ID, ChatId: message.Chat.ID}); err == nil { ); 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.Chat.IsForum {
if message.ThreadID != int(chat.TopicId) { if message.ThreadID != int(chat.TopicId) {
b.DeleteMessage(message.Chat.ID, message.ID) 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)}) 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) go waitAndDelete(&b.API, res.Result, 10*time.Second)
return nil return nil
@ -167,6 +229,69 @@ func checkCaptcha(b *bot, update *echotron.Update) error {
b.BanChatMember(message.Chat.ID, sender.ID, nil) b.BanChatMember(message.Chat.ID, sender.ID, nil)
store.DeleteByID(ctx, user.Id) 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 return nil
} }
@ -195,12 +320,6 @@ func botAdded(b *bot, update *echotron.Update) error {
func setTopic(b *bot, update *echotron.Update) error { func setTopic(b *bot, update *echotron.Update) error {
m := update.Message 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) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
store := tongo.NewStore[db.Chat](Client) store := tongo.NewStore[db.Chat](Client)

View File

@ -6,6 +6,7 @@ import (
"kickerbot/db" "kickerbot/db"
"log" "log"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
@ -38,12 +39,32 @@ func (b *bot) Update(update *echotron.Update) {
return return
} }
if update.Message.Text != "" { if update.Message.Text != "" {
if update.Message.Text == "/settopic" { res, err := b.GetChatMember(update.ChatID(), update.Message.From.ID)
setTopic(b, update) if err != nil {
log.Println("Kicker 44: can't get user member: ", err)
return 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) checkCaptcha(b, update)
} }
checkAdminTopics(b, update)
} }
if update.ChatMember != nil { if update.ChatMember != nil {
m := update.ChatMember.NewChatMember m := update.ChatMember.NewChatMember
@ -164,3 +185,25 @@ func UserMention(u *echotron.User) string {
func UserMentionDB(u *db.User) string { func UserMentionDB(u *db.User) string {
return Mention(u.FirstName, u.UserId) 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
}

View File

@ -50,7 +50,6 @@ func TaskNotifyUsers(b *echotron.API) {
for _, user := range users { for _, user := range users {
if time.Since(user.LastNotification) > 2*time.Minute { if time.Since(user.LastNotification) > 2*time.Minute {
user.LastNotification = time.Now() user.LastNotification = time.Now()
text := fmt.Sprintf("*%s*, напоминаю, что тебе необходимо пройти капчу\\!", UserMentionDB(user))
store.ReplaceItem(ctx, *user, false) store.ReplaceItem(ctx, *user, false)
chat, err := chatStore.GetOne(ctx, tongo.E("chat_id", user.ChatId)) chat, err := chatStore.GetOne(ctx, tongo.E("chat_id", user.ChatId))
var topic int64 = 0 var topic int64 = 0
@ -60,6 +59,7 @@ func TaskNotifyUsers(b *echotron.API) {
} else { } else {
topic = chat.TopicId 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}) res, err := b.SendMessage(text, user.ChatId, &echotron.MessageOptions{MessageThreadID: topic, ParseMode: echotron.MarkdownV2, ReplyToMessageID: user.CaptchaMessage})
if err != nil { if err != nil {
log.Printf("Can't send notification to user: %s", err) 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)
}
}
}

View File

@ -38,6 +38,7 @@ func main() {
tasker := echotron.NewAPI(token) tasker := echotron.NewAPI(token)
scheduler.Every(30).Seconds().Do(func() { kicker.TaskKickOldUsers(&tasker) }) scheduler.Every(30).Seconds().Do(func() { kicker.TaskKickOldUsers(&tasker) })
scheduler.Every(30).Seconds().Do(func() { kicker.TaskNotifyUsers(&tasker) }) scheduler.Every(30).Seconds().Do(func() { kicker.TaskNotifyUsers(&tasker) })
scheduler.Every(2).Minutes().Do(func() { kicker.TaskRemoveOldMutes(&tasker) })
scheduler.StartAsync() scheduler.StartAsync()
Bot.Start() Bot.Start()
} }