"""媒体资源 DTO。 定义媒体上传/下载/元数据查询的数据传输对象,覆盖图片/文件/音频/视频 媒体资源的通用契约。所有渠道插件共享统一媒体 DTO,渠道插件实现 ``InboundAdapter.downloadAttachment`` 或 ``OutboundAdapter.sendMessage`` 时可使用这些 DTO 作为内部类型。 """ from __future__ import annotations from dataclasses import dataclass from datetime import datetime from enum import StrEnum from yuxi.channels.contract.errors import ValidationError class MediaType(StrEnum): """媒体类型枚举。""" IMAGE = "image" FILE = "file" AUDIO = "audio" VIDEO = "video" @dataclass(frozen=True) class MediaUploadResult: """媒体上传结果。 字段: media_id: 渠道侧媒体 ID(如飞书 image_key / file_key)。 url: 可访问的媒体 URL(部分渠道返回,可为 None)。 expires_at: 媒体资源过期时间(UTC),不过期时为 None。 """ media_id: str url: str | None = None expires_at: datetime | None = None def __post_init__(self) -> None: """校验 media_id 非空。 ``media_id`` 必须非空,在构造时即抛出 ``ValidationError``,避免 空媒体 ID 导致后续上传结果无法定位(INV-8)。 """ if not self.media_id: raise ValidationError("media_id", "must not be empty") @dataclass(frozen=True) class MediaDownloadResult: """媒体下载结果。 字段: content: 媒体二进制内容。 filename: 文件名(含扩展名),渠道未返回时为 None。 mime_type: MIME 类型(如 image/png),渠道未返回时为 None。 size: 内容字节数。 """ content: bytes size: int filename: str | None = None mime_type: str | None = None @dataclass(frozen=True) class MediaMetadata: """媒体元数据。 字段: media_id: 渠道侧媒体 ID。 media_type: 媒体类型。 size: 内容字节数。 mime_type: MIME 类型,渠道未返回时为 None。 created_at: 媒体创建时间(UTC),渠道未返回时为 None。 """ media_id: str media_type: MediaType size: int mime_type: str | None = None created_at: datetime | None = None def __post_init__(self) -> None: """校验 media_id 非空与 size 非负。 ``media_id`` 必须非空,``size`` 必须非负,在构造时即抛出 ``ValidationError``,避免空媒体 ID 或负字节数导致元数据失效(INV-8)。 """ if not self.media_id: raise ValidationError("media_id", "must not be empty") if self.size < 0: raise ValidationError("size", "must not be negative") @dataclass(frozen=True) class AttachmentUploadResult: """附件上传结果(MSG-ATTACH-UPLOAD)。 描述通过插件 ``AttachmentUploadAdapter`` 适配器上传附件后的返回结果,包括 渠道侧附件 ID、可访问 URL、MIME 类型与字节数,由 ``MessageManagementPort.uploadAttachment`` 引用(FR-12)。 字段: attachment_id: 渠道侧附件 ID。 url: 可访问的附件 URL。 mime_type: MIME 类型(如 image/png)。 size_bytes: 内容字节数。 expires_at: 附件资源过期时间(UTC),不过期时为 None。 """ attachment_id: str url: str mime_type: str size_bytes: int expires_at: datetime | None = None def __post_init__(self) -> None: """校验 attachment_id / url / mime_type 非空与 size_bytes 为正整数。 必填字符串字段必须非空,``size_bytes`` 必须为正整数,在构造时即 抛出 ``ValidationError``,避免空附件 ID / URL 或非正字节数导致上传 结果失效(INV-8)。 """ if not self.attachment_id: raise ValidationError("attachment_id", "must not be empty") if not self.url: raise ValidationError("url", "must not be empty") if not self.mime_type: raise ValidationError("mime_type", "must not be empty") if self.size_bytes <= 0: raise ValidationError("size_bytes", "must be a positive integer")