wappa 0.1.0__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 +85 -0
- wappa/api/__init__.py +1 -0
- wappa/api/controllers/__init__.py +10 -0
- wappa/api/controllers/webhook_controller.py +441 -0
- wappa/api/dependencies/__init__.py +15 -0
- wappa/api/dependencies/whatsapp_dependencies.py +220 -0
- wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
- wappa/api/middleware/__init__.py +7 -0
- wappa/api/middleware/error_handler.py +158 -0
- wappa/api/middleware/owner.py +99 -0
- wappa/api/middleware/request_logging.py +184 -0
- wappa/api/routes/__init__.py +6 -0
- wappa/api/routes/health.py +102 -0
- wappa/api/routes/webhooks.py +211 -0
- wappa/api/routes/whatsapp/__init__.py +15 -0
- wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
- wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
- wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
- wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
- wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
- wappa/api/routes/whatsapp_combined.py +35 -0
- wappa/cli/__init__.py +9 -0
- wappa/cli/main.py +199 -0
- wappa/core/__init__.py +6 -0
- wappa/core/config/__init__.py +5 -0
- wappa/core/config/settings.py +161 -0
- wappa/core/events/__init__.py +41 -0
- wappa/core/events/default_handlers.py +642 -0
- wappa/core/events/event_dispatcher.py +244 -0
- wappa/core/events/event_handler.py +247 -0
- wappa/core/events/webhook_factory.py +219 -0
- wappa/core/factory/__init__.py +15 -0
- wappa/core/factory/plugin.py +68 -0
- wappa/core/factory/wappa_builder.py +326 -0
- wappa/core/logging/__init__.py +5 -0
- wappa/core/logging/context.py +100 -0
- wappa/core/logging/logger.py +343 -0
- wappa/core/plugins/__init__.py +34 -0
- wappa/core/plugins/auth_plugin.py +169 -0
- wappa/core/plugins/cors_plugin.py +128 -0
- wappa/core/plugins/custom_middleware_plugin.py +182 -0
- wappa/core/plugins/database_plugin.py +235 -0
- wappa/core/plugins/rate_limit_plugin.py +183 -0
- wappa/core/plugins/redis_plugin.py +224 -0
- wappa/core/plugins/wappa_core_plugin.py +261 -0
- wappa/core/plugins/webhook_plugin.py +253 -0
- wappa/core/types.py +108 -0
- wappa/core/wappa_app.py +546 -0
- wappa/database/__init__.py +18 -0
- wappa/database/adapter.py +107 -0
- wappa/database/adapters/__init__.py +17 -0
- wappa/database/adapters/mysql_adapter.py +187 -0
- wappa/database/adapters/postgresql_adapter.py +169 -0
- wappa/database/adapters/sqlite_adapter.py +174 -0
- wappa/domain/__init__.py +28 -0
- wappa/domain/builders/__init__.py +5 -0
- wappa/domain/builders/message_builder.py +189 -0
- wappa/domain/entities/__init__.py +5 -0
- wappa/domain/enums/messenger_platform.py +123 -0
- wappa/domain/factories/__init__.py +6 -0
- wappa/domain/factories/media_factory.py +450 -0
- wappa/domain/factories/message_factory.py +497 -0
- wappa/domain/factories/messenger_factory.py +244 -0
- wappa/domain/interfaces/__init__.py +32 -0
- wappa/domain/interfaces/base_repository.py +94 -0
- wappa/domain/interfaces/cache_factory.py +85 -0
- wappa/domain/interfaces/cache_interface.py +199 -0
- wappa/domain/interfaces/expiry_repository.py +68 -0
- wappa/domain/interfaces/media_interface.py +311 -0
- wappa/domain/interfaces/messaging_interface.py +523 -0
- wappa/domain/interfaces/pubsub_repository.py +151 -0
- wappa/domain/interfaces/repository_factory.py +108 -0
- wappa/domain/interfaces/shared_state_repository.py +122 -0
- wappa/domain/interfaces/state_repository.py +123 -0
- wappa/domain/interfaces/tables_repository.py +215 -0
- wappa/domain/interfaces/user_repository.py +114 -0
- wappa/domain/interfaces/webhooks/__init__.py +1 -0
- wappa/domain/models/media_result.py +110 -0
- wappa/domain/models/platforms/__init__.py +15 -0
- wappa/domain/models/platforms/platform_config.py +104 -0
- wappa/domain/services/__init__.py +11 -0
- wappa/domain/services/tenant_credentials_service.py +56 -0
- wappa/messaging/__init__.py +7 -0
- wappa/messaging/whatsapp/__init__.py +1 -0
- wappa/messaging/whatsapp/client/__init__.py +5 -0
- wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
- wappa/messaging/whatsapp/handlers/__init__.py +13 -0
- wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
- wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
- wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
- wappa/messaging/whatsapp/messenger/__init__.py +5 -0
- wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
- wappa/messaging/whatsapp/models/__init__.py +61 -0
- wappa/messaging/whatsapp/models/basic_models.py +65 -0
- wappa/messaging/whatsapp/models/interactive_models.py +287 -0
- wappa/messaging/whatsapp/models/media_models.py +215 -0
- wappa/messaging/whatsapp/models/specialized_models.py +304 -0
- wappa/messaging/whatsapp/models/template_models.py +261 -0
- wappa/persistence/cache_factory.py +93 -0
- wappa/persistence/json/__init__.py +14 -0
- wappa/persistence/json/cache_adapters.py +271 -0
- wappa/persistence/json/handlers/__init__.py +1 -0
- wappa/persistence/json/handlers/state_handler.py +250 -0
- wappa/persistence/json/handlers/table_handler.py +263 -0
- wappa/persistence/json/handlers/user_handler.py +213 -0
- wappa/persistence/json/handlers/utils/__init__.py +1 -0
- wappa/persistence/json/handlers/utils/file_manager.py +153 -0
- wappa/persistence/json/handlers/utils/key_factory.py +11 -0
- wappa/persistence/json/handlers/utils/serialization.py +121 -0
- wappa/persistence/json/json_cache_factory.py +76 -0
- wappa/persistence/json/storage_manager.py +285 -0
- wappa/persistence/memory/__init__.py +14 -0
- wappa/persistence/memory/cache_adapters.py +271 -0
- wappa/persistence/memory/handlers/__init__.py +1 -0
- wappa/persistence/memory/handlers/state_handler.py +250 -0
- wappa/persistence/memory/handlers/table_handler.py +280 -0
- wappa/persistence/memory/handlers/user_handler.py +213 -0
- wappa/persistence/memory/handlers/utils/__init__.py +1 -0
- wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
- wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
- wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
- wappa/persistence/memory/memory_cache_factory.py +76 -0
- wappa/persistence/memory/storage_manager.py +235 -0
- wappa/persistence/redis/README.md +699 -0
- wappa/persistence/redis/__init__.py +11 -0
- wappa/persistence/redis/cache_adapters.py +285 -0
- wappa/persistence/redis/ops.py +880 -0
- wappa/persistence/redis/redis_cache_factory.py +71 -0
- wappa/persistence/redis/redis_client.py +231 -0
- wappa/persistence/redis/redis_handler/__init__.py +26 -0
- wappa/persistence/redis/redis_handler/state_handler.py +176 -0
- wappa/persistence/redis/redis_handler/table.py +158 -0
- wappa/persistence/redis/redis_handler/user.py +138 -0
- wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
- wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
- wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
- wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
- wappa/persistence/redis/redis_manager.py +189 -0
- wappa/processors/__init__.py +6 -0
- wappa/processors/base_processor.py +262 -0
- wappa/processors/factory.py +550 -0
- wappa/processors/whatsapp_processor.py +810 -0
- wappa/schemas/__init__.py +6 -0
- wappa/schemas/core/__init__.py +71 -0
- wappa/schemas/core/base_message.py +499 -0
- wappa/schemas/core/base_status.py +322 -0
- wappa/schemas/core/base_webhook.py +312 -0
- wappa/schemas/core/types.py +253 -0
- wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
- wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
- wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
- wappa/schemas/factory.py +754 -0
- wappa/schemas/webhooks/__init__.py +3 -0
- wappa/schemas/whatsapp/__init__.py +6 -0
- wappa/schemas/whatsapp/base_models.py +285 -0
- wappa/schemas/whatsapp/message_types/__init__.py +93 -0
- wappa/schemas/whatsapp/message_types/audio.py +350 -0
- wappa/schemas/whatsapp/message_types/button.py +267 -0
- wappa/schemas/whatsapp/message_types/contact.py +464 -0
- wappa/schemas/whatsapp/message_types/document.py +421 -0
- wappa/schemas/whatsapp/message_types/errors.py +195 -0
- wappa/schemas/whatsapp/message_types/image.py +424 -0
- wappa/schemas/whatsapp/message_types/interactive.py +430 -0
- wappa/schemas/whatsapp/message_types/location.py +416 -0
- wappa/schemas/whatsapp/message_types/order.py +372 -0
- wappa/schemas/whatsapp/message_types/reaction.py +271 -0
- wappa/schemas/whatsapp/message_types/sticker.py +328 -0
- wappa/schemas/whatsapp/message_types/system.py +317 -0
- wappa/schemas/whatsapp/message_types/text.py +411 -0
- wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
- wappa/schemas/whatsapp/message_types/video.py +344 -0
- wappa/schemas/whatsapp/status_models.py +479 -0
- wappa/schemas/whatsapp/validators.py +454 -0
- wappa/schemas/whatsapp/webhook_container.py +438 -0
- wappa/webhooks/__init__.py +17 -0
- wappa/webhooks/core/__init__.py +71 -0
- wappa/webhooks/core/base_message.py +499 -0
- wappa/webhooks/core/base_status.py +322 -0
- wappa/webhooks/core/base_webhook.py +312 -0
- wappa/webhooks/core/types.py +253 -0
- wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
- wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
- wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
- wappa/webhooks/factory.py +754 -0
- wappa/webhooks/whatsapp/__init__.py +6 -0
- wappa/webhooks/whatsapp/base_models.py +285 -0
- wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
- wappa/webhooks/whatsapp/message_types/audio.py +350 -0
- wappa/webhooks/whatsapp/message_types/button.py +267 -0
- wappa/webhooks/whatsapp/message_types/contact.py +464 -0
- wappa/webhooks/whatsapp/message_types/document.py +421 -0
- wappa/webhooks/whatsapp/message_types/errors.py +195 -0
- wappa/webhooks/whatsapp/message_types/image.py +424 -0
- wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
- wappa/webhooks/whatsapp/message_types/location.py +416 -0
- wappa/webhooks/whatsapp/message_types/order.py +372 -0
- wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
- wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
- wappa/webhooks/whatsapp/message_types/system.py +317 -0
- wappa/webhooks/whatsapp/message_types/text.py +411 -0
- wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
- wappa/webhooks/whatsapp/message_types/video.py +344 -0
- wappa/webhooks/whatsapp/status_models.py +479 -0
- wappa/webhooks/whatsapp/validators.py +454 -0
- wappa/webhooks/whatsapp/webhook_container.py +438 -0
- wappa-0.1.0.dist-info/METADATA +269 -0
- wappa-0.1.0.dist-info/RECORD +211 -0
- wappa-0.1.0.dist-info/WHEEL +4 -0
- wappa-0.1.0.dist-info/entry_points.txt +2 -0
- wappa-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base message abstractions for platform-agnostic message handling.
|
|
3
|
+
|
|
4
|
+
This module defines the abstract base classes for different message types
|
|
5
|
+
that provide consistent interfaces regardless of the messaging platform.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
from .types import (
|
|
15
|
+
ConversationType,
|
|
16
|
+
InteractiveType,
|
|
17
|
+
MediaType,
|
|
18
|
+
MessageType,
|
|
19
|
+
PlatformType,
|
|
20
|
+
UniversalMessageData,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseMessageContext(BaseModel, ABC):
|
|
25
|
+
"""
|
|
26
|
+
Platform-agnostic message context base class.
|
|
27
|
+
|
|
28
|
+
Represents context information for replies, forwards, and threaded messages.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def original_message_id(self) -> str | None:
|
|
36
|
+
"""Get the ID of the original message being replied to or forwarded."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def original_sender_id(self) -> str | None:
|
|
42
|
+
"""Get the sender ID of the original message."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def is_reply(self) -> bool:
|
|
48
|
+
"""Check if this represents a reply context."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def is_forward(self) -> bool:
|
|
54
|
+
"""Check if this represents a forward context."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
59
|
+
"""Convert to platform-agnostic dictionary representation."""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class BaseMessage(BaseModel, ABC):
|
|
64
|
+
"""
|
|
65
|
+
Platform-agnostic message base class.
|
|
66
|
+
|
|
67
|
+
All platform-specific message models must inherit from this class
|
|
68
|
+
to provide a consistent interface for message processing.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(
|
|
72
|
+
extra="forbid", str_strip_whitespace=True, validate_assignment=True
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Universal fields
|
|
76
|
+
processed_at: datetime = Field(
|
|
77
|
+
default_factory=datetime.utcnow,
|
|
78
|
+
description="When the message was processed by our system",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def platform(self) -> PlatformType:
|
|
84
|
+
"""Get the platform this message came from."""
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def message_type(self) -> MessageType:
|
|
90
|
+
"""Get the universal message type."""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def message_id(self) -> str:
|
|
96
|
+
"""Get the unique message identifier."""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def sender_id(self) -> str:
|
|
102
|
+
"""Get the sender's universal identifier."""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def timestamp(self) -> int:
|
|
108
|
+
"""Get the message timestamp as Unix timestamp."""
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def conversation_id(self) -> str:
|
|
114
|
+
"""Get the conversation/chat identifier."""
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def conversation_type(self) -> ConversationType:
|
|
119
|
+
"""Get the type of conversation (private, group, channel)."""
|
|
120
|
+
return ConversationType.PRIVATE # Default implementation
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def has_context(self) -> bool:
|
|
124
|
+
"""Check if this message has context (reply, forward, etc.)."""
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def get_context(self) -> BaseMessageContext | None:
|
|
129
|
+
"""Get message context if available."""
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def to_universal_dict(self) -> UniversalMessageData:
|
|
134
|
+
"""
|
|
135
|
+
Convert to platform-agnostic dictionary representation.
|
|
136
|
+
|
|
137
|
+
This method should return a standardized dictionary structure
|
|
138
|
+
that can be used across all platforms for Symphony AI processing.
|
|
139
|
+
"""
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def get_platform_data(self) -> dict[str, Any]:
|
|
144
|
+
"""
|
|
145
|
+
Get platform-specific data for advanced processing.
|
|
146
|
+
|
|
147
|
+
This returns platform-specific fields that don't have
|
|
148
|
+
universal equivalents but may be needed for specialized logic.
|
|
149
|
+
"""
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
def get_message_summary(self) -> dict[str, Any]:
|
|
153
|
+
"""
|
|
154
|
+
Get a summary of the message for logging and analytics.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Dictionary with key message information.
|
|
158
|
+
"""
|
|
159
|
+
return {
|
|
160
|
+
"message_id": self.message_id,
|
|
161
|
+
"platform": self.platform.value,
|
|
162
|
+
"message_type": self.message_type.value,
|
|
163
|
+
"sender_id": self.sender_id,
|
|
164
|
+
"conversation_id": self.conversation_id,
|
|
165
|
+
"conversation_type": self.conversation_type.value,
|
|
166
|
+
"timestamp": self.timestamp,
|
|
167
|
+
"processed_at": self.processed_at.isoformat(),
|
|
168
|
+
"has_context": self.has_context(),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def from_platform_data(cls, data: dict[str, Any], **kwargs) -> "BaseMessage":
|
|
174
|
+
"""
|
|
175
|
+
Create message instance from platform-specific data.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
data: Raw message data from platform webhook
|
|
179
|
+
**kwargs: Additional platform-specific parameters
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Validated message instance
|
|
183
|
+
"""
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class BaseTextMessage(BaseMessage):
|
|
188
|
+
"""
|
|
189
|
+
Platform-agnostic text message base class.
|
|
190
|
+
|
|
191
|
+
Handles text content with universal methods for common operations.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def message_type(self) -> MessageType:
|
|
196
|
+
"""Text messages always have TEXT type."""
|
|
197
|
+
return MessageType.TEXT
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
@abstractmethod
|
|
201
|
+
def text_content(self) -> str:
|
|
202
|
+
"""Get the text content of the message."""
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
@abstractmethod
|
|
207
|
+
def is_reply(self) -> bool:
|
|
208
|
+
"""Check if this is a reply to another message."""
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def is_forwarded(self) -> bool:
|
|
214
|
+
"""Check if this message was forwarded."""
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def is_frequently_forwarded(self) -> bool:
|
|
219
|
+
"""Check if this message was forwarded many times."""
|
|
220
|
+
return False # Default implementation
|
|
221
|
+
|
|
222
|
+
@abstractmethod
|
|
223
|
+
def get_reply_context(self) -> tuple[str | None, str | None]:
|
|
224
|
+
"""
|
|
225
|
+
Get reply context information.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Tuple of (original_sender_id, original_message_id) if reply,
|
|
229
|
+
(None, None) otherwise.
|
|
230
|
+
"""
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
def get_text_length(self) -> int:
|
|
234
|
+
"""Get the length of the text content."""
|
|
235
|
+
return len(self.text_content)
|
|
236
|
+
|
|
237
|
+
def contains_keyword(self, keyword: str, case_sensitive: bool = False) -> bool:
|
|
238
|
+
"""Check if text content contains a specific keyword."""
|
|
239
|
+
text = self.text_content
|
|
240
|
+
search_text = text if case_sensitive else text.lower()
|
|
241
|
+
search_keyword = keyword if case_sensitive else keyword.lower()
|
|
242
|
+
return search_keyword in search_text
|
|
243
|
+
|
|
244
|
+
def contains_any_keyword(
|
|
245
|
+
self, keywords: list[str], case_sensitive: bool = False
|
|
246
|
+
) -> bool:
|
|
247
|
+
"""Check if text content contains any of the specified keywords."""
|
|
248
|
+
return any(self.contains_keyword(kw, case_sensitive) for kw in keywords)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class BaseInteractiveMessage(BaseMessage):
|
|
252
|
+
"""
|
|
253
|
+
Platform-agnostic interactive message base class.
|
|
254
|
+
|
|
255
|
+
Handles button clicks, list selections, and other interactive elements.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def message_type(self) -> MessageType:
|
|
260
|
+
"""Interactive messages always have INTERACTIVE type."""
|
|
261
|
+
return MessageType.INTERACTIVE
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
@abstractmethod
|
|
265
|
+
def interactive_type(self) -> InteractiveType:
|
|
266
|
+
"""Get the type of interactive element (button, list, etc.)."""
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
@abstractmethod
|
|
271
|
+
def selected_option_id(self) -> str:
|
|
272
|
+
"""Get the ID of the selected option."""
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
@abstractmethod
|
|
277
|
+
def selected_option_title(self) -> str:
|
|
278
|
+
"""Get the title/text of the selected option."""
|
|
279
|
+
pass
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
@abstractmethod
|
|
283
|
+
def original_message_id(self) -> str:
|
|
284
|
+
"""Get the ID of the original interactive message."""
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
@abstractmethod
|
|
288
|
+
def is_button_reply(self) -> bool:
|
|
289
|
+
"""Check if this is a button selection."""
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
@abstractmethod
|
|
293
|
+
def is_list_reply(self) -> bool:
|
|
294
|
+
"""Check if this is a list selection."""
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
def matches_option_pattern(self, pattern: str) -> bool:
|
|
298
|
+
"""Check if selected option ID matches a pattern (e.g., starts with prefix)."""
|
|
299
|
+
return self.selected_option_id.startswith(pattern)
|
|
300
|
+
|
|
301
|
+
def get_option_category(self, delimiter: str = "_") -> str | None:
|
|
302
|
+
"""Extract option category from ID using delimiter."""
|
|
303
|
+
parts = self.selected_option_id.split(delimiter)
|
|
304
|
+
return parts[0] if len(parts) > 1 else None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class BaseMediaMessage(BaseMessage):
|
|
308
|
+
"""
|
|
309
|
+
Platform-agnostic media message base class.
|
|
310
|
+
|
|
311
|
+
Handles images, audio, video, and document messages.
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
@abstractmethod
|
|
316
|
+
def media_id(self) -> str:
|
|
317
|
+
"""Get the platform-specific media identifier."""
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
@abstractmethod
|
|
322
|
+
def media_type(self) -> MediaType:
|
|
323
|
+
"""Get the media MIME type."""
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
@abstractmethod
|
|
328
|
+
def file_size(self) -> int | None:
|
|
329
|
+
"""Get the file size in bytes if available."""
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
@abstractmethod
|
|
334
|
+
def caption(self) -> str | None:
|
|
335
|
+
"""Get the media caption/description if available."""
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
@abstractmethod
|
|
339
|
+
def get_download_info(self) -> dict[str, Any]:
|
|
340
|
+
"""
|
|
341
|
+
Get information needed to download the media file.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Dictionary with download URL, headers, or other platform-specific info.
|
|
345
|
+
"""
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
def has_caption(self) -> bool:
|
|
349
|
+
"""Check if the media has a caption."""
|
|
350
|
+
caption = self.caption
|
|
351
|
+
return caption is not None and len(caption.strip()) > 0
|
|
352
|
+
|
|
353
|
+
def is_image(self) -> bool:
|
|
354
|
+
"""Check if this is an image message."""
|
|
355
|
+
return self.message_type == MessageType.IMAGE
|
|
356
|
+
|
|
357
|
+
def is_audio(self) -> bool:
|
|
358
|
+
"""Check if this is an audio message."""
|
|
359
|
+
return self.message_type == MessageType.AUDIO
|
|
360
|
+
|
|
361
|
+
def is_video(self) -> bool:
|
|
362
|
+
"""Check if this is a video message."""
|
|
363
|
+
return self.message_type == MessageType.VIDEO
|
|
364
|
+
|
|
365
|
+
def is_document(self) -> bool:
|
|
366
|
+
"""Check if this is a document message."""
|
|
367
|
+
return self.message_type == MessageType.DOCUMENT
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class BaseImageMessage(BaseMediaMessage):
|
|
371
|
+
"""Platform-agnostic image message base class."""
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def message_type(self) -> MessageType:
|
|
375
|
+
"""Image messages always have IMAGE type."""
|
|
376
|
+
return MessageType.IMAGE
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class BaseAudioMessage(BaseMediaMessage):
|
|
380
|
+
"""Platform-agnostic audio message base class."""
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def message_type(self) -> MessageType:
|
|
384
|
+
"""Audio messages always have AUDIO type."""
|
|
385
|
+
return MessageType.AUDIO
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
@abstractmethod
|
|
389
|
+
def is_voice_message(self) -> bool:
|
|
390
|
+
"""Check if this is a voice message (as opposed to audio file)."""
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
@abstractmethod
|
|
395
|
+
def duration(self) -> int | None:
|
|
396
|
+
"""Get audio duration in seconds if available."""
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class BaseVideoMessage(BaseMediaMessage):
|
|
401
|
+
"""Platform-agnostic video message base class."""
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def message_type(self) -> MessageType:
|
|
405
|
+
"""Video messages always have VIDEO type."""
|
|
406
|
+
return MessageType.VIDEO
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
@abstractmethod
|
|
410
|
+
def duration(self) -> int | None:
|
|
411
|
+
"""Get video duration in seconds if available."""
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
@abstractmethod
|
|
416
|
+
def dimensions(self) -> tuple[int, int] | None:
|
|
417
|
+
"""Get video dimensions (width, height) if available."""
|
|
418
|
+
pass
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class BaseDocumentMessage(BaseMediaMessage):
|
|
422
|
+
"""Platform-agnostic document message base class."""
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def message_type(self) -> MessageType:
|
|
426
|
+
"""Document messages always have DOCUMENT type."""
|
|
427
|
+
return MessageType.DOCUMENT
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
@abstractmethod
|
|
431
|
+
def filename(self) -> str | None:
|
|
432
|
+
"""Get the original filename if available."""
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
@abstractmethod
|
|
437
|
+
def file_extension(self) -> str | None:
|
|
438
|
+
"""Get the file extension if available."""
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class BaseContactMessage(BaseMessage):
|
|
443
|
+
"""Platform-agnostic contact sharing message base class."""
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def message_type(self) -> MessageType:
|
|
447
|
+
"""Contact messages always have CONTACT type."""
|
|
448
|
+
return MessageType.CONTACT
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
@abstractmethod
|
|
452
|
+
def contact_name(self) -> str:
|
|
453
|
+
"""Get the shared contact's name."""
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
@abstractmethod
|
|
458
|
+
def contact_phone(self) -> str | None:
|
|
459
|
+
"""Get the shared contact's phone number if available."""
|
|
460
|
+
pass
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
@abstractmethod
|
|
464
|
+
def contact_data(self) -> dict[str, Any]:
|
|
465
|
+
"""Get all available contact information."""
|
|
466
|
+
pass
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class BaseLocationMessage(BaseMessage):
|
|
470
|
+
"""Platform-agnostic location sharing message base class."""
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def message_type(self) -> MessageType:
|
|
474
|
+
"""Location messages always have LOCATION type."""
|
|
475
|
+
return MessageType.LOCATION
|
|
476
|
+
|
|
477
|
+
@property
|
|
478
|
+
@abstractmethod
|
|
479
|
+
def latitude(self) -> float:
|
|
480
|
+
"""Get the latitude coordinate."""
|
|
481
|
+
pass
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
@abstractmethod
|
|
485
|
+
def longitude(self) -> float:
|
|
486
|
+
"""Get the longitude coordinate."""
|
|
487
|
+
pass
|
|
488
|
+
|
|
489
|
+
@property
|
|
490
|
+
@abstractmethod
|
|
491
|
+
def address(self) -> str | None:
|
|
492
|
+
"""Get the address description if available."""
|
|
493
|
+
pass
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
@abstractmethod
|
|
497
|
+
def location_name(self) -> str | None:
|
|
498
|
+
"""Get the location name if available."""
|
|
499
|
+
pass
|