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
@@ -8,104 +8,102 @@ This module handles all cache statistics operations including:
8
8
  - Cache health monitoring
9
9
  """
10
10
 
11
- from ..models.redis_demo_models import CacheStats
12
- from ..utils.cache_utils import get_cache_ttl
13
- from ..utils.message_utils import extract_command_from_message, extract_user_data
14
11
  from wappa.webhooks import IncomingMessageWebhook
15
12
 
13
+ from ..models.redis_demo_models import CacheStats
14
+ from ..utils.message_utils import extract_command_from_message, extract_user_data
16
15
  from .score_base import ScoreBase
17
16
 
18
17
 
19
18
  class CacheStatisticsScore(ScoreBase):
20
19
  """
21
20
  Handles cache statistics monitoring and reporting operations.
22
-
21
+
23
22
  Follows Single Responsibility Principle by focusing only
24
23
  on cache performance monitoring and statistics.
25
24
  """
26
-
25
+
27
26
  async def can_handle(self, webhook: IncomingMessageWebhook) -> bool:
28
27
  """
29
28
  This score handles /STATS command specifically.
30
-
29
+
31
30
  Args:
32
31
  webhook: Incoming message webhook
33
-
32
+
34
33
  Returns:
35
34
  True if this is a /STATS command
36
35
  """
37
36
  message_text = webhook.get_message_text()
38
37
  if not message_text:
39
38
  return False
40
-
39
+
41
40
  command, _ = extract_command_from_message(message_text.strip())
42
41
  return command == "/STATS"
43
-
42
+
44
43
  async def process(self, webhook: IncomingMessageWebhook) -> bool:
45
44
  """
46
45
  Process cache statistics request.
47
-
46
+
48
47
  Args:
49
48
  webhook: Incoming message webhook
50
-
49
+
51
50
  Returns:
52
51
  True if processing was successful
53
52
  """
54
53
  if not await self.validate_dependencies():
55
54
  return False
56
-
55
+
57
56
  try:
58
57
  await self._handle_stats_request(webhook)
59
58
  self._record_processing(success=True)
60
59
  return True
61
-
60
+
62
61
  except Exception as e:
63
62
  await self._handle_error(e, "cache_statistics_processing")
64
63
  return False
65
-
64
+
66
65
  async def _handle_stats_request(self, webhook: IncomingMessageWebhook) -> None:
67
66
  """
68
67
  Handle /STATS command to show cache statistics.
69
-
68
+
70
69
  Args:
71
70
  webhook: Incoming message webhook
72
71
  """
73
72
  try:
74
73
  user_data = extract_user_data(webhook)
75
- user_id = user_data['user_id']
76
-
74
+ user_id = user_data["user_id"]
75
+
77
76
  # Collect statistics from all cache layers
78
77
  stats = await self._collect_cache_statistics()
79
-
78
+
80
79
  # Generate statistics report
81
80
  stats_message = self._format_statistics_message(stats)
82
-
81
+
83
82
  # Mark message as read with typing indicator first
84
83
  await self.messenger.mark_as_read(
85
- message_id=webhook.message.message_id,
86
- typing=True
84
+ message_id=webhook.message.message_id, typing=True
87
85
  )
88
-
86
+
89
87
  # Send statistics to user
90
88
  result = await self.messenger.send_text(
91
89
  recipient=user_id,
92
90
  text=stats_message,
93
- reply_to_message_id=webhook.message.message_id
91
+ reply_to_message_id=webhook.message.message_id,
94
92
  )
95
-
93
+
96
94
  if result.success:
97
95
  self.logger.info(f"✅ Cache statistics sent to {user_id}")
98
96
  else:
99
97
  self.logger.error(f"❌ Failed to send statistics: {result.error}")
100
-
98
+
101
99
  except Exception as e:
102
100
  self.logger.error(f"Error handling stats request: {e}")
103
101
  raise
104
-
102
+
105
103
  async def _collect_cache_statistics(self) -> CacheStats:
106
104
  """
107
105
  Get or create cache statistics from table cache (persistent storage).
108
-
106
+
109
107
  Returns:
110
108
  CacheStats object from table cache or newly created
