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,10 +6,12 @@ that demonstrate interactive features and specialized messaging capabilities.
6
6
  """
7
7
 
8
8
  import time
9
- from typing import Dict
10
9
 
10
+ from wappa.messaging.whatsapp.models.interactive_models import (
11
+ InteractiveHeader,
12
+ ReplyButton,
13
+ )
11
14
  from wappa.webhooks import IncomingMessageWebhook
12
- from wappa.messaging.whatsapp.models.interactive_models import ReplyButton, InteractiveHeader
13
15
 
14
16
  from ..models.state_models import ButtonState, ListState, StateType
15
17
  from ..models.user_models import UserProfile
@@ -18,11 +20,11 @@ from ..utils.cache_utils import CacheHelper
18
20
 
19
21
  class CommandHandlers:
20
22
  """Collection of handlers for special commands."""
21
-
23
+
22
24
  def __init__(self, messenger, cache_factory, logger):
23
25
  """
24
26
  Initialize command handlers.
25
-
27
+
26
28
  Args:
27
29
  messenger: IMessenger instance for sending messages
28
30
  cache_factory: Cache factory for data persistence
@@ -31,56 +33,59 @@ class CommandHandlers:
31
33
  self.messenger = messenger
32
34
  self.cache_helper = CacheHelper(cache_factory)
33
35
  self.logger = logger
34
-
35
- async def handle_button_command(self, webhook: IncomingMessageWebhook,
36
- user_profile: UserProfile) -> Dict[str, any]:
36
+
37
+ async def handle_button_command(
38
+ self, webhook: IncomingMessageWebhook, user_profile: UserProfile
39
+ ) -> dict[str, any]:
37
40
  """
38
41
  Handle /button command - creates interactive button message.
39
-
42
+
40
43
  Args:
41
44
  webhook: IncomingMessageWebhook with command
42
45
  user_profile: User profile for tracking
43
-
46
+
44
47
  Returns:
45
48
  Result dictionary with operation status
46
49
  """
47
50
  try:
48
51
  start_time = time.time()
49
-
52
+
50
53
  user_id = webhook.user.user_id
51
54
  message_id = webhook.message.message_id
52
-
55
+
53
56
  self.logger.info(f"🔘 Processing /button command from {user_id}")
54
-
57
+
55
58
  # Clean up any existing button state
56
- existing_state = await self.cache_helper.get_user_state(user_id, StateType.BUTTON)
59
+ existing_state = await self.cache_helper.get_user_state(
60
+ user_id, StateType.BUTTON
61
+ )
57
62
  if existing_state:
58
63
  await self.cache_helper.remove_user_state(user_id, StateType.BUTTON)
59
-
64
+
60
65
  # Create button data for state storage (as dictionaries)
61
66
  button_data = [
62
67
  {"id": "kitty", "title": "🐱 Kitty"},
63
- {"id": "puppy", "title": "🐶 Puppy"}
68
+ {"id": "puppy", "title": "🐶 Puppy"},
64
69
  ]
65
-
70
+
66
71
  # Create button objects for WhatsApp messenger
67
72
  buttons = [
68
73
  ReplyButton(id="kitty", title="🐱 Kitty"),
69
- ReplyButton(id="puppy", title="🐶 Puppy")
74
+ ReplyButton(id="puppy", title="🐶 Puppy"),
70
75
  ]
71
-
76
+
72
77
  # Create button state with 10 minute TTL (using dictionaries)
73
78
  button_state = ButtonState.create_button_state(
74
79
  user_id=user_id,
75
80
  buttons=button_data,
76
81
  message_text="Choose your favorite animal! You have 10 minutes to decide.",
77
82
  ttl_seconds=600, # 10 minutes
78
- original_message_id=message_id
83
+ original_message_id=message_id,
79
84
  )
80
-
85
+
81
86
  # Save the state
82
87
  await self.cache_helper.save_user_state(button_state)
83
-
88
+
84
89
  # Send button message
