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