package main import ( "context" "database/sql" "errors" "fmt" "log/slog" "os" "os/signal" "strconv" "strings" "syscall" "time" "monitoring-pf/internal/pf" ) func main() { cfg := pf.LoadConfig() logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) slog.SetDefault(logger) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() app, err := pf.OpenApp(ctx, cfg) if err != nil { slog.Error("db_open_failed", "error", err) os.Exit(1) } defer app.Close() if !app.TG.Enabled() { slog.Error("telegram_token_missing") os.Exit(1) } slog.Info("monitoring_pf_go_bot_started") var offset int64 for ctx.Err() == nil { updates, err := app.TG.GetUpdates(ctx, offset) if err != nil { slog.Warn("telegram_get_updates_failed", "error", err) time.Sleep(3 * time.Second) continue } for _, update := range updates { offset = update.UpdateID + 1 if update.Message != nil { handleMessage(ctx, app, update.Message) } } } } func handleMessage(ctx context.Context, app *pf.App, msg *pf.TGMessage) { text := strings.TrimSpace(msg.Text) if text == "" || !strings.HasPrefix(text, "/") { return } command, arg := splitCommand(text) switch command { case "/start": handleStart(ctx, app, msg, arg) case "/whoami": handleWhoami(ctx, app, msg) case "/list": handleList(ctx, app, msg) case "/check": handleCheck(ctx, app, msg) } } func splitCommand(text string) (string, string) { parts := strings.Fields(text) if len(parts) == 0 { return "", "" } command := strings.Split(parts[0], "@")[0] arg := "" if len(parts) > 1 { arg = parts[1] } return command, arg } func handleStart(ctx context.Context, app *pf.App, msg *pf.TGMessage, portalUserID string) { chatID := strconv.FormatInt(msg.Chat.ID, 10) user := msg.From username := "" name := "user_" + chatID if user != nil { username = user.Username name = user.FullName() } if portalUserID == "" { _ = app.TG.SendMessage(ctx, chatID, "Откройте Portal → Мониторинг PF и нажмите подключение Telegram.\n"+ "Бот должен получить команду вида:\n/start ваш_код_из_Portal") return } emp, err := app.LinkTelegram(ctx, portalUserID, chatID, username, name) if err != nil { _ = app.TG.SendMessage(ctx, chatID, "Не удалось подключить Telegram: "+err.Error()) return } _ = app.TG.SendMessage(ctx, chatID, fmt.Sprintf("✅ Привет, %s! Telegram подключен к вашему аккаунту Portal.\nТеперь можно добавлять объекты мониторинга в Portal.", emp.Name)) } func handleWhoami(ctx context.Context, app *pf.App, msg *pf.TGMessage) { chatID := strconv.FormatInt(msg.Chat.ID, 10) emp, err := app.EmployeeByChatID(ctx, chatID) if errors.Is(err, sql.ErrNoRows) { _ = app.TG.SendMessage(ctx, chatID, "Вы пока не подключены. Откройте Portal → Мониторинг PF и запустите подключение.\nchat_id: "+chatID+"") return } if err != nil { _ = app.TG.SendMessage(ctx, chatID, "Ошибка: "+err.Error()) return } _ = app.TG.SendMessage(ctx, chatID, fmt.Sprintf("Вы: %s\nchat_id: %s", emp.Name, chatID)) } func handleList(ctx context.Context, app *pf.App, msg *pf.TGMessage) { chatID := strconv.FormatInt(msg.Chat.ID, 10) emp, err := app.EmployeeByChatID(ctx, chatID) if errors.Is(err, sql.ErrNoRows) { _ = app.TG.SendMessage(ctx, chatID, "Сначала подключитесь через Portal → Мониторинг PF.") return } if err != nil { _ = app.TG.SendMessage(ctx, chatID, "Ошибка: "+err.Error()) return } projects, err := app.ListProjects(ctx, emp.ID) if err != nil { _ = app.TG.SendMessage(ctx, chatID, "Ошибка: "+err.Error()) return } if len(projects) == 0 { _ = app.TG.SendMessage(ctx, chatID, "У вас пока нет проектов.") return } lines := []string{fmt.Sprintf("Ваши проекты (%d):", len(projects))} for _, p := range projects { permit := "—" if p.DLDPermit != nil { permit = *p.DLDPermit } lines = append(lines, fmt.Sprintf("• #%d %s — %s (%s)", p.ID, p.Title, permit, p.DealType)) } _ = app.TG.SendMessage(ctx, chatID, strings.Join(lines, "\n")) } func handleCheck(ctx context.Context, app *pf.App, msg *pf.TGMessage) { chatID := strconv.FormatInt(msg.Chat.ID, 10) emp, err := app.EmployeeByChatID(ctx, chatID) if errors.Is(err, sql.ErrNoRows) { _ = app.TG.SendMessage(ctx, chatID, "Сначала подключитесь через Portal → Мониторинг PF.") return } if err != nil { _ = app.TG.SendMessage(ctx, chatID, "Ошибка: "+err.Error()) return } projects, err := app.ListProjects(ctx, emp.ID) if err != nil { _ = app.TG.SendMessage(ctx, chatID, "Ошибка: "+err.Error()) return } if len(projects) == 0 { _ = app.TG.SendMessage(ctx, chatID, "У вас нет проектов.") return } _ = app.TG.SendMessage(ctx, chatID, fmt.Sprintf("⏳ Запускаю проверку %d проектов…", len(projects))) total := 0 for _, p := range projects { changes, err := app.Worker.CheckProject(ctx, p.ID) if err != nil { slog.Warn("check_project_failed", "project_id", p.ID, "error", err) continue } total += changes } _ = app.TG.SendMessage(ctx, chatID, fmt.Sprintf("✅ Готово. Изменений: %d", total)) }