303 lines
14 KiB
Go
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
|
|
} |