Compare commits
50 Commits
0331f3e29e
...
2abb18cb49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2abb18cb49 | ||
|
|
0949b1c812 | ||
|
|
22c2b63069 | ||
|
|
04d8e8f086 | ||
|
|
b0137e83fc | ||
|
|
feb5a67d6a | ||
|
|
d92e4899c8 | ||
|
|
8352829b92 | ||
|
|
c78c1fe295 | ||
|
|
81cecbaaf6 | ||
|
|
065f4fffbf | ||
|
|
92dd0bbb2d | ||
|
|
201437d6c2 | ||
|
|
bf1d7df88f | ||
|
|
5923f6bf3f | ||
|
|
ed99f4a16f | ||
|
|
8b6317b31a | ||
|
|
ce608abff1 | ||
|
|
95e92dea3c | ||
|
|
b6dae4b24c | ||
|
|
098ef11ca3 | ||
|
|
0564467c59 | ||
|
|
9f923a04fa | ||
|
|
319a4b0f66 | ||
|
|
84b59379d7 | ||
|
|
2134659426 | ||
|
|
96af28b365 | ||
|
|
4c1e4180b7 | ||
|
|
b7c01addcf | ||
|
|
61214ca405 | ||
|
|
14e250bfc3 | ||
|
|
dff3fc58ad | ||
|
|
c785e3676c | ||
|
|
b94873b6a8 | ||
|
|
722431e8a5 | ||
|
|
e7b21a45b3 | ||
|
|
ee254ed865 | ||
|
|
121022fb54 | ||
|
|
a00b7e874b | ||
|
|
ecd55a3031 | ||
|
|
59d7f07f48 | ||
|
|
8a937724c1 | ||
|
|
94894b2c88 | ||
|
|
5144cb58dc | ||
|
|
38c7bef71c | ||
|
|
4c0d2ec3a3 | ||
|
|
b770b2c5dc | ||
|
|
c81e781247 | ||
|
|
c87e4683db | ||
|
|
ce7c1613b1 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.env
|
||||
**.jpg
|
||||
**.db
|
||||
credentials.json
|
||||
|
||||
62
cmd/app/botcontroller.go
Normal file
62
cmd/app/botcontroller.go
Normal file
@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
|
||||
"github.com/akulij/ticketbot/config"
|
||||
)
|
||||
|
||||
type BotController struct {
|
||||
cfg config.Config
|
||||
bot *tgbotapi.BotAPI
|
||||
db *gorm.DB
|
||||
updates tgbotapi.UpdatesChannel
|
||||
}
|
||||
|
||||
func GetBotController() BotController {
|
||||
cfg := config.GetConfig()
|
||||
log.Printf("Token value: '%v'\n", cfg.BotToken)
|
||||
log.Printf("Admin password: '%v'\n", cfg.AdminPass)
|
||||
log.Printf("Admin ID: '%v'\n", *cfg.AdminID)
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(cfg.BotToken)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
db, err := GetDB()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
bot.Debug = true // set true only while development, should be set to false in production
|
||||
|
||||
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
|
||||
updates := bot.GetUpdatesChan(u)
|
||||
|
||||
return BotController{cfg: cfg, bot: bot, db: db, updates: updates}
|
||||
}
|
||||
|
||||
func (bc BotController) LogMessage(update tgbotapi.Update) error {
|
||||
var msg *tgbotapi.Message
|
||||
if update.Message != nil {
|
||||
msg = update.Message
|
||||
} else {
|
||||
return errors.New("invalid update provided to message logger")
|
||||
}
|
||||
|
||||
var UserID = msg.From.ID
|
||||
|
||||
bc.LogMessageRaw(UserID, msg.Text, msg.Time())
|
||||
|
||||
return nil
|
||||
}
|
||||
230
cmd/app/db.go
230
cmd/app/db.go
@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@ -11,10 +13,26 @@ type User struct {
|
||||
gorm.Model
|
||||
ID int64
|
||||
State string
|
||||
MsgCounter uint
|
||||
RoleBitmask uint
|
||||
}
|
||||
|
||||
func (bc BotController) GetUserByID(UserID int64) (User, error) {
|
||||
var user User
|
||||
bc.db.First(&user, "ID", UserID)
|
||||
if user == (User{}) {
|
||||
return User{}, errors.New("No content")
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
gorm.Model
|
||||
ID int64
|
||||
Username string
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
|
||||
func (u User) IsAdmin() bool {
|
||||
return u.RoleBitmask&1 == 1
|
||||
}
|
||||
@ -25,8 +43,9 @@ func (u User) IsEffectiveAdmin() bool {
|
||||
|
||||
type BotContent struct {
|
||||
gorm.Model
|
||||
Literal string
|
||||
Content string
|
||||
Literal string
|
||||
Content string
|
||||
Metadata string
|
||||
}
|
||||
|
||||
func GetDB() (*gorm.DB, error) {
|
||||
@ -36,7 +55,12 @@ func GetDB() (*gorm.DB, error) {
|
||||
}
|
||||
|
||||
db.AutoMigrate(&User{})
|
||||
db.AutoMigrate(&UserInfo{})
|
||||
db.AutoMigrate(&BotContent{})
|
||||
db.AutoMigrate(&Message{})
|
||||
db.AutoMigrate(&Reservation{})
|
||||
db.AutoMigrate(&Event{})
|
||||
db.AutoMigrate(&Task{})
|
||||
|
||||
return db, err
|
||||
}
|
||||
@ -55,9 +79,201 @@ func (bc BotController) GetBotContent(Literal string) string {
|
||||
return content
|
||||
}
|
||||
|
||||
func (bc BotController) SetBotContent(Literal string, Content string) {
|
||||
c := BotContent{Literal: Literal, Content: Content}
|
||||
bc.db.FirstOrCreate(&c, "Literal", Literal)
|
||||
bc.db.Model(&c).Update("Content", Content)
|
||||
func (bc BotController) GetBotContentMetadata(Literal string) (string, error) {
|
||||
var c BotContent
|
||||
bc.db.First(&c, "Literal", Literal)
|
||||
if c == (BotContent{}) {
|
||||
return "[]", errors.New("No metadata")
|
||||
}
|
||||
return c.Metadata, nil
|
||||
}
|
||||
|
||||
func (bc BotController) SetBotContent(Literal string, Content string, Metadata string) {
|
||||
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)
|
||||
}
|
||||
|
||||
func (bc BotController) GetUser(UserID int64) User {
|
||||
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)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func (bc BotController) UpdateUserInfo(ui UserInfo) {
|
||||
bc.db.Save(&ui)
|
||||
}
|
||||
|
||||
func (bc BotController) GetUserInfo(UserID int64) (UserInfo, error) {
|
||||
var ui UserInfo
|
||||
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 ui, nil
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
gorm.Model
|
||||
UserID int64
|
||||
Msg string
|
||||
Datetime *time.Time
|
||||
}
|
||||
|
||||
func (bc BotController) LogMessageRaw(UserID int64, Msg string, Time time.Time) {
|
||||
msg := Message{
|
||||
UserID: UserID,
|
||||
Msg: Msg,
|
||||
Datetime: &Time,
|
||||
}
|
||||
bc.db.Create(&msg)
|
||||
}
|
||||
|
||||
type ReservationStatus int64
|
||||
|
||||
const (
|
||||
Booked ReservationStatus = iota
|
||||
Paid
|
||||
)
|
||||
|
||||
var ReservationStatusString = []string{
|
||||
"Забронировано",
|
||||
"Оплачено",
|
||||
}
|
||||
|
||||
type Reservation struct {
|
||||
gorm.Model
|
||||
ID int64 `gorm:"primary_key"`
|
||||
UserID int64 `gorm:"uniqueIndex:user_event_uniq"`
|
||||
EnteredName string
|
||||
TimeBooked *time.Time
|
||||
EventID int64 `gorm:"uniqueIndex:user_event_uniq"`
|
||||
Status ReservationStatus
|
||||
}
|
||||
|
||||
func (bc BotController) GetAllReservations() ([]Reservation, error) {
|
||||
var reservations []Reservation
|
||||
result := bc.db.Find(&reservations)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return reservations, nil
|
||||
}
|
||||
|
||||
func (bc BotController) GetReservationsByEventID(EventID int64) ([]Reservation, error) {
|
||||
var reservations []Reservation
|
||||
result := bc.db.Where("event_id = ?", EventID).Find(&reservations)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return reservations, nil
|
||||
}
|
||||
|
||||
func (bc BotController) CountReservationsByEventID(EventID int64) (int64, error) {
|
||||
var count int64
|
||||
result := bc.db.Model(&Reservation{}).Where("event_id = ?", EventID).Count(&count)
|
||||
if result.Error != nil {
|
||||
return 0, result.Error
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (bc BotController) CreateReservation(userID int64, eventID int64, name string) (Reservation, error) {
|
||||
var dubaiLocation, _ = time.LoadLocation("Asia/Dubai")
|
||||
timenow := time.Now().In(dubaiLocation)
|
||||
reservation := Reservation{
|
||||
UserID: userID,
|
||||
EventID: eventID,
|
||||
TimeBooked: &timenow,
|
||||
Status: Booked,
|
||||
EnteredName: name,
|
||||
}
|
||||
result := bc.db.Create(&reservation)
|
||||
return reservation, result.Error
|
||||
}
|
||||
|
||||
func (bc BotController) GetReservationByID(reservationID int64) (Reservation, error) {
|
||||
var reservation Reservation
|
||||
result := bc.db.First(&reservation, reservationID)
|
||||
if result.Error != nil {
|
||||
return Reservation{}, result.Error
|
||||
}
|
||||
return reservation, nil
|
||||
}
|
||||
|
||||
func (bc BotController) UpdateReservation(r Reservation) {
|
||||
bc.db.Save(&r)
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
gorm.Model
|
||||
ID int64 `gorm:"primary_key"`
|
||||
Date *time.Time `gorm:"unique"`
|
||||
}
|
||||
|
||||
func (bc BotController) GetAllEvents() ([]Event, error) {
|
||||
var events []Event
|
||||
result := bc.db.Find(&events)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (bc BotController) GetEvent(EventID int64) (Event, error) {
|
||||
var event Event
|
||||
result := bc.db.First(&event, EventID)
|
||||
if result.Error != nil {
|
||||
return Event{}, result.Error
|
||||
}
|
||||
return event, nil
|
||||
}
|
||||
|
||||
type TaskType int64
|
||||
|
||||
const (
|
||||
SyncSheet TaskType = iota
|
||||
NotifyAboutEvent
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
gorm.Model
|
||||
ID int64 `gorm:"primary_key"`
|
||||
Type TaskType
|
||||
EventID int64
|
||||
}
|
||||
|
||||
func (bc BotController) CreateSimpleTask(taskType TaskType) error {
|
||||
task := Task{
|
||||
Type: taskType,
|
||||
}
|
||||
return bc.CreateTask(task)
|
||||
}
|
||||
|
||||
func (bc BotController) CreateTask(task Task) error {
|
||||
result := bc.db.Create(&task)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
func (bc BotController) DeleteTask(taskID int64) error {
|
||||
result := bc.db.Delete(&Task{}, taskID)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
func (bc BotController) GetAllTasks() ([]Task, error) {
|
||||
var tasks []Task
|
||||
result := bc.db.Find(&tasks)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
61
cmd/app/gsheets.go
Normal file
61
cmd/app/gsheets.go
Normal file
@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/sheets/v4"
|
||||
)
|
||||
|
||||
func (bc *BotController) SyncPaidUsersToSheet() error {
|
||||
reservations, _ := bc.GetAllReservations()
|
||||
|
||||
ctx := context.Background()
|
||||
srv, err := sheets.NewService(ctx,
|
||||
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", "Имя", "Фамилия", "Никнейм", "Указанное имя", "Дата", "Телефон", "Статус"})
|
||||
|
||||
for _, reservation := range reservations {
|
||||
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]
|
||||
|
||||
values = append(values, []interface{}{user.ID, ui.FirstName, ui.LastName, ui.Username, reservation.EnteredName, formatDate(event.Date), "", status})
|
||||
}
|
||||
|
||||
// Prepare the data to be written to the sheet
|
||||
valueRange := &sheets.ValueRange{
|
||||
Values: values,
|
||||
}
|
||||
|
||||
_, err = srv.Spreadsheets.Values.Clear(bc.cfg.SheetID, "A1:Z1000", &sheets.ClearValuesRequest{}).Do()
|
||||
|
||||
// Write the data to the specified range in the sheet
|
||||
_, err = srv.Spreadsheets.Values.Update(bc.cfg.SheetID, "A1", valueRange).ValueInputOption("RAW").Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write data to sheet: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully synced %d reservations to the Google Sheet.", len(reservations))
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatDate(t *time.Time) string {
|
||||
return t.Format("02.01 15:04")
|
||||
}
|
||||
411
cmd/app/main.go
411
cmd/app/main.go
@ -1,67 +1,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
|
||||
"github.com/akulij/ticketbot/config"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BotController struct {
|
||||
cfg config.Config
|
||||
bot *tgbotapi.BotAPI
|
||||
db *gorm.DB
|
||||
updates tgbotapi.UpdatesChannel
|
||||
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
|
||||
"/broadcast": handleBroadcastCommand, // use /broadcast `msg` to send msg to every known user
|
||||
}
|
||||
|
||||
func GetBotController() BotController {
|
||||
cfg := config.GetConfig()
|
||||
fmt.Printf("Token value: '%v'\n", cfg.BotToken)
|
||||
fmt.Printf("Admin password: '%v'\n", cfg.AdminPass)
|
||||
fmt.Printf("Admin ID: '%v'\n", *cfg.AdminID)
|
||||
var dubaiLocation, _ = time.LoadLocation("Asia/Dubai")
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(cfg.BotToken)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
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),
|
||||
}
|
||||
|
||||
db, err := GetDB()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
const seatscnt = 10
|
||||
|
||||
bot.Debug = true // set true only while development, should be set to false in production
|
||||
|
||||
log.Printf("Authorized on account %s", bot.Self.UserName)
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
|
||||
updates := bot.GetUpdatesChan(u)
|
||||
|
||||
return BotController{cfg: cfg, bot: bot, db: db, updates: updates}
|
||||
var WeekLabels = []string{
|
||||
"ВС",
|
||||
"ПН",
|
||||
"ВТ",
|
||||
"СР",
|
||||
"ЧТ",
|
||||
"ПТ",
|
||||
"СБ",
|
||||
}
|
||||
|
||||
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.Ceil(delta.Minutes())) == 8*60 { // 8 hours
|
||||
reservations, _ := bc.GetReservationsByEventID(event.ID)
|
||||
for _, reservation := range reservations {
|
||||
uid := reservation.UserID
|
||||
|
||||
go func() {
|
||||
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 {
|
||||
handleMessage(bc, 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()))
|
||||
|
||||
// TODO: REMOVE
|
||||
reservation := Reservation{UserID: UserID, EventID: 1, Status: Paid}
|
||||
bc.db.Create(&reservation)
|
||||
|
||||
text := update.Message.Text
|
||||
if strings.HasPrefix(text, "/") {
|
||||
handleCommand(bc, update, user)
|
||||
} else {
|
||||
handleDefaultMessage(bc, update, user)
|
||||
}
|
||||
} else if update.CallbackQuery != nil {
|
||||
handleCallbackQuery(bc, update)
|
||||
} else if update.ChannelPost != nil {
|
||||
@ -69,47 +126,76 @@ func ProcessUpdate(bc BotController, update tgbotapi.Update) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(bc BotController, update tgbotapi.Update) {
|
||||
var UserID = update.Message.From.ID
|
||||
func handleCommand(bc BotController, update tgbotapi.Update, user User) {
|
||||
msg := update.Message
|
||||
|
||||
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)
|
||||
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
|
||||
log.Printf("[Entities] %s", update.Message.Entities)
|
||||
log.Printf("[COMMAND] %s", update.Message.Command())
|
||||
|
||||
command := "/" + msg.Command() // if it is not a command, then it will simply be "/"
|
||||
if user.IsAdmin() {
|
||||
f, exists := adminCommands[command] // f is a function that handles specified command
|
||||
if exists {
|
||||
f(bc, update, user)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
possibleCommand := strings.Split(update.Message.Text, " ")[0]
|
||||
args := strings.Split(update.Message.Text, " ")[1:]
|
||||
log.Printf("Args: %s", args)
|
||||
|
||||
switch {
|
||||
case possibleCommand == "/start":
|
||||
// commands for non-admins
|
||||
switch command {
|
||||
case "/start":
|
||||
handleStartCommand(bc, update, user)
|
||||
case possibleCommand == "/id" && user.IsAdmin():
|
||||
bc.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, strconv.FormatInt(update.Message.Chat.ID, 10)))
|
||||
case possibleCommand == "/secret" && len(args) > 0 && args[0] == bc.cfg.AdminPass:
|
||||
case "/secret":
|
||||
handleSecretCommand(bc, update, user)
|
||||
case possibleCommand == "/panel" && user.IsAdmin():
|
||||
handlePanelCommand(bc, update, user)
|
||||
case possibleCommand == "/usermode" && user.IsEffectiveAdmin():
|
||||
handleUserModeCommand(bc, update, user)
|
||||
default:
|
||||
handleDefaultMessage(bc, update, user)
|
||||
}
|
||||
}
|
||||
|
||||
func handleCallbackQuery(bc BotController, update tgbotapi.Update) {
|
||||
var user User
|
||||
bc.db.First(&user, "id", update.CallbackQuery.From.ID)
|
||||
user := bc.GetUser(update.CallbackQuery.From.ID)
|
||||
|
||||
if update.CallbackQuery.Data == "more_info" {
|
||||
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 strings.HasPrefix(update.CallbackQuery.Data, "paidcallback:") {
|
||||
token := strings.Split(update.CallbackQuery.Data, ":")[1]
|
||||
reservationid, err := strconv.ParseInt(token, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing reservation token: %s\n", err)
|
||||
return
|
||||
}
|
||||
reservation, _ := bc.GetReservationByID(reservationid)
|
||||
reservation.Status = Paid
|
||||
bc.UpdateReservation(reservation)
|
||||
notifyPaid(bc, reservation)
|
||||
|
||||
sendMessage(bc, update.CallbackQuery.From.ID, bc.GetBotContent("post_payment_message"))
|
||||
} else if strings.HasPrefix(update.CallbackQuery.Data, "reservedate:") {
|
||||
datetoken := strings.Split(update.CallbackQuery.Data, ":")[1]
|
||||
eventid, err := strconv.ParseInt(datetoken, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing date token: %s\n", err)
|
||||
return
|
||||
}
|
||||
taken, _ := bc.CountReservationsByEventID(eventid)
|
||||
if taken >= seatscnt {
|
||||
sendMessage(bc, user.ID, bc.GetBotContent("soldout_message"))
|
||||
return
|
||||
}
|
||||
// event, _ := bc.GetEvent(eventid)
|
||||
reservation, err := bc.CreateReservation(update.CallbackQuery.From.ID, eventid, "Не указано")
|
||||
if err != nil {
|
||||
log.Printf("Error creating reservation: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
bc.db.Model(&user).Update("state", "enternamereservation:"+strconv.FormatInt(reservation.ID, 10))
|
||||
sendMessage(bc, user.ID, bc.GetBotContent("reserved_message"))
|
||||
|
||||
if update.CallbackQuery.Data == "leave_ticket_button" {
|
||||
handleLeaveTicketButton(bc, update, user)
|
||||
} else if user.IsEffectiveAdmin() {
|
||||
handleAdminCallback(bc, update, user)
|
||||
}
|
||||
@ -125,7 +211,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)
|
||||
@ -140,63 +226,67 @@ 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"),
|
||||
),
|
||||
)
|
||||
if user.IsAdmin() {
|
||||
kbd = tgbotapi.NewInlineKeyboardMarkup(
|
||||
rows := [][]tgbotapi.InlineKeyboardButton{}
|
||||
events, _ := bc.GetAllEvents()
|
||||
for _, event := range events {
|
||||
if event.Date.Sub(time.Now()) < 2*time.Hour {
|
||||
continue
|
||||
}
|
||||
k, _ := getDateButton(*event.Date)
|
||||
taken, _ := bc.CountReservationsByEventID(event.ID)
|
||||
k = strings.Join([]string{
|
||||
k,
|
||||
"(" + strconv.FormatInt(taken, 10) + "/" + strconv.Itoa(seatscnt) + ")",
|
||||
}, " ")
|
||||
if taken == seatscnt {
|
||||
k += " (Распродано)"
|
||||
}
|
||||
token := "reservedate:" + strconv.FormatInt(event.ID, 10)
|
||||
rows = append(rows,
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData(bc.GetBotContent("leave_ticket_button"), "leave_ticket_button"),
|
||||
),
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("Panel", "panel"),
|
||||
tgbotapi.NewInlineKeyboardButtonData(k, token),
|
||||
),
|
||||
)
|
||||
}
|
||||
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 == "" {
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, bc.GetBotContent("start"))
|
||||
msg.ParseMode = "markdown"
|
||||
msg.ReplyMarkup = kbd
|
||||
var entities []tgbotapi.MessageEntity
|
||||
meta, _ := bc.GetBotContentMetadata("start")
|
||||
json.Unmarshal([]byte(meta), &entities)
|
||||
msg.Entities = entities
|
||||
bc.bot.Send(msg)
|
||||
} else {
|
||||
msg := tgbotapi.NewPhoto(update.Message.Chat.ID, tgbotapi.FileID(img))
|
||||
msg.Caption = bc.GetBotContent("start")
|
||||
msg.ReplyMarkup = kbd
|
||||
var entities []tgbotapi.MessageEntity
|
||||
meta, _ := bc.GetBotContentMetadata("start")
|
||||
json.Unmarshal([]byte(meta), &entities)
|
||||
msg.CaptionEntities = entities
|
||||
bc.bot.Send(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSecretCommand(bc BotController, update tgbotapi.Update, user User) {
|
||||
bc.db.Model(&user).Update("state", "start")
|
||||
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)
|
||||
if update.Message.CommandArguments() == bc.cfg.AdminPass || user.IsAdmin() {
|
||||
bc.db.Model(&user).Update("state", "start")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePanelCommand(bc BotController, update tgbotapi.Update, user User) {
|
||||
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")),
|
||||
tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Ссылка на канал", "update:channel_link")),
|
||||
)
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Выберите пункт для изменения")
|
||||
msg.ReplyMarkup = kbd
|
||||
bc.bot.Send(msg)
|
||||
handlePanel(bc, user)
|
||||
}
|
||||
|
||||
func handleUserModeCommand(bc BotController, update tgbotapi.Update, user User) {
|
||||
@ -206,6 +296,27 @@ func handleUserModeCommand(bc BotController, update tgbotapi.Update, user User)
|
||||
bc.bot.Send(msg)
|
||||
}
|
||||
|
||||
func handleDeopCommand(bc BotController, update tgbotapi.Update, user User) {
|
||||
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask&(^uint(0b11)))
|
||||
log.Printf("Set role bitmask (%b) for user: %d", user.RoleBitmask, user.ID)
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "DeOPed you!")
|
||||
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
|
||||
@ -239,6 +350,19 @@ func handleDefaultMessage(bc BotController, update tgbotapi.Update, user User) {
|
||||
bc.db.Model(&user).Update("state", "start")
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, bc.GetBotContent("sended_notify"))
|
||||
bc.bot.Send(msg)
|
||||
} else if strings.HasPrefix(user.State, "enternamereservation:") {
|
||||
resstr := strings.Split(user.State, ":")[1]
|
||||
reservationid, _ := strconv.ParseInt(resstr, 10, 64)
|
||||
reservation, _ := bc.GetReservationByID(reservationid)
|
||||
reservation.EnteredName = update.Message.Text
|
||||
nd := time.Now().In(dubaiLocation)
|
||||
reservation.TimeBooked = &nd
|
||||
bc.UpdateReservation(reservation)
|
||||
|
||||
sendMessageKeyboard(bc, user.ID, bc.GetBotContent("ask_to_pay"),
|
||||
generateTgInlineKeyboard(map[string]string{"ТЕСТ оплачено": "paidcallback:" + strconv.FormatInt(reservationid, 10)}),
|
||||
)
|
||||
|
||||
} else if user.IsEffectiveAdmin() {
|
||||
if user.State != "start" {
|
||||
if strings.HasPrefix(user.State, "imgset:") {
|
||||
@ -246,7 +370,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 := ""
|
||||
@ -256,13 +380,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)
|
||||
@ -334,30 +462,12 @@ func handleAdminCallback(bc BotController, update tgbotapi.Update, user User) {
|
||||
} 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))"))
|
||||
bc.bot.Send(tgbotapi.NewMessage(user.ID, "Send me asset (text or picture (NOT as file)).\nSay `unset` to delete image.\nSay /start to cancel action"))
|
||||
}
|
||||
}
|
||||
|
||||
func handlePanelCallback(bc BotController, update tgbotapi.Update, user User) {
|
||||
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")),
|
||||
tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Ссылка на канал", "update:channel_link")),
|
||||
)
|
||||
msg := tgbotapi.NewMessage(user.ID, "Выберите пункт для изменения")
|
||||
msg.ReplyMarkup = kbd
|
||||
bc.bot.Send(msg)
|
||||
handlePanel(bc, user)
|
||||
}
|
||||
|
||||
func DownloadFile(filepath string, url string) error {
|
||||
@ -382,16 +492,67 @@ func DownloadFile(filepath string, url string) error {
|
||||
}
|
||||
|
||||
func notifyAdminAboutError(bc BotController, errorMessage string) {
|
||||
// admins := getAdmins(bc)
|
||||
// for _, admin := range admins {
|
||||
// bc.bot.Send(tgbotapi.NewMessage(admin.ID, "ChannelID is set to "+strconv.FormatInt(post.SenderChat.ID, 10)))
|
||||
// delcmd := tgbotapi.NewDeleteMessage(post.SenderChat.ID, post.MessageID)
|
||||
// bc.bot.Send(delcmd)
|
||||
// }
|
||||
// Check if AdminID is set in the config
|
||||
adminID := *bc.cfg.AdminID
|
||||
adminID := *bc.cfg.AdminID
|
||||
if adminID == 0 {
|
||||
log.Println("AdminID is not set in the configuration.")
|
||||
}
|
||||
|
||||
|
||||
msg := tgbotapi.NewMessage(
|
||||
adminID,
|
||||
fmt.Sprintf("Error occurred: %s", errorMessage),
|
||||
)
|
||||
bc.bot.Send(msg)
|
||||
}
|
||||
|
||||
func getAdmins(bc BotController) []User {
|
||||
var admins []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("reservedate:%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,
|
||||
}
|
||||
}
|
||||
|
||||
func notifyPaid(bc BotController, reservation Reservation) {
|
||||
chatidstr := bc.GetBotContent("supportchatid")
|
||||
chatid, _ := strconv.ParseInt(chatidstr, 10, 64)
|
||||
ui, _ := bc.GetUserInfo(reservation.UserID)
|
||||
event, _ := bc.GetEvent(reservation.EventID)
|
||||
msg := fmt.Sprintf(
|
||||
"Пользователь %s (%s) оплатил на %s",
|
||||
ui.FirstName,
|
||||
ui.Username,
|
||||
event.Date.Format("02.01 15:04"),
|
||||
)
|
||||
|
||||
bc.bot.Send(tgbotapi.NewMessage(chatid, msg))
|
||||
}
|
||||
|
||||
33
cmd/app/panel.go
Normal file
33
cmd/app/panel.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
var assets = map[string]string{
|
||||
"Стартовая картинка": "preview_image",
|
||||
"Приветственный текст": "start",
|
||||
"Кнопка для заявки": "leave_ticket_button",
|
||||
"ID чата": "supportchatid",
|
||||
"ID канала": "channelid",
|
||||
"Уведомление об отправке тикета": "sended_notify",
|
||||
"Просьба оставить тикет": "leaveticket_message",
|
||||
"Просьба подписаться на канал": "subscribe_message",
|
||||
"Ссылка на канал": "channel_link",
|
||||
"Подробнее о мероприятии": "more_info",
|
||||
"Текст о мероприятии": "more_info_text",
|
||||
"Текст: напоминание за 8 часов": "notify_pre_event",
|
||||
"Текст: забронированно и ввести имя": "reserved_message",
|
||||
"Текст: после имени на оплату": "ask_to_pay",
|
||||
"Текст: распродано": "soldout_message",
|
||||
"Текст: После оплаты": "post_payment_message",
|
||||
}
|
||||
|
||||
func handlePanel(bc BotController, user User) {
|
||||
if !user.IsAdmin() {
|
||||
return
|
||||
}
|
||||
if !user.IsEffectiveAdmin() {
|
||||
bc.db.Model(&user).Update("RoleBitmask", user.RoleBitmask|0b10)
|
||||
sendMessage(bc, user.ID, "You was in usermode, turned back to admin mode...")
|
||||
}
|
||||
m := Map(assets, func(v string) string { return "update:" + v })
|
||||
kbd := generateTgInlineKeyboard(m)
|
||||
sendMessageKeyboard(bc, user.ID, "Выберите пункт для изменения", kbd)
|
||||
}
|
||||
37
cmd/app/tghelper.go
Normal file
37
cmd/app/tghelper.go
Normal file
@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
func Map[K comparable, T, V any](ts map[K]T, fn func(T) V) map[K]V {
|
||||
result := make(map[K]V, len(ts))
|
||||
for k, t := range ts {
|
||||
result[k] = fn(t)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func generateTgInlineKeyboard(buttonsCallback map[string]string) tgbotapi.InlineKeyboardMarkup {
|
||||
rows := [][]tgbotapi.InlineKeyboardButton{}
|
||||
for k, v := range buttonsCallback {
|
||||
rows = append(rows,
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData(k, v),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return tgbotapi.NewInlineKeyboardMarkup(rows...)
|
||||
}
|
||||
|
||||
func sendMessage(bc BotController, UserID int64, Msg string) {
|
||||
msg := tgbotapi.NewMessage(UserID, Msg)
|
||||
bc.bot.Send(msg)
|
||||
}
|
||||
|
||||
func sendMessageKeyboard(bc BotController, UserID int64, Msg string, Kbd tgbotapi.InlineKeyboardMarkup) {
|
||||
msg := tgbotapi.NewMessage(UserID, Msg)
|
||||
msg.ReplyMarkup = Kbd
|
||||
bc.bot.Send(msg)
|
||||
}
|
||||
@ -8,18 +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
|
||||
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
|
||||
}
|
||||
|
||||
29
go.mod
29
go.mod
@ -1,14 +1,39 @@
|
||||
module github.com/akulij/ticketbot
|
||||
|
||||
go 1.22.2
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.15.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.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
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/api v0.228.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gorm.io/driver/sqlite v1.5.6 // indirect
|
||||
gorm.io/gorm v1.25.11 // indirect
|
||||
)
|
||||
|
||||
49
go.sum
49
go.sum
@ -1,5 +1,26 @@
|
||||
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
|
||||
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
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/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
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=
|
||||
@ -8,8 +29,36 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
|
||||
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=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
|
||||
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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=
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user