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
|
@@ -7,74 +7,74 @@ This module handles all state-related commands including:
|
|
|
7
7
|
- WAPPA state message processing
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
11
|
+
|
|
10
12
|
from ..models.redis_demo_models import StateHandler
|
|
11
13
|
from ..utils.cache_utils import create_state_key, get_cache_ttl
|
|
12
14
|
from ..utils.message_utils import extract_command_from_message, extract_user_data
|
|
13
|
-
from wappa.webhooks import IncomingMessageWebhook
|
|
14
|
-
|
|
15
15
|
from .score_base import ScoreBase
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class StateCommandsScore(ScoreBase):
|
|
19
19
|
"""
|
|
20
20
|
Handles state-related command processing.
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
Follows Single Responsibility Principle by focusing only
|
|
23
23
|
on state management and command processing.
|
|
24
24
|
"""
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
async def can_handle(self, webhook: IncomingMessageWebhook) -> bool:
|
|
27
27
|
"""
|
|
28
28
|
This score handles /WAPPA, /EXIT commands and messages in WAPPA state.
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
Args:
|
|
31
31
|
webhook: Incoming message webhook
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
Returns:
|
|
34
34
|
True if this is a state command or user is in WAPPA state
|
|
35
35
|
"""
|
|
36
36
|
message_text = webhook.get_message_text()
|
|
37
37
|
if not message_text:
|
|
38
38
|
return False
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
# Check for state commands
|
|
41
41
|
command, _ = extract_command_from_message(message_text.strip())
|
|
42
|
-
if command in [
|
|
42
|
+
if command in ["/WAPPA", "/EXIT"]:
|
|
43
43
|
return True
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
# Check if user is in WAPPA state (need to handle regular messages in state)
|
|
46
46
|
user_data = extract_user_data(webhook)
|
|
47
|
-
user_id = user_data[
|
|
48
|
-
|
|
47
|
+
user_id = user_data["user_id"]
|
|
48
|
+
|
|
49
49
|
try:
|
|
50
50
|
state_key = create_state_key(user_id)
|
|
51
51
|
state = await self.state_cache.get(state_key, models=StateHandler)
|
|
52
52
|
return state is not None and state.is_wappa
|
|
53
53
|
except:
|
|
54
54
|
return False
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
async def process(self, webhook: IncomingMessageWebhook) -> bool:
|
|
57
57
|
"""
|
|
58
58
|
Process state commands and WAPPA state messages.
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
Args:
|
|
61
61
|
webhook: Incoming message webhook
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
Returns:
|
|
64
64
|
True if processing was successful
|
|
65
65
|
"""
|
|
66
66
|
if not await self.validate_dependencies():
|
|
67
67
|
return False
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
try:
|
|
70
70
|
message_text = webhook.get_message_text()
|
|
71
71
|
if not message_text:
|
|
72
72
|
return False
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
command, remaining_text = extract_command_from_message(message_text.strip())
|
|
75
75
|
user_data = extract_user_data(webhook)
|
|
76
|
-
user_id = user_data[
|
|
77
|
-
|
|
76
|
+
user_id = user_data["user_id"]
|
|
77
|
+
|
|
78
78
|
if command == "/WAPPA":
|
|
79
79
|
await self._handle_wappa_activation(webhook, user_id)
|
|
80
80
|
elif command == "/EXIT":
|
|
@@ -82,18 +82,20 @@ class StateCommandsScore(ScoreBase):
|
|
|
82
82
|
else:
|
|
83
83
|
# Handle regular message in WAPPA state
|
|
84
84
|
await self._handle_wappa_state_message(webhook, user_id, message_text)
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
self._record_processing(success=True)
|
|
87
87
|
return True
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
except Exception as e:
|
|
90
90
|
await self._handle_error(e, "state_command_processing")
|
|
91
91
|
return False
|
|
92
|
-
|
|
93
|
-
async def _handle_wappa_activation(
|
|
92
|
+
|
|
93
|
+
async def _handle_wappa_activation(
|
|
94
|
+
self, webhook: IncomingMessageWebhook, user_id: str
|
|
95
|
+
) -> None:
|
|
94
96
|
"""
|
|
95
97
|
Handle /WAPPA command activation.
|
|
96
|
-
|
|
98
|
+
|
|
97
99
|
Args:
|
|
98
100
|
webhook: Incoming message webhook
|
|
99
101
|
user_id: User's phone number ID
|
|
@@ -102,18 +104,17 @@ class StateCommandsScore(ScoreBase):
|
|
|
102
104
|
# Create and save WAPPA state
|
|
103
105
|
state = StateHandler()
|
|
104
106
|
state.activate_wappa()
|
|
105
|
-
|
|
107
|
+
|
|
106
108
|
# Store state with TTL
|
|
107
109
|
state_key = create_state_key(user_id)
|
|
108
|
-
ttl = get_cache_ttl(
|
|
110
|
+
ttl = get_cache_ttl("state")
|
|
109
111
|
await self.state_cache.set(state_key, state, ttl=ttl)
|
|
110
|
-
|
|
112
|
+
|
|
111
113
|
# Mark message as read with typing indicator first
|
|
112
114
|
await self.messenger.mark_as_read(
|
|
113
|
-
message_id=webhook.message.message_id,
|
|
114
|
-
typing=True
|
|
115
|
+
message_id=webhook.message.message_id, typing=True
|
|
115
116
|
)
|
|
116
|
-
|
|
117
|
+
|
|
117
118
|
# Send activation message
|
|
118
119
|
activation_message = (
|
|
119
120
|
"🎉 You are in wappa state, to exit wappa state write /EXIT\n\n"
|
|
@@ -122,50 +123,53 @@ class StateCommandsScore(ScoreBase):
|
|
|
122
123
|
"• Your state is cached in Redis\n"
|
|
123
124
|
"• Write /EXIT to leave WAPPA state"
|
|
124
125
|
)
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
result = await self.messenger.send_text(
|
|
127
128
|
recipient=user_id,
|
|
128
129
|
text=activation_message,
|
|
129
|
-
reply_to_message_id=webhook.message.message_id
|
|
130
|
+
reply_to_message_id=webhook.message.message_id,
|
|
130
131
|
)
|
|
131
|
-
|
|
132
|
+
|
|
132
133
|
if result.success:
|
|
133
134
|
self.logger.info(f"✅ WAPPA state activated for {user_id}")
|
|
134
135
|
else:
|
|
135
|
-
self.logger.error(
|
|
136
|
-
|
|
136
|
+
self.logger.error(
|
|
137
|
+
f"❌ Failed to send WAPPA activation message: {result.error}"
|
|
138
|
+
)
|
|
139
|
+
|
|
137
140
|
except Exception as e:
|
|
138
141
|
self.logger.error(f"Error in WAPPA activation: {e}")
|
|
139
142
|
raise
|
|
140
|
-
|
|
141
|
-
async def _handle_wappa_exit(
|
|
143
|
+
|
|
144
|
+
async def _handle_wappa_exit(
|
|
145
|
+
self, webhook: IncomingMessageWebhook, user_id: str
|
|
146
|
+
) -> None:
|
|
142
147
|
"""
|
|
143
148
|
Handle /EXIT command deactivation.
|
|
144
|
-
|
|
149
|
+
|
|
145
150
|
Args:
|
|
146
151
|
webhook: Incoming message webhook
|
|
147
152
|
user_id: User's phone number ID
|
|
148
153
|
"""
|
|
149
154
|
try:
|
|
150
155
|
state_key = create_state_key(user_id)
|
|
151
|
-
|
|
156
|
+
|
|
152
157
|
# Check if user has WAPPA state
|
|
153
158
|
state = await self.state_cache.get(state_key, models=StateHandler)
|
|
154
|
-
|
|
159
|
+
|
|
155
160
|
if state and state.is_wappa:
|
|
156
161
|
# Calculate session stats
|
|
157
162
|
duration = state.get_state_duration() or 0
|
|
158
163
|
command_count = state.command_count
|
|
159
|
-
|
|
164
|
+
|
|
160
165
|
# Deactivate and delete state
|
|
161
166
|
await self.state_cache.delete(state_key)
|
|
162
|
-
|
|
167
|
+
|
|
163
168
|
# Mark message as read with typing indicator first
|
|
164
169
|
await self.messenger.mark_as_read(
|
|
165
|
-
message_id=webhook.message.message_id,
|
|
166
|
-
typing=True
|
|
170
|
+
message_id=webhook.message.message_id, typing=True
|
|
167
171
|
)
|
|
168
|
-
|
|
172
|
+
|
|
169
173
|
# Send exit message
|
|
170
174
|
exit_message = (
|
|
171
175
|
"👋 You are no longer in wappa state!\n\n"
|
|
@@ -174,34 +178,39 @@ class StateCommandsScore(ScoreBase):
|
|
|
174
178
|
f"• Commands processed: {command_count}\n"
|
|
175
179
|
f"• State cleared from Redis cache"
|
|
176
180
|
)
|
|
177
|
-
|
|
181
|
+
|
|
178
182
|
result = await self.messenger.send_text(
|
|
179
183
|
recipient=user_id,
|
|
180
184
|
text=exit_message,
|
|
181
|
-
reply_to_message_id=webhook.message.message_id
|
|
185
|
+
reply_to_message_id=webhook.message.message_id,
|
|
182
186
|
)
|
|
183
|
-
|
|
187
|
+
|
|
184
188
|
if result.success:
|
|
185
|
-
self.logger.info(
|
|
189
|
+
self.logger.info(
|
|
190
|
+
f"✅ WAPPA state deactivated for {user_id} (duration: {duration}s)"
|
|
191
|
+
)
|
|
186
192
|
else:
|
|
187
|
-
self.logger.error(
|
|
193
|
+
self.logger.error(
|
|
194
|
+
f"❌ Failed to send WAPPA exit message: {result.error}"
|
|
195
|
+
)
|
|
188
196
|
else:
|
|
189
197
|
# No active state found
|
|
190
198
|
await self.messenger.send_text(
|
|
191
199
|
recipient=user_id,
|
|
192
200
|
text="❓ You are not currently in WAPPA state. Send /WAPPA to activate it.",
|
|
193
|
-
reply_to_message_id=webhook.message.message_id
|
|
201
|
+
reply_to_message_id=webhook.message.message_id,
|
|
194
202
|
)
|
|
195
|
-
|
|
203
|
+
|
|
196
204
|
except Exception as e:
|
|
197
205
|
self.logger.error(f"Error in WAPPA exit: {e}")
|
|
198
206
|
raise
|
|
199
|
-
|
|
200
|
-
async def _handle_wappa_state_message(
|
|
201
|
-
|
|
207
|
+
|
|
208
|
+
async def _handle_wappa_state_message(
|
|
209
|
+
self, webhook: IncomingMessageWebhook, user_id: str, message_text: str
|
|
210
|
+
) -> None:
|
|
202
211
|
"""
|
|
203
212
|
Handle regular messages when user is in WAPPA state.
|
|
204
|
-
|
|
213
|
+
|
|
205
214
|
Args:
|
|
206
215
|
webhook: Incoming message webhook
|
|
207
216
|
user_id: User's phone number ID
|
|
@@ -210,44 +219,47 @@ class StateCommandsScore(ScoreBase):
|
|
|
210
219
|
try:
|
|
211
220
|
state_key = create_state_key(user_id)
|
|
212
221
|
state = await self.state_cache.get(state_key, models=StateHandler)
|
|
213
|
-
|
|
222
|
+
|
|
214
223
|
if state and state.is_wappa:
|
|
215
224
|
# Update state with command processing
|
|
216
225
|
state.process_command(message_text)
|
|
217
|
-
|
|
226
|
+
|
|
218
227
|
# Save updated state
|
|
219
|
-
ttl = get_cache_ttl(
|
|
228
|
+
ttl = get_cache_ttl("state")
|
|
220
229
|
await self.state_cache.set(state_key, state, ttl=ttl)
|
|
221
|
-
|
|
230
|
+
|
|
222
231
|
# Mark message as read with typing indicator first
|
|
223
232
|
await self.messenger.mark_as_read(
|
|
224
|
-
message_id=webhook.message.message_id,
|
|
225
|
-
typing=True
|
|
233
|
+
message_id=webhook.message.message_id, typing=True
|
|
226
234
|
)
|
|
227
|
-
|
|
235
|
+
|
|
228
236
|
# Send WAPPA response
|
|
229
237
|
result = await self.messenger.send_text(
|
|
230
238
|
recipient=user_id,
|
|
231
239
|
text="Hola Wapp@ ;)",
|
|
232
|
-
reply_to_message_id=webhook.message.message_id
|
|
240
|
+
reply_to_message_id=webhook.message.message_id,
|
|
233
241
|
)
|
|
234
|
-
|
|
242
|
+
|
|
235
243
|
if result.success:
|
|
236
|
-
self.logger.info(
|
|
244
|
+
self.logger.info(
|
|
245
|
+
f"✅ WAPPA response sent to {user_id} (command #{state.command_count})"
|
|
246
|
+
)
|
|
237
247
|
else:
|
|
238
|
-
self.logger.error(
|
|
239
|
-
|
|
248
|
+
self.logger.error(
|
|
249
|
+
f"❌ Failed to send WAPPA response: {result.error}"
|
|
250
|
+
)
|
|
251
|
+
|
|
240
252
|
except Exception as e:
|
|
241
253
|
self.logger.error(f"Error handling WAPPA state message: {e}")
|
|
242
254
|
raise
|
|
243
|
-
|
|
255
|
+
|
|
244
256
|
async def is_user_in_wappa_state(self, user_id: str) -> bool:
|
|
245
257
|
"""
|
|
246
258
|
Check if user is currently in WAPPA state (for other score modules).
|
|
247
|
-
|
|
259
|
+
|
|
248
260
|
Args:
|
|
249
261
|
user_id: User's phone number ID
|
|
250
|
-
|
|
262
|
+
|
|
251
263
|
Returns:
|
|
252
264
|
True if user is in WAPPA state
|
|
253
265
|
"""
|
|
@@ -257,4 +269,4 @@ class StateCommandsScore(ScoreBase):
|
|
|
257
269
|
return state is not None and state.is_wappa
|
|
258
270
|
except Exception as e:
|
|
259
271
|
self.logger.error(f"Error checking WAPPA state for {user_id}: {e}")
|
|
260
|
-
return False
|
|
272
|
+
return False
|
|
@@ -7,64 +7,64 @@ This module handles all user-related operations including:
|
|
|
7
7
|
- User activity tracking
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
11
|
+
|
|
10
12
|
from ..models.redis_demo_models import User
|
|
11
13
|
from ..utils.cache_utils import create_user_profile_key, get_cache_ttl
|
|
12
14
|
from ..utils.message_utils import extract_user_data
|
|
13
|
-
from wappa.webhooks import IncomingMessageWebhook
|
|
14
|
-
|
|
15
15
|
from .score_base import ScoreBase
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class UserManagementScore(ScoreBase):
|
|
19
19
|
"""
|
|
20
20
|
Handles user profile management and caching operations.
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
Follows Single Responsibility Principle by focusing only
|
|
23
23
|
on user-related business logic.
|
|
24
24
|
"""
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
async def can_handle(self, webhook: IncomingMessageWebhook) -> bool:
|
|
27
27
|
"""
|
|
28
28
|
This score handles all webhooks since every message needs user management.
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
Args:
|
|
31
31
|
webhook: Incoming message webhook
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
Returns:
|
|
34
34
|
Always True since user management is needed for all messages
|
|
35
35
|
"""
|
|
36
36
|
return True
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
async def process(self, webhook: IncomingMessageWebhook) -> bool:
|
|
39
39
|
"""
|
|
40
40
|
Process user data extraction and caching.
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
Args:
|
|
43
43
|
webhook: Incoming message webhook
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
Returns:
|
|
46
46
|
True if user processing was successful
|
|
47
47
|
"""
|
|
48
48
|
if not await self.validate_dependencies():
|
|
49
49
|
return False
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
try:
|
|
52
52
|
user_data = extract_user_data(webhook)
|
|
53
|
-
user_id = user_data[
|
|
54
|
-
user_name = user_data[
|
|
55
|
-
|
|
53
|
+
user_id = user_data["user_id"]
|
|
54
|
+
user_name = user_data["user_name"]
|
|
55
|
+
|
|
56
56
|
# Get or create user profile
|
|
57
57
|
user = await self._get_or_create_user(user_id, user_name)
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
if user:
|
|
60
60
|
# Update user activity
|
|
61
61
|
await self._update_user_activity(user, user_id)
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
# Send welcome/acknowledgment message for regular messages
|
|
64
64
|
await self._send_welcome_message(webhook, user, user_id)
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
self._record_processing(success=True)
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
self.logger.info(
|
|
69
69
|
f"👤 User profile updated: {user_id} "
|
|
70
70
|
f"(messages: {user.message_count}, name: {user.user_name})"
|
|
@@ -73,35 +73,39 @@ class UserManagementScore(ScoreBase):
|
|
|
73
73
|
else:
|
|
74
74
|
self._record_processing(success=False)
|
|
75
75
|
return False
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
except Exception as e:
|
|
78
78
|
await self._handle_error(e, "user_management_processing")
|
|
79
79
|
return False
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
async def _get_or_create_user(self, user_id: str, user_name: str) -> User:
|
|
82
82
|
"""
|
|
83
83
|
Get existing user or create new one.
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
Args:
|
|
86
86
|
user_id: User's phone number ID
|
|
87
87
|
user_name: User's display name
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
Returns:
|
|
90
90
|
User profile instance
|
|
91
91
|
"""
|
|
92
92
|
try:
|
|
93
93
|
# Generate cache key using utility function
|
|
94
94
|
cache_key = create_user_profile_key(user_id)
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
# Try to get existing user with BaseModel deserialization
|
|
97
97
|
user = await self.user_cache.get(cache_key, models=User)
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
if user:
|
|
100
100
|
# User exists, update name if provided and different
|
|
101
|
-
if
|
|
101
|
+
if (
|
|
102
|
+
user_name
|
|
103
|
+
and user_name != "Unknown User"
|
|
104
|
+
and user.user_name != user_name
|
|
105
|
+
):
|
|
102
106
|
user.user_name = user_name
|
|
103
107
|
self.logger.debug(f"Updated user name: {user_id} -> {user_name}")
|
|
104
|
-
|
|
108
|
+
|
|
105
109
|
self.logger.debug(f"👤 User cache HIT: {user_id}")
|
|
106
110
|
return user
|
|
107
111
|
else:
|
|
@@ -109,20 +113,22 @@ class UserManagementScore(ScoreBase):
|
|
|
109
113
|
user = User(
|
|
110
114
|
phone_number=user_id,
|
|
111
115
|
user_name=user_name if user_name != "Unknown User" else None,
|
|
112
|
-
message_count=0 # Will be incremented by increment_message_count()
|
|
116
|
+
message_count=0, # Will be incremented by increment_message_count()
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self.logger.info(
|
|
120
|
+
f"👤 User cache MISS: Creating new profile for {user_id}"
|
|
113
121
|
)
|
|
114
|
-
|
|
115
|
-
self.logger.info(f"👤 User cache MISS: Creating new profile for {user_id}")
|
|
116
122
|
return user
|
|
117
|
-
|
|
123
|
+
|
|
118
124
|
except Exception as e:
|
|
119
125
|
self.logger.error(f"Error getting/creating user {user_id}: {e}")
|
|
120
126
|
raise
|
|
121
|
-
|
|
127
|
+
|
|
122
128
|
async def _update_user_activity(self, user: User, user_id: str) -> None:
|
|
123
129
|
"""
|
|
124
130
|
Update user activity and save to cache.
|
|
125
|
-
|
|
131
|
+
|
|
126
132
|
Args:
|
|
127
133
|
user: User profile to update
|
|
128
134
|
user_id: User's phone number ID
|
|
@@ -130,26 +136,28 @@ class UserManagementScore(ScoreBase):
|
|
|
130
136
|
try:
|
|
131
137
|
# Update user activity
|
|
132
138
|
user.increment_message_count()
|
|
133
|
-
|
|
139
|
+
|
|
134
140
|
# Save updated user data with TTL
|
|
135
141
|
cache_key = create_user_profile_key(user_id)
|
|
136
|
-
ttl = get_cache_ttl(
|
|
137
|
-
|
|
142
|
+
ttl = get_cache_ttl("user")
|
|
143
|
+
|
|
138
144
|
await self.user_cache.set(cache_key, user, ttl=ttl)
|
|
139
|
-
|
|
140
|
-
self.logger.debug(
|
|
141
|
-
|
|
145
|
+
|
|
146
|
+
self.logger.debug(
|
|
147
|
+
f"User activity updated: {user_id} (count: {user.message_count})"
|
|
148
|
+
)
|
|
149
|
+
|
|
142
150
|
except Exception as e:
|
|
143
151
|
self.logger.error(f"Error updating user activity {user_id}: {e}")
|
|
144
152
|
raise
|
|
145
|
-
|
|
153
|
+
|
|
146
154
|
async def get_user_profile(self, user_id: str) -> User:
|
|
147
155
|
"""
|
|
148
156
|
Get user profile for other score modules.
|
|
149
|
-
|
|
157
|
+
|
|
150
158
|
Args:
|
|
151
159
|
user_id: User's phone number ID
|
|
152
|
-
|
|
160
|
+
|
|
153
161
|
Returns:
|
|
154
162
|
User profile or None if not found
|
|
155
163
|
"""
|
|
@@ -159,11 +167,13 @@ class UserManagementScore(ScoreBase):
|
|
|
159
167
|
except Exception as e:
|
|
160
168
|
self.logger.error(f"Error getting user profile {user_id}: {e}")
|
|
161
169
|
return None
|
|
162
|
-
|
|
163
|
-
async def _send_welcome_message(
|
|
170
|
+
|
|
171
|
+
async def _send_welcome_message(
|
|
172
|
+
self, webhook: IncomingMessageWebhook, user: User, user_id: str
|
|
173
|
+
) -> None:
|
|
164
174
|
"""
|
|
165
175
|
Send welcome/acknowledgment message based on user's message count.
|
|
166
|
-
|
|
176
|
+
|
|
167
177
|
Args:
|
|
168
178
|
webhook: Incoming message webhook
|
|
169
179
|
user: User profile data
|
|
@@ -171,22 +181,26 @@ class UserManagementScore(ScoreBase):
|
|
|
171
181
|
"""
|
|
172
182
|
try:
|
|
173
183
|
message_text = webhook.get_message_text() or ""
|
|
174
|
-
|
|
184
|
+
|
|
175
185
|
# Don't send welcome for commands (let other scores handle them)
|
|
176
|
-
if message_text.strip().startswith(
|
|
186
|
+
if message_text.strip().startswith("/"):
|
|
177
187
|
return
|
|
178
|
-
|
|
188
|
+
|
|
179
189
|
# Step 1: Mark message as read with typing indicator
|
|
180
190
|
read_result = await self.messenger.mark_as_read(
|
|
181
191
|
message_id=webhook.message.message_id,
|
|
182
|
-
typing=True # Show typing indicator
|
|
192
|
+
typing=True, # Show typing indicator
|
|
183
193
|
)
|
|
184
|
-
|
|
194
|
+
|
|
185
195
|
if not read_result.success:
|
|
186
|
-
self.logger.warning(
|
|
196
|
+
self.logger.warning(
|
|
197
|
+
f"⚠️ Failed to mark message as read: {read_result.error}"
|
|
198
|
+
)
|
|
187
199
|
else:
|
|
188
|
-
self.logger.debug(
|
|
189
|
-
|
|
200
|
+
self.logger.debug(
|
|
201
|
+
f"✅ Message marked as read with typing indicator: {webhook.message.message_id}"
|
|
202
|
+
)
|
|
203
|
+
|
|
190
204
|
# Create personalized welcome message based on message count
|
|
191
205
|
if user.message_count == 1:
|
|
192
206
|
# First message - welcome
|
|
@@ -206,18 +220,20 @@ class UserManagementScore(ScoreBase):
|
|
|
206
220
|
f"📝 Total messages: {user.message_count}\n"
|
|
207
221
|
f"💾 Profile cached in Redis"
|
|
208
222
|
)
|
|
209
|
-
|
|
223
|
+
|
|
210
224
|
# Step 2: Send welcome/acknowledgment message
|
|
211
225
|
result = await self.messenger.send_text(
|
|
212
226
|
recipient=user_id,
|
|
213
227
|
text=welcome_text,
|
|
214
|
-
reply_to_message_id=webhook.message.message_id
|
|
228
|
+
reply_to_message_id=webhook.message.message_id,
|
|
215
229
|
)
|
|
216
|
-
|
|
230
|
+
|
|
217
231
|
if result.success:
|
|
218
|
-
self.logger.info(
|
|
232
|
+
self.logger.info(
|
|
233
|
+
f"✅ Welcome message sent to {user_id} (marked as read + typing)"
|
|
234
|
+
)
|
|
219
235
|
else:
|
|
220
236
|
self.logger.error(f"❌ Failed to send welcome message: {result.error}")
|
|
221
|
-
|
|
237
|
+
|
|
222
238
|
except Exception as e:
|
|
223
|
-
self.logger.error(f"Error sending welcome message: {e}")
|
|
239
|
+
self.logger.error(f"Error sending welcome message: {e}")
|