diff --git a/cmd/app/main.go b/cmd/app/main.go index 077e2f4..f4a235a 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -2,40 +2,114 @@ package main import ( "fmt" + "time" "io" "log" "net/http" "os" "strconv" "strings" + "encoding/json" + "math" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) var adminCommands = map[string]func(BotController, tgbotapi.Update, User){ - "/secret": handleSecretCommand, // activate admin mode via /secret `AdminPass` - "/panel": handlePanelCommand, // open bot settings - "/usermode": handleDefaultMessage, // temporarly disable admin mode to test ui - "/deop": handleDeopCommand, // removes your admin rights at all! - "/id": handleDefaultMessage, // to check id of chat - "/setchannelid": handleDefaultMessage, // just type it in channel which one is supposed to be lined with bot + "/secret": handleSecretCommand, // activate admin mode via /secret `AdminPass` + "/panel": handlePanelCommand, // open bot settings + "/usermode": handleDefaultMessage, // temporarly disable admin mode to test ui + "/deop": handleDeopCommand, // removes your admin rights at all! + "/id": handleDefaultMessage, // to check id of chat + "/setchannelid": handleDefaultMessage, // just type it in channel which one is supposed to be lined with bot + "/broadcast": handleBroadcastCommand, // use /broadcast `msg` to send msg to every known user } -var nearDatesApril = []int{1, 3} // why? because it is as temporal as it can be +var dubaiLocation, _ = time.LoadLocation("Asia/Dubai") + +var nearestDates = []time.Time{ + time.Date(2025, 3, 28, 18, 0, 0, 0, dubaiLocation), + time.Date(2025, 4, 1, 18, 0, 0, 0, dubaiLocation), + time.Date(2025, 4, 2, 18, 0, 0, 0, dubaiLocation), +} + +var WeekLabels = []string{ + "None", + "ПН", + "ВТ", + "СР", + "ЧТ", + "ПТ", + "СБ", + "ВС", +} func main() { var bc = GetBotController() + button, callback := getDateButton(nearestDates[0]) + log.Printf("Buttons: %s, %s\n", button, callback) + log.Printf("Location: %s\n", dubaiLocation.String()) + log.Printf("Diff: %s\n", nearestDates[0].Sub(time.Now())) + + + // TODO: REMOVE + for _, date := range nearestDates { + event := Event{Date: &date} + bc.db.Create(&event) + } + + // Run other background tasks + go continiousSyncGSheets(bc) + go notifyAboutEvents(bc) for update := range bc.updates { go ProcessUpdate(bc, update) } } +func continiousSyncGSheets(bc BotController) { + for true { + err := bc.SyncPaidUsersToSheet() + if err != nil { + log.Printf("Error sync: %s\n", err) + } + + time.Sleep(60 * time.Second) + } +} + +func notifyAboutEvents(bc BotController) { + // TODO: migrate to tasks system + for true { + events, _ := bc.GetAllEvents() + for _, event := range events { + delta := event.Date.Sub(time.Now()) + if int(math.Floor(delta.Minutes())) == 8 * 60 { // 8 hours + reservations, _ := bc.GetReservationsByEventID(event.ID) + for _, reservation := range reservations { + uid := reservation.UserID + + msg := tgbotapi.NewMessage(uid, bc.GetBotContent("notify_pre_event")) + bc.bot.Send(msg) + } + } + } + + time.Sleep(60 * time.Second) + } +} + func ProcessUpdate(bc BotController, update tgbotapi.Update) { if update.Message != nil { var UserID = update.Message.From.ID user := bc.GetUser(UserID) bc.LogMessage(update) + log.Printf("Surname: %s\n", update.SentFrom().LastName) + bc.UpdateUserInfo(GetUserInfo(update.SentFrom())) + + // TODO: REMOVE + reservation := Reservation{UserID: UserID, EventID: 1, Status: Paid} + bc.db.Create(&reservation) text := update.Message.Text if strings.HasPrefix(text, "/") { @@ -78,8 +152,14 @@ func handleCommand(bc BotController, update tgbotapi.Update, user User) { func handleCallbackQuery(bc BotController, update tgbotapi.Update) { user := bc.GetUser(update.CallbackQuery.From.ID) - if update.CallbackQuery.Data == "leave_ticket_button" { - handleLeaveTicketButton(bc, update, user) + if update.CallbackQuery.Data == "more_info" { + sendMessage(bc, update.FromChat().ID, bc.GetBotContent("more_info_text")) + msg := tgbotapi.NewMessage(update.FromChat().ID, bc.GetBotContent("more_info_text")) + var entities []tgbotapi.MessageEntity + meta, _ := bc.GetBotContentMetadata("more_info_text") + json.Unmarshal([]byte(meta), &entities) + msg.Entities = entities + bc.bot.Send(msg) } else if user.IsEffectiveAdmin() { handleAdminCallback(bc, update, user) } @@ -95,7 +175,7 @@ func handleCallbackQuery(bc BotController, update tgbotapi.Update) { func handleChannelPost(bc BotController, update tgbotapi.Update) { post := update.ChannelPost if post.Text == "setchannelid" { - bc.SetBotContent("channelid", strconv.FormatInt(post.SenderChat.ID, 10)) + bc.SetBotContent("channelid", strconv.FormatInt(post.SenderChat.ID, 10), "") var admins []User bc.db.Where("role_bitmask & 1 = ?", 1).Find(&admins) @@ -110,11 +190,21 @@ func handleChannelPost(bc BotController, update tgbotapi.Update) { // Helper functions for specific commands func handleStartCommand(bc BotController, update tgbotapi.Update, user User) { bc.db.Model(&user).Update("state", "start") - kbd := tgbotapi.NewInlineKeyboardMarkup( - tgbotapi.NewInlineKeyboardRow( - tgbotapi.NewInlineKeyboardButtonData(bc.GetBotContent("leave_ticket_button"), "leave_ticket_button"), - ), - ) + rows := [][]tgbotapi.InlineKeyboardButton{} + for _, d := range nearestDates { + k, v := getDateButton(d) + rows = append(rows, + tgbotapi.NewInlineKeyboardRow( + tgbotapi.NewInlineKeyboardButtonData(k, v), + ), + ) + } + rows = append(rows, + tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData( + bc.GetBotContent("more_info"), "more_info", + )), + ) + kbd := tgbotapi.NewInlineKeyboardMarkup(rows...) img, err := bc.GetBotContentVerbose("preview_image") if err != nil || img == "" { @@ -157,6 +247,18 @@ func handleDeopCommand(bc BotController, update tgbotapi.Update, user User) { bc.bot.Send(msg) } +func handleBroadcastCommand(bc BotController, update tgbotapi.Update, user User) { + if !user.IsAdmin() {return} + + var users []User + bc.db.Find(&users) + + for _, user := range users { + user = user + // TODO!!! + } +} + func handleDefaultMessage(bc BotController, update tgbotapi.Update, user User) { if user.State == "leaveticket" { f := update.Message.From @@ -197,7 +299,7 @@ func handleDefaultMessage(bc BotController, update tgbotapi.Update, user User) { if update.Message.Text == "unset" { var l BotContent bc.db.First(&l, "Literal", Literal) - bc.SetBotContent(Literal, "") + bc.SetBotContent(Literal, "", "") } maxsize := 0 fileid := "" @@ -207,13 +309,17 @@ func handleDefaultMessage(bc BotController, update tgbotapi.Update, user User) { maxsize = p.FileSize } } - bc.SetBotContent(Literal, fileid) + bc.SetBotContent(Literal, fileid, "") bc.db.Model(&user).Update("state", "start") msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Successfully set new image!") bc.bot.Send(msg) } else if strings.HasPrefix(user.State, "stringset:") { Literal := strings.Split(user.State, ":")[1] - bc.SetBotContent(Literal, update.Message.Text) + + b, _ := json.Marshal(update.Message.Entities) + strEntities := string(b) + + bc.SetBotContent(Literal, update.Message.Text, strEntities) bc.db.Model(&user).Update("state", "start") msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Successfully set new text!") bc.bot.Send(msg) @@ -339,3 +445,28 @@ func getAdmins(bc BotController) []User { bc.db.Where("role_bitmask & 1 = ?", 1).Find(&admins) return admins } + +func getDateButton(date time.Time) (string, string) { + // Format the date as needed, e.g., "2006-01-02" + wday := WeekLabels[int(date.Local().Weekday())] + formattedDate := strings.Join([]string{ + date.Format("02.01.2006"), + "(" + wday + ")", + "в", + date.Format("15:04"), + }, " ") + + // Create a token similar to what GetBotContent accepts + token := fmt.Sprintf("date_%s", date.Format("200601021504")) // Example token format + + return strings.Join([]string{"Пойду", formattedDate}, " "), token +} + +func GetUserInfo(user *tgbotapi.User) UserInfo { + return UserInfo{ + ID: user.ID, + Username: user.UserName, + FirstName: user.FirstName, + LastName: user.LastName, + } +}