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,450 @@
1
+ """
2
+ Media factory pattern for creating platform-specific media message objects.
3
+
4
+ This factory creates properly formatted media message objects based on
5
+ existing payload generation logic from whatsapp_latest/services/handle_media.py.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any
10
+
11
+ from wappa.schemas.core.types import PlatformType
12
+
13
+
14
+ class MediaFactory(ABC):
15
+ """
16
+ Abstract factory for creating platform-specific media message objects.
17
+
18
+ This factory creates properly formatted media message objects that can be
19
+ sent through the IMessenger interface while maintaining platform
20
+ compatibility and type safety.
21
+
22
+ Based on existing payload generation patterns from handle_media.py.
23
+ """
24
+
25
+ @property
26
+ @abstractmethod
27
+ def platform(self) -> PlatformType:
28
+ """Get the platform this factory creates messages for."""
29
+ pass
30
+
31
+ # Media Message Creation Methods
32
+ @abstractmethod
33
+ def create_image_message(
34
+ self,
35
+ media_reference: str,
36
+ recipient: str,
37
+ caption: str | None = None,
38
+ reply_to_message_id: str | None = None,
39
+ is_url: bool = False,
40
+ ) -> dict[str, Any]:
41
+ """Create an image message payload.
42
+
43
+ Args:
44
+ media_reference: Media ID or URL
45
+ recipient: Recipient identifier
46
+ caption: Optional caption for the image
47
+ reply_to_message_id: Optional message ID to reply to
48
+ is_url: Whether media_reference is a URL (True) or media ID (False)
49
+
50
+ Returns:
51
+ Platform-specific image message payload
52
+ """
53
+ pass
54
+
55
+ @abstractmethod
56
+ def create_video_message(
57
+ self,
58
+ media_reference: str,
59
+ recipient: str,
60
+ caption: str | None = None,
61
+ reply_to_message_id: str | None = None,
62
+ is_url: bool = False,
63
+ ) -> dict[str, Any]:
64
+ """Create a video message payload.
65
+
66
+ Args:
67
+ media_reference: Media ID or URL
68
+ recipient: Recipient identifier
69
+ caption: Optional caption for the video
70
+ reply_to_message_id: Optional message ID to reply to
71
+ is_url: Whether media_reference is a URL (True) or media ID (False)
72
+
73
+ Returns:
74
+ Platform-specific video message payload
75
+ """
76
+ pass
77
+
78
+ @abstractmethod
79
+ def create_audio_message(
80
+ self,
81
+ media_reference: str,
82
+ recipient: str,
83
+ reply_to_message_id: str | None = None,
84
+ is_url: bool = False,
85
+ ) -> dict[str, Any]:
86
+ """Create an audio message payload.
87
+
88
+ Args:
89
+ media_reference: Media ID or URL
90
+ recipient: Recipient identifier
91
+ reply_to_message_id: Optional message ID to reply to
92
+ is_url: Whether media_reference is a URL (True) or media ID (False)
93
+
94
+ Returns:
95
+ Platform-specific audio message payload
96
+ """
97
+ pass
98
+
99
+ @abstractmethod
100
+ def create_document_message(
101
+ self,
102
+ media_reference: str,
103
+ recipient: str,
104
+ filename: str | None = None,
105
+ reply_to_message_id: str | None = None,
106
+ is_url: bool = False,
107
+ ) -> dict[str, Any]:
108
+ """Create a document message payload.
109
+
110
+ Args:
111
+ media_reference: Media ID or URL
112
+ recipient: Recipient identifier
113
+ filename: Optional filename for the document
114
+ reply_to_message_id: Optional message ID to reply to
115
+ is_url: Whether media_reference is a URL (True) or media ID (False)
116
+
117
+ Returns:
118
+ Platform-specific document message payload
119
+ """
120
+ pass
121
+
122
+ @abstractmethod
123
+ def create_sticker_message(
124
+ self,
125
+ media_reference: str,
126
+ recipient: str,
127
+ reply_to_message_id: str | None = None,
128
+ is_url: bool = False,
129
+ ) -> dict[str, Any]:
130
+ """Create a sticker message payload.
131
+
132
+ Args:
133
+ media_reference: Media ID or URL
134
+ recipient: Recipient identifier
135
+ reply_to_message_id: Optional message ID to reply to
136
+ is_url: Whether media_reference is a URL (True) or media ID (False)
137
+
138
+ Returns:
139
+ Platform-specific sticker message payload
140
+ """
141
+ pass
142
+
143
+ # Validation Methods
144
+ @abstractmethod
145
+ def validate_media_message(self, message_payload: dict[str, Any]) -> bool:
146
+ """Validate media message payload against platform constraints.
147
+
148
+ Args:
149
+ message_payload: Media message payload to validate
150
+
151
+ Returns:
152
+ True if payload is valid, False otherwise
153
+ """
154
+ pass
155
+
156
+ @abstractmethod
157
+ def get_media_limits(self) -> dict[str, Any]:
158
+ """Get platform-specific media limits.
159
+
160
+ Returns:
161
+ Dictionary containing platform-specific media limits
162
+ """
163
+ pass
164
+
165
+
166
+ class WhatsAppMediaFactory(MediaFactory):
167
+ """WhatsApp implementation of the media factory.
168
+
169
+ Based on existing payload generation patterns from
170
+ whatsapp_latest/services/handle_media.py send_media() method.
171
+ """
172
+
173
+ @property
174
+ def platform(self) -> PlatformType:
175
+ """Get the platform this factory creates messages for."""
176
+ return PlatformType.WHATSAPP
177
+
178
+ def create_image_message(
179
+ self,
180
+ media_reference: str,
181
+ recipient: str,
182
+ caption: str | None = None,
183
+ reply_to_message_id: str | None = None,
184
+ is_url: bool = False,
185
+ ) -> dict[str, Any]:
186
+ """Create WhatsApp image message payload.
187
+
188
+ Based on existing send_media() logic for MediaType.IMAGE.
189
+ """
190
+ payload = {
191
+ "messaging_product": "whatsapp",
192
+ "recipient_type": "individual",
193
+ "to": recipient,
194
+ "type": "image",
195
+ }
196
+
197
+ # Create media object based on reference type
198
+ if is_url:
199
+ media_obj = {"link": media_reference}
200
+ else:
201
+ media_obj = {"id": media_reference}
202
+
203
+ # Add optional caption
204
+ if caption:
205
+ media_obj["caption"] = caption
206
+
207
+ payload["image"] = media_obj
208
+
209
+ # Add reply context if specified
210
+ if reply_to_message_id:
211
+ payload["context"] = {"message_id": reply_to_message_id}
212
+
213
+ return payload
214
+
215
+ def create_video_message(
216
+ self,
217
+ media_reference: str,
218
+ recipient: str,
219
+ caption: str | None = None,
220
+ reply_to_message_id: str | None = None,
221
+ is_url: bool = False,
222
+ ) -> dict[str, Any]:
223
+ """Create WhatsApp video message payload.
224
+
225
+ Based on existing send_media() logic for MediaType.VIDEO.
226
+ """
227
+ payload = {
228
+ "messaging_product": "whatsapp",
229
+ "recipient_type": "individual",
230
+ "to": recipient,
231
+ "type": "video",
232
+ }
233
+
234
+ # Create media object based on reference type
235
+ if is_url:
236
+ media_obj = {"link": media_reference}
237
+ else:
238
+ media_obj = {"id": media_reference}
239
+
240
+ # Add optional caption
241
+ if caption:
242
+ media_obj["caption"] = caption
243
+
244
+ payload["video"] = media_obj
245
+
246
+ # Add reply context if specified
247
+ if reply_to_message_id:
248
+ payload["context"] = {"message_id": reply_to_message_id}
249
+
250
+ return payload
251
+
252
+ def create_audio_message(
253
+ self,
254
+ media_reference: str,
255
+ recipient: str,
256
+ reply_to_message_id: str | None = None,
257
+ is_url: bool = False,
258
+ ) -> dict[str, Any]:
259
+ """Create WhatsApp audio message payload.
260
+
261
+ Based on existing send_media() logic for MediaType.AUDIO.
262
+ Note: Audio messages do not support captions.
263
+ """
264
+ payload = {
265
+ "messaging_product": "whatsapp",
266
+ "recipient_type": "individual",
267
+ "to": recipient,
268
+ "type": "audio",
269
+ }
270
+
271
+ # Create media object based on reference type
272
+ if is_url:
273
+ media_obj = {"link": media_reference}
274
+ else:
275
+ media_obj = {"id": media_reference}
276
+
277
+ payload["audio"] = media_obj
278
+
279
+ # Add reply context if specified
280
+ if reply_to_message_id:
281
+ payload["context"] = {"message_id": reply_to_message_id}
282
+
283
+ return payload
284
+
285
+ def create_document_message(
286
+ self,
287
+ media_reference: str,
288
+ recipient: str,
289
+ filename: str | None = None,
290
+ reply_to_message_id: str | None = None,
291
+ is_url: bool = False,
292
+ ) -> dict[str, Any]:
293
+ """Create WhatsApp document message payload.
294
+
295
+ Based on existing send_media() logic for MediaType.DOCUMENT.
296
+ """
297
+ payload = {
298
+ "messaging_product": "whatsapp",
299
+ "recipient_type": "individual",
300
+ "to": recipient,
301
+ "type": "document",
302
+ }
303
+
304
+ # Create media object based on reference type
305
+ if is_url:
306
+ media_obj = {"link": media_reference}
307
+ else:
308
+ media_obj = {"id": media_reference}
309
+
310
+ # Add optional filename
311
+ if filename:
312
+ media_obj["filename"] = filename
313
+
314
+ payload["document"] = media_obj
315
+
316
+ # Add reply context if specified
317
+ if reply_to_message_id:
318
+ payload["context"] = {"message_id": reply_to_message_id}
319
+
320
+ return payload
321
+
322
+ def create_sticker_message(
323
+ self,
324
+ media_reference: str,
325
+ recipient: str,
326
+ reply_to_message_id: str | None = None,
327
+ is_url: bool = False,
328
+ ) -> dict[str, Any]:
329
+ """Create WhatsApp sticker message payload.
330
+
331
+ Based on existing send_media() logic for MediaType.STICKER.
332
+ Note: Sticker messages do not support captions.
333
+ """
334
+ payload = {
335
+ "messaging_product": "whatsapp",
336
+ "recipient_type": "individual",
337
+ "to": recipient,
338
+ "type": "sticker",
339
+ }
340
+
341
+ # Create media object based on reference type
342
+ if is_url:
343
+ media_obj = {"link": media_reference}
344
+ else:
345
+ media_obj = {"id": media_reference}
346
+
347
+ payload["sticker"] = media_obj
348
+
349
+ # Add reply context if specified
350
+ if reply_to_message_id:
351
+ payload["context"] = {"message_id": reply_to_message_id}
352
+
353
+ return payload
354
+
355
+ def validate_media_message(self, message_payload: dict[str, Any]) -> bool:
356
+ """Validate WhatsApp media message payload.
357
+
358
+ Performs validation against WhatsApp Business API requirements.
359
+ """
360
+ try:
361
+ # Check required fields
362
+ if "messaging_product" not in message_payload:
363
+ return False
364
+ if message_payload["messaging_product"] != "whatsapp":
365
+ return False
366
+ if "to" not in message_payload:
367
+ return False
368
+ if "type" not in message_payload:
369
+ return False
370
+
371
+ message_type = message_payload["type"]
372
+ valid_types = ["image", "video", "audio", "document", "sticker"]
373
+ if message_type not in valid_types:
374
+ return False
375
+
376
+ # Check that media object exists
377
+ if message_type not in message_payload:
378
+ return False
379
+
380
+ media_obj = message_payload[message_type]
381
+
382
+ # Must have either 'id' or 'link'
383
+ if "id" not in media_obj and "link" not in media_obj:
384
+ return False
385
+
386
+ # Validate caption length if present
387
+ if "caption" in media_obj:
388
+ if len(media_obj["caption"]) > 1024:
389
+ return False
390
+ # Caption not allowed for audio and sticker
391
+ if message_type in ["audio", "sticker"]:
392
+ return False
393
+
394
+ return True
395
+
396
+ except (KeyError, TypeError):
397
+ return False
398
+
399
+ def get_media_limits(self) -> dict[str, Any]:
400
+ """Get WhatsApp-specific media limits.
401
+
402
+ Based on existing MediaType.get_max_file_size() and supported types.
403
+ """
404
+ return {
405
+ "max_caption_length": 1024,
406
+ "max_filename_length": 255,
407
+ "supported_media_types": {
408
+ "image": ["image/jpeg", "image/png"],
409
+ "video": ["video/mp4", "video/3gpp"],
410
+ "audio": [
411
+ "audio/aac",
412
+ "audio/amr",
413
+ "audio/mpeg",
414
+ "audio/mp4",
415
+ "audio/ogg",
416
+ ],
417
+ "document": [
418
+ "text/plain",
419
+ "application/pdf",
420
+ "application/vnd.ms-powerpoint",
421
+ "application/msword",
422
+ "application/vnd.ms-excel",
423
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
424
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
425
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
426
+ ],
427
+ "sticker": ["image/webp"],
428
+ },
429
+ "max_file_sizes": {
430
+ "image": 5 * 1024 * 1024, # 5MB
431
+ "video": 16 * 1024 * 1024, # 16MB
432
+ "audio": 16 * 1024 * 1024, # 16MB
433
+ "document": 100 * 1024 * 1024, # 100MB
434
+ "sticker": 500 * 1024, # 500KB (animated), 100KB (static)
435
+ },
436
+ "caption_support": {
437
+ "image": True,
438
+ "video": True,
439
+ "audio": False,
440
+ "document": False, # Uses filename instead
441
+ "sticker": False,
442
+ },
443
+ "filename_support": {
444
+ "document": True,
445
+ "image": False,
446
+ "video": False,
447
+ "audio": False,
448
+ "sticker": False,
449
+ },
450
+ }