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,497 @@
1
+ """
2
+ Message factory pattern for creating platform-specific message objects.
3
+
4
+ This factory creates properly formatted message objects that can be sent through
5
+ the IMessenger interface while maintaining platform compatibility and type safety.
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 MessageFactory(ABC):
15
+ """
16
+ Abstract factory for creating platform-specific message objects.
17
+
18
+ This factory creates properly formatted message objects that can be
19
+ sent through the IMessenger interface while maintaining platform
20
+ compatibility and type safety.
21
+
22
+ Supports messaging operations:
23
+ - Basic messages (create_text_message, create_read_status_message)
24
+ - Media messages (create_image_message, create_video_message, etc.)
25
+
26
+ Future implementations will add:
27
+ - Interactive messages (create_button_message, create_list_message, etc.)
28
+ - Specialized messages (create_contact_message, create_location_message, etc.)
29
+ """
30
+
31
+ @property
32
+ @abstractmethod
33
+ def platform(self) -> PlatformType:
34
+ """Get the platform this factory creates messages for."""
35
+ pass
36
+
37
+ # Basic Messages
38
+ @abstractmethod
39
+ def create_text_message(
40
+ self,
41
+ text: str,
42
+ recipient: str,
43
+ reply_to_message_id: str | None = None,
44
+ disable_preview: bool = False,
45
+ ) -> dict[str, Any]:
46
+ """Create a text message payload.
47
+
48
+ Args:
49
+ text: Text content of the message
50
+ recipient: Recipient identifier
51
+ reply_to_message_id: Optional message ID to reply to
52
+ disable_preview: Whether to disable URL preview
53
+
54
+ Returns:
55
+ Platform-specific message payload
56
+ """
57
+ pass
58
+
59
+ @abstractmethod
60
+ def create_read_status_message(
61
+ self, message_id: str, typing: bool = False
62
+ ) -> dict[str, Any]:
63
+ """Create a read status message payload.
64
+
65
+ Args:
66
+ message_id: Message ID to mark as read
67
+ typing: Whether to show typing indicator
68
+
69
+ Returns:
70
+ Platform-specific read status payload
71
+ """
72
+ pass
73
+
74
+ # Media Messages
75
+ @abstractmethod
76
+ def create_image_message(
77
+ self,
78
+ media_reference: str,
79
+ recipient: str,
80
+ caption: str | None = None,
81
+ reply_to_message_id: str | None = None,
82
+ is_url: bool = False,
83
+ ) -> dict[str, Any]:
84
+ """Create an image message payload.
85
+
86
+ Args:
87
+ media_reference: Media ID or URL
88
+ recipient: Recipient identifier
89
+ caption: Optional caption for the image
90
+ reply_to_message_id: Optional message ID to reply to
91
+ is_url: Whether media_reference is a URL (True) or media ID (False)
92
+
93
+ Returns:
94
+ Platform-specific image message payload
95
+ """
96
+ pass
97
+
98
+ @abstractmethod
99
+ def create_video_message(
100
+ self,
101
+ media_reference: str,
102
+ recipient: str,
103
+ caption: str | None = None,
104
+ reply_to_message_id: str | None = None,
105
+ is_url: bool = False,
106
+ ) -> dict[str, Any]:
107
+ """Create a video message payload.
108
+
109
+ Args:
110
+ media_reference: Media ID or URL
111
+ recipient: Recipient identifier
112
+ caption: Optional caption for the video
113
+ reply_to_message_id: Optional message ID to reply to
114
+ is_url: Whether media_reference is a URL (True) or media ID (False)
115
+
116
+ Returns:
117
+ Platform-specific video message payload
118
+ """
119
+ pass
120
+
121
+ @abstractmethod
122
+ def create_audio_message(
123
+ self,
124
+ media_reference: str,
125
+ recipient: str,
126
+ reply_to_message_id: str | None = None,
127
+ is_url: bool = False,
128
+ ) -> dict[str, Any]:
129
+ """Create an audio message payload.
130
+
131
+ Args:
132
+ media_reference: Media ID or URL
133
+ recipient: Recipient identifier
134
+ reply_to_message_id: Optional message ID to reply to
135
+ is_url: Whether media_reference is a URL (True) or media ID (False)
136
+
137
+ Returns:
138
+ Platform-specific audio message payload
139
+ """
140
+ pass
141
+
142
+ @abstractmethod
143
+ def create_document_message(
144
+ self,
145
+ media_reference: str,
146
+ recipient: str,
147
+ filename: str | None = None,
148
+ reply_to_message_id: str | None = None,
149
+ is_url: bool = False,
150
+ ) -> dict[str, Any]:
151
+ """Create a document message payload.
152
+
153
+ Args:
154
+ media_reference: Media ID or URL
155
+ recipient: Recipient identifier
156
+ filename: Optional filename for the document
157
+ reply_to_message_id: Optional message ID to reply to
158
+ is_url: Whether media_reference is a URL (True) or media ID (False)
159
+
160
+ Returns:
161
+ Platform-specific document message payload
162
+ """
163
+ pass
164
+
165
+ @abstractmethod
166
+ def create_sticker_message(
167
+ self,
168
+ media_reference: str,
169
+ recipient: str,
170
+ reply_to_message_id: str | None = None,
171
+ is_url: bool = False,
172
+ ) -> dict[str, Any]:
173
+ """Create a sticker message payload.
174
+
175
+ Args:
176
+ media_reference: Media ID or URL
177
+ recipient: Recipient identifier
178
+ reply_to_message_id: Optional message ID to reply to
179
+ is_url: Whether media_reference is a URL (True) or media ID (False)
180
+
181
+ Returns:
182
+ Platform-specific sticker message payload
183
+ """
184
+ pass
185
+
186
+ # Validation
187
+ @abstractmethod
188
+ def validate_message(self, message_payload: dict[str, Any]) -> bool:
189
+ """Validate message payload against platform constraints.
190
+
191
+ Args:
192
+ message_payload: Message payload to validate
193
+
194
+ Returns:
195
+ True if payload is valid, False otherwise
196
+ """
197
+ pass
198
+
199
+ @abstractmethod
200
+ def get_message_limits(self) -> dict[str, Any]:
201
+ """Get platform-specific message limits.
202
+
203
+ Returns:
204
+ Dictionary containing platform-specific limits
205
+ """
206
+ pass
207
+
208
+
209
+ class WhatsAppMessageFactory(MessageFactory):
210
+ """WhatsApp implementation of the message factory."""
211
+
212
+ @property
213
+ def platform(self) -> PlatformType:
214
+ """Get the platform this factory creates messages for."""
215
+ return PlatformType.WHATSAPP
216
+
217
+ def create_text_message(
218
+ self,
219
+ text: str,
220
+ recipient: str,
221
+ reply_to_message_id: str | None = None,
222
+ disable_preview: bool = False,
223
+ ) -> dict[str, Any]:
224
+ """Create WhatsApp text message payload.
225
+
226
+ Creates properly formatted WhatsApp Business API payload for text messages
227
+ with support for URL preview control and reply context.
228
+ """
229
+ # Check for URLs for preview control
230
+ has_url = "http://" in text or "https://" in text
231
+
232
+ payload = {
233
+ "messaging_product": "whatsapp",
234
+ "to": recipient,
235
+ "type": "text",
236
+ "text": {"body": text, "preview_url": has_url and not disable_preview},
237
+ }
238
+
239
+ # Add reply context if specified
240
+ if reply_to_message_id:
241
+ payload["context"] = {"message_id": reply_to_message_id}
242
+
243
+ return payload
244
+
245
+ def create_read_status_message(
246
+ self, message_id: str, typing: bool = False
247
+ ) -> dict[str, Any]:
248
+ """Create WhatsApp read status payload with typing support.
249
+
250
+ Creates WhatsApp Business API payload for marking messages as read
251
+ with optional typing indicator support.
252
+ """
253
+ payload = {
254
+ "messaging_product": "whatsapp",
255
+ "status": "read",
256
+ "message_id": message_id,
257
+ }
258
+
259
+ # Add typing indicator if requested (key requirement)
260
+ if typing:
261
+ payload["typing_indicator"] = {"type": "text"}
262
+
263
+ return payload
264
+
265
+ def create_image_message(
266
+ self,
267
+ media_reference: str,
268
+ recipient: str,
269
+ caption: str | None = None,
270
+ reply_to_message_id: str | None = None,
271
+ is_url: bool = False,
272
+ ) -> dict[str, Any]:
273
+ """Create WhatsApp image message payload."""
274
+ payload = {
275
+ "messaging_product": "whatsapp",
276
+ "recipient_type": "individual",
277
+ "to": recipient,
278
+ "type": "image",
279
+ }
280
+
281
+ # Create media object based on reference type
282
+ if is_url:
283
+ media_obj = {"link": media_reference}
284
+ else:
285
+ media_obj = {"id": media_reference}
286
+
287
+ # Add optional caption
288
+ if caption:
289
+ media_obj["caption"] = caption
290
+
291
+ payload["image"] = media_obj
292
+
293
+ # Add reply context if specified
294
+ if reply_to_message_id:
295
+ payload["context"] = {"message_id": reply_to_message_id}
296
+
297
+ return payload
298
+
299
+ def create_video_message(
300
+ self,
301
+ media_reference: str,
302
+ recipient: str,
303
+ caption: str | None = None,
304
+ reply_to_message_id: str | None = None,
305
+ is_url: bool = False,
306
+ ) -> dict[str, Any]:
307
+ """Create WhatsApp video message payload."""
308
+ payload = {
309
+ "messaging_product": "whatsapp",
310
+ "recipient_type": "individual",
311
+ "to": recipient,
312
+ "type": "video",
313
+ }
314
+
315
+ # Create media object based on reference type
316
+ if is_url:
317
+ media_obj = {"link": media_reference}
318
+ else:
319
+ media_obj = {"id": media_reference}
320
+
321
+ # Add optional caption
322
+ if caption:
323
+ media_obj["caption"] = caption
324
+
325
+ payload["video"] = media_obj
326
+
327
+ # Add reply context if specified
328
+ if reply_to_message_id:
329
+ payload["context"] = {"message_id": reply_to_message_id}
330
+
331
+ return payload
332
+
333
+ def create_audio_message(
334
+ self,
335
+ media_reference: str,
336
+ recipient: str,
337
+ reply_to_message_id: str | None = None,
338
+ is_url: bool = False,
339
+ ) -> dict[str, Any]:
340
+ """Create WhatsApp audio message payload."""
341
+ payload = {
342
+ "messaging_product": "whatsapp",
343
+ "recipient_type": "individual",
344
+ "to": recipient,
345
+ "type": "audio",
346
+ }
347
+
348
+ # Create media object based on reference type
349
+ if is_url:
350
+ media_obj = {"link": media_reference}
351
+ else:
352
+ media_obj = {"id": media_reference}
353
+
354
+ payload["audio"] = media_obj
355
+
356
+ # Add reply context if specified
357
+ if reply_to_message_id:
358
+ payload["context"] = {"message_id": reply_to_message_id}
359
+
360
+ return payload
361
+
362
+ def create_document_message(
363
+ self,
364
+ media_reference: str,
365
+ recipient: str,
366
+ filename: str | None = None,
367
+ reply_to_message_id: str | None = None,
368
+ is_url: bool = False,
369
+ ) -> dict[str, Any]:
370
+ """Create WhatsApp document message payload."""
371
+ payload = {
372
+ "messaging_product": "whatsapp",
373
+ "recipient_type": "individual",
374
+ "to": recipient,
375
+ "type": "document",
376
+ }
377
+
378
+ # Create media object based on reference type
379
+ if is_url:
380
+ media_obj = {"link": media_reference}
381
+ else:
382
+ media_obj = {"id": media_reference}
383
+
384
+ # Add optional filename
385
+ if filename:
386
+ media_obj["filename"] = filename
387
+
388
+ payload["document"] = media_obj
389
+
390
+ # Add reply context if specified
391
+ if reply_to_message_id:
392
+ payload["context"] = {"message_id": reply_to_message_id}
393
+
394
+ return payload
395
+
396
+ def create_sticker_message(
397
+ self,
398
+ media_reference: str,
399
+ recipient: str,
400
+ reply_to_message_id: str | None = None,
401
+ is_url: bool = False,
402
+ ) -> dict[str, Any]:
403
+ """Create WhatsApp sticker message payload."""
404
+ payload = {
405
+ "messaging_product": "whatsapp",
406
+ "recipient_type": "individual",
407
+ "to": recipient,
408
+ "type": "sticker",
409
+ }
410
+
411
+ # Create media object based on reference type
412
+ if is_url:
413
+ media_obj = {"link": media_reference}
414
+ else:
415
+ media_obj = {"id": media_reference}
416
+
417
+ payload["sticker"] = media_obj
418
+
419
+ # Add reply context if specified
420
+ if reply_to_message_id:
421
+ payload["context"] = {"message_id": reply_to_message_id}
422
+
423
+ return payload
424
+
425
+ def validate_message(self, message_payload: dict[str, Any]) -> bool:
426
+ """Validate WhatsApp message payload.
427
+
428
+ Performs basic validation against WhatsApp Business API requirements.
429
+ """
430
+ try:
431
+ # Check required fields
432
+ if "messaging_product" not in message_payload:
433
+ return False
434
+ if message_payload["messaging_product"] != "whatsapp":
435
+ return False
436
+ if "to" not in message_payload:
437
+ return False
438
+
439
+ # Validate text messages
440
+ if message_payload.get("type") == "text":
441
+ if "text" not in message_payload:
442
+ return False
443
+ if "body" not in message_payload["text"]:
444
+ return False
445
+ if len(message_payload["text"]["body"]) > 4096:
446
+ return False
447
+
448
+ # Validate read status messages
449
+ if "status" in message_payload:
450
+ if message_payload["status"] == "read":
451
+ if "message_id" not in message_payload:
452
+ return False
453
+
454
+ return True
455
+
456
+ except (KeyError, TypeError):
457
+ return False
458
+
459
+ def get_message_limits(self) -> dict[str, Any]:
460
+ """Get WhatsApp-specific message limits.
461
+
462
+ Returns current WhatsApp Business API limits for message validation.
463
+ """
464
+ return {
465
+ "max_text_length": 4096,
466
+ "max_caption_length": 1024,
467
+ "max_buttons": 3,
468
+ "max_button_title_length": 20,
469
+ "max_list_sections": 10,
470
+ "max_list_items_per_section": 10,
471
+ "max_list_item_title_length": 24,
472
+ "max_list_item_description_length": 72,
473
+ "supported_media_types": [
474
+ "image/jpeg",
475
+ "image/png",
476
+ "image/webp",
477
+ "video/mp4",
478
+ "video/3gpp",
479
+ "audio/aac",
480
+ "audio/amr",
481
+ "audio/mpeg",
482
+ "audio/mp4",
483
+ "audio/ogg",
484
+ "application/pdf",
485
+ "text/plain",
486
+ "application/vnd.ms-excel",
487
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
488
+ "application/msword",
489
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
490
+ ],
491
+ "max_media_size": {
492
+ "image": 5242880, # 5MB
493
+ "video": 16777216, # 16MB
494
+ "audio": 16777216, # 16MB
495
+ "document": 104857600, # 100MB
496
+ },
497
+ }