85
90
  button_result = await self.messenger.send_button_message(
86
91
  buttons=buttons,
@@ -88,18 +93,20 @@ class CommandHandlers:
88
93
  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
94
  header=InteractiveHeader(type="text", text="Interactive Button Demo"),
90
95
  footer="⏰ Expires in 10 minutes",
91
- reply_to_message_id=message_id
96
+ reply_to_message_id=message_id,
92
97
  )
93
-
98
+
94
99
  if not button_result.success:
95
- self.logger.error(f"Failed to send button message: {button_result.error}")
100
+ self.logger.error(
101
+ f"Failed to send button message: {button_result.error}"
102
+ )
96
103
  await self.cache_helper.remove_user_state(user_id, StateType.BUTTON)
97
104
  return {"success": False, "error": "Failed to send button message"}
98
-
105
+
99
106
  # Update button state with message ID
100
107
  button_state.interactive_message_id = button_result.message_id
101
108
  await self.cache_helper.save_user_state(button_state)
102
-
109
+
103
110
  # Send instruction message
104
111
  instruction_text = (
105
112
  "📋 *How to use this demo:*\n\n"
@@ -110,56 +117,56 @@ class CommandHandlers:
110
117
  "5. ⏰ State expires in 10 minutes if no selection is made\n\n"
111
118
  "💡 *Pro tip*: This demonstrates state management with TTL!"
112
119
  )
113
-
114
- await self.messenger.send_text(
115
- recipient=user_id,
116
- text=instruction_text
117
- )
118
-
120
+
121
+ await self.messenger.send_text(recipient=user_id, text=instruction_text)
122
+
119
123
  # Update user activity
120
124
  await self.cache_helper.update_user_activity(user_id, "text", "/button")
121
-
125
+
122
126
  processing_time = int((time.time() - start_time) * 1000)
123
127
  self.logger.info(f"✅ Button command processed in {processing_time}ms")
124
-
128
+
125
129
  return {
126
130
  "success": True,
127
131
  "command": "/button",
128
132
  "state_created": True,
129
133
  "state_ttl_seconds": 600,
130
134
  "buttons_sent": True,
131
- "processing_time_ms": processing_time
135
+ "processing_time_ms": processing_time,
132
136
  }
133
-
137
+
134
138
  except Exception as e:
135
139
  self.logger.error(f"❌ Error handling /button command: {e}", exc_info=True)
136
140
  return {"success": False, "error": str(e)}
137
-
138
- async def handle_list_command(self, webhook: IncomingMessageWebhook,
139
- user_profile: UserProfile) -> Dict[str, any]:
141
+
142
+ async def handle_list_command(
143
+ self, webhook: IncomingMessageWebhook, user_profile: UserProfile
144
+ ) -> dict[str, any]:
140
145
  """
141
146
  Handle /list command - creates interactive list message.
142
-
147
+
143
148
  Args:
144
149
  webhook: IncomingMessageWebhook with command
145
150
  user_profile: User profile for tracking
146
-
151
+
147
152
  Returns:
148
153
  Result dictionary with operation status
149
154
  """
150
155
  try:
151
156
  start_time = time.time()
152
-
157
+
153
158
  user_id = webhook.user.user_id
154
159
  message_id = webhook.message.message_id
155
-
160
+
156
161
  self.logger.info(f"📋 Processing /list command from {user_id}")
157
-
162
+
158
163
  # Clean up any existing list state
159
- existing_state = await self.cache_helper.get_user_state(user_id, StateType.LIST)
164
+ existing_state = await self.cache_helper.get_user_state(
165
+ user_id, StateType.LIST
166
+ )
160
167
  if existing_state:
161
168
  await self.cache_helper.remove_user_state(user_id, StateType.LIST)
162
-
169
+
163
170
  # Create list sections with media options
164
171
  sections = [
165
172
  {
@@ -168,27 +175,27 @@ class CommandHandlers:
168
175
  {
169
176
  "id": "image_file",
170
177
  "title": "🖼️ Image",
171
- "description": "Get a sample image file"
178
+ "description": "Get a sample image file",
172
179
  },
173
180
  {
174
181
  "id": "video_file",
175
182
  "title": "🎬 Video",
176
- "description": "Get a sample video file"
183
+ "description": "Get a sample video file",
177
184
  },
178
185
  {
179
186
  "id": "audio_file",
180
187
  "title": "🎵 Audio",
181
- "description": "Get a sample audio file"
188
+ "description": "Get a sample audio file",
182
189
  },
183
190
  {
184
191
  "id": "document_file",
185
192
  "title": "📄 Document",
186
- "description": "Get a sample document file"
187
- }
188
- ]
193
+ "description": "Get a sample document file",
194
+ },
195
+ ],
189
196
  }
