mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
f376a2abe3
Unify webhook handling across channel adapters and add the WeChat Official Account channel so inbound routing and replies work without platform-specific handlers. Add adapter-scoped proxy support and stable config field ordering so restricted network environments can deliver WeChat and Telegram messages reliably.
107 lines
3.2 KiB
Go
107 lines
3.2 KiB
Go
package channel
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
type webhookConfigStore interface {
|
|
ListConfigsByType(ctx context.Context, channelType ChannelType) ([]ChannelConfig, error)
|
|
}
|
|
|
|
type webhookInboundManager interface {
|
|
HandleInbound(ctx context.Context, cfg ChannelConfig, msg InboundMessage) error
|
|
Registry() *Registry
|
|
}
|
|
|
|
// WebhookHandler dispatches public webhook callbacks to channel adapters.
|
|
type WebhookHandler struct {
|
|
logger *slog.Logger
|
|
store webhookConfigStore
|
|
manager webhookInboundManager
|
|
registry *Registry
|
|
}
|
|
|
|
// NewWebhookServerHandler creates the generic channel webhook handler.
|
|
func NewWebhookServerHandler(log *slog.Logger, store *Store, manager *Manager) *WebhookHandler {
|
|
if log == nil {
|
|
log = slog.Default()
|
|
}
|
|
var registry *Registry
|
|
if manager != nil {
|
|
registry = manager.Registry()
|
|
}
|
|
return &WebhookHandler{
|
|
logger: log.With(slog.String("handler", "channel_webhook")),
|
|
store: store,
|
|
manager: manager,
|
|
registry: registry,
|
|
}
|
|
}
|
|
|
|
// Register registers generic channel webhook routes.
|
|
func (h *WebhookHandler) Register(e *echo.Echo) {
|
|
e.GET("/channels/:platform/webhook/:config_id", h.Handle)
|
|
e.POST("/channels/:platform/webhook/:config_id", h.Handle)
|
|
}
|
|
|
|
// Handle resolves the channel config and delegates the request to the adapter.
|
|
func (h *WebhookHandler) Handle(c echo.Context) error {
|
|
if h.store == nil || h.manager == nil || h.registry == nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "channel webhook dependencies not configured")
|
|
}
|
|
channelType, err := h.registry.ParseChannelType(c.Param("platform"))
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
}
|
|
configID := strings.TrimSpace(c.Param("config_id"))
|
|
if configID == "" {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "config id is required")
|
|
}
|
|
cfg, err := h.findConfigByID(c.Request().Context(), channelType, configID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cfg.Disabled {
|
|
return echo.NewHTTPError(http.StatusForbidden, "channel config is disabled")
|
|
}
|
|
receiver, ok := h.registry.GetWebhookReceiver(channelType)
|
|
if !ok {
|
|
return echo.NewHTTPError(http.StatusNotFound, "channel webhook receiver not found")
|
|
}
|
|
if err := receiver.HandleWebhook(c.Request().Context(), cfg, h.manager.HandleInbound, c.Request(), c.Response()); err != nil {
|
|
var httpErr *echo.HTTPError
|
|
if errors.As(err, &httpErr) {
|
|
return httpErr
|
|
}
|
|
if h.logger != nil {
|
|
h.logger.Warn(
|
|
"channel webhook failed",
|
|
slog.String("channel", channelType.String()),
|
|
slog.String("config_id", configID),
|
|
slog.Any("error", err),
|
|
)
|
|
}
|
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *WebhookHandler) findConfigByID(ctx context.Context, channelType ChannelType, configID string) (ChannelConfig, error) {
|
|
items, err := h.store.ListConfigsByType(ctx, channelType)
|
|
if err != nil {
|
|
return ChannelConfig{}, echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
|
}
|
|
for _, item := range items {
|
|
if item.ChannelType == channelType && strings.TrimSpace(item.ID) == configID {
|
|
return item, nil
|
|
}
|
|
}
|
|
return ChannelConfig{}, echo.NewHTTPError(http.StatusNotFound, "channel config not found")
|
|
}
|