package main import ( "context" "encoding/json" "fmt" "os" "strings" "text/tabwriter" "time" "git.warky.dev/wdevs/whatshooked/pkg/models" "git.warky.dev/wdevs/whatshooked/pkg/storage" resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" "github.com/google/uuid" "github.com/spf13/cobra" ) var hookUser string var hooksCmd = &cobra.Command{ Use: "hooks", Short: "Manage webhooks", Run: func(cmd *cobra.Command, args []string) { client := NewClient(cliConfig) listHooks(client) }, } var hooksListCmd = &cobra.Command{ Use: "list", Short: "List all hooks", Run: func(cmd *cobra.Command, args []string) { client := NewClient(cliConfig) listHooks(client) }, } var hooksAddCmd = &cobra.Command{ Use: "add", Short: "Add a new hook", Run: func(cmd *cobra.Command, args []string) { client := NewClient(cliConfig) addHook(client) }, } var hooksRemoveCmd = &cobra.Command{ Use: "remove ", Short: "Remove a hook by ID", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { client := NewClient(cliConfig) removeHook(client, args[0]) }, } func init() { hooksAddCmd.Flags().StringVarP(&hookUser, "user", "u", "", "Owner username for DB mode (default: first admin)") hooksCmd.AddCommand(hooksListCmd) hooksCmd.AddCommand(hooksAddCmd) hooksCmd.AddCommand(hooksRemoveCmd) } func listHooks(client *Client) { if serverAvailable(client) { listHooksHTTP(client) } else { fmt.Println("[server unavailable, reading from database]") if !tryInitDB() { fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.") return } listHooksDB() } } func listHooksHTTP(client *Client) { resp, err := client.Get("/api/hooks") checkError(err) defer resp.Body.Close() var hooks []map[string]interface{} checkError(decodeJSON(resp, &hooks)) if len(hooks) == 0 { fmt.Println("No hooks configured") return } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "ID\tNAME\tURL\tMETHOD\tACTIVE") for _, h := range hooks { fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n", h["id"], h["name"], h["url"], h["method"], h["active"]) } w.Flush() } func listHooksDB() { var hooks []models.ModelPublicHook err := storage.DB.NewSelect().Model(&hooks).OrderExpr("created_at ASC").Scan(context.Background()) checkError(err) if len(hooks) == 0 { fmt.Println("No hooks configured") return } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "ID\tNAME\tURL\tMETHOD\tACTIVE\tEVENTS") for _, h := range hooks { active := "yes" if !h.Active { active = "no" } events := parseEventsJSON(h.Events.String()) fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", h.ID.String(), h.Name.String(), h.URL.String(), h.Method.String(), active, events, ) } w.Flush() } func addHook(client *Client) { name := promptRequired("Hook Name") url := promptRequired("Webhook URL") method := promptLine("HTTP Method", "POST") fmt.Println("\nAvailable events:") fmt.Println(" whatsapp.connected, whatsapp.disconnected, whatsapp.qr.code") fmt.Println(" message.received, message.sent, message.delivered, message.read") fmt.Println(" hook.triggered, hook.success, hook.failed") eventsRaw := promptLine("\nEvents (comma-separated, or Enter for all)", "") description := promptLine("Description (optional)", "") var events []string if eventsRaw != "" { for _, e := range strings.Split(eventsRaw, ",") { if t := strings.TrimSpace(e); t != "" { events = append(events, t) } } } if serverAvailable(client) { addHookHTTP(client, name, url, method, description, events) } else { fmt.Println("[server unavailable, writing to database]") if !tryInitDB() { fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.") return } addHookDB(name, url, method, description, events) } } func addHookHTTP(client *Client, name, url, method, description string, events []string) { payload := map[string]interface{}{ "name": name, "url": url, "method": method, "description": description, "events": events, "active": true, } resp, err := client.Post("/api/hooks/add", payload) checkError(err) defer resp.Body.Close() fmt.Println("Hook added successfully") } func addHookDB(name, url, method, description string, events []string) { userID := dbOwnerUserID(hookUser) if userID == "" { fmt.Println("Error: no users found in database. Create a user first with: users add") return } eventsJSON := "[]" if len(events) > 0 { b, _ := json.Marshal(events) eventsJSON = string(b) } id := uuid.New().String() now := time.Now() hook := &models.ModelPublicHook{ ID: resolvespec_common.NewSqlString(id), Name: resolvespec_common.NewSqlString(name), URL: resolvespec_common.NewSqlString(url), Method: resolvespec_common.NewSqlString(method), Description: resolvespec_common.NewSqlString(description), Events: resolvespec_common.NewSqlString(eventsJSON), UserID: resolvespec_common.NewSqlString(userID), Active: true, CreatedAt: resolvespec_common.NewSqlTimeStamp(now), UpdatedAt: resolvespec_common.NewSqlTimeStamp(now), } repo := storage.NewHookRepository(storage.DB) checkError(repo.Create(context.Background(), hook)) fmt.Printf("Hook '%s' added (ID: %s)\n", name, id) } func removeHook(client *Client, id string) { if serverAvailable(client) { resp, err := client.Post("/api/hooks/remove", map[string]string{"id": id}) checkError(err) defer resp.Body.Close() fmt.Println("Hook removed successfully") } else { fmt.Println("[server unavailable, removing from database]") if !tryInitDB() { fmt.Println("Error: server unreachable and no database config found. Use --server-config to specify config path.") return } repo := storage.NewHookRepository(storage.DB) checkError(repo.Delete(context.Background(), id)) fmt.Printf("Hook '%s' removed\n", id) } } // parseEventsJSON parses a JSON events string into a comma-separated display string. func parseEventsJSON(raw string) string { if raw == "" || raw == "[]" { return "all" } var events []string if err := json.Unmarshal([]byte(raw), &events); err != nil { return raw } return strings.Join(events, ", ") }