190
197
  ]
191
-
198
+
192
199
  # Create list state with 10 minute TTL
193
200
  list_state = ListState.create_list_state(
194
201
  user_id=user_id,
@@ -196,12 +203,12 @@ class CommandHandlers:
196
203
  message_text="Choose the type of media file you want to receive!",
197
204
  button_text="Choose Media",
198
205
  ttl_seconds=600, # 10 minutes
199
- original_message_id=message_id
206
+ original_message_id=message_id,
200
207
  )
201
-
208
+
202
209
  # Save the state
203
210
  await self.cache_helper.save_user_state(list_state)
204
-
211
+
205
212
  # Send list message
206
213
  list_result = await self.messenger.send_list_message(
207
214
  sections=sections,
@@ -210,18 +217,18 @@ class CommandHandlers:
210
217
  button_text="Choose Media",
211
218
  header="Interactive List Demo",
212
219
  footer="⏰ Expires in 10 minutes",
213
- reply_to_message_id=message_id
220
+ reply_to_message_id=message_id,
214
221
  )
215
-
222
+
216
223
  if not list_result.success:
217
224
  self.logger.error(f"Failed to send list message: {list_result.error}")
218
225
  await self.cache_helper.remove_user_state(user_id, StateType.LIST)
219
226
  return {"success": False, "error": "Failed to send list message"}
220
-
227
+
221
228
  # Update list state with message ID
222
229
  list_state.interactive_message_id = list_result.message_id
223
230
  await self.cache_helper.save_user_state(list_state)
224
-
231
+
225
232
  # Send instruction message
226
233
  instruction_text = (
227
234
  "📋 *How to use this demo:*\n\n"
@@ -233,51 +240,49 @@ class CommandHandlers:
233
240
  "6. ⏰ State expires in 10 minutes if no selection is made\n\n"
234
241
  "💡 *Pro tip*: This demonstrates list interactions with media responses!"
235
242
  )
236
-
237
- await self.messenger.send_text(
238
- recipient=user_id,
239
- text=instruction_text
240
- )
241
-
243
+
244
+ await self.messenger.send_text(recipient=user_id, text=instruction_text)
245
+
242
246
  # Update user activity
243
247
  await self.cache_helper.update_user_activity(user_id, "text", "/list")
244
-
248
+
245
249
  processing_time = int((time.time() - start_time) * 1000)
246
250
  self.logger.info(f"✅ List command processed in {processing_time}ms")
247
-
251
+
248
252
  return {
249
253
  "success": True,
250
254
  "command": "/list",
251
255
  "state_created": True,
252
256
  "state_ttl_seconds": 600,
253
257
  "list_sent": True,
254
- "processing_time_ms": processing_time
258
+ "processing_time_ms": processing_time,
255
259
  }
256
-
260
+
257
261
  except Exception as e:
258
262
  self.logger.error(f"❌ Error handling /list command: {e}", exc_info=True)
259
263
  return {"success": False, "error": str(e)}
260
-
261
- async def handle_cta_command(self, webhook: IncomingMessageWebhook,
262
- user_profile: UserProfile) -> Dict[str, any]:
264
+
265
+ async def handle_cta_command(
266
+ self, webhook: IncomingMessageWebhook, user_profile: UserProfile
267
+ ) -> dict[str, any]:
263
268
  """
264
269
  Handle /cta command - sends call-to-action message.
265
-
270
+
266
271
  Args:
267
272
  webhook: IncomingMessageWebhook with command
268
273
  user_profile: User profile for tracking
269
-
274
+
270
275
  Returns:
271
276
  Result dictionary with operation status
272
277
  """
273
278
  try:
274
279
  start_time = time.time()
