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.
- wappa/__init__.py +85 -0
- wappa/api/__init__.py +1 -0
- wappa/api/controllers/__init__.py +10 -0
- wappa/api/controllers/webhook_controller.py +441 -0
- wappa/api/dependencies/__init__.py +15 -0
- wappa/api/dependencies/whatsapp_dependencies.py +220 -0
- wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
- wappa/api/middleware/__init__.py +7 -0
- wappa/api/middleware/error_handler.py +158 -0
- wappa/api/middleware/owner.py +99 -0
- wappa/api/middleware/request_logging.py +184 -0
- wappa/api/routes/__init__.py +6 -0
- wappa/api/routes/health.py +102 -0
- wappa/api/routes/webhooks.py +211 -0
- wappa/api/routes/whatsapp/__init__.py +15 -0
- wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
- wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
- wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
- wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
- wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
- wappa/api/routes/whatsapp_combined.py +35 -0
- wappa/cli/__init__.py +9 -0
- wappa/cli/main.py +199 -0
- wappa/core/__init__.py +6 -0
- wappa/core/config/__init__.py +5 -0
- wappa/core/config/settings.py +161 -0
- wappa/core/events/__init__.py +41 -0
- wappa/core/events/default_handlers.py +642 -0
- wappa/core/events/event_dispatcher.py +244 -0
- wappa/core/events/event_handler.py +247 -0
- wappa/core/events/webhook_factory.py +219 -0
- wappa/core/factory/__init__.py +15 -0
- wappa/core/factory/plugin.py +68 -0
- wappa/core/factory/wappa_builder.py +326 -0
- wappa/core/logging/__init__.py +5 -0
- wappa/core/logging/context.py +100 -0
- wappa/core/logging/logger.py +343 -0
- wappa/core/plugins/__init__.py +34 -0
- wappa/core/plugins/auth_plugin.py +169 -0
- wappa/core/plugins/cors_plugin.py +128 -0
- wappa/core/plugins/custom_middleware_plugin.py +182 -0
- wappa/core/plugins/database_plugin.py +235 -0
- wappa/core/plugins/rate_limit_plugin.py +183 -0
- wappa/core/plugins/redis_plugin.py +224 -0
- wappa/core/plugins/wappa_core_plugin.py +261 -0
- wappa/core/plugins/webhook_plugin.py +253 -0
- wappa/core/types.py +108 -0
- wappa/core/wappa_app.py +546 -0
- wappa/database/__init__.py +18 -0
- wappa/database/adapter.py +107 -0
- wappa/database/adapters/__init__.py +17 -0
- wappa/database/adapters/mysql_adapter.py +187 -0
- wappa/database/adapters/postgresql_adapter.py +169 -0
- wappa/database/adapters/sqlite_adapter.py +174 -0
- wappa/domain/__init__.py +28 -0
- wappa/domain/builders/__init__.py +5 -0
- wappa/domain/builders/message_builder.py +189 -0
- wappa/domain/entities/__init__.py +5 -0
- wappa/domain/enums/messenger_platform.py +123 -0
- wappa/domain/factories/__init__.py +6 -0
- wappa/domain/factories/media_factory.py +450 -0
- wappa/domain/factories/message_factory.py +497 -0
- wappa/domain/factories/messenger_factory.py +244 -0
- wappa/domain/interfaces/__init__.py +32 -0
- wappa/domain/interfaces/base_repository.py +94 -0
- wappa/domain/interfaces/cache_factory.py +85 -0
- wappa/domain/interfaces/cache_interface.py +199 -0
- wappa/domain/interfaces/expiry_repository.py +68 -0
- wappa/domain/interfaces/media_interface.py +311 -0
- wappa/domain/interfaces/messaging_interface.py +523 -0
- wappa/domain/interfaces/pubsub_repository.py +151 -0
- wappa/domain/interfaces/repository_factory.py +108 -0
- wappa/domain/interfaces/shared_state_repository.py +122 -0
- wappa/domain/interfaces/state_repository.py +123 -0
- wappa/domain/interfaces/tables_repository.py +215 -0
- wappa/domain/interfaces/user_repository.py +114 -0
- wappa/domain/interfaces/webhooks/__init__.py +1 -0
- wappa/domain/models/media_result.py +110 -0
- wappa/domain/models/platforms/__init__.py +15 -0
- wappa/domain/models/platforms/platform_config.py +104 -0
- wappa/domain/services/__init__.py +11 -0
- wappa/domain/services/tenant_credentials_service.py +56 -0
- wappa/messaging/__init__.py +7 -0
- wappa/messaging/whatsapp/__init__.py +1 -0
- wappa/messaging/whatsapp/client/__init__.py +5 -0
- wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
- wappa/messaging/whatsapp/handlers/__init__.py +13 -0
- wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
- wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
- wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
- wappa/messaging/whatsapp/messenger/__init__.py +5 -0
- wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
- wappa/messaging/whatsapp/models/__init__.py +61 -0
- wappa/messaging/whatsapp/models/basic_models.py +65 -0
- wappa/messaging/whatsapp/models/interactive_models.py +287 -0
- wappa/messaging/whatsapp/models/media_models.py +215 -0
- wappa/messaging/whatsapp/models/specialized_models.py +304 -0
- wappa/messaging/whatsapp/models/template_models.py +261 -0
- wappa/persistence/cache_factory.py +93 -0
- wappa/persistence/json/__init__.py +14 -0
- wappa/persistence/json/cache_adapters.py +271 -0
- wappa/persistence/json/handlers/__init__.py +1 -0
- wappa/persistence/json/handlers/state_handler.py +250 -0
- wappa/persistence/json/handlers/table_handler.py +263 -0
- wappa/persistence/json/handlers/user_handler.py +213 -0
- wappa/persistence/json/handlers/utils/__init__.py +1 -0
- wappa/persistence/json/handlers/utils/file_manager.py +153 -0
- wappa/persistence/json/handlers/utils/key_factory.py +11 -0
- wappa/persistence/json/handlers/utils/serialization.py +121 -0
- wappa/persistence/json/json_cache_factory.py +76 -0
- wappa/persistence/json/storage_manager.py +285 -0
- wappa/persistence/memory/__init__.py +14 -0
- wappa/persistence/memory/cache_adapters.py +271 -0
- wappa/persistence/memory/handlers/__init__.py +1 -0
- wappa/persistence/memory/handlers/state_handler.py +250 -0
- wappa/persistence/memory/handlers/table_handler.py +280 -0
- wappa/persistence/memory/handlers/user_handler.py +213 -0
- wappa/persistence/memory/handlers/utils/__init__.py +1 -0
- wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
- wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
- wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
- wappa/persistence/memory/memory_cache_factory.py +76 -0
- wappa/persistence/memory/storage_manager.py +235 -0
- wappa/persistence/redis/README.md +699 -0
- wappa/persistence/redis/__init__.py +11 -0
- wappa/persistence/redis/cache_adapters.py +285 -0
- wappa/persistence/redis/ops.py +880 -0
- wappa/persistence/redis/redis_cache_factory.py +71 -0
- wappa/persistence/redis/redis_client.py +231 -0
- wappa/persistence/redis/redis_handler/__init__.py +26 -0
- wappa/persistence/redis/redis_handler/state_handler.py +176 -0
- wappa/persistence/redis/redis_handler/table.py +158 -0
- wappa/persistence/redis/redis_handler/user.py +138 -0
- wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
- wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
- wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
- wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
- wappa/persistence/redis/redis_manager.py +189 -0
- wappa/processors/__init__.py +6 -0
- wappa/processors/base_processor.py +262 -0
- wappa/processors/factory.py +550 -0
- wappa/processors/whatsapp_processor.py +810 -0
- wappa/schemas/__init__.py +6 -0
- wappa/schemas/core/__init__.py +71 -0
- wappa/schemas/core/base_message.py +499 -0
- wappa/schemas/core/base_status.py +322 -0
- wappa/schemas/core/base_webhook.py +312 -0
- wappa/schemas/core/types.py +253 -0
- wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
- wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
- wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
- wappa/schemas/factory.py +754 -0
- wappa/schemas/webhooks/__init__.py +3 -0
- wappa/schemas/whatsapp/__init__.py +6 -0
- wappa/schemas/whatsapp/base_models.py +285 -0
- wappa/schemas/whatsapp/message_types/__init__.py +93 -0
- wappa/schemas/whatsapp/message_types/audio.py +350 -0
- wappa/schemas/whatsapp/message_types/button.py +267 -0
- wappa/schemas/whatsapp/message_types/contact.py +464 -0
- wappa/schemas/whatsapp/message_types/document.py +421 -0
- wappa/schemas/whatsapp/message_types/errors.py +195 -0
- wappa/schemas/whatsapp/message_types/image.py +424 -0
- wappa/schemas/whatsapp/message_types/interactive.py +430 -0
- wappa/schemas/whatsapp/message_types/location.py +416 -0
- wappa/schemas/whatsapp/message_types/order.py +372 -0
- wappa/schemas/whatsapp/message_types/reaction.py +271 -0
- wappa/schemas/whatsapp/message_types/sticker.py +328 -0
- wappa/schemas/whatsapp/message_types/system.py +317 -0
- wappa/schemas/whatsapp/message_types/text.py +411 -0
- wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
- wappa/schemas/whatsapp/message_types/video.py +344 -0
- wappa/schemas/whatsapp/status_models.py +479 -0
- wappa/schemas/whatsapp/validators.py +454 -0
- wappa/schemas/whatsapp/webhook_container.py +438 -0
- wappa/webhooks/__init__.py +17 -0
- wappa/webhooks/core/__init__.py +71 -0
- wappa/webhooks/core/base_message.py +499 -0
- wappa/webhooks/core/base_status.py +322 -0
- wappa/webhooks/core/base_webhook.py +312 -0
- wappa/webhooks/core/types.py +253 -0
- wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
- wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
- wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
- wappa/webhooks/factory.py +754 -0
- wappa/webhooks/whatsapp/__init__.py +6 -0
- wappa/webhooks/whatsapp/base_models.py +285 -0
- wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
- wappa/webhooks/whatsapp/message_types/audio.py +350 -0
- wappa/webhooks/whatsapp/message_types/button.py +267 -0
- wappa/webhooks/whatsapp/message_types/contact.py +464 -0
- wappa/webhooks/whatsapp/message_types/document.py +421 -0
- wappa/webhooks/whatsapp/message_types/errors.py +195 -0
- wappa/webhooks/whatsapp/message_types/image.py +424 -0
- wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
- wappa/webhooks/whatsapp/message_types/location.py +416 -0
- wappa/webhooks/whatsapp/message_types/order.py +372 -0
- wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
- wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
- wappa/webhooks/whatsapp/message_types/system.py +317 -0
- wappa/webhooks/whatsapp/message_types/text.py +411 -0
- wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
- wappa/webhooks/whatsapp/message_types/video.py +344 -0
- wappa/webhooks/whatsapp/status_models.py +479 -0
- wappa/webhooks/whatsapp/validators.py +454 -0
- wappa/webhooks/whatsapp/webhook_container.py +438 -0
- wappa-0.1.0.dist-info/METADATA +269 -0
- wappa-0.1.0.dist-info/RECORD +211 -0
- wappa-0.1.0.dist-info/WHEEL +4 -0
- wappa-0.1.0.dist-info/entry_points.txt +2 -0
- 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"}
|