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
@@ -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.json_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 ['/WAPPA', '/EXIT']:
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['user_id']
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['user_id']
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(self, webhook: IncomingMessageWebhook, user_id: str) -> None:
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('state')
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 JSON files\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(f"❌ Failed to send WAPPA activation message: {result.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(self, webhook: IncomingMessageWebhook, user_id: str) -> None:
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 JSON 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(f"✅ WAPPA state deactivated for {user_id} (duration: {duration}s)")
189
+ self.logger.info(
190
+ f"✅ WAPPA state deactivated for {user_id} (duration: {duration}s)"
191
+ )
186
192
  else:
187
- self.logger.error(f"❌ Failed to send WAPPA exit message: {result.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(self, webhook: IncomingMessageWebhook,
201
- user_id: str, message_text: str) -> None:
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('state')
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(f"✅ WAPPA response sent to {user_id} (command #{state.command_count})")
244
+ self.logger.info(
245
+ f"✅ WAPPA response sent to {user_id} (command #{state.command_count})"
246
+ )
237
247
  else:
238
- self.logger.error(f"❌ Failed to send WAPPA response: {result.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.json_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['user_id']
54
- user_name = user_data['user_name']
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 user_name and user_name != "Unknown User" and user.user_name != user_name:
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('user')
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(f"User activity updated: {user_id} (count: {user.message_count})")
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(self, webhook: IncomingMessageWebhook, user: User, user_id: str) -> None:
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(f"⚠️ Failed to mark message as read: {read_result.error}")
196
+ self.logger.warning(
197
+ f"⚠️ Failed to mark message as read: {read_result.error}"
198
+ )
187
199
  else:
188
- self.logger.debug(f"✅ Message marked as read with typing indicator: {webhook.message.message_id}")
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 JSON files"
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(f"✅ Welcome message sent to {user_id} (marked as read + typing)")
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}")
@@ -18,9 +18,9 @@ from .message_utils import (
18
18
 
19
19
  __all__ = [
20
20
  "generate_cache_key",
21
- "get_cache_ttl",
21
+ "get_cache_ttl",
22
22
  "validate_cache_key",
23
23
  "extract_user_data",
24
24
  "format_timestamp",
25
25
  "sanitize_message_text",
26
- ]
26
+ ]