275
-
280
+
276
281
  user_id = webhook.user.user_id
277
282
  message_id = webhook.message.message_id
278
-
283
+
279
284
  self.logger.info(f"🔗 Processing /cta command from {user_id}")
280
-
285
+
281
286
  # Send CTA message with link to Wappa documentation
282
287
  cta_result = await self.messenger.send_cta_message(
283
288
  button_text="📚 View Documentation",
@@ -286,13 +291,13 @@ class CommandHandlers:
286
291
  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
292
  header="CTA Button Demo",
288
293
  footer="External link - opens in browser",
289
- reply_to_message_id=message_id
294
+ reply_to_message_id=message_id,
290
295
  )
291
-
296
+
292
297
  if not cta_result.success:
293
298
  self.logger.error(f"Failed to send CTA message: {cta_result.error}")
294
299
  return {"success": False, "error": "Failed to send CTA message"}
295
-
300
+
296
301
  # Send follow-up explanation
297
302
  explanation_text = (
298
303
  "📋 *About CTA Buttons:*\n\n"
@@ -307,56 +312,54 @@ class CommandHandlers:
307
312
  "• Provide easy access to support or contact forms\n\n"
308
313
  "💡 *Pro tip*: CTA buttons are great for bridging WhatsApp conversations with web experiences!"
309
314
  )
310
-
311
- await self.messenger.send_text(
312
- recipient=user_id,
313
- text=explanation_text
314
- )
315
-
315
+
316
+ await self.messenger.send_text(recipient=user_id, text=explanation_text)
317
+
316
318
  # Update user activity
317
319
  await self.cache_helper.update_user_activity(user_id, "text", "/cta")
318
-
320
+
319
321
  processing_time = int((time.time() - start_time) * 1000)
320
322
  self.logger.info(f"✅ CTA command processed in {processing_time}ms")
321
-
323
+
322
324
  return {
323
325
  "success": True,
324
326
  "command": "/cta",
325
327
  "cta_sent": True,
326
328
  "url": "https://wappa.mimeia.com/docs",
327
- "processing_time_ms": processing_time
329
+ "processing_time_ms": processing_time,
328
330
  }
329
-
331
+
330
332
  except Exception as e:
331
333
  self.logger.error(f"❌ Error handling /cta command: {e}", exc_info=True)
332
334
  return {"success": False, "error": str(e)}
333
-
334
- async def handle_location_command(self, webhook: IncomingMessageWebhook,
335
- user_profile: UserProfile) -> Dict[str, any]:
335
+
336
+ async def handle_location_command(
337
+ self, webhook: IncomingMessageWebhook, user_profile: UserProfile
338
+ ) -> dict[str, any]:
336
339
  """
337
340
  Handle /location command - sends predefined location.
338
-
341
+
339
342
  Args:
340
343
  webhook: IncomingMessageWebhook with command
341
344
  user_profile: User profile for tracking
342
-
345
+
343
346
  Returns:
344
347
  Result dictionary with operation status
345
348
  """
346
349
  try:
347
350
  start_time = time.time()
348
-
351
+
349
352
  user_id = webhook.user.user_id
350
353
  message_id = webhook.message.message_id
351
-
354
+
352
355
  self.logger.info(f"📍 Processing /location command from {user_id}")
353
-
356
+
354
357
  # Predefined coordinates (Bogotá, Colombia)
355
358
  latitude = 4.616738
356
359
  longitude = -74.089853
357
360
  location_name = "Bogotá, Colombia"
358
361
  location_address = "Bogotá D.C., Colombia"
359
-
362
+
360
363
  # Send location message
361
364
  location_result = await self.messenger.send_location(
362
365
  latitude=latitude,
@@ -364,13 +367,13 @@ class CommandHandlers:
364
367
  recipient=user_id,
365
368
  name=location_name,
366
369
  address=location_address,
367
- reply_to_message_id=message_id
370
+ reply_to_message_id=message_id,
368
371
  )
369
-
372
+
370
373
  if not location_result.success:
371
374
  self.logger.error(f"Failed to send location: {location_result.error}")
