Server qr fixes.
This commit is contained in:
@@ -79,6 +79,10 @@ func (eb *EventBus) SubscribeAll(subscriber Subscriber) {
|
|||||||
EventWhatsAppDisconnected,
|
EventWhatsAppDisconnected,
|
||||||
EventWhatsAppPairSuccess,
|
EventWhatsAppPairSuccess,
|
||||||
EventWhatsAppPairFailed,
|
EventWhatsAppPairFailed,
|
||||||
|
EventWhatsAppQRCode,
|
||||||
|
EventWhatsAppQRTimeout,
|
||||||
|
EventWhatsAppQRError,
|
||||||
|
EventWhatsAppPairEvent,
|
||||||
EventMessageReceived,
|
EventMessageReceived,
|
||||||
EventMessageSent,
|
EventMessageSent,
|
||||||
EventMessageFailed,
|
EventMessageFailed,
|
||||||
|
|||||||
@@ -38,10 +38,63 @@ func (h *Handlers) AddAccount(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.config.WhatsApp = append(h.config.WhatsApp, account)
|
h.config.WhatsApp = append(h.config.WhatsApp, account)
|
||||||
if h.configPath != "" {
|
if h.configPath != "" {
|
||||||
if err := config.Save(h.configPath, h.config); err != nil {
|
if err := config.Save(h.configPath, h.config); err != nil {
|
||||||
logging.Error("Failed to save config", "error", err)
|
logging.Error("Failed to save config after adding account", "account_id", account.ID, "error", err)
|
||||||
|
} else {
|
||||||
|
logging.Info("Config saved after adding account", "account_id", account.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
writeJSON(w, map[string]string{"status": "ok", "account_id": account.ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAccount removes a WhatsApp account from the system
|
||||||
|
func (h *Handlers) RemoveAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect the account
|
||||||
|
if err := h.whatsappMgr.Disconnect(req.ID); err != nil {
|
||||||
|
logging.Warn("Failed to disconnect account during removal", "account_id", req.ID, "error", err)
|
||||||
|
// Continue with removal even if disconnect fails
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from config
|
||||||
|
found := false
|
||||||
|
newAccounts := make([]config.WhatsAppConfig, 0)
|
||||||
|
for _, acc := range h.config.WhatsApp {
|
||||||
|
if acc.ID != req.ID {
|
||||||
|
newAccounts = append(newAccounts, acc)
|
||||||
|
} else {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
http.Error(w, "Account not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.config.WhatsApp = newAccounts
|
||||||
|
|
||||||
|
// Save config
|
||||||
|
if h.configPath != "" {
|
||||||
|
if err := config.Save(h.configPath, h.config); err != nil {
|
||||||
|
logging.Error("Failed to save config after removing account", "account_id", req.ID, "error", err)
|
||||||
|
} else {
|
||||||
|
logging.Info("Config saved after removing account", "account_id", req.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
writeJSON(w, map[string]string{"status": "ok"})
|
writeJSON(w, map[string]string{"status": "ok"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,14 @@ func (h *Handlers) AddHook(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.config.Hooks = h.hookMgr.ListHooks()
|
h.config.Hooks = h.hookMgr.ListHooks()
|
||||||
if h.configPath != "" {
|
if h.configPath != "" {
|
||||||
if err := config.Save(h.configPath, h.config); err != nil {
|
if err := config.Save(h.configPath, h.config); err != nil {
|
||||||
logging.Error("Failed to save config", "error", err)
|
logging.Error("Failed to save config after adding hook", "hook_id", hook.ID, "error", err)
|
||||||
|
} else {
|
||||||
|
logging.Info("Config saved after adding hook", "hook_id", hook.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
writeJSON(w, map[string]string{"status": "ok"})
|
writeJSON(w, map[string]string{"status": "ok", "hook_id": hook.ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveHook removes a hook from the system
|
// RemoveHook removes a hook from the system
|
||||||
@@ -66,7 +68,9 @@ func (h *Handlers) RemoveHook(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.config.Hooks = h.hookMgr.ListHooks()
|
h.config.Hooks = h.hookMgr.ListHooks()
|
||||||
if h.configPath != "" {
|
if h.configPath != "" {
|
||||||
if err := config.Save(h.configPath, h.config); err != nil {
|
if err := config.Save(h.configPath, h.config); err != nil {
|
||||||
logging.Error("Failed to save config", "error", err)
|
logging.Error("Failed to save config after removing hook", "hook_id", req.ID, "error", err)
|
||||||
|
} else {
|
||||||
|
logging.Info("Config saved after removing hook", "hook_id", req.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,8 @@ func NewManager(eventBus *events.EventBus, mediaConfig config.MediaConfig, cfg *
|
|||||||
// Connect establishes a connection to a WhatsApp account using the appropriate client type
|
// Connect establishes a connection to a WhatsApp account using the appropriate client type
|
||||||
func (m *Manager) Connect(ctx context.Context, cfg config.WhatsAppConfig) error {
|
func (m *Manager) Connect(ctx context.Context, cfg config.WhatsAppConfig) error {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if _, exists := m.clients[cfg.ID]; exists {
|
if _, exists := m.clients[cfg.ID]; exists {
|
||||||
|
m.mu.Unlock()
|
||||||
return fmt.Errorf("client %s already connected", cfg.ID)
|
return fmt.Errorf("client %s already connected", cfg.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,21 +53,39 @@ func (m *Manager) Connect(ctx context.Context, cfg config.WhatsAppConfig) error
|
|||||||
case "business-api":
|
case "business-api":
|
||||||
client, err = businessapi.NewClient(cfg, m.eventBus, m.mediaConfig)
|
client, err = businessapi.NewClient(cfg, m.eventBus, m.mediaConfig)
|
||||||
case "whatsmeow", "":
|
case "whatsmeow", "":
|
||||||
client, err = whatsmeow.NewClient(cfg, m.eventBus, m.mediaConfig)
|
// Create callback for phone number updates
|
||||||
|
onPhoneUpdate := func(accountID, phoneNumber string) {
|
||||||
|
m.updatePhoneNumberInConfig(accountID, phoneNumber)
|
||||||
|
}
|
||||||
|
client, err = whatsmeow.NewClient(cfg, m.eventBus, m.mediaConfig, onPhoneUpdate)
|
||||||
default:
|
default:
|
||||||
|
m.mu.Unlock()
|
||||||
return fmt.Errorf("unknown client type: %s", cfg.Type)
|
return fmt.Errorf("unknown client type: %s", cfg.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
m.mu.Unlock()
|
||||||
return fmt.Errorf("failed to create client: %w", err)
|
return fmt.Errorf("failed to create client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.Connect(ctx); err != nil {
|
// Register client immediately so it's available for QR code serving during pairing
|
||||||
return fmt.Errorf("failed to connect: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.clients[cfg.ID] = client
|
m.clients[cfg.ID] = client
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
// Connect in background (this can block during QR pairing)
|
||||||
|
go func() {
|
||||||
|
if err := client.Connect(ctx); err != nil {
|
||||||
|
logging.Error("Failed to connect client", "account_id", cfg.ID, "error", err)
|
||||||
|
// Remove client if connection fails
|
||||||
|
m.mu.Lock()
|
||||||
|
delete(m.clients, cfg.ID)
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.eventBus.Publish(events.WhatsAppPairFailedEvent(ctx, cfg.ID, err))
|
||||||
|
} else {
|
||||||
logging.Info("Client connected", "account_id", cfg.ID, "type", client.GetType())
|
logging.Info("Client connected", "account_id", cfg.ID, "type", client.GetType())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,3 +186,29 @@ func (m *Manager) GetClient(id string) (Client, bool) {
|
|||||||
client, exists := m.clients[id]
|
client, exists := m.clients[id]
|
||||||
return client, exists
|
return client, exists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updatePhoneNumberInConfig updates the phone number for an account in config and saves it
|
||||||
|
func (m *Manager) updatePhoneNumberInConfig(accountID, phoneNumber string) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
// Find and update the account in config
|
||||||
|
for i := range m.config.WhatsApp {
|
||||||
|
if m.config.WhatsApp[i].ID == accountID {
|
||||||
|
if m.config.WhatsApp[i].PhoneNumber != phoneNumber {
|
||||||
|
m.config.WhatsApp[i].PhoneNumber = phoneNumber
|
||||||
|
logging.Info("Updated phone number in config", "account_id", accountID, "phone", phoneNumber)
|
||||||
|
|
||||||
|
// Save config if callback is available
|
||||||
|
if m.onConfigUpdate != nil {
|
||||||
|
if err := m.onConfigUpdate(m.config); err != nil {
|
||||||
|
logging.Error("Failed to save config after phone update", "account_id", accountID, "error", err)
|
||||||
|
} else {
|
||||||
|
logging.Debug("Config saved with updated phone number", "account_id", accountID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
package whatsmeow
|
package whatsmeow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -20,7 +17,9 @@ import (
|
|||||||
|
|
||||||
qrterminal "github.com/mdp/qrterminal/v3"
|
qrterminal "github.com/mdp/qrterminal/v3"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
|
"go.mau.fi/whatsmeow/proto/waCompanionReg"
|
||||||
"go.mau.fi/whatsmeow/proto/waE2E"
|
"go.mau.fi/whatsmeow/proto/waE2E"
|
||||||
|
"go.mau.fi/whatsmeow/store"
|
||||||
"go.mau.fi/whatsmeow/store/sqlstore"
|
"go.mau.fi/whatsmeow/store/sqlstore"
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
waEvents "go.mau.fi/whatsmeow/types/events"
|
waEvents "go.mau.fi/whatsmeow/types/events"
|
||||||
@@ -43,11 +42,13 @@ type Client struct {
|
|||||||
showQR bool
|
showQR bool
|
||||||
keepAliveCancel context.CancelFunc
|
keepAliveCancel context.CancelFunc
|
||||||
qrCode string
|
qrCode string
|
||||||
|
qrCodePNG []byte // Cached PNG data
|
||||||
qrCodeMutex sync.RWMutex
|
qrCodeMutex sync.RWMutex
|
||||||
|
onPhoneUpdate func(accountID, phoneNumber string) // Callback when phone number is updated
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new whatsmeow client
|
// NewClient creates a new whatsmeow client
|
||||||
func NewClient(cfg config.WhatsAppConfig, eventBus *events.EventBus, mediaConfig config.MediaConfig) (*Client, error) {
|
func NewClient(cfg config.WhatsAppConfig, eventBus *events.EventBus, mediaConfig config.MediaConfig, onPhoneUpdate func(accountID, phoneNumber string)) (*Client, error) {
|
||||||
if cfg.Type != "whatsmeow" && cfg.Type != "" {
|
if cfg.Type != "whatsmeow" && cfg.Type != "" {
|
||||||
return nil, fmt.Errorf("invalid client type for whatsmeow: %s", cfg.Type)
|
return nil, fmt.Errorf("invalid client type for whatsmeow: %s", cfg.Type)
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,7 @@ func NewClient(cfg config.WhatsAppConfig, eventBus *events.EventBus, mediaConfig
|
|||||||
eventBus: eventBus,
|
eventBus: eventBus,
|
||||||
mediaConfig: mediaConfig,
|
mediaConfig: mediaConfig,
|
||||||
showQR: cfg.ShowQR,
|
showQR: cfg.ShowQR,
|
||||||
|
onPhoneUpdate: onPhoneUpdate,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,10 @@ func (c *Client) Connect(ctx context.Context) error {
|
|||||||
if err := os.MkdirAll(c.sessionPath, 0700); err != nil {
|
if err := os.MkdirAll(c.sessionPath, 0700); err != nil {
|
||||||
return fmt.Errorf("failed to create session directory: %w", err)
|
return fmt.Errorf("failed to create session directory: %w", err)
|
||||||
}
|
}
|
||||||
|
// store.SetOSInfo("Linux", store.GetWAVersion())
|
||||||
|
store.DeviceProps.PlatformType = waCompanionReg.DeviceProps_CLOUD_API.Enum()
|
||||||
|
store.DeviceProps.Os = proto.String("whatshooked.warky.dev")
|
||||||
|
store.DeviceProps.RequireFullSync = proto.Bool(false)
|
||||||
|
|
||||||
// Create database container for session storage
|
// Create database container for session storage
|
||||||
dbPath := filepath.Join(c.sessionPath, "session.db")
|
dbPath := filepath.Join(c.sessionPath, "session.db")
|
||||||
@@ -89,9 +95,18 @@ func (c *Client) Connect(ctx context.Context) error {
|
|||||||
return fmt.Errorf("failed to get device: %w", err)
|
return fmt.Errorf("failed to get device: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set custom client information
|
// Set custom client information that will be shown in WhatsApp
|
||||||
deviceStore.Platform = "WhatsHooked"
|
deviceStore.Platform = "git.warky.dev/wdevs/whatshooked"
|
||||||
deviceStore.BusinessName = "git.warky.dev/wdevs/whatshooked"
|
|
||||||
|
// Set PushName BEFORE pairing - this is what shows up in WhatsApp linked devices
|
||||||
|
if deviceStore.PushName == "" {
|
||||||
|
deviceStore.PushName = fmt.Sprintf("WhatsHooked %s", c.phoneNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save device store to persist device info before pairing
|
||||||
|
if err := deviceStore.Save(ctx); err != nil {
|
||||||
|
logging.Warn("Failed to save device store", "account_id", c.id, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
clientLog := waLog.Stdout("Client", "ERROR", true)
|
clientLog := waLog.Stdout("Client", "ERROR", true)
|
||||||
@@ -118,11 +133,21 @@ func (c *Client) Connect(ctx context.Context) error {
|
|||||||
case "code":
|
case "code":
|
||||||
logging.Info("QR code received for pairing", "account_id", c.id)
|
logging.Info("QR code received for pairing", "account_id", c.id)
|
||||||
|
|
||||||
// Store QR code
|
// Generate PNG (this regenerates on each new QR code)
|
||||||
|
pngData, err := c.generateQRCodePNG(evt.Code)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("Failed to generate QR code PNG", "account_id", c.id, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store QR code and PNG (updates cached version)
|
||||||
c.qrCodeMutex.Lock()
|
c.qrCodeMutex.Lock()
|
||||||
c.qrCode = evt.Code
|
c.qrCode = evt.Code
|
||||||
|
c.qrCodePNG = pngData
|
||||||
|
qrCodeSize := len(pngData)
|
||||||
c.qrCodeMutex.Unlock()
|
c.qrCodeMutex.Unlock()
|
||||||
|
|
||||||
|
logging.Debug("QR code PNG updated", "account_id", c.id, "size_bytes", qrCodeSize)
|
||||||
|
|
||||||
// Generate QR code URL
|
// Generate QR code URL
|
||||||
qrURL := c.generateQRCodeURL()
|
qrURL := c.generateQRCodeURL()
|
||||||
|
|
||||||
@@ -143,10 +168,24 @@ func (c *Client) Connect(ctx context.Context) error {
|
|||||||
|
|
||||||
case "success":
|
case "success":
|
||||||
logging.Info("Pairing successful", "account_id", c.id, "phone", c.phoneNumber)
|
logging.Info("Pairing successful", "account_id", c.id, "phone", c.phoneNumber)
|
||||||
|
|
||||||
|
// Clear cached QR code after successful pairing
|
||||||
|
c.qrCodeMutex.Lock()
|
||||||
|
c.qrCode = ""
|
||||||
|
c.qrCodePNG = nil
|
||||||
|
c.qrCodeMutex.Unlock()
|
||||||
|
|
||||||
c.eventBus.Publish(events.WhatsAppPairSuccessEvent(ctx, c.id))
|
c.eventBus.Publish(events.WhatsAppPairSuccessEvent(ctx, c.id))
|
||||||
|
|
||||||
case "timeout":
|
case "timeout":
|
||||||
logging.Warn("QR code timeout", "account_id", c.id)
|
logging.Warn("QR code timeout", "account_id", c.id)
|
||||||
|
|
||||||
|
// Clear cached QR code on timeout
|
||||||
|
c.qrCodeMutex.Lock()
|
||||||
|
c.qrCode = ""
|
||||||
|
c.qrCodePNG = nil
|
||||||
|
c.qrCodeMutex.Unlock()
|
||||||
|
|
||||||
c.eventBus.Publish(events.WhatsAppQRTimeoutEvent(ctx, c.id))
|
c.eventBus.Publish(events.WhatsAppQRTimeoutEvent(ctx, c.id))
|
||||||
|
|
||||||
case "error":
|
case "error":
|
||||||
@@ -167,12 +206,7 @@ func (c *Client) Connect(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if deviceStore.PushName == "" {
|
// PushName is already set before pairing, no need to set it again here
|
||||||
deviceStore.PushName = fmt.Sprintf("WhatsHooked %s", c.phoneNumber)
|
|
||||||
if err := deviceStore.Save(ctx); err != nil {
|
|
||||||
logging.Error("failed to save device store", "account_id", c.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.IsConnected() {
|
if client.IsConnected() {
|
||||||
err := client.SendPresence(ctx, types.PresenceAvailable)
|
err := client.SendPresence(ctx, types.PresenceAvailable)
|
||||||
@@ -521,6 +555,11 @@ func (c *Client) handleEvent(evt interface{}) {
|
|||||||
if c.phoneNumber != phoneNumber {
|
if c.phoneNumber != phoneNumber {
|
||||||
c.phoneNumber = phoneNumber
|
c.phoneNumber = phoneNumber
|
||||||
logging.Info("Updated phone number from WhatsApp", "account_id", c.id, "phone", phoneNumber)
|
logging.Info("Updated phone number from WhatsApp", "account_id", c.id, "phone", phoneNumber)
|
||||||
|
|
||||||
|
// Trigger config update callback to persist the phone number
|
||||||
|
if c.onPhoneUpdate != nil {
|
||||||
|
c.onPhoneUpdate(c.id, phoneNumber)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if c.phoneNumber != "" {
|
} else if c.phoneNumber != "" {
|
||||||
phoneNumber = c.phoneNumber
|
phoneNumber = c.phoneNumber
|
||||||
@@ -703,49 +742,31 @@ func (c *Client) generateQRCodeURL() string {
|
|||||||
return fmt.Sprintf("%s/api/qr/%s", baseURL, c.id)
|
return fmt.Sprintf("%s/api/qr/%s", baseURL, c.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQRCodePNG generates a PNG image of the current QR code
|
// GetQRCodePNG returns the cached PNG image of the current QR code
|
||||||
func (c *Client) GetQRCodePNG() ([]byte, error) {
|
func (c *Client) GetQRCodePNG() ([]byte, error) {
|
||||||
c.qrCodeMutex.RLock()
|
c.qrCodeMutex.RLock()
|
||||||
qrCodeData := c.qrCode
|
defer c.qrCodeMutex.RUnlock()
|
||||||
c.qrCodeMutex.RUnlock()
|
|
||||||
|
|
||||||
if qrCodeData == "" {
|
if c.qrCode == "" {
|
||||||
return nil, fmt.Errorf("no QR code available")
|
return nil, fmt.Errorf("no QR code available")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.qrCodePNG == nil {
|
||||||
|
return nil, fmt.Errorf("QR code PNG not yet generated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.qrCodePNG, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateQRCodePNG generates a PNG image from QR code data
|
||||||
|
func (c *Client) generateQRCodePNG(qrCodeData string) ([]byte, error) {
|
||||||
// Generate QR code using rsc.io/qr
|
// Generate QR code using rsc.io/qr
|
||||||
code, err := qr.Encode(qrCodeData, qr.L)
|
code, err := qr.Encode(qrCodeData, qr.H)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to encode QR code: %w", err)
|
return nil, fmt.Errorf("failed to encode QR code: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale the QR code for better visibility (8x scale)
|
// Use the library's built-in PNG method with a scale factor
|
||||||
img := code.Image()
|
// Scale of 8 means each QR module is 8x8 pixels
|
||||||
scale := 8
|
return code.PNG(), nil
|
||||||
bounds := img.Bounds()
|
|
||||||
scaledWidth := bounds.Dx() * scale
|
|
||||||
scaledHeight := bounds.Dy() * scale
|
|
||||||
|
|
||||||
// Create a new image with scaled dimensions
|
|
||||||
scaledImg := image.NewRGBA(image.Rect(0, 0, scaledWidth, scaledHeight))
|
|
||||||
|
|
||||||
// Scale the image
|
|
||||||
for y := 0; y < bounds.Dy(); y++ {
|
|
||||||
for x := 0; x < bounds.Dx(); x++ {
|
|
||||||
pixel := img.At(x, y)
|
|
||||||
for sy := 0; sy < scale; sy++ {
|
|
||||||
for sx := 0; sx < scale; sx++ {
|
|
||||||
scaledImg.Set(x*scale+sx, y*scale+sy, pixel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode to PNG
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := png.Encode(&buf, scaledImg); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to encode PNG: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ func (s *Server) setupRoutes() *http.ServeMux {
|
|||||||
// Account management (with auth)
|
// Account management (with auth)
|
||||||
mux.HandleFunc("/api/accounts", h.Auth(h.Accounts))
|
mux.HandleFunc("/api/accounts", h.Auth(h.Accounts))
|
||||||
mux.HandleFunc("/api/accounts/add", h.Auth(h.AddAccount))
|
mux.HandleFunc("/api/accounts/add", h.Auth(h.AddAccount))
|
||||||
|
mux.HandleFunc("/api/accounts/remove", h.Auth(h.RemoveAccount))
|
||||||
|
|
||||||
// Send messages (with auth)
|
// Send messages (with auth)
|
||||||
mux.HandleFunc("/api/send", h.Auth(h.SendMessage))
|
mux.HandleFunc("/api/send", h.Auth(h.SendMessage))
|
||||||
|
|||||||
Reference in New Issue
Block a user