diff --git a/cmd/app/main.go b/cmd/app/main.go index 59d173e..5ddebdb 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -3,19 +3,64 @@ package main import ( "fmt" "log" - "os" + "strings" + "strconv" + "errors" + "net/http" + "os" + "io" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + + "github.com/akulij/ticketbot/config" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) -func main() { - token := os.Getenv("BOTTOKEN") - fmt.Printf("Token value: '%v'\n", token) - bot, err := tgbotapi.NewBotAPI(token) +type User struct { + gorm.Model + ID int64 + State string + MsgCounter uint + RoleBitmask uint +} + +func (u User) IsAdmin() bool { + return u.RoleBitmask & 1 == 1 +} + +func (u User) IsEffectiveAdmin() bool { + return u.RoleBitmask & 0b10 == 0b10 +} + +type BotContent struct { + gorm.Model + Literal string + Content string +} + +type BotController struct { + cfg config.Config + bot *tgbotapi.BotAPI + db *gorm.DB + updates tgbotapi.UpdatesChannel +} + +func GetBotController() BotController { + cfg := config.GetConfig() + fmt.Printf("Token value: '%v'\n", cfg.BotToken) + fmt.Printf("Admin password: '%v'\n", cfg.AdminPass) + bot, err := tgbotapi.NewBotAPI(cfg.BotToken) if err != nil { log.Panic(err) } + db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}) + db.AutoMigrate(&BotContent{}) + + bot.Debug = true log.Printf("Authorized on account %s", bot.Self.UserName) @@ -25,14 +70,265 @@ func main() { updates := bot.GetUpdatesChan(u) - for update := range updates { + return BotController{cfg: cfg, bot: bot, db: db, updates: updates} +} + +func (bc BotController) GetBotContentVerbose(Literal string) (string, error) { + var c BotContent + bc.db.First(&c, "Literal", Literal) + if c == (BotContent{}) { + return "[Unitialized] Init in Admin panel! Literal: " + Literal, errors.New("No content") + } + return c.Content, nil +} +func (bc BotController) GetBotContent(Literal string) string { + content, _ := bc.GetBotContentVerbose(Literal) + return content +} +func (bc BotController) SetBotContent(Literal string, Content string) { + bc.db.Create(&BotContent{Literal: Literal, Content: Content}) +} + +func main() { + var bc = GetBotController() + + for update := range bc.updates { if update.Message != nil { + var UserID = update.Message.From.ID + + var user User + bc.db.First(&user, "id", UserID) + if user == (User{}) { + log.Printf("New user: [%d]", UserID) + user = User{ID: UserID , State: "start"} + bc.db.Create(&user) + } + + bc.db.Model(&user).Update("MsgCounter", user.MsgCounter + 1) + log.Printf("User[%d] messages: %d", user.ID, user.MsgCounter) + log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text) - msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text) - msg.ReplyToMessageID = update.Message.MessageID + possibleCommand := strings.Split(update.Message.Text, " ")[0] + args := strings.Split(update.Message.Text, " ")[1:] + log.Printf("Args: %s", args) - bot.Send(msg) + if possibleCommand == "/start" { + kbd := tgbotapi.NewInlineKeyboardMarkup( + tgbotapi.NewInlineKeyboardRow( + tgbotapi.NewInlineKeyboardButtonData(bc.GetBotContent("leave_ticket_button"), "leave_ticket_button"), + ), + ) + if user.IsAdmin() { + kbd = tgbotapi.NewInlineKeyboardMarkup( + tgbotapi.NewInlineKeyboardRow( + tgbotapi.NewInlineKeyboardButtonData(bc.GetBotContent("leave_ticket_button"), "leave_ticket_button"), + ), + tgbotapi.NewInlineKeyboardRow( + tgbotapi.NewInlineKeyboardButtonData("Panel", "panel"), + ), + ) + } + img, err := bc.GetBotContentVerbose("preview_image") + if err != nil { + msg := tgbotapi.NewMessage(update.Message.Chat.ID, bc.GetBotContent("start")) + // msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Hello, [user](tg://user?id=958170391)") + msg.ParseMode = "markdown" + msg.ReplyMarkup = kbd + bc.bot.Send(msg) + } else { + url, _ := bc.bot.GetFileDirectURL(img) + DownloadFile("./preview.jpg", url) + msg := tgbotapi.NewPhoto(update.Message.Chat.ID, tgbotapi.FilePath("./preview.jpg")) + msg.Caption = bc.GetBotContent("start") + msg.ReplyMarkup = kbd + bc.bot.Send(msg) + } + + } else if possibleCommand == "/id" && user.IsAdmin() { + log.Printf("THERe") + bc.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, strconv.FormatInt(update.Message.Chat.ID, 10))) + } else if possibleCommand == "/secret" && args[0] == bc.cfg.AdminPass { + bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask | 0b11) // set real admin ID (0b1) and effective admin toggle (0b10) + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "You are admin now!") + bc.bot.Send(msg) + + } else if possibleCommand == "/panel" && user.IsAdmin() { + if !user.IsEffectiveAdmin() { + bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask | 0b10) + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "You was in usermode, turned back to admin mode...") + bc.bot.Send(msg) + } + kbd := tgbotapi.NewInlineKeyboardMarkup( + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Стартовая картинка", "update:preview_image")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Приветственный текст", "update:start")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Кнопка для заявки", "update:leave_ticket_button")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("ID чата", "update:supportchatid")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("ID канала", "update:channelid")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Уведомление об отправке тикета", "update:sended_notify")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Просьба оставить тикет", "update:leaveticket_message")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Просьба подписаться на канал", "update:subscribe_message")), + ) + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Выберите пункт для изменения") + msg.ReplyMarkup = kbd + bc.bot.Send(msg) + + } else if possibleCommand == "/usermode" && user.IsEffectiveAdmin() { + bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask & (^uint(0b10))) + log.Printf("Set role bitmask (%b) for user: %d", user.RoleBitmask, user.ID) + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Simulating user experience!") + bc.bot.Send(msg) + } else if user.IsEffectiveAdmin() { + if user.State != "start" { + if strings.HasPrefix(user.State, "imgset:") { + Literal := strings.Split(user.State, ":")[1] + if update.Message.Text == "unset" { + var l BotContent + bc.db.First(&l, "Literal", Literal) + bc.db.Delete(l) + } + maxsize := 0 + fileid := "" + for _, p := range update.Message.Photo { + if p.FileSize > maxsize { + fileid = p.FileID + maxsize = p.FileSize + } + } + bc.SetBotContent(Literal, fileid) + } else if strings.HasPrefix(user.State, "stringset:") { + Literal := strings.Split(user.State, ":")[1] + bc.SetBotContent(Literal, update.Message.Text) + } + bc.db.Model(&user).Update("state", "start") + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Succesfully set new image!") + bc.bot.Send(msg) + } + } else { + if user.State == "leaveticket" { + f := update.Message.From + ticket := fmt.Sprintf("User: %s %s\nUsername: %s\nText:\n", + f.FirstName, f.LastName, + f.UserName) + // Offset := len(ticket) + // Length := len(update.Message.Text) + ticket += update.Message.Text + chatidstr, err := bc.GetBotContentVerbose("supportchatid") + if err != nil { + var admins []User + bc.db.Where("RoleBitmask & 1 = ?", 1).Find(&admins) + for _, admin := range admins { + msg := tgbotapi.NewMessage(admin.ID, "Support ChatID is not set!!!") + msg.Entities = []tgbotapi.MessageEntity{tgbotapi.MessageEntity{ + Type: "code", + Offset: 1, + Length: 2, + }} + bc.bot.Send(msg) + } + } + chatid, _ := strconv.ParseInt(chatidstr, 10, 64) + _, err = bc.bot.Send(tgbotapi.NewMessage(chatid, ticket)) + + if err != nil { + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Something went wrong, try again...") + bc.bot.Send(msg) + continue + } + + bc.db.Model(&user).Update("state", "start") + msg := tgbotapi.NewMessage(update.Message.Chat.ID, bc.GetBotContent("sended_notify")) + bc.bot.Send(msg) + } + } + } else if update.CallbackQuery != nil { + var user User + bc.db.First(&user, "id", update.CallbackQuery.From.ID) + if update.CallbackQuery.Data == "leave_ticket_button" { + chatidstr, err := bc.GetBotContentVerbose("channelid") + if err != nil { + var admins []User + bc.db.Where("RoleBitmask & 1 = ?", 1).Find(&admins) + for _, admin := range admins { + bc.bot.Send(tgbotapi.NewMessage(admin.ID, "ChannelID is not set!!!")) + } + } + chatid, _ := strconv.ParseInt(chatidstr, 10, 64) + + member, err := bc.bot.GetChatMember(tgbotapi.GetChatMemberConfig{ + ChatConfigWithUser: tgbotapi.ChatConfigWithUser{ + UserID: update.CallbackQuery.From.ID, + SuperGroupUsername: chatidstr, + ChatID: chatid, + }, + }) + if err != nil { + if strings.Contains(err.Error(), "chat not found") { + bc.bot.Send(tgbotapi.NewMessage(user.ID, "No channel ID is set!!!")) + } + } + log.Printf("M: %s, E: %s", member, err) + s := member.Status + if s == "member" || s == "creator" || s == "admin" { + bc.db.Model(&user).Update("state", "leaveticket") + bc.bot.Send(tgbotapi.NewMessage(user.ID, bc.GetBotContent("leaveticket_message"))) + } else { + bc.bot.Send(tgbotapi.NewMessage(user.ID, bc.GetBotContent("subscribe_message"))) + } + } else if user.IsEffectiveAdmin() { + if strings.HasPrefix(update.CallbackQuery.Data, "update:") { + Label := strings.Split(update.CallbackQuery.Data, ":")[1] + if Label == "preview_image" { + bc.db.Model(&user).Update("state", "imgset:" + Label) + } else { + bc.db.Model(&user).Update("state", "stringset:" + Label) + } + bc.bot.Send(tgbotapi.NewMessage(user.ID, "Send me asset (text or picture (NOT as file))")) + } + } + if user.IsAdmin() { + if update.CallbackQuery.Data == "panel" { + if !user.IsEffectiveAdmin() { + bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask | 0b10) + msg := tgbotapi.NewMessage(user.ID, "You was in usermode, turned back to admin mode...") + bc.bot.Send(msg) + } + kbd := tgbotapi.NewInlineKeyboardMarkup( + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Стартовая картинка", "update:preview_image")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Приветственный текст", "update:start")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Кнопка для заявки", "update:leave_ticket_button")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("ID чата", "update:supportchatid")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("ID канала", "update:channelid")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Уведомление об отправке тикета", "update:sended_notify")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Просьба оставить тикет", "update:leaveticket_message")), + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Просьба подписаться на канал", "update:subscribe_message")), + ) + msg := tgbotapi.NewMessage(user.ID, "Выберите пункт для изменения") + msg.ReplyMarkup = kbd + bc.bot.Send(msg) + } + } } } } + +func DownloadFile(filepath string, url string) error { + + // Get the data + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + return err +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..260d6bb --- /dev/null +++ b/config/config.go @@ -0,0 +1,24 @@ +package config + +import ( + "context" + "log" + + "github.com/sethvargo/go-envconfig" +) + +type Config struct { + BotToken string `env:"BOTTOKEN, required"` + AdminPass string `env:"ADMINPASSWORD, required"` +} + +func GetConfig() Config { + ctx := context.Background() + + var c Config + if err := envconfig.Process(ctx, &c); err != nil { + log.Fatal(err) + } + + return c +} diff --git a/go.mod b/go.mod index 63616cb..69765db 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,14 @@ -module github.com/akulij/zarabot +module github.com/akulij/ticketbot go 1.22.2 -require github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect +require ( + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/sethvargo/go-envconfig v1.0.1 // indirect + golang.org/x/text v0.14.0 // indirect + gorm.io/driver/sqlite v1.5.6 // indirect + gorm.io/gorm v1.25.11 // indirect +) diff --git a/go.sum b/go.sum index db8e45c..d50eaec 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,18 @@ 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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/sethvargo/go-envconfig v1.0.1 h1:9wglip/5fUfaH0lQecLM8AyOClMw0gT0A9K2c2wozao= +github.com/sethvargo/go-envconfig v1.0.1/go.mod h1:OKZ02xFaD3MvWBBmEW45fQr08sJEsonGrrOdicvQmQA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= +gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= +gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=