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
|
@@ -6,7 +6,6 @@ IncomingMessageWebhook objects based on message type.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import time
|
|
9
|
-
from typing import Optional
|
|
10
9
|
|
|
11
10
|
from wappa.webhooks import IncomingMessageWebhook
|
|
12
11
|
|
|
@@ -24,16 +23,18 @@ from ..models.webhook_metadata import (
|
|
|
24
23
|
|
|
25
24
|
class MetadataExtractor:
|
|
26
25
|
"""Utility class for extracting metadata from webhooks."""
|
|
27
|
-
|
|
26
|
+
|
|
28
27
|
@staticmethod
|
|
29
|
-
def extract_metadata(
|
|
28
|
+
def extract_metadata(
|
|
29
|
+
webhook: IncomingMessageWebhook, start_time: float = None
|
|
30
|
+
) -> WebhookMetadata:
|
|
30
31
|
"""
|
|
31
32
|
Extract appropriate metadata from webhook based on message type.
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
Args:
|
|
34
35
|
webhook: IncomingMessageWebhook to extract metadata from
|
|
35
36
|
start_time: Optional start time for processing time calculation
|
|
36
|
-
|
|
37
|
+
|
|
37
38
|
Returns:
|
|
38
39
|
WebhookMetadata object with extracted information
|
|
39
40
|
"""
|
|
@@ -41,70 +42,84 @@ class MetadataExtractor:
|
|
|
41
42
|
processing_time_ms = None
|
|
42
43
|
if start_time is not None:
|
|
43
44
|
processing_time_ms = int((time.time() - start_time) * 1000)
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
# Get message type from webhook
|
|
46
47
|
message_type_name = webhook.get_message_type_name().lower()
|
|
47
|
-
|
|
48
|
+
|
|
48
49
|
try:
|
|
49
50
|
# Map message type string to enum
|
|
50
51
|
message_type = MessageType(message_type_name)
|
|
51
52
|
except ValueError:
|
|
52
53
|
# Handle unknown message types
|
|
53
54
|
return UnknownMessageMetadata.from_webhook(webhook, processing_time_ms)
|
|
54
|
-
|
|
55
|
+
|
|
55
56
|
# Extract metadata based on message type
|
|
56
57
|
if message_type == MessageType.TEXT:
|
|
57
58
|
return TextMessageMetadata.from_webhook(webhook, processing_time_ms)
|
|
58
|
-
|
|
59
|
-
elif message_type in [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
|
|
60
|
+
elif message_type in [
|
|
61
|
+
MessageType.IMAGE,
|
|
62
|
+
MessageType.VIDEO,
|
|
63
|
+
MessageType.AUDIO,
|
|
64
|
+
MessageType.VOICE,
|
|
65
|
+
MessageType.DOCUMENT,
|
|
66
|
+
MessageType.STICKER,
|
|
67
|
+
]:
|
|
68
|
+
return MediaMessageMetadata.from_webhook(
|
|
69
|
+
webhook, message_type, processing_time_ms
|
|
70
|
+
)
|
|
71
|
+
|
|
63
72
|
elif message_type == MessageType.LOCATION:
|
|
64
73
|
return LocationMessageMetadata.from_webhook(webhook, processing_time_ms)
|
|
65
|
-
|
|
74
|
+
|
|
66
75
|
elif message_type in [MessageType.CONTACT, MessageType.CONTACTS]:
|
|
67
76
|
return ContactMessageMetadata.from_webhook(webhook, processing_time_ms)
|
|
68
|
-
|
|
77
|
+
|
|
69
78
|
elif message_type == MessageType.INTERACTIVE:
|
|
70
79
|
return InteractiveMessageMetadata.from_webhook(webhook, processing_time_ms)
|
|
71
|
-
|
|
80
|
+
|
|
72
81
|
else:
|
|
73
82
|
# Fallback for unsupported types
|
|
74
83
|
return UnknownMessageMetadata.from_webhook(webhook, processing_time_ms)
|
|
75
|
-
|
|
84
|
+
|
|
76
85
|
@staticmethod
|
|
77
86
|
def format_metadata_for_user(metadata: WebhookMetadata) -> str:
|
|
78
87
|
"""
|
|
79
88
|
Format metadata for display to user.
|
|
80
|
-
|
|
89
|
+
|
|
81
90
|
Args:
|
|
82
91
|
metadata: WebhookMetadata object to format
|
|
83
|
-
|
|
92
|
+
|
|
84
93
|
Returns:
|
|
85
94
|
Formatted string for user display
|
|
86
95
|
"""
|
|
87
96
|
lines = []
|
|
88
97
|
lines.append("📊 *Message Metadata*")
|
|
89
98
|
lines.append("─" * 30)
|
|
90
|
-
|
|
99
|
+
|
|
91
100
|
# Basic information
|
|
92
101
|
lines.append(f"🆔 Message ID: `{metadata.message_id[:20]}...`")
|
|
93
102
|
# Handle both enum and string values for message_type
|
|
94
|
-
message_type_str =
|
|
103
|
+
message_type_str = (
|
|
104
|
+
metadata.message_type.value
|
|
105
|
+
if hasattr(metadata.message_type, "value")
|
|
106
|
+
else str(metadata.message_type)
|
|
107
|
+
)
|
|
95
108
|
lines.append(f"📱 Message Type: `{message_type_str}`")
|
|
96
109
|
lines.append(f"👤 User: {metadata.user_name or metadata.user_id}")
|
|
97
|
-
lines.append(
|
|
110
|
+
lines.append(
|
|
111
|
+
f"🕐 Timestamp: {metadata.timestamp.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
112
|
+
)
|
|
98
113
|
lines.append(f"🏢 Tenant: `{metadata.tenant_id}`")
|
|
99
114
|
lines.append(f"🌐 Platform: `{metadata.platform}`")
|
|
100
|
-
|
|
115
|
+
|
|
101
116
|
# Processing metadata
|
|
102
117
|
if metadata.processing_time_ms:
|
|
103
118
|
lines.append(f"⚡ Processing Time: {metadata.processing_time_ms}ms")
|
|
104
|
-
|
|
119
|
+
|
|
105
120
|
if metadata.cache_hit:
|
|
106
121
|
lines.append("💾 Cache: Hit ✅")
|
|
107
|
-
|
|
122
|
+
|
|
108
123
|
# Type-specific metadata
|
|
109
124
|
if isinstance(metadata, TextMessageMetadata):
|
|
110
125
|
lines.append("")
|
|
@@ -116,7 +131,7 @@ class MetadataExtractor:
|
|
|
116
131
|
lines.append("👥 Contains Mentions: Yes")
|
|
117
132
|
if metadata.is_forwarded:
|
|
118
133
|
lines.append("↪️ Forwarded: Yes")
|
|
119
|
-
|
|
134
|
+
|
|
120
135
|
elif isinstance(metadata, MediaMessageMetadata):
|
|
121
136
|
lines.append("")
|
|
122
137
|
lines.append(f"🎬 *{metadata.media_type.title()} Media Details:*")
|
|
@@ -124,9 +139,13 @@ class MetadataExtractor:
|
|
|
124
139
|
if metadata.mime_type:
|
|
125
140
|
lines.append(f"📄 MIME Type: `{metadata.mime_type}`")
|
|
126
141
|
if metadata.file_size:
|
|
127
|
-
lines.append(
|
|
142
|
+
lines.append(
|
|
143
|
+
f"📦 File Size: {MetadataExtractor._format_file_size(metadata.file_size)}"
|
|
144
|
+
)
|
|
128
145
|
if metadata.caption:
|
|
129
|
-
lines.append(
|
|
146
|
+
lines.append(
|
|
147
|
+
f"💬 Caption: {metadata.caption[:50]}{'...' if len(metadata.caption) > 50 else ''}"
|
|
148
|
+
)
|
|
130
149
|
if metadata.width and metadata.height:
|
|
131
150
|
lines.append(f"📐 Dimensions: {metadata.width}x{metadata.height}")
|
|
132
151
|
if metadata.duration:
|
|
@@ -135,7 +154,7 @@ class MetadataExtractor:
|
|
|
135
154
|
lines.append(f"📎 Filename: `{metadata.filename}`")
|
|
136
155
|
if metadata.is_forwarded:
|
|
137
156
|
lines.append("↪️ Forwarded: Yes")
|
|
138
|
-
|
|
157
|
+
|
|
139
158
|
elif isinstance(metadata, LocationMessageMetadata):
|
|
140
159
|
lines.append("")
|
|
141
160
|
lines.append("📍 *Location Details:*")
|
|
@@ -146,20 +165,22 @@ class MetadataExtractor:
|
|
|
146
165
|
lines.append(f"🏠 Address: {metadata.location_address}")
|
|
147
166
|
if metadata.is_forwarded:
|
|
148
167
|
lines.append("↪️ Forwarded: Yes")
|
|
149
|
-
|
|
168
|
+
|
|
150
169
|
elif isinstance(metadata, ContactMessageMetadata):
|
|
151
170
|
lines.append("")
|
|
152
171
|
lines.append("👥 *Contact Details:*")
|
|
153
172
|
lines.append(f"📇 Contact Count: {metadata.contacts_count}")
|
|
154
173
|
if metadata.contact_names:
|
|
155
|
-
lines.append(
|
|
174
|
+
lines.append(
|
|
175
|
+
f"👤 Names: {', '.join(metadata.contact_names[:3])}{'...' if len(metadata.contact_names) > 3 else ''}"
|
|
176
|
+
)
|
|
156
177
|
if metadata.has_phone_numbers:
|
|
157
178
|
lines.append("📞 Has Phone Numbers: Yes")
|
|
158
179
|
if metadata.has_emails:
|
|
159
180
|
lines.append("✉️ Has Emails: Yes")
|
|
160
181
|
if metadata.is_forwarded:
|
|
161
182
|
lines.append("↪️ Forwarded: Yes")
|
|
162
|
-
|
|
183
|
+
|
|
163
184
|
elif isinstance(metadata, InteractiveMessageMetadata):
|
|
164
185
|
lines.append("")
|
|
165
186
|
lines.append("🔘 *Interactive Details:*")
|
|
@@ -168,82 +189,98 @@ class MetadataExtractor:
|
|
|
168
189
|
if metadata.selection_title:
|
|
169
190
|
lines.append(f"🏷️ Selection: {metadata.selection_title}")
|
|
170
191
|
if metadata.context_message_id:
|
|
171
|
-
lines.append(
|
|
172
|
-
|
|
192
|
+
lines.append(
|
|
193
|
+
f"💬 Context Message: `{metadata.context_message_id[:20]}...`"
|
|
194
|
+
)
|
|
195
|
+
|
|
173
196
|
elif isinstance(metadata, UnknownMessageMetadata):
|
|
174
197
|
lines.append("")
|
|
175
198
|
lines.append("❓ *Unknown Message Type*")
|
|
176
199
|
lines.append("🔍 Raw data captured for debugging")
|
|
177
|
-
|
|
200
|
+
|
|
178
201
|
return "\n".join(lines)
|
|
179
|
-
|
|
202
|
+
|
|
180
203
|
@staticmethod
|
|
181
204
|
def get_metadata_summary(metadata: WebhookMetadata) -> dict:
|
|
182
205
|
"""
|
|
183
206
|
Get a summary of metadata for logging/analytics.
|
|
184
|
-
|
|
207
|
+
|
|
185
208
|
Args:
|
|
186
209
|
metadata: WebhookMetadata object to summarize
|
|
187
|
-
|
|
210
|
+
|
|
188
211
|
Returns:
|
|
189
212
|
Dictionary with metadata summary
|
|
190
213
|
"""
|
|
191
214
|
# Handle both enum and string values for message_type
|
|
192
|
-
message_type_value =
|
|
193
|
-
|
|
215
|
+
message_type_value = (
|
|
216
|
+
metadata.message_type.value
|
|
217
|
+
if hasattr(metadata.message_type, "value")
|
|
218
|
+
else str(metadata.message_type)
|
|
219
|
+
)
|
|
220
|
+
|
|
194
221
|
summary = {
|
|
195
222
|
"message_id": metadata.message_id,
|
|
196
223
|
"message_type": message_type_value,
|
|
197
224
|
"user_id": metadata.user_id,
|
|
198
225
|
"timestamp": metadata.timestamp.isoformat(),
|
|
199
226
|
"processing_time_ms": metadata.processing_time_ms,
|
|
200
|
-
"cache_hit": metadata.cache_hit
|
|
227
|
+
"cache_hit": metadata.cache_hit,
|
|
201
228
|
}
|
|
202
|
-
|
|
229
|
+
|
|
203
230
|
# Add type-specific summary data
|
|
204
231
|
if isinstance(metadata, TextMessageMetadata):
|
|
205
|
-
summary.update(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
232
|
+
summary.update(
|
|
233
|
+
{
|
|
234
|
+
"text_length": metadata.text_length,
|
|
235
|
+
"has_urls": metadata.has_urls,
|
|
236
|
+
"has_mentions": metadata.has_mentions,
|
|
237
|
+
"is_forwarded": metadata.is_forwarded,
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
212
241
|
elif isinstance(metadata, MediaMessageMetadata):
|
|
213
|
-
summary.update(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
summary.update(
|
|
243
|
+
{
|
|
244
|
+
"media_id": metadata.media_id,
|
|
245
|
+
"media_type": metadata.media_type,
|
|
246
|
+
"file_size": metadata.file_size,
|
|
247
|
+
"has_caption": bool(metadata.caption),
|
|
248
|
+
"is_forwarded": metadata.is_forwarded,
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
|
|
221
252
|
elif isinstance(metadata, LocationMessageMetadata):
|
|
222
|
-
summary.update(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
253
|
+
summary.update(
|
|
254
|
+
{
|
|
255
|
+
"latitude": metadata.latitude,
|
|
256
|
+
"longitude": metadata.longitude,
|
|
257
|
+
"has_name": bool(metadata.location_name),
|
|
258
|
+
"has_address": bool(metadata.location_address),
|
|
259
|
+
"is_forwarded": metadata.is_forwarded,
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
|
|
230
263
|
elif isinstance(metadata, ContactMessageMetadata):
|
|
231
|
-
summary.update(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
264
|
+
summary.update(
|
|
265
|
+
{
|
|
266
|
+
"contacts_count": metadata.contacts_count,
|
|
267
|
+
"has_phone_numbers": metadata.has_phone_numbers,
|
|
268
|
+
"has_emails": metadata.has_emails,
|
|
269
|
+
"is_forwarded": metadata.is_forwarded,
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
|
|
238
273
|
elif isinstance(metadata, InteractiveMessageMetadata):
|
|
239
|
-
summary.update(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
274
|
+
summary.update(
|
|
275
|
+
{
|
|
276
|
+
"interaction_type": metadata.interaction_type,
|
|
277
|
+
"selection_id": metadata.selection_id,
|
|
278
|
+
"has_title": bool(metadata.selection_title),
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
|
|
245
282
|
return summary
|
|
246
|
-
|
|
283
|
+
|
|
247
284
|
@staticmethod
|
|
248
285
|
def _format_file_size(size_bytes: int) -> str:
|
|
249
286
|
"""Format file size in human-readable format."""
|
|
@@ -258,14 +295,16 @@ class MetadataExtractor:
|
|
|
258
295
|
|
|
259
296
|
|
|
260
297
|
# Convenience functions for direct use
|
|
261
|
-
def extract_webhook_metadata(
|
|
298
|
+
def extract_webhook_metadata(
|
|
299
|
+
webhook: IncomingMessageWebhook, start_time: float = None
|
|
300
|
+
) -> WebhookMetadata:
|
|
262
301
|
"""
|
|
263
302
|
Extract metadata from webhook (convenience function).
|
|
264
|
-
|
|
303
|
+
|
|
265
304
|
Args:
|
|
266
305
|
webhook: IncomingMessageWebhook to process
|
|
267
306
|
start_time: Optional start time for processing measurement
|
|
268
|
-
|
|
307
|
+
|
|
269
308
|
Returns:
|
|
270
309
|
WebhookMetadata object
|
|
271
310
|
"""
|
|
@@ -275,10 +314,10 @@ def extract_webhook_metadata(webhook: IncomingMessageWebhook, start_time: float
|
|
|
275
314
|
def format_metadata_message(metadata: WebhookMetadata) -> str:
|
|
276
315
|
"""
|
|
277
316
|
Format metadata for user display (convenience function).
|
|
278
|
-
|
|
317
|
+
|
|
279
318
|
Args:
|
|
280
319
|
metadata: WebhookMetadata to format
|
|
281
|
-
|
|
320
|
+
|
|
282
321
|
Returns:
|
|
283
322
|
Formatted string for user
|
|
284
323
|
"""
|
|
@@ -288,11 +327,11 @@ def format_metadata_message(metadata: WebhookMetadata) -> str:
|
|
|
288
327
|
def get_processing_summary(metadata: WebhookMetadata) -> dict:
|
|
289
328
|
"""
|
|
290
329
|
Get processing summary (convenience function).
|
|
291
|
-
|
|
330
|
+
|
|
292
331
|
Args:
|
|
293
332
|
metadata: WebhookMetadata to summarize
|
|
294
|
-
|
|
333
|
+
|
|
295
334
|
Returns:
|
|
296
335
|
Summary dictionary
|
|
297
336
|
"""
|
|
298
|
-
return MetadataExtractor.get_metadata_summary(metadata)
|
|
337
|
+
return MetadataExtractor.get_metadata_summary(metadata)
|
wappa/cli/main.py
CHANGED
|
@@ -398,7 +398,7 @@ def _show_examples_menu(target_directory: str) -> None:
|
|
|
398
398
|
table.add_column("Key Features", style="yellow")
|
|
399
399
|
|
|
400
400
|
example_keys = list(EXAMPLES.keys())
|
|
401
|
-
for i, (
|
|
401
|
+
for i, (_key, example) in enumerate(EXAMPLES.items(), 1):
|
|
402
402
|
features_text = ", ".join(example["features"][:3]) # Show first 3 features
|
|
403
403
|
if len(example["features"]) > 3:
|
|
404
404
|
features_text += "..."
|
|
@@ -496,14 +496,19 @@ def _copy_example(example_key: str, target_directory: str) -> None:
|
|
|
496
496
|
# Copy all files from the example (including hidden files, excluding .git and __pycache__)
|
|
497
497
|
for item in source_path.iterdir():
|
|
498
498
|
# Skip .git and __pycache__ directories
|
|
499
|
-
if item.name in {
|
|
499
|
+
if item.name in {".git", "__pycache__"}:
|
|
500
500
|
continue
|
|
501
|
-
|
|
501
|
+
|
|
502
502
|
if item.is_file():
|
|
503
503
|
shutil.copy2(item, target_path / item.name)
|
|
504
504
|
console.print(f"📝 Copied: {item.name}")
|
|
505
505
|
elif item.is_dir():
|
|
506
|
-
shutil.copytree(
|
|
506
|
+
shutil.copytree(
|
|
507
|
+
item,
|
|
508
|
+
target_path / item.name,
|
|
509
|
+
dirs_exist_ok=True,
|
|
510
|
+
ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
|
|
511
|
+
)
|
|
507
512
|
console.print(f"📁 Copied: {item.name}/")
|
|
508
513
|
|
|
509
514
|
console.print("\n✅ Example copied successfully!")
|
wappa/core/__init__.py
CHANGED
|
@@ -10,37 +10,37 @@ Clean Architecture: Core application logic and framework components.
|
|
|
10
10
|
# Configuration & Settings
|
|
11
11
|
from .config.settings import settings
|
|
12
12
|
|
|
13
|
-
# Logging System
|
|
14
|
-
from .logging import get_logger, get_app_logger, setup_app_logging
|
|
15
|
-
|
|
16
13
|
# Event System
|
|
17
14
|
from .events import (
|
|
18
|
-
WappaEventHandler,
|
|
19
|
-
WappaEventDispatcher,
|
|
20
|
-
DefaultMessageHandler,
|
|
21
|
-
DefaultStatusHandler,
|
|
22
15
|
DefaultErrorHandler,
|
|
23
16
|
DefaultHandlerFactory,
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
DefaultMessageHandler,
|
|
18
|
+
DefaultStatusHandler,
|
|
26
19
|
ErrorLogStrategy,
|
|
27
|
-
|
|
20
|
+
MessageLogStrategy,
|
|
21
|
+
StatusLogStrategy,
|
|
22
|
+
WappaEventDispatcher,
|
|
23
|
+
WappaEventHandler,
|
|
28
24
|
WebhookEndpointType,
|
|
25
|
+
WebhookURLFactory,
|
|
29
26
|
webhook_url_factory,
|
|
30
27
|
)
|
|
31
28
|
|
|
32
29
|
# Factory System
|
|
33
30
|
from .factory import WappaBuilder, WappaPlugin
|
|
34
31
|
|
|
32
|
+
# Logging System
|
|
33
|
+
from .logging import get_app_logger, get_logger, setup_app_logging
|
|
34
|
+
|
|
35
35
|
# Plugin System
|
|
36
36
|
from .plugins import (
|
|
37
|
-
WappaCorePlugin,
|
|
38
37
|
AuthPlugin,
|
|
39
38
|
CORSPlugin,
|
|
39
|
+
CustomMiddlewarePlugin,
|
|
40
40
|
DatabasePlugin,
|
|
41
|
-
RedisPlugin,
|
|
42
41
|
RateLimitPlugin,
|
|
43
|
-
|
|
42
|
+
RedisPlugin,
|
|
43
|
+
WappaCorePlugin,
|
|
44
44
|
WebhookPlugin,
|
|
45
45
|
)
|
|
46
46
|
|
|
@@ -50,16 +50,14 @@ from .wappa_app import Wappa
|
|
|
50
50
|
__all__ = [
|
|
51
51
|
# Configuration
|
|
52
52
|
"settings",
|
|
53
|
-
|
|
54
53
|
# Logging
|
|
55
|
-
"get_logger",
|
|
54
|
+
"get_logger",
|
|
56
55
|
"get_app_logger",
|
|
57
56
|
"setup_app_logging",
|
|
58
|
-
|
|
59
57
|
# Event System
|
|
60
58
|
"WappaEventHandler",
|
|
61
59
|
"WappaEventDispatcher",
|
|
62
|
-
"DefaultMessageHandler",
|
|
60
|
+
"DefaultMessageHandler",
|
|
63
61
|
"DefaultStatusHandler",
|
|
64
62
|
"DefaultErrorHandler",
|
|
65
63
|
"DefaultHandlerFactory",
|
|
@@ -67,23 +65,20 @@ __all__ = [
|
|
|
67
65
|
"StatusLogStrategy",
|
|
68
66
|
"ErrorLogStrategy",
|
|
69
67
|
"WebhookURLFactory",
|
|
70
|
-
"WebhookEndpointType",
|
|
68
|
+
"WebhookEndpointType",
|
|
71
69
|
"webhook_url_factory",
|
|
72
|
-
|
|
73
70
|
# Factory System
|
|
74
71
|
"WappaBuilder",
|
|
75
|
-
"WappaPlugin",
|
|
76
|
-
|
|
72
|
+
"WappaPlugin",
|
|
77
73
|
# Plugin System
|
|
78
74
|
"WappaCorePlugin",
|
|
79
75
|
"AuthPlugin",
|
|
80
|
-
"CORSPlugin",
|
|
76
|
+
"CORSPlugin",
|
|
81
77
|
"DatabasePlugin",
|
|
82
78
|
"RedisPlugin",
|
|
83
79
|
"RateLimitPlugin",
|
|
84
80
|
"CustomMiddlewarePlugin",
|
|
85
81
|
"WebhookPlugin",
|
|
86
|
-
|
|
87
82
|
# Core Application
|
|
88
83
|
"Wappa",
|
|
89
84
|
]
|
wappa/core/config/settings.py
CHANGED
|
@@ -45,7 +45,7 @@ def _get_version_from_pyproject() -> str:
|
|
|
45
45
|
def _is_cli_context() -> bool:
|
|
46
46
|
"""
|
|
47
47
|
Detect if we're running in CLI context (help, init, examples) vs server context (dev, prod).
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
Returns:
|
|
50
50
|
True if running CLI commands that don't need WhatsApp credentials
|
|
51
51
|
"""
|
|
@@ -53,12 +53,12 @@ def _is_cli_context() -> bool:
|
|
|
53
53
|
if len(sys.argv) > 1:
|
|
54
54
|
# Direct CLI commands that don't need credentials
|
|
55
55
|
cli_only_commands = {"--help", "-h", "init", "examples"}
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
# Check for help flag or CLI-only commands
|
|
58
58
|
for arg in sys.argv[1:]:
|
|
59
59
|
if arg in cli_only_commands:
|
|
60
60
|
return True
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
# Check if we're running wappa command directly (not through uvicorn)
|
|
63
63
|
if any("wappa" in arg for arg in sys.argv):
|
|
64
64
|
# If no server commands (dev/prod) are present, assume CLI context
|
|
@@ -66,7 +66,7 @@ def _is_cli_context() -> bool:
|
|
|
66
66
|
has_server_command = any(cmd in sys.argv for cmd in server_commands)
|
|
67
67
|
if not has_server_command:
|
|
68
68
|
return True
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
return False
|
|
71
71
|
|
|
72
72
|
|
|
@@ -129,7 +129,7 @@ class Settings:
|
|
|
129
129
|
|
|
130
130
|
# Apply validation (skip WhatsApp validation for CLI-only commands)
|
|
131
131
|
self._validate_settings()
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
# Only validate WhatsApp credentials for server operations
|
|
134
134
|
if not _is_cli_context():
|
|
135
135
|
self._validate_whatsapp_credentials()
|
|
@@ -156,6 +156,8 @@ class Settings:
|
|
|
156
156
|
raise ValueError("WP_PHONE_ID is required")
|
|
157
157
|
if not self.wp_bid:
|
|
158
158
|
raise ValueError("WP_BID is required")
|
|
159
|
+
if not self.whatsapp_webhook_verify_token:
|
|
160
|
+
raise ValueError("WHATSAPP_WEBHOOK_VERIFY_TOKEN is required")
|
|
159
161
|
|
|
160
162
|
@property
|
|
161
163
|
def owner_id(self) -> str:
|
|
@@ -116,7 +116,7 @@ class DefaultMessageHandler:
|
|
|
116
116
|
self._update_stats(webhook)
|
|
117
117
|
|
|
118
118
|
# Get logger with tenant context
|
|
119
|
-
|
|
119
|
+
webhook.tenant.get_tenant_key() if webhook.tenant else "unknown"
|
|
120
120
|
logger = get_logger(__name__)
|
|
121
121
|
|
|
122
122
|
# Log based on strategy
|