wappa 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wappa might be problematic. Click here for more details.

Files changed (211) hide show
  1. wappa/__init__.py +85 -0
  2. wappa/api/__init__.py +1 -0
  3. wappa/api/controllers/__init__.py +10 -0
  4. wappa/api/controllers/webhook_controller.py +441 -0
  5. wappa/api/dependencies/__init__.py +15 -0
  6. wappa/api/dependencies/whatsapp_dependencies.py +220 -0
  7. wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
  8. wappa/api/middleware/__init__.py +7 -0
  9. wappa/api/middleware/error_handler.py +158 -0
  10. wappa/api/middleware/owner.py +99 -0
  11. wappa/api/middleware/request_logging.py +184 -0
  12. wappa/api/routes/__init__.py +6 -0
  13. wappa/api/routes/health.py +102 -0
  14. wappa/api/routes/webhooks.py +211 -0
  15. wappa/api/routes/whatsapp/__init__.py +15 -0
  16. wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
  17. wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
  18. wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
  19. wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
  20. wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
  21. wappa/api/routes/whatsapp_combined.py +35 -0
  22. wappa/cli/__init__.py +9 -0
  23. wappa/cli/main.py +199 -0
  24. wappa/core/__init__.py +6 -0
  25. wappa/core/config/__init__.py +5 -0
  26. wappa/core/config/settings.py +161 -0
  27. wappa/core/events/__init__.py +41 -0
  28. wappa/core/events/default_handlers.py +642 -0
  29. wappa/core/events/event_dispatcher.py +244 -0
  30. wappa/core/events/event_handler.py +247 -0
  31. wappa/core/events/webhook_factory.py +219 -0
  32. wappa/core/factory/__init__.py +15 -0
  33. wappa/core/factory/plugin.py +68 -0
  34. wappa/core/factory/wappa_builder.py +326 -0
  35. wappa/core/logging/__init__.py +5 -0
  36. wappa/core/logging/context.py +100 -0
  37. wappa/core/logging/logger.py +343 -0
  38. wappa/core/plugins/__init__.py +34 -0
  39. wappa/core/plugins/auth_plugin.py +169 -0
  40. wappa/core/plugins/cors_plugin.py +128 -0
  41. wappa/core/plugins/custom_middleware_plugin.py +182 -0
  42. wappa/core/plugins/database_plugin.py +235 -0
  43. wappa/core/plugins/rate_limit_plugin.py +183 -0
  44. wappa/core/plugins/redis_plugin.py +224 -0
  45. wappa/core/plugins/wappa_core_plugin.py +261 -0
  46. wappa/core/plugins/webhook_plugin.py +253 -0
  47. wappa/core/types.py +108 -0
  48. wappa/core/wappa_app.py +546 -0
  49. wappa/database/__init__.py +18 -0
  50. wappa/database/adapter.py +107 -0
  51. wappa/database/adapters/__init__.py +17 -0
  52. wappa/database/adapters/mysql_adapter.py +187 -0
  53. wappa/database/adapters/postgresql_adapter.py +169 -0
  54. wappa/database/adapters/sqlite_adapter.py +174 -0
  55. wappa/domain/__init__.py +28 -0
  56. wappa/domain/builders/__init__.py +5 -0
  57. wappa/domain/builders/message_builder.py +189 -0
  58. wappa/domain/entities/__init__.py +5 -0
  59. wappa/domain/enums/messenger_platform.py +123 -0
  60. wappa/domain/factories/__init__.py +6 -0
  61. wappa/domain/factories/media_factory.py +450 -0
  62. wappa/domain/factories/message_factory.py +497 -0
  63. wappa/domain/factories/messenger_factory.py +244 -0
  64. wappa/domain/interfaces/__init__.py +32 -0
  65. wappa/domain/interfaces/base_repository.py +94 -0
  66. wappa/domain/interfaces/cache_factory.py +85 -0
  67. wappa/domain/interfaces/cache_interface.py +199 -0
  68. wappa/domain/interfaces/expiry_repository.py +68 -0
  69. wappa/domain/interfaces/media_interface.py +311 -0
  70. wappa/domain/interfaces/messaging_interface.py +523 -0
  71. wappa/domain/interfaces/pubsub_repository.py +151 -0
  72. wappa/domain/interfaces/repository_factory.py +108 -0
  73. wappa/domain/interfaces/shared_state_repository.py +122 -0
  74. wappa/domain/interfaces/state_repository.py +123 -0
  75. wappa/domain/interfaces/tables_repository.py +215 -0
  76. wappa/domain/interfaces/user_repository.py +114 -0
  77. wappa/domain/interfaces/webhooks/__init__.py +1 -0
  78. wappa/domain/models/media_result.py +110 -0
  79. wappa/domain/models/platforms/__init__.py +15 -0
  80. wappa/domain/models/platforms/platform_config.py +104 -0
  81. wappa/domain/services/__init__.py +11 -0
  82. wappa/domain/services/tenant_credentials_service.py +56 -0
  83. wappa/messaging/__init__.py +7 -0
  84. wappa/messaging/whatsapp/__init__.py +1 -0
  85. wappa/messaging/whatsapp/client/__init__.py +5 -0
  86. wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
  87. wappa/messaging/whatsapp/handlers/__init__.py +13 -0
  88. wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
  89. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
  90. wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
  91. wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
  92. wappa/messaging/whatsapp/messenger/__init__.py +5 -0
  93. wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
  94. wappa/messaging/whatsapp/models/__init__.py +61 -0
  95. wappa/messaging/whatsapp/models/basic_models.py +65 -0
  96. wappa/messaging/whatsapp/models/interactive_models.py +287 -0
  97. wappa/messaging/whatsapp/models/media_models.py +215 -0
  98. wappa/messaging/whatsapp/models/specialized_models.py +304 -0
  99. wappa/messaging/whatsapp/models/template_models.py +261 -0
  100. wappa/persistence/cache_factory.py +93 -0
  101. wappa/persistence/json/__init__.py +14 -0
  102. wappa/persistence/json/cache_adapters.py +271 -0
  103. wappa/persistence/json/handlers/__init__.py +1 -0
  104. wappa/persistence/json/handlers/state_handler.py +250 -0
  105. wappa/persistence/json/handlers/table_handler.py +263 -0
  106. wappa/persistence/json/handlers/user_handler.py +213 -0
  107. wappa/persistence/json/handlers/utils/__init__.py +1 -0
  108. wappa/persistence/json/handlers/utils/file_manager.py +153 -0
  109. wappa/persistence/json/handlers/utils/key_factory.py +11 -0
  110. wappa/persistence/json/handlers/utils/serialization.py +121 -0
  111. wappa/persistence/json/json_cache_factory.py +76 -0
  112. wappa/persistence/json/storage_manager.py +285 -0
  113. wappa/persistence/memory/__init__.py +14 -0
  114. wappa/persistence/memory/cache_adapters.py +271 -0
  115. wappa/persistence/memory/handlers/__init__.py +1 -0
  116. wappa/persistence/memory/handlers/state_handler.py +250 -0
  117. wappa/persistence/memory/handlers/table_handler.py +280 -0
  118. wappa/persistence/memory/handlers/user_handler.py +213 -0
  119. wappa/persistence/memory/handlers/utils/__init__.py +1 -0
  120. wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
  121. wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
  122. wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
  123. wappa/persistence/memory/memory_cache_factory.py +76 -0
  124. wappa/persistence/memory/storage_manager.py +235 -0
  125. wappa/persistence/redis/README.md +699 -0
  126. wappa/persistence/redis/__init__.py +11 -0
  127. wappa/persistence/redis/cache_adapters.py +285 -0
  128. wappa/persistence/redis/ops.py +880 -0
  129. wappa/persistence/redis/redis_cache_factory.py +71 -0
  130. wappa/persistence/redis/redis_client.py +231 -0
  131. wappa/persistence/redis/redis_handler/__init__.py +26 -0
  132. wappa/persistence/redis/redis_handler/state_handler.py +176 -0
  133. wappa/persistence/redis/redis_handler/table.py +158 -0
  134. wappa/persistence/redis/redis_handler/user.py +138 -0
  135. wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
  136. wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
  137. wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
  138. wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
  139. wappa/persistence/redis/redis_manager.py +189 -0
  140. wappa/processors/__init__.py +6 -0
  141. wappa/processors/base_processor.py +262 -0
  142. wappa/processors/factory.py +550 -0
  143. wappa/processors/whatsapp_processor.py +810 -0
  144. wappa/schemas/__init__.py +6 -0
  145. wappa/schemas/core/__init__.py +71 -0
  146. wappa/schemas/core/base_message.py +499 -0
  147. wappa/schemas/core/base_status.py +322 -0
  148. wappa/schemas/core/base_webhook.py +312 -0
  149. wappa/schemas/core/types.py +253 -0
  150. wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
  151. wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
  152. wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
  153. wappa/schemas/factory.py +754 -0
  154. wappa/schemas/webhooks/__init__.py +3 -0
  155. wappa/schemas/whatsapp/__init__.py +6 -0
  156. wappa/schemas/whatsapp/base_models.py +285 -0
  157. wappa/schemas/whatsapp/message_types/__init__.py +93 -0
  158. wappa/schemas/whatsapp/message_types/audio.py +350 -0
  159. wappa/schemas/whatsapp/message_types/button.py +267 -0
  160. wappa/schemas/whatsapp/message_types/contact.py +464 -0
  161. wappa/schemas/whatsapp/message_types/document.py +421 -0
  162. wappa/schemas/whatsapp/message_types/errors.py +195 -0
  163. wappa/schemas/whatsapp/message_types/image.py +424 -0
  164. wappa/schemas/whatsapp/message_types/interactive.py +430 -0
  165. wappa/schemas/whatsapp/message_types/location.py +416 -0
  166. wappa/schemas/whatsapp/message_types/order.py +372 -0
  167. wappa/schemas/whatsapp/message_types/reaction.py +271 -0
  168. wappa/schemas/whatsapp/message_types/sticker.py +328 -0
  169. wappa/schemas/whatsapp/message_types/system.py +317 -0
  170. wappa/schemas/whatsapp/message_types/text.py +411 -0
  171. wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
  172. wappa/schemas/whatsapp/message_types/video.py +344 -0
  173. wappa/schemas/whatsapp/status_models.py +479 -0
  174. wappa/schemas/whatsapp/validators.py +454 -0
  175. wappa/schemas/whatsapp/webhook_container.py +438 -0
  176. wappa/webhooks/__init__.py +17 -0
  177. wappa/webhooks/core/__init__.py +71 -0
  178. wappa/webhooks/core/base_message.py +499 -0
  179. wappa/webhooks/core/base_status.py +322 -0
  180. wappa/webhooks/core/base_webhook.py +312 -0
  181. wappa/webhooks/core/types.py +253 -0
  182. wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
  183. wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
  184. wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
  185. wappa/webhooks/factory.py +754 -0
  186. wappa/webhooks/whatsapp/__init__.py +6 -0
  187. wappa/webhooks/whatsapp/base_models.py +285 -0
  188. wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
  189. wappa/webhooks/whatsapp/message_types/audio.py +350 -0
  190. wappa/webhooks/whatsapp/message_types/button.py +267 -0
  191. wappa/webhooks/whatsapp/message_types/contact.py +464 -0
  192. wappa/webhooks/whatsapp/message_types/document.py +421 -0
  193. wappa/webhooks/whatsapp/message_types/errors.py +195 -0
  194. wappa/webhooks/whatsapp/message_types/image.py +424 -0
  195. wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
  196. wappa/webhooks/whatsapp/message_types/location.py +416 -0
  197. wappa/webhooks/whatsapp/message_types/order.py +372 -0
  198. wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
  199. wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
  200. wappa/webhooks/whatsapp/message_types/system.py +317 -0
  201. wappa/webhooks/whatsapp/message_types/text.py +411 -0
  202. wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
  203. wappa/webhooks/whatsapp/message_types/video.py +344 -0
  204. wappa/webhooks/whatsapp/status_models.py +479 -0
  205. wappa/webhooks/whatsapp/validators.py +454 -0
  206. wappa/webhooks/whatsapp/webhook_container.py +438 -0
  207. wappa-0.1.0.dist-info/METADATA +269 -0
  208. wappa-0.1.0.dist-info/RECORD +211 -0
  209. wappa-0.1.0.dist-info/WHEEL +4 -0
  210. wappa-0.1.0.dist-info/entry_points.txt +2 -0
  211. wappa-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,344 @@
