Major refactor to library
This commit is contained in:
@@ -4,37 +4,20 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/internal/config"
|
||||
"git.warky.dev/wdevs/whatshooked/internal/eventlogger"
|
||||
"git.warky.dev/wdevs/whatshooked/internal/events"
|
||||
"git.warky.dev/wdevs/whatshooked/internal/hooks"
|
||||
"git.warky.dev/wdevs/whatshooked/internal/logging"
|
||||
"git.warky.dev/wdevs/whatshooked/internal/utils"
|
||||
"git.warky.dev/wdevs/whatshooked/internal/whatsapp"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/logging"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/whatshooked"
|
||||
)
|
||||
|
||||
var (
|
||||
configPath = flag.String("config", "", "Path to configuration file (optional, defaults to user home directory)")
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
config *config.Config
|
||||
configPath string
|
||||
whatsappMgr *whatsapp.Manager
|
||||
hookMgr *hooks.Manager
|
||||
httpServer *http.Server
|
||||
eventBus *events.EventBus
|
||||
eventLogger *eventlogger.Logger
|
||||
}
|
||||
|
||||
// resolveConfigPath determines the config file path to use
|
||||
// Priority: 1) provided path (if exists), 2) config.json in current dir, 3) .whatshooked/config.json in user home
|
||||
func resolveConfigPath(providedPath string) (string, error) {
|
||||
@@ -44,7 +27,7 @@ func resolveConfigPath(providedPath string) (string, error) {
|
||||
return providedPath, nil
|
||||
}
|
||||
// Directory doesn't exist, fall through to default locations
|
||||
logging.Info("Provided config path directory does not exist, using default locations", "path", providedPath)
|
||||
fmt.Fprintf(os.Stderr, "Provided config path directory does not exist, using default locations: %s\n", providedPath)
|
||||
}
|
||||
|
||||
// Check for config.json in current directory
|
||||
@@ -78,70 +61,21 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
cfg, err := config.Load(cfgPath)
|
||||
// Create WhatsHooked instance from config file
|
||||
wh, err := whatshooked.NewFromFile(cfgPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to load config from %s: %v\n", cfgPath, err)
|
||||
fmt.Fprintf(os.Stderr, "Failed to initialize WhatsHooked from %s: %v\n", cfgPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Initialize logging
|
||||
logging.Init(cfg.LogLevel)
|
||||
logging.Info("Starting WhatsHooked server", "config_path", cfgPath)
|
||||
|
||||
// Create event bus
|
||||
eventBus := events.NewEventBus()
|
||||
|
||||
// Create server with config update callback
|
||||
srv := &Server{
|
||||
config: cfg,
|
||||
configPath: cfgPath,
|
||||
eventBus: eventBus,
|
||||
whatsappMgr: whatsapp.NewManager(eventBus, cfg.Media, cfg, cfgPath, func(updatedCfg *config.Config) error {
|
||||
return config.Save(cfgPath, updatedCfg)
|
||||
}),
|
||||
hookMgr: hooks.NewManager(eventBus),
|
||||
}
|
||||
|
||||
// Initialize event logger if enabled
|
||||
if cfg.EventLogger.Enabled && len(cfg.EventLogger.Targets) > 0 {
|
||||
evtLogger, err := eventlogger.NewLogger(cfg.EventLogger, cfg.Database)
|
||||
if err != nil {
|
||||
logging.Error("Failed to initialize event logger", "error", err)
|
||||
} else {
|
||||
srv.eventLogger = evtLogger
|
||||
// Subscribe to all events
|
||||
srv.eventBus.SubscribeAll(func(event events.Event) {
|
||||
srv.eventLogger.Log(event)
|
||||
})
|
||||
logging.Info("Event logger initialized", "targets", cfg.EventLogger.Targets)
|
||||
// Start the built-in HTTP server (non-blocking goroutine)
|
||||
go func() {
|
||||
if err := wh.StartServer(); err != nil {
|
||||
logging.Error("HTTP server error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load hooks
|
||||
srv.hookMgr.LoadHooks(cfg.Hooks)
|
||||
|
||||
// Start hook manager to listen for events
|
||||
srv.hookMgr.Start()
|
||||
|
||||
// Subscribe to hook success events to handle webhook responses
|
||||
srv.eventBus.Subscribe(events.EventHookSuccess, srv.handleHookResponse)
|
||||
|
||||
// Start HTTP server for CLI BEFORE connecting to WhatsApp
|
||||
// This ensures all infrastructure is ready before events start flowing
|
||||
srv.startHTTPServer()
|
||||
|
||||
// Give HTTP server a moment to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
logging.Info("HTTP server ready, connecting to WhatsApp accounts")
|
||||
|
||||
// Connect to WhatsApp accounts
|
||||
ctx := context.Background()
|
||||
for _, waCfg := range cfg.WhatsApp {
|
||||
if err := srv.whatsappMgr.Connect(ctx, waCfg); err != nil {
|
||||
logging.Error("Failed to connect to WhatsApp", "account_id", waCfg.ID, "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for interrupt signal
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
@@ -154,66 +88,15 @@ func main() {
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if srv.httpServer != nil {
|
||||
srv.httpServer.Shutdown(shutdownCtx)
|
||||
// Stop server
|
||||
if err := wh.StopServer(shutdownCtx); err != nil {
|
||||
logging.Error("Error stopping server", "error", err)
|
||||
}
|
||||
|
||||
srv.whatsappMgr.DisconnectAll()
|
||||
|
||||
// Close event logger
|
||||
if srv.eventLogger != nil {
|
||||
if err := srv.eventLogger.Close(); err != nil {
|
||||
logging.Error("Failed to close event logger", "error", err)
|
||||
}
|
||||
// Close WhatsHooked (disconnects WhatsApp, closes event logger, etc.)
|
||||
if err := wh.Close(); err != nil {
|
||||
logging.Error("Error closing WhatsHooked", "error", err)
|
||||
}
|
||||
|
||||
logging.Info("Server stopped")
|
||||
}
|
||||
|
||||
// handleHookResponse processes hook success events to handle two-way communication
|
||||
func (s *Server) handleHookResponse(event events.Event) {
|
||||
// Use event context for sending message
|
||||
ctx := event.Context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
// Extract response from event data
|
||||
responseData, ok := event.Data["response"]
|
||||
if !ok || responseData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to cast to HookResponse
|
||||
resp, ok := responseData.(hooks.HookResponse)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !resp.SendMessage {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine which account to use - default to first available if not specified
|
||||
targetAccountID := resp.AccountID
|
||||
if targetAccountID == "" && len(s.config.WhatsApp) > 0 {
|
||||
targetAccountID = s.config.WhatsApp[0].ID
|
||||
}
|
||||
|
||||
// Format phone number to JID format
|
||||
formattedJID := utils.FormatPhoneToJID(resp.To, s.config.Server.DefaultCountryCode)
|
||||
|
||||
// Parse JID
|
||||
jid, err := types.ParseJID(formattedJID)
|
||||
if err != nil {
|
||||
logging.Error("Invalid JID in hook response", "jid", formattedJID, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send message with context
|
||||
if err := s.whatsappMgr.SendTextMessage(ctx, targetAccountID, jid, resp.Text); err != nil {
|
||||
logging.Error("Failed to send message from hook response", "error", err)
|
||||
} else {
|
||||
logging.Info("Message sent from hook response", "account_id", targetAccountID, "to", resp.To)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user