Files
haidang1810andDuy /zuey/ c24677272c Fix/whatsapp connect (#1254)
* fix: recover whatsapp qr after deleted device

* fix: whatsapp connect

* fix whatsapp instance device scoping

---------

Co-authored-by: Duy /zuey/ <duy@wearetopgroup.com>
2026-06-22 13:22:06 +07:00

107 lines
3.7 KiB
Go

package whatsapp
import (
"database/sql"
"encoding/json"
"fmt"
"go.mau.fi/whatsmeow/types"
"github.com/nextlevelbuilder/goclaw/internal/audio"
"github.com/nextlevelbuilder/goclaw/internal/bus"
"github.com/nextlevelbuilder/goclaw/internal/channels"
"github.com/nextlevelbuilder/goclaw/internal/config"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
// whatsappCredentials maps encrypted credentials JSON from channel_instances.
type whatsappCredentials struct {
DeviceJID string `json:"device_jid,omitempty"`
BridgeURL string `json:"bridge_url,omitempty"`
}
// whatsappInstanceConfig maps the non-secret config JSONB from the channel_instances table.
type whatsappInstanceConfig struct {
DMPolicy string `json:"dm_policy,omitempty"`
GroupPolicy string `json:"group_policy,omitempty"`
RequireMention *bool `json:"require_mention,omitempty"`
HistoryLimit int `json:"history_limit,omitempty"`
AllowFrom []string `json:"allow_from,omitempty"`
BlockReply *bool `json:"block_reply,omitempty"`
ChatBehavior *config.ChatBehaviorConfig `json:"chat_behavior,omitempty"`
}
// FactoryWithDB returns a ChannelFactory with DB access for whatsmeow auth state.
// dialect must be "pgx" (PostgreSQL) or "sqlite3" (SQLite/desktop).
func FactoryWithDB(db *sql.DB, pendingStore store.PendingMessageStore, dialect string) channels.ChannelFactory {
return FactoryWithDBAudio(db, pendingStore, dialect, nil, nil)
}
// FactoryWithDBAudio returns a ChannelFactory with DB access, STT support, and builtin-tools store
// for reading stt.whatsapp_enabled opt-in setting per message.
func FactoryWithDBAudio(db *sql.DB, pendingStore store.PendingMessageStore, dialect string,
audioMgr *audio.Manager, builtinToolStore store.BuiltinToolStore) channels.ChannelFactory {
return func(name string, creds json.RawMessage, cfg json.RawMessage,
msgBus *bus.MessageBus, pairingSvc store.PairingStore) (channels.Channel, error) {
var ic whatsappInstanceConfig
if len(cfg) > 0 {
if err := json.Unmarshal(cfg, &ic); err != nil {
return nil, fmt.Errorf("decode whatsapp config: %w", err)
}
}
var wc whatsappCredentials
if len(creds) > 0 {
if err := json.Unmarshal(creds, &wc); err != nil {
return nil, fmt.Errorf("decode whatsapp credentials: %w", err)
}
if wc.BridgeURL != "" {
return nil, fmt.Errorf("whatsapp: bridge_url is no longer supported - " +
"WhatsApp now runs natively via whatsmeow. Remove bridge_url from credentials")
}
}
if len(cfg) > 0 {
var legacy struct {
BridgeURL string `json:"bridge_url"`
}
if json.Unmarshal(cfg, &legacy) == nil && legacy.BridgeURL != "" {
return nil, fmt.Errorf("whatsapp: bridge_url is no longer supported - " +
"WhatsApp now runs natively via whatsmeow. Remove bridge_url from config")
}
}
waCfg := config.WhatsAppConfig{
Enabled: true,
AllowFrom: ic.AllowFrom,
DMPolicy: ic.DMPolicy,
GroupPolicy: ic.GroupPolicy,
RequireMention: ic.RequireMention,
HistoryLimit: ic.HistoryLimit,
BlockReply: ic.BlockReply,
ChatBehavior: ic.ChatBehavior,
}
// DB instances default to "pairing" for groups (secure by default).
if waCfg.GroupPolicy == "" {
waCfg.GroupPolicy = "pairing"
}
var opts []Option
if wc.DeviceJID != "" {
jid, err := types.ParseJID(wc.DeviceJID)
if err != nil {
return nil, fmt.Errorf("decode whatsapp credentials device_jid: %w", err)
}
opts = append(opts, WithDeviceJID(jid))
}
ch, err := New(waCfg, msgBus, pairingSvc, db, pendingStore, dialect, audioMgr, builtinToolStore, opts...)
if err != nil {
return nil, err
}
ch.SetName(name)
return ch, nil
}
}