feat(02-03): add MediaAttachment model, WhatsApp normalizer, and signature verification
- Add MediaType(StrEnum) and MediaAttachment(BaseModel) to shared/models/message.py - Add media: list[MediaAttachment] field to MessageContent - Add whatsapp_app_secret, whatsapp_verify_token, and MinIO settings to shared/config.py - Add normalize_whatsapp_event() to gateway/normalize.py (text, image, document support) - Create whatsapp.py adapter with verify_whatsapp_signature() and verify_hub_challenge() - 30 new passing tests (signature verification + normalizer)
This commit is contained in:
@@ -65,6 +65,38 @@ class Settings(BaseSettings):
|
||||
description="Slack app-level token for Socket Mode (xapp-...)",
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# WhatsApp
|
||||
# -------------------------------------------------------------------------
|
||||
whatsapp_app_secret: str = Field(
|
||||
default="",
|
||||
description="WhatsApp app secret for HMAC-SHA256 webhook signature verification",
|
||||
)
|
||||
whatsapp_verify_token: str = Field(
|
||||
default="",
|
||||
description="WhatsApp webhook verification token (hub.verify_token)",
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# MinIO / Object Storage
|
||||
# -------------------------------------------------------------------------
|
||||
minio_endpoint: str = Field(
|
||||
default="http://localhost:9000",
|
||||
description="MinIO endpoint URL (S3-compatible)",
|
||||
)
|
||||
minio_access_key: str = Field(
|
||||
default="minioadmin",
|
||||
description="MinIO access key",
|
||||
)
|
||||
minio_secret_key: str = Field(
|
||||
default="minioadmin",
|
||||
description="MinIO secret key",
|
||||
)
|
||||
minio_media_bucket: str = Field(
|
||||
default="konstruct-media",
|
||||
description="MinIO bucket name for media attachments",
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# LLM Providers
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -28,6 +28,38 @@ class ChannelType(StrEnum):
|
||||
SIGNAL = "signal"
|
||||
|
||||
|
||||
class MediaType(StrEnum):
|
||||
"""Supported media attachment types."""
|
||||
|
||||
IMAGE = "image"
|
||||
DOCUMENT = "document"
|
||||
AUDIO = "audio"
|
||||
VIDEO = "video"
|
||||
|
||||
|
||||
class MediaAttachment(BaseModel):
|
||||
"""
|
||||
A media file attached to a message (image, document, audio, or video).
|
||||
|
||||
After normalization, `url` contains a placeholder media ID URL from the
|
||||
channel's API. The channel adapter downloads the media and stores it in
|
||||
MinIO, then updates `storage_key` and `url` with the final presigned URL.
|
||||
"""
|
||||
|
||||
media_type: MediaType = Field(description="Type of media: image, document, audio, or video")
|
||||
url: str | None = Field(
|
||||
default=None,
|
||||
description="Download URL — placeholder media ID URL after normalization, presigned MinIO URL after storage",
|
||||
)
|
||||
storage_key: str | None = Field(
|
||||
default=None,
|
||||
description="MinIO object key: {tenant_id}/{agent_id}/{message_id}/{filename}",
|
||||
)
|
||||
mime_type: str | None = Field(default=None, description="MIME type (e.g. image/jpeg)")
|
||||
filename: str | None = Field(default=None, description="Original filename if available")
|
||||
size_bytes: int | None = Field(default=None, description="File size in bytes if available")
|
||||
|
||||
|
||||
class SenderInfo(BaseModel):
|
||||
"""Information about the message sender."""
|
||||
|
||||
@@ -50,6 +82,10 @@ class MessageContent(BaseModel):
|
||||
default_factory=list,
|
||||
description="List of user/bot IDs mentioned in the message",
|
||||
)
|
||||
media: list[MediaAttachment] = Field(
|
||||
default_factory=list,
|
||||
description="Typed media attachments (images, documents, audio, video)",
|
||||
)
|
||||
|
||||
|
||||
class KonstructMessage(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user