130 lines
3.8 KiB
Go
130 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"regexp"
|
|
"syscall"
|
|
|
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
"github.com/paramah/gw_telegram/internal/application/usecase"
|
|
"github.com/paramah/gw_telegram/internal/config"
|
|
"github.com/paramah/gw_telegram/internal/domain/entity"
|
|
"github.com/paramah/gw_telegram/internal/infrastructure/n8n"
|
|
"github.com/paramah/gw_telegram/internal/infrastructure/router"
|
|
"github.com/paramah/gw_telegram/internal/infrastructure/speech"
|
|
"github.com/paramah/gw_telegram/internal/infrastructure/storage"
|
|
infratelegram "github.com/paramah/gw_telegram/internal/infrastructure/telegram"
|
|
"github.com/paramah/gw_telegram/internal/interfaces"
|
|
)
|
|
|
|
func main() {
|
|
cfg := config.MustLoad()
|
|
logger := newLogger(cfg.Log)
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
// Telegram bot
|
|
bot, err := tgbotapi.NewBotAPI(cfg.Bot.Token)
|
|
if err != nil {
|
|
logger.Error("failed to create telegram bot", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
bot.Debug = cfg.Bot.Debug
|
|
logger.Info("telegram bot authorized", "username", bot.Self.UserName)
|
|
|
|
// Redis
|
|
redisOpts, err := redis.ParseURL(cfg.Redis.URL)
|
|
if err != nil {
|
|
logger.Error("invalid redis url", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
redisClient := redis.NewClient(redisOpts)
|
|
|
|
// Infrastructure
|
|
gateway := infratelegram.NewBotGateway(bot, logger)
|
|
downloader := infratelegram.NewTelegramFileDownloader(bot)
|
|
sessionStore := storage.NewRedisSessionStore(redisClient, cfg.Redis.TTLHours)
|
|
|
|
workflows := map[string]n8n.WorkflowConfig{
|
|
"default": {
|
|
ID: "default",
|
|
WebhookURL: cfg.N8n.WebhookURL,
|
|
AuthToken: cfg.N8n.AuthToken,
|
|
},
|
|
}
|
|
dispatcher := n8n.NewWebhookDispatcher(workflows, logger)
|
|
|
|
// Intent router with default rules
|
|
rules := []router.Rule{
|
|
{
|
|
Pattern: regexp.MustCompile(`(?i)^/start`),
|
|
IntentName: "start",
|
|
Target: entity.RouteTarget{Type: entity.RouteTargetBuiltin, WorkflowID: "start"},
|
|
Priority: 100,
|
|
},
|
|
{
|
|
Pattern: regexp.MustCompile(`(?i)^/help`),
|
|
IntentName: "help",
|
|
Target: entity.RouteTarget{Type: entity.RouteTargetBuiltin, WorkflowID: "help"},
|
|
Priority: 100,
|
|
},
|
|
{
|
|
Pattern: regexp.MustCompile(`.*`),
|
|
IntentName: "general_query",
|
|
Target: entity.RouteTarget{Type: entity.RouteTargetN8n, WorkflowID: "default"},
|
|
Priority: 0,
|
|
},
|
|
}
|
|
intentRouter := router.NewRuleBasedRouter(rules)
|
|
|
|
// Speech
|
|
transcriber := speech.NewOpenAIWhisper(cfg.Speech.OpenAIKey, cfg.Speech.WhisperModel, cfg.Speech.Language)
|
|
converter := speech.NewFFmpegConverter(cfg.Speech.FFmpegPath, "")
|
|
voiceFileStore, err := speech.NewLocalVoiceFileStore(cfg.Speech.VoiceStorePath)
|
|
if err != nil {
|
|
logger.Error("failed to initialise voice file store", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Use cases
|
|
textUC := usecase.NewHandleTextMessage(intentRouter, dispatcher, sessionStore, gateway, logger)
|
|
voiceUC := usecase.NewHandleVoiceMessage(downloader, converter, transcriber, voiceFileStore, textUC, gateway, logger)
|
|
|
|
// Handler + poller
|
|
handler := interfaces.NewTelegramHandler(textUC, voiceUC, logger)
|
|
poller := infratelegram.NewUpdatePoller(bot, handler, logger)
|
|
|
|
logger.Info("starting bot", "mode", cfg.Bot.Mode)
|
|
if err := poller.Start(ctx); err != nil && err != context.Canceled {
|
|
logger.Error("poller stopped with error", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
logger.Info("bot stopped gracefully")
|
|
}
|
|
|
|
func newLogger(cfg config.LogConfig) *slog.Logger {
|
|
var level slog.Level
|
|
switch cfg.Level {
|
|
case "debug":
|
|
level = slog.LevelDebug
|
|
case "warn":
|
|
level = slog.LevelWarn
|
|
case "error":
|
|
level = slog.LevelError
|
|
default:
|
|
level = slog.LevelInfo
|
|
}
|
|
opts := &slog.HandlerOptions{Level: level}
|
|
if cfg.Format == "text" {
|
|
return slog.New(slog.NewTextHandler(os.Stdout, opts))
|
|
}
|
|
return slog.New(slog.NewJSONHandler(os.Stdout, opts))
|
|
}
|