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,253 @@
1
+ """
2
+ Unified data types and enums for cross-platform messaging compatibility.
3
+
4
+ This module defines the common types used across all messaging platforms
5
+ to ensure consistent data handling regardless of the underlying platform.
6
+ """
7
+
8
+ from enum import Enum
9
+ from typing import Any
10
+
11
+
12
+ class PlatformType(str, Enum):
13
+ """Supported messaging platforms in the Mimeia platform."""
14
+
15
+ WHATSAPP = "whatsapp"
16
+ TELEGRAM = "telegram"
17
+ TEAMS = "teams"
18
+ INSTAGRAM = "instagram"
19
+
20
+
21
+ class MessageType(str, Enum):
22
+ """Universal message types across all platforms."""
23
+
24
+ TEXT = "text"
25
+ INTERACTIVE = "interactive"
26
+ BUTTON = "button" # Button reply messages (WhatsApp quick reply buttons)
27
+ IMAGE = "image"
28
+ AUDIO = "audio"
29
+ VIDEO = "video"
30
+ DOCUMENT = "document"
31
+ CONTACT = "contact"
32
+ LOCATION = "location"
33
+ ORDER = "order" # Order/catalog messages
34
+ STICKER = "sticker"
35
+ REACTION = "reaction"
36
+ SYSTEM = "system" # System notifications, member joins, etc.
37
+ UNSUPPORTED = "unsupported" # Unsupported message types
38
+
39
+
40
+ class WebhookType(str, Enum):
41
+ """Types of webhook events that platforms can send."""
42
+
43
+ INCOMING_MESSAGES = "incoming_messages"
44
+ STATUS_UPDATES = "status_updates"
45
+ ERRORS = "errors"
46
+ MEMBER_UPDATES = "member_updates" # For group/channel management
47
+ SYSTEM_EVENTS = "system_events"
48
+
49
+
50
+ class MessageStatus(str, Enum):
51
+ """Universal message delivery status across platforms."""
52
+
53
+ SENT = "sent"
54
+ DELIVERED = "delivered"
55
+ READ = "read"
56
+ FAILED = "failed"
57
+ DELETED = "deleted"
58
+ PENDING = "pending"
59
+
60
+
61
+ class InteractiveType(str, Enum):
62
+ """Types of interactive elements across platforms."""
63
+
64
+ BUTTON_REPLY = "button_reply"
65
+ LIST_REPLY = "list_reply"
66
+ QUICK_REPLY = "quick_reply"
67
+ INLINE_KEYBOARD = "inline_keyboard" # Telegram inline keyboards
68
+ CAROUSEL = "carousel"
69
+ MENU = "menu"
70
+
71
+
72
+ class MediaType(str, Enum):
73
+ """Media content types with MIME type mapping."""
74
+
75
+ IMAGE_JPEG = "image/jpeg"
76
+ IMAGE_PNG = "image/png"
77
+ IMAGE_GIF = "image/gif"
78
+ IMAGE_WEBP = "image/webp"
79
+
80
+ AUDIO_AAC = "audio/aac"
81
+ AUDIO_MP3 = "audio/mp3"
82
+ AUDIO_OGG = "audio/ogg"
83
+ AUDIO_WAV = "audio/wav"
84
+
85
+ VIDEO_MP4 = "video/mp4"
86
+ VIDEO_AVI = "video/avi"
87
+ VIDEO_MOV = "video/mov"
88
+ VIDEO_WEBM = "video/webm"
89
+
90
+ DOCUMENT_PDF = "application/pdf"
91
+ DOCUMENT_DOC = "application/msword"
92
+ DOCUMENT_DOCX = (
93
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
94
+ )
95
+ DOCUMENT_TXT = "text/plain"
96
+ DOCUMENT_CSV = "text/csv"
97
+
98
+
99
+ class ConversationType(str, Enum):
100
+ """Types of conversations across platforms."""
101
+
102
+ PRIVATE = "private" # 1-on-1 conversation
103
+ GROUP = "group" # Group chat
104
+ CHANNEL = "channel" # Broadcast channel
105
+ BUSINESS = "business" # Business conversation (WhatsApp Business)
106
+
107
+
108
+ class UserRole(str, Enum):
109
+ """User roles in conversations."""
110
+
111
+ MEMBER = "member"
112
+ ADMIN = "admin"
113
+ OWNER = "owner"
114
+ MODERATOR = "moderator"
115
+ BOT = "bot"
116
+
117
+
118
+ # Type aliases for complex types
119
+ PlatformData = dict[str, Any]
120
+ MessageMetadata = dict[str, Any]
121
+ UniversalMessageData = dict[str, str | int | bool | None | dict | list]
122
+
123
+
124
+ class ErrorCode(str, Enum):
125
+ """Universal error codes for webhook processing."""
126
+
127
+ VALIDATION_ERROR = "validation_error"
128
+ AUTHENTICATION_ERROR = "authentication_error"
129
+ RATE_LIMIT_ERROR = "rate_limit_error"
130
+ PLATFORM_ERROR = "platform_error"
131
+ NETWORK_ERROR = "network_error"
132
+ PROCESSING_ERROR = "processing_error"
133
+ UNKNOWN_MESSAGE_TYPE = "unknown_message_type"
134
+ SIGNATURE_VALIDATION_FAILED = "signature_validation_failed"
135
+
136
+
137
+ class ProcessingPriority(str, Enum):
138
+ """Priority levels for message processing."""
139
+
140
+ LOW = "low"
141
+ NORMAL = "normal"
142
+ HIGH = "high"
143
+ URGENT = "urgent"
144
+
145
+
146
+ # Platform capability mapping
147
+ PLATFORM_CAPABILITIES = {
148
+ PlatformType.WHATSAPP: {
149
+ "message_types": {
150
+ MessageType.TEXT,
151
+ MessageType.INTERACTIVE,
152
+ MessageType.BUTTON,
153
+ MessageType.IMAGE,
154
+ MessageType.AUDIO,
155
+ MessageType.VIDEO,
156
+ MessageType.DOCUMENT,
157
+ MessageType.CONTACT,
158
+ MessageType.LOCATION,
159
+ MessageType.ORDER,
160
+ MessageType.STICKER,
161
+ MessageType.REACTION,
162
+ MessageType.SYSTEM,
163
+ MessageType.UNSUPPORTED,
164
+ },
165
+ "interactive_types": {InteractiveType.BUTTON_REPLY, InteractiveType.LIST_REPLY},
166
+ "max_text_length": 4096,
167
+ "max_media_size": 16 * 1024 * 1024, # 16MB
168
+ "supports_threads": False,
169
+ "supports_reactions": True,
170
+ "supports_editing": False,
171
+ },
172
+ PlatformType.TELEGRAM: {
173
+ "message_types": {
174
+ MessageType.TEXT,
175
+ MessageType.INTERACTIVE,
176
+ MessageType.IMAGE,
177
+ MessageType.AUDIO,
178
+ MessageType.VIDEO,
179
+ MessageType.DOCUMENT,
180
+ MessageType.CONTACT,
181
+ MessageType.LOCATION,
182
+ MessageType.STICKER,
183
+ },
184
+ "interactive_types": {
185
+ InteractiveType.INLINE_KEYBOARD,
186
+ InteractiveType.QUICK_REPLY,
187
+ },
188
+ "max_text_length": 4096,
189
+ "max_media_size": 50 * 1024 * 1024, # 50MB
190
+ "supports_threads": True,
191
+ "supports_reactions": True,
192
+ "supports_editing": True,
193
+ },
194
+ PlatformType.TEAMS: {
195
+ "message_types": {MessageType.TEXT, MessageType.IMAGE, MessageType.DOCUMENT},
196
+ "interactive_types": {InteractiveType.BUTTON_REPLY, InteractiveType.CAROUSEL},
197
+ "max_text_length": 28000,
198
+ "max_media_size": 100 * 1024 * 1024, # 100MB
199
+ "supports_threads": True,
200
+ "supports_reactions": True,
201
+ "supports_editing": True,
202
+ },
203
+ PlatformType.INSTAGRAM: {
204
+ "message_types": {
205
+ MessageType.TEXT,
206
+ MessageType.IMAGE,
207
+ MessageType.VIDEO,
208
+ MessageType.STICKER,
209
+ },
210
+ "interactive_types": {
211
+ InteractiveType.QUICK_REPLY,
212
+ InteractiveType.BUTTON_REPLY,
213
+ },
214
+ "max_text_length": 1000,
215
+ "max_media_size": 8 * 1024 * 1024, # 8MB
216
+ "supports_threads": False,
217
+ "supports_reactions": True,
218
+ "supports_editing": False,
219
+ },
220
+ }
221
+
222
+
223
+ def get_platform_capabilities(platform: PlatformType) -> dict[str, Any]:
224
+ """Get capabilities for a specific platform."""
225
+ return PLATFORM_CAPABILITIES.get(platform, {})
226
+
227
+
228
+ def is_message_type_supported(
229
+ platform: PlatformType, message_type: MessageType
230
+ ) -> bool:
231
+ """Check if a message type is supported by a platform."""
232
+ capabilities = get_platform_capabilities(platform)
233
+ return message_type in capabilities.get("message_types", set())
234
+
235
+
236
+ def is_interactive_type_supported(
237
+ platform: PlatformType, interactive_type: InteractiveType
238
+ ) -> bool:
239
+ """Check if an interactive type is supported by a platform."""
240
+ capabilities = get_platform_capabilities(platform)
241
+ return interactive_type in capabilities.get("interactive_types", set())
242
+
243
+
244
+ def get_max_text_length(platform: PlatformType) -> int:
245
+ """Get maximum text length for a platform."""
246
+ capabilities = get_platform_capabilities(platform)
247
+ return capabilities.get("max_text_length", 4096)
248
+
249
+
250
+ def get_max_media_size(platform: PlatformType) -> int:
251
+ """Get maximum media file size for a platform."""
252
+ capabilities = get_platform_capabilities(platform)
253
+ return capabilities.get("max_media_size", 16 * 1024 * 1024)
@@ -0,0 +1,48 @@
1
+ """
2
+ Universal Webhook Interface system for platform-agnostic webhook handling.
3
+
4
+ This module provides universal webhook interfaces that all messaging platforms
5
+ must adapt to, using WhatsApp as the comprehensive template. The system uses
6
+ 3 universal webhook types that work across all platforms:
7
+
8
+ 1. IncomingMessageWebhook - All incoming messages from users
9
+ 2. StatusWebhook - Message delivery status updates (includes "outgoing" status)
10
+ 3. ErrorWebhook - System, app, and account-level errors
11
+
12
+ Note: "Outgoing message" webhooks are actually status updates that use StatusWebhook.
13
+
14
+ All platforms (WhatsApp, Teams, Telegram, Instagram) must transform their
15
+ platform-specific webhooks into these universal interfaces via processor adapters.
16
+ """
17
+
18
+ from .base_components import (
19
+ AdReferralBase,
20
+ BusinessContextBase,
21
+ ConversationBase,
22
+ ErrorDetailBase,
23
+ ForwardContextBase,
24
+ TenantBase,
25
+ UserBase,
26
+ )
27
+ from .universal_webhooks import (
28
+ ErrorWebhook,
29
+ IncomingMessageWebhook,
30
+ StatusWebhook,
31
+ UniversalWebhook,
32
+ )
33
+
34
+ __all__ = [
35
+ # Base components
36
+ "TenantBase",
37
+ "UserBase",
38
+ "BusinessContextBase",
39
+ "ForwardContextBase",
40
+ "AdReferralBase",
41
+ "ConversationBase",
42
+ "ErrorDetailBase",
43
+ # Universal webhook interfaces
44
+ "IncomingMessageWebhook",
45
+ "StatusWebhook",
46
+ "ErrorWebhook",
47
+ "UniversalWebhook",
48
+ ]
@@ -0,0 +1,293 @@
1
+ """
2
+ Universal base components for platform-agnostic webhook interfaces.
3
+
4
+ These components provide the building blocks for all universal webhook types.
5
+ They are designed based on WhatsApp's comprehensive webhook structure and
6
+ represent the "standard" that all messaging platforms should adapt to.
7
+ """
8
+
9
+ from datetime import datetime
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+
13
+
14
+ class TenantBase(BaseModel):
15
+ """
16
+ Universal business/tenant identification component.
17
+
18
+ Represents the business account that messages are sent to/from.
19
+ Based on WhatsApp's metadata structure but platform-agnostic.
20
+ """
21
+
22
+ model_config = ConfigDict(
23
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
24
+ )
25
+
26
+ # Core tenant identification
27
+ business_phone_number_id: str = Field(description="Unique business phone number ID")
28
+ display_phone_number: str = Field(description="Business display phone number")
29
+
30
+ # Platform-specific tenant ID (WhatsApp Business Account ID, Teams tenant ID, etc.)
31
+ platform_tenant_id: str = Field(description="Platform-specific tenant identifier")
32
+
33
+ def get_tenant_key(self) -> str:
34
+ """Get unique tenant key for this business account."""
35
+ # For WhatsApp, the business_phone_number_id IS the tenant identifier
36
+ # For other platforms, they might use platform_tenant_id, but for consistency
37
+ # we use business_phone_number_id as the primary tenant key
38
+ return self.business_phone_number_id
39
+
40
+
41
+ class UserBase(BaseModel):
42
+ """
43
+ Universal user/contact identification component.
44
+
45
+ Represents the end user sending messages to the business.
46
+ Based on WhatsApp's contact structure but platform-agnostic.
47
+ """
48
+
49
+ model_config = ConfigDict(
50
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
51
+ )
52
+
53
+ # Core user identification
54
+ user_id: str = Field(
55
+ description="Platform-specific user ID (WhatsApp wa_id, Teams user ID, etc.)"
56
+ )
57
+ phone_number: str = Field(description="User's phone number")
58
+
59
+ # User profile information
60
+ profile_name: str | None = Field(default=None, description="User's display name")
61
+
62
+ # Security and identity
63
+ identity_key_hash: str | None = Field(
64
+ default=None,
65
+ description="Identity key hash for security validation (WhatsApp feature)",
66
+ )
67
+
68
+ def get_display_name(self) -> str:
69
+ """Get user's display name or fallback to phone number."""
70
+ return self.profile_name or self.phone_number
71
+
72
+ def get_user_key(self) -> str:
73
+ """Get unique user key for this contact."""
74
+ return f"{self.user_id}:{self.phone_number}"
75
+
76
+
77
+ class BusinessContextBase(BaseModel):
78
+ """
79
+ Universal business interaction context component.
80
+
81
+ Represents context when users interact with business features like
82
+ product catalogs, business buttons, etc. Based on WhatsApp's business
83
+ features but designed to be universal.
84
+ """
85
+
86
+ model_config = ConfigDict(
87
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
88
+ )
89
+
90
+ # Business message context
91
+ contextual_message_id: str = Field(
92
+ description="ID of the business message that triggered this interaction"
93
+ )
94
+ business_phone_number: str = Field(description="Business phone number from context")
95
+
96
+ # Product catalog context (for e-commerce interactions)
97
+ catalog_id: str | None = Field(default=None, description="Product catalog ID")
98
+ product_retailer_id: str | None = Field(
99
+ default=None, description="Product ID from catalog"
100
+ )
101
+
102
+ def has_product_context(self) -> bool:
103
+ """Check if this interaction involves a product catalog."""
104
+ return self.catalog_id is not None and self.product_retailer_id is not None
105
+
106
+
107
+ class ForwardContextBase(BaseModel):
108
+ """
109
+ Universal message forwarding context component.
110
+
111
+ Represents information about forwarded messages. Based on WhatsApp's
112
+ forwarding detection but designed to be universal.
113
+ """
114
+
115
+ model_config = ConfigDict(
116
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
117
+ )
118
+
119
+ # Forwarding indicators
120
+ is_forwarded: bool = Field(
121
+ default=False, description="Whether the message was forwarded"
122
+ )
123
+ is_frequently_forwarded: bool = Field(
124
+ default=False,
125
+ description="Whether the message has been forwarded multiple times (>5)",
126
+ )
127
+
128
+ # Optional forwarding metadata
129
+ forward_count: int | None = Field(
130
+ default=None, description="Number of times forwarded (if known)"
131
+ )
132
+ original_sender: str | None = Field(
133
+ default=None, description="Original sender ID (if available)"
134
+ )
135
+
136
+
137
+ class AdReferralBase(BaseModel):
138
+ """
139
+ Universal advertisement referral context component.
140
+
141
+ Represents information when users interact via advertisements.
142
+ Based on WhatsApp's Click-to-WhatsApp ads but designed to be universal
143
+ for all advertising platforms.
144
+ """
145
+
146
+ model_config = ConfigDict(
147
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
148
+ )
149
+
150
+ # Core ad identification
151
+ source_type: str = Field(
152
+ description="Type of ad source (e.g., 'ad', 'social', 'search')"
153
+ )
154
+ source_id: str = Field(description="Advertisement ID")
155
+ source_url: str = Field(description="Advertisement URL")
156
+
157
+ # Ad content
158
+ ad_body: str | None = Field(default=None, description="Advertisement primary text")
159
+ ad_headline: str | None = Field(default=None, description="Advertisement headline")
160
+
161
+ # Media content
162
+ media_type: str | None = Field(
163
+ default=None, description="Ad media type (image, video)"
164
+ )
165
+ image_url: str | None = Field(default=None, description="Ad image URL")
166
+ video_url: str | None = Field(default=None, description="Ad video URL")
167
+ thumbnail_url: str | None = Field(
168
+ default=None, description="Ad video thumbnail URL"
169
+ )
170
+
171
+ # Tracking and attribution
172
+ click_id: str | None = Field(default=None, description="Click tracking ID")
173
+
174
+ # Welcome message (for chat ads)
175
+ welcome_message_text: str | None = Field(
176
+ default=None, description="Predefined welcome message from the ad"
177
+ )
178
+
179
+ def has_media(self) -> bool:
180
+ """Check if this ad referral includes media content."""
181
+ return self.image_url is not None or self.video_url is not None
182
+
183
+ def is_video_ad(self) -> bool:
184
+ """Check if this is a video advertisement."""
185
+ return self.media_type == "video" and self.video_url is not None
186
+
187
+
188
+ class ConversationBase(BaseModel):
189
+ """
190
+ Universal conversation and billing context component.
191
+
192
+ Represents conversation state and billing information. Based on WhatsApp's
193
+ conversation-based pricing but designed to be universal for all platforms
194
+ that may implement conversation tracking.
195
+ """
196
+
197
+ model_config = ConfigDict(
198
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
199
+ )
200
+
201
+ # Conversation identification
202
+ conversation_id: str = Field(description="Unique conversation identifier")
203
+ expiration_timestamp: datetime | None = Field(
204
+ default=None, description="When this conversation window expires"
205
+ )
206
+
207
+ # Conversation categorization
208
+ category: str | None = Field(
209
+ default=None,
210
+ description="Conversation category (service, marketing, authentication, etc.)",
211
+ )
212
+ origin_type: str | None = Field(
213
+ default=None,
214
+ description="How this conversation was initiated (user, business, service, etc.)",
215
+ )
216
+
217
+ # Billing information
218
+ is_billable: bool | None = Field(
219
+ default=None, description="Whether this conversation is billable"
220
+ )
221
+ pricing_model: str | None = Field(
222
+ default=None,
223
+ description="Pricing model (CBP=conversation-based, PMP=per-message)",
224
+ )
225
+ pricing_category: str | None = Field(
226
+ default=None,
227
+ description="Pricing category/rate (service, marketing, authentication, etc.)",
228
+ )
229
+ pricing_type: str | None = Field(
230
+ default=None,
231
+ description="Pricing type (regular, free_customer_service, free_entry_point)",
232
+ )
233
+
234
+ def is_free_conversation(self) -> bool:
235
+ """Check if this conversation is free (not billable)."""
236
+ return self.is_billable is False or self.pricing_type in [
237
+ "free_customer_service",
238
+ "free_entry_point",
239
+ ]
240
+
241
+ def is_expired(self) -> bool:
242
+ """Check if this conversation has expired."""
243
+ if self.expiration_timestamp is None:
244
+ return False
245
+ return datetime.utcnow() > self.expiration_timestamp
246
+
247
+
248
+ class ErrorDetailBase(BaseModel):
249
+ """
250
+ Universal error detail component.
251
+
252
+ Represents structured error information from messaging platforms.
253
+ Based on WhatsApp's error structure but designed to be universal.
254
+ """
255
+
256
+ model_config = ConfigDict(
257
+ extra="forbid", str_strip_whitespace=True, validate_assignment=True
258
+ )
259
+
260
+ # Core error identification
261
+ error_code: int = Field(description="Numeric error code")
262
+ error_title: str = Field(description="Human-readable error title")
263
+ error_message: str = Field(description="Detailed error message")
264
+
265
+ # Additional error context
266
+ error_details: str | None = Field(
267
+ default=None, description="Extended error details"
268
+ )
269
+ documentation_url: str | None = Field(
270
+ default=None, description="URL to error code documentation"
271
+ )
272
+
273
+ # Error categorization
274
+ error_type: str | None = Field(
275
+ default=None, description="Type of error (system, validation, rate_limit, etc.)"
276
+ )
277
+ is_retryable: bool | None = Field(
278
+ default=None, description="Whether this error condition can be retried"
279
+ )
280
+
281
+ # Timestamp
282
+ occurred_at: datetime | None = Field(
283
+ default=None, description="When this error occurred"
284
+ )
285
+
286
+ def is_temporary_error(self) -> bool:
287
+ """Check if this is likely a temporary error that could be retried."""
288
+ temporary_codes = [429, 500, 502, 503, 504] # Rate limits and server errors
289
+ return self.error_code in temporary_codes or self.is_retryable is True
290
+
291
+ def get_error_summary(self) -> str:
292
+ """Get a concise error summary for logging."""
293
+ return f"Error {self.error_code}: {self.error_title}"