wappa 0.1.7__py3-none-any.whl → 0.1.9__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 (80) hide show
  1. wappa/cli/examples/init/.env.example +33 -0
  2. wappa/cli/examples/init/app/__init__.py +0 -0
  3. wappa/cli/examples/init/app/main.py +8 -0
  4. wappa/cli/examples/init/app/master_event.py +8 -0
  5. wappa/cli/examples/json_cache_example/.env.example +33 -0
  6. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  7. wappa/cli/examples/json_cache_example/app/main.py +235 -0
  8. wappa/cli/examples/json_cache_example/app/master_event.py +419 -0
  9. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  10. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +275 -0
  11. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  12. wappa/cli/examples/json_cache_example/app/scores/score_base.py +186 -0
  13. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +248 -0
  14. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +190 -0
  15. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +260 -0
  16. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +223 -0
  17. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  18. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +176 -0
  19. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +246 -0
  20. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  21. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  22. wappa/cli/examples/openai_transcript/app/main.py +8 -0
  23. wappa/cli/examples/openai_transcript/app/master_event.py +53 -0
  24. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  25. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +76 -0
  26. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  27. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  28. wappa/cli/examples/redis_cache_example/app/main.py +234 -0
  29. wappa/cli/examples/redis_cache_example/app/master_event.py +419 -0
  30. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +275 -0
  31. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  32. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +186 -0
  33. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +248 -0
  34. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +190 -0
  35. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +260 -0
  36. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +223 -0
  37. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  38. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +176 -0
  39. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +246 -0
  40. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  41. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  42. wappa/cli/examples/simple_echo_example/app/main.py +183 -0
  43. wappa/cli/examples/simple_echo_example/app/master_event.py +209 -0
  44. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  45. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  46. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  47. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  48. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +484 -0
  49. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +551 -0
  50. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +492 -0
  51. wappa/cli/examples/wappa_full_example/app/main.py +257 -0
  52. wappa/cli/examples/wappa_full_example/app/master_event.py +445 -0
  53. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  54. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  55. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  56. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  57. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  58. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  59. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  60. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  61. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  62. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  63. wappa/cli/examples/wappa_full_example/app/models/state_models.py +425 -0
  64. wappa/cli/examples/wappa_full_example/app/models/user_models.py +287 -0
  65. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +301 -0
  66. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  67. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +483 -0
  68. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +473 -0
  69. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +298 -0
  70. wappa/cli/main.py +8 -4
  71. wappa/core/config/settings.py +34 -2
  72. wappa/persistence/__init__.py +2 -2
  73. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
  74. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/RECORD +77 -13
  75. wappa/cli/examples/init/pyproject.toml +0 -7
  76. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  77. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  78. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/WHEEL +0 -0
  79. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
  80. {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,223 @@
1
+ """
2
+ User Management Score - Single Responsibility: User profile and caching logic.
3
+
4
+ This module handles all user-related operations including:
5
+ - User profile creation and updates
6
+ - User data caching with TTL management
7
+ - User activity tracking
8
+ """
9
+
10
+ from ..models.redis_demo_models import User
11
+ from ..utils.cache_utils import create_user_profile_key, get_cache_ttl
12
+ from ..utils.message_utils import extract_user_data
13
+ from wappa.webhooks import IncomingMessageWebhook
14
+
15
+ from .score_base import ScoreBase
16
+
17
+
18
+ class UserManagementScore(ScoreBase):
19
+ """
20
+ Handles user profile management and caching operations.
21
+
22
+ Follows Single Responsibility Principle by focusing only
23
+ on user-related business logic.
24
+ """
25
+
26
+ async def can_handle(self, webhook: IncomingMessageWebhook) -> bool:
27
+ """
28
+ This score handles all webhooks since every message needs user management.
29
+
30
+ Args:
31
+ webhook: Incoming message webhook
32
+
33
+ Returns:
34
+ Always True since user management is needed for all messages
35
+ """
36
+ return True
37
+
38
+ async def process(self, webhook: IncomingMessageWebhook) -> bool:
39
+ """
40
+ Process user data extraction and caching.
41
+
42
+ Args:
43
+ webhook: Incoming message webhook
44
+
45
+ Returns:
46
+ True if user processing was successful
47
+ """
48
+ if not await self.validate_dependencies():
49
+ return False
50
+
51
+ try:
52
+ user_data = extract_user_data(webhook)
53
+ user_id = user_data['user_id']
54
+ user_name = user_data['user_name']
55
+
56
+ # Get or create user profile
57
+ user = await self._get_or_create_user(user_id, user_name)
58
+
59
+ if user:
60
+ # Update user activity
61
+ await self._update_user_activity(user, user_id)
62
+
63
+ # Send welcome/acknowledgment message for regular messages
64
+ await self._send_welcome_message(webhook, user, user_id)
65
+
66
+ self._record_processing(success=True)
67
+
68
+ self.logger.info(
69
+ f"👤 User profile updated: {user_id} "
70
+ f"(messages: {user.message_count}, name: {user.user_name})"
71
+ )
72
+ return True
73
+ else:
74
+ self._record_processing(success=False)
75
+ return False
76
+
77
+ except Exception as e:
78
+ await self._handle_error(e, "user_management_processing")
79
+ return False
80
+
81
+ async def _get_or_create_user(self, user_id: str, user_name: str) -> User:
82
+ """
83
+ Get existing user or create new one.
84
+
85
+ Args:
86
+ user_id: User's phone number ID
87
+ user_name: User's display name
88
+
89
+ Returns:
90
+ User profile instance
91
+ """
92
+ try:
93
+ # Generate cache key using utility function
94
+ cache_key = create_user_profile_key(user_id)
95
+
96
+ # Try to get existing user with BaseModel deserialization
97
+ user = await self.user_cache.get(cache_key, models=User)
98
+
99
+ if user:
100
+ # User exists, update name if provided and different
101
+ if user_name and user_name != "Unknown User" and user.user_name != user_name:
102
+ user.user_name = user_name
103
+ self.logger.debug(f"Updated user name: {user_id} -> {user_name}")
104
+
105
+ self.logger.debug(f"👤 User cache HIT: {user_id}")
106
+ return user
107
+ else:
108
+ # Create new user profile
109
+ user = User(
110
+ phone_number=user_id,
111
+ user_name=user_name if user_name != "Unknown User" else None,
112
+ message_count=0 # Will be incremented by increment_message_count()
113
+ )
114
+
115
+ self.logger.info(f"👤 User cache MISS: Creating new profile for {user_id}")
116
+ return user
117
+
118
+ except Exception as e:
119
+ self.logger.error(f"Error getting/creating user {user_id}: {e}")
120
+ raise
121
+
122
+ async def _update_user_activity(self, user: User, user_id: str) -> None:
123
+ """
124
+ Update user activity and save to cache.
125
+
126
+ Args:
127
+ user: User profile to update
128
+ user_id: User's phone number ID
129
+ """
130
+ try:
131
+ # Update user activity
132
+ user.increment_message_count()
133
+
134
+ # Save updated user data with TTL
135
+ cache_key = create_user_profile_key(user_id)
136
+ ttl = get_cache_ttl('user')
137
+
138
+ 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
+
142
+ except Exception as e:
143
+ self.logger.error(f"Error updating user activity {user_id}: {e}")
144
+ raise
145
+
146
+ async def get_user_profile(self, user_id: str) -> User:
147
+ """
148
+ Get user profile for other score modules.
149
+
150
+ Args:
151
+ user_id: User's phone number ID
152
+
153
+ Returns:
154
+ User profile or None if not found
155
+ """
156
+ try:
157
+ cache_key = create_user_profile_key(user_id)
158
+ return await self.user_cache.get(cache_key, models=User)
159
+ except Exception as e:
160
+ self.logger.error(f"Error getting user profile {user_id}: {e}")
161
+ return None
162
+
163
+ async def _send_welcome_message(self, webhook: IncomingMessageWebhook, user: User, user_id: str) -> None:
164
+ """
165
+ Send welcome/acknowledgment message based on user's message count.
166
+
167
+ Args:
168
+ webhook: Incoming message webhook
169
+ user: User profile data
170
+ user_id: User's phone number ID
171
+ """
172
+ try:
173
+ message_text = webhook.get_message_text() or ""
174
+
175
+ # Don't send welcome for commands (let other scores handle them)
176
+ if message_text.strip().startswith('/'):
177
+ return
178
+
179
+ # Step 1: Mark message as read with typing indicator
180
+ read_result = await self.messenger.mark_as_read(
181
+ message_id=webhook.message.message_id,
182
+ typing=True # Show typing indicator
183
+ )
184
+
185
+ if not read_result.success:
186
+ self.logger.warning(f"⚠️ Failed to mark message as read: {read_result.error}")
187
+ else:
188
+ self.logger.debug(f"✅ Message marked as read with typing indicator: {webhook.message.message_id}")
189
+
190
+ # Create personalized welcome message based on message count
191
+ if user.message_count == 1:
192
+ # First message - welcome
193
+ welcome_text = (
194
+ f"👋 Welcome to Wappa Redis Cache Demo, {user.user_name}!\n\n"
195
+ f"🎯 Available commands:\n"
196
+ f"• `/WAPPA` - Enter special state\n"
197
+ f"• `/EXIT` - Leave special state\n"
198
+ f"• `/HISTORY` - View message history\n"
199
+ f"• `/STATS` - View cache statistics\n\n"
200
+ f"💎 Your profile is cached in Redis!"
201
+ )
202
+ else:
203
+ # Regular acknowledgment
204
+ welcome_text = (
205
+ f"✅ Message received, {user.user_name}!\n"
206
+ f"📝 Total messages: {user.message_count}\n"
207
+ f"💾 Profile cached in Redis"
208
+ )
209
+
210
+ # Step 2: Send welcome/acknowledgment message
211
+ result = await self.messenger.send_text(
212
+ recipient=user_id,
213
+ text=welcome_text,
214
+ reply_to_message_id=webhook.message.message_id
215
+ )
216
+
217
+ if result.success:
218
+ self.logger.info(f"✅ Welcome message sent to {user_id} (marked as read + typing)")
219
+ else:
220
+ self.logger.error(f"❌ Failed to send welcome message: {result.error}")
221
+
222
+ except Exception as e:
223
+ self.logger.error(f"Error sending welcome message: {e}")
@@ -0,0 +1,26 @@
1
+ """
2
+ Utility modules for Redis Cache Example.
3
+
4
+ This package contains shared utility functions used across
5
+ the score modules, following the Single Responsibility Principle.
6
+ """
7
+
8
+ from .cache_utils import (
9
+ generate_cache_key,
10
+ get_cache_ttl,
11
+ validate_cache_key,
12
+ )
13
+ from .message_utils import (
14
+ extract_user_data,
15
+ format_timestamp,
16
+ sanitize_message_text,
17
+ )
18
+
19
+ __all__ = [
20
+ "generate_cache_key",
21
+ "get_cache_ttl",
22
+ "validate_cache_key",
23
+ "extract_user_data",
24
+ "format_timestamp",
25
+ "sanitize_message_text",
26
+ ]
@@ -0,0 +1,176 @@
1
+ """
2
+ Cache utility functions following Single Responsibility Principle.
3
+
4
+ This module provides cache-related helper functions used across
5
+ different score modules for consistent cache management.
6
+ """
7
+
8
+ import re
9
+ from datetime import datetime
10
+ from typing import Optional
11
+
12
+
13
+ def generate_cache_key(prefix: str, identifier: str, suffix: Optional[str] = None) -> str:
14
+ """
15
+ Generate a standardized cache key.
16
+
17
+ Args:
18
+ prefix: Cache type prefix (e.g., 'user', 'state', 'msg_history')
19
+ identifier: Unique identifier (e.g., user_id, session_id)
20
+ suffix: Optional suffix for additional specificity
21
+
22
+ Returns:
23
+ Properly formatted cache key
24
+
25
+ Examples:
26
+ >>> generate_cache_key('user', '1234567890')
27
+ 'user:1234567890'
28
+ >>> generate_cache_key('msg_history', '1234567890', 'recent')
29
+ 'msg_history:1234567890:recent'
30
+ """
31
+ if not prefix or not identifier:
32
+ raise ValueError("Both prefix and identifier are required")
33
+
34
+ # Sanitize inputs
35
+ prefix = sanitize_cache_component(prefix)
36
+ identifier = sanitize_cache_component(identifier)
37
+
38
+ key = f"{prefix}:{identifier}"
39
+
40
+ if suffix:
41
+ suffix = sanitize_cache_component(suffix)
42
+ key += f":{suffix}"
43
+
44
+ return key
45
+
46
+
47
+ def sanitize_cache_component(component: str) -> str:
48
+ """
49
+ Sanitize a cache key component by removing invalid characters.
50
+
51
+ Args:
52
+ component: Component string to sanitize
53
+
54
+ Returns:
55
+ Sanitized component safe for cache keys
56
+ """
57
+ # Remove spaces and special characters, keep alphanumeric, underscore, hyphen
58
+ sanitized = re.sub(r'[^\w\-]', '_', str(component).strip())
59
+
60
+ # Remove multiple consecutive underscores
61
+ sanitized = re.sub(r'_+', '_', sanitized)
62
+
63
+ # Remove leading/trailing underscores
64
+ return sanitized.strip('_')
65
+
66
+
67
+ def get_cache_ttl(cache_type: str) -> int:
68
+ """
69
+ Get standard TTL (time-to-live) values for different cache types.
70
+
71
+ Args:
72
+ cache_type: Type of cache ('user', 'state', 'message', 'statistics')
73
+
74
+ Returns:
75
+ TTL in seconds
76
+
77
+ Raises:
78
+ ValueError: If cache_type is not recognized
79
+ """
80
+ ttl_mapping = {
81
+ 'user': 86400, # 24 hours - User profiles
82
+ 'state': 3600, # 1 hour - Command states
83
+ 'message': 604800, # 7 days - Message history
84
+ 'statistics': 3600, # 1 hour - Cache statistics
85
+ 'temporary': 600, # 10 minutes - Temporary data
86
+ }
87
+
88
+ if cache_type not in ttl_mapping:
89
+ raise ValueError(f"Unknown cache_type: {cache_type}. Valid types: {list(ttl_mapping.keys())}")
90
+
91
+ return ttl_mapping[cache_type]
92
+
93
+
94
+ def validate_cache_key(key: str) -> bool:
95
+ """
96
+ Validate if a cache key follows the expected format.
97
+
98
+ Args:
99
+ key: Cache key to validate
100
+
101
+ Returns:
102
+ True if valid, False otherwise
103
+ """
104
+ if not key or not isinstance(key, str):
105
+ return False
106
+
107
+ # Basic format check: should contain at least one colon
108
+ if ':' not in key:
109
+ return False
110
+
111
+ # Check for invalid characters (spaces, special chars except : _ -)
112
+ if re.search(r'[^\w:\-]', key):
113
+ return False
114
+
115
+ # Should not start or end with colon
116
+ if key.startswith(':') or key.endswith(':'):
117
+ return False
118
+
119
+ # Should not have consecutive colons
120
+ if '::' in key:
121
+ return False
122
+
123
+ return True
124
+
125
+
126
+ def create_user_profile_key(user_id: str) -> str:
127
+ """Create standardized user profile cache key."""
128
+ return generate_cache_key('user', user_id, 'profile')
129
+
130
+
131
+ def create_message_history_key(user_id: str) -> str:
132
+ """Create standardized message history cache key."""
133
+ return generate_cache_key('msg_history', user_id)
134
+
135
+
136
+ def create_state_key(user_id: str, state_type: str = 'wappa') -> str:
137
+ """Create standardized state cache key."""
138
+ return generate_cache_key('state', user_id, state_type)
139
+
140
+
141
+ def create_statistics_key(scope: str = 'global') -> str:
142
+ """Create standardized cache statistics key."""
143
+ return generate_cache_key('stats', scope)
144
+
145
+
146
+ def format_cache_error(operation: str, key: str, error: Exception) -> str:
147
+ """
148
+ Format cache operation error messages consistently.
149
+
150
+ Args:
151
+ operation: Cache operation ('get', 'set', 'delete')
152
+ key: Cache key that failed
153
+ error: Exception that occurred
154
+
155
+ Returns:
156
+ Formatted error message
157
+ """
158
+ return f"Cache {operation} failed for key '{key}': {str(error)}"
159
+
160
+
161
+ def log_cache_operation(logger, operation: str, key: str, success: bool,
162
+ duration_ms: Optional[float] = None) -> None:
163
+ """
164
+ Log cache operations consistently across score modules.
165
+
166
+ Args:
167
+ logger: Logger instance
168
+ operation: Cache operation performed
169
+ key: Cache key used
170
+ success: Whether operation succeeded
171
+ duration_ms: Optional operation duration in milliseconds
172
+ """
173
+ status = "✅" if success else "❌"
174
+ duration_str = f" ({duration_ms:.1f}ms)" if duration_ms else ""
175
+
176
+ logger.debug(f"{status} Cache {operation}: {key}{duration_str}")
@@ -0,0 +1,246 @@
1
+ """
2
+ Message processing utility functions following Single Responsibility Principle.
3
+
4
+ This module provides message-related helper functions used across
5
+ different score modules for consistent message handling.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from typing import Dict, Optional, Tuple
10
+
11
+ from wappa.webhooks import IncomingMessageWebhook
12
+
13
+
14
+ def extract_user_data(webhook: IncomingMessageWebhook) -> Dict[str, str]:
15
+ """
16
+ Extract user data from webhook in a standardized format.
17
+
18
+ Args:
19
+ webhook: Incoming message webhook
20
+
21
+ Returns:
22
+ Dictionary with standardized user data
23
+ """
24
+ return {
25
+ 'user_id': webhook.user.user_id,
26
+ 'user_name': webhook.user.profile_name or "Unknown User",
27
+ 'tenant_id': webhook.tenant.get_tenant_key(),
28
+ 'message_id': webhook.message.message_id,
29
+ }
30
+
31
+
32
+ def sanitize_message_text(text: str, max_length: int = 500) -> str:
33
+ """
34
+ Sanitize message text for safe storage and processing.
35
+
36
+ Args:
37
+ text: Raw message text
38
+ max_length: Maximum allowed length
39
+
40
+ Returns:
41
+ Sanitized message text
42
+ """
43
+ if not text:
44
+ return ""
45
+
46
+ # Convert to string and strip whitespace
47
+ sanitized = str(text).strip()
48
+
49
+ # Truncate if too long
50
+ if len(sanitized) > max_length:
51
+ sanitized = sanitized[:max_length-3] + "..."
52
+
53
+ # Replace problematic characters
54
+ sanitized = sanitized.replace('\x00', '').replace('\r\n', '\n')
55
+
56
+ return sanitized
57
+
58
+
59
+ def format_timestamp(dt: datetime, format_type: str = 'display') -> str:
60
+ """
61
+ Format timestamps consistently across the application.
62
+
63
+ Args:
64
+ dt: Datetime to format
65
+ format_type: Format type ('display', 'compact', 'iso')
66
+
67
+ Returns:
68
+ Formatted timestamp string
69
+ """
70
+ if format_type == 'display':
71
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
72
+ elif format_type == 'compact':
73
+ return dt.strftime("%m/%d %H:%M")
74
+ elif format_type == 'iso':
75
+ return dt.isoformat()
76
+ else:
77
+ raise ValueError(f"Unknown format_type: {format_type}")
78
+
79
+
80
+ def extract_command_from_message(text: str) -> Tuple[Optional[str], str]:
81
+ """
82
+ Extract command and remaining text from message.
83
+
84
+ Args:
85
+ text: Message text
86
+
87
+ Returns:
88
+ Tuple of (command, remaining_text)
89
+ Command is None if no command found
90
+
91
+ Examples:
92
+ >>> extract_command_from_message("/WAPPA hello")
93
+ ("/WAPPA", "hello")
94
+ >>> extract_command_from_message("regular message")
95
+ (None, "regular message")
96
+ """
97
+ if not text:
98
+ return None, ""
99
+
100
+ text = text.strip()
101
+
102
+ # Check if it starts with a command
103
+ if text.startswith('/'):
104
+ parts = text.split(' ', 1)
105
+ command = parts[0].upper()
106
+ remaining = parts[1] if len(parts) > 1 else ""
107
+ return command, remaining
108
+
109
+ return None, text
110
+
111
+
112
+ def is_special_command(text: str) -> bool:
113
+ """
114
+ Check if message text contains a special command.
115
+
116
+ Args:
117
+ text: Message text to check
118
+
119
+ Returns:
120
+ True if text contains a recognized command
121
+ """
122
+ command, _ = extract_command_from_message(text)
123
+
124
+ if not command:
125
+ return False
126
+
127
+ # List of recognized commands
128
+ special_commands = ['/WAPPA', '/EXIT', '/HISTORY', '/HELP', '/STATUS']
129
+
130
+ return command in special_commands
131
+
132
+
133
+ def get_message_type_display_name(message_type: str) -> str:
134
+ """
135
+ Get human-readable display name for message types.
136
+
137
+ Args:
138
+ message_type: Technical message type
139
+
140
+ Returns:
141
+ Human-readable display name
142
+ """
143
+ type_mapping = {
144
+ 'text': 'Text',
145
+ 'image': 'Image',
146
+ 'audio': 'Audio',
147
+ 'video': 'Video',
148
+ 'document': 'Document',
149
+ 'location': 'Location',
150
+ 'contacts': 'Contact',
151
+ 'interactive': 'Interactive',
152
+ 'button': 'Button Response',
153
+ 'list': 'List Response',
154
+ 'sticker': 'Sticker',
155
+ }
156
+
157
+ return type_mapping.get(message_type.lower(), message_type.title())
158
+
159
+
160
+ def create_user_greeting(user_name: Optional[str], message_count: int) -> str:
161
+ """
162
+ Create personalized user greeting message.
163
+
164
+ Args:
165
+ user_name: User's display name (can be None)
166
+ message_count: Number of messages from user
167
+
168
+ Returns:
169
+ Personalized greeting text
170
+ """
171
+ name = user_name or "there"
172
+
173
+ if message_count == 1:
174
+ return f"👋 Hello {name}! Welcome to the Redis Cache Demo!"
175
+ elif message_count < 5:
176
+ return f"👋 Hello {name}! Nice to see you again!"
177
+ else:
178
+ return f"👋 Hello {name}! You're becoming a regular here! ({message_count} messages)"
179
+
180
+
181
+ def format_message_history_display(messages, total_count: int, display_count: int = 20) -> str:
182
+ """
183
+ Format message history for display to user.
184
+
185
+ Args:
186
+ messages: List of MessageHistory objects
187
+ total_count: Total number of messages in history
188
+ display_count: Number of messages being displayed
189
+
190
+ Returns:
191
+ Formatted history text
192
+ """
193
+ if not messages:
194
+ return "📚 Your message history is empty. Start chatting to build your history!"
195
+
196
+ history_text = f"📚 Your Message History ({total_count} total messages):\n\n"
197
+
198
+ for i, msg_history in enumerate(messages, 1):
199
+ timestamp_str = format_timestamp(msg_history.timestamp, 'compact')
200
+ msg_type = f"[{get_message_type_display_name(msg_history.message_type)}]" if msg_history.message_type != "text" else ""
201
+
202
+ # Truncate long messages for display
203
+ display_message = sanitize_message_text(msg_history.message, 50)
204
+
205
+ history_text += f"{i:2d}. {timestamp_str} {msg_type} {display_message}\n"
206
+
207
+ if total_count > display_count:
208
+ history_text += f"\n... showing last {display_count} of {total_count} messages"
209
+
210
+ return history_text
211
+
212
+
213
+ def create_cache_info_message(user_profile, cache_stats) -> str:
214
+ """
215
+ Create informational message about cache status.
216
+
217
+ Args:
218
+ user_profile: User profile data
219
+ cache_stats: Cache statistics data
220
+
221
+ Returns:
222
+ Formatted cache information message
223
+ """
224
+ info_lines = [
225
+ f"👤 Your Profile:",
226
+ f"• Messages sent: {user_profile.message_count}",
227
+ f"• First seen: {format_timestamp(user_profile.first_seen, 'compact')}",
228
+ f"• Last seen: {format_timestamp(user_profile.last_seen, 'compact')}",
229
+ "",
230
+ f"🎯 Special Commands:",
231
+ f"• Send '/WAPPA' to enter special state",
232
+ f"• Send '/EXIT' to leave special state",
233
+ f"• Send '/HISTORY' to see your message history",
234
+ "",
235
+ f"📊 Cache Statistics:",
236
+ f"• Total operations: {cache_stats.total_operations}",
237
+ f"• User cache hit rate: {cache_stats.get_user_hit_rate():.1%}",
238
+ f"• Active states: {cache_stats.state_cache_active}",
239
+ "",
240
+ f"💾 This demo showcases Redis caching:",
241
+ f"• User data cached in user_cache",
242
+ f"• Message history stored in table_cache per user",
243
+ f"• Commands tracked in state_cache"
244
+ ]
245
+
246
+ return "\n".join(info_lines)
@@ -0,0 +1,33 @@
1
+ # ================================================================
2
+ # WAPPA WHATSAPP FRAMEWORK CONFIGURATION
3
+ # ================================================================
4
+
5
+ # General Configuration
6
+ PORT=8000
7
+ TIME_ZONE=America/Bogota
8
+
9
+ # DEBUG or INFO or WARNING or ERROR or CRITICAL
10
+ LOG_LEVEL=DEBUG
11
+ LOG_DIR=./logs
12
+ ## Environment DEV or PROD
13
+ ENVIRONMENT=DEV
14
+
15
+ # WhatsApp Graph API
16
+ BASE_URL=https://graph.facebook.com/
17
+ API_VERSION=v23.0
18
+
19
+ # WhatsApp Business API Credentials
20
+ WP_ACCESS_TOKEN=
21
+ WP_PHONE_ID=
22
+ WP_BID=
23
+
24
+ # Webhook Configuration
25
+ WHATSAPP_WEBHOOK_VERIFY_TOKEN=
26
+
27
+ # Redis Configuration (Optional - uncomment to enable Redis persistence)
28
+ REDIS_URL=redis://localhost:6379/
29
+ REDIS_MAX_CONNECTIONS=64
30
+
31
+ # Optional: AI Tools
32
+ # OPENAI_API_KEY=
33
+