1
+ """
2
+ WhatsApp video message schema.
3
+
4
+ This module contains Pydantic models for processing WhatsApp video messages,
5
+ including video files, forwarded videos, and videos sent via Click-to-WhatsApp ads.
6
+ """
7
+
8
+ from typing import Any, Literal
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
11
+
12
+ from wappa.webhooks.core.base_message import BaseMessageContext, BaseVideoMessage
13
+ from wappa.webhooks.core.types import (
14
+ ConversationType,
15
+ MediaType,
16
+ PlatformType,
17
+ UniversalMessageData,
18
+ )
19
+ from wappa.webhooks.whatsapp.base_models import AdReferral, MessageContext
20
+
21
+
22
+ class VideoContent(BaseModel):
23
+ """Video message content."""
24
+
25
+ model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
26
+
27
+ caption: str | None = Field(None, description="Video caption text (optional)")
28
+ mime_type: str = Field(
29
+ ..., description="MIME type of the video file (e.g., 'video/mp4')"
30
+ )
31
+ sha256: str = Field(..., description="SHA-256 hash of the video file")
32
+ id: str = Field(..., description="Media asset ID for retrieving the video file")
33
+
34
+ @field_validator("caption")
35
+ @classmethod
36
+ def validate_caption(cls, v: str | None) -> str | None:
37
+ """Validate video caption if present."""
38
+ if v is not None:
39
+ v = v.strip()
40
+ if not v:
41
+ return None
42
+ if len(v) > 1024: # WhatsApp caption limit
43
+ raise ValueError("Video caption cannot exceed 1024 characters")
44
+ return v
45
+
46
+ @field_validator("mime_type")
47
+ @classmethod
48
+ def validate_mime_type(cls, v: str) -> str:
49
+ """Validate video MIME type format."""
50
+ if not v.startswith("video/"):
51
+ raise ValueError("Video MIME type must start with 'video/'")
52
+ return v.lower()
53
+
54
+ @field_validator("id")
55
+ @classmethod
56
+ def validate_media_id(cls, v: str) -> str:
57
+ """Validate media asset ID."""
58
+ if not v or len(v) < 10:
59
+ raise ValueError("Media asset ID must be at least 10 characters")
60
+ return v
61
+
62
+
63
+ class WhatsAppVideoMessage(BaseVideoMessage):
64
+ """
65
+ WhatsApp video message model.
66
+
67
+ Supports various video message scenarios:
68
+ - Regular video uploads
69
+ - Forwarded video messages
70
+ - Click-to-WhatsApp ad video messages
71
+ """
72
+
73
+ model_config = ConfigDict(
74
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
75
+ )
76
+
77
+ # Standard message fields
78
+ from_: str = Field(
79
+ ..., alias="from", description="WhatsApp user phone number who sent the message"
80
+ )
81
+ id: str = Field(..., description="Unique WhatsApp message ID")
82
+ timestamp_str: str = Field(
83
+ ..., alias="timestamp", description="Unix timestamp when the message was sent"
84
+ )
85
+ type: Literal["video"] = Field(
86
+ ..., description="Message type, always 'video' for video messages"
87
+ )
88
+
89
+ # Video content
90
+ video: VideoContent = Field(..., description="Video message content and metadata")
91
+
92
+ # Optional context fields
93
+ context: MessageContext | None = Field(
94
+ None, description="Context for forwards (video messages don't support replies)"
95
+ )
96
+ referral: AdReferral | None = Field(
97
+ None, description="Click-to-WhatsApp ad referral information"
98
+ )
99
+
100
+ @field_validator("from_")
101
+ @classmethod
102
+ def validate_from_phone(cls, v: str) -> str:
103
+ """Validate sender phone number format."""
104
+ if not v or len(v) < 8:
105
+ raise ValueError("Sender phone number must be at least 8 characters")
106
+ # Remove common prefixes and validate numeric
107
+ phone = v.replace("+", "").replace("-", "").replace(" ", "")
108
+ if not phone.isdigit():
109
+ raise ValueError("Phone number must contain only digits (and +)")
110
+ return v
111
+
112
+ @field_validator("id")
113
+ @classmethod
114
+ def validate_message_id(cls, v: str) -> str:
115
+ """Validate WhatsApp message ID format."""
116
+ if not v or len(v) < 10:
117
+ raise ValueError("WhatsApp message ID must be at least 10 characters")
118
+ # WhatsApp message IDs typically start with 'wamid.'
119
+ if not v.startswith("wamid."):
120
+ raise ValueError("WhatsApp message ID should start with 'wamid.'")
121
+ return v
122
+
123
+ @field_validator("timestamp_str")
124
+ @classmethod
125
+ def validate_timestamp(cls, v: str) -> str:
126
+ """Validate Unix timestamp format."""
127
+ if not v.isdigit():
128
+ raise ValueError("Timestamp must be numeric")
129
+ # Validate reasonable timestamp range (after 2020, before 2100)
130
+ timestamp_int = int(v)
131
+ if timestamp_int < 1577836800 or timestamp_int > 4102444800:
132
+ raise ValueError("Timestamp must be a valid Unix timestamp")
133
+ return v
134
+
135
+ @model_validator(mode="after")
136
+ def validate_message_consistency(self):
137
+ """Validate message field consistency."""
138
+ # If we have a referral, this should be from an ad
139
+ if self.referral and self.context:
140
+ raise ValueError(
141
+ "Message cannot have both referral (ad) and context (forward)"
142
+ )
143
+
144
+ # Video messages typically only support forwarding context, not replies
145
+ if (
146
+ self.context
147
+ and self.context.id
148
+ and not (self.context.forwarded or self.context.frequently_forwarded)
149
+ ):
150
+ raise ValueError(
151
+ "Video messages do not support reply context, only forwarding"
152
+ )
153
+
154
+ return self
155
+
156
+ @property
157
+ def is_forwarded(self) -> bool:
158
+ """Check if this video message was forwarded."""
159
+ return self.context is not None and (
160
+ self.context.forwarded or self.context.frequently_forwarded
161
+ )
162
+
163
+ @property
164
+ def is_frequently_forwarded(self) -> bool:
165
+ """Check if this video message was forwarded more than 5 times."""
166
+ return self.context is not None and self.context.frequently_forwarded is True
167
+
168
+ @property
169
+ def has_caption(self) -> bool:
170
+ """Check if this video has a caption."""
171
+ return self.video.caption is not None and len(self.video.caption.strip()) > 0
172
+
173
+ @property
174
+ def is_ad_message(self) -> bool:
175
+ """Check if this video message came from a Click-to-WhatsApp ad."""
176
+ return self.referral is not None
177
+
178
+ @property
179
+ def sender_phone(self) -> str:
180
+ """Get the sender's phone number (clean accessor)."""
181
+ return self.from_
182
+
183
+ @property
184
+ def media_id(self) -> str:
185
+ """Get the media asset ID for downloading the video file."""
186
+ return self.video.id
187
+
188
+ @property
189
+ def mime_type(self) -> str:
190
+ """Get the video MIME type."""
191
+ return self.video.mime_type
192
+
193
+ @property
194
+ def file_hash(self) -> str:
195
+ """Get the SHA-256 hash of the video file."""
196
+ return self.video.sha256
197
+
198
+ @property
199
+ def caption(self) -> str | None:
200
+ """Get the video caption."""
201
+ return self.video.caption
202
+
203
+ @property
204
+ def unix_timestamp(self) -> int:
205
+ """Get the timestamp as an integer."""
206
+ return self.timestamp
207
+
208
+ def get_ad_context(self) -> tuple[str | None, str | None]:
209
+ """
210
+ Get ad context information for Click-to-WhatsApp video messages.
211
+
212
+ Returns:
213
+ Tuple of (ad_id, ad_click_id) if this came from an ad,
214
+ (None, None) otherwise.
215
+ """
216
+ if self.is_ad_message and self.referral:
217
+ return (self.referral.source_id, self.referral.ctwa_clid)
218
+ return (None, None)
219
+
220
+ def to_summary_dict(self) -> dict[str, str | bool | int]:
221
+ """
222
+ Create a summary dictionary for logging and analysis.
223
+
224
+ Returns:
225
+ Dictionary with key message information for structured logging.
226
+ """
227
+ return {
228
+ "message_id": self.id,
229
+ "sender": self.sender_phone,
230
+ "timestamp": self.unix_timestamp,
231
+ "type": self.type,
232
+ "media_id": self.media_id,
233
+ "mime_type": self.mime_type,
234
+ "has_caption": self.has_caption,
235
+ "caption_length": len(self.caption) if self.caption else 0,
236
+ "is_forwarded": self.is_forwarded,
237
+ "is_frequently_forwarded": self.is_frequently_forwarded,
238
+ "is_ad_message": self.is_ad_message,
239
+ }
240
+
241
+ # Implement abstract methods from BaseMessage
242
+ @property
243
+ def platform(self) -> PlatformType:
244
+ return PlatformType.WHATSAPP
245
+
246
+ @property
247
+ def message_id(self) -> str:
248
+ return self.id
249
+
250
+ @property
251
+ def sender_id(self) -> str:
252
+ return self.from_
253
+
254
+ @property
255
+ def timestamp(self) -> int:
256
+ return int(self.timestamp_str)
257
+
258
+ @property
259
+ def conversation_id(self) -> str:
260
+ return self.from_
261
+
262
+ @property
263
+ def conversation_type(self) -> ConversationType:
264
+ return ConversationType.PRIVATE
265
+
266
+ def has_context(self) -> bool:
267
+ return self.context is not None
268
+
269
+ def get_context(self) -> BaseMessageContext | None:
270
+ from .text import WhatsAppMessageContext
271
+
272
+ return WhatsAppMessageContext(self.context) if self.context else None
273
+
274
+ def to_universal_dict(self) -> UniversalMessageData:
275
+ return {
276
+ "platform": self.platform.value,
277
+ "message_type": self.message_type.value,
278
+ "message_id": self.message_id,
279
+ "sender_id": self.sender_id,
280
+ "conversation_id": self.conversation_id,
281
+ "timestamp": self.timestamp,
282
+ "processed_at": self.processed_at.isoformat(),
283
+ "has_context": self.has_context(),
284
+ "media_id": self.media_id,
285
+ "media_type": self.media_type.value,
286
+ "caption": self.caption,
287
+ "duration": self.duration,
288
+ "dimensions": self.dimensions,
289
+ "whatsapp_data": {
290
+ "whatsapp_id": self.id,
291
+ "from": self.from_,
292
+ "type": self.type,
293
+ "video_content": self.video.model_dump(),
294
+ },
295
+ }
296
+
297
+ def get_platform_data(self) -> dict[str, Any]:
298
+ return {
299
+ "whatsapp_message_id": self.id,
300
+ "video_content": self.video.model_dump(),
301
+ }
302
+
303
+ # Implement abstract methods from BaseMediaMessage
304
+ @property
305
+ def media_id(self) -> str:
306
+ return self.video.id
307
+
308
+ @property
309
+ def media_type(self) -> MediaType:
310
+ try:
311
+ return MediaType(self.video.mime_type)
312
+ except ValueError:
313
+ return MediaType.VIDEO_MP4
314
+
315
+ @property
316
+ def file_size(self) -> int | None:
317
+ return None
318
+
319
+ @property
320
+ def caption(self) -> str | None:
321
+ return self.video.caption
322
+
323
+ def get_download_info(self) -> dict[str, Any]:
324
+ return {
325
+ "media_id": self.media_id,
326
+ "mime_type": self.media_type.value,
327
+ "sha256": self.video.sha256,
328
+ "platform": "whatsapp",
329
+ }
330
+
331
+ # Implement abstract methods from BaseVideoMessage
332
+ @property
333
+ def duration(self) -> int | None:
334
+ return None
335
+
336
+ @property
337
+ def dimensions(self) -> tuple[int, int] | None:
338
+ return None
339
+
340
+ @classmethod
341
+ def from_platform_data(
342
+ cls, data: dict[str, Any], **kwargs
343
+ ) -> "WhatsAppVideoMessage":
344
+ return cls.model_validate(data)