Files
gochat/internal/channel/provider.go
T
2026-06-04 15:44:48 +08:00

303 lines
14 KiB
Go

package channel
import (
"context"
"time"
"github.com/gochat/gochat/internal/model"
)
// ChannelType enumerates supported channel types.
// Reference: Chatwoot app/models/inbox.rb channel_type enum
// P2D: 使用 snake_case 标识符,替代 Chatwoot 的 Channel::PascalCase
type ChannelType string
const (
ChannelWebWidget ChannelType = "web_widget"
ChannelTelegram ChannelType = "telegram"
ChannelFacebook ChannelType = "facebook"
ChannelInstagram ChannelType = "instagram"
ChannelWhatsApp ChannelType = "whatsapp"
ChannelEmail ChannelType = "email"
ChannelTwilioSMS ChannelType = "twilio_sms"
ChannelTwilioWA ChannelType = "twilio_whatsapp"
ChannelLine ChannelType = "line"
ChannelSlack ChannelType = "slack"
ChannelAPI ChannelType = "api"
ChannelTikTok ChannelType = "tiktok"
ChannelMicrosoft ChannelType = "microsoft"
)
// ChannelProvider is the core interface that all channel providers must implement.
// Reference: P2D §3.2 — Chatwoot's Channelable concern + polymorphic channel association
//
// Design principles:
// - Interface methods follow message lifecycle (create→send→receive→callback→destroy)
// - Each Provider is stateless; channel state is persisted via Inbox.ChannelConfig + GORM models
// - context.Context is used for timeout/cancel/tracing propagation
type ChannelProvider interface {
// === Identity & Metadata ===
// Type returns the channel type identifier
Type() ChannelType
// Name returns the human-readable channel name (e.g. "Telegram", "Web Widget")
Name() string
// Description returns a short description of the channel
Description() string
// === Configuration & Validation ===
// ConfigSchema returns the JSON Schema definition for channel configuration
// Used for frontend dynamic form rendering and backend config validation
// Corresponds to Chatwoot's EDITABLE_ATTRS per channel
ConfigSchema() *ConfigSchemaDefinition
// ValidateConfig validates channel configuration validity
// Called on Inbox create/update, replaces Chatwoot's channel model before_validation
ValidateConfig(ctx context.Context, config ChannelConfig) error
// DefaultConfig returns default configuration with preset values
DefaultConfig() ChannelConfig
// === Lifecycle: Create & Destroy ===
// OnCreate callback after channel creation
// Corresponds to Chatwoot's before_save :setup_telegram_webhook etc.
// Returns possibly updated config (e.g. webhook URL, token)
OnCreate(ctx context.Context, inbox *model.Inbox, config ChannelConfig) (ChannelConfig, error)
// OnDestroy callback before channel destruction
// Cleanup external resources (delete Telegram webhook, cancel FB Page subscription etc.)
OnDestroy(ctx context.Context, inbox *model.Inbox, config ChannelConfig) error
// === Messaging: Inbound ===
// ProcessIncoming transforms raw external payload into IncomingMessage
// From webhook callback or polling data → IncomingMessage
// Corresponds to Chatwoot's WebhooksController + MessageBuilder per channel
ProcessIncoming(ctx context.Context, inbox *model.Inbox, rawPayload []byte) (*IncomingMessage, error)
// ValidateWebhookRequest verifies webhook callback authenticity
// Each Provider implements its own signature verification (FB signature, TG token, WA HMAC etc.)
ValidateWebhookRequest(ctx context.Context, inbox *model.Inbox, request *WebhookRequest) error
// === Messaging: Outbound ===
// SendMessage sends a message to the external channel
// Corresponds to Chatwoot's SendOn*Service per channel
// Returns external channel message ID for correlation tracking
SendMessage(ctx context.Context, inbox *model.Inbox, message *model.Message, contact *model.Contact) (*SendResult, error)
// === Contact Info ===
// GetContactProfile fetches contact profile from external channel (avatar, name etc.)
// Corresponds to Chatwoot's Telegram.get_telegram_profile_image / Facebook.get_user_profile etc.
GetContactProfile(ctx context.Context, inbox *model.Inbox, contactSource string) (*ContactProfile, error)
// === Capability Declaration ===
// Capabilities returns the set of features this channel supports
// Used by frontend to determine available Inbox features (attachments, typing indicators etc.)
Capabilities() ChannelCapabilities
}
// OAuthProvider is the sub-interface for channels requiring OAuth authentication.
// Reference: P2D §3.3 — Chatwoot's Reauthorizable concern + RefreshOauthTokenService
//
// Applicable channels: Facebook, Instagram, WhatsApp(360dialog), Slack
type OAuthProvider interface {
ChannelProvider // embed base interface
// OAuthConfig returns OAuth configuration requirements definition
OAuthConfig() *OAuthConfigDefinition
// BuildAuthURL constructs the OAuth authorization redirect URL
// Corresponds to Chatwoot's omniauth authorize URL
BuildAuthURL(ctx context.Context, accountID uint, redirectURL string) (string, error)
// ExchangeToken exchanges OAuth code for access_token
// Corresponds to Chatwoot's omniauth callback processing
ExchangeToken(ctx context.Context, code string, redirectURL string) (*OAuthTokenResult, error)
// RefreshToken refreshes an expired access_token
// Corresponds to Chatwoot's RefreshOauthTokenService per channel
RefreshToken(ctx context.Context, inbox *model.Inbox, config ChannelConfig) (*OAuthTokenResult, error)
// CheckAuthorizationError checks if an API call returned authorization error
// Corresponds to Chatwoot Reauthorizable.authorization_error!
CheckAuthorizationError(ctx context.Context, apiError error) bool
// OnReauthorization callback when re-authorization is needed
// Corresponds to Chatwoot's prompt_reauthorization! → email notification + UI hint
OnReauthorization(ctx context.Context, inbox *model.Inbox) error
}
// PollingProvider is the sub-interface for channels requiring active message polling.
// Reference: P2D §3.4 — Chatwoot's Imap::FetchService / Scheduler periodic fetch
//
// Applicable channels: Email (IMAP)
type PollingProvider interface {
ChannelProvider // embed base interface
// PollInterval returns the polling interval duration
PollInterval() time.Duration
// PollMessages fetches new messages from external channel
// Corresponds to Chatwoot's Imap::FetchService / Email::ProcessMailService
PollMessages(ctx context.Context, inbox *model.Inbox) ([]*IncomingMessage, error)
}
// --- Domain Types ---
// ChannelConfig stores per-inbox channel configuration as typed map.
// Reference: P2D §7 — replaces Chatwoot's per-channel DB models with unified JSON config
type ChannelConfig map[string]interface{}
// ConfigSchemaDefinition defines the JSON Schema for channel configuration validation.
// Reference: P2D §7 — replaces Chatwoot's EDITABLE_ATTRS constants
type ConfigSchemaDefinition struct {
Type string `json:"type"` // "object"
Properties map[string]ConfigProperty `json:"properties"` // field definitions
Required []string `json:"required"` // mandatory fields
}
// ConfigProperty defines a single configuration field.
type ConfigProperty struct {
Type string `json:"type"` // "string", "number", "boolean", "array"
Description string `json:"description"` // human-readable field description
Default interface{} `json:"default,omitempty"` // default value
Enum []string `json:"enum,omitempty"` // allowed values (for enum fields)
Pattern string `json:"pattern,omitempty"` // regex pattern for validation
Format string `json:"format,omitempty"` // "url", "email", "uri" etc.
Secret bool `json:"secret,omitempty"` // true for sensitive fields (bot_token etc.)
Required bool `json:"required,omitempty"` // whether this property is required
}
// OAuthConfigDefinition defines OAuth requirements for a channel.
// Reference: P2D §3.3
type OAuthConfigDefinition struct {
Provider string `json:"provider"` // "facebook", "google", "slack" etc.
Scopes []string `json:"scopes"` // required OAuth scopes
AuthorizeURL string `json:"authorize_url"` // OAuth authorize endpoint
TokenURL string `json:"token_url"` // OAuth token exchange endpoint
RefreshURL string `json:"refresh_url"` // OAuth token refresh endpoint
RequiresRefresh bool `json:"requires_refresh"` // whether token needs periodic refresh
TokenExpiry int `json:"token_expiry"` // token expiry in seconds (0 = no expiry)
}
// OAuthTokenResult contains the result of OAuth token exchange/refresh.
type OAuthTokenResult struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
Scope string `json:"scope,omitempty"`
Extra ChannelConfig `json:"extra,omitempty"` // provider-specific extra data
}
// IncomingMessage represents a message received from an external channel.
// Reference: P2D §8 — unified cross-channel inbound message structure
// Corresponds to Chatwoot's IncomingMessageService input
type IncomingMessage struct {
// Source identification
ChannelType ChannelType `json:"channel_type"`
SourceID string `json:"source_id"` // external message ID (TG message_id, FB mid etc.)
ConversationID string `json:"conversation_id"` // external conversation/thread ID
// Sender identification
SenderID string `json:"sender_id"` // external sender ID (TG user_id, FB sender_id etc.)
SenderName string `json:"sender_name"` // sender display name
SenderType SenderType `json:"sender_type"` // contact / agent / system
// Message content
Content string `json:"content"` // text content
ContentType ContentType `json:"content_type"` // text / image / file / audio / video / location / email
Attachments []Attachment `json:"attachments"` // media attachments
ReplyToID string `json:"reply_to_id,omitempty"` // replied-to message source ID
// Metadata
InboxID uint `json:"inbox_id"`
AccountID uint `json:"account_id"`
ReceivedAt time.Time `json:"received_at"`
Extra ChannelConfig `json:"extra,omitempty"` // channel-specific metadata
SenderExtra ChannelConfig `json:"sender_extra,omitempty"` // sender-specific metadata (e.g., Telegram user details)
ConversationExtra ChannelConfig `json:"conversation_extra,omitempty"` // conversation-specific metadata (e.g., group info)
}
// SenderType identifies the type of message sender.
type SenderType string
const (
SenderContact SenderType = "contact"
SenderAgent SenderType = "agent"
SenderSystem SenderType = "system"
)
// ContentType identifies the type of message content.
// Reference: Chatwoot message_type enum + content_type classification
type ContentType string
const (
ContentText ContentType = "text"
ContentImage ContentType = "image"
ContentFile ContentType = "file"
ContentAudio ContentType = "audio"
ContentVideo ContentType = "video"
ContentLocation ContentType = "location"
ContentEmail ContentType = "email"
ContentTemplate ContentType = "template" // WhatsApp template messages
)
// Attachment represents a media attachment in a message.
type Attachment struct {
URL string `json:"url"` // download URL
ContentType string `json:"content_type"` // MIME type
Filename string `json:"filename,omitempty"` // original filename
FileSize int64 `json:"file_size,omitempty"` // file size in bytes
ThumbnailURL string `json:"thumbnail_url,omitempty"` // thumbnail URL (for images/videos)
Extra ChannelConfig `json:"extra,omitempty"` // channel-specific metadata
}
// SendResult contains the result of an outbound message send operation.
type SendResult struct {
ExternalID string `json:"external_id"` // external channel message ID
DeliveredAt time.Time `json:"delivered_at"` // delivery timestamp
Extra ChannelConfig `json:"extra,omitempty"` // channel-specific response data
}
// WebhookRequest wraps an incoming webhook HTTP request.
// Reference: P2D §6 — unified webhook entry point
type WebhookRequest struct {
ChannelType ChannelType `json:"channel_type"` // identifies which provider handles this
Identifier string `json:"identifier"` // inbox identifier (TG bot token, FB page ID etc.)
Headers map[string]string `json:"headers"` // HTTP headers for signature verification
Body []byte `json:"body"` // raw request body
QueryParams map[string]string `json:"query_params"` // URL query parameters
Method string `json:"method"` // HTTP method (GET/POST)
}
// ContactProfile contains contact profile info fetched from external channel.
type ContactProfile struct {
Name string `json:"name"`
AvatarURL string `json:"avatar_url,omitempty"`
Extra ChannelConfig `json:"extra,omitempty"` // channel-specific profile data
}
// ChannelCapabilities declares what features a channel supports.
// Reference: P2D §3.2 — replaces Chatwoot's per-channel feature flags
type ChannelCapabilities struct {
SupportsAttachments bool `json:"supports_attachments"`
SupportsLocation bool `json:"supports_location"`
SupportsTypingIndicator bool `json:"supports_typing_indicator"`
SupportsDeliveryStatus bool `json:"supports_delivery_status"`
SupportsReplies bool `json:"supports_replies"` // reply-to specific messages
SupportsEmojiReactions bool `json:"supports_emoji_reactions"`
SupportsVoiceMessages bool `json:"supports_voice_messages"`
SupportsVideoCalls bool `json:"supports_video_calls"`
SupportsCustomCards bool `json:"supports_custom_cards"` // rich message cards
SupportsTemplates bool `json:"supports_templates"` // WhatsApp message templates
SupportsEmailHeaders bool `json:"supports_email_headers"` // Email subject/cc/bcc
MaxAttachmentSize int64 `json:"max_attachment_size"` // max attachment size in bytes
MaxTextLength int `json:"max_text_length"` // max text message length
}