feat: initial commit
This commit is contained in:
125
cmd/bot/main.go
Normal file
125
cmd/bot/main.go
Normal file
@@ -0,0 +1,125 @@
|
||||
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)
|
||||
|
||||
// n8n workflows (configure via env: N8N_WORKFLOW_<INTENT>=<url>)
|
||||
workflows := map[string]n8n.WorkflowConfig{
|
||||
"default": {
|
||||
ID: "default",
|
||||
WebhookURL: cfg.N8n.BaseURL + "/webhook/default",
|
||||
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, "")
|
||||
|
||||
// Use cases
|
||||
textUC := usecase.NewHandleTextMessage(intentRouter, dispatcher, sessionStore, gateway, logger)
|
||||
voiceUC := usecase.NewHandleVoiceMessage(downloader, converter, transcriber, 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))
|
||||
}
|
||||
Reference in New Issue
Block a user