wappa 0.1.7__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.

Files changed (80) hide show
  1. wappa/cli/examples/init/.env.example +33 -0
  2. wappa/cli/examples/init/app/__init__.py +0 -0
  3. wappa/cli/examples/init/app/main.py +8 -0
  4. wappa/cli/examples/init/app/master_event.py +8 -0
  5. wappa/cli/examples/json_cache_example/.env.example +33 -0
  6. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  7. wappa/cli/examples/json_cache_example/app/main.py +235 -0
  8. wappa/cli/examples/json_cache_example/app/master_event.py +419 -0
  9. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  10. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +275 -0
  11. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  12. wappa/cli/examples/json_cache_example/app/scores/score_base.py +186 -0
  13. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +248 -0
  14. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +190 -0
  15. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +260 -0
  16. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +223 -0
  17. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  18. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +176 -0
  19. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +246 -0
  20. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  21. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  22. wappa/cli/examples/openai_transcript/app/main.py +8 -0
  23. wappa/cli/examples/openai_transcript/app/master_event.py +53 -0
  24. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  25. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +76 -0
  26. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  27. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  28. wappa/cli/examples/redis_cache_example/app/main.py +234 -0
  29. wappa/cli/examples/redis_cache_example/app/master_event.py +419 -0
  30. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +275 -0
  31. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  32. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +186 -0
  33. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +248 -0
  34. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +190 -0
  35. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +260 -0
  36. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +223 -0
  37. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  38. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +176 -0
  39. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +246 -0
  40. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  41. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  42. wappa/cli/examples/simple_echo_example/app/main.py +183 -0
  43. wappa/cli/examples/simple_echo_example/app/master_event.py +209 -0
  44. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  45. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  46. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  47. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  48. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +484 -0
  49. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +551 -0
  50. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +492 -0
  51. wappa/cli/examples/wappa_full_example/app/main.py +257 -0
  52. wappa/cli/examples/wappa_full_example/app/master_event.py +445 -0
  53. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  54. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  55. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  56. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  57. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  58. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  59. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  60. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  61. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  62. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  63. wappa/cli/examples/wappa_full_example/app/models/state_models.py +425 -0
  64. wappa/cli/examples/wappa_full_example/app/models/user_models.py +287 -0
  65. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +301 -0
  66. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  67. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +483 -0
  68. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +473 -0
  69. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +298 -0
  70. wappa/cli/main.py +8 -4
  71. wappa/core/config/settings.py +34 -2
  72. wappa/persistence/__init__.py +2 -2
  73. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
  74. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/RECORD +77 -13
  75. wappa/cli/examples/init/pyproject.toml +0 -7
  76. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  77. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  78. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/WHEEL +0 -0
  79. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
  80. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,551 @@
