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
|
@@ -8,104 +8,102 @@ This module handles all cache statistics operations including:
|
|
|
8
8
|
- Cache health monitoring
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from ..models.json_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.json_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[
|
|
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(
|
|
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 = "JSON"
|
|
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 operation
|
|
134
134
|
try:
|
|
135
135
|
# Test connectivity by creating a temporary data entry
|
|
136
|
-
test_data = {
|
|
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 data in user_cache
|
|
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
|
|
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 = "File System"
|
|
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 = "File System Issues"
|
|
157
|
+
stats.connection_status = "File System Issues"
|
|
151
158
|
stats.is_healthy = False
|
|
152
159
|
stats.errors += 1
|
|
153
|
-
self.logger.warning(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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.json_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[
|
|
84
|
-
tenant_id = user_data[
|
|
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 JSON cache with TTL
|
|
115
|
-
ttl = get_cache_ttl(
|
|
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[
|
|
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
|