372
375
  return {"success": False, "error": "Failed to send location"}
373
-
376
+
374
377
  # Send follow-up explanation
375
378
  explanation_text = (
376
379
  f"📍 *Location Demo*\n\n"
@@ -386,47 +389,52 @@ class CommandHandlers:
386
389
  f"• Useful for sharing business locations, meeting points, etc.\n\n"
387
390
  f"💡 *Pro tip*: Location messages are perfect for businesses to share their address with customers!"
388
391
  )
389
-
390
- await self.messenger.send_text(
391
- recipient=user_id,
392
- text=explanation_text
393
- )
394
-
392
+
393
+ await self.messenger.send_text(recipient=user_id, text=explanation_text)
394
+
395
395
  # Update user activity
396
396
  await self.cache_helper.update_user_activity(user_id, "text", "/location")
397
-
397
+
398
398
  processing_time = int((time.time() - start_time) * 1000)
399
399
  self.logger.info(f"✅ Location command processed in {processing_time}ms")
400
-
400
+
401
401
  return {
402
402
  "success": True,
403
403
  "command": "/location",
404
404
  "location_sent": True,
405
405
  "coordinates": {"latitude": latitude, "longitude": longitude},
406
406
  "location_name": location_name,
407
- "processing_time_ms": processing_time
407
+ "processing_time_ms": processing_time,
408
408
  }
409
-
409
+
410
410
  except Exception as e:
411
- self.logger.error(f"❌ Error handling /location command: {e}", exc_info=True)
411
+ self.logger.error(
412
+ f"❌ Error handling /location command: {e}", exc_info=True
413
+ )
412
414
  return {"success": False, "error": str(e)}
413
415
 
414
416
 
415
417
  # Command mapping for easy lookup
416
418
  COMMAND_HANDLERS = {
417
419
  "/button": "handle_button_command",
418
- "/list": "handle_list_command",
420
+ "/list": "handle_list_command",
419
421
  "/cta": "handle_cta_command",
420
- "/location": "handle_location_command"
422
+ "/location": "handle_location_command",
421
423
  }
422
424
 
423
425
 
424
426
  # 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
+ async def handle_command(
428
+ command: str,
429
+ webhook: IncomingMessageWebhook,
430
+ user_profile: UserProfile,
431
+ messenger,
432
+ cache_factory,
433
+ logger,
434
+ ) -> dict[str, any]:
427
435
  """
428
436
  Handle command based on command string (convenience function).
429
-
437
+
430
438
  Args:
431
439
  command: Command string (e.g., "/button")
432
440
  webhook: IncomingMessageWebhook with command
@@ -434,13 +442,13 @@ async def handle_command(command: str, webhook: IncomingMessageWebhook,
434
442
  messenger: IMessenger instance
435
443
  cache_factory: Cache factory
436
444
  logger: Logger instance
437
-
445
+
438
446
  Returns:
439
447
  Result dictionary
440
448
  """
441
449
  handlers = CommandHandlers(messenger, cache_factory, logger)
442
450
  command_lower = command.lower()
443
-
451
+
444
452
  if command_lower == "/button":
445
453
  return await handlers.handle_button_command(webhook, user_profile)
446
454
  elif command_lower == "/list":
@@ -457,10 +465,10 @@ async def handle_command(command: str, webhook: IncomingMessageWebhook,
457
465
  def is_special_command(text: str) -> bool:
458
466
  """
459
467
  Check if text is a special command.
460
-
468
+
461
469
  Args:
462
470
  text: Message text to check
463
-
471
+
464
472
  Returns:
465
473
  True if it's a special command, False otherwise
466
474
  """
@@ -471,14 +479,14 @@ def is_special_command(text: str) -> bool:
471
479
  def get_command_from_text(text: str) -> str:
472
480
  """
473
481
  Extract command from message text.
474
-
482
+
475
483
  Args:
476
484
  text: Message text
477
-
485
+
478
486
  Returns:
479
487
  Command string or empty string if not a command
480
488
  """
481
489
  text_clean = text.strip().lower()
482
490
  if is_special_command(text_clean):
483
491
  return text_clean
484
- return ""
492
+ return ""