wappa 0.1.8__py3-none-any.whl → 0.1.10__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 (147) hide show
  1. wappa/__init__.py +4 -5
  2. wappa/api/controllers/webhook_controller.py +5 -2
  3. wappa/api/dependencies/__init__.py +0 -5
  4. wappa/api/middleware/error_handler.py +4 -4
  5. wappa/api/middleware/owner.py +11 -5
  6. wappa/api/routes/webhooks.py +2 -2
  7. wappa/cli/__init__.py +1 -1
  8. wappa/cli/examples/init/.env.example +33 -0
  9. wappa/cli/examples/init/app/__init__.py +0 -0
  10. wappa/cli/examples/init/app/main.py +9 -0
  11. wappa/cli/examples/init/app/master_event.py +10 -0
  12. wappa/cli/examples/json_cache_example/.env.example +33 -0
  13. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  14. wappa/cli/examples/json_cache_example/app/main.py +247 -0
  15. wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
  16. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  17. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
  18. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  19. wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
  20. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
  21. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
  22. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
  23. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
  24. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  25. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
  26. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
  27. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  28. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  29. wappa/cli/examples/openai_transcript/app/main.py +9 -0
  30. wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
  31. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  32. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
  33. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  34. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  35. wappa/cli/examples/redis_cache_example/app/main.py +246 -0
  36. wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
  37. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
  38. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  39. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
  40. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
  41. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
  42. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
  43. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
  44. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  45. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
  46. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
  47. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  48. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  49. wappa/cli/examples/simple_echo_example/app/main.py +191 -0
  50. wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
  51. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  52. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  53. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  54. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  55. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
  56. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
  57. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
  58. wappa/cli/examples/wappa_full_example/app/main.py +269 -0
  59. wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
  60. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  61. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  62. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  63. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  64. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  65. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  66. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  67. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  68. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  69. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  70. wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
  71. wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
  72. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
  73. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  74. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
  75. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
  76. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
  77. wappa/cli/main.py +14 -5
  78. wappa/core/__init__.py +18 -23
  79. wappa/core/config/settings.py +7 -5
  80. wappa/core/events/default_handlers.py +1 -1
  81. wappa/core/factory/wappa_builder.py +38 -25
  82. wappa/core/plugins/redis_plugin.py +1 -3
  83. wappa/core/plugins/wappa_core_plugin.py +7 -6
  84. wappa/core/types.py +12 -12
  85. wappa/core/wappa_app.py +10 -8
  86. wappa/database/__init__.py +3 -4
  87. wappa/domain/enums/messenger_platform.py +1 -2
  88. wappa/domain/factories/media_factory.py +5 -20
  89. wappa/domain/factories/message_factory.py +5 -20
  90. wappa/domain/factories/messenger_factory.py +2 -4
  91. wappa/domain/interfaces/cache_interface.py +7 -7
  92. wappa/domain/interfaces/media_interface.py +2 -5
  93. wappa/domain/models/media_result.py +1 -3
  94. wappa/domain/models/platforms/platform_config.py +1 -3
  95. wappa/messaging/__init__.py +9 -12
  96. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
  97. wappa/models/__init__.py +27 -35
  98. wappa/persistence/__init__.py +12 -15
  99. wappa/persistence/cache_factory.py +0 -1
  100. wappa/persistence/json/__init__.py +1 -1
  101. wappa/persistence/json/cache_adapters.py +37 -25
  102. wappa/persistence/json/handlers/state_handler.py +60 -52
  103. wappa/persistence/json/handlers/table_handler.py +51 -49
  104. wappa/persistence/json/handlers/user_handler.py +71 -55
  105. wappa/persistence/json/handlers/utils/file_manager.py +42 -39
  106. wappa/persistence/json/handlers/utils/key_factory.py +1 -1
  107. wappa/persistence/json/handlers/utils/serialization.py +13 -11
  108. wappa/persistence/json/json_cache_factory.py +4 -8
  109. wappa/persistence/json/storage_manager.py +66 -79
  110. wappa/persistence/memory/__init__.py +1 -1
  111. wappa/persistence/memory/cache_adapters.py +37 -25
  112. wappa/persistence/memory/handlers/state_handler.py +62 -52
  113. wappa/persistence/memory/handlers/table_handler.py +59 -53
  114. wappa/persistence/memory/handlers/user_handler.py +75 -55
  115. wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
  116. wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
  117. wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
  118. wappa/persistence/memory/memory_cache_factory.py +3 -7
  119. wappa/persistence/memory/storage_manager.py +52 -62
  120. wappa/persistence/redis/cache_adapters.py +27 -21
  121. wappa/persistence/redis/ops.py +11 -11
  122. wappa/persistence/redis/redis_client.py +4 -6
  123. wappa/persistence/redis/redis_manager.py +12 -4
  124. wappa/processors/factory.py +5 -5
  125. wappa/schemas/factory.py +2 -5
  126. wappa/schemas/whatsapp/message_types/errors.py +3 -12
  127. wappa/schemas/whatsapp/validators.py +3 -3
  128. wappa/webhooks/__init__.py +17 -18
  129. wappa/webhooks/factory.py +3 -5
  130. wappa/webhooks/whatsapp/__init__.py +10 -13
  131. wappa/webhooks/whatsapp/message_types/audio.py +0 -4
  132. wappa/webhooks/whatsapp/message_types/document.py +1 -9
  133. wappa/webhooks/whatsapp/message_types/errors.py +3 -12
  134. wappa/webhooks/whatsapp/message_types/location.py +1 -21
  135. wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
  136. wappa/webhooks/whatsapp/message_types/text.py +0 -6
  137. wappa/webhooks/whatsapp/message_types/video.py +1 -20
  138. wappa/webhooks/whatsapp/status_models.py +2 -2
  139. wappa/webhooks/whatsapp/validators.py +3 -3
  140. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
  141. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
  142. wappa/cli/examples/init/pyproject.toml +0 -7
  143. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  144. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  145. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
  146. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
  147. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,504 @@
