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.
- 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/core/config/settings.py +34 -2
- wappa/persistence/__init__.py +2 -2
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/RECORD +77 -13
- 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.7.dist-info → wappa-0.1.9.dist-info}/WHEEL +0 -0
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
- {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}"}
|