marqetive-lib 0.1.6__py3-none-any.whl → 0.1.7__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.
- marqetive/__init__.py +13 -7
- marqetive/core/__init__.py +6 -4
- marqetive/core/base.py +92 -13
- marqetive/core/models.py +111 -7
- marqetive/platforms/instagram/__init__.py +2 -1
- marqetive/platforms/instagram/media.py +79 -13
- marqetive/platforms/instagram/models.py +74 -0
- marqetive/platforms/linkedin/__init__.py +51 -2
- marqetive/platforms/linkedin/client.py +978 -94
- marqetive/platforms/linkedin/media.py +156 -47
- marqetive/platforms/linkedin/models.py +413 -0
- marqetive/platforms/tiktok/__init__.py +2 -1
- marqetive/platforms/tiktok/media.py +112 -51
- marqetive/platforms/tiktok/models.py +82 -0
- marqetive/platforms/twitter/__init__.py +2 -1
- marqetive/platforms/twitter/client.py +86 -0
- marqetive/platforms/twitter/media.py +133 -65
- marqetive/platforms/twitter/models.py +58 -0
- {marqetive_lib-0.1.6.dist-info → marqetive_lib-0.1.7.dist-info}/METADATA +1 -9
- marqetive_lib-0.1.7.dist-info/RECORD +39 -0
- marqetive_lib-0.1.6.dist-info/RECORD +0 -35
- {marqetive_lib-0.1.6.dist-info → marqetive_lib-0.1.7.dist-info}/WHEEL +0 -0
marqetive/__init__.py
CHANGED
|
@@ -62,6 +62,8 @@ from marqetive.core.models import (
|
|
|
62
62
|
PostCreateRequest,
|
|
63
63
|
PostStatus,
|
|
64
64
|
PostUpdateRequest,
|
|
65
|
+
ProgressEvent,
|
|
66
|
+
ProgressStatus,
|
|
65
67
|
)
|
|
66
68
|
|
|
67
69
|
# Factory
|
|
@@ -85,17 +87,21 @@ __all__ = [
|
|
|
85
87
|
"BackoffConfig",
|
|
86
88
|
"STANDARD_BACKOFF",
|
|
87
89
|
"retry_async",
|
|
88
|
-
#
|
|
89
|
-
"AuthCredentials",
|
|
90
|
+
# Enums
|
|
90
91
|
"AccountStatus",
|
|
91
|
-
"
|
|
92
|
+
"CommentStatus",
|
|
93
|
+
"MediaType",
|
|
92
94
|
"PostStatus",
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
+
"ProgressStatus",
|
|
96
|
+
# Core Models
|
|
97
|
+
"AuthCredentials",
|
|
95
98
|
"Comment",
|
|
96
|
-
"CommentStatus",
|
|
97
99
|
"MediaAttachment",
|
|
98
|
-
"
|
|
100
|
+
"Post",
|
|
101
|
+
"ProgressEvent",
|
|
102
|
+
# Base Request Models
|
|
103
|
+
"PostCreateRequest",
|
|
104
|
+
"PostUpdateRequest",
|
|
99
105
|
# Exceptions
|
|
100
106
|
"PlatformError",
|
|
101
107
|
"PlatformAuthError",
|
marqetive/core/__init__.py
CHANGED
|
@@ -31,17 +31,19 @@ __all__ = [
|
|
|
31
31
|
# Base class
|
|
32
32
|
"SocialMediaPlatform",
|
|
33
33
|
"ProgressCallback",
|
|
34
|
-
#
|
|
34
|
+
# Enums
|
|
35
35
|
"AccountStatus",
|
|
36
|
+
"CommentStatus",
|
|
37
|
+
"MediaType",
|
|
38
|
+
"PostStatus",
|
|
39
|
+
# Core Models
|
|
36
40
|
"AuthCredentials",
|
|
37
41
|
"Comment",
|
|
38
|
-
"CommentStatus",
|
|
39
42
|
"MediaAttachment",
|
|
40
|
-
"MediaType",
|
|
41
43
|
"PlatformResponse",
|
|
42
44
|
"Post",
|
|
45
|
+
# Base Request Models
|
|
43
46
|
"PostCreateRequest",
|
|
44
|
-
"PostStatus",
|
|
45
47
|
"PostUpdateRequest",
|
|
46
48
|
# Exceptions
|
|
47
49
|
"InvalidFileTypeError",
|
marqetive/core/base.py
CHANGED
|
@@ -5,8 +5,9 @@ for implementing platform-specific clients (Instagram, Twitter, LinkedIn, etc.).
|
|
|
5
5
|
All concrete implementations must implement the abstract methods defined here.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import inspect
|
|
8
9
|
from abc import ABC, abstractmethod
|
|
9
|
-
from collections.abc import Callable
|
|
10
|
+
from collections.abc import Awaitable, Callable
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
from traceback import TracebackException
|
|
12
13
|
from typing import Any
|
|
@@ -24,11 +25,15 @@ from marqetive.core.models import (
|
|
|
24
25
|
Post,
|
|
25
26
|
PostCreateRequest,
|
|
26
27
|
PostUpdateRequest,
|
|
28
|
+
ProgressEvent,
|
|
29
|
+
ProgressStatus,
|
|
27
30
|
)
|
|
28
31
|
|
|
29
|
-
# Type
|
|
30
|
-
#
|
|
31
|
-
type
|
|
32
|
+
# Type aliases for progress callbacks
|
|
33
|
+
# Supports both sync and async callbacks using ProgressEvent
|
|
34
|
+
type SyncProgressCallback = Callable[[ProgressEvent], None]
|
|
35
|
+
type AsyncProgressCallback = Callable[[ProgressEvent], Awaitable[None]]
|
|
36
|
+
type ProgressCallback = SyncProgressCallback | AsyncProgressCallback
|
|
32
37
|
|
|
33
38
|
|
|
34
39
|
class SocialMediaPlatform(ABC):
|
|
@@ -162,28 +167,66 @@ class SocialMediaPlatform(ABC):
|
|
|
162
167
|
self._rate_limit_remaining = remaining
|
|
163
168
|
self._rate_limit_reset = reset_time
|
|
164
169
|
|
|
165
|
-
def _emit_progress(
|
|
170
|
+
async def _emit_progress(
|
|
166
171
|
self,
|
|
167
172
|
operation: str,
|
|
173
|
+
status: ProgressStatus,
|
|
168
174
|
progress: int,
|
|
169
175
|
total: int,
|
|
170
176
|
message: str | None = None,
|
|
177
|
+
*,
|
|
178
|
+
entity_id: str | None = None,
|
|
179
|
+
file_path: str | None = None,
|
|
180
|
+
bytes_uploaded: int | None = None,
|
|
181
|
+
total_bytes: int | None = None,
|
|
171
182
|
) -> None:
|
|
172
183
|
"""Emit a progress update if a callback is registered.
|
|
173
184
|
|
|
174
|
-
This method
|
|
185
|
+
This method supports both synchronous and asynchronous callbacks.
|
|
186
|
+
It is safe to call even if no callback is registered.
|
|
175
187
|
|
|
176
188
|
Args:
|
|
177
|
-
operation: Name of the operation (e.g., "upload_media", "create_post")
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
operation: Name of the operation (e.g., "upload_media", "create_post", "create_thread")
|
|
190
|
+
status: Current status of the operation
|
|
191
|
+
progress: Current progress value (0-100 for percentage, or bytes)
|
|
192
|
+
total: Total value for completion (100 for percentage, or total bytes)
|
|
193
|
+
message: Optional human-readable status message
|
|
194
|
+
entity_id: Optional platform-specific ID (media_id, post_id, etc.)
|
|
195
|
+
file_path: Optional file path for upload operations
|
|
196
|
+
bytes_uploaded: Optional bytes uploaded so far
|
|
197
|
+
total_bytes: Optional total bytes to upload
|
|
181
198
|
|
|
182
199
|
Example:
|
|
183
|
-
>>> self._emit_progress(
|
|
200
|
+
>>> await self._emit_progress(
|
|
201
|
+
... operation="upload_media",
|
|
202
|
+
... status=ProgressStatus.UPLOADING,
|
|
203
|
+
... progress=50,
|
|
204
|
+
... total=100,
|
|
205
|
+
... message="Uploading image 1 of 3",
|
|
206
|
+
... entity_id="media_123",
|
|
207
|
+
... )
|
|
184
208
|
"""
|
|
185
|
-
if self._progress_callback is
|
|
186
|
-
|
|
209
|
+
if self._progress_callback is None:
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
event = ProgressEvent(
|
|
213
|
+
operation=operation,
|
|
214
|
+
platform=self.platform_name,
|
|
215
|
+
status=status,
|
|
216
|
+
progress=progress,
|
|
217
|
+
total=total,
|
|
218
|
+
message=message,
|
|
219
|
+
entity_id=entity_id,
|
|
220
|
+
file_path=file_path,
|
|
221
|
+
bytes_uploaded=bytes_uploaded,
|
|
222
|
+
total_bytes=total_bytes,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
result = self._progress_callback(event)
|
|
226
|
+
|
|
227
|
+
# If callback returned a coroutine, await it
|
|
228
|
+
if inspect.iscoroutine(result):
|
|
229
|
+
await result
|
|
187
230
|
|
|
188
231
|
# ==================== Abstract Authentication Methods ====================
|
|
189
232
|
|
|
@@ -307,6 +350,42 @@ class SocialMediaPlatform(ABC):
|
|
|
307
350
|
"""
|
|
308
351
|
pass
|
|
309
352
|
|
|
353
|
+
async def create_thread(
|
|
354
|
+
self,
|
|
355
|
+
posts: list[PostCreateRequest],
|
|
356
|
+
) -> list[Post]:
|
|
357
|
+
"""Create a thread of connected posts.
|
|
358
|
+
|
|
359
|
+
Not all platforms support threads. The default implementation raises
|
|
360
|
+
NotImplementedError. Platforms that support threads (like Twitter)
|
|
361
|
+
should override this method.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
posts: List of post requests to create as a thread.
|
|
365
|
+
Each post can have its own content, media, etc.
|
|
366
|
+
First post is the head of the thread.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
List of Post objects for each post in the thread.
|
|
370
|
+
|
|
371
|
+
Raises:
|
|
372
|
+
NotImplementedError: If platform doesn't support threads.
|
|
373
|
+
ValidationError: If posts list is empty.
|
|
374
|
+
PlatformAuthError: If not authenticated.
|
|
375
|
+
|
|
376
|
+
Example:
|
|
377
|
+
>>> # Twitter thread example
|
|
378
|
+
>>> posts = [
|
|
379
|
+
... TwitterPostRequest(content="Thread start! 1/3"),
|
|
380
|
+
... TwitterPostRequest(content="Middle 2/3", media_urls=[...]),
|
|
381
|
+
... TwitterPostRequest(content="End 3/3"),
|
|
382
|
+
... ]
|
|
383
|
+
>>> thread = await client.create_thread(posts)
|
|
384
|
+
"""
|
|
385
|
+
raise NotImplementedError(
|
|
386
|
+
f"{self.platform_name} does not support thread creation"
|
|
387
|
+
)
|
|
388
|
+
|
|
310
389
|
# ==================== Abstract Comment Methods ====================
|
|
311
390
|
|
|
312
391
|
@abstractmethod
|
marqetive/core/models.py
CHANGED
|
@@ -6,10 +6,10 @@ and type safety.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from datetime import UTC, datetime, timedelta
|
|
9
|
-
from enum import Enum
|
|
9
|
+
from enum import Enum, StrEnum
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from pydantic import BaseModel, Field, HttpUrl
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field, HttpUrl
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class MediaType(str, Enum):
|
|
@@ -19,6 +19,8 @@ class MediaType(str, Enum):
|
|
|
19
19
|
VIDEO = "video"
|
|
20
20
|
CAROUSEL = "carousel"
|
|
21
21
|
DOCUMENT = "document"
|
|
22
|
+
REEL = "reel"
|
|
23
|
+
STORY = "story"
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class PostStatus(str, Enum):
|
|
@@ -55,6 +57,29 @@ class AccountStatus(str, Enum):
|
|
|
55
57
|
ERROR = "error"
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
class ProgressStatus(StrEnum):
|
|
61
|
+
"""Standard progress status for operations across all platforms.
|
|
62
|
+
|
|
63
|
+
Used with ProgressEvent to provide consistent progress tracking
|
|
64
|
+
for long-running operations like media uploads and post creation.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
INITIALIZING: Operation is starting, preparing resources.
|
|
68
|
+
UPLOADING: Actively transferring data (e.g., uploading media).
|
|
69
|
+
PROCESSING: Server-side processing (e.g., video transcoding).
|
|
70
|
+
FINALIZING: Completing the operation (e.g., publishing post).
|
|
71
|
+
COMPLETED: Operation finished successfully.
|
|
72
|
+
FAILED: Operation failed with an error.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
INITIALIZING = "initializing"
|
|
76
|
+
UPLOADING = "uploading"
|
|
77
|
+
PROCESSING = "processing"
|
|
78
|
+
FINALIZING = "finalizing"
|
|
79
|
+
COMPLETED = "completed"
|
|
80
|
+
FAILED = "failed"
|
|
81
|
+
|
|
82
|
+
|
|
58
83
|
class MediaAttachment(BaseModel):
|
|
59
84
|
"""Represents a media attachment (image, video, etc.).
|
|
60
85
|
|
|
@@ -294,8 +319,82 @@ class PlatformResponse(BaseModel):
|
|
|
294
319
|
rate_limit_reset: datetime | None = None
|
|
295
320
|
|
|
296
321
|
|
|
322
|
+
class ProgressEvent(BaseModel):
|
|
323
|
+
"""Unified progress event for tracking long-running operations.
|
|
324
|
+
|
|
325
|
+
Provides a consistent interface for progress callbacks across all platforms.
|
|
326
|
+
Supports both synchronous and asynchronous callbacks.
|
|
327
|
+
|
|
328
|
+
Attributes:
|
|
329
|
+
operation: Name of the operation (e.g., "upload_media", "create_post", "create_thread").
|
|
330
|
+
platform: Platform name (e.g., "twitter", "linkedin", "instagram", "tiktok").
|
|
331
|
+
status: Current status of the operation.
|
|
332
|
+
progress: Current progress value (0-100 for percentage, or bytes uploaded).
|
|
333
|
+
total: Total value for completion (100 for percentage, or total bytes).
|
|
334
|
+
message: Optional human-readable status message.
|
|
335
|
+
entity_id: Optional platform-specific ID (media_id, post_id, container_id).
|
|
336
|
+
file_path: Optional file path for upload operations.
|
|
337
|
+
bytes_uploaded: Optional bytes uploaded so far.
|
|
338
|
+
total_bytes: Optional total bytes to upload.
|
|
339
|
+
|
|
340
|
+
Example:
|
|
341
|
+
>>> # Progress callback for media upload
|
|
342
|
+
>>> def on_progress(event: ProgressEvent) -> None:
|
|
343
|
+
... print(f"{event.operation}: {event.percentage:.1f}% - {event.message}")
|
|
344
|
+
...
|
|
345
|
+
>>> # Async progress callback
|
|
346
|
+
>>> async def on_progress_async(event: ProgressEvent) -> None:
|
|
347
|
+
... await log_to_database(event)
|
|
348
|
+
|
|
349
|
+
>>> event = ProgressEvent(
|
|
350
|
+
... operation="upload_media",
|
|
351
|
+
... platform="twitter",
|
|
352
|
+
... status=ProgressStatus.UPLOADING,
|
|
353
|
+
... progress=50,
|
|
354
|
+
... total=100,
|
|
355
|
+
... message="Uploading image 1 of 2",
|
|
356
|
+
... bytes_uploaded=524288,
|
|
357
|
+
... total_bytes=1048576,
|
|
358
|
+
... )
|
|
359
|
+
>>> print(event.percentage) # 50.0
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
operation: str
|
|
363
|
+
platform: str
|
|
364
|
+
status: ProgressStatus
|
|
365
|
+
progress: int
|
|
366
|
+
total: int
|
|
367
|
+
message: str | None = None
|
|
368
|
+
|
|
369
|
+
# Optional detailed info
|
|
370
|
+
entity_id: str | None = None
|
|
371
|
+
file_path: str | None = None
|
|
372
|
+
bytes_uploaded: int | None = None
|
|
373
|
+
total_bytes: int | None = None
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def percentage(self) -> float:
|
|
377
|
+
"""Calculate progress as a percentage.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Progress percentage (0.0 to 100.0).
|
|
381
|
+
"""
|
|
382
|
+
if self.total == 0:
|
|
383
|
+
return 0.0
|
|
384
|
+
return (self.progress / self.total) * 100
|
|
385
|
+
|
|
386
|
+
model_config = ConfigDict(frozen=True)
|
|
387
|
+
|
|
388
|
+
|
|
297
389
|
class PostCreateRequest(BaseModel):
|
|
298
|
-
"""
|
|
390
|
+
"""Base request model for creating a new post.
|
|
391
|
+
|
|
392
|
+
This is a minimal base model with universal fields that work across all platforms.
|
|
393
|
+
For platform-specific features, use the dedicated request models:
|
|
394
|
+
- TwitterPostRequest
|
|
395
|
+
- LinkedInPostRequest
|
|
396
|
+
- InstagramPostRequest
|
|
397
|
+
- TikTokPostRequest
|
|
299
398
|
|
|
300
399
|
Attributes:
|
|
301
400
|
content: Text content of the post
|
|
@@ -305,7 +404,7 @@ class PostCreateRequest(BaseModel):
|
|
|
305
404
|
link: URL to include in the post
|
|
306
405
|
tags: List of hashtags or user tags
|
|
307
406
|
location: Location/place tag for the post
|
|
308
|
-
additional_data: Platform-specific data
|
|
407
|
+
additional_data: Platform-specific data for backward compatibility
|
|
309
408
|
|
|
310
409
|
Example:
|
|
311
410
|
>>> request = PostCreateRequest(
|
|
@@ -326,10 +425,15 @@ class PostCreateRequest(BaseModel):
|
|
|
326
425
|
|
|
327
426
|
|
|
328
427
|
class PostUpdateRequest(BaseModel):
|
|
329
|
-
"""
|
|
428
|
+
"""Base request model for updating an existing post.
|
|
429
|
+
|
|
430
|
+
Note: Not all platforms support editing posts:
|
|
431
|
+
- Twitter: No editing support (for most users)
|
|
432
|
+
- Instagram: No editing support
|
|
433
|
+
- TikTok: No editing support
|
|
434
|
+
- LinkedIn: Supports updating content, CTA, and landing page
|
|
330
435
|
|
|
331
|
-
|
|
332
|
-
updated vary by platform.
|
|
436
|
+
For LinkedIn-specific update features, use LinkedInPostUpdateRequest.
|
|
333
437
|
|
|
334
438
|
Attributes:
|
|
335
439
|
content: Updated text content
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Instagram platform integration."""
|
|
2
2
|
|
|
3
3
|
from marqetive.platforms.instagram.client import InstagramClient
|
|
4
|
+
from marqetive.platforms.instagram.models import InstagramPostRequest
|
|
4
5
|
|
|
5
|
-
__all__ = ["InstagramClient"]
|
|
6
|
+
__all__ = ["InstagramClient", "InstagramPostRequest"]
|
|
@@ -11,8 +11,9 @@ This module provides comprehensive media management for:
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import asyncio
|
|
14
|
+
import inspect
|
|
14
15
|
import logging
|
|
15
|
-
from collections.abc import Callable
|
|
16
|
+
from collections.abc import Awaitable, Callable
|
|
16
17
|
from dataclasses import dataclass
|
|
17
18
|
from enum import Enum
|
|
18
19
|
from typing import Any, Literal
|
|
@@ -23,8 +24,14 @@ from marqetive.core.exceptions import (
|
|
|
23
24
|
MediaUploadError,
|
|
24
25
|
ValidationError,
|
|
25
26
|
)
|
|
27
|
+
from marqetive.core.models import ProgressEvent, ProgressStatus
|
|
26
28
|
from marqetive.utils.retry import STANDARD_BACKOFF, retry_async
|
|
27
29
|
|
|
30
|
+
# Type aliases for progress callbacks
|
|
31
|
+
type SyncProgressCallback = Callable[[ProgressEvent], None]
|
|
32
|
+
type AsyncProgressCallback = Callable[[ProgressEvent], Awaitable[None]]
|
|
33
|
+
type ProgressCallback = SyncProgressCallback | AsyncProgressCallback
|
|
34
|
+
|
|
28
35
|
logger = logging.getLogger(__name__)
|
|
29
36
|
|
|
30
37
|
# Instagram API limits
|
|
@@ -136,7 +143,7 @@ class InstagramMediaManager:
|
|
|
136
143
|
*,
|
|
137
144
|
api_version: str = "v21.0",
|
|
138
145
|
timeout: float = 30.0,
|
|
139
|
-
progress_callback:
|
|
146
|
+
progress_callback: ProgressCallback | None = None,
|
|
140
147
|
) -> None:
|
|
141
148
|
"""Initialize Instagram media manager.
|
|
142
149
|
|
|
@@ -145,7 +152,8 @@ class InstagramMediaManager:
|
|
|
145
152
|
access_token: Instagram/Facebook access token.
|
|
146
153
|
api_version: Instagram Graph API version.
|
|
147
154
|
timeout: Request timeout in seconds.
|
|
148
|
-
progress_callback: Optional callback
|
|
155
|
+
progress_callback: Optional callback for progress updates.
|
|
156
|
+
Receives ProgressEvent objects with upload status and metrics.
|
|
149
157
|
"""
|
|
150
158
|
self.ig_user_id = ig_user_id
|
|
151
159
|
self.access_token = access_token
|
|
@@ -167,6 +175,44 @@ class InstagramMediaManager:
|
|
|
167
175
|
"""Exit async context and cleanup."""
|
|
168
176
|
await self.client.aclose()
|
|
169
177
|
|
|
178
|
+
async def _emit_progress(
|
|
179
|
+
self,
|
|
180
|
+
status: ProgressStatus,
|
|
181
|
+
progress: int,
|
|
182
|
+
total: int,
|
|
183
|
+
message: str | None = None,
|
|
184
|
+
*,
|
|
185
|
+
entity_id: str | None = None,
|
|
186
|
+
file_path: str | None = None,
|
|
187
|
+
bytes_uploaded: int | None = None,
|
|
188
|
+
total_bytes: int | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Emit a progress update if a callback is registered.
|
|
191
|
+
|
|
192
|
+
Supports both sync and async callbacks.
|
|
193
|
+
"""
|
|
194
|
+
if self.progress_callback is None:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
event = ProgressEvent(
|
|
198
|
+
operation="upload_media",
|
|
199
|
+
platform="instagram",
|
|
200
|
+
status=status,
|
|
201
|
+
progress=progress,
|
|
202
|
+
total=total,
|
|
203
|
+
message=message,
|
|
204
|
+
entity_id=entity_id,
|
|
205
|
+
file_path=file_path,
|
|
206
|
+
bytes_uploaded=bytes_uploaded,
|
|
207
|
+
total_bytes=total_bytes,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
result = self.progress_callback(event)
|
|
211
|
+
|
|
212
|
+
# If callback returned a coroutine, await it
|
|
213
|
+
if inspect.iscoroutine(result):
|
|
214
|
+
await result
|
|
215
|
+
|
|
170
216
|
async def create_feed_containers(
|
|
171
217
|
self,
|
|
172
218
|
media_items: list[MediaItem],
|
|
@@ -260,9 +306,14 @@ class InstagramMediaManager:
|
|
|
260
306
|
container_ids.append(container_id)
|
|
261
307
|
|
|
262
308
|
# Notify progress
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
309
|
+
progress = int(((idx + 1) / len(media_items)) * 100)
|
|
310
|
+
await self._emit_progress(
|
|
311
|
+
status=ProgressStatus.COMPLETED,
|
|
312
|
+
progress=progress,
|
|
313
|
+
total=100,
|
|
314
|
+
message=f"Created container {idx + 1}/{len(media_items)}",
|
|
315
|
+
entity_id=container_id,
|
|
316
|
+
)
|
|
266
317
|
|
|
267
318
|
# If carousel, create parent container
|
|
268
319
|
if is_carousel:
|
|
@@ -355,8 +406,13 @@ class InstagramMediaManager:
|
|
|
355
406
|
media_type="reel",
|
|
356
407
|
)
|
|
357
408
|
|
|
358
|
-
|
|
359
|
-
|
|
409
|
+
await self._emit_progress(
|
|
410
|
+
status=ProgressStatus.COMPLETED,
|
|
411
|
+
progress=100,
|
|
412
|
+
total=100,
|
|
413
|
+
message="Reel container ready for publishing",
|
|
414
|
+
entity_id=container_id,
|
|
415
|
+
)
|
|
360
416
|
|
|
361
417
|
return container_id
|
|
362
418
|
|
|
@@ -426,8 +482,13 @@ class InstagramMediaManager:
|
|
|
426
482
|
media_type="story",
|
|
427
483
|
)
|
|
428
484
|
|
|
429
|
-
|
|
430
|
-
|
|
485
|
+
await self._emit_progress(
|
|
486
|
+
status=ProgressStatus.COMPLETED,
|
|
487
|
+
progress=100,
|
|
488
|
+
total=100,
|
|
489
|
+
message="Story container ready for publishing",
|
|
490
|
+
entity_id=container_id,
|
|
491
|
+
)
|
|
431
492
|
|
|
432
493
|
return container_id
|
|
433
494
|
|
|
@@ -549,9 +610,14 @@ class InstagramMediaManager:
|
|
|
549
610
|
)
|
|
550
611
|
|
|
551
612
|
# Notify progress
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
613
|
+
progress = min(int((elapsed / timeout) * 90), 90) # Cap at 90%
|
|
614
|
+
await self._emit_progress(
|
|
615
|
+
status=ProgressStatus.PROCESSING,
|
|
616
|
+
progress=progress,
|
|
617
|
+
total=100,
|
|
618
|
+
message=f"Processing {media_type} container",
|
|
619
|
+
entity_id=container_id,
|
|
620
|
+
)
|
|
555
621
|
|
|
556
622
|
await asyncio.sleep(check_interval)
|
|
557
623
|
elapsed += check_interval
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Instagram-specific models for post creation.
|
|
2
|
+
|
|
3
|
+
This module defines Instagram-specific data models for creating feed posts,
|
|
4
|
+
carousels, reels, and stories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from marqetive.core.models import MediaType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InstagramPostRequest(BaseModel):
|
|
15
|
+
"""Instagram-specific post creation request.
|
|
16
|
+
|
|
17
|
+
Supports feed posts, carousels, reels, and stories.
|
|
18
|
+
Instagram requires media for all posts (no text-only posts).
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
caption: Post caption/text
|
|
22
|
+
media_urls: List of media URLs (required, 1 for single, 2-10 for carousel)
|
|
23
|
+
media_ids: List of pre-uploaded media container IDs
|
|
24
|
+
media_type: Type of post (IMAGE, VIDEO, CAROUSEL, REEL, STORY)
|
|
25
|
+
alt_texts: Alt text for each media item (accessibility)
|
|
26
|
+
location_id: Facebook Place ID for location tag
|
|
27
|
+
cover_url: Cover/thumbnail image URL (for Reels)
|
|
28
|
+
share_to_feed: Share Reel/Story to main feed
|
|
29
|
+
collaborators: List of collaborator Instagram user IDs
|
|
30
|
+
product_tags: Product tags for shopping posts
|
|
31
|
+
audio_name: Audio track name (for Reels)
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> # Single image post
|
|
35
|
+
>>> request = InstagramPostRequest(
|
|
36
|
+
... caption="Beautiful sunset!",
|
|
37
|
+
... media_urls=["https://example.com/sunset.jpg"],
|
|
38
|
+
... media_type=MediaType.IMAGE,
|
|
39
|
+
... alt_texts=["A colorful sunset over the ocean"]
|
|
40
|
+
... )
|
|
41
|
+
|
|
42
|
+
>>> # Carousel post
|
|
43
|
+
>>> request = InstagramPostRequest(
|
|
44
|
+
... caption="Our product lineup",
|
|
45
|
+
... media_urls=[
|
|
46
|
+
... "https://example.com/product1.jpg",
|
|
47
|
+
... "https://example.com/product2.jpg",
|
|
48
|
+
... "https://example.com/product3.jpg"
|
|
49
|
+
... ],
|
|
50
|
+
... media_type=MediaType.CAROUSEL,
|
|
51
|
+
... alt_texts=["Product 1", "Product 2", "Product 3"]
|
|
52
|
+
... )
|
|
53
|
+
|
|
54
|
+
>>> # Reel
|
|
55
|
+
>>> request = InstagramPostRequest(
|
|
56
|
+
... caption="Check out this tutorial!",
|
|
57
|
+
... media_urls=["https://example.com/tutorial.mp4"],
|
|
58
|
+
... media_type=MediaType.REEL,
|
|
59
|
+
... cover_url="https://example.com/thumbnail.jpg",
|
|
60
|
+
... share_to_feed=True
|
|
61
|
+
... )
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
caption: str | None = None
|
|
65
|
+
media_urls: list[str] = Field(default_factory=list)
|
|
66
|
+
media_ids: list[str] = Field(default_factory=list)
|
|
67
|
+
media_type: MediaType = MediaType.IMAGE
|
|
68
|
+
alt_texts: list[str] = Field(default_factory=list)
|
|
69
|
+
location_id: str | None = None
|
|
70
|
+
cover_url: str | None = None
|
|
71
|
+
share_to_feed: bool = True
|
|
72
|
+
collaborators: list[str] = Field(default_factory=list)
|
|
73
|
+
product_tags: list[dict[str, Any]] = Field(default_factory=list)
|
|
74
|
+
audio_name: str | None = None
|
|
@@ -1,5 +1,54 @@
|
|
|
1
|
-
"""LinkedIn platform integration.
|
|
1
|
+
"""LinkedIn platform integration using the Community Management API.
|
|
2
|
+
|
|
3
|
+
This module provides the LinkedIn client and related models for interacting
|
|
4
|
+
with LinkedIn's Community Management API, supporting:
|
|
5
|
+
- Posts (create, read, update, delete, list)
|
|
6
|
+
- Comments (with nested replies)
|
|
7
|
+
- Reactions (Like, Celebrate, Love, Insightful, Support, Funny)
|
|
8
|
+
- Social metadata and engagement metrics
|
|
9
|
+
- Organization (Company Page) management
|
|
10
|
+
- Media uploads (images, videos, documents)
|
|
11
|
+
"""
|
|
2
12
|
|
|
3
13
|
from marqetive.platforms.linkedin.client import LinkedInClient
|
|
14
|
+
from marqetive.platforms.linkedin.media import (
|
|
15
|
+
LinkedInMediaManager,
|
|
16
|
+
MediaAsset,
|
|
17
|
+
UploadProgress,
|
|
18
|
+
VideoProcessingState,
|
|
19
|
+
)
|
|
20
|
+
from marqetive.platforms.linkedin.models import (
|
|
21
|
+
CallToActionLabel,
|
|
22
|
+
CommentsState,
|
|
23
|
+
FeedDistribution,
|
|
24
|
+
LinkedInPostRequest,
|
|
25
|
+
LinkedInPostUpdateRequest,
|
|
26
|
+
Organization,
|
|
27
|
+
OrganizationType,
|
|
28
|
+
PostVisibility,
|
|
29
|
+
Reaction,
|
|
30
|
+
ReactionType,
|
|
31
|
+
SocialMetadata,
|
|
32
|
+
)
|
|
4
33
|
|
|
5
|
-
__all__ = [
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Client
|
|
36
|
+
"LinkedInClient",
|
|
37
|
+
# Media
|
|
38
|
+
"LinkedInMediaManager",
|
|
39
|
+
"MediaAsset",
|
|
40
|
+
"UploadProgress",
|
|
41
|
+
"VideoProcessingState",
|
|
42
|
+
# Models
|
|
43
|
+
"CallToActionLabel",
|
|
44
|
+
"CommentsState",
|
|
45
|
+
"FeedDistribution",
|
|
46
|
+
"LinkedInPostRequest",
|
|
47
|
+
"LinkedInPostUpdateRequest",
|
|
48
|
+
"Organization",
|
|
49
|
+
"OrganizationType",
|
|
50
|
+
"PostVisibility",
|
|
51
|
+
"Reaction",
|
|
52
|
+
"ReactionType",
|
|
53
|
+
"SocialMetadata",
|
|
54
|
+
]
|