Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
Nefrace | 2f8e15ee4a | |
Nefrace | 3aeefc2b2d | |
Nefrace | c149e29dca | |
Nefrace | 715b6f5b7b | |
nefrace | 70385fd500 | |
nefrace | 15d5321704 | |
nefrace | d3e1c99442 | |
nefrace | a0e8b8d190 | |
Nefrace | e0972d8a2a | |
Nefrace | c6abbbef1d | |
Nefrace | 4afbc2564b | |
Nefrace | a3e12f3c45 | |
nefrace | 82f49079ab | |
Nefrace | 09d7496c93 | |
nefrace | 833a23ba77 |
11
README.md
11
README.md
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
Бот, генерирующий капчу в виде картинки с набором логотипов игровых движков. Проверяемому необходимо выбрать правильный и написать ответ цифрой. Если ответ неверный: бот банит пользователя.
|
Бот, генерирующий капчу в виде картинки с набором логотипов игровых движков. Проверяемому необходимо выбрать правильный и написать ответ цифрой. Если ответ неверный: бот банит пользователя.
|
||||||
|
|
||||||
|
## Основные команды админа
|
||||||
|
`/settopic` - устанавливает выбранный топик в качестве "полигона" для капчи. Все сообщения с капчей пойдут туда, все сообщения пользователей, не связанные с капчей, будут удаляться.
|
||||||
|
|
||||||
|
`/admin` - устанавливает выбранный топик как "админский" и запрещает другим пользователям туда писать, удаляя все их сообщения. Отменяет данное действие команда `/unadmin`.
|
||||||
|
|
||||||
|
`/mute [дни=1] [сообщение]` - даёт пользователю, на чьё сообщение был ответ, молчанку. По-умолчанию один день, а в качестве сообщения указывается текст оригинала. Информация о молчанке сохраняется в базе до момента её окончания, но пока нет никакого способа посмотреть на неё, помимо прямого доступа к базе.
|
||||||
|
|
||||||
## Модули
|
## Модули
|
||||||
|
|
||||||
### Kicker
|
### Kicker
|
||||||
|
@ -29,10 +36,6 @@ MONGO_URI=mongodb://mongo:27017
|
||||||
```
|
```
|
||||||
MONGO_INITDB_ROOT_USERNAME=<логин для базы>
|
MONGO_INITDB_ROOT_USERNAME=<логин для базы>
|
||||||
MONGO_INITDB_ROOT_PASSWORD=<пароль для базы>
|
MONGO_INITDB_ROOT_PASSWORD=<пароль для базы>
|
||||||
|
|
||||||
ME_CONFIG_MONGODB_ADMINUSERNAME=<логин для базы>
|
|
||||||
ME_CONFIG_MONGODB_ADMINPASSWORD=<пароль для базы>
|
|
||||||
ME_CONFIG_MONGODB_URL=mongodb://<логин>:<пароль>@mongo:27017/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Затем запустить команду `docker-compose up -d --build`, чтобы собрать образ бота и запустить контейнеры в стэке.
|
Затем запустить команду `docker-compose up -d --build`, чтобы собрать образ бота и запустить контейнеры в стэке.
|
||||||
|
|
|
@ -16,6 +16,21 @@ 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 LogChannel struct {
|
||||||
|
tongo.Item `bson:",inline"`
|
||||||
|
ChatId int64 `bson:"chat_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LogChannel) Coll() string { return "log_channel" }
|
||||||
|
|
||||||
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 +48,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" }
|
||||||
|
|
|
@ -10,24 +10,16 @@ services:
|
||||||
env_file:
|
env_file:
|
||||||
- bot.env
|
- bot.env
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo
|
image: mongo:4
|
||||||
restart: always
|
restart: always
|
||||||
env_file:
|
env_file:
|
||||||
- mongo.env
|
- mongo.env
|
||||||
ports:
|
ports:
|
||||||
- ${HOST}:28003:27017
|
- 27017
|
||||||
volumes:
|
volumes:
|
||||||
- mongodata:/data/db
|
- mongodata:/data/db
|
||||||
- mongoconfig:/data/configdb
|
- mongoconfig:/data/configdb
|
||||||
|
|
||||||
mongo-express:
|
|
||||||
image: mongo-express
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- ${HOST}:8090:8081
|
|
||||||
env_file:
|
|
||||||
- mongo.env
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mongodata:
|
mongodata:
|
||||||
mongoconfig:
|
mongoconfig:
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -21,6 +22,10 @@ func userJoined(b *bot, update *echotron.Update) error {
|
||||||
store := tongo.NewStore[db.User](Client)
|
store := tongo.NewStore[db.User](Client)
|
||||||
usr := update.Message.NewChatMembers[0]
|
usr := update.Message.NewChatMembers[0]
|
||||||
message := update.Message
|
message := update.Message
|
||||||
|
_, err := b.DeleteMessage(message.Chat.ID, message.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Can't delete message: %v", err)
|
||||||
|
}
|
||||||
user, err := store.GetOne(ctx, tongo.E("chat_id", update.ChatID()), tongo.E("user_id", usr.ID))
|
user, err := store.GetOne(ctx, tongo.E("chat_id", update.ChatID()), tongo.E("user_id", usr.ID))
|
||||||
var captcha *captchagen.Captcha
|
var captcha *captchagen.Captcha
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,10 +51,6 @@ 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)
|
|
||||||
// if err != nil {
|
|
||||||
// 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,138 @@ 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),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
logstore := tongo.NewStore[db.LogChannel](Client)
|
||||||
|
logchat, _ := logstore.GetOne(ctx)
|
||||||
|
if logchat != nil {
|
||||||
|
msg := fmt.Sprintf("Пользователю %s выдана молчанка админом *%s* \\#muted\n\nПричина: %s\\.", MentionWithData(reply.From), UserMention(message.From), EscapeText(echotron.MarkdownV2, msg))
|
||||||
|
if _, err := b.SendMessage(
|
||||||
|
msg,
|
||||||
|
logchat.ChatId,
|
||||||
|
&echotron.MessageOptions{
|
||||||
|
ParseMode: echotron.MarkdownV2,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
log.Printf("Can't log muted message with reason: %v", err)
|
||||||
|
log.Println(msg)
|
||||||
|
}
|
||||||
|
b.ForwardMessage(logchat.ChatId, message.Chat.ID, reply.ID, &echotron.ForwardOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 banUser(b *bot, update *echotron.Update) error {
|
||||||
|
message := update.Message
|
||||||
|
|
||||||
|
if message.ReplyToMessage == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
reply := message.ReplyToMessage
|
||||||
|
items := strings.SplitN(message.Text, " ", 2)
|
||||||
|
reason := ""
|
||||||
|
if len(items) == 2 {
|
||||||
|
reason = items[1]
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
_, err := b.BanChatMember(message.Chat.ID, reply.From.ID, &echotron.BanOptions{})
|
||||||
|
if err != nil {
|
||||||
|
b.SendMessage(fmt.Sprintf("Не могу забанить юзера: %v", err), b.chatID, &echotron.MessageOptions{ReplyToMessageID: message.ID})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts := echotron.MessageOptions{
|
||||||
|
ParseMode: echotron.MarkdownV2,
|
||||||
|
}
|
||||||
|
|
||||||
|
if message.ThreadID != 0 {
|
||||||
|
opts.MessageThreadID = int64(message.ThreadID)
|
||||||
|
}
|
||||||
|
if _, err := b.SendMessage(
|
||||||
|
fmt.Sprintf("Пользователь %s забанен\\.", UserMention(reply.From)),
|
||||||
|
message.Chat.ID,
|
||||||
|
&opts,
|
||||||
|
); err != nil {
|
||||||
|
log.Printf("Can't send ban message: %v", err)
|
||||||
|
}
|
||||||
|
logstore := tongo.NewStore[db.LogChannel](Client)
|
||||||
|
logchat, _ := logstore.GetOne(ctx)
|
||||||
|
if logchat != nil {
|
||||||
|
msg := fmt.Sprintf("Пользователь %s забанен админом *%s* \\#banned\n\nПричина: %s\\.", MentionWithData(reply.From), UserMention(message.From), EscapeText(echotron.MarkdownV2, reason))
|
||||||
|
if _, err := b.SendMessage(
|
||||||
|
msg,
|
||||||
|
logchat.ChatId,
|
||||||
|
&echotron.MessageOptions{
|
||||||
|
ParseMode: echotron.MarkdownV2,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
log.Printf("Can't log banned message with reason: %v", err)
|
||||||
|
log.Println(msg)
|
||||||
|
}
|
||||||
|
b.ForwardMessage(logchat.ChatId, message.Chat.ID, reply.ID, &echotron.ForwardOptions{})
|
||||||
|
}
|
||||||
|
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
|
||||||
|
@ -96,6 +229,17 @@ func userBanned(b *bot, update *echotron.Update) error {
|
||||||
if user, err := store.GetOne(ctx, tongo.E("user_id", u.ID), tongo.E("chat_id", c.ID)); err == nil { //d.GetUser(ctx, db.User{UserId: sender.ID, ChatId: message.Chat.ID}); err == nil {
|
if user, err := store.GetOne(ctx, tongo.E("user_id", u.ID), tongo.E("chat_id", c.ID)); err == nil { //d.GetUser(ctx, db.User{UserId: sender.ID, ChatId: message.Chat.ID}); err == nil {
|
||||||
store.DeleteByID(ctx, user.Id)
|
store.DeleteByID(ctx, user.Id)
|
||||||
}
|
}
|
||||||
|
logstore := tongo.NewStore[db.LogChannel](Client)
|
||||||
|
logchat, _ := logstore.GetOne(ctx)
|
||||||
|
if logchat != nil && m.From.ID != b.Me.ID {
|
||||||
|
b.SendMessage(
|
||||||
|
fmt.Sprintf("Пользователь %s забанен админом *%s* \\#banned\\.", MentionWithData(u), UserMention(&m.From)),
|
||||||
|
logchat.ChatId,
|
||||||
|
&echotron.MessageOptions{
|
||||||
|
ParseMode: echotron.MarkdownV2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,27 +251,33 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text_runes := []rune(message.Text)
|
userText := message.Text
|
||||||
guess := string(text_runes[0])
|
if userText == "" {
|
||||||
|
userText = message.Caption
|
||||||
|
}
|
||||||
|
text_runes := []rune(userText)
|
||||||
|
guess := ""
|
||||||
|
if len(text_runes) > 0 {
|
||||||
|
guess = string(text_runes[0])
|
||||||
|
}
|
||||||
solved := false
|
solved := false
|
||||||
if num, err := strconv.Atoi(guess); err == nil {
|
if num, err := strconv.Atoi(guess); err == nil {
|
||||||
if num == int(user.CorrectAnswer) {
|
if num == int(user.CorrectAnswer) {
|
||||||
|
@ -152,7 +302,7 @@ func checkCaptcha(b *bot, update *echotron.Update) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Can't send welcome message: %s", err)
|
log.Printf("Can't send welcome message: %s", err)
|
||||||
}
|
}
|
||||||
go waitAndDelete(&b.API, res.Result, timeout*time.Second)
|
go waitAndDelete(&b.API, res.Result, timeout)
|
||||||
// time.Sleep(time.Second * 10)
|
// time.Sleep(time.Second * 10)
|
||||||
// _, err = b.DeleteMessage(message.Chat.ID, res.Result.ID)
|
// _, err = b.DeleteMessage(message.Chat.ID, res.Result.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,12 +311,80 @@ func checkCaptcha(b *bot, update *echotron.Update) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !solved {
|
if !solved {
|
||||||
|
logstore := tongo.NewStore[db.LogChannel](Client)
|
||||||
|
|
||||||
b.DeleteMessage(message.Chat.ID, message.ID)
|
b.DeleteMessage(message.Chat.ID, message.ID)
|
||||||
b.DeleteMessage(message.Chat.ID, user.CaptchaMessage)
|
b.DeleteMessage(message.Chat.ID, user.CaptchaMessage)
|
||||||
b.DeleteMessage(message.Chat.ID, user.JoinedMessage)
|
b.DeleteMessage(message.Chat.ID, user.JoinedMessage)
|
||||||
b.BanChatMember(message.Chat.ID, sender.ID, nil)
|
b.BanChatMember(message.Chat.ID, sender.ID, nil)
|
||||||
|
if logchat, _ := logstore.GetOne(ctx); logchat != nil {
|
||||||
|
b.SendMessage(fmt.Sprintf("Пользователь %s провалил капчу \\#banned \\#captcha", MentionWithData(sender)), logchat.ChatId, &echotron.MessageOptions{ParseMode: echotron.MarkdownV2})
|
||||||
|
}
|
||||||
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 +413,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)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"kickerbot/db"
|
"kickerbot/db"
|
||||||
"log"
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -37,13 +38,40 @@ func (b *bot) Update(update *echotron.Update) {
|
||||||
userLeft(b, update)
|
userLeft(b, update)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if update.Message.Text != "" {
|
if update.Message.Text != "" || update.Message.Caption != "" {
|
||||||
if update.Message.Text == "/settopic" {
|
if update.Message.Text == "" {
|
||||||
setTopic(b, update)
|
update.Message.Text = update.Message.Caption
|
||||||
|
}
|
||||||
|
res, err := b.GetChatMember(update.ChatID(), update.Message.From.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Kicker 44: can't get user member: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkCaptcha(b, update)
|
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
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(update.Message.Text, "/ban") {
|
||||||
|
banUser(b, update)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
checkCaptcha(b, update)
|
||||||
|
checkAdminTopics(b, update)
|
||||||
}
|
}
|
||||||
if update.ChatMember != nil {
|
if update.ChatMember != nil {
|
||||||
m := update.ChatMember.NewChatMember
|
m := update.ChatMember.NewChatMember
|
||||||
|
@ -147,14 +175,16 @@ func MentionUser(user *echotron.User) string {
|
||||||
|
|
||||||
var chars = []string{"_", "\\*", "\\[", "\\]", "\\(", "\\)", "~", "`", ">", "#", "\\+", "\\-", "=", "|", "{", "}", "\\.", "!"}
|
var chars = []string{"_", "\\*", "\\[", "\\]", "\\(", "\\)", "~", "`", ">", "#", "\\+", "\\-", "=", "|", "{", "}", "\\.", "!"}
|
||||||
var r = strings.Join(chars, "")
|
var r = strings.Join(chars, "")
|
||||||
var reg = regexp.MustCompile("[" + r + "]+")
|
var reg = regexp.MustCompile("[" + r + "]")
|
||||||
|
|
||||||
func EscapeMd2(s string) string {
|
func EscapeMd2(s string) string {
|
||||||
return reg.ReplaceAllString(s, "\\$0")
|
return reg.ReplaceAllString(s, "\\$0")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Mention(name string, id int64) string {
|
func Mention(name string, id int64) string {
|
||||||
return fmt.Sprintf("[%s](tg://user?id=%d)", EscapeMd2(name), id)
|
text := fmt.Sprintf("[%s](tg://user?id=%d)", EscapeMd2(name), id)
|
||||||
|
log.Println(text)
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserMention(u *echotron.User) string {
|
func UserMention(u *echotron.User) string {
|
||||||
|
@ -164,3 +194,45 @@ 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 MentionWithData(u *echotron.User) string {
|
||||||
|
userid := strconv.FormatInt(u.ID, 10)
|
||||||
|
username := u.Username
|
||||||
|
userstr := fmt.Sprintf("userid: `%v`", userid)
|
||||||
|
if username != "" {
|
||||||
|
userstr += fmt.Sprintf(", username: @%s", u.Username)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("*%s* \\(%s\\)", UserMention(u), userstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MentionWithDataDB(u *db.User) string {
|
||||||
|
userid := strconv.FormatInt(u.UserId, 10)
|
||||||
|
username := u.Username
|
||||||
|
userstr := fmt.Sprintf("userid: `%v`", userid)
|
||||||
|
if username != "" {
|
||||||
|
userstr += fmt.Sprintf(", username: @%s", u.Username)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("*%s* \\(%s\\)", UserMentionDB(u), userstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -22,17 +22,30 @@ func TaskKickOldUsers(b *echotron.API) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
old := now.Add(-10 * time.Minute)
|
old := now.Add(-10 * time.Minute)
|
||||||
store := tongo.NewStore[db.User](Client)
|
store := tongo.NewStore[db.User](Client)
|
||||||
|
logstore := tongo.NewStore[db.LogChannel](Client)
|
||||||
|
logchat, _ := logstore.GetOne(ctx)
|
||||||
users, err := store.GetMany(ctx, tongo.E("date_joined", tongo.D(tongo.E("$lt", old))), tongo.E("is_joined", false))
|
users, err := store.GetMany(ctx, tongo.E("date_joined", tongo.D(tongo.E("$lt", old))), tongo.E("is_joined", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error in deleting task: %v", err)
|
log.Printf("Error in deleting task: %v", err)
|
||||||
}
|
}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
_, err := b.BanChatMember(user.ChatId, user.UserId, &echotron.BanOptions{RevokeMessages: true})
|
_, err := b.BanChatMember(user.ChatId, user.UserId, &echotron.BanOptions{RevokeMessages: true})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("User was not banned: ", err)
|
log.Println("User was not banned: ", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if logchat != nil {
|
||||||
|
b.SendMessage(
|
||||||
|
fmt.Sprintf("Пользователь %s не прошёл капчу \\#captcha", MentionWithDataDB(user)),
|
||||||
|
logchat.ChatId,
|
||||||
|
&echotron.MessageOptions{ParseMode: echotron.MarkdownV2})
|
||||||
|
}
|
||||||
log.Printf("User %s was banned", user.FirstName)
|
log.Printf("User %s was banned", user.FirstName)
|
||||||
|
_, err = b.DeleteMessage(user.ChatId, user.CaptchaMessage)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERR: Captcha message not deleted: ", err)
|
||||||
|
}
|
||||||
store.DeleteByID(ctx, user.Id)
|
store.DeleteByID(ctx, user.Id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +59,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
|
||||||
|
@ -56,6 +68,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)
|
||||||
|
@ -64,3 +77,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1
main.go
1
main.go
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue