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 @@ selections, including state validation, response processing, and cleanup.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import time
|
|
9
|
-
from typing import Dict
|
|
10
9
|
|
|
11
10
|
from wappa.webhooks import IncomingMessageWebhook
|
|
12
11
|
|
|
@@ -19,11 +18,11 @@ from ..utils.metadata_extractor import MetadataExtractor
|
|
|
19
18
|
|
|
20
19
|
class StateHandlers:
|
|
21
20
|
"""Collection of handlers for interactive state management."""
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
def __init__(self, messenger, cache_factory, logger):
|
|
24
23
|
"""
|
|
25
24
|
Initialize state handlers.
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
Args:
|
|
28
27
|
messenger: IMessenger instance for sending messages
|
|
29
28
|
cache_factory: Cache factory for data persistence
|
|
@@ -32,34 +31,37 @@ class StateHandlers:
|
|
|
32
31
|
self.messenger = messenger
|
|
33
32
|
self.cache_helper = CacheHelper(cache_factory)
|
|
34
33
|
self.logger = logger
|
|
35
|
-
|
|
36
|
-
async def handle_button_state_response(
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
|
|
35
|
+
async def handle_button_state_response(
|
|
36
|
+
self,
|
|
37
|
+
webhook: IncomingMessageWebhook,
|
|
38
|
+
user_profile: UserProfile,
|
|
39
|
+
button_state: ButtonState,
|
|
40
|
+
) -> dict[str, any]:
|
|
39
41
|
"""
|
|
40
42
|
Handle response when user is in button state.
|
|
41
|
-
|
|
43
|
+
|
|
42
44
|
Args:
|
|
43
45
|
webhook: IncomingMessageWebhook with user input
|
|
44
46
|
user_profile: User profile for tracking
|
|
45
47
|
button_state: Active button state
|
|
46
|
-
|
|
48
|
+
|
|
47
49
|
Returns:
|
|
48
50
|
Result dictionary with operation status
|
|
49
51
|
"""
|
|
50
52
|
try:
|
|
51
53
|
start_time = time.time()
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
user_id = webhook.user.user_id
|
|
54
56
|
message_id = webhook.message.message_id
|
|
55
57
|
message_type = webhook.get_message_type_name()
|
|
56
|
-
|
|
58
|
+
|
|
57
59
|
self.logger.info(f"🔘 Processing button state response from {user_id}")
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
# Check if this is an interactive button selection
|
|
60
62
|
if message_type == "interactive":
|
|
61
63
|
selection_id = webhook.get_interactive_selection()
|
|
62
|
-
|
|
64
|
+
|
|
63
65
|
if button_state.is_valid_selection(selection_id):
|
|
64
66
|
# Valid button selection - process it
|
|
65
67
|
return await self._process_button_selection(
|
|
@@ -67,61 +69,70 @@ class StateHandlers:
|
|
|
67
69
|
)
|
|
68
70
|
else:
|
|
69
71
|
# Invalid selection
|
|
70
|
-
await self._send_invalid_button_selection_message(
|
|
72
|
+
await self._send_invalid_button_selection_message(
|
|
73
|
+
user_id, message_id, selection_id
|
|
74
|
+
)
|
|
71
75
|
button_state.increment_attempts()
|
|
72
76
|
await self.cache_helper.save_user_state(button_state)
|
|
73
|
-
|
|
77
|
+
|
|
74
78
|
return {
|
|
75
79
|
"success": False,
|
|
76
80
|
"error": "Invalid button selection",
|
|
77
81
|
"selection_id": selection_id,
|
|
78
|
-
"attempts": button_state.attempts
|
|
82
|
+
"attempts": button_state.attempts,
|
|
79
83
|
}
|
|
80
84
|
else:
|
|
81
85
|
# Non-interactive message while in button state - send reminder
|
|
82
|
-
await self._send_button_state_reminder(
|
|
86
|
+
await self._send_button_state_reminder(
|
|
87
|
+
user_id, message_id, button_state
|
|
88
|
+
)
|
|
83
89
|
button_state.increment_attempts()
|
|
84
90
|
await self.cache_helper.save_user_state(button_state)
|
|
85
|
-
|
|
91
|
+
|
|
86
92
|
return {
|
|
87
93
|
"success": False,
|
|
88
94
|
"error": "Expected button selection",
|
|
89
95
|
"message_type": message_type,
|
|
90
96
|
"reminder_sent": True,
|
|
91
|
-
"attempts": button_state.attempts
|
|
97
|
+
"attempts": button_state.attempts,
|
|
92
98
|
}
|
|
93
|
-
|
|
99
|
+
|
|
94
100
|
except Exception as e:
|
|
95
|
-
self.logger.error(
|
|
101
|
+
self.logger.error(
|
|
102
|
+
f"❌ Error handling button state response: {e}", exc_info=True
|
|
103
|
+
)
|
|
96
104
|
return {"success": False, "error": str(e)}
|
|
97
|
-
|
|
98
|
-
async def handle_list_state_response(
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
|
|
106
|
+
async def handle_list_state_response(
|
|
107
|
+
self,
|
|
108
|
+
webhook: IncomingMessageWebhook,
|
|
109
|
+
user_profile: UserProfile,
|
|
110
|
+
list_state: ListState,
|
|
111
|
+
) -> dict[str, any]:
|
|
101
112
|
"""
|
|
102
113
|
Handle response when user is in list state.
|
|
103
|
-
|
|
114
|
+
|
|
104
115
|
Args:
|
|
105
116
|
webhook: IncomingMessageWebhook with user input
|
|
106
117
|
user_profile: User profile for tracking
|
|
107
118
|
list_state: Active list state
|
|
108
|
-
|
|
119
|
+
|
|
109
120
|
Returns:
|
|
110
121
|
Result dictionary with operation status
|
|
111
122
|
"""
|
|
112
123
|
try:
|
|
113
124
|
start_time = time.time()
|
|
114
|
-
|
|
125
|
+
|
|
115
126
|
user_id = webhook.user.user_id
|
|
116
127
|
message_id = webhook.message.message_id
|
|
117
128
|
message_type = webhook.get_message_type_name()
|
|
118
|
-
|
|
129
|
+
|
|
119
130
|
self.logger.info(f"📋 Processing list state response from {user_id}")
|
|
120
|
-
|
|
131
|
+
|
|
121
132
|
# Check if this is an interactive list selection
|
|
122
133
|
if message_type == "interactive":
|
|
123
134
|
selection_id = webhook.get_interactive_selection()
|
|
124
|
-
|
|
135
|
+
|
|
125
136
|
if list_state.is_valid_selection(selection_id):
|
|
126
137
|
# Valid list selection - process it
|
|
127
138
|
return await self._process_list_selection(
|
|
@@ -129,86 +140,90 @@ class StateHandlers:
|
|
|
129
140
|
)
|
|
130
141
|
else:
|
|
131
142
|
# Invalid selection
|
|
132
|
-
await self._send_invalid_list_selection_message(
|
|
143
|
+
await self._send_invalid_list_selection_message(
|
|
144
|
+
user_id, message_id, selection_id
|
|
145
|
+
)
|
|
133
146
|
list_state.increment_attempts()
|
|
134
147
|
await self.cache_helper.save_user_state(list_state)
|
|
135
|
-
|
|
148
|
+
|
|
136
149
|
return {
|
|
137
150
|
"success": False,
|
|
138
151
|
"error": "Invalid list selection",
|
|
139
152
|
"selection_id": selection_id,
|
|
140
|
-
"attempts": list_state.attempts
|
|
153
|
+
"attempts": list_state.attempts,
|
|
141
154
|
}
|
|
142
155
|
else:
|
|
143
156
|
# Non-interactive message while in list state - send reminder
|
|
144
157
|
await self._send_list_state_reminder(user_id, message_id, list_state)
|
|
145
158
|
list_state.increment_attempts()
|
|
146
159
|
await self.cache_helper.save_user_state(list_state)
|
|
147
|
-
|
|
160
|
+
|
|
148
161
|
return {
|
|
149
162
|
"success": False,
|
|
150
163
|
"error": "Expected list selection",
|
|
151
164
|
"message_type": message_type,
|
|
152
165
|
"reminder_sent": True,
|
|
153
|
-
"attempts": list_state.attempts
|
|
166
|
+
"attempts": list_state.attempts,
|
|
154
167
|
}
|
|
155
|
-
|
|
168
|
+
|
|
156
169
|
except Exception as e:
|
|
157
|
-
self.logger.error(
|
|
170
|
+
self.logger.error(
|
|
171
|
+
f"❌ Error handling list state response: {e}", exc_info=True
|
|
172
|
+
)
|
|
158
173
|
return {"success": False, "error": str(e)}
|
|
159
|
-
|
|
160
|
-
async def _process_button_selection(
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
|
|
175
|
+
async def _process_button_selection(
|
|
176
|
+
self,
|
|
177
|
+
webhook: IncomingMessageWebhook,
|
|
178
|
+
user_profile: UserProfile,
|
|
179
|
+
button_state: ButtonState,
|
|
180
|
+
selection_id: str,
|
|
181
|
+
start_time: float,
|
|
182
|
+
) -> dict[str, any]:
|
|
163
183
|
"""Process valid button selection."""
|
|
164
184
|
user_id = webhook.user.user_id
|
|
165
185
|
message_id = webhook.message.message_id
|
|
166
|
-
|
|
186
|
+
|
|
167
187
|
# Handle the selection in the state
|
|
168
188
|
button_state.handle_selection(selection_id)
|
|
169
189
|
selected_button = button_state.get_selected_button()
|
|
170
|
-
|
|
190
|
+
|
|
171
191
|
# Remove state from cache
|
|
172
192
|
await self.cache_helper.remove_user_state(user_id, StateType.BUTTON)
|
|
173
|
-
|
|
193
|
+
|
|
174
194
|
# Extract and format metadata
|
|
175
195
|
metadata = MetadataExtractor.extract_metadata(webhook, start_time)
|
|
176
196
|
metadata_text = MetadataExtractor.format_metadata_for_user(metadata)
|
|
177
|
-
|
|
197
|
+
|
|
178
198
|
# Send metadata response
|
|
179
199
|
await self.messenger.send_text(
|
|
180
|
-
recipient=user_id,
|
|
181
|
-
text=metadata_text,
|
|
182
|
-
reply_to_message_id=message_id
|
|
200
|
+
recipient=user_id, text=metadata_text, reply_to_message_id=message_id
|
|
183
201
|
)
|
|
184
|
-
|
|
202
|
+
|
|
185
203
|
# Send corresponding media file based on selection
|
|
186
204
|
media_sent = False
|
|
187
205
|
media_file = None
|
|
188
|
-
|
|
206
|
+
|
|
189
207
|
if selection_id == "kitty":
|
|
190
208
|
media_file = "kitty.png"
|
|
191
209
|
elif selection_id == "puppy":
|
|
192
210
|
media_file = "puppy.png"
|
|
193
|
-
|
|
211
|
+
|
|
194
212
|
if media_file:
|
|
195
213
|
media_result = await send_local_media_file(
|
|
196
214
|
messenger=self.messenger,
|
|
197
215
|
recipient=user_id,
|
|
198
216
|
filename=media_file,
|
|
199
217
|
media_subdir="buttons",
|
|
200
|
-
caption=f"Here's your {selection_id}! 🎉"
|
|
218
|
+
caption=f"Here's your {selection_id}! 🎉",
|
|
201
219
|
)
|
|
202
220
|
media_sent = media_result["success"]
|
|
203
|
-
|
|
221
|
+
|
|
204
222
|
if not media_sent:
|
|
205
223
|
# Send fallback text if media fails
|
|
206
224
|
fallback_text = f"🎉 You selected: *{selected_button['title']}*\n\n(Media file not found: {media_file})"
|
|
207
|
-
await self.messenger.send_text(
|
|
208
|
-
|
|
209
|
-
text=fallback_text
|
|
210
|
-
)
|
|
211
|
-
|
|
225
|
+
await self.messenger.send_text(recipient=user_id, text=fallback_text)
|
|
226
|
+
|
|
212
227
|
# Send completion message
|
|
213
228
|
completion_text = (
|
|
214
229
|
f"✅ *Button Selection Complete!*\n\n"
|
|
@@ -219,18 +234,17 @@ class StateHandlers:
|
|
|
219
234
|
f"💡 *What happened*: You successfully clicked a button, received metadata, "
|
|
220
235
|
f"and got your chosen animal {'image' if media_sent else '(image failed to load)'}!"
|
|
221
236
|
)
|
|
222
|
-
|
|
223
|
-
await self.messenger.send_text(
|
|
224
|
-
|
|
225
|
-
text=completion_text
|
|
226
|
-
)
|
|
227
|
-
|
|
237
|
+
|
|
238
|
+
await self.messenger.send_text(recipient=user_id, text=completion_text)
|
|
239
|
+
|
|
228
240
|
# Update user activity
|
|
229
|
-
await self.cache_helper.update_user_activity(
|
|
230
|
-
|
|
241
|
+
await self.cache_helper.update_user_activity(
|
|
242
|
+
user_id, "interactive", interaction_type="button"
|
|
243
|
+
)
|
|
244
|
+
|
|
231
245
|
processing_time = int((time.time() - start_time) * 1000)
|
|
232
246
|
self.logger.info(f"✅ Button selection processed in {processing_time}ms")
|
|
233
|
-
|
|
247
|
+
|
|
234
248
|
return {
|
|
235
249
|
"success": True,
|
|
236
250
|
"selection_type": "button",
|
|
@@ -240,39 +254,42 @@ class StateHandlers:
|
|
|
240
254
|
"media_sent": media_sent,
|
|
241
255
|
"media_file": media_file,
|
|
242
256
|
"state_cleaned": True,
|
|
243
|
-
"processing_time_ms": processing_time
|
|
257
|
+
"processing_time_ms": processing_time,
|
|
244
258
|
}
|
|
245
|
-
|
|
246
|
-
async def _process_list_selection(
|
|
247
|
-
|
|
248
|
-
|
|
259
|
+
|
|
260
|
+
async def _process_list_selection(
|
|
261
|
+
self,
|
|
262
|
+
webhook: IncomingMessageWebhook,
|
|
263
|
+
user_profile: UserProfile,
|
|
264
|
+
list_state: ListState,
|
|
265
|
+
selection_id: str,
|
|
266
|
+
start_time: float,
|
|
267
|
+
) -> dict[str, any]:
|
|
249
268
|
"""Process valid list selection."""
|
|
250
269
|
user_id = webhook.user.user_id
|
|
251
270
|
message_id = webhook.message.message_id
|
|
252
|
-
|
|
271
|
+
|
|
253
272
|
# Handle the selection in the state
|
|
254
273
|
list_state.handle_selection(selection_id)
|
|
255
274
|
selected_item = list_state.get_selected_item()
|
|
256
|
-
|
|
275
|
+
|
|
257
276
|
# Remove state from cache
|
|
258
277
|
await self.cache_helper.remove_user_state(user_id, StateType.LIST)
|
|
259
|
-
|
|
278
|
+
|
|
260
279
|
# Extract and format metadata
|
|
261
280
|
metadata = MetadataExtractor.extract_metadata(webhook, start_time)
|
|
262
281
|
metadata_text = MetadataExtractor.format_metadata_for_user(metadata)
|
|
263
|
-
|
|
282
|
+
|
|
264
283
|
# Send metadata response
|
|
265
284
|
await self.messenger.send_text(
|
|
266
|
-
recipient=user_id,
|
|
267
|
-
text=metadata_text,
|
|
268
|
-
reply_to_message_id=message_id
|
|
285
|
+
recipient=user_id, text=metadata_text, reply_to_message_id=message_id
|
|
269
286
|
)
|
|
270
|
-
|
|
287
|
+
|
|
271
288
|
# Send corresponding media file based on selection
|
|
272
289
|
media_sent = False
|
|
273
290
|
media_file = None
|
|
274
291
|
media_type = None
|
|
275
|
-
|
|
292
|
+
|
|
276
293
|
# Map selection to media file
|
|
277
294
|
if selection_id == "image_file":
|
|
278
295
|
media_file = "image.png"
|
|
@@ -286,25 +303,22 @@ class StateHandlers:
|
|
|
286
303
|
elif selection_id == "document_file":
|
|
287
304
|
media_file = "document.pdf"
|
|
288
305
|
media_type = "document"
|
|
289
|
-
|
|
306
|
+
|
|
290
307
|
if media_file and media_type:
|
|
291
308
|
media_result = await send_local_media_file(
|
|
292
309
|
messenger=self.messenger,
|
|
293
310
|
recipient=user_id,
|
|
294
311
|
filename=media_file,
|
|
295
312
|
media_subdir="list",
|
|
296
|
-
caption=f"Here's your {media_type} file! 🎉"
|
|
313
|
+
caption=f"Here's your {media_type} file! 🎉",
|
|
297
314
|
)
|
|
298
315
|
media_sent = media_result["success"]
|
|
299
|
-
|
|
316
|
+
|
|
300
317
|
if not media_sent:
|
|
301
318
|
# Send fallback text if media fails
|
|
302
319
|
fallback_text = f"🎉 You selected: *{selected_item['title']}*\n\n(Media file not found: {media_file})"
|
|
303
|
-
await self.messenger.send_text(
|
|
304
|
-
|
|
305
|
-
text=fallback_text
|
|
306
|
-
)
|
|
307
|
-
|
|
320
|
+
await self.messenger.send_text(recipient=user_id, text=fallback_text)
|
|
321
|
+
|
|
308
322
|
# Send completion message
|
|
309
323
|
completion_text = (
|
|
310
324
|
f"✅ *List Selection Complete!*\n\n"
|
|
@@ -317,18 +331,17 @@ class StateHandlers:
|
|
|
317
331
|
f"💡 *What happened*: You successfully selected from a list, received metadata, "
|
|
318
332
|
f"and got your chosen {media_type} {'file' if media_sent else '(file failed to load)'}!"
|
|
319
333
|
)
|
|
320
|
-
|
|
321
|
-
await self.messenger.send_text(
|
|
322
|
-
|
|
323
|
-
text=completion_text
|
|
324
|
-
)
|
|
325
|
-
|
|
334
|
+
|
|
335
|
+
await self.messenger.send_text(recipient=user_id, text=completion_text)
|
|
336
|
+
|
|
326
337
|
# Update user activity
|
|
327
|
-
await self.cache_helper.update_user_activity(
|
|
328
|
-
|
|
338
|
+
await self.cache_helper.update_user_activity(
|
|
339
|
+
user_id, "interactive", interaction_type="list"
|
|
340
|
+
)
|
|
341
|
+
|
|
329
342
|
processing_time = int((time.time() - start_time) * 1000)
|
|
330
343
|
self.logger.info(f"✅ List selection processed in {processing_time}ms")
|
|
331
|
-
|
|
344
|
+
|
|
332
345
|
return {
|
|
333
346
|
"success": True,
|
|
334
347
|
"selection_type": "list",
|
|
@@ -339,14 +352,15 @@ class StateHandlers:
|
|
|
339
352
|
"media_file": media_file,
|
|
340
353
|
"media_type": media_type,
|
|
341
354
|
"state_cleaned": True,
|
|
342
|
-
"processing_time_ms": processing_time
|
|
355
|
+
"processing_time_ms": processing_time,
|
|
343
356
|
}
|
|
344
|
-
|
|
345
|
-
async def _send_button_state_reminder(
|
|
346
|
-
|
|
357
|
+
|
|
358
|
+
async def _send_button_state_reminder(
|
|
359
|
+
self, user_id: str, message_id: str, button_state: ButtonState
|
|
360
|
+
) -> None:
|
|
347
361
|
"""Send reminder message when user is in button state."""
|
|
348
362
|
time_remaining = button_state.time_remaining_minutes()
|
|
349
|
-
|
|
363
|
+
|
|
350
364
|
reminder_text = (
|
|
351
365
|
f"🔘 *Hey Wappa! We love your enthusiasm, but please press a button!*\n\n"
|
|
352
366
|
f"⚠️ *You're currently in Button Demo mode*\n"
|
|
@@ -355,18 +369,17 @@ class StateHandlers:
|
|
|
355
369
|
f"🔢 *Attempt*: {button_state.attempts + 1}/{button_state.max_attempts}\n\n"
|
|
356
370
|
f"💡 *Tip*: Look for the message with 🐱 Kitty and 🐶 Puppy buttons above!"
|
|
357
371
|
)
|
|
358
|
-
|
|
372
|
+
|
|
359
373
|
await self.messenger.send_text(
|
|
360
|
-
recipient=user_id,
|
|
361
|
-
text=reminder_text,
|
|
362
|
-
reply_to_message_id=message_id
|
|
374
|
+
recipient=user_id, text=reminder_text, reply_to_message_id=message_id
|
|
363
375
|
)
|
|
364
|
-
|
|
365
|
-
async def _send_list_state_reminder(
|
|
366
|
-
|
|
376
|
+
|
|
377
|
+
async def _send_list_state_reminder(
|
|
378
|
+
self, user_id: str, message_id: str, list_state: ListState
|
|
379
|
+
) -> None:
|
|
367
380
|
"""Send reminder message when user is in list state."""
|
|
368
381
|
time_remaining = list_state.time_remaining_minutes()
|
|
369
|
-
|
|
382
|
+
|
|
370
383
|
reminder_text = (
|
|
371
384
|
f"📋 *Hey Wappa! We love your enthusiasm, but please make a list selection!*\n\n"
|
|
372
385
|
f"⚠️ *You're currently in List Demo mode*\n"
|
|
@@ -375,15 +388,14 @@ class StateHandlers:
|
|
|
375
388
|
f"🔢 *Attempt*: {list_state.attempts + 1}/{list_state.max_attempts}\n\n"
|
|
376
389
|
f"💡 *Tip*: Look for the message with the 'Choose Media' button above!"
|
|
377
390
|
)
|
|
378
|
-
|
|
391
|
+
|
|
379
392
|
await self.messenger.send_text(
|
|
380
|
-
recipient=user_id,
|
|
381
|
-
text=reminder_text,
|
|
382
|
-
reply_to_message_id=message_id
|
|
393
|
+
recipient=user_id, text=reminder_text, reply_to_message_id=message_id
|
|
383
394
|
)
|
|
384
|
-
|
|
385
|
-
async def _send_invalid_button_selection_message(
|
|
386
|
-
|
|
395
|
+
|
|
396
|
+
async def _send_invalid_button_selection_message(
|
|
397
|
+
self, user_id: str, message_id: str, selection_id: str
|
|
398
|
+
) -> None:
|
|
387
399
|
"""Send message for invalid button selection."""
|
|
388
400
|
error_text = (
|
|
389
401
|
f"❌ *Invalid Button Selection*\n\n"
|
|
@@ -391,15 +403,14 @@ class StateHandlers:
|
|
|
391
403
|
f"✅ *Valid options*: `kitty`, `puppy`\n\n"
|
|
392
404
|
f"💡 *Please try again*: Click one of the valid buttons above!"
|
|
393
405
|
)
|
|
394
|
-
|
|
406
|
+
|
|
395
407
|
await self.messenger.send_text(
|
|
396
|
-
recipient=user_id,
|
|
397
|
-
text=error_text,
|
|
398
|
-
reply_to_message_id=message_id
|
|
408
|
+
recipient=user_id, text=error_text, reply_to_message_id=message_id
|
|
399
409
|
)
|
|
400
|
-
|
|
401
|
-
async def _send_invalid_list_selection_message(
|
|
402
|
-
|
|
410
|
+
|
|
411
|
+
async def _send_invalid_list_selection_message(
|
|
412
|
+
self, user_id: str, message_id: str, selection_id: str
|
|
413
|
+
) -> None:
|
|
403
414
|
"""Send message for invalid list selection."""
|
|
404
415
|
error_text = (
|
|
405
416
|
f"❌ *Invalid List Selection*\n\n"
|
|
@@ -407,75 +418,84 @@ class StateHandlers:
|
|
|
407
418
|
f"✅ *Valid options*: `image_file`, `video_file`, `audio_file`, `document_file`\n\n"
|
|
408
419
|
f"💡 *Please try again*: Use the list above to make a valid selection!"
|
|
409
420
|
)
|
|
410
|
-
|
|
421
|
+
|
|
411
422
|
await self.messenger.send_text(
|
|
412
|
-
recipient=user_id,
|
|
413
|
-
text=error_text,
|
|
414
|
-
reply_to_message_id=message_id
|
|
423
|
+
recipient=user_id, text=error_text, reply_to_message_id=message_id
|
|
415
424
|
)
|
|
416
425
|
|
|
417
426
|
|
|
418
427
|
# Convenience functions for direct use
|
|
419
|
-
async def handle_user_in_state(
|
|
420
|
-
|
|
428
|
+
async def handle_user_in_state(
|
|
429
|
+
webhook: IncomingMessageWebhook,
|
|
430
|
+
user_profile: UserProfile,
|
|
431
|
+
messenger,
|
|
432
|
+
cache_factory,
|
|
433
|
+
logger,
|
|
434
|
+
) -> dict[str, any]:
|
|
421
435
|
"""
|
|
422
436
|
Handle user response when they are in an active state (convenience function).
|
|
423
|
-
|
|
437
|
+
|
|
424
438
|
Args:
|
|
425
439
|
webhook: IncomingMessageWebhook with user response
|
|
426
440
|
user_profile: User profile for tracking
|
|
427
441
|
messenger: IMessenger instance
|
|
428
442
|
cache_factory: Cache factory
|
|
429
443
|
logger: Logger instance
|
|
430
|
-
|
|
444
|
+
|
|
431
445
|
Returns:
|
|
432
446
|
Result dictionary or None if no active state
|
|
433
447
|
"""
|
|
434
448
|
handlers = StateHandlers(messenger, cache_factory, logger)
|
|
435
449
|
cache_helper = CacheHelper(cache_factory)
|
|
436
450
|
user_id = webhook.user.user_id
|
|
437
|
-
|
|
451
|
+
|
|
438
452
|
# Check for active button state
|
|
439
453
|
button_state = await cache_helper.get_user_state(user_id, StateType.BUTTON)
|
|
440
454
|
if button_state and button_state.is_active():
|
|
441
455
|
# Ensure we have the correct type
|
|
442
456
|
if isinstance(button_state, ButtonState):
|
|
443
|
-
return await handlers.handle_button_state_response(
|
|
457
|
+
return await handlers.handle_button_state_response(
|
|
458
|
+
webhook, user_profile, button_state
|
|
459
|
+
)
|
|
444
460
|
else:
|
|
445
461
|
logger.warning(f"Button state returned wrong type: {type(button_state)}")
|
|
446
|
-
|
|
462
|
+
|
|
447
463
|
# Check for active list state
|
|
448
464
|
list_state = await cache_helper.get_user_state(user_id, StateType.LIST)
|
|
449
465
|
if list_state and list_state.is_active():
|
|
450
466
|
# Ensure we have the correct type
|
|
451
467
|
if isinstance(list_state, ListState):
|
|
452
|
-
return await handlers.handle_list_state_response(
|
|
468
|
+
return await handlers.handle_list_state_response(
|
|
469
|
+
webhook, user_profile, list_state
|
|
470
|
+
)
|
|
453
471
|
else:
|
|
454
472
|
logger.warning(f"List state returned wrong type: {type(list_state)}")
|
|
455
|
-
|
|
473
|
+
|
|
456
474
|
# No active state found
|
|
457
475
|
return None
|
|
458
476
|
|
|
459
477
|
|
|
460
|
-
async def cleanup_expired_user_states(
|
|
478
|
+
async def cleanup_expired_user_states(
|
|
479
|
+
cache_factory, logger, user_id: str = None
|
|
480
|
+
) -> int:
|
|
461
481
|
"""
|
|
462
482
|
Cleanup expired states for a specific user or all users (convenience function).
|
|
463
|
-
|
|
483
|
+
|
|
464
484
|
Args:
|
|
465
485
|
cache_factory: Cache factory
|
|
466
486
|
logger: Logger instance
|
|
467
487
|
user_id: Optional specific user ID to clean up
|
|
468
|
-
|
|
488
|
+
|
|
469
489
|
Returns:
|
|
470
490
|
Number of states cleaned up
|
|
471
491
|
"""
|
|
472
492
|
cache_helper = CacheHelper(cache_factory)
|
|
473
|
-
|
|
493
|
+
|
|
474
494
|
try:
|
|
475
495
|
# This is a simplified cleanup - in a real implementation you would
|
|
476
496
|
# scan Redis keys and clean up expired states
|
|
477
497
|
cleanup_count = 0
|
|
478
|
-
|
|
498
|
+
|
|
479
499
|
if user_id:
|
|
480
500
|
# Clean up specific user's states
|
|
481
501
|
for state_type in [StateType.BUTTON, StateType.LIST]:
|
|
@@ -483,10 +503,12 @@ async def cleanup_expired_user_states(cache_factory, logger, user_id: str = None
|
|
|
483
503
|
if state and state.is_expired():
|
|
484
504
|
await cache_helper.remove_user_state(user_id, state_type)
|
|
485
505
|
cleanup_count += 1
|
|
486
|
-
logger.info(
|
|
487
|
-
|
|
506
|
+
logger.info(
|
|
507
|
+
f"🧹 Cleaned up expired {state_type.value} state for user {user_id}"
|
|
508
|
+
)
|
|
509
|
+
|
|
488
510
|
return cleanup_count
|
|
489
|
-
|
|
511
|
+
|
|
490
512
|
except Exception as e:
|
|
491
513
|
logger.error(f"❌ Error during state cleanup: {e}")
|
|
492
|
-
return 0
|
|
514
|
+
return 0
|