package main import ( "context" "encoding/json" "fmt" "os" "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 ( accountUser string accountPhoneNumber string accountType string accountSessionPath string accountDisplayName string ) var accountsCmd = &cobra.Command{ Use: "accounts", Short: "Manage WhatsApp accounts", Run: func(cmd *cobra.Command, args []string) { client := NewClient(cliConfig) listAccounts(client) }, } var accountsListCmd = &cobra.Command{ Use: "list", Short: "List all accounts", Run: func(cmd *cobra.Command, args []string) { client := NewClient(cliConfig) listAccounts(client) }, } var accountsAddCmd = &cobra.Command{ Use: "add", Short: "Add a new WhatsApp account", Run: func(cmd *cobra.Command, args []string) { client := NewClient(cliConfig) addAccount(client) }, } var accountsRemoveCmd = &cobra.Command{ Use: "remove ", 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 []map[string]interface{} checkError(decodeJSON(resp, &accounts)) 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 { 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) { phone := accountPhoneNumber if phone == "" { phone = promptRequired("Phone Number (with country code)") } acctType := accountType if acctType == "" { acctType = promptLine("Account type (whatsmeow/business-api)", "whatsmeow") } displayName := accountDisplayName if displayName == "" { displayName = promptLine("Display name (optional)", "") } 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) } }