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,516 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Media handling utilities for the Wappa Full Example application.
|
|
3
|
+
|
|
4
|
+
This module provides functions for downloading and uploading media files,
|
|
5
|
+
handling different media types, and managing local media storage.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import aiohttp
|
|
13
|
+
|
|
14
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MediaHandler:
|
|
18
|
+
"""Utility class for handling media operations."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, temp_dir: str | None = None):
|
|
21
|
+
"""
|
|
22
|
+
Initialize MediaHandler.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
temp_dir: Optional temporary directory path for downloads
|
|
26
|
+
"""
|
|
27
|
+
self.temp_dir = temp_dir or tempfile.gettempdir()
|
|
28
|
+
self.session: aiohttp.ClientSession | None = None
|
|
29
|
+
|
|
30
|
+
async def __aenter__(self):
|
|
31
|
+
"""Async context manager entry."""
|
|
32
|
+
self.session = aiohttp.ClientSession()
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
36
|
+
"""Async context manager exit."""
|
|
37
|
+
if self.session:
|
|
38
|
+
await self.session.close()
|
|
39
|
+
|
|
40
|
+
async def get_media_info_from_webhook(
|
|
41
|
+
self, webhook: IncomingMessageWebhook
|
|
42
|
+
) -> dict[str, str] | None:
|
|
43
|
+
"""
|
|
44
|
+
Extract media information from webhook.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
webhook: IncomingMessageWebhook containing media
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Dictionary with media info or None if no media found
|
|
51
|
+
"""
|
|
52
|
+
message = webhook.message
|
|
53
|
+
message_type = webhook.get_message_type_name().lower()
|
|
54
|
+
|
|
55
|
+
if message_type not in [
|
|
56
|
+
"image",
|
|
57
|
+
"video",
|
|
58
|
+
"audio",
|
|
59
|
+
"voice",
|
|
60
|
+
"document",
|
|
61
|
+
"sticker",
|
|
62
|
+
]:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
media_info = {"type": message_type, "message_id": message.message_id}
|
|
66
|
+
|
|
67
|
+
# Try to extract media ID (different field names possible)
|
|
68
|
+
media_id = None
|
|
69
|
+
for field_name in ["media_id", "id", f"{message_type}_id"]:
|
|
70
|
+
if hasattr(message, field_name):
|
|
71
|
+
media_id = getattr(message, field_name)
|
|
72
|
+
if media_id:
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
if not media_id:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
media_info["media_id"] = media_id
|
|
79
|
+
|
|
80
|
+
# Extract additional metadata
|
|
81
|
+
if hasattr(message, "mime_type"):
|
|
82
|
+
media_info["mime_type"] = message.mime_type
|
|
83
|
+
|
|
84
|
+
if hasattr(message, "file_size"):
|
|
85
|
+
media_info["file_size"] = message.file_size
|
|
86
|
+
|
|
87
|
+
if hasattr(message, "filename"):
|
|
88
|
+
media_info["filename"] = message.filename
|
|
89
|
+
|
|
90
|
+
if hasattr(message, "caption"):
|
|
91
|
+
media_info["caption"] = message.caption
|
|
92
|
+
|
|
93
|
+
# For images and videos, get dimensions
|
|
94
|
+
if message_type in ["image", "video"]:
|
|
95
|
+
if hasattr(message, "width"):
|
|
96
|
+
media_info["width"] = message.width
|
|
97
|
+
if hasattr(message, "height"):
|
|
98
|
+
media_info["height"] = message.height
|
|
99
|
+
|
|
100
|
+
# For audio and video, get duration
|
|
101
|
+
if message_type in ["audio", "video", "voice"]:
|
|
102
|
+
if hasattr(message, "duration"):
|
|
103
|
+
media_info["duration"] = message.duration
|
|
104
|
+
|
|
105
|
+
return media_info
|
|
106
|
+
|
|
107
|
+
async def download_media_by_id(
|
|
108
|
+
self, media_id: str, messenger, media_type: str = None
|
|
109
|
+
) -> tuple[str, dict[str, str]] | None:
|
|
110
|
+
"""
|
|
111
|
+
Download media using media_id through WhatsApp API.
|
|
112
|
+
|
|
113
|
+
Note: This is a placeholder implementation. The actual implementation
|
|
114
|
+
would need to integrate with the WhatsApp Business API to download media.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
media_id: Media ID from WhatsApp
|
|
118
|
+
messenger: IMessenger instance for API calls
|
|
119
|
+
media_type: Optional media type hint
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Tuple of (file_path, metadata) or None if failed
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
# This is a placeholder implementation
|
|
126
|
+
# In a real implementation, you would:
|
|
127
|
+
# 1. Use messenger or direct API calls to get media URL
|
|
128
|
+
# 2. Download the media file
|
|
129
|
+
# 3. Save to temporary location
|
|
130
|
+
# 4. Return file path and metadata
|
|
131
|
+
|
|
132
|
+
# For now, return None to indicate media download not implemented
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
print(f"Error downloading media {media_id}: {e}")
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
async def upload_local_media(
|
|
140
|
+
self, file_path: str, media_type: str = None
|
|
141
|
+
) -> str | None:
|
|
142
|
+
"""
|
|
143
|
+
Upload local media file to get media_id for sending.
|
|
144
|
+
|
|
145
|
+
Note: This is a placeholder implementation. The actual implementation
|
|
146
|
+
would need to integrate with the WhatsApp Business API media upload endpoint.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
file_path: Path to local media file
|
|
150
|
+
media_type: Type of media (image, video, audio, document)
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Media ID if successful, None if failed
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
# This is a placeholder implementation
|
|
157
|
+
# In a real implementation, you would:
|
|
158
|
+
# 1. Upload the file to WhatsApp Business API
|
|
159
|
+
# 2. Get the media_id from the response
|
|
160
|
+
# 3. Return the media_id
|
|
161
|
+
|
|
162
|
+
# For now, return the file path as a placeholder
|
|
163
|
+
if os.path.exists(file_path):
|
|
164
|
+
return f"local_{os.path.basename(file_path)}"
|
|
165
|
+
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
print(f"Error uploading media {file_path}: {e}")
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def get_local_media_path(self, filename: str, media_subdir: str = None) -> str:
|
|
173
|
+
"""
|
|
174
|
+
Get path to local media file.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
filename: Name of the media file
|
|
178
|
+
media_subdir: Optional subdirectory within media folder
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Full path to media file
|
|
182
|
+
"""
|
|
183
|
+
# Construct path relative to app directory
|
|
184
|
+
base_dir = Path(__file__).parent.parent # Go up to app directory
|
|
185
|
+
media_dir = base_dir / "media"
|
|
186
|
+
|
|
187
|
+
if media_subdir:
|
|
188
|
+
media_dir = media_dir / media_subdir
|
|
189
|
+
|
|
190
|
+
return str(media_dir / filename)
|
|
191
|
+
|
|
192
|
+
def media_file_exists(self, filename: str, media_subdir: str = None) -> bool:
|
|
193
|
+
"""
|
|
194
|
+
Check if local media file exists.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
filename: Name of the media file
|
|
198
|
+
media_subdir: Optional subdirectory within media folder
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
True if file exists, False otherwise
|
|
202
|
+
"""
|
|
203
|
+
file_path = self.get_local_media_path(filename, media_subdir)
|
|
204
|
+
return os.path.exists(file_path)
|
|
205
|
+
|
|
206
|
+
def get_media_type_from_extension(self, filename: str) -> str:
|
|
207
|
+
"""
|
|
208
|
+
Determine media type from file extension.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
filename: Name of the file
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Media type string
|
|
215
|
+
"""
|
|
216
|
+
extension = Path(filename).suffix.lower()
|
|
217
|
+
|
|
218
|
+
# Image extensions
|
|
219
|
+
if extension in [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"]:
|
|
220
|
+
return "image"
|
|
221
|
+
|
|
222
|
+
# Video extensions
|
|
223
|
+
elif extension in [".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm"]:
|
|
224
|
+
return "video"
|
|
225
|
+
|
|
226
|
+
# Audio extensions
|
|
227
|
+
elif extension in [".mp3", ".wav", ".aac", ".ogg", ".m4a", ".flac"]:
|
|
228
|
+
return "audio"
|
|
229
|
+
|
|
230
|
+
# Document extensions
|
|
231
|
+
elif extension in [
|
|
232
|
+
".pdf",
|
|
233
|
+
".doc",
|
|
234
|
+
".docx",
|
|
235
|
+
".xls",
|
|
236
|
+
".xlsx",
|
|
237
|
+
".ppt",
|
|
238
|
+
".pptx",
|
|
239
|
+
".txt",
|
|
240
|
+
]:
|
|
241
|
+
return "document"
|
|
242
|
+
|
|
243
|
+
# Default
|
|
244
|
+
else:
|
|
245
|
+
return "document"
|
|
246
|
+
|
|
247
|
+
async def send_media_by_file(
|
|
248
|
+
self,
|
|
249
|
+
messenger,
|
|
250
|
+
recipient: str,
|
|
251
|
+
file_path: str,
|
|
252
|
+
caption: str = None,
|
|
253
|
+
reply_to_message_id: str = None,
|
|
254
|
+
) -> dict[str, any]:
|
|
255
|
+
"""
|
|
256
|
+
Send media file using appropriate messenger method.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
messenger: IMessenger instance
|
|
260
|
+
recipient: Recipient phone number
|
|
261
|
+
file_path: Path to media file
|
|
262
|
+
caption: Optional caption
|
|
263
|
+
reply_to_message_id: Optional message to reply to
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Result dictionary with success status and details
|
|
267
|
+
"""
|
|
268
|
+
try:
|
|
269
|
+
if not os.path.exists(file_path):
|
|
270
|
+
return {
|
|
271
|
+
"success": False,
|
|
272
|
+
"error": f"File not found: {file_path}",
|
|
273
|
+
"method": "file_not_found",
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
media_type = self.get_media_type_from_extension(file_path)
|
|
277
|
+
|
|
278
|
+
# Send using appropriate method based on media type
|
|
279
|
+
if media_type == "image":
|
|
280
|
+
result = await messenger.send_image(
|
|
281
|
+
image_source=file_path,
|
|
282
|
+
recipient=recipient,
|
|
283
|
+
caption=caption,
|
|
284
|
+
reply_to_message_id=reply_to_message_id,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
elif media_type == "video":
|
|
288
|
+
result = await messenger.send_video(
|
|
289
|
+
video_source=file_path,
|
|
290
|
+
recipient=recipient,
|
|
291
|
+
caption=caption,
|
|
292
|
+
reply_to_message_id=reply_to_message_id,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
elif media_type == "audio":
|
|
296
|
+
result = await messenger.send_audio(
|
|
297
|
+
audio_source=file_path,
|
|
298
|
+
recipient=recipient,
|
|
299
|
+
reply_to_message_id=reply_to_message_id,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
elif media_type == "document":
|
|
303
|
+
filename = os.path.basename(file_path)
|
|
304
|
+
result = await messenger.send_document(
|
|
305
|
+
document_source=file_path,
|
|
306
|
+
recipient=recipient,
|
|
307
|
+
filename=filename,
|
|
308
|
+
caption=caption,
|
|
309
|
+
reply_to_message_id=reply_to_message_id,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
else:
|
|
313
|
+
return {
|
|
314
|
+
"success": False,
|
|
315
|
+
"error": f"Unsupported media type: {media_type}",
|
|
316
|
+
"method": "unsupported_type",
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
"success": result.success,
|
|
321
|
+
"message_id": result.message_id
|
|
322
|
+
if hasattr(result, "message_id")
|
|
323
|
+
else None,
|
|
324
|
+
"error": result.error if hasattr(result, "error") else None,
|
|
325
|
+
"method": f"send_{media_type}",
|
|
326
|
+
"media_type": media_type,
|
|
327
|
+
"file_path": file_path,
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
return {
|
|
332
|
+
"success": False,
|
|
333
|
+
"error": f"Error sending media: {str(e)}",
|
|
334
|
+
"method": "exception",
|
|
335
|
+
"file_path": file_path,
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async def send_media_by_id(
|
|
339
|
+
self,
|
|
340
|
+
messenger,
|
|
341
|
+
recipient: str,
|
|
342
|
+
media_id: str,
|
|
343
|
+
media_type: str,
|
|
344
|
+
caption: str = None,
|
|
345
|
+
reply_to_message_id: str = None,
|
|
346
|
+
) -> dict[str, any]:
|
|
347
|
+
"""
|
|
348
|
+
Send media using media_id (relay existing media).
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
messenger: IMessenger instance
|
|
352
|
+
recipient: Recipient phone number
|
|
353
|
+
media_id: Media ID to send
|
|
354
|
+
media_type: Type of media
|
|
355
|
+
caption: Optional caption
|
|
356
|
+
reply_to_message_id: Optional message to reply to
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Result dictionary with success status and details
|
|
360
|
+
"""
|
|
361
|
+
try:
|
|
362
|
+
# For relaying media using media_id, we need to use the media_id as source
|
|
363
|
+
# This assumes the messenger can handle media_id as source parameter
|
|
364
|
+
|
|
365
|
+
if media_type == "image":
|
|
366
|
+
result = await messenger.send_image(
|
|
367
|
+
image_source=media_id, # Using media_id as source
|
|
368
|
+
recipient=recipient,
|
|
369
|
+
caption=caption,
|
|
370
|
+
reply_to_message_id=reply_to_message_id,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
elif media_type == "video":
|
|
374
|
+
result = await messenger.send_video(
|
|
375
|
+
video_source=media_id,
|
|
376
|
+
recipient=recipient,
|
|
377
|
+
caption=caption,
|
|
378
|
+
reply_to_message_id=reply_to_message_id,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
elif media_type in ["audio", "voice"]:
|
|
382
|
+
result = await messenger.send_audio(
|
|
383
|
+
audio_source=media_id,
|
|
384
|
+
recipient=recipient,
|
|
385
|
+
reply_to_message_id=reply_to_message_id,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
elif media_type == "document":
|
|
389
|
+
result = await messenger.send_document(
|
|
390
|
+
document_source=media_id,
|
|
391
|
+
recipient=recipient,
|
|
392
|
+
caption=caption,
|
|
393
|
+
reply_to_message_id=reply_to_message_id,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
elif media_type == "sticker":
|
|
397
|
+
result = await messenger.send_sticker(
|
|
398
|
+
sticker_source=media_id,
|
|
399
|
+
recipient=recipient,
|
|
400
|
+
reply_to_message_id=reply_to_message_id,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
else:
|
|
404
|
+
return {
|
|
405
|
+
"success": False,
|
|
406
|
+
"error": f"Unsupported media type for relay: {media_type}",
|
|
407
|
+
"method": "unsupported_relay_type",
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
"success": result.success,
|
|
412
|
+
"message_id": result.message_id
|
|
413
|
+
if hasattr(result, "message_id")
|
|
414
|
+
else None,
|
|
415
|
+
"error": result.error if hasattr(result, "error") else None,
|
|
416
|
+
"method": f"relay_{media_type}",
|
|
417
|
+
"media_type": media_type,
|
|
418
|
+
"media_id": media_id,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
except Exception as e:
|
|
422
|
+
return {
|
|
423
|
+
"success": False,
|
|
424
|
+
"error": f"Error relaying media: {str(e)}",
|
|
425
|
+
"method": "relay_exception",
|
|
426
|
+
"media_id": media_id,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# Convenience functions for direct use
|
|
431
|
+
async def extract_media_info(webhook: IncomingMessageWebhook) -> dict[str, str] | None:
|
|
432
|
+
"""
|
|
433
|
+
Extract media information from webhook (convenience function).
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
webhook: IncomingMessageWebhook to process
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Media info dictionary or None
|
|
440
|
+
"""
|
|
441
|
+
handler = MediaHandler()
|
|
442
|
+
return await handler.get_media_info_from_webhook(webhook)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
async def send_local_media_file(
|
|
446
|
+
messenger,
|
|
447
|
+
recipient: str,
|
|
448
|
+
filename: str,
|
|
449
|
+
media_subdir: str = None,
|
|
450
|
+
caption: str = None,
|
|
451
|
+
reply_to_message_id: str = None,
|
|
452
|
+
) -> dict[str, any]:
|
|
453
|
+
"""
|
|
454
|
+
Send local media file (convenience function).
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
messenger: IMessenger instance
|
|
458
|
+
recipient: Recipient phone number
|
|
459
|
+
filename: Name of media file in media directory
|
|
460
|
+
media_subdir: Optional subdirectory
|
|
461
|
+
caption: Optional caption
|
|
462
|
+
reply_to_message_id: Optional message to reply to
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Result dictionary
|
|
466
|
+
"""
|
|
467
|
+
handler = MediaHandler()
|
|
468
|
+
file_path = handler.get_local_media_path(filename, media_subdir)
|
|
469
|
+
|
|
470
|
+
return await handler.send_media_by_file(
|
|
471
|
+
messenger=messenger,
|
|
472
|
+
recipient=recipient,
|
|
473
|
+
file_path=file_path,
|
|
474
|
+
caption=caption,
|
|
475
|
+
reply_to_message_id=reply_to_message_id,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
async def relay_webhook_media(
|
|
480
|
+
messenger,
|
|
481
|
+
webhook: IncomingMessageWebhook,
|
|
482
|
+
recipient: str,
|
|
483
|
+
reply_to_message_id: str = None,
|
|
484
|
+
) -> dict[str, any]:
|
|
485
|
+
"""
|
|
486
|
+
Relay media from webhook to recipient (convenience function).
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
messenger: IMessenger instance
|
|
490
|
+
webhook: Original webhook with media
|
|
491
|
+
recipient: Recipient phone number
|
|
492
|
+
reply_to_message_id: Optional message to reply to
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Result dictionary
|
|
496
|
+
"""
|
|
497
|
+
handler = MediaHandler()
|
|
498
|
+
|
|
499
|
+
# Extract media info from webhook
|
|
500
|
+
media_info = await handler.get_media_info_from_webhook(webhook)
|
|
501
|
+
if not media_info:
|
|
502
|
+
return {
|
|
503
|
+
"success": False,
|
|
504
|
+
"error": "No media found in webhook",
|
|
505
|
+
"method": "no_media",
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
# Relay using media_id
|
|
509
|
+
return await handler.send_media_by_id(
|
|
510
|
+
messenger=messenger,
|
|
511
|
+
recipient=recipient,
|
|
512
|
+
media_id=media_info["media_id"],
|
|
513
|
+
media_type=media_info["type"],
|
|
514
|
+
caption=media_info.get("caption"),
|
|
515
|
+
reply_to_message_id=reply_to_message_id,
|
|
516
|
+
)
|