Files
Hein 4b44340c58
Some checks failed
CI / Test (1.22) (push) Failing after -30m28s
CI / Lint (push) Failing after -30m32s
CI / Build (push) Failing after -30m31s
CI / Test (1.23) (push) Failing after -30m31s
More management tools
2026-03-04 22:30:40 +02:00

174 lines
5.3 KiB
Go

package businessapi
import (
"context"
"net/url"
"git.warky.dev/wdevs/whatshooked/pkg/events"
"git.warky.dev/wdevs/whatshooked/pkg/logging"
"go.mau.fi/whatsmeow/types"
)
// ListCatalogs returns all product catalogs linked to the business account.
func (c *Client) ListCatalogs(ctx context.Context) (*CatalogListResponse, error) {
wabaID, err := c.resolveWABAID(ctx)
if err != nil {
return nil, err
}
params := url.Values{
"fields": {"id,name,product_count"},
}
var resp CatalogListResponse
if err := c.graphAPIGet(ctx, wabaID+"/product_catalogs", params, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ListProducts returns products in a specific catalog.
func (c *Client) ListProducts(ctx context.Context, catalogID string) (*ProductListResponse, error) {
params := url.Values{
"fields": {"product_retailer_id,name,description,image_url,base_price,currency,availability,category"},
}
var resp ProductListResponse
if err := c.graphAPIGet(ctx, catalogID+"/products", params, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// SendCatalogMessage sends a catalog message that shares the full product catalog.
// thumbnailProductRetailerID is optional — when non-empty it sets which product image
// appears as the catalog preview thumbnail.
func (c *Client) SendCatalogMessage(ctx context.Context, jid types.JID, bodyText string, thumbnailProductRetailerID string) (string, error) {
if ctx == nil {
ctx = context.Background()
}
phoneNumber := jidToPhoneNumber(jid)
action := map[string]any{
"name": "catalog_message",
}
if thumbnailProductRetailerID != "" {
action["parameters"] = map[string]any{
"thumbnail_product_retailer_id": thumbnailProductRetailerID,
}
}
msg := map[string]any{
"messaging_product": "whatsapp",
"to": phoneNumber,
"type": "interactive",
"interactive": map[string]any{
"type": "catalog_message",
"body": map[string]any{"text": bodyText},
"action": action,
},
}
messageID, err := c.postToMessagesEndpoint(ctx, msg)
if err != nil {
c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, bodyText, err))
return "", err
}
logging.Debug("Catalog message sent via Business API", "account_id", c.id, "to", phoneNumber)
c.eventBus.Publish(events.MessageSentEvent(ctx, c.id, messageID, phoneNumber, bodyText))
return messageID, nil
}
// SendSingleProduct sends a single-product interactive message.
func (c *Client) SendSingleProduct(ctx context.Context, jid types.JID, catalogID, productRetailerID, bodyText, footerText string) (string, error) {
if ctx == nil {
ctx = context.Background()
}
phoneNumber := jidToPhoneNumber(jid)
interactive := map[string]any{
"type": "product",
"header": map[string]any{
"type": "product",
"product_retailer_id": productRetailerID,
},
"body": map[string]any{"text": bodyText},
"action": map[string]any{
"catalog_id": catalogID,
"product_retailer_id": productRetailerID,
},
}
if footerText != "" {
interactive["footer"] = map[string]any{"text": footerText}
}
msg := map[string]any{
"messaging_product": "whatsapp",
"to": phoneNumber,
"type": "interactive",
"interactive": interactive,
}
messageID, err := c.postToMessagesEndpoint(ctx, msg)
if err != nil {
c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, bodyText, err))
return "", err
}
logging.Debug("Single product sent via Business API", "account_id", c.id, "to", phoneNumber, "product", productRetailerID)
c.eventBus.Publish(events.MessageSentEvent(ctx, c.id, messageID, phoneNumber, bodyText))
return messageID, nil
}
// SendProductList sends a multi-product list message. Up to 30 products across up to 10 sections.
func (c *Client) SendProductList(ctx context.Context, jid types.JID, headerText, bodyText, footerText, catalogID string, sections []ProductListSection) (string, error) {
if ctx == nil {
ctx = context.Background()
}
phoneNumber := jidToPhoneNumber(jid)
actionSections := make([]map[string]any, len(sections))
for i, s := range sections {
items := make([]map[string]any, len(s.ProductItems))
for j, item := range s.ProductItems {
items[j] = map[string]any{"product_retailer_id": item.ProductRetailerID}
}
actionSections[i] = map[string]any{
"title": s.Title,
"product_items": items,
}
}
interactive := map[string]any{
"type": "product_list",
"header": map[string]any{"type": "text", "text": headerText},
"body": map[string]any{"text": bodyText},
"action": map[string]any{
"catalog_id": catalogID,
"sections": actionSections,
},
}
if footerText != "" {
interactive["footer"] = map[string]any{"text": footerText}
}
msg := map[string]any{
"messaging_product": "whatsapp",
"to": phoneNumber,
"type": "interactive",
"interactive": interactive,
}
messageID, err := c.postToMessagesEndpoint(ctx, msg)
if err != nil {
c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, bodyText, err))
return "", err
}
logging.Debug("Product list sent via Business API", "account_id", c.id, "to", phoneNumber, "sections", len(sections))
c.eventBus.Publish(events.MessageSentEvent(ctx, c.id, messageID, phoneNumber, bodyText))
return messageID, nil
}