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,523 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Messaging interface for platform-agnostic communication.
|
|
3
|
+
|
|
4
|
+
This interface defines the contract for messaging operations that can be
|
|
5
|
+
implemented across different messaging platforms (WhatsApp, Telegram, Teams, etc.).
|
|
6
|
+
|
|
7
|
+
Implements messaging operations:
|
|
8
|
+
- Basic messaging (send_text, mark_as_read)
|
|
9
|
+
- Media messaging (send_image, send_video, send_audio, send_document, send_sticker)
|
|
10
|
+
- Interactive messaging (send_button_message, send_list_message, send_cta_message)
|
|
11
|
+
- Template messaging (send_text_template, send_media_template, send_location_template)
|
|
12
|
+
|
|
13
|
+
Implements specialized messaging:
|
|
14
|
+
- Specialized messaging (send_contact, send_location, send_location_request)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from wappa.schemas.core.types import PlatformType
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from wappa.messaging.whatsapp.models.basic_models import MessageResult
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class IMessenger(ABC):
|
|
28
|
+
"""
|
|
29
|
+
Messaging interface for platform-agnostic communication.
|
|
30
|
+
|
|
31
|
+
Provides messaging operations:
|
|
32
|
+
- Basic messaging: send_text, mark_as_read
|
|
33
|
+
- Media messaging: send_image, send_video, send_audio, send_document, send_sticker
|
|
34
|
+
- Interactive messaging: send_button_message, send_list_message, send_cta_message
|
|
35
|
+
- Template messaging: send_text_template, send_media_template, send_location_template
|
|
36
|
+
|
|
37
|
+
Specialized messaging:
|
|
38
|
+
- Specialized messaging: send_contact, send_location, send_location_request
|
|
39
|
+
|
|
40
|
+
Key Design Decisions:
|
|
41
|
+
- tenant_id property provides the platform-specific tenant identifier
|
|
42
|
+
- All methods return MessageResult for consistent response handling
|
|
43
|
+
- Supports typing indicator in mark_as_read as specifically requested
|
|
44
|
+
- Media methods support both URLs and file paths for flexibility
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def platform(self) -> PlatformType:
|
|
50
|
+
"""Get the platform this messenger handles.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
PlatformType enum value (e.g., WHATSAPP, TELEGRAM, TEAMS)
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def tenant_id(self) -> str:
|
|
60
|
+
"""Get the tenant ID this messenger serves.
|
|
61
|
+
|
|
62
|
+
Note: In WhatsApp context, this is the phone_number_id.
|
|
63
|
+
Different platforms may use different tenant identifiers.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Platform-specific tenant identifier
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
async def send_text(
|
|
72
|
+
self,
|
|
73
|
+
text: str,
|
|
74
|
+
recipient: str,
|
|
75
|
+
reply_to_message_id: str | None = None,
|
|
76
|
+
disable_preview: bool = False,
|
|
77
|
+
) -> "MessageResult":
|
|
78
|
+
"""Send a text message.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
text: Text content of the message (1-4096 characters)
|
|
82
|
+
recipient: Recipient identifier (phone number, user ID, etc.)
|
|
83
|
+
reply_to_message_id: Optional message ID to reply to (creates thread)
|
|
84
|
+
disable_preview: Whether to disable URL preview for links
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
MessageResult with operation status and metadata
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
Platform-specific exceptions for API failures
|
|
91
|
+
"""
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
async def mark_as_read(
|
|
96
|
+
self, message_id: str, typing: bool = False
|
|
97
|
+
) -> "MessageResult":
|
|
98
|
+
"""Mark a message as read, optionally with typing indicator.
|
|
99
|
+
|
|
100
|
+
Key requirement: Support for typing indicator boolean parameter.
|
|
101
|
+
When typing=True, shows typing indicator to the sender.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
message_id: Platform-specific message identifier to mark as read
|
|
105
|
+
typing: Whether to show typing indicator when marking as read
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
MessageResult with operation status and metadata
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
Platform-specific exceptions for API failures
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
# Media Messaging Methods
|
|
116
|
+
@abstractmethod
|
|
117
|
+
async def send_image(
|
|
118
|
+
self,
|
|
119
|
+
image_source: str | Path,
|
|
120
|
+
recipient: str,
|
|
121
|
+
caption: str | None = None,
|
|
122
|
+
reply_to_message_id: str | None = None,
|
|
123
|
+
) -> "MessageResult":
|
|
124
|
+
"""Send an image message.
|
|
125
|
+
|
|
126
|
+
Supports JPEG and PNG images up to 5MB.
|
|
127
|
+
Images must be 8-bit, RGB or RGBA (WhatsApp Cloud API 2025).
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
image_source: Image URL or file path
|
|
131
|
+
recipient: Recipient identifier
|
|
132
|
+
caption: Optional caption for the image (max 1024 characters)
|
|
133
|
+
reply_to_message_id: Optional message ID to reply to
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
MessageResult with operation status and metadata
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
Platform-specific exceptions for API failures
|
|
140
|
+
"""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
@abstractmethod
|
|
144
|
+
async def send_video(
|
|
145
|
+
self,
|
|
146
|
+
video_source: str | Path,
|
|
147
|
+
recipient: str,
|
|
148
|
+
caption: str | None = None,
|
|
149
|
+
reply_to_message_id: str | None = None,
|
|
150
|
+
) -> "MessageResult":
|
|
151
|
+
"""Send a video message.
|
|
152
|
+
|
|
153
|
+
Supports MP4 and 3GP videos up to 16MB.
|
|
154
|
+
Only H.264 video codec and AAC audio codec supported.
|
|
155
|
+
Single audio stream or no audio stream only (WhatsApp Cloud API 2025).
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
video_source: Video URL or file path
|
|
159
|
+
recipient: Recipient identifier
|
|
160
|
+
caption: Optional caption for the video (max 1024 characters)
|
|
161
|
+
reply_to_message_id: Optional message ID to reply to
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
MessageResult with operation status and metadata
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
Platform-specific exceptions for API failures
|
|
168
|
+
"""
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
@abstractmethod
|
|
172
|
+
async def send_audio(
|
|
173
|
+
self,
|
|
174
|
+
audio_source: str | Path,
|
|
175
|
+
recipient: str,
|
|
176
|
+
reply_to_message_id: str | None = None,
|
|
177
|
+
) -> "MessageResult":
|
|
178
|
+
"""Send an audio message.
|
|
179
|
+
|
|
180
|
+
Supports AAC, AMR, MP3, M4A, and OGG audio up to 16MB.
|
|
181
|
+
OGG must use OPUS codecs only, mono input only (WhatsApp Cloud API 2025).
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
audio_source: Audio URL or file path
|
|
185
|
+
recipient: Recipient identifier
|
|
186
|
+
reply_to_message_id: Optional message ID to reply to
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
MessageResult with operation status and metadata
|
|
190
|
+
|
|
191
|
+
Note:
|
|
192
|
+
Audio messages do not support captions.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
Platform-specific exceptions for API failures
|
|
196
|
+
"""
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
@abstractmethod
|
|
200
|
+
async def send_document(
|
|
201
|
+
self,
|
|
202
|
+
document_source: str | Path,
|
|
203
|
+
recipient: str,
|
|
204
|
+
filename: str | None = None,
|
|
205
|
+
caption: str | None = None,
|
|
206
|
+
reply_to_message_id: str | None = None,
|
|
207
|
+
) -> "MessageResult":
|
|
208
|
+
"""Send a document message.
|
|
209
|
+
|
|
210
|
+
Supports TXT, PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX up to 100MB.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
document_source: Document URL or file path
|
|
214
|
+
recipient: Recipient identifier
|
|
215
|
+
filename: Optional filename for the document
|
|
216
|
+
caption: Optional caption for the document (max 1024 characters)
|
|
217
|
+
reply_to_message_id: Optional message ID to reply to
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
MessageResult with operation status and metadata
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
Platform-specific exceptions for API failures
|
|
224
|
+
"""
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
@abstractmethod
|
|
228
|
+
async def send_sticker(
|
|
229
|
+
self,
|
|
230
|
+
sticker_source: str | Path,
|
|
231
|
+
recipient: str,
|
|
232
|
+
reply_to_message_id: str | None = None,
|
|
233
|
+
) -> "MessageResult":
|
|
234
|
+
"""Send a sticker message.
|
|
235
|
+
|
|
236
|
+
Supports WebP images only.
|
|
237
|
+
Static stickers: 100KB max, Animated stickers: 500KB max.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
sticker_source: Sticker URL or file path (WebP format)
|
|
241
|
+
recipient: Recipient identifier
|
|
242
|
+
reply_to_message_id: Optional message ID to reply to
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
MessageResult with operation status and metadata
|
|
246
|
+
|
|
247
|
+
Note:
|
|
248
|
+
Sticker messages do not support captions.
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
Platform-specific exceptions for API failures
|
|
252
|
+
"""
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
# Interactive Messaging Methods
|
|
256
|
+
@abstractmethod
|
|
257
|
+
async def send_button_message(
|
|
258
|
+
self,
|
|
259
|
+
buttons: list[dict[str, str]],
|
|
260
|
+
recipient: str,
|
|
261
|
+
body: str,
|
|
262
|
+
header: dict | None = None,
|
|
263
|
+
footer: str | None = None,
|
|
264
|
+
reply_to_message_id: str | None = None,
|
|
265
|
+
) -> "MessageResult":
|
|
266
|
+
"""Send an interactive button message.
|
|
267
|
+
|
|
268
|
+
Supports up to 3 quick reply buttons with optional header and footer.
|
|
269
|
+
Based on WhatsApp Cloud API 2025 interactive button specifications.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
buttons: List of button objects with 'id' and 'title' keys (max 3 buttons)
|
|
273
|
+
recipient: Recipient identifier
|
|
274
|
+
body: Main message text (max 1024 characters)
|
|
275
|
+
header: Optional header content with type and content
|
|
276
|
+
footer: Optional footer text (max 60 characters)
|
|
277
|
+
reply_to_message_id: Optional message ID to reply to
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
MessageResult with operation status and metadata
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
Platform-specific exceptions for API failures
|
|
284
|
+
"""
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
@abstractmethod
|
|
288
|
+
async def send_list_message(
|
|
289
|
+
self,
|
|
290
|
+
sections: list[dict],
|
|
291
|
+
recipient: str,
|
|
292
|
+
body: str,
|
|
293
|
+
button_text: str,
|
|
294
|
+
header: str | None = None,
|
|
295
|
+
footer: str | None = None,
|
|
296
|
+
reply_to_message_id: str | None = None,
|
|
297
|
+
) -> "MessageResult":
|
|
298
|
+
"""Send an interactive list message.
|
|
299
|
+
|
|
300
|
+
Supports sectioned lists with rows (max 10 sections, 10 rows per section).
|
|
301
|
+
Based on WhatsApp Cloud API 2025 interactive list specifications.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
sections: List of section objects with title and rows
|
|
305
|
+
recipient: Recipient identifier
|
|
306
|
+
body: Main message text (max 4096 characters)
|
|
307
|
+
button_text: Text for the button that opens the list (max 20 characters)
|
|
308
|
+
header: Optional header text (max 60 characters)
|
|
309
|
+
footer: Optional footer text (max 60 characters)
|
|
310
|
+
reply_to_message_id: Optional message ID to reply to
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
MessageResult with operation status and metadata
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
Platform-specific exceptions for API failures
|
|
317
|
+
"""
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
@abstractmethod
|
|
321
|
+
async def send_cta_message(
|
|
322
|
+
self,
|
|
323
|
+
button_text: str,
|
|
324
|
+
button_url: str,
|
|
325
|
+
recipient: str,
|
|
326
|
+
body: str,
|
|
327
|
+
header: str | None = None,
|
|
328
|
+
footer: str | None = None,
|
|
329
|
+
reply_to_message_id: str | None = None,
|
|
330
|
+
) -> "MessageResult":
|
|
331
|
+
"""Send an interactive call-to-action URL button message.
|
|
332
|
+
|
|
333
|
+
Supports external URL buttons for call-to-action scenarios.
|
|
334
|
+
Based on WhatsApp Cloud API 2025 CTA URL specifications.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
button_text: Text to display on the button
|
|
338
|
+
button_url: URL to load when button is tapped (must start with http:// or https://)
|
|
339
|
+
recipient: Recipient identifier
|
|
340
|
+
body: Main message text
|
|
341
|
+
header: Optional header text
|
|
342
|
+
footer: Optional footer text
|
|
343
|
+
reply_to_message_id: Optional message ID to reply to
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
MessageResult with operation status and metadata
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
Platform-specific exceptions for API failures
|
|
350
|
+
"""
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
# Template Messaging Methods
|
|
354
|
+
@abstractmethod
|
|
355
|
+
async def send_text_template(
|
|
356
|
+
self,
|
|
357
|
+
template_name: str,
|
|
358
|
+
recipient: str,
|
|
359
|
+
body_parameters: list[dict] | None = None,
|
|
360
|
+
language_code: str = "es",
|
|
361
|
+
) -> "MessageResult":
|
|
362
|
+
"""Send a text-only template message.
|
|
363
|
+
|
|
364
|
+
Supports WhatsApp Business templates with parameter substitution.
|
|
365
|
+
Templates must be pre-approved by WhatsApp for use.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
template_name: Name of the approved WhatsApp template
|
|
369
|
+
recipient: Recipient identifier
|
|
370
|
+
body_parameters: List of parameter objects for text replacement
|
|
371
|
+
language_code: BCP-47 language code for template (default: "es")
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
MessageResult with operation status and metadata
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
Platform-specific exceptions for API failures
|
|
378
|
+
"""
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
@abstractmethod
|
|
382
|
+
async def send_media_template(
|
|
383
|
+
self,
|
|
384
|
+
template_name: str,
|
|
385
|
+
recipient: str,
|
|
386
|
+
media_type: str,
|
|
387
|
+
media_id: str | None = None,
|
|
388
|
+
media_url: str | None = None,
|
|
389
|
+
body_parameters: list[dict] | None = None,
|
|
390
|
+
language_code: str = "es",
|
|
391
|
+
) -> "MessageResult":
|
|
392
|
+
"""Send a template message with media header.
|
|
393
|
+
|
|
394
|
+
Supports templates with image, video, or document headers.
|
|
395
|
+
Either media_id (uploaded media) or media_url (external media) must be provided.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
template_name: Name of the approved WhatsApp template
|
|
399
|
+
recipient: Recipient identifier
|
|
400
|
+
media_type: Type of media header ("image", "video", "document")
|
|
401
|
+
media_id: ID of pre-uploaded media (exclusive with media_url)
|
|
402
|
+
media_url: URL of external media (exclusive with media_id)
|
|
403
|
+
body_parameters: List of parameter objects for text replacement
|
|
404
|
+
language_code: BCP-47 language code for template (default: "es")
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
MessageResult with operation status and metadata
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
Platform-specific exceptions for API failures
|
|
411
|
+
"""
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
@abstractmethod
|
|
415
|
+
async def send_location_template(
|
|
416
|
+
self,
|
|
417
|
+
template_name: str,
|
|
418
|
+
recipient: str,
|
|
419
|
+
latitude: str,
|
|
420
|
+
longitude: str,
|
|
421
|
+
name: str,
|
|
422
|
+
address: str,
|
|
423
|
+
body_parameters: list[dict] | None = None,
|
|
424
|
+
language_code: str = "es",
|
|
425
|
+
) -> "MessageResult":
|
|
426
|
+
"""Send a template message with location header.
|
|
427
|
+
|
|
428
|
+
Supports templates with geographic location headers showing a map preview.
|
|
429
|
+
Coordinates must be valid latitude (-90 to 90) and longitude (-180 to 180).
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
template_name: Name of the approved WhatsApp template
|
|
433
|
+
recipient: Recipient identifier
|
|
434
|
+
latitude: Location latitude as string (e.g., "37.483307")
|
|
435
|
+
longitude: Location longitude as string (e.g., "-122.148981")
|
|
436
|
+
name: Name/title of the location
|
|
437
|
+
address: Physical address of the location
|
|
438
|
+
body_parameters: List of parameter objects for text replacement
|
|
439
|
+
language_code: BCP-47 language code for template (default: "es")
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
MessageResult with operation status and metadata
|
|
443
|
+
|
|
444
|
+
Raises:
|
|
445
|
+
Platform-specific exceptions for API failures
|
|
446
|
+
"""
|
|
447
|
+
pass
|
|
448
|
+
|
|
449
|
+
# Specialized Messaging Methods
|
|
450
|
+
@abstractmethod
|
|
451
|
+
async def send_contact(
|
|
452
|
+
self, contact: dict, recipient: str, reply_to_message_id: str | None = None
|
|
453
|
+
) -> "MessageResult":
|
|
454
|
+
"""Send a contact card message.
|
|
455
|
+
|
|
456
|
+
Shares contact information including name, phone numbers, emails, and addresses.
|
|
457
|
+
Contact cards are automatically added to the recipient's address book.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
contact: Contact information dictionary with required 'name' and 'phones' fields
|
|
461
|
+
recipient: Recipient identifier
|
|
462
|
+
reply_to_message_id: Optional message ID to reply to
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
MessageResult with operation status and metadata
|
|
466
|
+
|
|
467
|
+
Raises:
|
|
468
|
+
Platform-specific exceptions for API failures
|
|
469
|
+
"""
|
|
470
|
+
pass
|
|
471
|
+
|
|
472
|
+
@abstractmethod
|
|
473
|
+
async def send_location(
|
|
474
|
+
self,
|
|
475
|
+
latitude: float,
|
|
476
|
+
longitude: float,
|
|
477
|
+
recipient: str,
|
|
478
|
+
name: str | None = None,
|
|
479
|
+
address: str | None = None,
|
|
480
|
+
reply_to_message_id: str | None = None,
|
|
481
|
+
) -> "MessageResult":
|
|
482
|
+
"""Send a location message.
|
|
483
|
+
|
|
484
|
+
Shares geographic coordinates with optional location name and address.
|
|
485
|
+
Recipients see a map preview with the shared location.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
latitude: Location latitude in decimal degrees (-90 to 90)
|
|
489
|
+
longitude: Location longitude in decimal degrees (-180 to 180)
|
|
490
|
+
recipient: Recipient identifier
|
|
491
|
+
name: Optional location name (e.g., "Coffee Shop")
|
|
492
|
+
address: Optional street address
|
|
493
|
+
reply_to_message_id: Optional message ID to reply to
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
MessageResult with operation status and metadata
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
Platform-specific exceptions for API failures
|
|
500
|
+
"""
|
|
501
|
+
pass
|
|
502
|
+
|
|
503
|
+
@abstractmethod
|
|
504
|
+
async def send_location_request(
|
|
505
|
+
self, body: str, recipient: str, reply_to_message_id: str | None = None
|
|
506
|
+
) -> "MessageResult":
|
|
507
|
+
"""Send a location request message.
|
|
508
|
+
|
|
509
|
+
Sends an interactive message that prompts the recipient to share their location.
|
|
510
|
+
Recipients see a "Send Location" button that allows easy location sharing.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
body: Request message text (max 1024 characters)
|
|
514
|
+
recipient: Recipient identifier
|
|
515
|
+
reply_to_message_id: Optional message ID to reply to
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
MessageResult with operation status and metadata
|
|
519
|
+
|
|
520
|
+
Raises:
|
|
521
|
+
Platform-specific exceptions for API failures
|
|
522
|
+
"""
|
|
523
|
+
pass
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pub/Sub repository interface.
|
|
3
|
+
|
|
4
|
+
Defines contract for Redis pub/sub messaging in Redis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from collections.abc import AsyncIterator, Callable
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .base_repository import IBaseRepository
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IPubSubRepository(IBaseRepository):
|
|
15
|
+
"""
|
|
16
|
+
Interface for Redis pub/sub messaging.
|
|
17
|
+
|
|
18
|
+
Handles real-time messaging and event broadcasting with context binding.
|
|
19
|
+
Uses the 'pubsub' Redis pool (database 5).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
async def publish_message(
|
|
24
|
+
self, channel: str, message: dict[str, Any], user_id: str | None = None
|
|
25
|
+
) -> int:
|
|
26
|
+
"""
|
|
27
|
+
Publish message to channel.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
channel: Channel name
|
|
31
|
+
message: Message data
|
|
32
|
+
user_id: Optional user context for filtering
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Number of subscribers that received the message
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
async def subscribe_to_channel(
|
|
41
|
+
self, channel: str, callback: Callable[[dict[str, Any]], None]
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Subscribe to channel with callback.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
channel: Channel name
|
|
48
|
+
callback: Function to call when message received
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def unsubscribe_from_channel(self, channel: str) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Unsubscribe from channel.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
channel: Channel name
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
async def subscribe_to_pattern(
|
|
64
|
+
self, pattern: str, callback: Callable[[str, dict[str, Any]], None]
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Subscribe to channel pattern with callback.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
pattern: Channel pattern (e.g., 'user:*:events')
|
|
71
|
+
callback: Function to call with (channel, message) when message received
|
|
72
|
+
"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def unsubscribe_from_pattern(self, pattern: str) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Unsubscribe from channel pattern.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
pattern: Channel pattern
|
|
82
|
+
"""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
async def get_channel_subscribers(self, channel: str) -> int:
|
|
87
|
+
"""
|
|
88
|
+
Get number of subscribers for channel.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
channel: Channel name
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Number of subscribers
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
@abstractmethod
|
|
99
|
+
async def get_active_channels(self) -> list[str]:
|
|
100
|
+
"""
|
|
101
|
+
Get list of active channels.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of channel names with active subscribers
|
|
105
|
+
"""
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
async def publish_user_event(
|
|
110
|
+
self, user_id: str, event_type: str, event_data: dict[str, Any]
|
|
111
|
+
) -> int:
|
|
112
|
+
"""
|
|
113
|
+
Publish user-specific event.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
user_id: User identifier
|
|
117
|
+
event_type: Type of event
|
|
118
|
+
event_data: Event data
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Number of subscribers that received the event
|
|
122
|
+
"""
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
async def subscribe_to_user_events(
|
|
127
|
+
self, user_id: str, callback: Callable[[str, dict[str, Any]], None]
|
|
128
|
+
) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Subscribe to all events for specific user.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
user_id: User identifier
|
|
134
|
+
callback: Function to call with (event_type, event_data)
|
|
135
|
+
"""
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
@abstractmethod
|
|
139
|
+
async def listen_for_messages(
|
|
140
|
+
self, channels: list[str]
|
|
141
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
142
|
+
"""
|
|
143
|
+
Listen for messages on multiple channels.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
channels: List of channel names
|
|
147
|
+
|
|
148
|
+
Yields:
|
|
149
|
+
Message data as it arrives
|
|
150
|
+
"""
|
|
151
|
+
pass
|