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,416 @@
1
+ """
2
+ WhatsApp template message handler.
3
+
4
+ Provides template messaging operations using WhatsApp Cloud API:
5
+ - Text-only templates
6
+ - Media templates (image, video, document headers)
7
+ - Location templates with coordinate headers
8
+
9
+ Migrated from whatsapp_latest/services/send_templates.py with SOLID architecture.
10
+ """
11
+
12
+ from wappa.core.logging.logger import get_logger
13
+ from wappa.messaging.whatsapp.client.whatsapp_client import WhatsAppClient
14
+ from wappa.messaging.whatsapp.models.basic_models import MessageResult
15
+ from wappa.messaging.whatsapp.models.template_models import (
16
+ MediaType,
17
+ TemplateParameter,
18
+ TemplateParameterType,
19
+ )
20
+
21
+
22
+ class WhatsAppTemplateHandler:
23
+ """
24
+ Handler for WhatsApp template message operations.
25
+
26
+ Provides composition-based template functionality for WhatsAppMessenger:
27
+ - Text templates with parameter substitution
28
+ - Media templates with header media content
29
+ - Location templates with geographic coordinates
30
+
31
+ Based on WhatsApp Cloud API 2025 template specifications.
32
+ """
33
+
34
+ def __init__(self, client: WhatsAppClient, tenant_id: str):
35
+ """Initialize template handler.
36
+
37
+ Args:
38
+ client: Configured WhatsApp client for API operations
39
+ tenant_id: Tenant identifier for logging context
40
+ """
41
+ self.client = client
42
+ self._tenant_id = tenant_id
43
+ self.logger = get_logger(__name__)
44
+
45
+ async def send_text_template(
46
+ self,
47
+ phone_number: str,
48
+ template_name: str,
49
+ body_parameters: list[TemplateParameter] | None = None,
50
+ language_code: str = "es",
51
+ ) -> MessageResult:
52
+ """
53
+ Send a text-only WhatsApp template message.
54
+
55
+ Args:
56
+ phone_number: Recipient's phone number in E.164 format
57
+ template_name: Name of the approved template
58
+ body_parameters: List of parameters for template text replacement
59
+ language_code: BCP-47 language code for the template
60
+
61
+ Returns:
62
+ MessageResult with operation status and metadata
63
+
64
+ Raises:
65
+ ValueError: If template parameters are invalid
66
+ Exception: For API request failures
67
+ """
68
+ try:
69
+ # Build template data
70
+ template_data = {"name": template_name, "language": {"code": language_code}}
71
+
72
+ # Add body parameters if provided
73
+ if body_parameters:
74
+ # Convert TemplateParameter objects to API format
75
+ api_parameters = []
76
+ for param in body_parameters:
77
+ if param.type == TemplateParameterType.TEXT:
78
+ api_parameters.append({"type": "text", "text": param.text})
79
+ # Future: Add support for currency, date_time, etc.
80
+
81
+ template_data["components"] = [
82
+ {"type": "body", "parameters": api_parameters}
83
+ ]
84
+
85
+ # Build message payload
86
+ payload = {
87
+ "messaging_product": "whatsapp",
88
+ "to": phone_number,
89
+ "type": "template",
90
+ "template": template_data,
91
+ }
92
+
93
+ self.logger.debug(
94
+ f"Sending text template '{template_name}' to {phone_number}"
95
+ )
96
+
97
+ # Send template message
98
+ response = await self.client.post_request(payload)
99
+
100
+ # Parse response
101
+ if response.get("messages"):
102
+ message_id = response["messages"][0].get("id")
103
+ self.logger.info(
104
+ f"Text template '{template_name}' sent successfully to {phone_number}"
105
+ )
106
+
107
+ return MessageResult(
108
+ success=True,
109
+ message_id=message_id,
110
+ platform="whatsapp",
111
+ raw_response=response,
112
+ )
113
+ else:
114
+ error_msg = f"No message ID in response for template '{template_name}'"
115
+ self.logger.error(error_msg)
116
+
117
+ return MessageResult(
118
+ success=False,
119
+ platform="whatsapp",
120
+ error=error_msg,
121
+ error_code="NO_MESSAGE_ID",
122
+ raw_response=response,
123
+ )
124
+
125
+ except Exception as e:
126
+ error_msg = f"Failed to send text template '{template_name}' to {phone_number}: {str(e)}"
127
+ self.logger.exception(error_msg)
128
+
129
+ return MessageResult(
130
+ success=False,
131
+ platform="whatsapp",
132
+ error=error_msg,
133
+ error_code="TEMPLATE_SEND_FAILED",
134
+ )
135
+
136
+ async def send_media_template(
137
+ self,
138
+ phone_number: str,
139
+ template_name: str,
140
+ media_type: MediaType,
141
+ media_id: str | None = None,
142
+ media_url: str | None = None,
143
+ body_parameters: list[TemplateParameter] | None = None,
144
+ language_code: str = "es",
145
+ ) -> MessageResult:
146
+ """
147
+ Send a WhatsApp template message with media header.
148
+
149
+ Args:
150
+ phone_number: Recipient's phone number in E.164 format
151
+ template_name: Name of the approved template
152
+ media_type: Type of media (image, video, document)
153
+ media_id: ID of pre-uploaded media (exclusive with media_url)
154
+ media_url: URL of media to include (exclusive with media_id)
155
+ body_parameters: List of parameters for template text replacement
156
+ language_code: BCP-47 language code for the template
157
+
158
+ Returns:
159
+ MessageResult with operation status and metadata
160
+
161
+ Raises:
162
+ ValueError: If media parameters are invalid or both/neither media source provided
163
+ Exception: For API request failures
164
+ """
165
+ try:
166
+ # Validate media source
167
+ if (media_id and media_url) or (not media_id and not media_url):
168
+ raise ValueError(
169
+ "Either media_id or media_url must be provided, but not both"
170
+ )
171
+
172
+ # Build header component with media
173
+ header_component = {
174
+ "type": "header",
175
+ "parameters": [
176
+ {
177
+ "type": media_type.value,
178
+ media_type.value: {"id": media_id}
179
+ if media_id
180
+ else {"link": media_url},
181
+ }
182
+ ],
183
+ }
184
+
185
+ # Build template components
186
+ components = [header_component]
187
+
188
+ # Add body parameters if provided
189
+ if body_parameters:
190
+ api_parameters = []
191
+ for param in body_parameters:
192
+ if param.type == TemplateParameterType.TEXT:
193
+ api_parameters.append({"type": "text", "text": param.text})
194
+
195
+ components.append({"type": "body", "parameters": api_parameters})
196
+
197
+ # Build template data
198
+ template_data = {
199
+ "name": template_name,
200
+ "language": {"code": language_code},
201
+ "components": components,
202
+ }
203
+
204
+ # Build message payload
205
+ payload = {
206
+ "messaging_product": "whatsapp",
207
+ "recipient_type": "individual",
208
+ "to": phone_number,
209
+ "type": "template",
210
+ "template": template_data,
211
+ }
212
+
213
+ self.logger.debug(
214
+ f"Sending media template '{template_name}' ({media_type.value}) to {phone_number}"
215
+ )
216
+
217
+ # Send template message
218
+ response = await self.client.post_request(payload)
219
+
220
+ # Parse response
221
+ if response.get("messages"):
222
+ message_id = response["messages"][0].get("id")
223
+ self.logger.info(
224
+ f"Media template '{template_name}' sent successfully to {phone_number}"
225
+ )
226
+
227
+ return MessageResult(
228
+ success=True,
229
+ message_id=message_id,
230
+ platform="whatsapp",
231
+ raw_response=response,
232
+ )
233
+ else:
234
+ error_msg = (
235
+ f"No message ID in response for media template '{template_name}'"
236
+ )
237
+ self.logger.error(error_msg)
238
+
239
+ return MessageResult(
240
+ success=False,
241
+ platform="whatsapp",
242
+ error=error_msg,
243
+ error_code="NO_MESSAGE_ID",
244
+ raw_response=response,
245
+ )
246
+
247
+ except Exception as e:
248
+ error_msg = f"Failed to send media template '{template_name}' to {phone_number}: {str(e)}"
249
+ self.logger.exception(error_msg)
250
+
251
+ return MessageResult(
252
+ success=False,
253
+ platform="whatsapp",
254
+ error=error_msg,
255
+ error_code="MEDIA_TEMPLATE_SEND_FAILED",
256
+ )
257
+
258
+ async def send_location_template(
259
+ self,
260
+ phone_number: str,
261
+ template_name: str,
262
+ latitude: str,
263
+ longitude: str,
264
+ name: str,
265
+ address: str,
266
+ body_parameters: list[TemplateParameter] | None = None,
267
+ language_code: str = "es",
268
+ ) -> MessageResult:
269
+ """
270
+ Send a location-based WhatsApp template message.
271
+
272
+ Args:
273
+ phone_number: Recipient's phone number in E.164 format
274
+ template_name: Name of the approved template
275
+ latitude: Location latitude as string
276
+ longitude: Location longitude as string
277
+ name: Name/title of the location
278
+ address: Physical address of the location
279
+ body_parameters: List of parameters for template text replacement
280
+ language_code: BCP-47 language code for the template
281
+
282
+ Returns:
283
+ MessageResult with operation status and metadata
284
+
285
+ Raises:
286
+ ValueError: If location parameters are invalid
287
+ Exception: For API request failures
288
+ """
289
+ try:
290
+ # Validate coordinates (basic range check)
291
+ try:
292
+ lat = float(latitude)
293
+ lon = float(longitude)
294
+ if not (-90 <= lat <= 90):
295
+ raise ValueError("Latitude must be between -90 and 90 degrees")
296
+ if not (-180 <= lon <= 180):
297
+ raise ValueError("Longitude must be between -180 and 180 degrees")
298
+ except ValueError as e:
299
+ if "could not convert" in str(e):
300
+ raise ValueError("Latitude and longitude must be valid numbers")
301
+ raise
302
+
303
+ # Build location parameter
304
+ location_param = {
305
+ "type": "location",
306
+ "location": {
307
+ "latitude": latitude,
308
+ "longitude": longitude,
309
+ "name": name,
310
+ "address": address,
311
+ },
312
+ }
313
+
314
+ # Build header component with location
315
+ header_component = {"type": "header", "parameters": [location_param]}
316
+
317
+ # Build template components
318
+ components = [header_component]
319
+
320
+ # Add body parameters if provided
321
+ if body_parameters:
322
+ api_parameters = []
323
+ for param in body_parameters:
324
+ if param.type == TemplateParameterType.TEXT:
325
+ api_parameters.append({"type": "text", "text": param.text})
326
+
327
+ components.append({"type": "body", "parameters": api_parameters})
328
+
329
+ # Build template data
330
+ template_data = {
331
+ "name": template_name,
332
+ "language": {"code": language_code},
333
+ "components": components,
334
+ }
335
+
336
+ # Build message payload
337
+ payload = {
338
+ "messaging_product": "whatsapp",
339
+ "recipient_type": "individual",
340
+ "to": phone_number,
341
+ "type": "template",
342
+ "template": template_data,
343
+ }
344
+
345
+ self.logger.debug(
346
+ f"Sending location template '{template_name}' to {phone_number}"
347
+ )
348
+
349
+ # Send template message
350
+ response = await self.client.post_request(payload)
351
+
352
+ # Parse response
353
+ if response.get("messages"):
354
+ message_id = response["messages"][0].get("id")
355
+ self.logger.info(
356
+ f"Location template '{template_name}' sent successfully to {phone_number}"
357
+ )
358
+
359
+ return MessageResult(
360
+ success=True,
361
+ message_id=message_id,
362
+ platform="whatsapp",
363
+ raw_response=response,
364
+ )
365
+ else:
366
+ error_msg = (
367
+ f"No message ID in response for location template '{template_name}'"
368
+ )
369
+ self.logger.error(error_msg)
370
+
371
+ return MessageResult(
372
+ success=False,
373
+ platform="whatsapp",
374
+ error=error_msg,
375
+ error_code="NO_MESSAGE_ID",
376
+ raw_response=response,
377
+ )
378
+
379
+ except Exception as e:
380
+ error_msg = f"Failed to send location template '{template_name}' to {phone_number}: {str(e)}"
381
+ self.logger.exception(error_msg)
382
+
383
+ return MessageResult(
384
+ success=False,
385
+ platform="whatsapp",
386
+ error=error_msg,
387
+ error_code="LOCATION_TEMPLATE_SEND_FAILED",
388
+ )
389
+
390
+ async def get_template_info(self, template_name: str) -> dict:
391
+ """
392
+ Get information about a specific template.
393
+
394
+ Args:
395
+ template_name: Name of the template to query
396
+
397
+ Returns:
398
+ Dict with template information or error details
399
+ """
400
+ try:
401
+ # This would typically call WhatsApp's template management API
402
+ # For now, return basic info structure
403
+ self.logger.debug(f"Getting template info for '{template_name}'")
404
+
405
+ return {
406
+ "template_name": template_name,
407
+ "status": "APPROVED", # This should come from actual API
408
+ "category": "MARKETING", # This should come from actual API
409
+ "language": "es", # This should come from actual API
410
+ }
411
+
412
+ except Exception as e:
413
+ self.logger.exception(
414
+ f"Failed to get template info for '{template_name}': {str(e)}"
415
+ )
416
+ return {"template_name": template_name, "error": str(e), "status": "ERROR"}
@@ -0,0 +1,5 @@
1
+ """WhatsApp messenger implementations package."""
2
+
3
+ from .whatsapp_messenger import WhatsAppMessenger
4
+
5
+ __all__ = ["WhatsAppMessenger"]