Files
worktimeBot/cmd/bot/main.go
db123 49c0e82bac refactor: migrate from go-telegram-bot-api/v5 to go-telegram/bot
- Replaced tgbotapi with go-telegram/bot (zero-dependency, context-aware, Bot API 10.0)
- All handler methods now accept context.Context; threading ctx through all sends/edits
- Changed from function-based (NewMessage/NewEditMessageText/NewInlineKeyboardMarkup) to struct-param API (SendMessageParams/EditMessageTextParams/InlineKeyboardMarkup)
- Added colored buttons: DEL buttons in calendar use Style: 'danger' (red)
- Both polling and webhook modes preserved with new library patterns
- Context-based shutdown (signal.NotifyContext) replaces stop channel
2026-06-24 14:56:50 +03:30

154 lines
3.4 KiB
Go

package main
import (
"context"
"log/slog"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
tgbot "github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
"github.com/joho/godotenv"
"worktimeBot/internal/bot"
"worktimeBot/internal/db"
)
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelInfo,
})))
godotenv.Load()
token := os.Getenv("BOT_TOKEN")
dbPath := getEnvDefault("DB_PATH", "db.sqlite3")
dbStore, err := db.NewStore(dbPath)
if err != nil {
slog.Error("failed to open database", "path", dbPath, "error", err)
os.Exit(1)
}
allowedUsers := parseAllowedUsers(os.Getenv("BOT_ALLOWED_USERS"))
var h *bot.Handler
dispatch := func(ctx context.Context, b *tgbot.Bot, update *models.Update) {
if update.Message != nil {
h.HandleMessage(ctx, update.Message)
}
if update.CallbackQuery != nil {
h.HandleCallback(ctx, update.CallbackQuery)
}
}
b, err := tgbot.New(token, tgbot.WithDefaultHandler(dispatch))
if err != nil {
slog.Error("failed to create bot", "error", err)
os.Exit(1)
}
h = bot.NewHandler(b, dbStore, allowedUsers)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
if url := os.Getenv("BOT_WEBHOOK_URL"); url != "" {
startWebhook(ctx, b, h, url)
} else {
b.DeleteWebhook(ctx, &tgbot.DeleteWebhookParams{})
go dailyReportScheduler(ctx, h, dbStore)
slog.Info("bot started",
"allowed_users", len(allowedUsers),
"mode", "polling",
)
b.Start(ctx)
}
dbStore.Close()
}
// dailyReportScheduler runs a periodic ticker that triggers daily report checks every 30 seconds.
func dailyReportScheduler(ctx context.Context, h *bot.Handler, store *db.Store) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
checkDailyReports(ctx, h, store)
}
}
}
// checkDailyReports iterates all users and sends a daily report to those whose report time has arrived.
func checkDailyReports(ctx context.Context, h *bot.Handler, store *db.Store) {
users, err := store.GetAllUsers()
if err != nil {
slog.Error("report scheduler: get users", "error", err)
return
}
now := time.Now().UTC()
for _, u := range users {
if !u.ReportEnabled || !u.IsActive {
continue
}
loc, err := time.LoadLocation(u.Timezone)
if err != nil {
continue
}
localNow := now.In(loc)
today := localNow.Format("2006-01-02")
currentHHMM := localNow.Format("15:04")
if currentHHMM != u.ReportTime {
continue
}
if u.LastReportDate != nil && *u.LastReportDate == today {
continue
}
slog.Info("sending daily report",
"user_id", u.ID, "chat_id", u.ChatID, "timezone", u.Timezone,
)
h.SendDailyReport(ctx, u.ID, u.ChatID)
}
}
// parseAllowedUsers parses a comma-separated list of Telegram user IDs into a set.
func parseAllowedUsers(raw string) map[int64]bool {
m := make(map[int64]bool)
for _, s := range strings.Split(raw, ",") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
id, err := strconv.ParseInt(s, 10, 64)
if err == nil {
m[id] = true
} else {
slog.Warn("invalid user ID in BOT_ALLOWED_USERS", "value", s)
}
}
return m
}
// getEnvDefault returns the environment variable value or a default if unset or empty.
func getEnvDefault(key, defaultValue string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultValue
}