111
109
  """
@@ -113,57 +111,70 @@ class CacheStatisticsScore(ScoreBase):
113
111
  # Use table cache to store/retrieve statistics (proper table key format)
114
112
  # table_name:pkid format as required by table cache
115
113
  stats_key = self.table_cache.create_table_key("cache_statistics", "global")
116
-
114
+
117
115
  # Try to get existing stats from table cache
118
116
  existing_stats = await self.table_cache.get(stats_key, models=CacheStats)
119
-
117
+
120
118
  if existing_stats:
121
119
  # Update existing stats
122
120
  stats = existing_stats
123
121
  stats.total_operations += 1 # Increment operation count
124
122
  stats.last_updated = stats.last_updated # Will auto-update via Pydantic
125
- self.logger.debug("📊 Retrieved existing cache statistics from table cache")
123
+ self.logger.debug(
124
+ "📊 Retrieved existing cache statistics from table cache"
125
+ )
126
126
  else:
127
127
  # Create new stats
128
128
  stats = CacheStats()
129
129
  stats.cache_type = "Redis"
130
130
  stats.total_operations = 1
131
131
  self.logger.info("📊 Created new cache statistics in table cache")
132
-
132
+
133
133
  # Test cache connectivity using a simple hash operation
134
134
  try:
135
135
  # Test connectivity by creating a temporary hash (proper Redis usage)
136
- test_data = {"test": "connectivity_check", "timestamp": str(stats.last_updated)}
136
+ test_data = {
137
+ "test": "connectivity_check",
138
+ "timestamp": str(stats.last_updated),
139
+ }
137
140
  test_key = "connectivity_test"
138
-
141
+
139
142
  # Store test hash in user_cache (proper hash usage)
140
143
  await self.user_cache.set(test_key, test_data, ttl=5)
141
144
  test_result = await self.user_cache.get(test_key)
142
-
143
- if test_result and isinstance(test_result, dict) and test_result.get("test") == "connectivity_check":
145
+
146
+ if (
147
+ test_result
148
+ and isinstance(test_result, dict)
149
+ and test_result.get("test") == "connectivity_check"
150
+ ):
144
151
  stats.connection_status = "Connected"
145
152
  stats.is_healthy = True
146
153
  # Clean up test key
147
154
  await self.user_cache.delete(test_key)
148
155
  self.logger.debug("✅ Cache connectivity test passed")
149
156
  else:
150
- stats.connection_status = "Connection Issues"
157
+ stats.connection_status = "Connection Issues"
151
158
  stats.is_healthy = False
152
159
  stats.errors += 1
153
- self.logger.warning(f"⚠️ Cache connectivity test failed: got {test_result}")
154
-
160
+ self.logger.warning(
161
+ f"⚠️ Cache connectivity test failed: got {test_result}"
162
+ )
163
+
155
164
  except Exception as cache_error:
156
165
  self.logger.error(f"Cache connectivity test failed: {cache_error}")
157
166
  stats.connection_status = f"Error: {str(cache_error)}"
158
167
  stats.is_healthy = False
159
168
  stats.errors += 1
160
-
169
+
161
170
  # Store updated statistics in table cache (persistent like message history)
162
171
  await self.table_cache.set(stats_key, stats)
163
-
164
- self.logger.info(f"📊 Cache statistics updated in table cache: {stats.connection_status}")
172
+
173
+ self.logger.info(
174
+ f"📊 Cache statistics updated in table cache: {stats.connection_status}"
175
+ )
165
176
  return stats
166
-
177
+
167
178
  except Exception as e:
168
179
  self.logger.error(f"Error collecting cache statistics: {e}")
169
180
  # Return minimal error stats (don't store errors)
@@ -173,50 +184,47 @@ class CacheStatisticsScore(ScoreBase):
173
184
  error_stats.is_healthy = False
174
185
  error_stats.errors = 1
175
186
  return error_stats
176
-
187
+
177
188
  def _format_statistics_message(self, stats: CacheStats) -> str:
178
189
  """
179
190
  Format cache statistics for user display.
180
-
191
+
181
192
  Args:
182
193
  stats: CacheStats object with collected metrics
183
-
194
+
184
195
  Returns:
185
196
  Formatted statistics message
