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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""TikTok-specific models for post creation.
|
|
2
|
+
|
|
3
|
+
This module defines TikTok-specific data models for creating video posts,
|
|
4
|
+
including privacy settings and content toggles.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PrivacyLevel(StrEnum):
|
|
13
|
+
"""Privacy level options for TikTok posts.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
PUBLIC: Visible to everyone
|
|
17
|
+
FRIENDS: Visible to mutual followers/friends only
|
|
18
|
+
PRIVATE: Visible only to the author (for unaudited apps)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
PUBLIC = "PUBLIC_TO_EVERYONE"
|
|
22
|
+
FRIENDS = "MUTUAL_FOLLOW_FRIENDS"
|
|
23
|
+
PRIVATE = "SELF_ONLY"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TikTokPostRequest(BaseModel):
|
|
27
|
+
"""TikTok-specific post creation request.
|
|
28
|
+
|
|
29
|
+
TikTok only supports video posts. Videos must be between 3 seconds
|
|
30
|
+
and 10 minutes, and under 4GB in size.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
title: Video title/caption (max 2200 characters)
|
|
34
|
+
video_url: URL to video file (required)
|
|
35
|
+
video_id: Pre-uploaded video ID
|
|
36
|
+
privacy_level: Privacy setting (PUBLIC, FRIENDS, PRIVATE)
|
|
37
|
+
disable_comment: Disable comments on the video
|
|
38
|
+
disable_duet: Disable duet feature
|
|
39
|
+
disable_stitch: Disable stitch feature
|
|
40
|
+
video_cover_timestamp_ms: Timestamp in ms for auto-generated cover
|
|
41
|
+
brand_content_toggle: Mark as branded/sponsored content
|
|
42
|
+
brand_organic_toggle: Mark as organic branded content
|
|
43
|
+
schedule_time: Unix timestamp to schedule post (10 mins to 10 days ahead)
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> # Public video post
|
|
47
|
+
>>> request = TikTokPostRequest(
|
|
48
|
+
... title="Check out this dance!",
|
|
49
|
+
... video_url="https://example.com/dance.mp4",
|
|
50
|
+
... privacy_level=PrivacyLevel.PUBLIC
|
|
51
|
+
... )
|
|
52
|
+
|
|
53
|
+
>>> # Private video with features disabled
|
|
54
|
+
>>> request = TikTokPostRequest(
|
|
55
|
+
... title="Personal video",
|
|
56
|
+
... video_url="https://example.com/video.mp4",
|
|
57
|
+
... privacy_level=PrivacyLevel.PRIVATE,
|
|
58
|
+
... disable_comment=True,
|
|
59
|
+
... disable_duet=True,
|
|
60
|
+
... disable_stitch=True
|
|
61
|
+
... )
|
|
62
|
+
|
|
63
|
+
>>> # Branded content
|
|
64
|
+
>>> request = TikTokPostRequest(
|
|
65
|
+
... title="Sponsored review",
|
|
66
|
+
... video_url="https://example.com/review.mp4",
|
|
67
|
+
... privacy_level=PrivacyLevel.PUBLIC,
|
|
68
|
+
... brand_content_toggle=True
|
|
69
|
+
... )
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
title: str | None = None
|
|
73
|
+
video_url: str | None = None
|
|
74
|
+
video_id: str | None = None
|
|
75
|
+
privacy_level: PrivacyLevel = PrivacyLevel.PRIVATE
|
|
76
|
+
disable_comment: bool = False
|
|
77
|
+
disable_duet: bool = False
|
|
78
|
+
disable_stitch: bool = False
|
|
79
|
+
video_cover_timestamp_ms: int | None = None
|
|
80
|
+
brand_content_toggle: bool = False
|
|
81
|
+
brand_organic_toggle: bool = False
|
|
82
|
+
schedule_time: int | None = Field(default=None, description="Unix timestamp")
|
|
@@ -527,6 +527,92 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
527
527
|
media_type=media_type,
|
|
528
528
|
) from e
|
|
529
529
|
|
|
530
|
+
# ==================== Thread Methods ====================
|
|
531
|
+
|
|
532
|
+
async def create_thread(
|
|
533
|
+
self,
|
|
534
|
+
posts: list[PostCreateRequest],
|
|
535
|
+
) -> list[Post]:
|
|
536
|
+
"""Create a Twitter thread (multiple linked tweets).
|
|
537
|
+
|
|
538
|
+
Each tweet in the list can have its own content, media, polls, and alt texts.
|
|
539
|
+
Tweets are posted sequentially, with each tweet replying to the previous one.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
posts: List of PostCreateRequest objects to create as a thread.
|
|
543
|
+
First tweet is the head of the thread.
|
|
544
|
+
Use TwitterPostRequest for Twitter-specific features.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
List of Post objects for each tweet in the thread.
|
|
548
|
+
|
|
549
|
+
Raises:
|
|
550
|
+
ValidationError: If posts list is empty.
|
|
551
|
+
PlatformAuthError: If not authenticated.
|
|
552
|
+
MediaUploadError: If media upload fails.
|
|
553
|
+
RuntimeError: If client not used as context manager.
|
|
554
|
+
|
|
555
|
+
Example:
|
|
556
|
+
>>> from marqetive.platforms.twitter import TwitterPostRequest
|
|
557
|
+
>>> tweets = [
|
|
558
|
+
... TwitterPostRequest(content="Thread start! 1/3"),
|
|
559
|
+
... TwitterPostRequest(content="Middle 2/3", media_urls=["..."]),
|
|
560
|
+
... TwitterPostRequest(content="End 3/3", poll_options=["Yes", "No"]),
|
|
561
|
+
... ]
|
|
562
|
+
>>> async with TwitterClient(credentials) as client:
|
|
563
|
+
... thread_posts = await client.create_thread(tweets)
|
|
564
|
+
... for post in thread_posts:
|
|
565
|
+
... print(f"Tweet {post.post_id}: {post.content}")
|
|
566
|
+
"""
|
|
567
|
+
from marqetive.core.models import ProgressStatus
|
|
568
|
+
from marqetive.platforms.twitter.models import TwitterPostRequest
|
|
569
|
+
|
|
570
|
+
if not posts:
|
|
571
|
+
raise ValidationError(
|
|
572
|
+
"At least one tweet is required for thread creation",
|
|
573
|
+
platform=self.platform_name,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
created_posts: list[Post] = []
|
|
577
|
+
reply_to_id: str | None = None
|
|
578
|
+
|
|
579
|
+
for idx, post_request in enumerate(posts):
|
|
580
|
+
# Convert to TwitterPostRequest if needed and set reply chain
|
|
581
|
+
if isinstance(post_request, TwitterPostRequest):
|
|
582
|
+
if reply_to_id is not None:
|
|
583
|
+
post_request = post_request.model_copy(
|
|
584
|
+
update={"reply_to_post_id": reply_to_id}
|
|
585
|
+
)
|
|
586
|
+
# TwitterPostRequest works with create_post via duck typing
|
|
587
|
+
final_request = post_request
|
|
588
|
+
else:
|
|
589
|
+
# Create TwitterPostRequest from generic PostCreateRequest
|
|
590
|
+
request_data: dict[str, Any] = {
|
|
591
|
+
"content": post_request.content,
|
|
592
|
+
"media_urls": post_request.media_urls,
|
|
593
|
+
"media_ids": post_request.media_ids,
|
|
594
|
+
}
|
|
595
|
+
if reply_to_id is not None:
|
|
596
|
+
request_data["reply_to_post_id"] = reply_to_id
|
|
597
|
+
final_request = TwitterPostRequest(**request_data)
|
|
598
|
+
|
|
599
|
+
# TwitterPostRequest has compatible interface with PostCreateRequest
|
|
600
|
+
created_post = await self.create_post(final_request) # type: ignore[arg-type]
|
|
601
|
+
created_posts.append(created_post)
|
|
602
|
+
reply_to_id = created_post.post_id
|
|
603
|
+
|
|
604
|
+
# Emit progress
|
|
605
|
+
await self._emit_progress(
|
|
606
|
+
operation="create_thread",
|
|
607
|
+
status=ProgressStatus.PROCESSING,
|
|
608
|
+
progress=idx + 1,
|
|
609
|
+
total=len(posts),
|
|
610
|
+
message=f"Tweet {idx + 1}/{len(posts)} created",
|
|
611
|
+
entity_id=created_post.post_id,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
return created_posts
|
|
615
|
+
|
|
530
616
|
# ==================== Helper Methods ====================
|
|
531
617
|
|
|
532
618
|
def _parse_tweet(
|
|
@@ -9,9 +9,10 @@ This module provides comprehensive media upload functionality for Twitter API v2
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import asyncio
|
|
12
|
+
import inspect
|
|
12
13
|
import logging
|
|
13
14
|
import os
|
|
14
|
-
from collections.abc import Callable
|
|
15
|
+
from collections.abc import Awaitable, Callable
|
|
15
16
|
from dataclasses import dataclass
|
|
16
17
|
from enum import Enum
|
|
17
18
|
from typing import Any, Literal
|
|
@@ -22,6 +23,7 @@ from marqetive.core.exceptions import (
|
|
|
22
23
|
InvalidFileTypeError,
|
|
23
24
|
MediaUploadError,
|
|
24
25
|
)
|
|
26
|
+
from marqetive.core.models import ProgressEvent, ProgressStatus
|
|
25
27
|
from marqetive.utils.file_handlers import download_file
|
|
26
28
|
from marqetive.utils.media import (
|
|
27
29
|
detect_mime_type,
|
|
@@ -30,6 +32,14 @@ from marqetive.utils.media import (
|
|
|
30
32
|
)
|
|
31
33
|
from marqetive.utils.retry import STANDARD_BACKOFF, retry_async
|
|
32
34
|
|
|
35
|
+
# Type aliases for progress callbacks
|
|
36
|
+
type SyncProgressCallback = Callable[[ProgressEvent], None]
|
|
37
|
+
type AsyncProgressCallback = Callable[[ProgressEvent], Awaitable[None]]
|
|
38
|
+
type ProgressCallback = SyncProgressCallback | AsyncProgressCallback
|
|
39
|
+
|
|
40
|
+
# Legacy callback type for backward compatibility
|
|
41
|
+
type LegacyProgressCallback = Callable[["UploadProgress"], None]
|
|
42
|
+
|
|
33
43
|
logger = logging.getLogger(__name__)
|
|
34
44
|
|
|
35
45
|
# Constants
|
|
@@ -76,6 +86,10 @@ SUPPORTED_GIF_TYPE = "image/gif"
|
|
|
76
86
|
class UploadProgress:
|
|
77
87
|
"""Progress information for media upload.
|
|
78
88
|
|
|
89
|
+
.. deprecated:: 0.2.0
|
|
90
|
+
Use :class:`marqetive.core.models.ProgressEvent` instead.
|
|
91
|
+
This class will be removed in a future version.
|
|
92
|
+
|
|
79
93
|
Attributes:
|
|
80
94
|
media_id: Twitter media ID (if available).
|
|
81
95
|
file_path: Path to file being uploaded.
|
|
@@ -136,13 +150,21 @@ class TwitterMediaManager:
|
|
|
136
150
|
>>> manager = TwitterMediaManager(bearer_token="your_token")
|
|
137
151
|
>>> result = await manager.upload_media("/path/to/image.jpg")
|
|
138
152
|
>>> print(f"Media ID: {result.media_id}")
|
|
153
|
+
|
|
154
|
+
>>> # With progress callback
|
|
155
|
+
>>> def on_progress(event: ProgressEvent) -> None:
|
|
156
|
+
... print(f"{event.operation}: {event.percentage:.1f}%")
|
|
157
|
+
>>> manager = TwitterMediaManager(
|
|
158
|
+
... bearer_token="your_token",
|
|
159
|
+
... progress_callback=on_progress,
|
|
160
|
+
... )
|
|
139
161
|
"""
|
|
140
162
|
|
|
141
163
|
def __init__(
|
|
142
164
|
self,
|
|
143
165
|
bearer_token: str,
|
|
144
166
|
*,
|
|
145
|
-
progress_callback:
|
|
167
|
+
progress_callback: ProgressCallback | None = None,
|
|
146
168
|
timeout: float = DEFAULT_REQUEST_TIMEOUT,
|
|
147
169
|
) -> None:
|
|
148
170
|
"""Initialize Twitter media manager.
|
|
@@ -150,6 +172,7 @@ class TwitterMediaManager:
|
|
|
150
172
|
Args:
|
|
151
173
|
bearer_token: Twitter OAuth 2.0 bearer token.
|
|
152
174
|
progress_callback: Optional callback for progress updates.
|
|
175
|
+
Accepts ProgressEvent and can be sync or async.
|
|
153
176
|
timeout: Request timeout in seconds.
|
|
154
177
|
"""
|
|
155
178
|
self.bearer_token = bearer_token
|
|
@@ -163,6 +186,44 @@ class TwitterMediaManager:
|
|
|
163
186
|
headers={"Authorization": f"Bearer {bearer_token}"},
|
|
164
187
|
)
|
|
165
188
|
|
|
189
|
+
async def _emit_progress(
|
|
190
|
+
self,
|
|
191
|
+
status: ProgressStatus,
|
|
192
|
+
progress: int,
|
|
193
|
+
total: int,
|
|
194
|
+
message: str | None = None,
|
|
195
|
+
*,
|
|
196
|
+
entity_id: str | None = None,
|
|
197
|
+
file_path: str | None = None,
|
|
198
|
+
bytes_uploaded: int | None = None,
|
|
199
|
+
total_bytes: int | None = None,
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Emit a progress update if a callback is registered.
|
|
202
|
+
|
|
203
|
+
Supports both sync and async callbacks.
|
|
204
|
+
"""
|
|
205
|
+
if self.progress_callback is None:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
event = ProgressEvent(
|
|
209
|
+
operation="upload_media",
|
|
210
|
+
platform="twitter",
|
|
211
|
+
status=status,
|
|
212
|
+
progress=progress,
|
|
213
|
+
total=total,
|
|
214
|
+
message=message,
|
|
215
|
+
entity_id=entity_id,
|
|
216
|
+
file_path=file_path,
|
|
217
|
+
bytes_uploaded=bytes_uploaded,
|
|
218
|
+
total_bytes=total_bytes,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
result = self.progress_callback(event)
|
|
222
|
+
|
|
223
|
+
# If callback returned a coroutine, await it
|
|
224
|
+
if inspect.iscoroutine(result):
|
|
225
|
+
await result
|
|
226
|
+
|
|
166
227
|
async def __aenter__(self) -> "TwitterMediaManager":
|
|
167
228
|
"""Enter async context."""
|
|
168
229
|
return self
|
|
@@ -264,15 +325,15 @@ class TwitterMediaManager:
|
|
|
264
325
|
file_size = os.path.getsize(file_path)
|
|
265
326
|
|
|
266
327
|
# Notify upload start
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
328
|
+
await self._emit_progress(
|
|
329
|
+
status=ProgressStatus.INITIALIZING,
|
|
330
|
+
progress=0,
|
|
331
|
+
total=100,
|
|
332
|
+
message="Initializing upload",
|
|
333
|
+
file_path=file_path,
|
|
334
|
+
bytes_uploaded=0,
|
|
335
|
+
total_bytes=file_size,
|
|
336
|
+
)
|
|
276
337
|
|
|
277
338
|
@retry_async(config=STANDARD_BACKOFF)
|
|
278
339
|
async def _do_upload() -> MediaUploadResult:
|
|
@@ -290,15 +351,15 @@ class TwitterMediaManager:
|
|
|
290
351
|
data["additional_owners"] = ",".join(additional_owners)
|
|
291
352
|
|
|
292
353
|
# Notify upload in progress
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
354
|
+
await self._emit_progress(
|
|
355
|
+
status=ProgressStatus.UPLOADING,
|
|
356
|
+
progress=0,
|
|
357
|
+
total=100,
|
|
358
|
+
message="Uploading file",
|
|
359
|
+
file_path=file_path,
|
|
360
|
+
bytes_uploaded=0,
|
|
361
|
+
total_bytes=file_size,
|
|
362
|
+
)
|
|
302
363
|
|
|
303
364
|
# Upload
|
|
304
365
|
response = await self.client.post(
|
|
@@ -319,15 +380,16 @@ class TwitterMediaManager:
|
|
|
319
380
|
)
|
|
320
381
|
|
|
321
382
|
# Notify completion
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
383
|
+
await self._emit_progress(
|
|
384
|
+
status=ProgressStatus.COMPLETED,
|
|
385
|
+
progress=100,
|
|
386
|
+
total=100,
|
|
387
|
+
message="Upload completed",
|
|
388
|
+
entity_id=media_id,
|
|
389
|
+
file_path=file_path,
|
|
390
|
+
bytes_uploaded=file_size,
|
|
391
|
+
total_bytes=file_size,
|
|
392
|
+
)
|
|
331
393
|
|
|
332
394
|
logger.info(f"Simple upload completed: {media_id}")
|
|
333
395
|
return result
|
|
@@ -388,15 +450,16 @@ class TwitterMediaManager:
|
|
|
388
450
|
)
|
|
389
451
|
|
|
390
452
|
# Notify upload start
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
453
|
+
await self._emit_progress(
|
|
454
|
+
status=ProgressStatus.UPLOADING,
|
|
455
|
+
progress=0,
|
|
456
|
+
total=100,
|
|
457
|
+
message="Starting chunked upload",
|
|
458
|
+
entity_id=media_id,
|
|
459
|
+
file_path=file_path,
|
|
460
|
+
bytes_uploaded=0,
|
|
461
|
+
total_bytes=file_size,
|
|
462
|
+
)
|
|
400
463
|
|
|
401
464
|
# STEP 2: Upload chunks
|
|
402
465
|
bytes_uploaded = 0
|
|
@@ -419,15 +482,17 @@ class TwitterMediaManager:
|
|
|
419
482
|
segment_index += 1
|
|
420
483
|
|
|
421
484
|
# Notify progress
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
485
|
+
progress_pct = int((bytes_uploaded / file_size) * 100)
|
|
486
|
+
await self._emit_progress(
|
|
487
|
+
status=ProgressStatus.UPLOADING,
|
|
488
|
+
progress=progress_pct,
|
|
489
|
+
total=100,
|
|
490
|
+
message=f"Uploading chunk {segment_index}",
|
|
491
|
+
entity_id=media_id,
|
|
492
|
+
file_path=file_path,
|
|
493
|
+
bytes_uploaded=bytes_uploaded,
|
|
494
|
+
total_bytes=file_size,
|
|
495
|
+
)
|
|
431
496
|
|
|
432
497
|
logger.debug(
|
|
433
498
|
f"Uploaded chunk {segment_index}: "
|
|
@@ -443,15 +508,16 @@ class TwitterMediaManager:
|
|
|
443
508
|
await self._wait_for_processing(result, file_path)
|
|
444
509
|
|
|
445
510
|
# Notify completion
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
511
|
+
await self._emit_progress(
|
|
512
|
+
status=ProgressStatus.COMPLETED,
|
|
513
|
+
progress=100,
|
|
514
|
+
total=100,
|
|
515
|
+
message="Chunked upload completed",
|
|
516
|
+
entity_id=media_id,
|
|
517
|
+
file_path=file_path,
|
|
518
|
+
bytes_uploaded=file_size,
|
|
519
|
+
total_bytes=file_size,
|
|
520
|
+
)
|
|
455
521
|
|
|
456
522
|
logger.info(f"Chunked upload completed: {media_id}")
|
|
457
523
|
return result
|
|
@@ -662,15 +728,17 @@ class TwitterMediaManager:
|
|
|
662
728
|
)
|
|
663
729
|
|
|
664
730
|
# Notify processing status
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
731
|
+
file_size = os.path.getsize(file_path)
|
|
732
|
+
await self._emit_progress(
|
|
733
|
+
status=ProgressStatus.PROCESSING,
|
|
734
|
+
progress=90, # Show 90% during processing
|
|
735
|
+
total=100,
|
|
736
|
+
message=f"Processing media ({state})",
|
|
737
|
+
entity_id=result.media_id,
|
|
738
|
+
file_path=file_path,
|
|
739
|
+
bytes_uploaded=file_size,
|
|
740
|
+
total_bytes=file_size,
|
|
741
|
+
)
|
|
674
742
|
|
|
675
743
|
await asyncio.sleep(check_after)
|
|
676
744
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Twitter/X-specific models for post creation.
|
|
2
|
+
|
|
3
|
+
This module defines Twitter-specific data models for creating tweets,
|
|
4
|
+
replies, quote tweets, and polls.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TwitterPostRequest(BaseModel):
|
|
11
|
+
"""Twitter/X-specific post creation request.
|
|
12
|
+
|
|
13
|
+
Supports tweets, replies, quote tweets, and media attachments.
|
|
14
|
+
Twitter has a 280 character limit for text content.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
content: Tweet text (max 280 characters)
|
|
18
|
+
media_urls: List of media URLs to attach (max 4 images or 1 video)
|
|
19
|
+
media_ids: List of pre-uploaded media IDs
|
|
20
|
+
reply_to_post_id: Tweet ID to reply to
|
|
21
|
+
quote_post_id: Tweet ID to quote
|
|
22
|
+
poll_options: List of poll options (2-4 options, each max 25 chars)
|
|
23
|
+
poll_duration_minutes: Poll duration in minutes (5-10080)
|
|
24
|
+
alt_texts: Alt text for each media item (for accessibility)
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
>>> # Simple tweet
|
|
28
|
+
>>> request = TwitterPostRequest(content="Hello Twitter!")
|
|
29
|
+
|
|
30
|
+
>>> # Reply to a tweet
|
|
31
|
+
>>> request = TwitterPostRequest(
|
|
32
|
+
... content="Great point!",
|
|
33
|
+
... reply_to_post_id="1234567890"
|
|
34
|
+
... )
|
|
35
|
+
|
|
36
|
+
>>> # Quote tweet with media
|
|
37
|
+
>>> request = TwitterPostRequest(
|
|
38
|
+
... content="Check this out!",
|
|
39
|
+
... quote_post_id="1234567890",
|
|
40
|
+
... media_urls=["https://example.com/image.jpg"]
|
|
41
|
+
... )
|
|
42
|
+
|
|
43
|
+
>>> # Tweet with poll
|
|
44
|
+
>>> request = TwitterPostRequest(
|
|
45
|
+
... content="What's your favorite?",
|
|
46
|
+
... poll_options=["Option A", "Option B", "Option C"],
|
|
47
|
+
... poll_duration_minutes=1440
|
|
48
|
+
... )
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
content: str | None = None
|
|
52
|
+
media_urls: list[str] = Field(default_factory=list, max_length=4)
|
|
53
|
+
media_ids: list[str] = Field(default_factory=list, max_length=4)
|
|
54
|
+
reply_to_post_id: str | None = None
|
|
55
|
+
quote_post_id: str | None = None
|
|
56
|
+
poll_options: list[str] = Field(default_factory=list, max_length=4)
|
|
57
|
+
poll_duration_minutes: int | None = Field(default=None, ge=5, le=10080)
|
|
58
|
+
alt_texts: list[str] = Field(default_factory=list)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: marqetive-lib
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Modern Python utilities for web APIs
|
|
5
5
|
Keywords: api,utilities,web,http,marqetive
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -238,14 +238,6 @@ except PlatformError as e:
|
|
|
238
238
|
print(f"Platform error: {e}")
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
-
## Requirements
|
|
242
|
-
|
|
243
|
-
- Python 3.12+
|
|
244
|
-
- httpx >= 0.28.1
|
|
245
|
-
- pydantic >= 2.0.0
|
|
246
|
-
- tweepy >= 4.16.0
|
|
247
|
-
- aiofiles >= 24.0.0
|
|
248
|
-
|
|
249
241
|
## Development
|
|
250
242
|
|
|
251
243
|
### Setup Development Environment
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
marqetive/__init__.py,sha256=pW77CUnzOQ0X1pb-GTcRgrrvsSaJBdVhGZLnvCD_4q4,3032
|
|
2
|
+
marqetive/core/__init__.py,sha256=0_0vzxJ619YIJkz1yzSvhnGDJRkrErs_QSg2q3Bloss,1172
|
|
3
|
+
marqetive/core/base.py,sha256=J5iYXpa2C371zVLNMx3eFnC0fdLWqTrjbVcqJQdGyrU,16147
|
|
4
|
+
marqetive/core/client.py,sha256=2_FoNpqaRglsWg10i5RTbyDg_kRQKhgWjYs6iDdFxLg,3210
|
|
5
|
+
marqetive/core/exceptions.py,sha256=Xyj0bzNiZm5VTErmzXgVW8T6IQnOpF92-HJiKPKjIio,7076
|
|
6
|
+
marqetive/core/models.py,sha256=L2gA4FhW0feAXQFsz2ce1ttd0vScMRhatoTclhDGCU0,14727
|
|
7
|
+
marqetive/factory.py,sha256=irZ5oN8a__kXZH70UN2uI7TzqTXu66d4QZ1FoxSoiK8,14092
|
|
8
|
+
marqetive/platforms/__init__.py,sha256=RBxlQSGyELsulSnwf5uaE1ohxFc7jC61OO9CrKaZp48,1312
|
|
9
|
+
marqetive/platforms/instagram/__init__.py,sha256=c1Gs0ozG6D7Z-Uz_UQ7S3joL0qUTT9eUZPWcePyESk8,229
|
|
10
|
+
marqetive/platforms/instagram/client.py,sha256=C5T9v1Aina8F3_Dk3d_I_RiVt7VvxTwwqdYeizDr0iQ,25187
|
|
11
|
+
marqetive/platforms/instagram/exceptions.py,sha256=TcD_pX4eSx_k4yW8DgfA6SGPiAz3VW7cMqM8DmiXIhg,8978
|
|
12
|
+
marqetive/platforms/instagram/media.py,sha256=0ZbUbpwJ025_hccL9X8qced_-LJGoL_-NdS84Op97VE,23228
|
|
13
|
+
marqetive/platforms/instagram/models.py,sha256=20v3m1037y3b_WlsKF8zAOgV23nFu63tfmmUN1CefOI,2769
|
|
14
|
+
marqetive/platforms/linkedin/__init__.py,sha256=_FrdZpqcXjcUW6C-25zGV7poGih9yzs6y1AFnuizTUQ,1384
|
|
15
|
+
marqetive/platforms/linkedin/client.py,sha256=_5sNiu0YDYLh8rLKA_dv0Ggrja8z3dLbk9_oLzFQ2yc,56287
|
|
16
|
+
marqetive/platforms/linkedin/exceptions.py,sha256=i5fARUkZik46bS3htZBwUInVzetsZx1APpKEXLrCKf0,9762
|
|
17
|
+
marqetive/platforms/linkedin/media.py,sha256=iWXUfqDYGsrTqeM6CGZ7a8xjpbdJ5qESolQL8Fv2PIg,20341
|
|
18
|
+
marqetive/platforms/linkedin/models.py,sha256=n7DqwVxYSbGYBmeEJ1woCZ6XhUIHcLx8Gpm8uCBACzI,12620
|
|
19
|
+
marqetive/platforms/tiktok/__init__.py,sha256=BqjkXTZDyBlcY3lvREy13yP9h3RcDga8E6Rl6f5KPp8,238
|
|
20
|
+
marqetive/platforms/tiktok/client.py,sha256=i_nyhVywh4feFu5vv4LrMQOkoLsxfxgpH6aKa9JjdOc,17060
|
|
21
|
+
marqetive/platforms/tiktok/exceptions.py,sha256=vxwyAKujMGZJh0LetG1QsLF95QfUs_kR6ujsWSHGqL0,10124
|
|
22
|
+
marqetive/platforms/tiktok/media.py,sha256=nDijJYq89dQfMFwq58P2PXOvZukxgVRzMrfKTOONn_c,25717
|
|
23
|
+
marqetive/platforms/tiktok/models.py,sha256=acV-fe_3O6kKbZzqytCNCATAwmFpjt0Ac2uacbjt1u8,2822
|
|
24
|
+
marqetive/platforms/twitter/__init__.py,sha256=dvcgVT-v-JOtjSz-OUvxGrn_43OI6w_ep42Wx_nHTSM,217
|
|
25
|
+
marqetive/platforms/twitter/client.py,sha256=08jV2hQVmGOpnG3C05u7bCqL7KapWn7bSsG0wbN_t5M,23270
|
|
26
|
+
marqetive/platforms/twitter/exceptions.py,sha256=eZ-dJKOXH_-bAMg29zWKbEqMFud29piEJ5IWfC9wFts,8926
|
|
27
|
+
marqetive/platforms/twitter/media.py,sha256=_7ka0ENlKr6EMxQQvPS_A38_a8MpjDE2zIrXe20QkuI,27079
|
|
28
|
+
marqetive/platforms/twitter/models.py,sha256=yPQlx40SlNmz7YGasXUqdx7rEDEgrQ64aYovlPKo6oc,2126
|
|
29
|
+
marqetive/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
marqetive/utils/__init__.py,sha256=bSrNajbxYBSKQayrPviLz8JeGjplnyK8y_NGDtgb7yQ,977
|
|
31
|
+
marqetive/utils/file_handlers.py,sha256=4TP5kmWofNTSZmlS683CM1UYP83WvRd_NubMbqtXv-g,12568
|
|
32
|
+
marqetive/utils/helpers.py,sha256=8-ljhL47SremKcQO2GF8DIHOPODEv1rSioVNuSPCbec,2634
|
|
33
|
+
marqetive/utils/media.py,sha256=Rvxw9XKU65n-z4G1bEihG3wXZBmjSDZUqClfjGFrg6k,12013
|
|
34
|
+
marqetive/utils/oauth.py,sha256=LQLXpThZUe0XbSpO3dJ5oW3sPRJuKjSk3_f5_3baUzA,12095
|
|
35
|
+
marqetive/utils/retry.py,sha256=lAniJLMNWp9XsHrvU0XBNifpNEjfde4MGfd5hlFTPfA,7636
|
|
36
|
+
marqetive/utils/token_validator.py,sha256=dNvDeHs2Du5UyMMH2ZOW6ydR7OwOEKA4c9e-rG0f9-0,6698
|
|
37
|
+
marqetive_lib-0.1.7.dist-info/METADATA,sha256=xsPKt4AbFy44aZtNeft0YeaSWNKrS-xWxsavavh2xtM,7875
|
|
38
|
+
marqetive_lib-0.1.7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
39
|
+
marqetive_lib-0.1.7.dist-info/RECORD,,
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
marqetive/__init__.py,sha256=TPjbyZ8ZRux2b6GqSG_7w9A7auPaTkxlamcfYIz0XVw,2907
|
|
2
|
-
marqetive/core/__init__.py,sha256=8n-Ys3qR4wcGUV2rZTz68ICknD1GrkYz82SxLo-dods,1129
|
|
3
|
-
marqetive/core/base.py,sha256=CKNTA6nErZ_Fx8uPtWxgNY054kmSTlGXL81zaF2d0R0,13248
|
|
4
|
-
marqetive/core/client.py,sha256=2_FoNpqaRglsWg10i5RTbyDg_kRQKhgWjYs6iDdFxLg,3210
|
|
5
|
-
marqetive/core/exceptions.py,sha256=Xyj0bzNiZm5VTErmzXgVW8T6IQnOpF92-HJiKPKjIio,7076
|
|
6
|
-
marqetive/core/models.py,sha256=W_yOuRWItWSn82n8vXRNN_ScdNkzY1De2qqXaVN2RGU,10974
|
|
7
|
-
marqetive/factory.py,sha256=irZ5oN8a__kXZH70UN2uI7TzqTXu66d4QZ1FoxSoiK8,14092
|
|
8
|
-
marqetive/platforms/__init__.py,sha256=RBxlQSGyELsulSnwf5uaE1ohxFc7jC61OO9CrKaZp48,1312
|
|
9
|
-
marqetive/platforms/instagram/__init__.py,sha256=7ClfTovAcCHac2DzKS7z1MFuZpy9lcwet7YP5d6MPeY,135
|
|
10
|
-
marqetive/platforms/instagram/client.py,sha256=C5T9v1Aina8F3_Dk3d_I_RiVt7VvxTwwqdYeizDr0iQ,25187
|
|
11
|
-
marqetive/platforms/instagram/exceptions.py,sha256=TcD_pX4eSx_k4yW8DgfA6SGPiAz3VW7cMqM8DmiXIhg,8978
|
|
12
|
-
marqetive/platforms/instagram/media.py,sha256=sZKbBpTac4hIR3OvVV3wL21uHOSQUUpBBKXrvC1zXPc,21161
|
|
13
|
-
marqetive/platforms/linkedin/__init__.py,sha256=zCnokoPYs56iA1sBSYIlaZW2J50L3CbnQpJSaOLrzP8,131
|
|
14
|
-
marqetive/platforms/linkedin/client.py,sha256=cU22ympiWxDPXSUIB0juAQUV3RCpBg-m2nb4D38N5Go,23806
|
|
15
|
-
marqetive/platforms/linkedin/exceptions.py,sha256=i5fARUkZik46bS3htZBwUInVzetsZx1APpKEXLrCKf0,9762
|
|
16
|
-
marqetive/platforms/linkedin/media.py,sha256=lYUKJWbI3mOsvdSOZfUytc4BWb_nIf4YjiKNpC7EpT0,16896
|
|
17
|
-
marqetive/platforms/tiktok/__init__.py,sha256=BQtxdECd2bW9_vV9W-MY4A1rdXi_xurGWWmzTjTUpMM,123
|
|
18
|
-
marqetive/platforms/tiktok/client.py,sha256=i_nyhVywh4feFu5vv4LrMQOkoLsxfxgpH6aKa9JjdOc,17060
|
|
19
|
-
marqetive/platforms/tiktok/exceptions.py,sha256=vxwyAKujMGZJh0LetG1QsLF95QfUs_kR6ujsWSHGqL0,10124
|
|
20
|
-
marqetive/platforms/tiktok/media.py,sha256=p42E0D3bp9x0RgdK7lqcBl2v5G70ZOpY4JTYS-oCgD4,23766
|
|
21
|
-
marqetive/platforms/twitter/__init__.py,sha256=AA5BELRvZyl2WE_7-puSEWArxZjaXcTJ_i8NGOWrv6k,129
|
|
22
|
-
marqetive/platforms/twitter/client.py,sha256=nVXFU1nZBimjzxq0uwCzi0hQy7tdkbW42bbV_A2w8jk,19627
|
|
23
|
-
marqetive/platforms/twitter/exceptions.py,sha256=eZ-dJKOXH_-bAMg29zWKbEqMFud29piEJ5IWfC9wFts,8926
|
|
24
|
-
marqetive/platforms/twitter/media.py,sha256=N8f9UZv1JPJoFDTrPOrvxqdShFU-DQPFBScBoCrZci4,24963
|
|
25
|
-
marqetive/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
marqetive/utils/__init__.py,sha256=bSrNajbxYBSKQayrPviLz8JeGjplnyK8y_NGDtgb7yQ,977
|
|
27
|
-
marqetive/utils/file_handlers.py,sha256=4TP5kmWofNTSZmlS683CM1UYP83WvRd_NubMbqtXv-g,12568
|
|
28
|
-
marqetive/utils/helpers.py,sha256=8-ljhL47SremKcQO2GF8DIHOPODEv1rSioVNuSPCbec,2634
|
|
29
|
-
marqetive/utils/media.py,sha256=Rvxw9XKU65n-z4G1bEihG3wXZBmjSDZUqClfjGFrg6k,12013
|
|
30
|
-
marqetive/utils/oauth.py,sha256=LQLXpThZUe0XbSpO3dJ5oW3sPRJuKjSk3_f5_3baUzA,12095
|
|
31
|
-
marqetive/utils/retry.py,sha256=lAniJLMNWp9XsHrvU0XBNifpNEjfde4MGfd5hlFTPfA,7636
|
|
32
|
-
marqetive/utils/token_validator.py,sha256=dNvDeHs2Du5UyMMH2ZOW6ydR7OwOEKA4c9e-rG0f9-0,6698
|
|
33
|
-
marqetive_lib-0.1.6.dist-info/METADATA,sha256=Xf_dBnNYwqOSf9GHJuormIVzyb1oRAJI_zQOsebTOxE,7986
|
|
34
|
-
marqetive_lib-0.1.6.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
35
|
-
marqetive_lib-0.1.6.dist-info/RECORD,,
|
|
File without changes
|