diff --git a/cmd/app/db.go b/cmd/app/db.go index 55cd966..bfe65b3 100644 --- a/cmd/app/db.go +++ b/cmd/app/db.go @@ -27,10 +27,10 @@ func (bc BotController) GetUserByID(UserID int64) (User, error) { type UserInfo struct { gorm.Model - ID int64 - Username string - FirstName string - LastName string + ID int64 + Username string + FirstName string + LastName string } func (u User) IsAdmin() bool { @@ -45,7 +45,7 @@ type BotContent struct { gorm.Model Literal string Content string - Metadata string + Metadata string } func GetDB() (*gorm.DB, error) { @@ -89,7 +89,7 @@ func (bc BotController) GetBotContentMetadata(Literal string) (string, error) { } func (bc BotController) SetBotContent(Literal string, Content string, Metadata string) { - c := BotContent{Literal: Literal, Content: Content, Metadata: Metadata} + c := BotContent{Literal: Literal, Content: Content, Metadata: Metadata} bc.db.FirstOrCreate(&c, "Literal", Literal) bc.db.Model(&c).Update("Content", Content) bc.db.Model(&c).Update("Metadata", Metadata) @@ -108,7 +108,7 @@ func (bc BotController) GetUser(UserID int64) User { } func (bc BotController) UpdateUserInfo(ui UserInfo) { - bc.db.Save(&ui) + bc.db.Save(&ui) } func (bc BotController) GetUserInfo(UserID int64) (UserInfo, error) { @@ -116,7 +116,7 @@ func (bc BotController) GetUserInfo(UserID int64) (UserInfo, error) { bc.db.First(&ui, "ID", UserID) if ui == (UserInfo{}) { log.Printf("NO UserInfo FOUND!!!, id: [%d]", UserID) - return UserInfo{}, errors.New("NO UserInfo FOUND!!!") + return UserInfo{}, errors.New("NO UserInfo FOUND!!!") } return ui, nil @@ -139,22 +139,24 @@ func (bc BotController) LogMessageRaw(UserID int64, Msg string, Time time.Time) } type ReservationStatus int64 + const ( - Booked ReservationStatus = iota - Paid + Booked ReservationStatus = iota + Paid ) + var ReservationStatusString = []string{ - "Забронировано", - "Оплачено", + "Забронировано", + "Оплачено", } type Reservation struct { - gorm.Model - ID int64 `gorm:"primary_key"` - UserID int64 `gorm:"uniqueIndex:user_event_uniq"` - TimeBooked *time.Time - EventID int64 `gorm:"uniqueIndex:user_event_uniq"` - Status ReservationStatus + gorm.Model + ID int64 `gorm:"primary_key"` + UserID int64 `gorm:"uniqueIndex:user_event_uniq"` + TimeBooked *time.Time + EventID int64 `gorm:"uniqueIndex:user_event_uniq"` + Status ReservationStatus } func (bc BotController) GetAllReservations() ([]Reservation, error) { @@ -176,9 +178,9 @@ func (bc BotController) GetReservationsByEventID(EventID int64) ([]Reservation, } type Event struct { - gorm.Model - ID int64 `gorm:"primary_key"` - Date *time.Time `gorm:"unique"` + gorm.Model + ID int64 `gorm:"primary_key"` + Date *time.Time `gorm:"unique"` } func (bc BotController) GetAllEvents() ([]Event, error) { @@ -200,23 +202,24 @@ func (bc BotController) GetEvent(EventID int64) (Event, error) { } type TaskType int64 + const ( - SyncSheet TaskType = iota - NotifyAboutEvent + SyncSheet TaskType = iota + NotifyAboutEvent ) type Task struct { - gorm.Model - ID int64 `gorm:"primary_key"` - Type TaskType - EventID int64 + gorm.Model + ID int64 `gorm:"primary_key"` + Type TaskType + EventID int64 } func (bc BotController) CreateSimpleTask(taskType TaskType) error { task := Task{ - Type: taskType, + Type: taskType, } - return bc.CreateTask(task) + return bc.CreateTask(task) } func (bc BotController) CreateTask(task Task) error { diff --git a/cmd/app/gsheets.go b/cmd/app/gsheets.go index b8b1117..6c16892 100644 --- a/cmd/app/gsheets.go +++ b/cmd/app/gsheets.go @@ -4,35 +4,37 @@ import ( "context" "fmt" "log" - "time" + "time" "google.golang.org/api/option" "google.golang.org/api/sheets/v4" ) func (bc *BotController) SyncPaidUsersToSheet() error { - reservations, _ := bc.GetAllReservations() + reservations, _ := bc.GetAllReservations() ctx := context.Background() srv, err := sheets.NewService(ctx, - option.WithCredentialsFile("./credentials.json"), - option.WithScopes(sheets.SpreadsheetsScope), - ) + option.WithCredentialsFile("./credentials.json"), + option.WithScopes(sheets.SpreadsheetsScope), + ) if err != nil { return fmt.Errorf("unable to retrieve Sheets client: %v", err) } var values [][]interface{} - values = append(values, []interface{}{"Телеграм ID", "Имя", "Фамилия", "Никнейм", "Указанное имя", "Дата", "Телефон", "Статус"}) + values = append(values, []interface{}{"Телеграм ID", "Имя", "Фамилия", "Никнейм", "Указанное имя", "Дата", "Телефон", "Статус"}) for _, reservation := range reservations { - if reservation.Status != Paid {continue} + if reservation.Status != Paid { + continue + } - uid := reservation.UserID - user, _ := bc.GetUserByID(uid) - ui, _ := bc.GetUserInfo(uid) - event, _ := bc.GetEvent(reservation.EventID) - status := ReservationStatusString[reservation.Status] + uid := reservation.UserID + user, _ := bc.GetUserByID(uid) + ui, _ := bc.GetUserInfo(uid) + event, _ := bc.GetEvent(reservation.EventID) + status := ReservationStatusString[reservation.Status] values = append(values, []interface{}{user.ID, ui.FirstName, ui.LastName, ui.Username, "TODO", formatDate(event.Date), "", status}) } @@ -55,5 +57,5 @@ func (bc *BotController) SyncPaidUsersToSheet() error { } func formatDate(t *time.Time) string { - return t.Format("02.01 15:04") + return t.Format("02.01 15:04") } diff --git a/cmd/app/main.go b/cmd/app/main.go index f4a235a..4cd88df 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -1,16 +1,16 @@ package main import ( + "encoding/json" "fmt" - "time" "io" "log" + "math" "net/http" "os" "strconv" "strings" - "encoding/json" - "math" + "time" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) @@ -22,45 +22,44 @@ var adminCommands = map[string]func(BotController, tgbotapi.Update, User){ "/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 + "/broadcast": handleBroadcastCommand, // use /broadcast `msg` to send msg to every known user } 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), + 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", - "ПН", - "ВТ", - "СР", - "ЧТ", - "ПТ", - "СБ", - "ВС", + "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())) + 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) + } - // TODO: REMOVE - for _, date := range nearestDates { - event := Event{Date: &date} - bc.db.Create(&event) - } - - // Run other background tasks - go continiousSyncGSheets(bc) - go notifyAboutEvents(bc) + // Run other background tasks + go continiousSyncGSheets(bc) + go notifyAboutEvents(bc) for update := range bc.updates { go ProcessUpdate(bc, update) @@ -68,35 +67,35 @@ func main() { } func continiousSyncGSheets(bc BotController) { - for true { - err := bc.SyncPaidUsersToSheet() - if err != nil { - log.Printf("Error sync: %s\n", err) - } + for true { + err := bc.SyncPaidUsersToSheet() + if err != nil { + log.Printf("Error sync: %s\n", err) + } - time.Sleep(60 * time.Second) - } + 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 + // 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) - } - } - } + msg := tgbotapi.NewMessage(uid, bc.GetBotContent("notify_pre_event")) + bc.bot.Send(msg) + } + } + } - time.Sleep(60 * time.Second) - } + time.Sleep(60 * time.Second) + } } func ProcessUpdate(bc BotController, update tgbotapi.Update) { @@ -104,12 +103,12 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) { 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())) + 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) + // TODO: REMOVE + reservation := Reservation{UserID: UserID, EventID: 1, Status: Paid} + bc.db.Create(&reservation) text := update.Message.Text if strings.HasPrefix(text, "/") { @@ -153,13 +152,13 @@ func handleCallbackQuery(bc BotController, update tgbotapi.Update) { user := bc.GetUser(update.CallbackQuery.From.ID) 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) + 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) } @@ -192,19 +191,19 @@ func handleStartCommand(bc BotController, update tgbotapi.Update, user User) { bc.db.Model(&user).Update("state", "start") rows := [][]tgbotapi.InlineKeyboardButton{} for _, d := range nearestDates { - k, v := getDateButton(d) + 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...) + 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 == "" { @@ -248,15 +247,17 @@ func handleDeopCommand(bc BotController, update tgbotapi.Update, user User) { } func handleBroadcastCommand(bc BotController, update tgbotapi.Update, user User) { - if !user.IsAdmin() {return} + if !user.IsAdmin() { + return + } var users []User bc.db.Find(&users) - for _, user := range users { - user = user - // TODO!!! - } + for _, user := range users { + user = user + // TODO!!! + } } func handleDefaultMessage(bc BotController, update tgbotapi.Update, user User) { @@ -316,8 +317,8 @@ func handleDefaultMessage(bc BotController, update tgbotapi.Update, user User) { } else if strings.HasPrefix(user.State, "stringset:") { Literal := strings.Split(user.State, ":")[1] - b, _ := json.Marshal(update.Message.Entities) - strEntities := string(b) + b, _ := json.Marshal(update.Message.Entities) + strEntities := string(b) bc.SetBotContent(Literal, update.Message.Text, strEntities) bc.db.Model(&user).Update("state", "start") @@ -448,18 +449,18 @@ func getAdmins(bc BotController) []User { func getDateButton(date time.Time) (string, string) { // Format the date as needed, e.g., "2006-01-02" - wday := WeekLabels[int(date.Local().Weekday())] + wday := WeekLabels[int(date.Local().Weekday())] formattedDate := strings.Join([]string{ - date.Format("02.01.2006"), - "(" + wday + ")", - "в", - date.Format("15:04"), - }, " ") + 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 + return strings.Join([]string{"Пойду", formattedDate}, " "), token } func GetUserInfo(user *tgbotapi.User) UserInfo { diff --git a/cmd/app/panel.go b/cmd/app/panel.go index de269d1..215f91b 100644 --- a/cmd/app/panel.go +++ b/cmd/app/panel.go @@ -11,8 +11,8 @@ var assets = map[string]string{ "Просьба подписаться на канал": "subscribe_message", "Ссылка на канал": "channel_link", "Подробнее о мероприятии": "more_info", - "Текст о мероприятии": "more_info_text", - "Текст: напоминание за 8 часов": "notify_pre_event", + "Текст о мероприятии": "more_info_text", + "Текст: напоминание за 8 часов": "notify_pre_event", } func handlePanel(bc BotController, user User) { diff --git a/config/config.go b/config/config.go index 72760c5..d381832 100644 --- a/config/config.go +++ b/config/config.go @@ -8,19 +8,19 @@ import ( ) type Config struct { - BotToken string `env:"BOTTOKEN, required"` - AdminPass string `env:"ADMINPASSWORD, required"` // to activate admin privileges in bot type command: /secret `AdminPass` - AdminID *int64 `env:"ADMINID"` // optional admin ID for notifications - SheetID string `env:"SHEETID, required"` // id of google sheet where users will be synced + BotToken string `env:"BOTTOKEN, required"` + AdminPass string `env:"ADMINPASSWORD, required"` // to activate admin privileges in bot type command: /secret `AdminPass` + AdminID *int64 `env:"ADMINID"` // optional admin ID for notifications + SheetID string `env:"SHEETID, required"` // id of google sheet where users will be synced } func GetConfig() Config { - ctx := context.Background() + ctx := context.Background() - var c Config - if err := envconfig.Process(ctx, &c); err != nil { - log.Fatal(err) - } + var c Config + if err := envconfig.Process(ctx, &c); err != nil { + log.Fatal(err) + } - return c + return c }