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.
- wappa/__init__.py +4 -5
- wappa/api/controllers/webhook_controller.py +5 -2
- wappa/api/dependencies/__init__.py +0 -5
- wappa/api/middleware/error_handler.py +4 -4
- wappa/api/middleware/owner.py +11 -5
- wappa/api/routes/webhooks.py +2 -2
- wappa/cli/__init__.py +1 -1
- wappa/cli/examples/init/app/main.py +2 -1
- wappa/cli/examples/init/app/master_event.py +5 -3
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -1
- wappa/cli/examples/json_cache_example/app/main.py +56 -44
- wappa/cli/examples/json_cache_example/app/master_event.py +181 -145
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -1
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +32 -51
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +2 -2
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +52 -46
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +70 -62
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +41 -44
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +83 -71
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +73 -57
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +2 -2
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +54 -56
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +85 -80
- wappa/cli/examples/openai_transcript/app/main.py +2 -1
- wappa/cli/examples/openai_transcript/app/master_event.py +31 -22
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +1 -1
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +37 -24
- wappa/cli/examples/redis_cache_example/app/__init__.py +1 -1
- wappa/cli/examples/redis_cache_example/app/main.py +56 -44
- wappa/cli/examples/redis_cache_example/app/master_event.py +181 -145
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +31 -50
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +2 -2
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +52 -46
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +70 -62
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +41 -44
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +83 -71
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +73 -57
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +2 -2
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +54 -56
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +85 -80
- wappa/cli/examples/simple_echo_example/app/__init__.py +1 -1
- wappa/cli/examples/simple_echo_example/app/main.py +41 -33
- wappa/cli/examples/simple_echo_example/app/master_event.py +78 -57
- wappa/cli/examples/wappa_full_example/app/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +134 -126
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +237 -229
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +170 -148
- wappa/cli/examples/wappa_full_example/app/main.py +51 -39
- wappa/cli/examples/wappa_full_example/app/master_event.py +179 -120
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +113 -104
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +92 -76
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +109 -83
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +132 -113
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +175 -132
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +126 -87
- wappa/cli/main.py +9 -4
- wappa/core/__init__.py +18 -23
- wappa/core/config/settings.py +7 -5
- wappa/core/events/default_handlers.py +1 -1
- wappa/core/factory/wappa_builder.py +38 -25
- wappa/core/plugins/redis_plugin.py +1 -3
- wappa/core/plugins/wappa_core_plugin.py +7 -6
- wappa/core/types.py +12 -12
- wappa/core/wappa_app.py +10 -8
- wappa/database/__init__.py +3 -4
- wappa/domain/enums/messenger_platform.py +1 -2
- wappa/domain/factories/media_factory.py +5 -20
- wappa/domain/factories/message_factory.py +5 -20
- wappa/domain/factories/messenger_factory.py +2 -4
- wappa/domain/interfaces/cache_interface.py +7 -7
- wappa/domain/interfaces/media_interface.py +2 -5
- wappa/domain/models/media_result.py +1 -3
- wappa/domain/models/platforms/platform_config.py +1 -3
- wappa/messaging/__init__.py +9 -12
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
- wappa/models/__init__.py +27 -35
- wappa/persistence/__init__.py +12 -15
- wappa/persistence/cache_factory.py +0 -1
- wappa/persistence/json/__init__.py +1 -1
- wappa/persistence/json/cache_adapters.py +37 -25
- wappa/persistence/json/handlers/state_handler.py +60 -52
- wappa/persistence/json/handlers/table_handler.py +51 -49
- wappa/persistence/json/handlers/user_handler.py +71 -55
- wappa/persistence/json/handlers/utils/file_manager.py +42 -39
- wappa/persistence/json/handlers/utils/key_factory.py +1 -1
- wappa/persistence/json/handlers/utils/serialization.py +13 -11
- wappa/persistence/json/json_cache_factory.py +4 -8
- wappa/persistence/json/storage_manager.py +66 -79
- wappa/persistence/memory/__init__.py +1 -1
- wappa/persistence/memory/cache_adapters.py +37 -25
- wappa/persistence/memory/handlers/state_handler.py +62 -52
- wappa/persistence/memory/handlers/table_handler.py +59 -53
- wappa/persistence/memory/handlers/user_handler.py +75 -55
- wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
- wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
- wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
- wappa/persistence/memory/memory_cache_factory.py +3 -7
- wappa/persistence/memory/storage_manager.py +52 -62
- wappa/persistence/redis/cache_adapters.py +27 -21
- wappa/persistence/redis/ops.py +11 -11
- wappa/persistence/redis/redis_client.py +4 -6
- wappa/persistence/redis/redis_manager.py +12 -4
- wappa/processors/factory.py +5 -5
- wappa/schemas/factory.py +2 -5
- wappa/schemas/whatsapp/message_types/errors.py +3 -12
- wappa/schemas/whatsapp/validators.py +3 -3
- wappa/webhooks/__init__.py +17 -18
- wappa/webhooks/factory.py +3 -5
- wappa/webhooks/whatsapp/__init__.py +10 -13
- wappa/webhooks/whatsapp/message_types/audio.py +0 -4
- wappa/webhooks/whatsapp/message_types/document.py +1 -9
- wappa/webhooks/whatsapp/message_types/errors.py +3 -12
- wappa/webhooks/whatsapp/message_types/location.py +1 -21
- wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
- wappa/webhooks/whatsapp/message_types/text.py +0 -6
- wappa/webhooks/whatsapp/message_types/video.py +1 -20
- wappa/webhooks/whatsapp/status_models.py +2 -2
- wappa/webhooks/whatsapp/validators.py +3 -3
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/RECORD +126 -126
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
- {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(
|
|
37
|
-
|
|
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(
|
|
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(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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(
|
|
112
|
-
|
|
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
|
-
|
|
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(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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(
|
|
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(
|
|
199
|
-
|
|
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,
|
|
220
|
-
longitude = getattr(webhook.message,
|
|
221
|
-
location_name = getattr(webhook.message,
|
|
222
|
-
location_address = getattr(webhook.message,
|
|
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
|
-
|
|
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(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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(
|
|
295
|
-
|
|
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,
|
|
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(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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(
|
|
392
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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(
|
|
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) ->
|
|
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,
|
|
470
|
+
if hasattr(contact_obj, "name") and contact_obj.name:
|
|
468
471
|
name_obj = contact_obj.name
|
|
469
|
-
contact_dict[
|
|
470
|
-
|
|
471
|
-
if hasattr(name_obj,
|
|
472
|
-
contact_dict[
|
|
473
|
-
if hasattr(name_obj,
|
|
474
|
-
contact_dict[
|
|
475
|
-
if hasattr(name_obj,
|
|
476
|
-
contact_dict[
|
|
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,
|
|
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[
|
|
486
|
+
contact_dict["phones"] = []
|
|
484
487
|
for phone in phones:
|
|
485
|
-
if hasattr(phone,
|
|
486
|
-
phone_dict = {
|
|
487
|
-
if hasattr(phone,
|
|
488
|
-
phone_dict[
|
|
489
|
-
contact_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,
|
|
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[
|
|
499
|
+
contact_dict["emails"] = []
|
|
497
500
|
for email in emails:
|
|
498
|
-
if hasattr(email,
|
|
499
|
-
email_dict = {
|
|
500
|
-
if hasattr(email,
|
|
501
|
-
email_dict[
|
|
502
|
-
contact_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,
|
|
511
|
+
if hasattr(contact_obj, "name") and contact_obj.name:
|
|
509
512
|
name_obj = contact_obj.name
|
|
510
|
-
if hasattr(name_obj,
|
|
513
|
+
if hasattr(name_obj, "formatted_name"):
|
|
511
514
|
return name_obj.formatted_name
|
|
512
|
-
elif hasattr(name_obj,
|
|
515
|
+
elif hasattr(name_obj, "first_name"):
|
|
513
516
|
first = name_obj.first_name or ""
|
|
514
|
-
last = getattr(name_obj,
|
|
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(
|
|
522
|
-
|
|
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}"}
|