wappa 0.1.8__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/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +9 -0
- wappa/cli/examples/init/app/master_event.py +10 -0
- wappa/cli/examples/json_cache_example/.env.example +33 -0
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/main.py +247 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/openai_transcript/.gitignore +63 -4
- wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
- wappa/cli/examples/openai_transcript/app/main.py +9 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
- wappa/cli/examples/redis_cache_example/.env.example +33 -0
- wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
- wappa/cli/examples/redis_cache_example/app/main.py +246 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/simple_echo_example/.env.example +33 -0
- wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
- wappa/cli/examples/simple_echo_example/app/main.py +191 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
- wappa/cli/examples/wappa_full_example/.env.example +33 -0
- wappa/cli/examples/wappa_full_example/.gitignore +63 -4
- wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
- wappa/cli/examples/wappa_full_example/app/main.py +269 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
- wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
- wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
- wappa/cli/main.py +14 -5
- 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.8.dist-info ā wappa-0.1.10.dist-info}/METADATA +362 -8
- {wappa-0.1.8.dist-info ā wappa-0.1.10.dist-info}/RECORD +144 -80
- wappa/cli/examples/init/pyproject.toml +0 -7
- wappa/cli/examples/simple_echo_example/.python-version +0 -1
- wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
- {wappa-0.1.8.dist-info ā wappa-0.1.10.dist-info}/WHEEL +0 -0
- {wappa-0.1.8.dist-info ā wappa-0.1.10.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.8.dist-info ā wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
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
|
+
|
|
10
|
+
|
|
11
|
+
def generate_cache_key(prefix: str, identifier: str, suffix: str | None = None) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Generate a standardized cache key.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
prefix: Cache type prefix (e.g., 'user', 'state', 'msg_history')
|
|
17
|
+
identifier: Unique identifier (e.g., user_id, session_id)
|
|
18
|
+
suffix: Optional suffix for additional specificity
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Properly formatted cache key
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> generate_cache_key('user', '1234567890')
|
|
25
|
+
'user:1234567890'
|
|
26
|
+
>>> generate_cache_key('msg_history', '1234567890', 'recent')
|
|
27
|
+
'msg_history:1234567890:recent'
|
|
28
|
+
"""
|
|
29
|
+
if not prefix or not identifier:
|
|
30
|
+
raise ValueError("Both prefix and identifier are required")
|
|
31
|
+
|
|
32
|
+
# Sanitize inputs
|
|
33
|
+
prefix = sanitize_cache_component(prefix)
|
|
34
|
+
identifier = sanitize_cache_component(identifier)
|
|
35
|
+
|
|
36
|
+
key = f"{prefix}:{identifier}"
|
|
37
|
+
|
|
38
|
+
if suffix:
|
|
39
|
+
suffix = sanitize_cache_component(suffix)
|
|
40
|
+
key += f":{suffix}"
|
|
41
|
+
|
|
42
|
+
return key
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def sanitize_cache_component(component: str) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Sanitize a cache key component by removing invalid characters.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
component: Component string to sanitize
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Sanitized component safe for cache keys
|
|
54
|
+
"""
|
|
55
|
+
# Remove spaces and special characters, keep alphanumeric, underscore, hyphen
|
|
56
|
+
sanitized = re.sub(r"[^\w\-]", "_", str(component).strip())
|
|
57
|
+
|
|
58
|
+
# Remove multiple consecutive underscores
|
|
59
|
+
sanitized = re.sub(r"_+", "_", sanitized)
|
|
60
|
+
|
|
61
|
+
# Remove leading/trailing underscores
|
|
62
|
+
return sanitized.strip("_")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_cache_ttl(cache_type: str) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Get standard TTL (time-to-live) values for different cache types.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
cache_type: Type of cache ('user', 'state', 'message', 'statistics')
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
TTL in seconds
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: If cache_type is not recognized
|
|
77
|
+
"""
|
|
78
|
+
ttl_mapping = {
|
|
79
|
+
"user": 86400, # 24 hours - User profiles
|
|
80
|
+
"state": 3600, # 1 hour - Command states
|
|
81
|
+
"message": 604800, # 7 days - Message history
|
|
82
|
+
"statistics": 3600, # 1 hour - Cache statistics
|
|
83
|
+
"temporary": 600, # 10 minutes - Temporary data
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if cache_type not in ttl_mapping:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Unknown cache_type: {cache_type}. Valid types: {list(ttl_mapping.keys())}"
|
|
89
|
+
)
|
|
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
|
+
return "::" not in key
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def create_user_profile_key(user_id: str) -> str:
|
|
124
|
+
"""Create standardized user profile cache key."""
|
|
125
|
+
return generate_cache_key("user", user_id, "profile")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def create_message_history_key(user_id: str) -> str:
|
|
129
|
+
"""Create standardized message history cache key."""
|
|
130
|
+
return generate_cache_key("msg_history", user_id)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def create_state_key(user_id: str, state_type: str = "wappa") -> str:
|
|
134
|
+
"""Create standardized state cache key."""
|
|
135
|
+
return generate_cache_key("state", user_id, state_type)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def create_statistics_key(scope: str = "global") -> str:
|
|
139
|
+
"""Create standardized cache statistics key."""
|
|
140
|
+
return generate_cache_key("stats", scope)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def format_cache_error(operation: str, key: str, error: Exception) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Format cache operation error messages consistently.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
operation: Cache operation ('get', 'set', 'delete')
|
|
149
|
+
key: Cache key that failed
|
|
150
|
+
error: Exception that occurred
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Formatted error message
|
|
154
|
+
"""
|
|
155
|
+
return f"Cache {operation} failed for key '{key}': {str(error)}"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def log_cache_operation(
|
|
159
|
+
logger, operation: str, key: str, success: bool, duration_ms: float | None = None
|
|
160
|
+
) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Log cache operations consistently across score modules.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
logger: Logger instance
|
|
166
|
+
operation: Cache operation performed
|
|
167
|
+
key: Cache key used
|
|
168
|
+
success: Whether operation succeeded
|
|
169
|
+
duration_ms: Optional operation duration in milliseconds
|
|
170
|
+
"""
|
|
171
|
+
status = "ā
" if success else "ā"
|
|
172
|
+
duration_str = f" ({duration_ms:.1f}ms)" if duration_ms else ""
|
|
173
|
+
|
|
174
|
+
logger.debug(f"{status} Cache {operation}: {key}{duration_str}")
|
|
@@ -0,0 +1,251 @@
|
|
|
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
|
+
|
|
10
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_user_data(webhook: IncomingMessageWebhook) -> dict[str, str]:
|
|
14
|
+
"""
|
|
15
|
+
Extract user data from webhook in a standardized format.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
webhook: Incoming message webhook
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Dictionary with standardized user data
|
|
22
|
+
"""
|
|
23
|
+
return {
|
|
24
|
+
"user_id": webhook.user.user_id,
|
|
25
|
+
"user_name": webhook.user.profile_name or "Unknown User",
|
|
26
|
+
"tenant_id": webhook.tenant.get_tenant_key(),
|
|
27
|
+
"message_id": webhook.message.message_id,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def sanitize_message_text(text: str, max_length: int = 500) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Sanitize message text for safe storage and processing.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
text: Raw message text
|
|
37
|
+
max_length: Maximum allowed length
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Sanitized message text
|
|
41
|
+
"""
|
|
42
|
+
if not text:
|
|
43
|
+
return ""
|
|
44
|
+
|
|
45
|
+
# Convert to string and strip whitespace
|
|
46
|
+
sanitized = str(text).strip()
|
|
47
|
+
|
|
48
|
+
# Truncate if too long
|
|
49
|
+
if len(sanitized) > max_length:
|
|
50
|
+
sanitized = sanitized[: max_length - 3] + "..."
|
|
51
|
+
|
|
52
|
+
# Replace problematic characters
|
|
53
|
+
sanitized = sanitized.replace("\x00", "").replace("\r\n", "\n")
|
|
54
|
+
|
|
55
|
+
return sanitized
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def format_timestamp(dt: datetime, format_type: str = "display") -> str:
|
|
59
|
+
"""
|
|
60
|
+
Format timestamps consistently across the application.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
dt: Datetime to format
|
|
64
|
+
format_type: Format type ('display', 'compact', 'iso')
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Formatted timestamp string
|
|
68
|
+
"""
|
|
69
|
+
if format_type == "display":
|
|
70
|
+
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
71
|
+
elif format_type == "compact":
|
|
72
|
+
return dt.strftime("%m/%d %H:%M")
|
|
73
|
+
elif format_type == "iso":
|
|
74
|
+
return dt.isoformat()
|
|
75
|
+
else:
|
|
76
|
+
raise ValueError(f"Unknown format_type: {format_type}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def extract_command_from_message(text: str) -> tuple[str | None, str]:
|
|
80
|
+
"""
|
|
81
|
+
Extract command and remaining text from message.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
text: Message text
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Tuple of (command, remaining_text)
|
|
88
|
+
Command is None if no command found
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
>>> extract_command_from_message("/WAPPA hello")
|
|
92
|
+
("/WAPPA", "hello")
|
|
93
|
+
>>> extract_command_from_message("regular message")
|
|
94
|
+
(None, "regular message")
|
|
95
|
+
"""
|
|
96
|
+
if not text:
|
|
97
|
+
return None, ""
|
|
98
|
+
|
|
99
|
+
text = text.strip()
|
|
100
|
+
|
|
101
|
+
# Check if it starts with a command
|
|
102
|
+
if text.startswith("/"):
|
|
103
|
+
parts = text.split(" ", 1)
|
|
104
|
+
command = parts[0].upper()
|
|
105
|
+
remaining = parts[1] if len(parts) > 1 else ""
|
|
106
|
+
return command, remaining
|
|
107
|
+
|
|
108
|
+
return None, text
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_special_command(text: str) -> bool:
|
|
112
|
+
"""
|
|
113
|
+
Check if message text contains a special command.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
text: Message text to check
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
True if text contains a recognized command
|
|
120
|
+
"""
|
|
121
|
+
command, _ = extract_command_from_message(text)
|
|
122
|
+
|
|
123
|
+
if not command:
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
# List of recognized commands
|
|
127
|
+
special_commands = ["/WAPPA", "/EXIT", "/HISTORY", "/HELP", "/STATUS"]
|
|
128
|
+
|
|
129
|
+
return command in special_commands
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_message_type_display_name(message_type: str) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Get human-readable display name for message types.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
message_type: Technical message type
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Human-readable display name
|
|
141
|
+
"""
|
|
142
|
+
type_mapping = {
|
|
143
|
+
"text": "Text",
|
|
144
|
+
"image": "Image",
|
|
145
|
+
"audio": "Audio",
|
|
146
|
+
"video": "Video",
|
|
147
|
+
"document": "Document",
|
|
148
|
+
"location": "Location",
|
|
149
|
+
"contacts": "Contact",
|
|
150
|
+
"interactive": "Interactive",
|
|
151
|
+
"button": "Button Response",
|
|
152
|
+
"list": "List Response",
|
|
153
|
+
"sticker": "Sticker",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return type_mapping.get(message_type.lower(), message_type.title())
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def create_user_greeting(user_name: str | None, message_count: int) -> str:
|
|
160
|
+
"""
|
|
161
|
+
Create personalized user greeting message.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
user_name: User's display name (can be None)
|
|
165
|
+
message_count: Number of messages from user
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Personalized greeting text
|
|
169
|
+
"""
|
|
170
|
+
name = user_name or "there"
|
|
171
|
+
|
|
172
|
+
if message_count == 1:
|
|
173
|
+
return f"š Hello {name}! Welcome to the Redis Cache Demo!"
|
|
174
|
+
elif message_count < 5:
|
|
175
|
+
return f"š Hello {name}! Nice to see you again!"
|
|
176
|
+
else:
|
|
177
|
+
return f"š Hello {name}! You're becoming a regular here! ({message_count} messages)"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def format_message_history_display(
|
|
181
|
+
messages, total_count: int, display_count: int = 20
|
|
182
|
+
) -> str:
|
|
183
|
+
"""
|
|
184
|
+
Format message history for display to user.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
messages: List of MessageHistory objects
|
|
188
|
+
total_count: Total number of messages in history
|
|
189
|
+
display_count: Number of messages being displayed
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Formatted history text
|
|
193
|
+
"""
|
|
194
|
+
if not messages:
|
|
195
|
+
return "š Your message history is empty. Start chatting to build your history!"
|
|
196
|
+
|
|
197
|
+
history_text = f"š Your Message History ({total_count} total messages):\n\n"
|
|
198
|
+
|
|
199
|
+
for i, msg_history in enumerate(messages, 1):
|
|
200
|
+
timestamp_str = format_timestamp(msg_history.timestamp, "compact")
|
|
201
|
+
msg_type = (
|
|
202
|
+
f"[{get_message_type_display_name(msg_history.message_type)}]"
|
|
203
|
+
if msg_history.message_type != "text"
|
|
204
|
+
else ""
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Truncate long messages for display
|
|
208
|
+
display_message = sanitize_message_text(msg_history.message, 50)
|
|
209
|
+
|
|
210
|
+
history_text += f"{i:2d}. {timestamp_str} {msg_type} {display_message}\n"
|
|
211
|
+
|
|
212
|
+
if total_count > display_count:
|
|
213
|
+
history_text += f"\n... showing last {display_count} of {total_count} messages"
|
|
214
|
+
|
|
215
|
+
return history_text
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def create_cache_info_message(user_profile, cache_stats) -> str:
|
|
219
|
+
"""
|
|
220
|
+
Create informational message about cache status.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
user_profile: User profile data
|
|
224
|
+
cache_stats: Cache statistics data
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Formatted cache information message
|
|
228
|
+
"""
|
|
229
|
+
info_lines = [
|
|
230
|
+
"š¤ Your Profile:",
|
|
231
|
+
f"⢠Messages sent: {user_profile.message_count}",
|
|
232
|
+
f"⢠First seen: {format_timestamp(user_profile.first_seen, 'compact')}",
|
|
233
|
+
f"⢠Last seen: {format_timestamp(user_profile.last_seen, 'compact')}",
|
|
234
|
+
"",
|
|
235
|
+
"šÆ Special Commands:",
|
|
236
|
+
"⢠Send '/WAPPA' to enter special state",
|
|
237
|
+
"⢠Send '/EXIT' to leave special state",
|
|
238
|
+
"⢠Send '/HISTORY' to see your message history",
|
|
239
|
+
"",
|
|
240
|
+
"š Cache Statistics:",
|
|
241
|
+
f"⢠Total operations: {cache_stats.total_operations}",
|
|
242
|
+
f"⢠User cache hit rate: {cache_stats.get_user_hit_rate():.1%}",
|
|
243
|
+
f"⢠Active states: {cache_stats.state_cache_active}",
|
|
244
|
+
"",
|
|
245
|
+
"š¾ This demo showcases Redis caching:",
|
|
246
|
+
"⢠User data cached in user_cache",
|
|
247
|
+
"⢠Message history stored in table_cache per user",
|
|
248
|
+
"⢠Commands tracked in state_cache",
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
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
|
+
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple Echo Example - Clean Architecture Implementation
|
|
3
|
+
|
|
4
|
+
This is a simplified version of the Redis cache example that demonstrates
|
|
5
|
+
basic Wappa functionality without complex dependencies.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Clean WappaEventHandler implementation
|
|
9
|
+
- Simple message echoing for all message types
|
|
10
|
+
- Proper dependency injection
|
|
11
|
+
- Professional logging and error handling
|
|
12
|
+
|
|
13
|
+
SETUP REQUIRED:
|
|
14
|
+
1. Create a .env file with your WhatsApp Business API credentials:
|
|
15
|
+
WP_ACCESS_TOKEN=your_access_token_here
|
|
16
|
+
WP_PHONE_ID=your_phone_number_id_here
|
|
17
|
+
WP_BID=your_business_id_here
|
|
18
|
+
|
|
19
|
+
USAGE:
|
|
20
|
+
- Direct Python: python -m app.main (from project root)
|
|
21
|
+
- FastAPI-style: uvicorn app.main:app --reload (from project root)
|
|
22
|
+
- Wappa CLI: wappa run app/main.py (from project root)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Import core Wappa components
|
|
26
|
+
from wappa import Wappa, __version__
|
|
27
|
+
from wappa.core.config.settings import settings
|
|
28
|
+
from wappa.core.logging import get_logger
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
# Import our simple echo handler
|
|
33
|
+
from .master_event import SimpleEchoHandler
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_configuration() -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Validate required configuration settings.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if configuration is valid, False otherwise
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# Check required WhatsApp credentials
|
|
45
|
+
missing_configs = []
|
|
46
|
+
|
|
47
|
+
if not settings.wp_access_token:
|
|
48
|
+
missing_configs.append("WP_ACCESS_TOKEN")
|
|
49
|
+
|
|
50
|
+
if not settings.wp_phone_id:
|
|
51
|
+
missing_configs.append("WP_PHONE_ID")
|
|
52
|
+
|
|
53
|
+
if not settings.wp_bid:
|
|
54
|
+
missing_configs.append("WP_BID")
|
|
55
|
+
|
|
56
|
+
if missing_configs:
|
|
57
|
+
logger.error(f"ā Missing required configuration: {', '.join(missing_configs)}")
|
|
58
|
+
logger.error("š” Create a .env file with the required credentials")
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
logger.info("ā
Configuration validation passed")
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def display_startup_information() -> None:
|
|
66
|
+
"""
|
|
67
|
+
Display startup information and demo features.
|
|
68
|
+
"""
|
|
69
|
+
print(f"š Wappa v{__version__} - Simple Echo Example")
|
|
70
|
+
print("=" * 70)
|
|
71
|
+
print()
|
|
72
|
+
|
|
73
|
+
print("š *CONFIGURATION STATUS:*")
|
|
74
|
+
print(
|
|
75
|
+
f" ⢠Access Token: {'ā
Configured' if settings.wp_access_token else 'ā Missing'}"
|
|
76
|
+
)
|
|
77
|
+
print(
|
|
78
|
+
f" ⢠Phone ID: {settings.wp_phone_id if settings.wp_phone_id else 'ā Missing'}"
|
|
79
|
+
)
|
|
80
|
+
print(f" ⢠Business ID: {'ā
Configured' if settings.wp_bid else 'ā Missing'}")
|
|
81
|
+
print(
|
|
82
|
+
f" ⢠Environment: {'š ļø Development' if settings.is_development else 'š Production'}"
|
|
83
|
+
)
|
|
84
|
+
print()
|
|
85
|
+
|
|
86
|
+
print("šÆ *DEMO FEATURES:*")
|
|
87
|
+
print(" ⢠Send any text message ā Get echo response")
|
|
88
|
+
print(" ⢠Send media files ā Get acknowledgment")
|
|
89
|
+
print(" ⢠Send location ā Get location confirmation")
|
|
90
|
+
print(" ⢠Send contacts ā Get contact confirmation")
|
|
91
|
+
print(" ⢠All messages are counted and logged")
|
|
92
|
+
print()
|
|
93
|
+
|
|
94
|
+
print("š§ *TECHNICAL FEATURES:*")
|
|
95
|
+
print(" ⢠Clean WappaEventHandler implementation")
|
|
96
|
+
print(" ⢠Proper dependency injection")
|
|
97
|
+
print(" ⢠Multi-message-type support")
|
|
98
|
+
print(" ⢠Professional error handling")
|
|
99
|
+
print(" ⢠Structured logging")
|
|
100
|
+
print()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def create_wappa_application() -> Wappa:
|
|
104
|
+
"""
|
|
105
|
+
Create and configure the Wappa application.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Configured Wappa application instance
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Create Wappa instance with JSON cache (simple file-based cache for echo example)
|
|
113
|
+
logger.info("šļø Creating Wappa application with JSON cache...")
|
|
114
|
+
app = Wappa(cache="json")
|
|
115
|
+
|
|
116
|
+
logger.info("ā
Wappa application created successfully")
|
|
117
|
+
return app
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"ā Failed to create Wappa application: {e}")
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main() -> None:
|
|
125
|
+
"""
|
|
126
|
+
Main application entry point.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
logger.info("š Starting Simple Echo Example")
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
# Display startup information
|
|
133
|
+
display_startup_information()
|
|
134
|
+
|
|
135
|
+
# Validate configuration before proceeding
|
|
136
|
+
if not validate_configuration():
|
|
137
|
+
logger.error(
|
|
138
|
+
"ā Configuration validation failed - cannot start application"
|
|
139
|
+
)
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# Create Wappa application
|
|
143
|
+
app = create_wappa_application()
|
|
144
|
+
|
|
145
|
+
# Create and set the simple echo handler
|
|
146
|
+
handler = SimpleEchoHandler()
|
|
147
|
+
app.set_event_handler(handler)
|
|
148
|
+
|
|
149
|
+
logger.info("ā
Application initialization completed with SimpleEchoHandler")
|
|
150
|
+
|
|
151
|
+
print("š Starting simple echo server...")
|
|
152
|
+
print("š” Press CTRL+C to stop the server")
|
|
153
|
+
print("=" * 70)
|
|
154
|
+
print()
|
|
155
|
+
|
|
156
|
+
# Start the application
|
|
157
|
+
app.run()
|
|
158
|
+
|
|
159
|
+
except KeyboardInterrupt:
|
|
160
|
+
logger.info("š Application stopped by user")
|
|
161
|
+
print("\\nš Simple echo server stopped by user")
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"ā Application startup error: {e}", exc_info=True)
|
|
165
|
+
print(f"\\nā Server error: {e}")
|
|
166
|
+
|
|
167
|
+
finally:
|
|
168
|
+
logger.info("š Simple echo example completed")
|
|
169
|
+
print("š Simple echo example completed")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# Module-level app instance for uvicorn compatibility
|
|
173
|
+
# This enables: uvicorn app.main:app --reload
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
logger.info("š¦ Creating module-level Wappa application instance")
|
|
177
|
+
app = Wappa()
|
|
178
|
+
|
|
179
|
+
# Create and set the simple echo handler
|
|
180
|
+
handler = SimpleEchoHandler()
|
|
181
|
+
app.set_event_handler(handler)
|
|
182
|
+
|
|
183
|
+
logger.info("ā
Module-level application instance ready with SimpleEchoHandler")
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.error(f"ā Failed to create module-level app instance: {e}")
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|