feat(cli): enhance user and hook management with new commands and flags
This commit is contained in:
@@ -1,13 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"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"
|
||||
)
|
||||
|
||||
// accountsCmd is the parent command for account management
|
||||
var (
|
||||
accountUser string
|
||||
accountPhoneNumber string
|
||||
accountType string
|
||||
accountSessionPath string
|
||||
accountDisplayName string
|
||||
)
|
||||
|
||||
var accountsCmd = &cobra.Command{
|
||||
Use: "accounts",
|
||||
Short: "Manage WhatsApp accounts",
|
||||
@@ -35,17 +50,47 @@ var accountsAddCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var accountsRemoveCmd = &cobra.Command{
|
||||
Use: "remove <id>",
|
||||
Short: "Remove a WhatsApp account by ID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
client := NewClient(cliConfig)
|
||||
removeAccount(client, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
accountsAddCmd.Flags().StringVarP(&accountPhoneNumber, "phone", "p", "", "Phone number (with country code)")
|
||||
accountsAddCmd.Flags().StringVarP(&accountType, "type", "t", "whatsmeow", "Account type (whatsmeow/business-api)")
|
||||
accountsAddCmd.Flags().StringVarP(&accountSessionPath, "session-path", "s", "", "Session path (auto-generated if omitted)")
|
||||
accountsAddCmd.Flags().StringVarP(&accountDisplayName, "display-name", "d", "", "Display name")
|
||||
accountsAddCmd.Flags().StringVarP(&accountUser, "user", "u", "", "Owner username for DB mode (default: first admin)")
|
||||
|
||||
accountsCmd.AddCommand(accountsListCmd)
|
||||
accountsCmd.AddCommand(accountsAddCmd)
|
||||
accountsCmd.AddCommand(accountsRemoveCmd)
|
||||
}
|
||||
|
||||
func listAccounts(client *Client) {
|
||||
if serverAvailable(client) {
|
||||
listAccountsHTTP(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
|
||||
}
|
||||
listAccountsDB()
|
||||
}
|
||||
}
|
||||
|
||||
func listAccountsHTTP(client *Client) {
|
||||
resp, err := client.Get("/api/accounts")
|
||||
checkError(err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
var accounts []config.WhatsAppConfig
|
||||
var accounts []map[string]interface{}
|
||||
checkError(decodeJSON(resp, &accounts))
|
||||
|
||||
if len(accounts) == 0 {
|
||||
@@ -53,37 +98,140 @@ func listAccounts(client *Client) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Configured accounts (%d):\n\n", len(accounts))
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tPHONE\tTYPE\tSTATUS\tACTIVE")
|
||||
for _, acc := range accounts {
|
||||
fmt.Printf("ID: %s\n", acc.ID)
|
||||
fmt.Printf("Phone Number: %s\n", acc.PhoneNumber)
|
||||
fmt.Printf("Session Path: %s\n", acc.SessionPath)
|
||||
fmt.Println()
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n",
|
||||
acc["id"], acc["phone_number"], acc["account_type"], acc["status"], acc["active"])
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func listAccountsDB() {
|
||||
var accounts []models.ModelPublicWhatsappAccount
|
||||
err := storage.DB.NewSelect().Model(&accounts).OrderExpr("created_at ASC").Scan(context.Background())
|
||||
checkError(err)
|
||||
|
||||
if len(accounts) == 0 {
|
||||
fmt.Println("No accounts configured")
|
||||
return
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tPHONE\tTYPE\tSTATUS\tACTIVE")
|
||||
for _, acc := range accounts {
|
||||
active := "yes"
|
||||
if !acc.Active {
|
||||
active = "no"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
|
||||
acc.ID.String(),
|
||||
acc.PhoneNumber.String(),
|
||||
acc.AccountType.String(),
|
||||
acc.Status.String(),
|
||||
active,
|
||||
)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func addAccount(client *Client) {
|
||||
var account config.WhatsAppConfig
|
||||
|
||||
fmt.Print("Account ID: ")
|
||||
if _, err := fmt.Scanln(&account.ID); err != nil {
|
||||
checkError(fmt.Errorf("error reading account ID: %v", err))
|
||||
phone := accountPhoneNumber
|
||||
if phone == "" {
|
||||
phone = promptRequired("Phone Number (with country code)")
|
||||
}
|
||||
|
||||
fmt.Print("Phone Number (with country code): ")
|
||||
if _, err := fmt.Scanln(&account.PhoneNumber); err != nil {
|
||||
checkError(fmt.Errorf("error reading phone number: %v", err))
|
||||
acctType := accountType
|
||||
if acctType == "" {
|
||||
acctType = promptLine("Account type (whatsmeow/business-api)", "whatsmeow")
|
||||
}
|
||||
|
||||
fmt.Print("Session Path: ")
|
||||
if _, err := fmt.Scanln(&account.SessionPath); err != nil {
|
||||
checkError(fmt.Errorf("error reading session path: %v", err))
|
||||
displayName := accountDisplayName
|
||||
if displayName == "" {
|
||||
displayName = promptLine("Display name (optional)", "")
|
||||
}
|
||||
|
||||
resp, err := client.Post("/api/accounts/add", account)
|
||||
if serverAvailable(client) {
|
||||
addAccountHTTP(client, phone, acctType, displayName)
|
||||
} 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
|
||||
}
|
||||
addAccountDB(phone, acctType, displayName)
|
||||
}
|
||||
}
|
||||
|
||||
func addAccountHTTP(client *Client, phone, acctType, displayName string) {
|
||||
payload := map[string]interface{}{
|
||||
"phone_number": phone,
|
||||
"account_type": acctType,
|
||||
"display_name": displayName,
|
||||
}
|
||||
resp, err := client.Post("/api/accounts/add", payload)
|
||||
checkError(err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Println("Account added successfully")
|
||||
fmt.Println("Check server logs for QR code to pair the device")
|
||||
}
|
||||
|
||||
func addAccountDB(phone, acctType, displayName string) {
|
||||
userID := dbOwnerUserID(accountUser)
|
||||
if userID == "" {
|
||||
fmt.Println("Error: no users found in database. Create a user first with: users add")
|
||||
return
|
||||
}
|
||||
|
||||
id := uuid.New().String()
|
||||
sessionPath := accountSessionPath
|
||||
if sessionPath == "" {
|
||||
sessionPath = fmt.Sprintf("./sessions/%s", id)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
var cfgJSON string
|
||||
if acctType == "business-api" {
|
||||
b, _ := json.Marshal(map[string]string{})
|
||||
cfgJSON = string(b)
|
||||
}
|
||||
|
||||
account := &models.ModelPublicWhatsappAccount{
|
||||
ID: resolvespec_common.NewSqlString(id),
|
||||
PhoneNumber: resolvespec_common.NewSqlString(phone),
|
||||
AccountType: resolvespec_common.NewSqlString(acctType),
|
||||
DisplayName: resolvespec_common.NewSqlString(displayName),
|
||||
SessionPath: resolvespec_common.NewSqlString(sessionPath),
|
||||
Config: resolvespec_common.NewSqlString(cfgJSON),
|
||||
Status: resolvespec_common.NewSqlString("disconnected"),
|
||||
UserID: resolvespec_common.NewSqlString(userID),
|
||||
Active: true,
|
||||
CreatedAt: resolvespec_common.NewSqlTimeStamp(now),
|
||||
UpdatedAt: resolvespec_common.NewSqlTimeStamp(now),
|
||||
}
|
||||
|
||||
repo := storage.NewWhatsAppAccountRepository(storage.DB)
|
||||
checkError(repo.Create(context.Background(), account))
|
||||
|
||||
fmt.Printf("Account '%s' added (ID: %s)\n", phone, id)
|
||||
fmt.Println("Start the server to connect and pair the device")
|
||||
}
|
||||
|
||||
func removeAccount(client *Client, id string) {
|
||||
if serverAvailable(client) {
|
||||
resp, err := client.Post("/api/accounts/remove", map[string]string{"id": id})
|
||||
checkError(err)
|
||||
defer resp.Body.Close()
|
||||
fmt.Println("Account 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.NewWhatsAppAccountRepository(storage.DB)
|
||||
checkError(repo.Delete(context.Background(), id))
|
||||
fmt.Printf("Account '%s' removed\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user