wappa 0.1.8__py3-none-any.whl → 0.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wappa might be problematic. Click here for more details.
- wappa/cli/examples/init/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +8 -0
- wappa/cli/examples/init/app/master_event.py +8 -0
- wappa/cli/examples/json_cache_example/.env.example +33 -0
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/main.py +235 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +419 -0
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +275 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +246 -0
- wappa/cli/examples/openai_transcript/.gitignore +63 -4
- wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
- wappa/cli/examples/openai_transcript/app/main.py +8 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +53 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +76 -0
- wappa/cli/examples/redis_cache_example/.env.example +33 -0
- wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
- wappa/cli/examples/redis_cache_example/app/main.py +234 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +419 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +275 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +246 -0
- wappa/cli/examples/simple_echo_example/.env.example +33 -0
- wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
- wappa/cli/examples/simple_echo_example/app/main.py +183 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +209 -0
- wappa/cli/examples/wappa_full_example/.env.example +33 -0
- wappa/cli/examples/wappa_full_example/.gitignore +63 -4
- wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +484 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +551 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/main.py +257 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +445 -0
- wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
- wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +425 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +287 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +301 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +483 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +473 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +298 -0
- wappa/cli/main.py +8 -4
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/RECORD +75 -11
- wappa/cli/examples/init/pyproject.toml +0 -7
- wappa/cli/examples/simple_echo_example/.python-version +0 -1
- wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/WHEEL +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Special command handlers for the Wappa Full Example application.
|
|
3
|
+
|
|
4
|
+
This module provides handlers for special commands like /button, /list, /cta, and /location
|
|
5
|
+
that demonstrate interactive features and specialized messaging capabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from typing import Dict
|
|
10
|
+
|
|
11
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
12
|
+
from wappa.messaging.whatsapp.models.interactive_models import ReplyButton, InteractiveHeader
|
|
13
|
+
|
|
14
|
+
from ..models.state_models import ButtonState, ListState, StateType
|
|
15
|
+
from ..models.user_models import UserProfile
|
|
16
|
+
from ..utils.cache_utils import CacheHelper
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CommandHandlers:
|
|
20
|
+
"""Collection of handlers for special commands."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, messenger, cache_factory, logger):
|
|
23
|
+
"""
|
|
24
|
+
Initialize command 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.logger = logger
|
|
34
|
+
|
|
35
|
+
async def handle_button_command(self, webhook: IncomingMessageWebhook,
|
|
36
|
+
user_profile: UserProfile) -> Dict[str, any]:
|
|
37
|
+
"""
|
|
38
|
+
Handle /button command - creates interactive button message.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
webhook: IncomingMessageWebhook with command
|
|
42
|
+
user_profile: User profile for tracking
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Result dictionary with operation status
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
start_time = time.time()
|
|
49
|
+
|
|
50
|
+
user_id = webhook.user.user_id
|
|
51
|
+
message_id = webhook.message.message_id
|
|
52
|
+
|
|
53
|
+
self.logger.info(f"🔘 Processing /button command from {user_id}")
|
|
54
|
+
|
|
55
|
+
# Clean up any existing button state
|
|
56
|
+
existing_state = await self.cache_helper.get_user_state(user_id, StateType.BUTTON)
|
|
57
|
+
if existing_state:
|
|
58
|
+
await self.cache_helper.remove_user_state(user_id, StateType.BUTTON)
|
|
59
|
+
|
|
60
|
+
# Create button data for state storage (as dictionaries)
|
|
61
|
+
button_data = [
|
|
62
|
+
{"id": "kitty", "title": "🐱 Kitty"},
|
|
63
|
+
{"id": "puppy", "title": "🐶 Puppy"}
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
# Create button objects for WhatsApp messenger
|
|
67
|
+
buttons = [
|
|
68
|
+
ReplyButton(id="kitty", title="🐱 Kitty"),
|
|
69
|
+
ReplyButton(id="puppy", title="🐶 Puppy")
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
# Create button state with 10 minute TTL (using dictionaries)
|
|
73
|
+
button_state = ButtonState.create_button_state(
|
|
74
|
+
user_id=user_id,
|
|
75
|
+
buttons=button_data,
|
|
76
|
+
message_text="Choose your favorite animal! You have 10 minutes to decide.",
|
|
77
|
+
ttl_seconds=600, # 10 minutes
|
|
78
|
+
original_message_id=message_id
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Save the state
|
|
82
|
+
await self.cache_helper.save_user_state(button_state)
|
|
83
|
+
|
|
84
|
+
# Send button message
|
|
85
|
+
button_result = await self.messenger.send_button_message(
|
|
86
|
+
buttons=buttons,
|
|
87
|
+
recipient=user_id,
|
|
88
|
+
body="🎯 *Button Demo Activated!*\n\nChoose your favorite animal below. You have 10 minutes to make your selection, or the state will expire automatically.",
|
|
89
|
+
header=InteractiveHeader(type="text", text="Interactive Button Demo"),
|
|
90
|
+
footer="⏰ Expires in 10 minutes",
|
|
91
|
+
reply_to_message_id=message_id
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if not button_result.success:
|
|
95
|
+
self.logger.error(f"Failed to send button message: {button_result.error}")
|
|
96
|
+
await self.cache_helper.remove_user_state(user_id, StateType.BUTTON)
|
|
97
|
+
return {"success": False, "error": "Failed to send button message"}
|
|
98
|
+
|
|
99
|
+
# Update button state with message ID
|
|
100
|
+
button_state.interactive_message_id = button_result.message_id
|
|
101
|
+
await self.cache_helper.save_user_state(button_state)
|
|
102
|
+
|
|
103
|
+
# Send instruction message
|
|
104
|
+
instruction_text = (
|
|
105
|
+
"📋 *How to use this demo:*\n\n"
|
|
106
|
+
"1. ✅ Click one of the buttons above to make your selection\n"
|
|
107
|
+
"2. 📸 You'll receive an image of your chosen animal\n"
|
|
108
|
+
"3. 📊 You'll also receive metadata about your selection\n"
|
|
109
|
+
"4. ⚠️ If you send any other message, I'll remind you to click a button\n"
|
|
110
|
+
"5. ⏰ State expires in 10 minutes if no selection is made\n\n"
|
|
111
|
+
"💡 *Pro tip*: This demonstrates state management with TTL!"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
await self.messenger.send_text(
|
|
115
|
+
recipient=user_id,
|
|
116
|
+
text=instruction_text
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Update user activity
|
|
120
|
+
await self.cache_helper.update_user_activity(user_id, "text", "/button")
|
|
121
|
+
|
|
122
|
+
processing_time = int((time.time() - start_time) * 1000)
|
|
123
|
+
self.logger.info(f"✅ Button command processed in {processing_time}ms")
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"success": True,
|
|
127
|
+
"command": "/button",
|
|
128
|
+
"state_created": True,
|
|
129
|
+
"state_ttl_seconds": 600,
|
|
130
|
+
"buttons_sent": True,
|
|
131
|
+
"processing_time_ms": processing_time
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
self.logger.error(f"❌ Error handling /button command: {e}", exc_info=True)
|
|
136
|
+
return {"success": False, "error": str(e)}
|
|
137
|
+
|
|
138
|
+
async def handle_list_command(self, webhook: IncomingMessageWebhook,
|
|
139
|
+
user_profile: UserProfile) -> Dict[str, any]:
|
|
140
|
+
"""
|
|
141
|
+
Handle /list command - creates interactive list message.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
webhook: IncomingMessageWebhook with command
|
|
145
|
+
user_profile: User profile for tracking
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Result dictionary with operation status
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
start_time = time.time()
|
|
152
|
+
|
|
153
|
+
user_id = webhook.user.user_id
|
|
154
|
+
message_id = webhook.message.message_id
|
|
155
|
+
|
|
156
|
+
self.logger.info(f"📋 Processing /list command from {user_id}")
|
|
157
|
+
|
|
158
|
+
# Clean up any existing list state
|
|
159
|
+
existing_state = await self.cache_helper.get_user_state(user_id, StateType.LIST)
|
|
160
|
+
if existing_state:
|
|
161
|
+
await self.cache_helper.remove_user_state(user_id, StateType.LIST)
|
|
162
|
+
|
|
163
|
+
# Create list sections with media options
|
|
164
|
+
sections = [
|
|
165
|
+
{
|
|
166
|
+
"title": "📁 Media Files",
|
|
167
|
+
"rows": [
|
|
168
|
+
{
|
|
169
|
+
"id": "image_file",
|
|
170
|
+
"title": "🖼️ Image",
|
|
171
|
+
"description": "Get a sample image file"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": "video_file",
|
|
175
|
+
"title": "🎬 Video",
|
|
176
|
+
"description": "Get a sample video file"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"id": "audio_file",
|
|
180
|
+
"title": "🎵 Audio",
|
|
181
|
+
"description": "Get a sample audio file"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"id": "document_file",
|
|
185
|
+
"title": "📄 Document",
|
|
186
|
+
"description": "Get a sample document file"
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
# Create list state with 10 minute TTL
|
|
193
|
+
list_state = ListState.create_list_state(
|
|
194
|
+
user_id=user_id,
|
|
195
|
+
sections=sections,
|
|
196
|
+
message_text="Choose the type of media file you want to receive!",
|
|
197
|
+
button_text="Choose Media",
|
|
198
|
+
ttl_seconds=600, # 10 minutes
|
|
199
|
+
original_message_id=message_id
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Save the state
|
|
203
|
+
await self.cache_helper.save_user_state(list_state)
|
|
204
|
+
|
|
205
|
+
# Send list message
|
|
206
|
+
list_result = await self.messenger.send_list_message(
|
|
207
|
+
sections=sections,
|
|
208
|
+
recipient=user_id,
|
|
209
|
+
body="🎯 *List Demo Activated!*\n\nSelect the type of media file you want to receive. You have 10 minutes to make your selection, or the state will expire automatically.",
|
|
210
|
+
button_text="Choose Media",
|
|
211
|
+
header="Interactive List Demo",
|
|
212
|
+
footer="⏰ Expires in 10 minutes",
|
|
213
|
+
reply_to_message_id=message_id
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if not list_result.success:
|
|
217
|
+
self.logger.error(f"Failed to send list message: {list_result.error}")
|
|
218
|
+
await self.cache_helper.remove_user_state(user_id, StateType.LIST)
|
|
219
|
+
return {"success": False, "error": "Failed to send list message"}
|
|
220
|
+
|
|
221
|
+
# Update list state with message ID
|
|
222
|
+
list_state.interactive_message_id = list_result.message_id
|
|
223
|
+
await self.cache_helper.save_user_state(list_state)
|
|
224
|
+
|
|
225
|
+
# Send instruction message
|
|
226
|
+
instruction_text = (
|
|
227
|
+
"📋 *How to use this demo:*\n\n"
|
|
228
|
+
"1. 📱 Tap the 'Choose Media' button above\n"
|
|
229
|
+
"2. 📋 Select one of the 4 media types from the list\n"
|
|
230
|
+
"3. 📎 You'll receive the corresponding media file\n"
|
|
231
|
+
"4. 📊 You'll also receive metadata about your selection\n"
|
|
232
|
+
"5. ⚠️ If you send any other message, I'll remind you to make a selection\n"
|
|
233
|
+
"6. ⏰ State expires in 10 minutes if no selection is made\n\n"
|
|
234
|
+
"💡 *Pro tip*: This demonstrates list interactions with media responses!"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
await self.messenger.send_text(
|
|
238
|
+
recipient=user_id,
|
|
239
|
+
text=instruction_text
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Update user activity
|
|
243
|
+
await self.cache_helper.update_user_activity(user_id, "text", "/list")
|
|
244
|
+
|
|
245
|
+
processing_time = int((time.time() - start_time) * 1000)
|
|
246
|
+
self.logger.info(f"✅ List command processed in {processing_time}ms")
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
"success": True,
|
|
250
|
+
"command": "/list",
|
|
251
|
+
"state_created": True,
|
|
252
|
+
"state_ttl_seconds": 600,
|
|
253
|
+
"list_sent": True,
|
|
254
|
+
"processing_time_ms": processing_time
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
except Exception as e:
|
|
258
|
+
self.logger.error(f"❌ Error handling /list command: {e}", exc_info=True)
|
|
259
|
+
return {"success": False, "error": str(e)}
|
|
260
|
+
|
|
261
|
+
async def handle_cta_command(self, webhook: IncomingMessageWebhook,
|
|
262
|
+
user_profile: UserProfile) -> Dict[str, any]:
|
|
263
|
+
"""
|
|
264
|
+
Handle /cta command - sends call-to-action message.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
webhook: IncomingMessageWebhook with command
|
|
268
|
+
user_profile: User profile for tracking
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Result dictionary with operation status
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
start_time = time.time()
|
|
275
|
+
|
|
276
|
+
user_id = webhook.user.user_id
|
|
277
|
+
message_id = webhook.message.message_id
|
|
278
|
+
|
|
279
|
+
self.logger.info(f"🔗 Processing /cta command from {user_id}")
|
|
280
|
+
|
|
281
|
+
# Send CTA message with link to Wappa documentation
|
|
282
|
+
cta_result = await self.messenger.send_cta_message(
|
|
283
|
+
button_text="📚 View Documentation",
|
|
284
|
+
button_url="https://wappa.mimeia.com/docs",
|
|
285
|
+
recipient=user_id,
|
|
286
|
+
body="🎯 *Call-to-Action Demo*\n\nThis is a demonstration of CTA (Call-to-Action) buttons that link to external websites. Click the button below to visit the Wappa framework documentation!",
|
|
287
|
+
header="CTA Button Demo",
|
|
288
|
+
footer="External link - opens in browser",
|
|
289
|
+
reply_to_message_id=message_id
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if not cta_result.success:
|
|
293
|
+
self.logger.error(f"Failed to send CTA message: {cta_result.error}")
|
|
294
|
+
return {"success": False, "error": "Failed to send CTA message"}
|
|
295
|
+
|
|
296
|
+
# Send follow-up explanation
|
|
297
|
+
explanation_text = (
|
|
298
|
+
"📋 *About CTA Buttons:*\n\n"
|
|
299
|
+
"✅ *What just happened:*\n"
|
|
300
|
+
"• A CTA (Call-to-Action) button was sent\n"
|
|
301
|
+
"• It links to: `https://wappa.mimeia.com/docs`\n"
|
|
302
|
+
"• When clicked, it opens in your default browser\n\n"
|
|
303
|
+
"🔗 *Use cases for CTA buttons:*\n"
|
|
304
|
+
"• Link to websites, documentation, or web apps\n"
|
|
305
|
+
"• Direct users to external resources\n"
|
|
306
|
+
"• Drive traffic to specific landing pages\n"
|
|
307
|
+
"• Provide easy access to support or contact forms\n\n"
|
|
308
|
+
"💡 *Pro tip*: CTA buttons are great for bridging WhatsApp conversations with web experiences!"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
await self.messenger.send_text(
|
|
312
|
+
recipient=user_id,
|
|
313
|
+
text=explanation_text
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Update user activity
|
|
317
|
+
await self.cache_helper.update_user_activity(user_id, "text", "/cta")
|
|
318
|
+
|
|
319
|
+
processing_time = int((time.time() - start_time) * 1000)
|
|
320
|
+
self.logger.info(f"✅ CTA command processed in {processing_time}ms")
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
"success": True,
|
|
324
|
+
"command": "/cta",
|
|
325
|
+
"cta_sent": True,
|
|
326
|
+
"url": "https://wappa.mimeia.com/docs",
|
|
327
|
+
"processing_time_ms": processing_time
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
self.logger.error(f"❌ Error handling /cta command: {e}", exc_info=True)
|
|
332
|
+
return {"success": False, "error": str(e)}
|
|
333
|
+
|
|
334
|
+
async def handle_location_command(self, webhook: IncomingMessageWebhook,
|
|
335
|
+
user_profile: UserProfile) -> Dict[str, any]:
|
|
336
|
+
"""
|
|
337
|
+
Handle /location command - sends predefined location.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
webhook: IncomingMessageWebhook with command
|
|
341
|
+
user_profile: User profile for tracking
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Result dictionary with operation status
|
|
345
|
+
"""
|
|
346
|
+
try:
|
|
347
|
+
start_time = time.time()
|
|
348
|
+
|
|
349
|
+
user_id = webhook.user.user_id
|
|
350
|
+
message_id = webhook.message.message_id
|
|
351
|
+
|
|
352
|
+
self.logger.info(f"📍 Processing /location command from {user_id}")
|
|
353
|
+
|
|
354
|
+
# Predefined coordinates (Bogotá, Colombia)
|
|
355
|
+
latitude = 4.616738
|
|
356
|
+
longitude = -74.089853
|
|
357
|
+
location_name = "Bogotá, Colombia"
|
|
358
|
+
location_address = "Bogotá D.C., Colombia"
|
|
359
|
+
|
|
360
|
+
# Send location message
|
|
361
|
+
location_result = await self.messenger.send_location(
|
|
362
|
+
latitude=latitude,
|
|
363
|
+
longitude=longitude,
|
|
364
|
+
recipient=user_id,
|
|
365
|
+
name=location_name,
|
|
366
|
+
address=location_address,
|
|
367
|
+
reply_to_message_id=message_id
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
if not location_result.success:
|
|
371
|
+
self.logger.error(f"Failed to send location: {location_result.error}")
|
|
372
|
+
return {"success": False, "error": "Failed to send location"}
|
|
373
|
+
|
|
374
|
+
# Send follow-up explanation
|
|
375
|
+
explanation_text = (
|
|
376
|
+
f"📍 *Location Demo*\n\n"
|
|
377
|
+
f"✅ *Location sent:*\n"
|
|
378
|
+
f"• *Name*: {location_name}\n"
|
|
379
|
+
f"• *Address*: {location_address}\n"
|
|
380
|
+
f"• *Coordinates*: {latitude}, {longitude}\n"
|
|
381
|
+
f"• *Maps Link*: @{latitude},{longitude},13.75z\n\n"
|
|
382
|
+
f"🗺️ *About location messages:*\n"
|
|
383
|
+
f"• Recipients can tap to open in Maps app\n"
|
|
384
|
+
f"• Shows location name and address if provided\n"
|
|
385
|
+
f"• Displays a map preview in the chat\n"
|
|
386
|
+
f"• Useful for sharing business locations, meeting points, etc.\n\n"
|
|
387
|
+
f"💡 *Pro tip*: Location messages are perfect for businesses to share their address with customers!"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
await self.messenger.send_text(
|
|
391
|
+
recipient=user_id,
|
|
392
|
+
text=explanation_text
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Update user activity
|
|
396
|
+
await self.cache_helper.update_user_activity(user_id, "text", "/location")
|
|
397
|
+
|
|
398
|
+
processing_time = int((time.time() - start_time) * 1000)
|
|
399
|
+
self.logger.info(f"✅ Location command processed in {processing_time}ms")
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
"success": True,
|
|
403
|
+
"command": "/location",
|
|
404
|
+
"location_sent": True,
|
|
405
|
+
"coordinates": {"latitude": latitude, "longitude": longitude},
|
|
406
|
+
"location_name": location_name,
|
|
407
|
+
"processing_time_ms": processing_time
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
except Exception as e:
|
|
411
|
+
self.logger.error(f"❌ Error handling /location command: {e}", exc_info=True)
|
|
412
|
+
return {"success": False, "error": str(e)}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# Command mapping for easy lookup
|
|
416
|
+
COMMAND_HANDLERS = {
|
|
417
|
+
"/button": "handle_button_command",
|
|
418
|
+
"/list": "handle_list_command",
|
|
419
|
+
"/cta": "handle_cta_command",
|
|
420
|
+
"/location": "handle_location_command"
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# Convenience functions for direct use
|
|
425
|
+
async def handle_command(command: str, webhook: IncomingMessageWebhook,
|
|
426
|
+
user_profile: UserProfile, messenger, cache_factory, logger) -> Dict[str, any]:
|
|
427
|
+
"""
|
|
428
|
+
Handle command based on command string (convenience function).
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
command: Command string (e.g., "/button")
|
|
432
|
+
webhook: IncomingMessageWebhook with command
|
|
433
|
+
user_profile: User profile for tracking
|
|
434
|
+
messenger: IMessenger instance
|
|
435
|
+
cache_factory: Cache factory
|
|
436
|
+
logger: Logger instance
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Result dictionary
|
|
440
|
+
"""
|
|
441
|
+
handlers = CommandHandlers(messenger, cache_factory, logger)
|
|
442
|
+
command_lower = command.lower()
|
|
443
|
+
|
|
444
|
+
if command_lower == "/button":
|
|
445
|
+
return await handlers.handle_button_command(webhook, user_profile)
|
|
446
|
+
elif command_lower == "/list":
|
|
447
|
+
return await handlers.handle_list_command(webhook, user_profile)
|
|
448
|
+
elif command_lower == "/cta":
|
|
449
|
+
return await handlers.handle_cta_command(webhook, user_profile)
|
|
450
|
+
elif command_lower == "/location":
|
|
451
|
+
return await handlers.handle_location_command(webhook, user_profile)
|
|
452
|
+
else:
|
|
453
|
+
logger.warning(f"Unsupported command: {command}")
|
|
454
|
+
return {"success": False, "error": f"Unsupported command: {command}"}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def is_special_command(text: str) -> bool:
|
|
458
|
+
"""
|
|
459
|
+
Check if text is a special command.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
text: Message text to check
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
True if it's a special command, False otherwise
|
|
466
|
+
"""
|
|
467
|
+
text_lower = text.strip().lower()
|
|
468
|
+
return text_lower in ["/button", "/list", "/cta", "/location"]
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def get_command_from_text(text: str) -> str:
|
|
472
|
+
"""
|
|
473
|
+
Extract command from message text.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
text: Message text
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
Command string or empty string if not a command
|
|
480
|
+
"""
|
|
481
|
+
text_clean = text.strip().lower()
|
|
482
|
+
if is_special_command(text_clean):
|
|
483
|
+
return text_clean
|
|
484
|
+
return ""
|