wappa 0.1.8__py3-none-any.whl → 0.1.9__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/cli/examples/init/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +8 -0
- wappa/cli/examples/init/app/master_event.py +8 -0
- wappa/cli/examples/json_cache_example/.env.example +33 -0
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/main.py +235 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +419 -0
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +275 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +246 -0
- wappa/cli/examples/openai_transcript/.gitignore +63 -4
- wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
- wappa/cli/examples/openai_transcript/app/main.py +8 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +53 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +76 -0
- wappa/cli/examples/redis_cache_example/.env.example +33 -0
- wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
- wappa/cli/examples/redis_cache_example/app/main.py +234 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +419 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +275 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +246 -0
- wappa/cli/examples/simple_echo_example/.env.example +33 -0
- wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
- wappa/cli/examples/simple_echo_example/app/main.py +183 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +209 -0
- wappa/cli/examples/wappa_full_example/.env.example +33 -0
- wappa/cli/examples/wappa_full_example/.gitignore +63 -4
- wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +484 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +551 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/main.py +257 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +445 -0
- wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
- wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +425 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +287 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +301 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +483 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +473 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +298 -0
- wappa/cli/main.py +8 -4
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/RECORD +75 -11
- wappa/cli/examples/init/pyproject.toml +0 -7
- wappa/cli/examples/simple_echo_example/.python-version +0 -1
- wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/WHEEL +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
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
|
+
from typing import Dict, Optional
|
|
11
|
+
|
|
12
|
+
from wappa import WappaEventHandler
|
|
13
|
+
from wappa.webhooks import ErrorWebhook, IncomingMessageWebhook, StatusWebhook
|
|
14
|
+
|
|
15
|
+
from .handlers.command_handlers import (
|
|
16
|
+
CommandHandlers,
|
|
17
|
+
get_command_from_text,
|
|
18
|
+
is_special_command,
|
|
19
|
+
)
|
|
20
|
+
from .handlers.message_handlers import MessageHandlers, handle_message_by_type
|
|
21
|
+
from .handlers.state_handlers import StateHandlers, handle_user_in_state
|
|
22
|
+
from .models.state_models import StateType
|
|
23
|
+
from .models.user_models import UserProfile
|
|
24
|
+
from .utils.cache_utils import CacheHelper
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WappaFullExampleHandler(WappaEventHandler):
|
|
28
|
+
"""
|
|
29
|
+
Comprehensive WappaEventHandler implementation demonstrating all framework features.
|
|
30
|
+
|
|
31
|
+
Features:
|
|
32
|
+
- Complete message type handling with metadata extraction
|
|
33
|
+
- Interactive commands (/button, /list, /cta, /location) with state management
|
|
34
|
+
- Media relay functionality using media_id
|
|
35
|
+
- User tracking and analytics with Redis cache
|
|
36
|
+
- Professional error handling and logging
|
|
37
|
+
- Welcome messages for first-time users
|
|
38
|
+
- State-based interactive workflows with TTL
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
"""Initialize the comprehensive Wappa example handler."""
|
|
43
|
+
super().__init__()
|
|
44
|
+
|
|
45
|
+
# Handler instances (initialized per request)
|
|
46
|
+
self.cache_helper: Optional[CacheHelper] = None
|
|
47
|
+
self.message_handlers: Optional[MessageHandlers] = None
|
|
48
|
+
self.command_handlers: Optional[CommandHandlers] = None
|
|
49
|
+
self.state_handlers: Optional[StateHandlers] = None
|
|
50
|
+
|
|
51
|
+
# Statistics
|
|
52
|
+
self._total_messages = 0
|
|
53
|
+
self._successful_processing = 0
|
|
54
|
+
self._failed_processing = 0
|
|
55
|
+
self._first_time_users = 0
|
|
56
|
+
self._commands_processed = 0
|
|
57
|
+
self._interactive_responses = 0
|
|
58
|
+
|
|
59
|
+
self.logger.info("🚀 WappaFullExampleHandler initialized - comprehensive demo ready")
|
|
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("Failed to handle user profile, continuing without caching")
|
|
90
|
+
user_profile = UserProfile(phone_number=webhook.user.user_id)
|
|
91
|
+
|
|
92
|
+
# 3. Mark message as read (as specified in requirements)
|
|
93
|
+
await self._mark_message_as_read(webhook)
|
|
94
|
+
|
|
95
|
+
# Extract basic message info for routing
|
|
96
|
+
user_id = webhook.user.user_id
|
|
97
|
+
message_text = webhook.get_message_text().strip()
|
|
98
|
+
message_type = webhook.get_message_type_name()
|
|
99
|
+
|
|
100
|
+
self.logger.info(
|
|
101
|
+
f"📨 Processing {message_type} from {user_id}: "
|
|
102
|
+
f"'{message_text[:50]}{'...' if len(message_text) > 50 else ''}'"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# 4. Check for active interactive states first
|
|
106
|
+
state_result = await handle_user_in_state(
|
|
107
|
+
webhook, user_profile, self.messenger, self.cache_factory, self.logger
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if state_result is not None:
|
|
111
|
+
# User was in an interactive state - handle accordingly
|
|
112
|
+
if state_result["success"]:
|
|
113
|
+
self._interactive_responses += 1
|
|
114
|
+
self._successful_processing += 1
|
|
115
|
+
self.logger.info(f"✅ Interactive state handled: {state_result.get('selection_type', 'unknown')}")
|
|
116
|
+
else:
|
|
117
|
+
self.logger.info(f"🔄 Interactive state reminder sent: {state_result.get('error', 'unknown')}")
|
|
118
|
+
|
|
119
|
+
await self._log_processing_stats(start_time)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
# 5. Check for special commands (only for text messages)
|
|
123
|
+
if message_type == "text" and is_special_command(message_text):
|
|
124
|
+
command = get_command_from_text(message_text)
|
|
125
|
+
await self._handle_special_command(webhook, user_profile, command)
|
|
126
|
+
self._commands_processed += 1
|
|
127
|
+
self._successful_processing += 1
|
|
128
|
+
await self._log_processing_stats(start_time)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
# 6. Handle regular message processing with metadata
|
|
132
|
+
result = await handle_message_by_type(
|
|
133
|
+
webhook, user_profile, self.messenger, self.cache_factory, self.logger
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if result["success"]:
|
|
137
|
+
self._successful_processing += 1
|
|
138
|
+
self.logger.info(f"✅ {message_type} message processed successfully")
|
|
139
|
+
else:
|
|
140
|
+
self._failed_processing += 1
|
|
141
|
+
self.logger.error(f"❌ {message_type} message processing failed: {result.get('error')}")
|
|
142
|
+
|
|
143
|
+
# Send error response to user
|
|
144
|
+
await self._send_error_response(webhook, result.get('error', 'Processing failed'))
|
|
145
|
+
|
|
146
|
+
await self._log_processing_stats(start_time)
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
self._failed_processing += 1
|
|
150
|
+
self.logger.error(f"❌ Critical error in message processing: {e}", exc_info=True)
|
|
151
|
+
|
|
152
|
+
# Send generic error response
|
|
153
|
+
try:
|
|
154
|
+
await self.messenger.send_text(
|
|
155
|
+
recipient=webhook.user.user_id,
|
|
156
|
+
text="❌ Sorry, something went wrong processing your message. Please try again.",
|
|
157
|
+
reply_to_message_id=webhook.message.message_id
|
|
158
|
+
)
|
|
159
|
+
except Exception as send_error:
|
|
160
|
+
self.logger.error(f"❌ Failed to send error response: {send_error}")
|
|
161
|
+
|
|
162
|
+
async def process_status(self, webhook: StatusWebhook) -> None:
|
|
163
|
+
"""
|
|
164
|
+
Process status webhooks from WhatsApp Business API.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
webhook: StatusWebhook containing delivery status information
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
status_value = webhook.status.value
|
|
171
|
+
recipient = webhook.recipient_id
|
|
172
|
+
message_id = webhook.message_id
|
|
173
|
+
|
|
174
|
+
self.logger.info(f"📊 Message status: {status_value.upper()} for {recipient} (msg: {message_id[:20]}...)")
|
|
175
|
+
|
|
176
|
+
# You can add custom status processing here
|
|
177
|
+
# For example: update delivery statistics, handle failed deliveries, etc.
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
self.logger.error(f"❌ Error processing status webhook: {e}", exc_info=True)
|
|
181
|
+
|
|
182
|
+
async def process_error(self, webhook: ErrorWebhook) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Process error webhooks from WhatsApp Business API.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
webhook: ErrorWebhook containing error information
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
error_count = webhook.get_error_count()
|
|
191
|
+
primary_error = webhook.get_primary_error()
|
|
192
|
+
|
|
193
|
+
self.logger.error(
|
|
194
|
+
f"🚨 WhatsApp API error: {error_count} errors, "
|
|
195
|
+
f"primary: {primary_error.error_code} - {primary_error.error_title}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Record error in statistics
|
|
199
|
+
self._failed_processing += 1
|
|
200
|
+
|
|
201
|
+
# You can add custom error handling logic here
|
|
202
|
+
# For example: alerting systems, retry mechanisms, etc.
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
self.logger.error(f"❌ Error processing error webhook: {e}", exc_info=True)
|
|
206
|
+
|
|
207
|
+
async def _setup_dependencies(self) -> bool:
|
|
208
|
+
"""Setup handler dependencies and validate state."""
|
|
209
|
+
if not self.validate_dependencies():
|
|
210
|
+
self.logger.error("❌ Dependencies not properly injected")
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
if not self.cache_factory:
|
|
214
|
+
self.logger.error("❌ Cache factory not available - Redis caching unavailable")
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
# Initialize helper instances
|
|
219
|
+
self.cache_helper = CacheHelper(self.cache_factory)
|
|
220
|
+
self.message_handlers = MessageHandlers(self.messenger, self.cache_factory, self.logger)
|
|
221
|
+
self.command_handlers = CommandHandlers(self.messenger, self.cache_factory, self.logger)
|
|
222
|
+
self.state_handlers = StateHandlers(self.messenger, self.cache_factory, self.logger)
|
|
223
|
+
|
|
224
|
+
self.logger.debug("✅ Handler dependencies initialized successfully")
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
self.logger.error(f"❌ Failed to setup dependencies: {e}", exc_info=True)
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
async def _handle_user_profile_and_welcome(self, webhook: IncomingMessageWebhook) -> Optional[UserProfile]:
|
|
232
|
+
"""Handle user profile caching and send welcome message to first-time users."""
|
|
233
|
+
try:
|
|
234
|
+
user_id = webhook.user.user_id
|
|
235
|
+
user_name = webhook.user.profile_name
|
|
236
|
+
|
|
237
|
+
# Get or create user profile
|
|
238
|
+
user_profile = await self.cache_helper.get_or_create_user_profile(
|
|
239
|
+
user_id, user_name, user_name
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Send welcome message to first-time users
|
|
243
|
+
if user_profile.is_first_time_user and not user_profile.has_received_welcome:
|
|
244
|
+
await self._send_welcome_message(webhook, user_profile)
|
|
245
|
+
user_profile.mark_welcome_sent()
|
|
246
|
+
await self.cache_helper.save_user_profile(user_profile)
|
|
247
|
+
self._first_time_users += 1
|
|
248
|
+
|
|
249
|
+
return user_profile
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
self.logger.error(f"❌ Error handling user profile: {e}", exc_info=True)
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
async def _send_welcome_message(self, webhook: IncomingMessageWebhook, user_profile: UserProfile) -> None:
|
|
256
|
+
"""Send welcome message with instructions to first-time users."""
|
|
257
|
+
user_id = webhook.user.user_id
|
|
258
|
+
display_name = user_profile.get_display_name()
|
|
259
|
+
|
|
260
|
+
welcome_text = (
|
|
261
|
+
f"🎉 *Welcome to Wappa Full Example, {display_name}!*\n\n"
|
|
262
|
+
f"This is a comprehensive demonstration of the Wappa framework capabilities.\n\n"
|
|
263
|
+
f"🚀 *What this example does:*\n"
|
|
264
|
+
f"• Echoes all message types with detailed metadata\n"
|
|
265
|
+
f"• Demonstrates interactive features (buttons, lists, CTA, locations)\n"
|
|
266
|
+
f"• Shows state management with TTL\n"
|
|
267
|
+
f"• Tracks user activity with Redis cache\n"
|
|
268
|
+
f"• Handles media relay using media_id\n\n"
|
|
269
|
+
f"🎯 *Try these special commands:*\n"
|
|
270
|
+
f"• `/button` - Interactive button demo with animal selection\n"
|
|
271
|
+
f"• `/list` - Interactive list demo with media files\n"
|
|
272
|
+
f"• `/cta` - Call-to-action button with external link\n"
|
|
273
|
+
f"• `/location` - Location sharing demonstration\n\n"
|
|
274
|
+
f"📨 *Send any message type to see it echoed with metadata:*\n"
|
|
275
|
+
f"• Text messages → Echo with metadata\n"
|
|
276
|
+
f"• Images/Videos/Audio/Documents → Relayed using media_id\n"
|
|
277
|
+
f"• Locations → Same location echoed back\n"
|
|
278
|
+
f"• Contacts → Contact information echoed back\n\n"
|
|
279
|
+
f"💡 *Pro tip*: This demo showcases production-ready patterns for building WhatsApp Business applications!"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
result = await self.messenger.send_text(
|
|
284
|
+
recipient=user_id,
|
|
285
|
+
text=welcome_text,
|
|
286
|
+
reply_to_message_id=webhook.message.message_id
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if result.success:
|
|
290
|
+
self.logger.info(f"👋 Welcome message sent to {display_name} ({user_id})")
|
|
291
|
+
else:
|
|
292
|
+
self.logger.error(f"❌ Failed to send welcome message: {result.error}")
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
self.logger.error(f"❌ Error sending welcome message: {e}")
|
|
296
|
+
|
|
297
|
+
async def _mark_message_as_read(self, webhook: IncomingMessageWebhook) -> None:
|
|
298
|
+
"""Mark incoming message as read (as specified in requirements)."""
|
|
299
|
+
try:
|
|
300
|
+
result = await self.messenger.mark_as_read(
|
|
301
|
+
message_id=webhook.message.message_id,
|
|
302
|
+
typing=False
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if result.success:
|
|
306
|
+
self.logger.debug(f"✅ Message marked as read: {webhook.message.message_id[:20]}...")
|
|
307
|
+
else:
|
|
308
|
+
self.logger.warning(f"⚠️ Failed to mark message as read: {result.error}")
|
|
309
|
+
|
|
310
|
+
except Exception as e:
|
|
311
|
+
self.logger.warning(f"⚠️ Error marking message as read: {e}")
|
|
312
|
+
|
|
313
|
+
async def _handle_special_command(self, webhook: IncomingMessageWebhook,
|
|
314
|
+
user_profile: UserProfile, command: str) -> None:
|
|
315
|
+
"""Handle special commands using command handlers."""
|
|
316
|
+
try:
|
|
317
|
+
if command == "/button":
|
|
318
|
+
result = await self.command_handlers.handle_button_command(webhook, user_profile)
|
|
319
|
+
elif command == "/list":
|
|
320
|
+
result = await self.command_handlers.handle_list_command(webhook, user_profile)
|
|
321
|
+
elif command == "/cta":
|
|
322
|
+
result = await self.command_handlers.handle_cta_command(webhook, user_profile)
|
|
323
|
+
elif command == "/location":
|
|
324
|
+
result = await self.command_handlers.handle_location_command(webhook, user_profile)
|
|
325
|
+
else:
|
|
326
|
+
self.logger.warning(f"Unsupported command: {command}")
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
if result["success"]:
|
|
330
|
+
self.logger.info(f"✅ Command {command} processed successfully")
|
|
331
|
+
else:
|
|
332
|
+
self.logger.error(f"❌ Command {command} processing failed: {result.get('error')}")
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
self.logger.error(f"❌ Error handling command {command}: {e}", exc_info=True)
|
|
336
|
+
|
|
337
|
+
async def _send_error_response(self, webhook: IncomingMessageWebhook, error_details: str) -> None:
|
|
338
|
+
"""Send user-friendly error response when processing fails."""
|
|
339
|
+
try:
|
|
340
|
+
user_id = webhook.user.user_id
|
|
341
|
+
message_id = webhook.message.message_id
|
|
342
|
+
|
|
343
|
+
error_message = (
|
|
344
|
+
"🚨 *Wappa Full Example - Processing Error*\n\n"
|
|
345
|
+
"❌ An error occurred while processing your message.\n"
|
|
346
|
+
"Our comprehensive demo system encountered an issue.\n\n"
|
|
347
|
+
"🔄 *Please try again* or contact support if the problem persists.\n\n"
|
|
348
|
+
"💡 *Tip*: Try using one of our special commands:\n"
|
|
349
|
+
"`/button` • `/list` • `/cta` • `/location`"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
result = await self.messenger.send_text(
|
|
353
|
+
recipient=user_id,
|
|
354
|
+
text=error_message,
|
|
355
|
+
reply_to_message_id=message_id
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if result.success:
|
|
359
|
+
self.logger.info(f"🚨 Error response sent to {user_id}")
|
|
360
|
+
else:
|
|
361
|
+
self.logger.error(f"❌ Failed to send error response: {result.error}")
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
self.logger.error(f"❌ Error sending error response: {e}")
|
|
365
|
+
|
|
366
|
+
async def _log_processing_stats(self, start_time: float) -> None:
|
|
367
|
+
"""Log processing statistics."""
|
|
368
|
+
processing_time = int((time.time() - start_time) * 1000)
|
|
369
|
+
success_rate = (self._successful_processing / max(1, self._total_messages)) * 100
|
|
370
|
+
|
|
371
|
+
self.logger.info(
|
|
372
|
+
f"📊 Processing Stats: "
|
|
373
|
+
f"time={processing_time}ms, "
|
|
374
|
+
f"total={self._total_messages}, "
|
|
375
|
+
f"success={self._successful_processing}, "
|
|
376
|
+
f"failed={self._failed_processing}, "
|
|
377
|
+
f"rate={success_rate:.1f}%, "
|
|
378
|
+
f"new_users={self._first_time_users}, "
|
|
379
|
+
f"commands={self._commands_processed}, "
|
|
380
|
+
f"interactions={self._interactive_responses}"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
async def get_handler_statistics(self) -> Dict[str, any]:
|
|
384
|
+
"""
|
|
385
|
+
Get comprehensive handler statistics.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Dictionary with processing statistics and handler metrics
|
|
389
|
+
"""
|
|
390
|
+
try:
|
|
391
|
+
success_rate = (self._successful_processing / max(1, self._total_messages)) * 100
|
|
392
|
+
|
|
393
|
+
stats = {
|
|
394
|
+
"handler_info": {
|
|
395
|
+
"name": "WappaFullExampleHandler",
|
|
396
|
+
"description": "Comprehensive WhatsApp Business API demo",
|
|
397
|
+
"features": [
|
|
398
|
+
"Complete message type handling",
|
|
399
|
+
"Interactive commands with state management",
|
|
400
|
+
"Media relay functionality",
|
|
401
|
+
"User tracking and analytics",
|
|
402
|
+
"Welcome messages for first-time users"
|
|
403
|
+
]
|
|
404
|
+
},
|
|
405
|
+
"processing_stats": {
|
|
406
|
+
"total_messages": self._total_messages,
|
|
407
|
+
"successful_processing": self._successful_processing,
|
|
408
|
+
"failed_processing": self._failed_processing,
|
|
409
|
+
"success_rate_percent": round(success_rate, 2)
|
|
410
|
+
},
|
|
411
|
+
"feature_usage": {
|
|
412
|
+
"first_time_users": self._first_time_users,
|
|
413
|
+
"commands_processed": self._commands_processed,
|
|
414
|
+
"interactive_responses": self._interactive_responses
|
|
415
|
+
},
|
|
416
|
+
"supported_commands": ["/button", "/list", "/cta", "/location"],
|
|
417
|
+
"supported_message_types": [
|
|
418
|
+
"text", "image", "video", "audio", "voice", "document",
|
|
419
|
+
"location", "contact", "contacts", "interactive"
|
|
420
|
+
]
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
# Add cache statistics if available
|
|
424
|
+
if self.cache_helper:
|
|
425
|
+
cache_stats = await self.cache_helper.get_cache_statistics()
|
|
426
|
+
stats["cache_stats"] = cache_stats
|
|
427
|
+
|
|
428
|
+
return stats
|
|
429
|
+
|
|
430
|
+
except Exception as e:
|
|
431
|
+
self.logger.error(f"❌ Error collecting handler statistics: {e}")
|
|
432
|
+
return {"error": f"Statistics collection failed: {str(e)}"}
|
|
433
|
+
|
|
434
|
+
def __str__(self) -> str:
|
|
435
|
+
"""String representation of the handler."""
|
|
436
|
+
success_rate = (self._successful_processing / max(1, self._total_messages)) * 100
|
|
437
|
+
return (
|
|
438
|
+
f"WappaFullExampleHandler("
|
|
439
|
+
f"messages={self._total_messages}, "
|
|
440
|
+
f"success_rate={success_rate:.1f}%, "
|
|
441
|
+
f"new_users={self._first_time_users}, "
|
|
442
|
+
f"commands={self._commands_processed}, "
|
|
443
|
+
f"interactions={self._interactive_responses}"
|
|
444
|
+
f")"
|
|
445
|
+
)
|
|
@@ -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
|
+
```
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# List Command Media Files
|
|
2
|
+
|
|
3
|
+
This directory contains media files sent in response to list selections in the `/list` command demo.
|
|
4
|
+
|
|
5
|
+
## Required Files
|
|
6
|
+
|
|
7
|
+
Place these files in this directory:
|
|
8
|
+
|
|
9
|
+
### `image.png`
|
|
10
|
+
- **Purpose**: Sent when user selects "🖼️ Image" from the list
|
|
11
|
+
- **Format**: PNG or JPG image
|
|
12
|
+
- **Size limit**: 5MB maximum
|
|
13
|
+
- **Content**: Sample image to demonstrate image sending
|
|
14
|
+
- **Suggested**: Colorful demo image, infographic, or screenshot
|
|
15
|
+
|
|
16
|
+
### `video.mp4`
|
|
17
|
+
- **Purpose**: Sent when user selects "🎬 Video" from the list
|
|
18
|
+
- **Format**: MP4 video file
|
|
19
|
+
- **Size limit**: 16MB maximum
|
|
20
|
+
- **Duration**: Keep under 60 seconds for demo purposes
|
|
21
|
+
- **Content**: Sample video demonstrating video messaging
|
|
22
|
+
- **Suggested**: Short demo video, animation, or screen recording
|
|
23
|
+
|
|
24
|
+
### `audio.mp3`
|
|
25
|
+
- **Purpose**: Sent when user selects "🎵 Audio" from the list
|
|
26
|
+
- **Format**: MP3, OGG, or AAC audio file
|
|
27
|
+
- **Size limit**: 16MB maximum
|
|
28
|
+
- **Duration**: Keep under 2 minutes for demo purposes
|
|
29
|
+
- **Content**: Sample audio demonstrating audio messaging
|
|
30
|
+
- **Suggested**: Music clip, voice recording, or sound effect
|
|
31
|
+
|
|
32
|
+
### `document.pdf`
|
|
33
|
+
- **Purpose**: Sent when user selects "📄 Document" from the list
|
|
34
|
+
- **Format**: PDF document
|
|
35
|
+
- **Size limit**: 100MB maximum
|
|
36
|
+
- **Content**: Sample document demonstrating document sharing
|
|
37
|
+
- **Suggested**: User guide, specification sheet, or informational PDF
|
|
38
|
+
|
|
39
|
+
## How It Works
|
|
40
|
+
|
|
41
|
+
1. User sends `/list` command
|
|
42
|
+
2. App creates interactive list message with 4 media type options
|
|
43
|
+
3. User selects one option from the list
|
|
44
|
+
4. App sends the corresponding media file from this directory
|
|
45
|
+
5. User receives the media file with a caption
|
|
46
|
+
|
|
47
|
+
## Interactive List Structure
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"title": "📁 Media Files",
|
|
52
|
+
"rows": [
|
|
53
|
+
{"id": "image_file", "title": "🖼️ Image", "description": "Get a sample image file"},
|
|
54
|
+
{"id": "video_file", "title": "🎬 Video", "description": "Get a sample video file"},
|
|
55
|
+
{"id": "audio_file", "title": "🎵 Audio", "description": "Get a sample audio file"},
|
|
56
|
+
{"id": "document_file", "title": "📄 Document", "description": "Get a sample document file"}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## File Recommendations
|
|
62
|
+
|
|
63
|
+
### For Demo/Testing:
|
|
64
|
+
- **Image**: Screenshots of the app, logos, or demo graphics
|
|
65
|
+
- **Video**: App walkthrough, feature demonstration, or intro video
|
|
66
|
+
- **Audio**: Welcome message, jingle, or app sounds
|
|
67
|
+
- **Document**: User manual, API documentation, or feature list
|
|
68
|
+
|
|
69
|
+
### For Production:
|
|
70
|
+
- **Image**: Product catalogs, infographics, charts
|
|
71
|
+
- **Video**: Product demos, tutorials, testimonials
|
|
72
|
+
- **Audio**: Voice messages, audio guides, music
|
|
73
|
+
- **Document**: Contracts, invoices, manuals, reports
|
|
74
|
+
|
|
75
|
+
## WhatsApp Business API Limits
|
|
76
|
+
|
|
77
|
+
- **Images**: JPEG, PNG up to 5MB, 8-bit RGB or RGBA
|
|
78
|
+
- **Videos**: MP4, 3GP up to 16MB, H.264 codec, AAC audio
|
|
79
|
+
- **Audio**: AAC, AMR, MP3, M4A, OGG up to 16MB
|
|
80
|
+
- **Documents**: TXT, PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX up to 100MB
|
|
81
|
+
|
|
82
|
+
## Fallback Behavior
|
|
83
|
+
|
|
84
|
+
If files are missing:
|
|
85
|
+
- App will send a text message instead
|
|
86
|
+
- Message will indicate the selected media type
|
|
87
|
+
- List functionality will still work, but without actual media
|
|
88
|
+
|
|
89
|
+
## Example Implementation
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# In state_handlers.py
|
|
93
|
+
media_mapping = {
|
|
94
|
+
"image_file": ("image.png", "image"),
|
|
95
|
+
"video_file": ("video.mp4", "video"),
|
|
96
|
+
"audio_file": ("audio.mp3", "audio"),
|
|
97
|
+
"document_file": ("document.pdf", "document")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
media_file, media_type = media_mapping.get(selection_id, (None, None))
|
|
101
|
+
|
|
102
|
+
if media_file:
|
|
103
|
+
await send_local_media_file(
|
|
104
|
+
messenger=self.messenger,
|
|
105
|
+
recipient=user_id,
|
|
106
|
+
filename=media_file,
|
|
107
|
+
media_subdir="list",
|
|
108
|
+
caption=f"Here's your {media_type} file! 🎉"
|
|
109
|
+
)
|
|
110
|
+
```
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|