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 func() {
if err := app.Close(); err != nil {
slog.Warn("app_close_failed", "error", err)
}
}()
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))
}