1
+ """
2
+ Master Event Handler for Wappa Full Example Application
3
+
4
+ This is the main WappaEventHandler implementation that demonstrates comprehensive
5
+ WhatsApp Business API functionality including metadata extraction, interactive
6
+ commands, state management, and all message type handling.
7
+ """
8
+
9
+ import time
10
+
11
+ from wappa import WappaEventHandler
12
+ from wappa.webhooks import ErrorWebhook, IncomingMessageWebhook, StatusWebhook
13
+
14
+ from .handlers.command_handlers import (
15
+ CommandHandlers,
16
+ get_command_from_text,
17
+ is_special_command,
18
+ )
19
+ from .handlers.message_handlers import MessageHandlers, handle_message_by_type
20
+ from .handlers.state_handlers import StateHandlers, handle_user_in_state
21
+ from .models.user_models import UserProfile
22
+ from .utils.cache_utils import CacheHelper
23
+
24
+
25
+ class WappaFullExampleHandler(WappaEventHandler):
26
+ """
27
+ Comprehensive WappaEventHandler implementation demonstrating all framework features.
28
+
29
+ Features:
30
+ - Complete message type handling with metadata extraction
31
+ - Interactive commands (/button, /list, /cta, /location) with state management
32
+ - Media relay functionality using media_id
33
+ - User tracking and analytics with Redis cache
34
+ - Professional error handling and logging
35
+ - Welcome messages for first-time users
36
+ - State-based interactive workflows with TTL
37
+ """
38
+
39
+ def __init__(self):
40
+ """Initialize the comprehensive Wappa example handler."""
41
+ super().__init__()
42
+
43
+ # Handler instances (initialized per request)
44
+ self.cache_helper: CacheHelper | None = None
45
+ self.message_handlers: MessageHandlers | None = None
46
+ self.command_handlers: CommandHandlers | None = None
47
+ self.state_handlers: StateHandlers | None = None
48
+
49
+ # Statistics
50
+ self._total_messages = 0
51
+ self._successful_processing = 0
52
+ self._failed_processing = 0
53
+ self._first_time_users = 0
54
+ self._commands_processed = 0
55
+ self._interactive_responses = 0
56
+
57
+ self.logger.info(
58
+ "🚀 WappaFullExampleHandler initialized - comprehensive demo ready"
59
+ )
60
+
61
+ async def process_message(self, webhook: IncomingMessageWebhook) -> None:
62
+ """
63
+ Main message processing method with comprehensive functionality.
64
+
65
+ This method orchestrates:
66
+ 1. Dependency validation and setup
67
+ 2. User profile management and welcome messages
68
+ 3. Message reading acknowledgment
69
+ 4. Interactive state checking and processing
70
+ 5. Special command handling
71
+ 6. Regular message processing with metadata extraction
72
+ 7. Statistics tracking and logging
73
+
74
+ Args:
75
+ webhook: IncomingMessageWebhook containing message data
76
+ """
77
+ start_time = time.time()
78
+ self._total_messages += 1
79
+
80
+ try:
81
+ # 1. Setup dependencies and validate
82
+ if not await self._setup_dependencies():
83
+ self._failed_processing += 1
84
+ return
85
+
86
+ # 2. Handle user profile and first-time user welcome
87
+ user_profile = await self._handle_user_profile_and_welcome(webhook)
88
+ if not user_profile:
89
+ self.logger.warning(
90
+ "Failed to handle user profile, continuing without caching"
91
+ )
92
+ user_profile = UserProfile(phone_number=webhook.user.user_id)
93
+
94
+ # 3. Mark message as read (as specified in requirements)
95
+ await self._mark_message_as_read(webhook)
96
+
97
+ # Extract basic message info for routing
98
+ user_id = webhook.user.user_id
99
+ message_text = webhook.get_message_text().strip()
100
+ message_type = webhook.get_message_type_name()
101
+
102
+ self.logger.info(
103
+ f"📨 Processing {message_type} from {user_id}: "
104
+ f"'{message_text[:50]}{'...' if len(message_text) > 50 else ''}'"
105
+ )
106
+
107
+ # 4. Check for active interactive states first
108
+ state_result = await handle_user_in_state(
109
+ webhook, user_profile, self.messenger, self.cache_factory, self.logger
110
+ )
111
+
112
+ if state_result is not None:
113
+ # User was in an interactive state - handle accordingly
114
+ if state_result["success"]:
115
+ self._interactive_responses += 1
116
+ self._successful_processing += 1
117
+ self.logger.info(
118
+ f"✅ Interactive state handled: {state_result.get('selection_type', 'unknown')}"
119
+ )
120
+ else:
121
+ self.logger.info(
122
+ f"🔄 Interactive state reminder sent: {state_result.get('error', 'unknown')}"
123
+ )
124
+
125
+ await self._log_processing_stats(start_time)
126
+ return
127
+
128
+ # 5. Check for special commands (only for text messages)
129
+ if message_type == "text" and is_special_command(message_text):
130
+ command = get_command_from_text(message_text)
131
+ await self._handle_special_command(webhook, user_profile, command)
132
+ self._commands_processed += 1
133
+ self._successful_processing += 1
134
+ await self._log_processing_stats(start_time)
135
+ return
136
+
137
+ # 6. Handle regular message processing with metadata
138
+ result = await handle_message_by_type(
139
+ webhook, user_profile, self.messenger, self.cache_factory, self.logger
140
+ )
141
+
142
+ if result["success"]:
143
+ self._successful_processing += 1
144
+ self.logger.info(f"✅ {message_type} message processed successfully")
145
+ else:
146
+ self._failed_processing += 1
147
+ self.logger.error(
148
+ f"❌ {message_type} message processing failed: {result.get('error')}"
149
+ )
150
+
151
+ # Send error response to user
152
+ await self._send_error_response(
153
+ webhook, result.get("error", "Processing failed")
154
+ )
155
+
156
+ await self._log_processing_stats(start_time)
157
+
158
+ except Exception as e:
159
+ self._failed_processing += 1
160
+ self.logger.error(
161
+ f"❌ Critical error in message processing: {e}", exc_info=True
162
+ )
163
+
164
+ # Send generic error response
165
+ try:
166
+ await self.messenger.send_text(
167
+ recipient=webhook.user.user_id,
168
+ text="❌ Sorry, something went wrong processing your message. Please try again.",
169
+ reply_to_message_id=webhook.message.message_id,
170
+ )
171
+ except Exception as send_error:
172
+ self.logger.error(f"❌ Failed to send error response: {send_error}")
173
+
174
+ async def process_status(self, webhook: StatusWebhook) -> None:
175
+ """
176
+ Process status webhooks from WhatsApp Business API.
177
+
178
+ Args:
179
+ webhook: StatusWebhook containing delivery status information
180
+ """
181
+ try:
182
+ status_value = webhook.status.value
183
+ recipient = webhook.recipient_id
184
+ message_id = webhook.message_id
185
+
186
+ self.logger.info(
187
+ f"📊 Message status: {status_value.upper()} for {recipient} (msg: {message_id[:20]}...)"
188
+ )
189
+
190
+ # You can add custom status processing here
191
+ # For example: update delivery statistics, handle failed deliveries, etc.
192
+
193
+ except Exception as e:
194
+ self.logger.error(f"❌ Error processing status webhook: {e}", exc_info=True)
195
+
196
+ async def process_error(self, webhook: ErrorWebhook) -> None:
197
+ """
198
+ Process error webhooks from WhatsApp Business API.
199
+
200
+ Args:
201
+ webhook: ErrorWebhook containing error information
202
+ """
203
+ try:
204
+ error_count = webhook.get_error_count()
205
+ primary_error = webhook.get_primary_error()
206
+
207
+ self.logger.error(
208
+ f"🚨 WhatsApp API error: {error_count} errors, "
209
+ f"primary: {primary_error.error_code} - {primary_error.error_title}"
210
+ )
211
+
212
+ # Record error in statistics
213
+ self._failed_processing += 1
214
+
215
+ # You can add custom error handling logic here
216
+ # For example: alerting systems, retry mechanisms, etc.
217
+
218
+ except Exception as e:
219
+ self.logger.error(f"❌ Error processing error webhook: {e}", exc_info=True)
220
+
221
+ async def _setup_dependencies(self) -> bool:
222
+ """Setup handler dependencies and validate state."""
223
+ if not self.validate_dependencies():
224
+ self.logger.error("❌ Dependencies not properly injected")
225
+ return False
226
+
227
+ if not self.cache_factory:
228
+ self.logger.error(
229
+ "❌ Cache factory not available - Redis caching unavailable"
230
+ )
231
+ return False
232
+
233
+ try:
234
+ # Initialize helper instances
235
+ self.cache_helper = CacheHelper(self.cache_factory)
236
+ self.message_handlers = MessageHandlers(
237
+ self.messenger, self.cache_factory, self.logger
238
+ )
239
+ self.command_handlers = CommandHandlers(
240
+ self.messenger, self.cache_factory, self.logger
241
+ )
242
+ self.state_handlers = StateHandlers(
243
+ self.messenger, self.cache_factory, self.logger
244
+ )
245
+
246
+ self.logger.debug("✅ Handler dependencies initialized successfully")
247
+ return True
248
+
249
+ except Exception as e:
250
+ self.logger.error(f"❌ Failed to setup dependencies: {e}", exc_info=True)
251
+ return False
252
+
253
+ async def _handle_user_profile_and_welcome(
254
+ self, webhook: IncomingMessageWebhook
255
+ ) -> UserProfile | None:
256
+ """Handle user profile caching and send welcome message to first-time users."""
257
+ try:
258
+ user_id = webhook.user.user_id
259
+ user_name = webhook.user.profile_name
260
+
261
+ # Get or create user profile
262
+ user_profile = await self.cache_helper.get_or_create_user_profile(
263
+ user_id, user_name, user_name
264
+ )
265
+
266
+ # Send welcome message to first-time users
267
+ if (
268
+ user_profile.is_first_time_user
269
+ and not user_profile.has_received_welcome
270
+ ):
271
+ await self._send_welcome_message(webhook, user_profile)
272
+ user_profile.mark_welcome_sent()
273
+ await self.cache_helper.save_user_profile(user_profile)
274
+ self._first_time_users += 1
275
+
276
+ return user_profile
277
+
278
+ except Exception as e:
279
+ self.logger.error(f"❌ Error handling user profile: {e}", exc_info=True)
280
+ return None
281
+
282
+ async def _send_welcome_message(
283
+ self, webhook: IncomingMessageWebhook, user_profile: UserProfile
284
+ ) -> None:
285
+ """Send welcome message with instructions to first-time users."""
286
+ user_id = webhook.user.user_id
287
+ display_name = user_profile.get_display_name()
288
+
289
+ welcome_text = (
290
+ f"🎉 *Welcome to Wappa Full Example, {display_name}!*\n\n"
291
+ f"This is a comprehensive demonstration of the Wappa framework capabilities.\n\n"
292
+ f"🚀 *What this example does:*\n"
293
+ f"• Echoes all message types with detailed metadata\n"
294
+ f"• Demonstrates interactive features (buttons, lists, CTA, locations)\n"
295
+ f"• Shows state management with TTL\n"
296
+ f"• Tracks user activity with Redis cache\n"
297
+ f"• Handles media relay using media_id\n\n"
298
+ f"🎯 *Try these special commands:*\n"
299
+ f"• `/button` - Interactive button demo with animal selection\n"
300
+ f"• `/list` - Interactive list demo with media files\n"
301
+ f"• `/cta` - Call-to-action button with external link\n"
302
+ f"• `/location` - Location sharing demonstration\n\n"
303
+ f"📨 *Send any message type to see it echoed with metadata:*\n"
304
+ f"• Text messages → Echo with metadata\n"
305
+ f"• Images/Videos/Audio/Documents → Relayed using media_id\n"
306
+ f"• Locations → Same location echoed back\n"
307
+ f"• Contacts → Contact information echoed back\n\n"
308
+ f"💡 *Pro tip*: This demo showcases production-ready patterns for building WhatsApp Business applications!"
309
+ )
310
+
311
+ try:
312
+ result = await self.messenger.send_text(
313
+ recipient=user_id,
314
+ text=welcome_text,
315
+ reply_to_message_id=webhook.message.message_id,
316
+ )
317
+
318
+ if result.success:
319
+ self.logger.info(
320
+ f"👋 Welcome message sent to {display_name} ({user_id})"
321
+ )
322
+ else:
323
+ self.logger.error(f"❌ Failed to send welcome message: {result.error}")
324
+
325
+ except Exception as e:
326
+ self.logger.error(f"❌ Error sending welcome message: {e}")
327
+
328
+ async def _mark_message_as_read(self, webhook: IncomingMessageWebhook) -> None:
329
+ """Mark incoming message as read (as specified in requirements)."""
330
+ try:
331
+ result = await self.messenger.mark_as_read(
332
+ message_id=webhook.message.message_id, typing=False
333
+ )
334
+
335
+ if result.success:
336
+ self.logger.debug(
337
+ f"✅ Message marked as read: {webhook.message.message_id[:20]}..."
338
+ )
339
+ else:
340
+ self.logger.warning(f"⚠️ Failed to mark message as read: {result.error}")
341
+
342
+ except Exception as e:
343
+ self.logger.warning(f"⚠️ Error marking message as read: {e}")
344
+
345
+ async def _handle_special_command(
346
+ self, webhook: IncomingMessageWebhook, user_profile: UserProfile, command: str
347
+ ) -> None:
348
+ """Handle special commands using command handlers."""
349
+ try:
350
+ if command == "/button":
351
+ result = await self.command_handlers.handle_button_command(
352
+ webhook, user_profile
353
+ )
354
+ elif command == "/list":
355
+ result = await self.command_handlers.handle_list_command(
356
+ webhook, user_profile
357
+ )
358
+ elif command == "/cta":
359
+ result = await self.command_handlers.handle_cta_command(
360
+ webhook, user_profile
361
+ )
362
+ elif command == "/location":
363
+ result = await self.command_handlers.handle_location_command(
364
+ webhook, user_profile
365
+ )
366
+ else:
367
+ self.logger.warning(f"Unsupported command: {command}")
368
+ return
369
+
370
+ if result["success"]:
371
+ self.logger.info(f"✅ Command {command} processed successfully")
372
+ else:
373
+ self.logger.error(
374
+ f"❌ Command {command} processing failed: {result.get('error')}"
375
+ )
376
+
377
+ except Exception as e:
378
+ self.logger.error(
379
+ f"❌ Error handling command {command}: {e}", exc_info=True
380
+ )
381
+
382
+ async def _send_error_response(
383
+ self, webhook: IncomingMessageWebhook, error_details: str
384
+ ) -> None:
385
+ """Send user-friendly error response when processing fails."""
386
+ try:
387
+ user_id = webhook.user.user_id
388
+ message_id = webhook.message.message_id
389
+
390
+ error_message = (
391
+ "🚨 *Wappa Full Example - Processing Error*\n\n"
392
+ "❌ An error occurred while processing your message.\n"
393
+ "Our comprehensive demo system encountered an issue.\n\n"
394
+ "🔄 *Please try again* or contact support if the problem persists.\n\n"
395
+ "💡 *Tip*: Try using one of our special commands:\n"
396
+ "`/button` • `/list` • `/cta` • `/location`"
397
+ )
398
+
399
+ result = await self.messenger.send_text(
400
+ recipient=user_id, text=error_message, reply_to_message_id=message_id
401
+ )
402
+
403
+ if result.success:
404
+ self.logger.info(f"🚨 Error response sent to {user_id}")
405
+ else:
406
+ self.logger.error(f"❌ Failed to send error response: {result.error}")
407
+
408
+ except Exception as e:
409
+ self.logger.error(f"❌ Error sending error response: {e}")
410
+
411
+ async def _log_processing_stats(self, start_time: float) -> None:
412
+ """Log processing statistics."""
413
+ processing_time = int((time.time() - start_time) * 1000)
414
+ success_rate = (
415
+ self._successful_processing / max(1, self._total_messages)
416
+ ) * 100
417
+
418
+ self.logger.info(
419
+ f"📊 Processing Stats: "
420
+ f"time={processing_time}ms, "
421
+ f"total={self._total_messages}, "
422
+ f"success={self._successful_processing}, "
423
+ f"failed={self._failed_processing}, "
424
+ f"rate={success_rate:.1f}%, "
425
+ f"new_users={self._first_time_users}, "
426
+ f"commands={self._commands_processed}, "
427
+ f"interactions={self._interactive_responses}"
428
+ )
429
+
430
+ async def get_handler_statistics(self) -> dict[str, any]:
431
+ """
432
+ Get comprehensive handler statistics.
433
+
434
+ Returns:
435
+ Dictionary with processing statistics and handler metrics
436
+ """
437
+ try:
438
+ success_rate = (
439
+ self._successful_processing / max(1, self._total_messages)
440
+ ) * 100
441
+
442
+ stats = {
443
+ "handler_info": {
444
+ "name": "WappaFullExampleHandler",
445
+ "description": "Comprehensive WhatsApp Business API demo",
446
+ "features": [
447
+ "Complete message type handling",
448
+ "Interactive commands with state management",
449
+ "Media relay functionality",
450
+ "User tracking and analytics",
451
+ "Welcome messages for first-time users",
452
+ ],
453
+ },
454
+ "processing_stats": {
455
+ "total_messages": self._total_messages,
456
+ "successful_processing": self._successful_processing,
457
+ "failed_processing": self._failed_processing,
458
+ "success_rate_percent": round(success_rate, 2),
459
+ },
460
+ "feature_usage": {
461
+ "first_time_users": self._first_time_users,
462
+ "commands_processed": self._commands_processed,
463
+ "interactive_responses": self._interactive_responses,
464
+ },
465
+ "supported_commands": ["/button", "/list", "/cta", "/location"],
466
+ "supported_message_types": [
467
+ "text",
468
+ "image",
469
+ "video",
470
+ "audio",
471
+ "voice",
472
+ "document",
473
+ "location",
474
+ "contact",
475
+ "contacts",
476
+ "interactive",
477
+ ],
478
+ }
479
+
480
+ # Add cache statistics if available
481
+ if self.cache_helper:
482
+ cache_stats = await self.cache_helper.get_cache_statistics()
483
+ stats["cache_stats"] = cache_stats
484
+
485
+ return stats
486
+
487
+ except Exception as e:
488
+ self.logger.error(f"❌ Error collecting handler statistics: {e}")
489
+ return {"error": f"Statistics collection failed: {str(e)}"}
490
+
491
+ def __str__(self) -> str:
492
+ """String representation of the handler."""
493
+ success_rate = (
494
+ self._successful_processing / max(1, self._total_messages)
495
+ ) * 100
496
+ return (
497
+ f"WappaFullExampleHandler("
498
+ f"messages={self._total_messages}, "
499
+ f"success_rate={success_rate:.1f}%, "
500
+ f"new_users={self._first_time_users}, "
501
+ f"commands={self._commands_processed}, "
502
+ f"interactions={self._interactive_responses}"
503
+ f")"
504
+ )
@@ -0,0 +1,54 @@
1
+ # Media Files Directory
2
+
3
+ This directory contains media files used by the Wappa Full Example application for interactive demonstrations.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ media/
9
+ ├── buttons/ # Media files for button command responses
10
+ │ ├── kitty.png # Image sent when user selects "Kitty" button
11
+ │ └── puppy.png # Image sent when user selects "Puppy" button
12
+ └── list/ # Media files for list command responses
13
+ ├── image.png # Sample image file for list selection
14
+ ├── video.mp4 # Sample video file for list selection
15
+ ├── audio.mp3 # Sample audio file for list selection
16
+ └── document.pdf # Sample document file for list selection
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ The application automatically serves these files when users interact with:
22
+
23
+ 1. **Button Command** (`/button`):
24
+ - User selects "🐱 Kitty" → sends `buttons/kitty.png`
25
+ - User selects "🐶 Puppy" → sends `buttons/puppy.png`
26
+
27
+ 2. **List Command** (`/list`):
28
+ - User selects "🖼️ Image" → sends `list/image.png`
29
+ - User selects "🎬 Video" → sends `list/video.mp4`
30
+ - User selects "🎵 Audio" → sends `list/audio.mp3`
31
+ - User selects "📄 Document" → sends `list/document.pdf`
32
+
33
+ ## File Requirements
34
+
35
+ - **Images**: PNG, JPG formats (max 5MB)
36
+ - **Videos**: MP4 format (max 16MB)
37
+ - **Audio**: MP3, OGG formats (max 16MB)
38
+ - **Documents**: PDF format (max 100MB)
39
+
40
+ ## Adding Your Own Files
41
+
42
+ Replace the placeholder files with your own media:
43
+
44
+ 1. Add your files to the appropriate subdirectories
45
+ 2. Use the exact filenames as listed above
46
+ 3. Ensure files meet WhatsApp Business API size limits
47
+ 4. Test with the interactive commands to verify functionality
48
+
49
+ ## Notes
50
+
51
+ - Files are loaded from the local filesystem
52
+ - The media handler automatically detects file types
53
+ - If files are missing, fallback text messages are sent instead
54
+ - This is a demonstration setup - in production, you might use cloud storage or CDN
@@ -0,0 +1,62 @@
1
+ # Button Command Media Files
2
+
3
+ This directory contains media files sent in response to button selections in the `/button` command demo.
4
+
5
+ ## Required Files
6
+
7
+ Place these files in this directory:
8
+
9
+ ### `kitty.png`
10
+ - **Purpose**: Sent when user clicks the "🐱 Kitty" button
11
+ - **Format**: PNG or JPG image
12
+ - **Size limit**: 5MB maximum
13
+ - **Dimensions**: Recommended 500x500px or similar
14
+ - **Content**: Image of a cute kitten
15
+
16
+ ### `puppy.png`
17
+ - **Purpose**: Sent when user clicks the "🐶 Puppy" button
18
+ - **Format**: PNG or JPG image
19
+ - **Size limit**: 5MB maximum
20
+ - **Dimensions**: Recommended 500x500px or similar
21
+ - **Content**: Image of a cute puppy
22
+
23
+ ## How It Works
24
+
25
+ 1. User sends `/button` command
26
+ 2. App creates interactive button message with "Kitty" and "Puppy" options
27
+ 3. User clicks one of the buttons
28
+ 4. App sends the corresponding image file from this directory
29
+ 5. User receives the image with a caption
30
+
31
+ ## File Sources
32
+
33
+ You can add your own images:
34
+ - Download free images from Unsplash, Pixabay, or similar
35
+ - Use your own photos
36
+ - Ensure you have rights to use the images
37
+ - Keep file sizes reasonable for WhatsApp
38
+
39
+ ## Fallback Behavior
40
+
41
+ If files are missing:
42
+ - App will send a text message instead
43
+ - Message will indicate the file is missing
44
+ - Button functionality will still work, but without media
45
+
46
+ ## Example Implementation
47
+
48
+ ```python
49
+ # In state_handlers.py
50
+ if selection_id == "kitty":
51
+ media_file = "kitty.png"
52
+ elif selection_id == "puppy":
53
+ media_file = "puppy.png"
54
+
55
+ media_result = await send_local_media_file(
56
+ messenger=self.messenger,
57
+ recipient=user_id,
58
+ filename=media_file,
59
+ media_subdir="buttons",
60
+ caption=f"Here's your {selection_id}! 🎉"
61
+ )
62
+ ```