1
+ """
2
+ Message handlers for different message types in the Wappa Full Example application.
3
+
4
+ This module provides handlers for processing and echoing different types of messages
5
+ including text, media, location, contact, and interactive messages.
6
+ """
7
+
8
+ import time
9
+ from typing import Dict, Optional
10
+
11
+ from wappa.webhooks import IncomingMessageWebhook
12
+
13
+ from ..models.user_models import UserProfile
14
+ from ..utils.cache_utils import CacheHelper
15
+ from ..utils.media_handler import MediaHandler, relay_webhook_media
16
+ from ..utils.metadata_extractor import MetadataExtractor
17
+
18
+
19
+ class MessageHandlers:
20
+ """Collection of message handlers for different message types."""
21
+
22
+ def __init__(self, messenger, cache_factory, logger):
23
+ """
24
+ Initialize message handlers.
25
+
26
+ Args:
27
+ messenger: IMessenger instance for sending messages
28
+ cache_factory: Cache factory for data persistence
29
+ logger: Logger instance
30
+ """
31
+ self.messenger = messenger
32
+ self.cache_helper = CacheHelper(cache_factory)
33
+ self.media_handler = MediaHandler()
34
+ self.logger = logger
35
+
36
+ async def handle_text_message(self, webhook: IncomingMessageWebhook,
37
+ user_profile: UserProfile) -> Dict[str, any]:
38
+ """
39
+ Handle text message with echo functionality.
40
+
41
+ Args:
42
+ webhook: IncomingMessageWebhook with text message
43
+ user_profile: User profile for tracking
44
+
45
+ Returns:
46
+ Result dictionary with operation status
47
+ """
48
+ try:
49
+ start_time = time.time()
50
+
51
+ # Extract message content
52
+ text_content = webhook.get_message_text()
53
+ user_id = webhook.user.user_id
54
+ message_id = webhook.message.message_id
55
+
56
+ self.logger.info(f"📝 Processing text message from {user_id}: '{text_content[:50]}'")
57
+
58
+ # Extract and format metadata
59
+ metadata = MetadataExtractor.extract_metadata(webhook, start_time)
60
+ metadata_text = MetadataExtractor.format_metadata_for_user(metadata)
61
+
62
+ # Send metadata response
63
+ metadata_result = await self.messenger.send_text(
64
+ recipient=user_id,
65
+ text=metadata_text,
66
+ reply_to_message_id=message_id
67
+ )
68
+
69
+ if not metadata_result.success:
70
+ self.logger.error(f"Failed to send metadata: {metadata_result.error}")
71
+ return {"success": False, "error": "Failed to send metadata"}
72
+
73
+ # Send echo response
74
+ echo_text = f"Echo - {text_content}"
75
+ echo_result = await self.messenger.send_text(
76
+ recipient=user_id,
77
+ text=echo_text
78
+ )
79
+
80
+ if not echo_result.success:
81
+ self.logger.error(f"Failed to send echo: {echo_result.error}")
82
+ return {"success": False, "error": "Failed to send echo"}
83
+
84
+ # Update user activity
85
+ await self.cache_helper.update_user_activity(user_id, "text")
86
+
87
+ # Store message in history
88
+ await self.cache_helper.store_message_history(user_id, {
89
+ "message_id": message_id,
90
+ "type": "text",
91
+ "content": text_content,
92
+ "timestamp": webhook.timestamp.isoformat(),
93
+ "echo_sent": True
94
+ })
95
+
96
+ processing_time = int((time.time() - start_time) * 1000)
97
+ self.logger.info(f"✅ Text message processed in {processing_time}ms")
98
+
99
+ return {
100
+ "success": True,
101
+ "message_type": "text",
102
+ "metadata_sent": True,
103
+ "echo_sent": True,
104
+ "processing_time_ms": processing_time
105
+ }
106
+
107
+ except Exception as e:
108
+ self.logger.error(f"❌ Error handling text message: {e}", exc_info=True)
109
+ return {"success": False, "error": str(e)}
110
+
111
+ async def handle_media_message(self, webhook: IncomingMessageWebhook,
112
+ user_profile: UserProfile) -> Dict[str, any]:
113
+ """
114
+ Handle media message with relay functionality.
115
+
116
+ Args:
117
+ webhook: IncomingMessageWebhook with media message
118
+ user_profile: User profile for tracking
119
+
120
+ Returns:
121
+ Result dictionary with operation status
122
+ """
123
+ try:
124
+ start_time = time.time()
125
+
126
+ user_id = webhook.user.user_id
127
+ message_id = webhook.message.message_id
128
+ message_type = webhook.get_message_type_name()
129
+
130
+ self.logger.info(f"🎬 Processing {message_type} message from {user_id}")
131
+
132
+ # Extract media information
133
+ media_info = await self.media_handler.get_media_info_from_webhook(webhook)
134
+ if not media_info:
135
+ return {"success": False, "error": "No media found in webhook"}
136
+
137
+ # Extract and format metadata
138
+ metadata = MetadataExtractor.extract_metadata(webhook, start_time)
139
+ metadata_text = MetadataExtractor.format_metadata_for_user(metadata)
140
+
141
+ # Send metadata response
142
+ metadata_result = await self.messenger.send_text(
143
+ recipient=user_id,
144
+ text=metadata_text,
145
+ reply_to_message_id=message_id
146
+ )
147
+
148
+ if not metadata_result.success:
149
+ self.logger.error(f"Failed to send metadata: {metadata_result.error}")
150
+ return {"success": False, "error": "Failed to send metadata"}
151
+
152
+ # Relay the same media using media_id
153
+ relay_result = await relay_webhook_media(
154
+ messenger=self.messenger,
155
+ webhook=webhook,
156
+ recipient=user_id
157
+ )
158
+
159
+ if not relay_result["success"]:
160
+ self.logger.error(f"Failed to relay media: {relay_result.get('error')}")
161
+
162
+ # Send fallback text response if media relay fails
163
+ fallback_text = f"📎 Media echo - {message_type} (relay failed, media_id: {media_info.get('media_id', 'unknown')[:20]}...)"
164
+ await self.messenger.send_text(
165
+ recipient=user_id,
166
+ text=fallback_text
167
+ )
168
+
169
+ # Update user activity
170
+ await self.cache_helper.update_user_activity(user_id, "media")
171
+
172
+ # Store message in history
173
+ await self.cache_helper.store_message_history(user_id, {
174
+ "message_id": message_id,
175
+ "type": message_type,
176
+ "media_id": media_info.get("media_id"),
177
+ "media_type": media_info.get("type"),
178
+ "timestamp": webhook.timestamp.isoformat(),
179
+ "relay_success": relay_result["success"]
180
+ })
181
+
182
+ processing_time = int((time.time() - start_time) * 1000)
183
+ self.logger.info(f"✅ {message_type} message processed in {processing_time}ms")
184
+
185
+ return {
186
+ "success": True,
187
+ "message_type": message_type,
188
+ "metadata_sent": True,
189
+ "media_relayed": relay_result["success"],
190
+ "media_info": media_info,
191
+ "processing_time_ms": processing_time
192
+ }
193
+
194
+ except Exception as e:
195
+ self.logger.error(f"❌ Error handling media message: {e}", exc_info=True)
196
+ return {"success": False, "error": str(e)}
197
+
198
+ async def handle_location_message(self, webhook: IncomingMessageWebhook,
199
+ user_profile: UserProfile) -> Dict[str, any]:
200
+ """
201
+ Handle location message with echo functionality.
202
+
203
+ Args:
204
+ webhook: IncomingMessageWebhook with location message
205
+ user_profile: User profile for tracking
206
+
207
+ Returns:
208
+ Result dictionary with operation status
209
+ """
210
+ try:
211
+ start_time = time.time()
212
+
213
+ user_id = webhook.user.user_id
214
+ message_id = webhook.message.message_id
215
+
216
+ self.logger.info(f"📍 Processing location message from {user_id}")
217
+
218
+ # Extract location data
219
+ latitude = getattr(webhook.message, 'latitude', None)
220
+ longitude = getattr(webhook.message, 'longitude', None)
221
+ location_name = getattr(webhook.message, 'name', None)
222
+ location_address = getattr(webhook.message, 'address', None)
223
+
224
+ if latitude is None or longitude is None:
225
+ return {"success": False, "error": "Invalid location data"}
226
+
227
+ # Extract and format metadata
228
+ metadata = MetadataExtractor.extract_metadata(webhook, start_time)
229
+ metadata_text = MetadataExtractor.format_metadata_for_user(metadata)
230
+
231
+ # Send metadata response
232
+ metadata_result = await self.messenger.send_text(
233
+ recipient=user_id,
234
+ text=metadata_text,
235
+ reply_to_message_id=message_id
236
+ )
237
+
238
+ if not metadata_result.success:
239
+ self.logger.error(f"Failed to send metadata: {metadata_result.error}")
240
+ return {"success": False, "error": "Failed to send metadata"}
241
+
242
+ # Echo the same location
243
+ location_result = await self.messenger.send_location(
244
+ latitude=float(latitude),
245
+ longitude=float(longitude),
246
+ recipient=user_id,
247
+ name=location_name,
248
+ address=location_address
249
+ )
250
+
251
+ if not location_result.success:
252
+ self.logger.error(f"Failed to send location: {location_result.error}")
253
+
254
+ # Send fallback text response
255
+ fallback_text = f"📍 Location echo - {latitude}, {longitude}"
256
+ if location_name:
257
+ fallback_text += f" ({location_name})"
258
+ await self.messenger.send_text(
259
+ recipient=user_id,
260
+ text=fallback_text
261
+ )
262
+
263
+ # Update user activity
264
+ await self.cache_helper.update_user_activity(user_id, "location")
265
+
266
+ # Store message in history
267
+ await self.cache_helper.store_message_history(user_id, {
268
+ "message_id": message_id,
269
+ "type": "location",
270
+ "latitude": latitude,
271
+ "longitude": longitude,
272
+ "name": location_name,
273
+ "address": location_address,
274
+ "timestamp": webhook.timestamp.isoformat(),
275
+ "echo_sent": location_result.success
276
+ })
277
+
278
+ processing_time = int((time.time() - start_time) * 1000)
279
+ self.logger.info(f"✅ Location message processed in {processing_time}ms")
280
+
281
+ return {
282
+ "success": True,
283
+ "message_type": "location",
284
+ "metadata_sent": True,
285
+ "location_echoed": location_result.success,
286
+ "coordinates": {"latitude": latitude, "longitude": longitude},
287
+ "processing_time_ms": processing_time
288
+ }
289
+
290
+ except Exception as e:
291
+ self.logger.error(f"❌ Error handling location message: {e}", exc_info=True)
292
+ return {"success": False, "error": str(e)}
293
+
294
+ async def handle_contact_message(self, webhook: IncomingMessageWebhook,
295
+ user_profile: UserProfile) -> Dict[str, any]:
296
+ """
297
+ Handle contact message with echo functionality.
298
+
299
+ Args:
300
+ webhook: IncomingMessageWebhook with contact message
301
+ user_profile: User profile for tracking
302
+
303
+ Returns:
304
+ Result dictionary with operation status
305
+ """
306
+ try:
307
+ start_time = time.time()
308
+
309
+ user_id = webhook.user.user_id
310
+ message_id = webhook.message.message_id
311
+
312
+ self.logger.info(f"👥 Processing contact message from {user_id}")
313
+
314
+ # Extract contact data
315
+ contacts = getattr(webhook.message, 'contacts', [])
316
+ if not isinstance(contacts, list):
317
+ contacts = [contacts] if contacts else []
318
+
319
+ if not contacts:
320
+ return {"success": False, "error": "No contact data found"}
321
+
322
+ # Extract and format metadata
323
+ metadata = MetadataExtractor.extract_metadata(webhook, start_time)
324
+ metadata_text = MetadataExtractor.format_metadata_for_user(metadata)
325
+
326
+ # Send metadata response
327
+ metadata_result = await self.messenger.send_text(
328
+ recipient=user_id,
329
+ text=metadata_text,
330
+ reply_to_message_id=message_id
331
+ )
332
+
333
+ if not metadata_result.success:
334
+ self.logger.error(f"Failed to send metadata: {metadata_result.error}")
335
+ return {"success": False, "error": "Failed to send metadata"}
336
+
337
+ # Echo the contacts (send the first contact)
338
+ contact_to_send = contacts[0] if contacts else None
339
+ contact_sent = False
340
+
341
+ if contact_to_send:
342
+ # Convert contact to the format expected by messenger
343
+ contact_dict = self._convert_contact_to_dict(contact_to_send)
344
+
345
+ contact_result = await self.messenger.send_contact(
346
+ contact=contact_dict,
347
+ recipient=user_id
348
+ )
349
+
350
+ contact_sent = contact_result.success
351
+
352
+ if not contact_sent:
353
+ self.logger.error(f"Failed to send contact: {contact_result.error}")
354
+
355
+ # Send fallback text response
356
+ contact_name = self._extract_contact_name(contact_to_send)
357
+ fallback_text = f"👤 Contact echo - {contact_name} (relay failed)"
358
+ await self.messenger.send_text(
359
+ recipient=user_id,
360
+ text=fallback_text
361
+ )
362
+
363
+ # Update user activity
364
+ await self.cache_helper.update_user_activity(user_id, "contact")
365
+
366
+ # Store message in history
367
+ await self.cache_helper.store_message_history(user_id, {
368
+ "message_id": message_id,
369
+ "type": "contact",
370
+ "contacts_count": len(contacts),
371
+ "timestamp": webhook.timestamp.isoformat(),
372
+ "echo_sent": contact_sent
373
+ })
374
+
375
+ processing_time = int((time.time() - start_time) * 1000)
376
+ self.logger.info(f"✅ Contact message processed in {processing_time}ms")
377
+
378
+ return {
379
+ "success": True,
380
+ "message_type": "contact",
381
+ "metadata_sent": True,
382
+ "contact_echoed": contact_sent,
383
+ "contacts_count": len(contacts),
384
+ "processing_time_ms": processing_time
385
+ }
386
+
387
+ except Exception as e:
388
+ self.logger.error(f"❌ Error handling contact message: {e}", exc_info=True)
389
+ return {"success": False, "error": str(e)}
390
+
391
+ async def handle_interactive_message(self, webhook: IncomingMessageWebhook,
392
+ user_profile: UserProfile) -> Dict[str, any]:
393
+ """
394
+ Handle interactive message (button/list selections).
395
+
396
+ Args:
397
+ webhook: IncomingMessageWebhook with interactive message
398
+ user_profile: User profile for tracking
399
+
400
+ Returns:
401
+ Result dictionary with operation status
402
+ """
403
+ try:
404
+ start_time = time.time()
405
+
406
+ user_id = webhook.user.user_id
407
+ message_id = webhook.message.message_id
408
+ selection_id = webhook.get_interactive_selection()
409
+
410
+ self.logger.info(f"🔘 Processing interactive message from {user_id}: {selection_id}")
411
+
412
+ # Extract and format metadata
413
+ metadata = MetadataExtractor.extract_metadata(webhook, start_time)
414
+ metadata_text = MetadataExtractor.format_metadata_for_user(metadata)
415
+
416
+ # Send metadata response
417
+ metadata_result = await self.messenger.send_text(
418
+ recipient=user_id,
419
+ text=metadata_text,
420
+ reply_to_message_id=message_id
421
+ )
422
+
423
+ if not metadata_result.success:
424
+ self.logger.error(f"Failed to send metadata: {metadata_result.error}")
425
+ return {"success": False, "error": "Failed to send metadata"}
426
+
427
+ # Send acknowledgment of selection
428
+ ack_text = f"✅ Interactive selection acknowledged: `{selection_id}`"
429
+ await self.messenger.send_text(
430
+ recipient=user_id,
431
+ text=ack_text
432
+ )
433
+
434
+ # Update user activity
435
+ await self.cache_helper.update_user_activity(user_id, "interactive")
436
+
437
+ # Store message in history
438
+ await self.cache_helper.store_message_history(user_id, {
439
+ "message_id": message_id,
440
+ "type": "interactive",
441
+ "selection_id": selection_id,
442
+ "timestamp": webhook.timestamp.isoformat(),
443
+ "processed": True
444
+ })
445
+
446
+ processing_time = int((time.time() - start_time) * 1000)
447
+ self.logger.info(f"✅ Interactive message processed in {processing_time}ms")
448
+
449
+ return {
450
+ "success": True,
451
+ "message_type": "interactive",
452
+ "metadata_sent": True,
453
+ "selection_acknowledged": True,
454
+ "selection_id": selection_id,
455
+ "processing_time_ms": processing_time
456
+ }
457
+
458
+ except Exception as e:
459
+ self.logger.error(f"❌ Error handling interactive message: {e}", exc_info=True)
460
+ return {"success": False, "error": str(e)}
461
+
462
+ def _convert_contact_to_dict(self, contact_obj) -> Dict[str, any]:
463
+ """Convert contact object to dictionary format for messenger."""
464
+ contact_dict = {}
465
+
466
+ # Extract name information
467
+ if hasattr(contact_obj, 'name') and contact_obj.name:
468
+ name_obj = contact_obj.name
469
+ contact_dict['name'] = {}
470
+
471
+ if hasattr(name_obj, 'formatted_name'):
472
+ contact_dict['name']['formatted_name'] = name_obj.formatted_name
473
+ if hasattr(name_obj, 'first_name'):
474
+ contact_dict['name']['first_name'] = name_obj.first_name
475
+ if hasattr(name_obj, 'last_name'):
476
+ contact_dict['name']['last_name'] = name_obj.last_name
477
+
478
+ # Extract phone numbers
479
+ if hasattr(contact_obj, 'phones') and contact_obj.phones:
480
+ phones = contact_obj.phones
481
+ if not isinstance(phones, list):
482
+ phones = [phones]
483
+ contact_dict['phones'] = []
484
+ for phone in phones:
485
+ if hasattr(phone, 'phone'):
486
+ phone_dict = {'phone': phone.phone}
487
+ if hasattr(phone, 'type'):
488
+ phone_dict['type'] = phone.type
489
+ contact_dict['phones'].append(phone_dict)
490
+
491
+ # Extract emails
492
+ if hasattr(contact_obj, 'emails') and contact_obj.emails:
493
+ emails = contact_obj.emails
494
+ if not isinstance(emails, list):
495
+ emails = [emails]
496
+ contact_dict['emails'] = []
497
+ for email in emails:
498
+ if hasattr(email, 'email'):
499
+ email_dict = {'email': email.email}
500
+ if hasattr(email, 'type'):
501
+ email_dict['type'] = email.type
502
+ contact_dict['emails'].append(email_dict)
503
+
504
+ return contact_dict
505
+
506
+ def _extract_contact_name(self, contact_obj) -> str:
507
+ """Extract contact name for display."""
508
+ if hasattr(contact_obj, 'name') and contact_obj.name:
509
+ name_obj = contact_obj.name
510
+ if hasattr(name_obj, 'formatted_name'):
511
+ return name_obj.formatted_name
512
+ elif hasattr(name_obj, 'first_name'):
513
+ first = name_obj.first_name or ""
514
+ last = getattr(name_obj, 'last_name', '') or ""
515
+ return f"{first} {last}".strip()
516
+
517
+ return "Unknown Contact"
518
+
519
+
520
+ # Convenience functions for direct use
521
+ async def handle_message_by_type(webhook: IncomingMessageWebhook, user_profile: UserProfile,
522
+ messenger, cache_factory, logger) -> Dict[str, any]:
523
+ """
524
+ Handle message based on its type (convenience function).
525
+
526
+ Args:
527
+ webhook: IncomingMessageWebhook to process
528
+ user_profile: User profile for tracking
529
+ messenger: IMessenger instance
530
+ cache_factory: Cache factory
531
+ logger: Logger instance
532
+
533
+ Returns:
534
+ Result dictionary
535
+ """
536
+ handlers = MessageHandlers(messenger, cache_factory, logger)
537
+ message_type = webhook.get_message_type_name().lower()
538
+
539
+ if message_type == "text":
540
+ return await handlers.handle_text_message(webhook, user_profile)
541
+ elif message_type in ["image", "video", "audio", "voice", "document", "sticker"]:
542
+ return await handlers.handle_media_message(webhook, user_profile)
543
+ elif message_type == "location":
544
+ return await handlers.handle_location_message(webhook, user_profile)
545
+ elif message_type in ["contact", "contacts"]:
546
+ return await handlers.handle_contact_message(webhook, user_profile)
547
+ elif message_type == "interactive":
548
+ return await handlers.handle_interactive_message(webhook, user_profile)
549
+ else:
550
+ logger.warning(f"Unsupported message type: {message_type}")
551
+ return {"success": False, "error": f"Unsupported message type: {message_type}"}