186
197
  """
187
198
  try:
188
199
  health_icon = "🟢" if stats.is_healthy else "🔴"
189
-
200
+
190
201
  message = [
191
- f"📊 *Cache Statistics Report*\n",
202
+ "📊 *Cache Statistics Report*\n",
192
203
  f"{health_icon} *Cache Status*: {stats.connection_status}\n",
193
204
  f"⚙️ *Cache Type*: {stats.cache_type}\n",
194
205
  f"📈 *Total Operations*: {stats.total_operations}\n",
195
206
  f"❌ *Errors*: {stats.errors}\n",
196
207
  f"🕐 *Last Updated*: {stats.last_updated.strftime('%H:%M:%S')}\n\n",
197
-
198
208
  # Cache metrics
199
209
  "*Cache Metrics:*\n",
200
210
  f"• User Cache Hits: {stats.user_cache_hits}\n",
201
211
  f"• User Cache Misses: {stats.user_cache_misses}\n",
202
212
  f"• Table Entries: {stats.table_cache_entries}\n",
203
213
  f"• Active States: {stats.state_cache_active}\n\n",
204
-
205
214
  # Performance indicators
206
215
  "*Performance:*\n",
207
216
  f"• Health: {'🟢 Healthy' if stats.is_healthy else '🔴 Unhealthy'}\n",
208
217
  f"• Connection: {stats.connection_status}\n\n",
209
-
210
218
  # Tips for users
211
219
  "*Available Commands:*\n",
212
220
  "• `/HISTORY` - View message history\n",
213
221
  "• `/WAPPA` - Enter WAPPA state\n",
214
222
  "• `/EXIT` - Exit WAPPA state\n",
215
- "• `/STATS` - View these statistics"
223
+ "• `/STATS` - View these statistics",
216
224
  ]
217
-
225
+
218
226
  return "".join(message)
219
-
227
+
220
228
  except Exception as e:
221
229
  self.logger.error(f"Error formatting statistics message: {e}")
222
230
  return (
@@ -224,25 +232,25 @@ class CacheStatisticsScore(ScoreBase):
224
232
  f"❌ Error formatting statistics: {str(e)}\n\n"
225
233
  "Please try again or contact support if the issue persists."
226
234
  )
227
-
235
+
228
236
  async def get_cache_health(self) -> dict:
229
237
  """
230
238
  Get cache health status (for other score modules).
231
-
239
+
232
240
  Returns:
233
241
  Dictionary with cache health information
234
242
  """
235
243
  try:
236
244
  stats = await self._collect_cache_statistics()
237
245
  return {
238
- 'healthy': stats.is_healthy,
239
- 'status': stats.connection_status,
240
- 'cache_type': stats.cache_type
246
+ "healthy": stats.is_healthy,
247
+ "status": stats.connection_status,
248
+ "cache_type": stats.cache_type,
241
249
  }
242
250
  except Exception as e:
243
251
  self.logger.error(f"Error getting cache health: {e}")
244
252
  return {
245
- 'healthy': False,
246
- 'status': f'Health Check Error: {str(e)}',
247
- 'cache_type': 'Unknown'
248
- }
253
+ "healthy": False,
254
+ "status": f"Health Check Error: {str(e)}",
255
+ "cache_type": "Unknown",
256
+ }
@@ -7,6 +7,8 @@ This module handles all message history operations including:
7
7
  - /HISTORY command processing
8
8
  """
9
9
 
10
+ from wappa.webhooks import IncomingMessageWebhook
11
+
10
12
  from ..models.redis_demo_models import MessageLog
11
13
  from ..utils.cache_utils import create_message_history_key, get_cache_ttl
