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,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
|
+
}
|