diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2ce0f66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ + +FROM golang:1.18.3 as builder +LABEL stage=tgbotbuilder +WORKDIR /src + +COPY go.mod . +COPY go.sum . +RUN go mod download + +COPY . . + +RUN make build + +FROM alpine:latest as certs +LABEL stage=tgbotbuilder +RUN apk --update add ca-certificates + +FROM scratch +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=builder /app /app +ADD folders.tar.gz / + + + +ENTRYPOINT [ "/app" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a589b8e --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: default build image run + +default: build + +build: + CGO_ENABLED=1 GOOS=linux go build -o /app -a -ldflags '-linkmode external -extldflags "-static"' cmd/main.go + +image: + docker build -t skillbot:latest . + docker image prune -f --filter label=stage=tgbotbuilder + +run: + go run cmd/main.go \ No newline at end of file diff --git a/README.md b/README.md index 1164b0e..eea8bd3 100644 --- a/README.md +++ b/README.md @@ -1 +1,98 @@ # tg-bot-users 🤖 + +Телеграм бот написан на языке GoLang. +База данных построена на SQLite3 + +# Список функций: + + +1. Бот отслеживает вход юбилейных пользователей в группы. Оповещение приходит связанным группам модераторов. + Копия в админку. Возможность поздравить юбилейного пользователя ответным сообщением, есть только у админки. + Но и из любой группы модераторов можно вызвать список последних юбилейных пользователей и поздравить пользователя. + (Первое поздравление из админки сделано в целях исключения дублирования нажатия "поздравить" из + админки и связанной группы модераторов.) + +2. При совпадении ID пользователя в списке юбилейных пользователей в админку приходит уведомление. +3. Список трех последних юбилейных пользователей можно вызвать из **меню администратора**. +4. Список всех юбилейных пользователей можно вызвать из **меню администратора**. +5. Список всех команд можно вызвать из **меню администратора**. Доступно прямое копирование команды нажатием на неё. +6. В меню встроена кнопка "Памятка модераторам", выводит текст в виде сообщения. Текст задается перед запуском в файле конфигурации. +7. Функция фильтрации содержания нецензурных слов в тесте сообщений в группах пользователей. +8. Функция пополнения списка нецензурных слов. Доступно в только в группах модераторов и админке. +9. Функция передачи ID и названия группы автоматическим сообщением в админку. +10. Реализовано несколько вариантов добавления групп модераторов: + 1 - Запрос из любой группы с ботом командой "", запрос приходит в админку, далее принимается решение одобрить или нет. + 2 - Примой командой с номером группы. Доступно из админки. + 3 - При связывании групп модераторов и пользователей, если группа модераторов не найдена в списке доступных. + (функция добавления группы модераторов без связывания с группами пользователей реализована с целью дальнейшего расширения функционала бота.) +11. Функция просмотра всех групп модераторов и пользователей. +12. Функция фильтрации символов в словах сообщений. Сделано с целью предотвращения маскировки нецензурных слов в сообщениях. + Пополнить или изменить список символов можно в файле конфигурации. +13. На большинство административных сообщений действует автоматическая очистка. + + + + +# Список команд: + +1. [x] **меню** _(Вызов только из групп модераторов и админки.)_ +2. [x] **chatinfo** _(Вызов из любой группы, где есть бот администратор. Присылает сообщение с ID и названием группы в админку.)_ +3. [x] **moder** _(Вызов из любой группы, где есть бот. Присылает запрос в админку на добавление группы в список + модераторов. Действует ограничение по времени 60 секунд.)_ +4. [x] **add-moder-user-link** _(Вызов только из админки. Связывает группу модераторов и пользователей для + персонализации оповещений о новых пользователях и нецензурных слов в группе.)_ +5. [x] **addmoderatorgroup + ID группы** _(Добавление группы модераторов по ID из админки.)_ +6. [x] **Мат** + слово _(Слово будет добавлено в базу, работает только в группах администраторов.)_ + + +# Конфигурация приложения: + +### Запуск приложения локально: /etc/tgbot/.env + +| ПАРАМЕТР: | ОПИСАНИЕ: | +|:-----------------------------:|:-----------------------------------------------------------------------------------------------:| +| TG-BOT-TOKEN | Токен для вашего бота, полученный от BotFather | +| MULTIPLICITY | Кратность выявления новых пользователей | +| TG-BOT-LOG-LEVEL | Уровень логирования приложения. `panic`, `fatal`, `error`, `warning`, `info`, `debug`, `trace` | +| MODERATORS-GROUP | ID группы админки | +| MSG-OF-BAD-WORDS-TO-USER-CHAT | Текст который будет выводиться в группу пользователей при обнаружении нецензурных слов | +| MSG-TO-CHAT-IF-NEW-USER | Текст приветствия в группу при вступлении в группу нового пользователя | +| MSG-MODERATOR-MEMBER | Текст памятка модераторам. Выводится при нажатии на кнопку `Памятка модераторам` | +| MSG-TRIM-SYMBOL | Список символов для удаления из строк при проверке слов в сообщениях | + + +Клонировать репозиторий, обновить пакеты командой `go mod tidy` , убедиться что все пакеты скачаны и установлены. +Запуск приложения из папки cmd - go run main.go + +# Запуск в Докер-контейнере: + +- Команда "make image" создает образ приложения. +--- +- Вам нужно создать `volume` под вашу базу данных командой "docker create volume `tgbot_data`" +--- +- При первом запуске контейнера вам нужно указать путь к вашей базе данных (`db/path`) и путь к файлу конфигурации, аргументами: + docker run -v `db/path`:/data -v `env-path`:/etc/tgbot skillbot:latest +--- +- Последующие запуски этого контейнера (а так-же последующих с таким же адресом базы) `docker start name`, где `name` это имя контейнера либо его ID, + сохранят базу данных предыдущих сеансов работы контейнера. + +### Примеры: +``` +docker create volume tgbot_data +``` +``` +make image +``` +``` +docker run -v /home/user/tgbot_data/:/data -v /home/user/go-projects/telegram_bot_skb/etc/tgbot:/etc/tgbot skillbot:latest +``` +``` +docker stop name +``` +``` +docker start name +``` + +### Ссылка на образ: + +- [Skillbot v1.0](https://hub.docker.com/repository/docker/azatf/skillbot) \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..ede9269 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,90 @@ +package main + +import ( + "flag" + tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "log" + "skbot/internal/callbackmsg" + "skbot/internal/chatmembers" + "skbot/internal/config" + "skbot/internal/functions" + "skbot/internal/textmsg" + "skbot/pkg/client/telegram" + "skbot/pkg/logging" +) + +var cfgPath string + +func init() { + flag.StringVar(&cfgPath, "config", "./etc/tgbot/.env", "config file path") +} + +func main() { + + log.Print("config initializing") + cfg := config.GetConfig(cfgPath) + + log.Print("logger initializing") + logger := logging.GetLogger(cfg.AppConfig.LogLevel) + + db, err := functions.NewFuncList(cfg, logger) + if err != nil { + logger.Fatal(err) + } + + err = db.NewData() + if err != nil { + logger.Error(err) + } + + updChan, bot, err := telegram.StartBotByChan(cfg, logger) + if err != nil { + logger.Fatal(err) + } + + moderGroup, err := db.GetModeratorsGroup() + if err != nil { + logger.Error(err) + } + + if len(moderGroup) == 0 { + + groupInfo, _ := bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, "test")) + _, _, err = db.AddModeratorsGroup(cfg.ModersGroupID.ModeratorsGroup, groupInfo.Chat.Title) + if err != nil { + logger.Info(err) + } + _, _ = bot.Send(tgb.NewDeleteMessage(groupInfo.Chat.ID, groupInfo.MessageID)) + + } + + defer bot.StopReceivingUpdates() + + for { + + update := <-updChan + + if update.Message != nil { + + if update.Message.Text != "" { + // text messages operations + textmsg.WithTextQueryDo(update, bot, logger, cfg) + + } else if update.Message.NewChatMembers != nil { + + chatmembers.WithChatMembersDo(update, bot, logger, cfg) + } + + } else if update.CallbackQuery != nil { + + callbackmsg.WithCallBackDo(update, bot, logger, cfg) + // TODO inline help + + } else if update.InlineQuery != nil { + + query := update.InlineQuery.Query + logger.Printf("response from Inline query: %s", query) + + } + } +} diff --git a/etc/tgbot/.env b/etc/tgbot/.env new file mode 100644 index 0000000..176cf65 --- /dev/null +++ b/etc/tgbot/.env @@ -0,0 +1,13 @@ +MULTIPLICITY=500 + +TG-BOT-TOKEN=(здесь ваш токен от бота, полученный от BotFather) +TG-BOT-LOG-LEVEL=(уровень логирования. По умолчанию trace.) + +MODERATORS-GROUP=(здесь ID группы администраторов (админки).) + +DB-FILE-PATH=./data + +MSG-OF-BAD-WORDS-TO-USER-CHAT="Уважаемые коллеги, просим вас воздержаться от нецензурных выражений в %s. Сообщение удалено, надеемся на ваше понимание." +MSG-TO-CHAT-IF-NEW-USER="🎉 Поздравляю, %s! Как же удачно вы попали в нужное время и в нужное место! Вы %d участник комьюнити. Вас ждут плюшки и печеньки!🎉" +MSG-MODERATOR-MEMBER="В этой памятке можно составить текст перед запуском бота, как напоминание об основных правилах, либо что-то еще на усмотрение администратора." +MSG-TRIM-SYMBOL="([]{}*).,!?;:" \ No newline at end of file diff --git a/folders.tar.gz b/folders.tar.gz new file mode 100644 index 0000000..1bf6740 Binary files /dev/null and b/folders.tar.gz differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7d922e3 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module skbot + +go 1.18 + +require ( + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 + github.com/ilyakaznacheev/cleanenv v1.3.0 + github.com/mattn/go-sqlite3 v1.14.14 + github.com/sirupsen/logrus v1.8.1 +) + +require ( + github.com/BurntSushi/toml v1.1.0 // indirect + github.com/joho/godotenv v1.4.0 // indirect + golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..220a7ff --- /dev/null +++ b/go.sum @@ -0,0 +1,26 @@ +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +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/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/ilyakaznacheev/cleanenv v1.3.0 h1:RapuLclPPUbmdd5Bi5UXScwMEZA6+ZNLU5OW9itPjj0= +github.com/ilyakaznacheev/cleanenv v1.3.0/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= diff --git a/internal/callbackmsg/callbackmsg.go b/internal/callbackmsg/callbackmsg.go new file mode 100644 index 0000000..73f217d --- /dev/null +++ b/internal/callbackmsg/callbackmsg.go @@ -0,0 +1,320 @@ +package callbackmsg + +import ( + "fmt" + tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "skbot/internal/chatmembers" + "skbot/internal/config" + "skbot/internal/data" + "skbot/internal/functions" + "skbot/internal/menu" + "skbot/internal/textmsg" + "skbot/pkg/logging" + "strconv" + "strings" + "time" +) + +func WithCallBackDo(update tgb.Update, bot *tgb.BotAPI, logger *logging.Logger, cfg *config.Config) { + + db, _ := functions.NewFuncList(cfg, logger) + callBackDoData := update.CallbackQuery.Data + + callInfo := strings.Split(callBackDoData, " ") + + var localUserId []string + + if callInfo[0] == "congratulation_again" && len(callInfo) > 1 { + + luckyMan, err := strconv.Atoi(callInfo[1]) + if err != nil { + logger.Error(err) + } + + jubileeUsers, err := db.GetAllJubileeUsers() + if err != nil { + logger.Error(err) + } + + user := jubileeUsers[luckyMan-1] + + err = db.MarkUser(user.ID) + if err != nil { + logger.Error(err) + } + + _, _ = bot.Send(tgb.NewMessage(user.GroupID, fmt.Sprintf(cfg.MsgText.MsgToChatIfNewUser, user.UserName, user.Serial))) + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + + msg2 := tgb.NewEditMessageText(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, update.CallbackQuery.Message.Text) + _, _ = bot.Send(msg2) + + } + + switch callBackDoData { + + // command list + case "com_list": + + msg := tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, menu.ComMenu) + msg.ParseMode = "markdown" + delMsg, _ := bot.Send(msg) + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + + go func() { + time.Sleep(60 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(update.CallbackQuery.Message.Chat.ID, delMsg.MessageID)) + }() + + // jubilee users + case "jubilee_list": + + users, err := db.GetJubileeUsers() + chatId := update.CallbackQuery.Message.Chat.ID + if err != nil { + logger.Info(err) + } + + moderGroupList, err := db.GetModeratorsGroup() + if err != nil { + logger.Error(err) + } + + for _, group := range moderGroupList { + + if group.ModerGroupID == chatId { + + for _, user := range users { + + var congrated string + if user.Marked == 1 { + congrated = "Уже поздравлен 👑👑👑" + } else { + congrated = "Не поздравлен 🎉" + } + + if user.Marked != 1 { + + text := fmt.Sprintf("№: `%d`, %s \nГруппа: *%s*\nИмя: *%s* Ник: *@%s*\nНомер: *%d* "+ + "Время: *%s* ", user.ID, congrated, user.GroupName, user.UserName, user.UserNick, + user.Serial, user.Time.UTC().Format(config.StructDateTimeFormat)) + + localUserId = append(localUserId, strconv.Itoa(user.ID)) + msg := tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, "Список юбилейный:\n"+text) + msg.ParseMode = "markdown" + msg.ReplyMarkup = tgb.NewInlineKeyboardMarkup( + tgb.NewInlineKeyboardRow( + tgb.NewInlineKeyboardButtonData("Поздравить", "congratulation_again"+" "+strconv.Itoa(user.ID)), + tgb.NewInlineKeyboardButtonData("Отклонить", "remove_button"), + )) + _, _ = bot.Send(msg) + } else { + + text := fmt.Sprintf("№: `%d`, %s \nГруппа: *%s*\nИмя: *%s* Ник: *@%s*\nНомер: *%d* "+ + "Время: *%s* ", user.ID, congrated, user.GroupName, user.UserName, user.UserNick, + user.Serial, user.Time.UTC().Format(config.StructDateTimeFormat)) + + localUserId = append(localUserId, strconv.Itoa(user.ID)) + msg := tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, "Список юбилейный:\n"+text) + msg.ParseMode = "markdown" + + _, _ = bot.Send(msg) + } + + } + } + } + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + + // add new moderators group + + case "all_jubilee_list": + + var list string + + users, err := db.GetAllJubileeUsers() + chatId := update.CallbackQuery.Message.Chat.ID + if err != nil { + logger.Info(err) + } + + moderGroupList, err := db.GetModeratorsGroup() + if err != nil { + logger.Error(err) + } + + for _, group := range moderGroupList { + + if group.ModerGroupID == chatId { + + for _, user := range users { + + var congrated string + if user.Marked == 1 { + congrated = "Уже поздравлен 👑👑👑" + } else { + congrated = "Не поздравлен 🎉" + } + + text := fmt.Sprintf("№: %d, %s Группа: %s, Имя: %s, Ник: @%s, Номер: %d, "+ + "Время: %s ", user.ID, congrated, user.GroupName, user.UserName, user.UserNick, + user.Serial, user.Time.Format(config.StructDateTimeFormat)) + + list = list + text + "\n\n" + + } + } + } + + _, _ = bot.Send(tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, "Список новых пользователей:\n"+list)) + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + + // add new moderators group + + case "add_new_mod": + + newGroupName := textmsg.MesInfo.Message.Chat.Title + newGroupId := textmsg.MesInfo.Message.Chat.ID + + if newGroupId != 0 { + + if update.CallbackQuery.Message.Chat.ID == cfg.ModersGroupID.ModeratorsGroup { + + logger.Info(newGroupName, newGroupId) + + text := fmt.Sprintf("Внимание! Вы подтверждаетете добавление группы: \n %s \nв список администраторов.", newGroupName) + msg := tgb.NewEditMessageText(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, text) + msgConf := tgb.NewEditMessageReplyMarkup(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, + tgb.NewInlineKeyboardMarkup(tgb.NewInlineKeyboardRow(menu.Button5, menu.Button11))) + + _, _ = bot.Send(msg) + _, _ = bot.Send(msgConf) + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + } + + } else { + + _, _ = bot.Send(tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, "Время вышло, повторите запрос.")) + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + } + + case "add_new_mod_true": + + newGroupName := textmsg.MesInfo.Message.Chat.Title + newGroupId := textmsg.MesInfo.Message.Chat.ID + + if newGroupId != 0 { + + if update.CallbackQuery.Message.Chat.ID == cfg.ModersGroupID.ModeratorsGroup { + + text := fmt.Sprintf("Внимание! Вы подтверждаетете добавление группы: \n %s \nв список администраторов.", newGroupName) + msg := tgb.NewEditMessageText(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, text) + _, _ = bot.Send(msg) + + b, _, err := db.AddModeratorsGroup(newGroupId, newGroupName) + + if b && err != nil { + + _, _ = bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, fmt.Sprintf("Группа %s уже есть.", newGroupName))) + } else if b && err == nil { + + _, _ = bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, fmt.Sprintf("Группа %s успешно добавлена.", newGroupName))) + } else { + logger.Error(err) + } + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + + textmsg.MesInfo.Message.Chat.ID = 0 + } + + } else { + + _, _ = bot.Send(tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, "Время вышло, повторите запрос.")) + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + } + + case "congratulation_new_user": + + msg := tgb.NewEditMessageText(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, update.CallbackQuery.Message.Text) + _, _ = bot.Send(msg) + + var newUser data.JubileeUser + users, err := db.GetJubileeUsers() + if err != nil { + logger.Error(err) + } + + for _, user := range users { + + if int64(user.UserID) == chatmembers.NewUserID { + newUser = user + } + } + + if newUser.UserID != 0 { + + err = db.MarkUser(newUser.ID) + if err != nil { + logger.Error(err) + } + logger.Infof("newUser ID %d", newUser.ID) + + text := fmt.Sprintf(cfg.MsgText.MsgToChatIfNewUser, newUser.UserName, newUser.Serial) + msg := tgb.NewMessage(newUser.GroupID, text) + + _, _ = bot.Send(msg) + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + + } else { + + msg := tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, "ID пользователя == 0! Видимо прошло слишком много времени."+ + "Списки пользователей можно запросить из `меню`.") + msg.ParseMode = "markdown" + + _, _ = bot.Send(msg) + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "❌")) + + } + + case "moderator_group_list": + + var list = "Список групп модераторов и пользователей: " + + groups, err := db.GetModeratorsGroup() + if err != nil { + logger.Error(err) + } + + for _, group := range groups { + + text := fmt.Sprintf("\n№: %d\nМодераторы: %s\nПользователи: %s", group.ID, + group.ModerGroupTitle, group.UserGroupTitle) + + list = list + text + "\n" + + } + + _, _ = bot.Send(tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, list)) + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "✅")) + + case "moderator_member": + + _, _ = bot.Send(tgb.NewMessage(update.CallbackQuery.Message.Chat.ID, cfg.MsgText.MsgModeratorMember)) + + _, _ = bot.Request(tgb.NewCallback(update.CallbackQuery.ID, "📩")) + + case "remove_button": + + _, _ = bot.Send(tgb.NewEditMessageText(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Message.MessageID, update.CallbackQuery.Message.Text)) + + } + +} diff --git a/internal/chatmembers/chatmembers.go b/internal/chatmembers/chatmembers.go new file mode 100644 index 0000000..e81597b --- /dev/null +++ b/internal/chatmembers/chatmembers.go @@ -0,0 +1,129 @@ +package chatmembers + +import ( + "fmt" + tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "skbot/internal/config" + "skbot/internal/data" + "skbot/internal/functions" + "skbot/pkg/logging" + "strconv" + "time" +) + +var NewUserID int64 + +func WithChatMembersDo(update tgb.Update, bot *tgb.BotAPI, logger *logging.Logger, cfg *config.Config) { + + db, _ := functions.NewFuncList(cfg, logger) + + newUser := update.Message.NewChatMembers[0] + NewUserID = newUser.ID + chatId := update.Message.Chat.ID + groupName := update.Message.Chat.Title + userCount := 0 + + logger.Infof("from members NewUserID %d", NewUserID) + + if !newUser.IsBot { + + moderGroupList, err := db.GetModeratorsGroup() + if err != nil { + logger.Error(err) + } + + for _, group := range moderGroupList { + + if update.Message.Chat.ID == group.UserGroupID { + + count, err := bot.GetChatMembersCount(tgb.ChatMemberCountConfig{ + ChatConfig: tgb.ChatConfig{ + ChatID: chatId, + SuperGroupUsername: groupName, + }, + }) + + msg := tgb.NewMessage(chatId, fmt.Sprintf("Рады вас приветствовать "+ + "%s! Давайте знакомиться, расскажите нам о себе пожалуйста.\n"+ + "Как вас зовут? \nИз какого вы города? \nЧто вас привело к нам?", newUser.FirstName)) + + ans, _ := bot.Send(msg) + + go func() { + + time.Sleep(60 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(chatId, ans.MessageID)) + }() + // TODO fix count 3 + if count%cfg.Multiplicity == 0 || count%cfg.Multiplicity == 1 || count%cfg.Multiplicity == 2 || count%4 == 0 { + + err = db.AddNewJubileeUser(&newUser, count, update) + if err != nil { + logger.Error(err) + } + } + + var newCheckUser data.JubileeUser + newUsers, err := db.GetAllJubileeUsers() + if err != nil { + logger.Error(err) + } + + var congrated string + + for _, user := range newUsers { + if int64(user.UserID) == NewUserID { + newCheckUser = user + userCount++ + } + } + if newCheckUser.Marked == 1 { + congrated = "Уже поздравлен 👑👑👑" + } else { + congrated = "Не поздравлен 🎉" + } + + if userCount > 1 { + msg := tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, + fmt.Sprintf("*Внимание!* У нас новый пользователь! %s, Но *найдено совпавдение* с таким ID `%d`, "+ + "рекомендую проверить весь список новых пользователей перед поздравлением.\nВызовите `меню`", congrated, newCheckUser.UserID)) + + msg.ParseMode = "markdown" + _, _ = bot.Send(msg) + } + //TODO FIX count 3 + if count%cfg.Multiplicity == 0 || count%cfg.Multiplicity == 1 || count%cfg.Multiplicity == 2 || count%4 == 0 { + + for _, group := range moderGroupList { + + if group.ModerGroupID == cfg.ModersGroupID.ModeratorsGroup { + + text := fmt.Sprintf("🎉 В группу: %s вступил юбилейный пользователь!\nИмя: %s "+ + "\nНик: @%s, \nНомер вступления: %d. \nВремя вступления %s", + groupName, newUser.FirstName, newUser.UserName, count, + time.Now().Format(config.StructDateTimeFormat)) + msg := tgb.NewMessage(group.ModerGroupID, text) + msg.ReplyMarkup = tgb.NewInlineKeyboardMarkup( + tgb.NewInlineKeyboardRow( + tgb.NewInlineKeyboardButtonData("Поздравить", "congratulation_again"+" "+strconv.Itoa(newCheckUser.ID)), + tgb.NewInlineKeyboardButtonData("Отклонить", "remove_button"), + )) + + logger.Infof("user ID %d from chatMembers", newCheckUser.ID) + + _, _ = bot.Send(msg) + + } else if update.Message.Chat.ID == group.UserGroupID { + text := fmt.Sprintf("🎉 В группу: %s вступил юбилейный пользователь!\nИмя: %s "+ + "\nНик: @%s, \nНомер вступления: %d. \nВремя вступления %s", + groupName, newUser.FirstName, newUser.UserName, count, + time.Now().Format(config.StructDateTimeFormat)) + + _, _ = bot.Send(tgb.NewMessage(group.ModerGroupID, text)) + } + } + } + } + } + } +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100755 index 0000000..010aa5d --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,50 @@ +package config + +import ( + "github.com/ilyakaznacheev/cleanenv" + "log" + "sync" +) + +type Config struct { + Telegram struct { + Token string `yaml:"tg_token" env:"TG-BOT-TOKEN" env-required:"true"` + Sert string `yaml:"tg_sert" env:"TG-BOT-SERT"` + } `yaml:"telegram" env:"TELEGRAM"` + AppConfig AppConfig `yaml:"app" env:"APP"` + ModersGroupID ModersGroupID `yaml:"moderators" enc:"MODERATORS"` + MsgText MsgText `yaml:"msg_text" env:"MSG-TEXT"` + DBFilePath string `yaml:"db_file_path" env:"DB-FILE-PATH" env-required:"true" env-default:"./internal/sqlitedb/"` + Multiplicity int `yaml:"multiplicity" env:"MULTIPLICITY" env-default:"500"` +} + +type AppConfig struct { + LogLevel string `yaml:"log_level" env:"TG-BOT-LOG-LEVEL" env-default:"trace" env-required:"true"` +} + +type ModersGroupID struct { + ModeratorsGroup int64 `yaml:"moderators_group" env:"MODERATORS-GROUP" env-required:"true"` +} + +type MsgText struct { + MsgOfBadWordToUserChat string `yaml:"msg_of_bad_word_to_user_chat" env:"MSG-OF-BAD-WORDS-TO-USER-CHAT" env-required:"true"` + MsgToChatIfNewUser string `yaml:"msg_to_chat_if_new_user" env:"MSG-TO-CHAT-IF-NEW-USER" env-required:"true"` + MsgModeratorMember string `yaml:"msg_moderator_member" env:"MSG-MODERATOR-MEMBER"` + MsgTrimSymbol string `yaml:"msg_trim_symbol" env:"MSG-TRIM-SYMBOL"` +} + +var instance *Config +var once sync.Once + +func GetConfig(path string) *Config { + once.Do(func() { + log.Printf("read application config from path %s", path) + + instance = &Config{} + + if err := cleanenv.ReadConfig(path, instance); err != nil { + log.Fatal(err) + } + }) + return instance +} diff --git a/internal/config/constants.go b/internal/config/constants.go new file mode 100755 index 0000000..05e78e2 --- /dev/null +++ b/internal/config/constants.go @@ -0,0 +1,5 @@ +package config + +const StructDateTimeFormat = "2006-01-02 15:04" +const StructDateFormat = "2006-01-02" +const StructTimeFormat = "15:04" diff --git a/internal/data/data.go b/internal/data/data.go new file mode 100755 index 0000000..685f357 --- /dev/null +++ b/internal/data/data.go @@ -0,0 +1,29 @@ +package data + +import ( + "time" +) + +type BadWords struct { + ID int + Word string +} +type ModeratorsGroup struct { + ID int + ModerGroupID int64 + ModerGroupTitle string + UserGroupID int64 + UserGroupTitle string +} + +type JubileeUser struct { + ID int + Serial int + UserID int + UserName string + UserNick string + Time time.Time + GroupName string + GroupID int64 + Marked int +} diff --git a/internal/functions/functions.go b/internal/functions/functions.go new file mode 100755 index 0000000..1798d9d --- /dev/null +++ b/internal/functions/functions.go @@ -0,0 +1,351 @@ +package functions + +import ( + "database/sql" + "errors" + "fmt" + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + _ "github.com/mattn/go-sqlite3" + "log" + "os" + "path" + "skbot/internal/config" + "skbot/internal/data" + "skbot/pkg/logging" + "strings" + "time" +) + +type list struct { + cfg *config.Config + logger *logging.Logger + db *sql.DB +} + +func NewFuncList(cfg *config.Config, logger *logging.Logger) (FuncList, error) { + + err := os.MkdirAll(cfg.DBFilePath, 0777) + if err != nil { + logger.Error(err) + } + + liteDb, err := sql.Open("sqlite3", path.Join(cfg.DBFilePath, "skb_bot_db.db")) + if err != nil { + logger.Fatalf("error open database %v", err) + } + + return &list{ + cfg: cfg, + logger: logger, + db: liteDb, + }, nil + +} + +func (l *list) NewData() error { + + stat, err := l.db.Prepare("CREATE TABLE IF NOT EXISTS moderators " + + "(id INTEGER PRIMARY KEY, moder_group_id INTEGER NOT NULL, moder_group_title TEXT DEFAULT no_title, " + + "user_group_id INTEGER DEFAULT 0, user_group_title TEXT DEFAULT no_title)") + _, err = stat.Exec() + if err != nil { + return err + } + + stat, err = l.db.Prepare("CREATE TABLE IF NOT EXISTS bad_words " + + "(id INTEGER PRIMARY KEY, word VARCHAR (30) NOT NULL)") + _, err = stat.Exec() + if err != nil { + return err + } + + stat, err = l.db.Prepare("CREATE TABLE IF NOT EXISTS newJubileeUsers " + + "(id INTEGER PRIMARY KEY, serial INTEGER NOT NULL, " + + "user_id INTEGER NOT NULL, user_name VARCHAR (30) NOT NULL, " + + "user_nick VARCHAR (50) DEFAULT ('нет ника'), time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + + "group_name VARCHAR (50) NOT NULL, group_id INTEGER NOT NULL, mark INTEGER DEFAULT 0)") + _, err = stat.Exec() + if err != nil { + return err + } + return nil +} + +type FuncList interface { + NewData() error + AddUserGroupList(moderGroup, userGroup int64, moderTitle, userTitle string) (bool, error) + CheckBadWords(badList []string) (clearList []string, haveBadWords bool, err error) + AddBadWord(word string) (bool, error) + AddModeratorsGroup(group int64, title string) (haveGroup bool, modGroups []data.ModeratorsGroup, err error) + GetModeratorsGroup() (groups []data.ModeratorsGroup, err error) + AddNewJubileeUser(newUser *tgbotapi.User, serial int, update tgbotapi.Update) error + GetJubileeUsers() (jubUsers []data.JubileeUser, err error) + GetAllJubileeUsers() (jubUsers []data.JubileeUser, err error) + MarkUser(userId int) error +} + +func TrimSymbolsFromSlice(s []string, cfg *config.Config) (words []string, err error) { + + var messageUpd []string + + for _, k := range s { + + k = strings.Trim(k, cfg.MsgText.MsgTrimSymbol) + messageUpd = append(messageUpd, k) + } + + words = messageUpd + + return words, nil +} + +func (l *list) CheckBadWords(badList []string) (clearList []string, haveBadWords bool, err error) { + + var badWords []data.BadWords + var badWord data.BadWords + haveBadWords = false + + rows, err := l.db.Query("SELECT * FROM bad_words") + if err != nil { + return nil, false, err + } + + for rows.Next() { + err = rows.Scan(&badWord.ID, &badWord.Word) + badWords = append(badWords, badWord) + } + + for _, word := range badList { + + for _, bad := range badWords { + + if word == bad.Word { + + l.logger.Infof("найдено совпадение матерного слова в базе: %s", word) + haveBadWords = true + } + } + } + + return clearList, haveBadWords, err + +} + +func (l *list) AddBadWord(word string) (bool, error) { + + var badWord data.BadWords + var haveWord = false + + rows, err := l.db.Query("SELECT * FROM bad_words") + if err != nil { + } + + for rows.Next() { + err = rows.Scan(&badWord.ID, &badWord.Word) + if badWord.Word == word { + haveWord = true + return true, nil + } + } + + if !haveWord { + + _, err = l.db.Exec(fmt.Sprintf("INSERT INTO bad_words (word) VALUES ('%s')", word)) + if err != nil { + return false, errors.New("ошибка добавления матерного слова в базу") + + } else { + return true, errors.New("новое матерное слово занесено в базу") + } + } + + return true, errors.New("added") + +} + +func (l *list) AddModeratorsGroup(group int64, title string) (haveGroup bool, modGroups []data.ModeratorsGroup, err error) { + + var modGroup data.ModeratorsGroup + haveGroup = false + + rows, err := l.db.Query("SELECT * FROM moderators") + if err != nil { + log.Println(err) + } + + for rows.Next() { + + err = rows.Scan(&modGroup.ID, &modGroup.ModerGroupID, &modGroup.ModerGroupTitle, &modGroup.UserGroupID, &modGroup.UserGroupTitle) + modGroups = append(modGroups, modGroup) + } + + for _, grp := range modGroups { + + if grp.ModerGroupID == group { + + haveGroup = true + return haveGroup, modGroups, errors.New("have group") + } + } + + if !haveGroup && group != 0 { + + _, err = l.db.Exec(fmt.Sprintf("INSERT INTO moderators (moder_group_id, moder_group_title, user_group_id , "+ + "user_group_title) VALUES ('%d', '%s', '0', 'Без пользователей.')", group, title)) + if err != nil { + log.Println(err) + } + + haveGroup = true + } + + return haveGroup, modGroups, nil +} + +func (l *list) GetModeratorsGroup() (groups []data.ModeratorsGroup, err error) { + + rows, err := l.db.Query("SELECT * FROM moderators") + if err != nil { + log.Println(err) + } + + var group data.ModeratorsGroup + + for rows.Next() { + + err = rows.Scan(&group.ID, &group.ModerGroupID, &group.ModerGroupTitle, &group.UserGroupID, &group.UserGroupTitle) + groups = append(groups, group) + } + + return groups, nil + +} + +func (l *list) AddNewJubileeUser(newUser *tgbotapi.User, serial int, update tgbotapi.Update) error { + + t := time.Now().Local().Format(config.StructDateTimeFormat) + + _, err := l.db.Exec(fmt.Sprintf("INSERT INTO newJubileeUsers (serial, user_id, user_name, user_nick, time, "+ + "group_name, group_id) VALUES ('%d', '%d', '%s', '%s', '%s', '%s', '%d')", serial, newUser.ID, newUser.FirstName, + newUser.UserName, t, update.Message.Chat.Title, update.Message.Chat.ID)) + + if err != nil { + l.logger.Error(err) + } + + return nil +} + +func (l *list) GetJubileeUsers() (jubUsers []data.JubileeUser, err error) { + + var user data.JubileeUser + var users []data.JubileeUser + + rows, err := l.db.Query("SELECT * FROM newJubileeUsers ORDER BY id DESC LIMIT 3 ") + if err != nil { + return nil, err + } + + for rows.Next() { + + err = rows.Scan(&user.ID, &user.Serial, &user.UserID, &user.UserName, &user.UserNick, + &user.Time, &user.GroupName, &user.GroupID, &user.Marked) + users = append(users, user) + } + + //TODO FIX serial 3 + for _, v := range users { + + if v.Serial%l.cfg.Multiplicity == 0 || v.Serial%l.cfg.Multiplicity == 1 || v.Serial%l.cfg.Multiplicity == 2 || v.Serial%4 == 0 { + jubUsers = append(jubUsers, v) + } + } + + return jubUsers, nil + +} + +func (l *list) GetAllJubileeUsers() (jubUsers []data.JubileeUser, err error) { + + var user data.JubileeUser + var users []data.JubileeUser + + rows, err := l.db.Query("SELECT * FROM newJubileeUsers") + if err != nil { + return nil, err + } + + for rows.Next() { + + err = rows.Scan(&user.ID, &user.Serial, &user.UserID, &user.UserName, &user.UserNick, + &user.Time, &user.GroupName, &user.GroupID, &user.Marked) + users = append(users, user) + } + + return users, nil + +} + +func (l *list) AddUserGroupList(moderGroup, userGroup int64, moderTitle, userTitle string) (bool, error) { + + var moderatorGroup data.ModeratorsGroup + var moderatorGroups []data.ModeratorsGroup + var haveGroup = false + + rows, err := l.db.Query("SELECT * FROM moderators") + if err != nil { + return false, err + } + + for rows.Next() { + + err := rows.Scan(&moderatorGroup.ID, &moderatorGroup.ModerGroupID, &moderatorGroup.ModerGroupTitle, &moderatorGroup.UserGroupID, &moderatorGroup.UserGroupTitle) + if err != nil { + return false, err + } + + moderatorGroups = append(moderatorGroups, moderatorGroup) + } + + for _, group := range moderatorGroups { + + if group.ModerGroupID == moderGroup && group.UserGroupID == userGroup { + + haveGroup = true + return true, nil + + } else if group.ModerGroupID == moderGroup && group.UserGroupID == 0 { + + _, err = l.db.Exec(fmt.Sprintf("UPDATE moderators SET (user_group_id, user_group_title) = ('%d', '%s') "+ + "WHERE moder_group_id = ('%d')", userGroup, userTitle, moderGroup)) + if err != nil { + return false, err + } + + haveGroup = true + return false, nil + } + } + + if !haveGroup { + + _, err = l.db.Exec(fmt.Sprintf("INSERT INTO moderators (moder_group_id, moder_group_title, user_group_id, user_group_title) "+ + "VALUES ('%d', '%s', '%d', '%s')", moderGroup, moderTitle, userGroup, userTitle)) + if err != nil { + return false, err + } + } + + return false, nil +} + +func (l *list) MarkUser(userId int) error { + + _, err := l.db.Exec(fmt.Sprintf("UPDATE newJubileeUsers SET (mark) = (1) WHERE id = ('%d')", userId)) + if err != nil { + l.logger.Error(err) + } + + return nil + +} diff --git a/internal/menu/menu.go b/internal/menu/menu.go new file mode 100755 index 0000000..29b28f4 --- /dev/null +++ b/internal/menu/menu.go @@ -0,0 +1,35 @@ +package menu + +import ( + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +const ComMenu = " Список доступных вам команд: 🛠 \n \n" + + "✅ `addmoderatorgroup` + номер _(добавление группы модераторов)._\n\n" + + "✅ `add-moder-group` _(отправляет запрос из группы где есть бот, на добавление этой группы в список групп модераторов)._\n\n" + + "✅ `add-moder-user-link` _(связывает группу модераторов и пользователей. Введите команду, " + + "затем через пробел номер группы модераторов, затем через пробел номер группы пользователей. " + + "Внимательно проверьте правильность номеров групп)._\n\n" + + "✅ `chatinfo` _(отправляет информацию в админку о имени и ID группы, откуда отправляется команда.\n" + + "Номер можно скопировать нажатием, для удобства добавления связи групп модераторов и пользователей.\n" + + "Сообщение будет удалено из группы отправителя, если бот админ)_\n\n" + + "✅ *Мат + слово* _(Слово будет добавлено в базу)._" + +var NumericKeyboard = tgbotapi.NewInlineKeyboardMarkup( + tgbotapi.NewInlineKeyboardRow(button1), + tgbotapi.NewInlineKeyboardRow(button2), + tgbotapi.NewInlineKeyboardRow(button8), + tgbotapi.NewInlineKeyboardRow(button3), + tgbotapi.NewInlineKeyboardRow(button9), +) + +var button1 = tgbotapi.NewInlineKeyboardButtonData("Список команд", "com_list") +var button2 = tgbotapi.NewInlineKeyboardButtonData("Список юбилейный (крайние трое)", "jubilee_list") +var button8 = tgbotapi.NewInlineKeyboardButtonData("Весь список юбилейных пользователей ", "all_jubilee_list") +var button3 = tgbotapi.NewInlineKeyboardButtonData("Список групп модераторов и пользователей.", "moderator_group_list") +var button9 = tgbotapi.NewInlineKeyboardButtonData("Памятка модераторам.", "moderator_member") + +var Button4 = tgbotapi.NewInlineKeyboardButtonData("Добавить группу", "add_new_mod") +var Button5 = tgbotapi.NewInlineKeyboardButtonData("Да, я уверен!", "add_new_mod_true") + +var Button11 = tgbotapi.NewInlineKeyboardButtonData("Отклонить", "remove_button") diff --git a/internal/textmsg/textmsg.go b/internal/textmsg/textmsg.go new file mode 100644 index 0000000..4f8c403 --- /dev/null +++ b/internal/textmsg/textmsg.go @@ -0,0 +1,289 @@ +package textmsg + +import ( + "fmt" + tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "log" + "skbot/internal/config" + "skbot/internal/functions" + "skbot/internal/menu" + "skbot/pkg/logging" + "strconv" + "strings" + "time" +) + +var MesInfo tgb.Update + +func WithTextQueryDo(update tgb.Update, bot *tgb.BotAPI, logger *logging.Logger, cfg *config.Config) { + + db, _ := functions.NewFuncList(cfg, logger) + + // trim symbols + if len(update.Message.Text) > 0 { + + command, err := functions.TrimSymbolsFromSlice(strings.Split(update.Message.Text, " "), cfg) + if err != nil { + logger.Info("error trim symbols from message") + } + + // check bad words in chat messages + + _, b, err := db.CheckBadWords(command) + if err != nil { + logger.Error("bad words error: ", err) + } + + // message to chat where found bad word, copy to moderators groups + + if b && strings.ToLower(command[0]) != "мат" { + + msgID := update.Message.MessageID + badText := update.Message.Text + + go func() { + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, msgID)) + }() + + badGuyName := update.Message.From.FirstName + badGuyNick := update.Message.From.UserName + badGuyID := update.Message.From.ID + groupName := update.Message.Chat.Title + + moderatorsGroups, err := db.GetModeratorsGroup() + if err != nil { + logger.Error(err) + } + + for _, v := range moderatorsGroups { + + if v.UserGroupID == update.Message.Chat.ID { + + modMess := tgb.NewMessage(v.ModerGroupID, fmt.Sprintf( + "Найдены нецензурные выражения:\nГруппа: %s\nИмя пользователя: %s\nНик пользователя: "+ + "@%s\nID пользователя: %d\nТекст сообщения: %s\nВремя: %s\nОригинал сообщения удален из чата.", + groupName, badGuyName, badGuyNick, badGuyID, badText, time.Now().Format(config.StructDateTimeFormat))) + + _, _ = bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, fmt.Sprintf( + "Найдены нецензурные выражения:\nГруппа: %s\nИмя пользователя: %s\nНик пользователя: "+ + "@%s\nID пользователя: %d\nТекст сообщения: %s\nВремя: %s\nОригинал сообщения удален из чата.", + groupName, badGuyName, badGuyNick, badGuyID, badText, time.Now().Format(config.StructDateTimeFormat)))) + _, _ = bot.Send(modMess) + } + + } + + cleanAnswer := tgb.NewMessage(update.Message.Chat.ID, fmt.Sprintf( + cfg.MsgText.MsgOfBadWordToUserChat, groupName)) + del, _ := bot.Send(cleanAnswer) + + go func() { + time.Sleep(30 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, del.MessageID)) + }() + } + + // add moderator group + if strings.Contains(strings.ToLower(command[0]), "addmoderatorgroup") { + + if update.Message.Chat.ID == cfg.ModersGroupID.ModeratorsGroup { + + newModGroup, err := strconv.ParseInt(command[1], 10, 64) + if err != nil { + logger.Error(err) + + } + + info, _ := bot.Send(tgb.NewMessage(newModGroup, "test")) + + b, _, err := db.AddModeratorsGroup(newModGroup, info.Chat.Title) + if err != nil { + logger.Error(err) + } + _, _ = bot.Send(tgb.NewDeleteMessage(newModGroup, info.MessageID)) + + if b && err != nil { + + _, _ = bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, fmt.Sprintf("Такая группа уже есть: %d", newModGroup))) + } + + if b && err == nil { + + _, _ = bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, fmt.Sprintf("Успешно добавлена: %d", newModGroup))) + + } + } + } + + // new users count + + if strings.Contains(strings.ToLower(command[0]), "chatinfo") { + + delMes := update.Message.MessageID + chatId := update.Message.Chat.ID + groupName := update.Message.Chat.Title + + go func() { + _, _ = bot.Send(tgb.NewDeleteMessage(chatId, delMes)) + }() + + msg := tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, fmt.Sprintf( + "ID группы: `%d`\nИмя группы: *%s*", chatId, groupName)) + msg.ParseMode = "markdown" + + _, err = bot.Send(msg) + if err != nil { + logger.Error(err) + } + } + + // add bad words to the base + + if strings.Contains(strings.ToLower(command[0]), "мат") && len(command) > 1 { + + moderators, err := db.GetModeratorsGroup() + if err != nil { + logger.Error(err) + } + + for _, moder := range moderators { + + if update.Message.Chat.ID == moder.ModerGroupID { + + go func() { + time.Sleep(5 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, update.Message.MessageID)) + + }() + + b, err := db.AddBadWord(command[1]) + if err != nil { + log.Println(err) + } + + if b == true && err == nil { + + del, _ := bot.Send(tgb.NewMessage(update.Message.Chat.ID, "Уже есть.")) + + go func() { + + time.Sleep(5 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, del.MessageID)) + + }() + + } else if b == true && err != nil { + + del, _ := bot.Send(tgb.NewMessage(update.Message.Chat.ID, "Добавлено.")) + + go func() { + + time.Sleep(5 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, del.MessageID)) + }() + } + } + } + } + + // menu moderators groups + + if strings.Contains(strings.ToLower(command[0]), "меню") { + + chatId := update.Message.Chat.ID + moderatorGroups, err := db.GetModeratorsGroup() + if err != nil { + return + } + + for _, group := range moderatorGroups { + + if group.ModerGroupID == chatId { + + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, update.Message.MessageID)) + + msg := tgb.NewMessage(update.Message.Chat.ID, "Меню закроется через 1 минуту") + + msg.ReplyMarkup = menu.NumericKeyboard + + delMes, err := bot.Send(msg) + if err != nil { + logger.Error(err) + } + + go func() { + time.Sleep(60 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, delMes.MessageID)) + }() + break + } + } + } + + // add moder group + if strings.Contains(strings.ToLower(command[0]), "add-moder-group") { + + MesInfo = update + + go func() { + time.Sleep(60 * time.Second) + MesInfo.Message.Chat.ID = 0 + }() + + chatId := update.Message.Chat.ID + chatName := update.Message.Chat.Title + userName := update.Message.From.FirstName + userNick := update.Message.From.UserName + + _, _ = bot.Send(tgb.NewDeleteMessage(update.Message.Chat.ID, update.Message.MessageID)) + + text := fmt.Sprintf("Новый запрос на добавление группы администраторов:\nНазвание группы: %s\n"+ + "Уникальный номер группы: %d\nИмя пользователя: %s\nНик пользователя: @%s\nВремя запроса: %s\n"+ + "Подтвердите в течении 60 секунд, или проигнорируйте сообщение.", + chatName, chatId, userName, userNick, time.Now().Format(config.StructDateTimeFormat)) + + msg := tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, text) + msg.ReplyMarkup = tgb.NewInlineKeyboardMarkup(tgb.NewInlineKeyboardRow(menu.Button4, menu.Button11)) + + msgDel, err := bot.Send(msg) + if err != nil { + logger.Error(err) + } + + go func() { + time.Sleep(60 * time.Second) + _, _ = bot.Send(tgb.NewDeleteMessage(msgDel.Chat.ID, msgDel.MessageID)) + }() + } + + // user groups link with moder group + if strings.Contains(strings.ToLower(command[0]), "add-moder-user-link") { + + if update.Message.Chat.ID == cfg.ModersGroupID.ModeratorsGroup { + + moderGroup, _ := strconv.ParseInt(command[1], 10, 64) + userGroup, _ := strconv.ParseInt(command[2], 10, 64) + + moderInfo, err1 := bot.Send(tgb.NewMessage(moderGroup, "test")) + if err != nil { + logger.Error(err1) + } + + userInfo, err2 := bot.Send(tgb.NewMessage(userGroup, "test")) + if err2 != nil { + logger.Error(err2) + } + + _, _ = bot.Send(tgb.NewDeleteMessage(moderInfo.Chat.ID, moderInfo.MessageID)) + _, _ = bot.Send(tgb.NewDeleteMessage(userInfo.Chat.ID, userInfo.MessageID)) + + b, err = db.AddUserGroupList(moderGroup, userGroup, moderInfo.Chat.Title, userInfo.Chat.Title) + if !b && err != nil { + logger.Error(err) + } + + if b && err == nil { + _, _ = bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, "Такая группа пользователей уже есть в базе.")) + } + if !b && err == nil { + _, _ = bot.Send(tgb.NewMessage(cfg.ModersGroupID.ModeratorsGroup, "Успешно добавлено.")) + } + } + } + } +} diff --git a/pkg/client/telegram/client.go b/pkg/client/telegram/client.go new file mode 100755 index 0000000..14f6e08 --- /dev/null +++ b/pkg/client/telegram/client.go @@ -0,0 +1,31 @@ +package telegram + +import ( + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "log" + "skbot/internal/config" + "skbot/pkg/logging" + "time" +) + +func StartBotByChan(cfg *config.Config, logger *logging.Logger) (tgbotapi.UpdatesChannel, *tgbotapi.BotAPI, error) { + + bot, err := tgbotapi.NewBotAPI(cfg.Telegram.Token) + if err != nil { + log.Fatal(err) + } + + bot.Debug = false + + logger.Infof("Bot %s started at: %v", bot.Self.UserName, time.Now().Format("2 January 2006 15:04")) + + u := tgbotapi.NewUpdate(0) + u.Timeout = 60 + u.Limit = 0 + u.Offset = 0 + + updates := bot.GetUpdatesChan(u) + + return updates, bot, nil + +} diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go new file mode 100755 index 0000000..c1a5057 --- /dev/null +++ b/pkg/logging/logging.go @@ -0,0 +1,47 @@ +package logging + +import ( + "fmt" + "github.com/sirupsen/logrus" + "log" + "os" + "path" + "runtime" +) + +type Logger struct { + *logrus.Entry +} + +func (s *Logger) ExtraFields(fields map[string]interface{}) *Logger { + return &Logger{s.WithFields(fields)} +} + +var instance Logger + +func GetLogger(level string) *Logger { + + logrusLevel, err := logrus.ParseLevel(level) + if err != nil { + log.Fatal(err) + } + + l := logrus.New() + l.SetReportCaller(true) + l.Formatter = &logrus.TextFormatter{ + CallerPrettyfier: func(f *runtime.Frame) (string, string) { + filename := path.Base(f.File) + return fmt.Sprintf("%s:%d", filename, f.Line), fmt.Sprintf("%s()", f.Function) + }, + DisableColors: false, + FullTimestamp: true, + } + + l.SetOutput(os.Stdout) + l.SetLevel(logrusLevel) + + instance = Logger{logrus.NewEntry(l)} + + return &instance + +}