12
14
  from ..utils.message_utils import (
@@ -15,83 +17,81 @@ from ..utils.message_utils import (
15
17
  format_message_history_display,
16
18
  sanitize_message_text,
17
19
  )
18
- from wappa.webhooks import IncomingMessageWebhook
19
-
20
20
  from .score_base import ScoreBase
21
21
 
22
22
 
23
23
  class MessageHistoryScore(ScoreBase):
24
24
  """
25
25
  Handles message history logging and retrieval operations.
26
-
26
+
27
27
  Follows Single Responsibility Principle by focusing only
28
28
  on message history management.
29
29
  """
30
-
30
+
31
31
  async def can_handle(self, webhook: IncomingMessageWebhook) -> bool:
32
32
  """
33
33
  This score handles all messages for logging, plus /HISTORY command.
34
-
34
+
35
35
  Args:
36
36
  webhook: Incoming message webhook
37
-
37
+
38
38
  Returns:
39
39
  Always True since all messages should be logged
40
40
  """
41
41
  return True
42
-
42
+
43
43
  async def process(self, webhook: IncomingMessageWebhook) -> bool:
44
44
  """
45
45
  Process message logging and handle /HISTORY command.
46
-
46
+
47
47
  Args:
48
48
  webhook: Incoming message webhook
49
-
49
+
50
50
  Returns:
51
51
  True if processing was successful
52
52
  """
53
53
  if not await self.validate_dependencies():
54
54
  return False
55
-
55
+
56
56
  try:
57
57
  # Always log the message first
58
58
  await self._log_message(webhook)
59
-
59
+
60
60
  # Check if this is a /HISTORY command
61
61
  message_text = webhook.get_message_text()
62
62
  if message_text:
63
63
  command, _ = extract_command_from_message(message_text.strip())
64
64
  if command == "/HISTORY":
65
65
  await self._handle_history_request(webhook)
66
-
66
+
67
67
  self._record_processing(success=True)
68
68
  return True
69
-
69
+
70
70
  except Exception as e:
71
71
  await self._handle_error(e, "message_history_processing")
72
72
  return False
73
-
73
+
74
74
  async def _log_message(self, webhook: IncomingMessageWebhook) -> None:
75
75
  """
76
76
  Log message to user's message history.
77
-
77
+
78
78
  Args:
79
79
  webhook: Incoming message webhook
80
80
  """
81
81
  try:
82
82
  user_data = extract_user_data(webhook)
83
- user_id = user_data['user_id']
84
- tenant_id = user_data['tenant_id']
85
-
83
+ user_id = user_data["user_id"]
84
+ tenant_id = user_data["tenant_id"]
85
+
86
86
  message_text = webhook.get_message_text()
87
87
  message_type = webhook.get_message_type_name()
88
-
88
+
89
89
  # Generate cache key using utility function
90
90
  log_key = create_message_history_key(user_id)
91
-
91
+
92
92
  # Try to get existing message history
93
93
  message_log = await self.table_cache.get(log_key, models=MessageLog)
94
-
94
+
95
95
  if message_log:
96
96
  self.logger.debug(
97
97
  f"📝 Found existing history for {user_id} "
@@ -99,51 +99,48 @@ class MessageHistoryScore(ScoreBase):
99
99
  )
100
100
  else:
101
101
  # Create new message history
102
- message_log = MessageLog(
103
- user_id=user_id,
104
- tenant_id=tenant_id
105
- )
102
+ message_log = MessageLog(user_id=user_id, tenant_id=tenant_id)
106
103
  self.logger.info(f"📝 Creating new message history for {user_id}")
107
-
104
+
108
105
  # Add the new message to history
109
106
  message_content = sanitize_message_text(
110
107
  message_text or f"[{message_type.upper()} MESSAGE]"
111
108
  )
112
109
  message_log.add_message(message_content, message_type)
113
-
110
+
114
111
  # Store back to Redis with TTL
115
- ttl = get_cache_ttl('message')
112
+ ttl = get_cache_ttl("message")
116
113
  await self.table_cache.set(log_key, message_log, ttl=ttl)
117
-
114
+
118
115
  self.logger.info(
119
116
  f"📝 Message logged: {user_id} "
120
117
  f"(total: {message_log.get_message_count()} messages)"
121
118
  )
122
-
119
+
123
120
  except Exception as e:
124
121
  self.logger.error(f"Error logging message: {e}")
125
122
  raise
126
-
123
+
127
124
  async def _handle_history_request(self, webhook: IncomingMessageWebhook) -> None:
128
125
  """
129
126
  Handle /HISTORY command to show user's message history.
130
-
127
+
131
128
  Args:
132
129
  webhook: Incoming message webhook
133
130
  """
134
131
  try:
135
132
  user_data = extract_user_data(webhook)
136
- user_id = user_data['user_id']
137
-
133
+ user_id = user_data["user_id"]
134
+
138
135
  # Get user's message history
139
136
  log_key = create_message_history_key(user_id)
140
137
  message_log = await self.table_cache.get(log_key, models=MessageLog)
141
-
138
+
142
139
  if message_log:
143
140
  # User has message history
144
141
  recent_messages = message_log.get_recent_messages(20)
145
142
  total_count = message_log.get_message_count()
146
-
143
+
147
144
  if recent_messages:
148
145
  # Format history for display
149
146
  history_text = format_message_history_display(
@@ -154,30 +151,30 @@ class MessageHistoryScore(ScoreBase):
154
151
  else:
155
152
  # No history found
156
153
  history_text = "📚 No message history found. This is your first message! Welcome! 👋"
157
-
154
+
158
155
  # Send history to user
159
156
  result = await self.messenger.send_text(
160
157
  recipient=user_id,
161
158
  text=history_text,
162
- reply_to_message_id=webhook.message.message_id
159
+ reply_to_message_id=webhook.message.message_id,
163
160
  )
164
-
161
+
165
162
  if result.success:
166
163
  self.logger.info(f"✅ History sent to {user_id}")
167
164
  else:
168
165
  self.logger.error(f"❌ Failed to send history: {result.error}")
169
-
166
+
170
167
  except Exception as e:
171
168
  self.logger.error(f"Error handling history request: {e}")
172
169
  raise
173
-
170
+
174
171
  async def get_message_count(self, user_id: str) -> int:
175
172
  """
176
173
  Get message count for user (for other score modules).
177
-
174
+
178
175
  Args:
179
176
  user_id: User's phone number ID
180
-
177
+
181
178
  Returns:
182
179
  Number of messages from user, 0 if no history
183
180
  """
@@ -187,4 +184,4 @@ class MessageHistoryScore(ScoreBase):
187
184
  return message_log.get_message_count() if message_log else 0
188
185
  except Exception as e:
189
186
  self.logger.error(f"Error getting message count for {user_id}: {e}")
190
- return 0
187
+ return 0