Compare commits
	
		
			37 Commits
		
	
	
		
			echotron
			...
			c149e29dca
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c149e29dca | |||
| 715b6f5b7b | |||
| 70385fd500 | |||
| 15d5321704 | |||
| d3e1c99442 | |||
| a0e8b8d190 | |||
| e0972d8a2a | |||
| c6abbbef1d | |||
| 4afbc2564b | |||
| a3e12f3c45 | |||
| 82f49079ab | |||
| 09d7496c93 | |||
| 833a23ba77 | |||
| b631b4423a | |||
| 9533b43b98 | |||
| 635f27f5ed | |||
| e1319a951d | |||
| 737585197e | |||
| ebb8d3ca93 | |||
| c2b2d7c7b7 | |||
| 09cf1dcea8 | |||
| de4200c150 | |||
| e1c7b56049 | |||
| bb0862ba8b | |||
| 1c4a0adb61 | |||
| 75454a3233 | |||
| 23d4e9893e | |||
| 8f661d68a1 | |||
| 7ac170ce04 | |||
| 7fba6ddb93 | |||
| 765be0673b | |||
| 15a27af994 | |||
| 7e8b026c5e | |||
| 919b341fc1 | |||
| e02e879378 | |||
| 49147208d1 | |||
| 90617d751d | 
							
								
								
									
										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`, чтобы собрать образ бота и запустить контейнеры в стэке.
 | 
				
			||||||
 | 
				
			|||||||
@ -6,11 +6,10 @@ import (
 | 
				
			|||||||
	"image"
 | 
						"image"
 | 
				
			||||||
	"image/color"
 | 
						"image/color"
 | 
				
			||||||
	"image/png"
 | 
						"image/png"
 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/fogleman/gg"
 | 
						"github.com/fogleman/gg"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -53,15 +52,14 @@ func initImage() *gg.Context {
 | 
				
			|||||||
// На пустое изображение наносятся логотипы из списка, предварительно перемешанного.
 | 
					// На пустое изображение наносятся логотипы из списка, предварительно перемешанного.
 | 
				
			||||||
// К изображениям также добавляются порядковые номера (начиная с 1 вместо 0),
 | 
					// К изображениям также добавляются порядковые номера (начиная с 1 вместо 0),
 | 
				
			||||||
// а правильный вариант возвращается вместе с итоговой картинкой
 | 
					// а правильный вариант возвращается вместе с итоговой картинкой
 | 
				
			||||||
func GenCaptcha() Captcha {
 | 
					func GenCaptcha() *Captcha {
 | 
				
			||||||
	dc := initImage()
 | 
						dc := initImage()
 | 
				
			||||||
	rand.Seed(time.Now().UnixNano())
 | 
					 | 
				
			||||||
	rand.Shuffle(len(Logos), func(i, j int) { Logos[i], Logos[j] = Logos[j], Logos[i] })                          // Перемешиваем логотипы
 | 
						rand.Shuffle(len(Logos), func(i, j int) { Logos[i], Logos[j] = Logos[j], Logos[i] })                          // Перемешиваем логотипы
 | 
				
			||||||
	rand.Shuffle(len(XPositions), func(i, j int) { XPositions[i], XPositions[j] = XPositions[j], XPositions[i] }) // И позиции
 | 
						rand.Shuffle(len(XPositions), func(i, j int) { XPositions[i], XPositions[j] = XPositions[j], XPositions[i] }) // И позиции
 | 
				
			||||||
	correct_answer := 0
 | 
						correct_answer := 0
 | 
				
			||||||
	for i, logo := range Logos {
 | 
						for i, logo := range Logos {
 | 
				
			||||||
		x := XPositions[i]
 | 
							x := XPositions[i]
 | 
				
			||||||
		y := rand.Intn(ImageHeight - logo.Image.Bounds().Dy() - 30)
 | 
							y := rand.Intn(ImageHeight - logo.Image.Bounds().Dy() - 70)
 | 
				
			||||||
		dc.DrawImage(logo.Image, x, y)
 | 
							dc.DrawImage(logo.Image, x, y)
 | 
				
			||||||
		if logo.IsCorrect {
 | 
							if logo.IsCorrect {
 | 
				
			||||||
			correct_answer = i + 1
 | 
								correct_answer = i + 1
 | 
				
			||||||
@ -78,7 +76,7 @@ func GenCaptcha() Captcha {
 | 
				
			|||||||
		CorrectAnswer: correct_answer,
 | 
							CorrectAnswer: correct_answer,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return captcha
 | 
						return &captcha
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (captcha *Captcha) ToReader() *bytes.Reader {
 | 
					func (captcha *Captcha) ToReader() *bytes.Reader {
 | 
				
			||||||
@ -106,7 +104,7 @@ func (captcha *Captcha) ToBytes() (*[]byte, error) {
 | 
				
			|||||||
// Логотипы читаются из папки /assets рядом с исполняемым файлом.
 | 
					// Логотипы читаются из папки /assets рядом с исполняемым файлом.
 | 
				
			||||||
// Принимается формат .png, логотип, представляющий правильный ответ называется godot.png
 | 
					// Принимается формат .png, логотип, представляющий правильный ответ называется godot.png
 | 
				
			||||||
func Init() error {
 | 
					func Init() error {
 | 
				
			||||||
	files, err := ioutil.ReadDir("./assets")
 | 
						files, err := os.ReadDir("./assets")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,62 @@
 | 
				
			|||||||
package db
 | 
					package db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.nefrace.ru/nefrace/tongo"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Chat struct {
 | 
					type Chat struct {
 | 
				
			||||||
	Id      int64
 | 
						tongo.Item `bson:",inline"`
 | 
				
			||||||
 | 
						ChatId     int64 `bson:"chat_id"`
 | 
				
			||||||
	Title      string
 | 
						Title      string
 | 
				
			||||||
	TopicId    int64  `bson:"topic_id"`
 | 
						TopicId    int64  `bson:"topic_id"`
 | 
				
			||||||
 | 
						Greet      string `bson:"greet"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
				
			||||||
	Id             int64
 | 
						tongo.Item       `bson:",inline"`
 | 
				
			||||||
 | 
						UserId           int64     `bson:"user_id"`
 | 
				
			||||||
	ChatId           int64     `bson:"chat_id"`
 | 
						ChatId           int64     `bson:"chat_id"`
 | 
				
			||||||
	Username         string    `bson:"username"`
 | 
						Username         string    `bson:"username"`
 | 
				
			||||||
	FirstName        string    `bson:"first_name"`
 | 
						FirstName        string    `bson:"first_name"`
 | 
				
			||||||
	LastName         string    `bson:"last_name"`
 | 
						LastName         string    `bson:"last_name"`
 | 
				
			||||||
	CorrectAnswer    int8      `bson:"correct_answer"`
 | 
						CorrectAnswer    int8      `bson:"correct_answer"`
 | 
				
			||||||
	CaptchaMessage   int       `bson:"captcha_message"`
 | 
						CaptchaMessage   int       `bson:"captcha_message"`
 | 
				
			||||||
	IsBanned       bool   `bson:"is_banned"`
 | 
						IsJoined         bool      `bson:"is_joined"`
 | 
				
			||||||
	DateJoined     int64  `bson:"date_joined"`
 | 
						DateJoined       time.Time `bson:"date_joined"`
 | 
				
			||||||
	JoinedMessage    int       `bson:"joined_message"`
 | 
						JoinedMessage    int       `bson:"joined_message"`
 | 
				
			||||||
 | 
						LastNotification time.Time `bson:"last_notification"`
 | 
				
			||||||
 | 
						Warnings         int       `bson:"warnings"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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:
 | 
				
			||||||
      - 127.0.0.1:28002:27017
 | 
					      - 27017
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - mongodata:/data/db
 | 
					      - mongodata:/data/db
 | 
				
			||||||
      - mongoconfig:/data/configdb
 | 
					      - mongoconfig:/data/configdb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mongo-express:
 | 
					 | 
				
			||||||
    image: mongo-express
 | 
					 | 
				
			||||||
    restart: always
 | 
					 | 
				
			||||||
    ports:
 | 
					 | 
				
			||||||
      - 127.0.0.1:8088:8081
 | 
					 | 
				
			||||||
    env_file:
 | 
					 | 
				
			||||||
      - mongo.env
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
volumes:
 | 
					volumes:
 | 
				
			||||||
  mongodata:
 | 
					  mongodata:
 | 
				
			||||||
  mongoconfig:
 | 
					  mongoconfig:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								go.mod
									
									
									
									
									
								
							@ -3,26 +3,31 @@ module kickerbot
 | 
				
			|||||||
go 1.19
 | 
					go 1.19
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/NicoNex/echotron/v3 v3.21.0
 | 
						github.com/NicoNex/echotron/v3 v3.26.1
 | 
				
			||||||
	github.com/fogleman/gg v1.3.0
 | 
						github.com/fogleman/gg v1.3.0
 | 
				
			||||||
	github.com/go-co-op/gocron v1.18.0
 | 
						github.com/go-co-op/gocron v1.33.1
 | 
				
			||||||
	github.com/joho/godotenv v1.4.0
 | 
						github.com/joho/godotenv v1.5.1
 | 
				
			||||||
	go.mongodb.org/mongo-driver v1.11.1
 | 
						go.mongodb.org/mongo-driver v1.12.1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
 | 
						github.com/google/uuid v1.3.1 // indirect
 | 
				
			||||||
 | 
						go.uber.org/atomic v1.11.0 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						git.nefrace.ru/nefrace/tongo v0.0.0-20230604223535-44cc124fb18a
 | 
				
			||||||
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
						github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
				
			||||||
	github.com/golang/snappy v0.0.4 // indirect
 | 
						github.com/golang/snappy v0.0.4 // indirect
 | 
				
			||||||
	github.com/klauspost/compress v1.15.13 // indirect
 | 
						github.com/klauspost/compress v1.16.7 // indirect
 | 
				
			||||||
	github.com/montanaflynn/stats v0.6.6 // indirect
 | 
						github.com/montanaflynn/stats v0.7.1 // indirect
 | 
				
			||||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
					 | 
				
			||||||
	github.com/robfig/cron/v3 v3.0.1 // indirect
 | 
						github.com/robfig/cron/v3 v3.0.1 // indirect
 | 
				
			||||||
	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
						github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 | 
				
			||||||
	github.com/xdg-go/scram v1.1.2 // indirect
 | 
						github.com/xdg-go/scram v1.1.2 // indirect
 | 
				
			||||||
	github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
						github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
				
			||||||
	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
 | 
						github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.4.0 // indirect
 | 
						golang.org/x/crypto v0.13.0 // indirect
 | 
				
			||||||
	golang.org/x/image v0.2.0 // indirect
 | 
						golang.org/x/image v0.12.0 // indirect
 | 
				
			||||||
	golang.org/x/sync v0.1.0 // indirect
 | 
						golang.org/x/sync v0.3.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.5.0 // indirect
 | 
						golang.org/x/text v0.13.0 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										91
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								go.sum
									
									
									
									
									
								
							@ -1,12 +1,15 @@
 | 
				
			|||||||
github.com/NicoNex/echotron/v3 v3.21.0 h1:6YDSYA/AGlPxWoUdOIRrBvUZ0RhRa2W4taS4eSrbdXw=
 | 
					git.nefrace.ru/nefrace/tongo v0.0.0-20230604223535-44cc124fb18a h1:rzWY7QiyuOMxTl6oMqkh2xi6m4KmNO2edZi8idmB57I=
 | 
				
			||||||
github.com/NicoNex/echotron/v3 v3.21.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI=
 | 
					git.nefrace.ru/nefrace/tongo v0.0.0-20230604223535-44cc124fb18a/go.mod h1:QfvcElrkpZQAXeDzEEVh9fbViVFYS/ZOsn8Zvu6cTcg=
 | 
				
			||||||
 | 
					github.com/NicoNex/echotron/v3 v3.26.1 h1:2I+jwvrMsbot+Di39Ro3k3XSiPJTtTh+fFOOiG4RzZI=
 | 
				
			||||||
 | 
					github.com/NicoNex/echotron/v3 v3.26.1/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI=
 | 
				
			||||||
 | 
					github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
 | 
					github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
 | 
				
			||||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 | 
					github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 | 
				
			||||||
github.com/go-co-op/gocron v1.18.0 h1:SxTyJ5xnSN4byCq7b10LmmszFdxQlSQJod8s3gbnXxA=
 | 
					github.com/go-co-op/gocron v1.33.1 h1:wjX+Dg6Ae29a/f9BSQjY1Rl+jflTpW9aDyMqseCj78c=
 | 
				
			||||||
github.com/go-co-op/gocron v1.18.0/go.mod h1:sD/a0Aadtw5CpflUJ/lpP9Vfdk979Wl1Sg33HPHg0FY=
 | 
					github.com/go-co-op/gocron v1.33.1/go.mod h1:NLi+bkm4rRSy1F8U7iacZOz0xPseMoIOnvabGoSe/no=
 | 
				
			||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
 | 
					github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
 | 
				
			||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 | 
					github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 | 
				
			||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
					github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
				
			||||||
@ -14,73 +17,73 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 | 
				
			|||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
					github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
				
			||||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
 | 
					github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
 | 
				
			||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
					github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
				
			||||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
 | 
					github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
 | 
				
			||||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
					github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
				
			||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
					github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 | 
				
			||||||
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
 | 
					github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
 | 
				
			||||||
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 | 
					github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 | 
				
			||||||
github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0=
 | 
					 | 
				
			||||||
github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 | 
					 | 
				
			||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 | 
					 | 
				
			||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
					github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 | 
				
			||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
					github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
				
			||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 | 
					 | 
				
			||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
					github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
				
			||||||
 | 
					github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
				
			||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 | 
					github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 | 
				
			||||||
github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ=
 | 
					github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
 | 
				
			||||||
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 | 
					github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 | 
				
			||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
					github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 | 
				
			||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
					 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
					github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
				
			||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
					github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
					github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
				
			||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 | 
					github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
				
			||||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 | 
					github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
				
			||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 | 
					github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 | 
				
			||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
					github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
				
			||||||
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
 | 
					 | 
				
			||||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 | 
					 | 
				
			||||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 | 
					github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 | 
				
			||||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 | 
					github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 | 
				
			||||||
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
 | 
					 | 
				
			||||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 | 
					 | 
				
			||||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 | 
					github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 | 
				
			||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 | 
					github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 | 
				
			||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 | 
					github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 | 
				
			||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
 | 
					github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
 | 
				
			||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
 | 
					github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
 | 
				
			||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
					github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
				
			||||||
go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE=
 | 
					go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
 | 
				
			||||||
go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
 | 
					go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
 | 
				
			||||||
go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8=
 | 
					go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 | 
				
			||||||
go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
 | 
					go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 | 
				
			||||||
 | 
					go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
					golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
					golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
					golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
					golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
				
			||||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
 | 
					golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
 | 
				
			||||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 | 
					golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 | 
				
			||||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
 | 
					golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
 | 
				
			||||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
 | 
					golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
 | 
				
			||||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
 | 
					 | 
				
			||||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
 | 
					 | 
				
			||||||
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
 | 
					 | 
				
			||||||
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
 | 
					 | 
				
			||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
					golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
				
			||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
					golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
					golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
					golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
				
			||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
					golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
				
			||||||
 | 
					golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					 | 
				
			||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 | 
					 | 
				
			||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
					golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
@ -88,25 +91,29 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
 | 
					golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
				
			||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
					golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
					golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
					golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
				
			||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 | 
					golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 | 
				
			||||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
 | 
					golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
				
			||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
					golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
 | 
				
			||||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 | 
					golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 | 
				
			||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
					 | 
				
			||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
					golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
					golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
				
			||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
					golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 | 
					golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 | 
				
			||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
					golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
				
			||||||
 | 
					gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
				
			|||||||
@ -2,91 +2,270 @@ package kicker
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"kickerbot/captchagen"
 | 
						"kickerbot/captchagen"
 | 
				
			||||||
	"kickerbot/db"
 | 
						"kickerbot/db"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tb "github.com/NicoNex/echotron/v3"
 | 
						"git.nefrace.ru/nefrace/tongo"
 | 
				
			||||||
	"go.mongodb.org/mongo-driver/bson"
 | 
						"github.com/NicoNex/echotron/v3"
 | 
				
			||||||
 | 
						"go.mongodb.org/mongo-driver/mongo"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func userJoined(b *bot, update *tb.Update) error {
 | 
					func userJoined(b *bot, update *echotron.Update) error {
 | 
				
			||||||
	captcha := captchagen.GenCaptcha()
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
				
			||||||
	_, err := b.DeleteMessage(update.Message.Chat.ID, update.Message.ID)
 | 
						defer cancel()
 | 
				
			||||||
 | 
						store := tongo.NewStore[db.User](Client)
 | 
				
			||||||
 | 
						usr := update.Message.NewChatMembers[0]
 | 
				
			||||||
 | 
						message := update.Message
 | 
				
			||||||
 | 
						_, 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()
 | 
						user, err := store.GetOne(ctx, tongo.E("chat_id", update.ChatID()), tongo.E("user_id", usr.ID))
 | 
				
			||||||
 | 
						var captcha *captchagen.Captcha
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		fmt.Printf("Error creating captcha bytes: %v", bytes)
 | 
							if errors.Is(err, mongo.ErrNoDocuments) {
 | 
				
			||||||
		b.SendMessage("Не могу создать капчу, @nefrace, проверь логи.", update.Message.From.ID, &tb.MessageOptions{MessageThreadID: update.Message.ThreadID})
 | 
								captcha = captchagen.GenCaptcha()
 | 
				
			||||||
	}
 | 
								user = &db.User{
 | 
				
			||||||
	message := update.Message
 | 
									Item:             tongo.NewID(),
 | 
				
			||||||
	user := db.User{
 | 
									UserId:           usr.ID,
 | 
				
			||||||
		Id:            message.From.ID,
 | 
									Username:         usr.Username,
 | 
				
			||||||
		Username:      message.From.Username,
 | 
									FirstName:        usr.FirstName,
 | 
				
			||||||
		FirstName:     message.From.FirstName,
 | 
									LastName:         usr.LastName,
 | 
				
			||||||
		LastName:      message.From.LastName,
 | 
									IsJoined:         false,
 | 
				
			||||||
		IsBanned:      false,
 | 
					 | 
				
			||||||
				ChatId:           message.Chat.ID,
 | 
									ChatId:           message.Chat.ID,
 | 
				
			||||||
				JoinedMessage:    message.ID,
 | 
									JoinedMessage:    message.ID,
 | 
				
			||||||
				CorrectAnswer:    int8(captcha.CorrectAnswer),
 | 
									CorrectAnswer:    int8(captcha.CorrectAnswer),
 | 
				
			||||||
		DateJoined:    time.Now().Unix(),
 | 
									DateJoined:       time.Now(),
 | 
				
			||||||
 | 
									LastNotification: time.Now(),
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
	user.CorrectAnswer = int8(captcha.CorrectAnswer)
 | 
							} else {
 | 
				
			||||||
	d := db.GetDatabase()
 | 
								log.Printf("Can't find user: %v", err)
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
							}
 | 
				
			||||||
	defer cancel()
 | 
						}
 | 
				
			||||||
	log.Print(user)
 | 
						if captcha == nil {
 | 
				
			||||||
	msg := fmt.Sprintf("Приветствую тебя, *[%s](tg://user?id=%d)*\\!\nДля подтверждения, что ты человек, выбери логотип движка, которому посвящен данный чат, и отправь его номер сюда\\.\n*_Я дам тебе две минуты на это\\._*", EscapeText(tb.MarkdownV2, user.FirstName), user.Id)
 | 
							return nil
 | 
				
			||||||
	options := tb.PhotoOptions{
 | 
						}
 | 
				
			||||||
 | 
						bytes, err := captcha.ToBytes()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("Error creating captcha bytes: %v", bytes)
 | 
				
			||||||
 | 
							b.SendMessage("Не могу создать капчу, @nefrace, проверь логи.", update.Message.Chat.ID, &echotron.MessageOptions{MessageThreadID: int64(update.Message.ThreadID)})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						msg := fmt.Sprintf("Приветствую тебя, *%s*\\!\nДля подтверждения, что ты человек, выбери логотип движка, которому посвящен данный чат, и отправь его номер сюда\\.\n*_Я дам тебе десять минут на это\\._*", UserMention(usr))
 | 
				
			||||||
 | 
						options := echotron.PhotoOptions{
 | 
				
			||||||
		Caption:   msg,
 | 
							Caption:   msg,
 | 
				
			||||||
		ParseMode: tb.MarkdownV2,
 | 
							ParseMode: echotron.MarkdownV2,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if message.Chat.IsForum {
 | 
						if message.Chat.IsForum {
 | 
				
			||||||
		options.MessageThreadID = int(b.CaptchaTopic)
 | 
							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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	user.CaptchaMessage = result.Result.ID
 | 
						user.CaptchaMessage = result.Result.ID
 | 
				
			||||||
 | 
						store.InsertOne(ctx, user)
 | 
				
			||||||
	d.NewUser(ctx, user)
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func userLeft(b *bot, update *tb.Update) error {
 | 
					func userLeft(b *bot, update *echotron.Update) error {
 | 
				
			||||||
	message := update.Message
 | 
						message := update.Message
 | 
				
			||||||
	sender := message.From
 | 
						sender := message.From
 | 
				
			||||||
	d := db.GetDatabase()
 | 
						store := tongo.NewStore[db.User](Client)
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						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), tongo.E("is_joined", false)); err == nil { //d.GetUser(ctx, db.User{UserId: sender.ID, ChatId: message.Chat.ID}); err == nil {
 | 
				
			||||||
		d.RemoveUser(ctx, user)
 | 
							store.DeleteByID(ctx, user.Id)
 | 
				
			||||||
		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)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func checkCaptcha(b *bot, update *tb.Update) error {
 | 
					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
 | 
						message := update.Message
 | 
				
			||||||
	sender := message.From
 | 
					
 | 
				
			||||||
	d := db.GetDatabase()
 | 
						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)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						defer cancel()
 | 
				
			||||||
	if user, err := d.GetUser(ctx, db.User{Id: sender.ID, ChatId: message.Chat.ID}); err == nil {
 | 
						until := time.Now().AddDate(0, 0, days)
 | 
				
			||||||
		if message.Chat.IsForum {
 | 
						_, err = b.RestrictChatMember(message.Chat.ID, reply.From.ID, muted, &echotron.RestrictOptions{UntilDate: int(until.Unix())})
 | 
				
			||||||
			chat, err := d.GetChat(ctx, message.Chat.ID)
 | 
						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 {
 | 
				
			||||||
 | 
						m := update.ChatMember
 | 
				
			||||||
 | 
						c := m.Chat
 | 
				
			||||||
 | 
						u := m.NewChatMember.User
 | 
				
			||||||
 | 
						store := tongo.NewStore[db.User](Client)
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func checkCaptcha(b *bot, update *echotron.Update) error {
 | 
				
			||||||
 | 
						message := update.Message
 | 
				
			||||||
 | 
						sender := message.From
 | 
				
			||||||
 | 
						store := tongo.NewStore[db.User](Client)
 | 
				
			||||||
 | 
						chatStore := tongo.NewStore[db.Chat](Client)
 | 
				
			||||||
 | 
						// 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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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 {
 | 
				
			||||||
 | 
							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*, сначала пройди [капчу](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
 | 
									return nil
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -95,72 +274,153 @@ func checkCaptcha(b *bot, update *tb.Update) error {
 | 
				
			|||||||
		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) {
 | 
				
			||||||
				_ = d.RemoveUser(ctx, user)
 | 
									user.IsJoined = true
 | 
				
			||||||
 | 
									store.ReplaceItem(ctx, *user, true)
 | 
				
			||||||
				solved = true
 | 
									solved = true
 | 
				
			||||||
				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)
 | 
				
			||||||
				msg := fmt.Sprintf("Приветствую тебя, *[%s](tg://user?id=%d)* успешно прошёл капчу\\!", EscapeText(tb.MarkdownV2, user.FirstName), user.Id)
 | 
									msg := fmt.Sprintf("Капча успешно пройдена пользователем *%s*", UserMention(sender))
 | 
				
			||||||
				options := tb.MessageOptions{
 | 
									timeout := 10 * time.Second
 | 
				
			||||||
					ParseMode: tb.MarkdownV2,
 | 
									if chat.Greet != "" {
 | 
				
			||||||
 | 
										msg = fmt.Sprintf(chat.Greet, UserMention(sender))
 | 
				
			||||||
 | 
										timeout = 2 * time.Minute
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									options := echotron.MessageOptions{
 | 
				
			||||||
 | 
										ParseMode: echotron.MarkdownV2,
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if message.Chat.IsForum {
 | 
									if message.Chat.IsForum {
 | 
				
			||||||
					options.MessageThreadID = int(b.CaptchaTopic)
 | 
										options.MessageThreadID = b.CaptchaTopic
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				res, err := b.SendMessage(msg, message.Chat.ID, &options)
 | 
									res, err := b.SendMessage(msg, message.Chat.ID, &options)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					log.Printf("Can't send welcome message: %s", err)
 | 
										log.Printf("Can't send welcome message: %s", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				time.Sleep(time.Second * 10)
 | 
									go waitAndDelete(&b.API, res.Result, timeout)
 | 
				
			||||||
				_, err = b.DeleteMessage(message.Chat.ID, res.Result.ID)
 | 
									// time.Sleep(time.Second * 10)
 | 
				
			||||||
 | 
									// _, err = b.DeleteMessage(message.Chat.ID, res.Result.ID)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					log.Printf("Can't delete welcome message: %s", err)
 | 
										log.Printf("Can't delete welcome message: %s", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		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)
 | 
				
			||||||
			_ = d.RemoveUser(ctx, user)
 | 
								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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} 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
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func botAdded(b *bot, update *tb.Update) error {
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func botAdded(b *bot, update *echotron.Update) error {
 | 
				
			||||||
	m := update.Message
 | 
						m := update.Message
 | 
				
			||||||
	chat := db.Chat{
 | 
						store := tongo.NewStore[db.Chat](Client)
 | 
				
			||||||
		Id:      m.Chat.ID,
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
						chat, err := store.GetOne(ctx, tongo.E("chat_id", m.Chat.ID))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							chat = &db.Chat{
 | 
				
			||||||
 | 
								Item:    tongo.NewID(),
 | 
				
			||||||
 | 
								ChatId:  m.Chat.ID,
 | 
				
			||||||
			Title:   m.Chat.Title,
 | 
								Title:   m.Chat.Title,
 | 
				
			||||||
			TopicId: 0,
 | 
								TopicId: 0,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
						}
 | 
				
			||||||
	defer cancel()
 | 
					
 | 
				
			||||||
	d := db.GetDatabase()
 | 
						_, err = store.InsertOne(ctx, chat)
 | 
				
			||||||
	err := d.NewChat(ctx, chat)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Print(err)
 | 
							log.Print(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func setTopic(b *bot, update *tb.Update) error {
 | 
					func setTopic(b *bot, update *echotron.Update) error {
 | 
				
			||||||
	m := update.Message
 | 
						m := update.Message
 | 
				
			||||||
	d := db.GetDatabase()
 | 
					 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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)
 | 
						b.CaptchaTopic = int64(m.ThreadID)
 | 
				
			||||||
	err = d.UpdateChat(ctx, chat, upd)
 | 
						err = store.ReplaceItem(ctx, *chat, false)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	b.DeleteMessage(m.Chat.ID, m.ID)
 | 
						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: int64(m.ThreadID)})
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										161
									
								
								kicker/kicker.go
									
									
									
									
									
								
							
							
						
						
									
										161
									
								
								kicker/kicker.go
									
									
									
									
									
								
							@ -2,22 +2,28 @@ package kicker
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"kickerbot/db"
 | 
						"kickerbot/db"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tb "github.com/NicoNex/echotron/v3"
 | 
						"git.nefrace.ru/nefrace/tongo"
 | 
				
			||||||
 | 
						"github.com/NicoNex/echotron/v3"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Client *tongo.Database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type bot struct {
 | 
					type bot struct {
 | 
				
			||||||
	chatID       int64
 | 
						chatID       int64
 | 
				
			||||||
	CaptchaTopic int64
 | 
						CaptchaTopic int64
 | 
				
			||||||
	Me           *tb.User
 | 
						Me           *echotron.User
 | 
				
			||||||
	tb.API
 | 
						echotron.API
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (b *bot) Update(update *tb.Update) {
 | 
					func (b *bot) Update(update *echotron.Update) {
 | 
				
			||||||
	if update.Message != nil {
 | 
						if update.Message != nil {
 | 
				
			||||||
		if len(update.Message.NewChatMembers) != 0 {
 | 
							if len(update.Message.NewChatMembers) != 0 {
 | 
				
			||||||
			for _, user := range update.Message.NewChatMembers {
 | 
								for _, user := range update.Message.NewChatMembers {
 | 
				
			||||||
@ -33,43 +39,77 @@ func (b *bot) Update(update *tb.Update) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if update.Message.Text != "" {
 | 
							if update.Message.Text != "" {
 | 
				
			||||||
 | 
								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" {
 | 
									if update.Message.Text == "/settopic" {
 | 
				
			||||||
					setTopic(b, update)
 | 
										setTopic(b, update)
 | 
				
			||||||
					return
 | 
										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)
 | 
								checkCaptcha(b, update)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							checkAdminTopics(b, update)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if update.ChatMember != nil {
 | 
				
			||||||
 | 
							m := update.ChatMember.NewChatMember
 | 
				
			||||||
 | 
							if m.Status == "kicked" {
 | 
				
			||||||
 | 
								userBanned(b, update)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Базовая структура для бота
 | 
					// Базовая структура для бота
 | 
				
			||||||
type Kicker struct {
 | 
					type Kicker struct {
 | 
				
			||||||
	Token      string
 | 
						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()
 | 
						store := tongo.NewStore[db.Chat](Client)
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						defer cancel()
 | 
				
			||||||
	chat := db.Chat{
 | 
					
 | 
				
			||||||
		Id:      chatID,
 | 
						chat, err := store.GetOne(ctx, tongo.E("chat_id", chatID))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							chat = &db.Chat{
 | 
				
			||||||
 | 
								Item:    tongo.NewID(),
 | 
				
			||||||
 | 
								ChatId:  chatID,
 | 
				
			||||||
			Title:   "",
 | 
								Title:   "",
 | 
				
			||||||
			TopicId: 0,
 | 
								TopicId: 0,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	if !d.ChatExists(ctx, chat) {
 | 
							if _, err := store.InsertOne(ctx, chat); err != nil {
 | 
				
			||||||
		if err := d.NewChat(ctx, chat); err != nil {
 | 
					 | 
				
			||||||
			return &bot{}
 | 
								return &bot{}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	chat, _ = d.GetChat(ctx, chatID)
 | 
					 | 
				
			||||||
	CaptchaTopic := chat.TopicId
 | 
						CaptchaTopic := chat.TopicId
 | 
				
			||||||
	result := &bot{
 | 
						result := &bot{
 | 
				
			||||||
		chatID,
 | 
							chatID,
 | 
				
			||||||
		CaptchaTopic,
 | 
							CaptchaTopic,
 | 
				
			||||||
		nil,
 | 
							nil,
 | 
				
			||||||
		tb.NewAPI(b.Token),
 | 
							echotron.NewAPI(b.Token),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						log.Println("New bot created with CaptchaTopic", result.CaptchaTopic)
 | 
				
			||||||
	me, err := result.GetMe()
 | 
						me, err := result.GetMe()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							log.Println(err)
 | 
				
			||||||
@ -81,23 +121,31 @@ func (b *Kicker) NewBot(chatID int64) tb.Bot {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Initialize bot with token
 | 
					// Initialize bot with token
 | 
				
			||||||
func (b *Kicker) Init() error {
 | 
					func (b *Kicker) Init() error {
 | 
				
			||||||
	dsp := tb.NewDispatcher(b.Token, b.NewBot)
 | 
						dsp := echotron.NewDispatcher(b.Token, b.NewBot)
 | 
				
			||||||
	b.Dispatcher = dsp
 | 
						b.Dispatcher = dsp
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (b *Kicker) Start() error {
 | 
					func (b *Kicker) Start() error {
 | 
				
			||||||
	return b.Dispatcher.Poll()
 | 
						return b.Dispatcher.PollOptions(true, echotron.UpdateOptions{
 | 
				
			||||||
 | 
							Timeout: 120,
 | 
				
			||||||
 | 
							AllowedUpdates: []echotron.UpdateType{
 | 
				
			||||||
 | 
								echotron.MessageUpdate,
 | 
				
			||||||
 | 
								echotron.ChatMemberUpdate,
 | 
				
			||||||
 | 
								echotron.MyChatMemberUpdate,
 | 
				
			||||||
 | 
								echotron.CallbackQueryUpdate,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func EscapeText(parseMode tb.ParseMode, text string) string {
 | 
					func EscapeText(parseMode echotron.ParseMode, text string) string {
 | 
				
			||||||
	var replacer *strings.Replacer
 | 
						var replacer *strings.Replacer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if parseMode == tb.HTML {
 | 
						if parseMode == echotron.HTML {
 | 
				
			||||||
		replacer = strings.NewReplacer("<", "<", ">", ">", "&", "&")
 | 
							replacer = strings.NewReplacer("<", "<", ">", ">", "&", "&")
 | 
				
			||||||
	} else if parseMode == tb.Markdown {
 | 
						} else if parseMode == echotron.Markdown {
 | 
				
			||||||
		replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[")
 | 
							replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[")
 | 
				
			||||||
	} else if parseMode == tb.MarkdownV2 {
 | 
						} else if parseMode == echotron.MarkdownV2 {
 | 
				
			||||||
		replacer = strings.NewReplacer(
 | 
							replacer = strings.NewReplacer(
 | 
				
			||||||
			"_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(",
 | 
								"_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(",
 | 
				
			||||||
			"\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>",
 | 
								"\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>",
 | 
				
			||||||
@ -110,3 +158,78 @@ func EscapeText(parseMode tb.ParseMode, text string) string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return replacer.Replace(text)
 | 
						return replacer.Replace(text)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func waitAndDelete(b *echotron.API, message *echotron.Message, t time.Duration) {
 | 
				
			||||||
 | 
						time.Sleep(t)
 | 
				
			||||||
 | 
						if _, err := b.DeleteMessage(message.Chat.ID, message.ID); err != nil {
 | 
				
			||||||
 | 
							log.Printf("Can't delay-delete message: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func MentionUser(user *echotron.User) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("[%s](tg://user?id=%d)", EscapeText(echotron.MarkdownV2, user.FirstName), user.ID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var chars = []string{"_", "\\*", "\\[", "\\]", "\\(", "\\)", "~", "`", ">", "#", "\\+", "\\-", "=", "|", "{", "}", "\\.", "!"}
 | 
				
			||||||
 | 
					var r = strings.Join(chars, "")
 | 
				
			||||||
 | 
					var reg = regexp.MustCompile("[" + r + "]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EscapeMd2(s string) string {
 | 
				
			||||||
 | 
						return reg.ReplaceAllString(s, "\\$0")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Mention(name string, id int64) string {
 | 
				
			||||||
 | 
						text := fmt.Sprintf("[%s](tg://user?id=%d)", EscapeMd2(name), id)
 | 
				
			||||||
 | 
						log.Println(text)
 | 
				
			||||||
 | 
						return text
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func UserMention(u *echotron.User) string {
 | 
				
			||||||
 | 
						return Mention(u.FirstName, u.ID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func UserMentionDB(u *db.User) string {
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,43 +2,90 @@ package kicker
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"kickerbot/db"
 | 
						"kickerbot/db"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tb "github.com/NicoNex/echotron/v3"
 | 
						"git.nefrace.ru/nefrace/tongo"
 | 
				
			||||||
	"go.mongodb.org/mongo-driver/bson"
 | 
						"github.com/NicoNex/echotron/v3"
 | 
				
			||||||
	"go.mongodb.org/mongo-driver/bson/primitive"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TaskBot struct {
 | 
					type TaskBot struct {
 | 
				
			||||||
	Token string
 | 
						Token string
 | 
				
			||||||
	tb.API
 | 
						echotron.API
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TaskKickOldUsers(b *tb.API) {
 | 
					func TaskKickOldUsers(b *echotron.API) {
 | 
				
			||||||
	d := db.GetDatabase()
 | 
					 | 
				
			||||||
	log.Print("STARTING KICKING TASK")
 | 
					 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						defer cancel()
 | 
				
			||||||
	now := time.Now().Unix()
 | 
						now := time.Now()
 | 
				
			||||||
	old := now - 120
 | 
						old := now.Add(-10 * time.Minute)
 | 
				
			||||||
	filter := bson.D{
 | 
						store := tongo.NewStore[db.User](Client)
 | 
				
			||||||
		primitive.E{Key: "date_joined", Value: bson.D{bson.E{Key: "$lt", Value: old}}},
 | 
						logstore := tongo.NewStore[db.LogChannel](Client)
 | 
				
			||||||
	}
 | 
						logchat, _ := logstore.GetOne(ctx)
 | 
				
			||||||
	users, err := d.GetUsers(ctx, filter)
 | 
						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.Id, &tb.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
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		b.DeleteMessage(user.ChatId, user.CaptchaMessage)
 | 
							if logchat != nil {
 | 
				
			||||||
		b.DeleteMessage(user.ChatId, user.JoinedMessage)
 | 
								b.SendMessage(
 | 
				
			||||||
		d.RemoveUser(ctx, user)
 | 
									fmt.Sprintf("Пользователь %s не прошёл капчу \\#captcha", MentionWithDataDB(user)),
 | 
				
			||||||
 | 
									logchat.ChatId,
 | 
				
			||||||
 | 
									&echotron.MessageOptions{ParseMode: echotron.MarkdownV2})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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, tongo.E("is_joined", false))
 | 
				
			||||||
 | 
						for _, user := range users {
 | 
				
			||||||
 | 
							if time.Since(user.LastNotification) > 2*time.Minute {
 | 
				
			||||||
 | 
								user.LastNotification = time.Now()
 | 
				
			||||||
 | 
								store.ReplaceItem(ctx, *user, false)
 | 
				
			||||||
 | 
								chat, err := chatStore.GetOne(ctx, tongo.E("chat_id", user.ChatId))
 | 
				
			||||||
 | 
								var topic int64 = 0
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Printf("Can't get chat from user: %s", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								} 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)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								go waitAndDelete(b, res.Result, 2*time.Minute)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								main.go
									
									
									
									
									
								
							@ -2,17 +2,19 @@ package main
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"kickerbot/captchagen"
 | 
						"kickerbot/captchagen"
 | 
				
			||||||
	"kickerbot/db"
 | 
					 | 
				
			||||||
	"kickerbot/kicker"
 | 
						"kickerbot/kicker"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.nefrace.ru/nefrace/tongo"
 | 
				
			||||||
	"github.com/NicoNex/echotron/v3"
 | 
						"github.com/NicoNex/echotron/v3"
 | 
				
			||||||
	"github.com/go-co-op/gocron"
 | 
						"github.com/go-co-op/gocron"
 | 
				
			||||||
	"github.com/joho/godotenv"
 | 
						"github.com/joho/godotenv"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var client *tongo.Database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	err := godotenv.Load()
 | 
						err := godotenv.Load()
 | 
				
			||||||
	captchagen.Init()
 | 
						captchagen.Init()
 | 
				
			||||||
@ -23,16 +25,20 @@ func main() {
 | 
				
			|||||||
	if !exists {
 | 
						if !exists {
 | 
				
			||||||
		log.Fatal("no token specified")
 | 
							log.Fatal("no token specified")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, dberr := db.Init(os.Getenv("MONGO_URI"))
 | 
						client, err = tongo.NewConnection(os.Getenv("MONGO_URI"), "godotkicker")
 | 
				
			||||||
	if dberr != nil {
 | 
						// _, dberr := db.Init(os.Getenv("MONGO_URI"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatal(err)
 | 
							log.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						kicker.Client = client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Bot := kicker.Kicker{Token: token}
 | 
						Bot := kicker.Kicker{Token: token}
 | 
				
			||||||
	Bot.Init()
 | 
						Bot.Init()
 | 
				
			||||||
	scheduler := gocron.NewScheduler(time.UTC)
 | 
						scheduler := gocron.NewScheduler(time.UTC)
 | 
				
			||||||
	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(2).Minutes().Do(func() { kicker.TaskRemoveOldMutes(&tasker) })
 | 
				
			||||||
	scheduler.StartAsync()
 | 
						scheduler.StartAsync()
 | 
				
			||||||
	Bot.Start()
 | 
						Bot.Start()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user