marqetive-lib 0.1.11__py3-none-any.whl → 0.1.13__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/platforms/twitter/client.py +21 -18
- marqetive/platforms/twitter/media.py +50 -42
- {marqetive_lib-0.1.11.dist-info → marqetive_lib-0.1.13.dist-info}/METADATA +1 -1
- {marqetive_lib-0.1.11.dist-info → marqetive_lib-0.1.13.dist-info}/RECORD +5 -5
- {marqetive_lib-0.1.11.dist-info → marqetive_lib-0.1.13.dist-info}/WHEEL +0 -0
|
@@ -207,14 +207,6 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
207
207
|
platform=self.platform_name,
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
-
# Validate tweet length (280 characters)
|
|
211
|
-
if request.content and len(request.content) > 280:
|
|
212
|
-
raise ValidationError(
|
|
213
|
-
f"Tweet exceeds 280 characters ({len(request.content)} characters)",
|
|
214
|
-
platform=self.platform_name,
|
|
215
|
-
field="content",
|
|
216
|
-
)
|
|
217
|
-
|
|
218
210
|
try:
|
|
219
211
|
media_ids = []
|
|
220
212
|
|
|
@@ -234,11 +226,17 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
234
226
|
if media_ids:
|
|
235
227
|
tweet_params["media_ids"] = media_ids
|
|
236
228
|
|
|
237
|
-
response = self._tweepy_client.create_tweet(**tweet_params)
|
|
229
|
+
response = self._tweepy_client.create_tweet(**tweet_params, user_auth=False)
|
|
238
230
|
tweet_id = response.data["id"] # type: ignore[index]
|
|
239
231
|
|
|
240
|
-
#
|
|
241
|
-
return
|
|
232
|
+
# Return minimal Post object without fetching details
|
|
233
|
+
return Post(
|
|
234
|
+
post_id=tweet_id,
|
|
235
|
+
platform=self.platform_name,
|
|
236
|
+
content=request.content or "",
|
|
237
|
+
status=PostStatus.PUBLISHED,
|
|
238
|
+
created_at=datetime.now(),
|
|
239
|
+
)
|
|
242
240
|
|
|
243
241
|
except tweepy.TweepyException as e:
|
|
244
242
|
if "429" in str(e):
|
|
@@ -278,6 +276,7 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
278
276
|
],
|
|
279
277
|
expansions=["attachments.media_keys"],
|
|
280
278
|
media_fields=["url", "type", "width", "height"],
|
|
279
|
+
user_auth=False,
|
|
281
280
|
)
|
|
282
281
|
|
|
283
282
|
if not response.data: # type: ignore[attr-defined]
|
|
@@ -339,7 +338,7 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
339
338
|
raise RuntimeError("Client must be used as async context manager")
|
|
340
339
|
|
|
341
340
|
try:
|
|
342
|
-
self._tweepy_client.delete_tweet(post_id)
|
|
341
|
+
self._tweepy_client.delete_tweet(post_id, user_auth=False)
|
|
343
342
|
return True
|
|
344
343
|
|
|
345
344
|
except tweepy.errors.NotFound as e: # type: ignore[attr-defined]
|
|
@@ -432,18 +431,22 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
432
431
|
response = self._tweepy_client.create_tweet(
|
|
433
432
|
text=content,
|
|
434
433
|
in_reply_to_tweet_id=post_id,
|
|
434
|
+
user_auth=False,
|
|
435
435
|
)
|
|
436
436
|
|
|
437
437
|
reply_id = response.data["id"] # type: ignore[index]
|
|
438
438
|
|
|
439
|
-
#
|
|
440
|
-
|
|
441
|
-
reply_id,
|
|
442
|
-
|
|
439
|
+
# Return minimal Comment object without fetching details
|
|
440
|
+
return Comment(
|
|
441
|
+
comment_id=reply_id,
|
|
442
|
+
post_id=post_id,
|
|
443
|
+
platform=self.platform_name,
|
|
444
|
+
content=content,
|
|
445
|
+
author_id="",
|
|
446
|
+
created_at=datetime.now(),
|
|
447
|
+
status=CommentStatus.VISIBLE,
|
|
443
448
|
)
|
|
444
449
|
|
|
445
|
-
return self._parse_reply(reply_response.data, post_id) # type: ignore[attr-defined]
|
|
446
|
-
|
|
447
450
|
except tweepy.TweepyException as e:
|
|
448
451
|
raise PlatformError(
|
|
449
452
|
f"Failed to create reply: {e}",
|
|
@@ -52,7 +52,7 @@ MAX_VIDEO_SIZE = 512 * 1024 * 1024 # 512MB for videos
|
|
|
52
52
|
DEFAULT_REQUEST_TIMEOUT = 120.0 # 2 minutes
|
|
53
53
|
|
|
54
54
|
# Twitter API v2 media upload endpoints
|
|
55
|
-
MEDIA_UPLOAD_BASE_URL = "https://api.x.com/2/media"
|
|
55
|
+
MEDIA_UPLOAD_BASE_URL = "https://api.x.com/2/media/upload"
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class MediaCategory(str, Enum):
|
|
@@ -342,9 +342,9 @@ class TwitterMediaManager:
|
|
|
342
342
|
async with aiofiles.open(file_path, "rb") as f:
|
|
343
343
|
file_data = await f.read()
|
|
344
344
|
|
|
345
|
-
# Prepare form data
|
|
345
|
+
# Prepare multipart form data
|
|
346
346
|
files = {"media": (os.path.basename(file_path), file_data)}
|
|
347
|
-
data = {}
|
|
347
|
+
data: dict[str, str] = {}
|
|
348
348
|
|
|
349
349
|
if media_category:
|
|
350
350
|
data["media_category"] = media_category.value
|
|
@@ -362,22 +362,24 @@ class TwitterMediaManager:
|
|
|
362
362
|
total_bytes=file_size,
|
|
363
363
|
)
|
|
364
364
|
|
|
365
|
-
# Upload
|
|
365
|
+
# Upload - v2 API uses base URL directly
|
|
366
366
|
response = await self.client.post(
|
|
367
|
-
|
|
367
|
+
self.base_url,
|
|
368
368
|
files=files,
|
|
369
369
|
data=data,
|
|
370
370
|
)
|
|
371
371
|
response.raise_for_status()
|
|
372
372
|
result_data = response.json()
|
|
373
|
+
logger.debug(f"Simple upload response: {result_data}")
|
|
373
374
|
|
|
374
|
-
# Parse result
|
|
375
|
-
|
|
375
|
+
# Parse result - v2 API returns {"data": {"id": "..."}}
|
|
376
|
+
media_data = result_data["data"]
|
|
377
|
+
media_id = str(media_data["id"])
|
|
376
378
|
result = MediaUploadResult(
|
|
377
379
|
media_id=media_id,
|
|
378
|
-
media_key=
|
|
379
|
-
size=
|
|
380
|
-
expires_after_secs=
|
|
380
|
+
media_key=media_data.get("media_key"),
|
|
381
|
+
size=media_data.get("size"),
|
|
382
|
+
expires_after_secs=media_data.get("expires_after_secs"),
|
|
381
383
|
)
|
|
382
384
|
|
|
383
385
|
# Notify completion
|
|
@@ -544,23 +546,27 @@ class TwitterMediaManager:
|
|
|
544
546
|
|
|
545
547
|
@retry_async(config=STANDARD_BACKOFF)
|
|
546
548
|
async def _do_init() -> str:
|
|
547
|
-
|
|
548
|
-
|
|
549
|
+
# v2 API uses JSON body for INIT
|
|
550
|
+
json_data: dict[str, Any] = {
|
|
549
551
|
"total_bytes": total_bytes,
|
|
550
552
|
"media_type": media_type,
|
|
551
553
|
"media_category": media_category.value,
|
|
554
|
+
"shared": False,
|
|
552
555
|
}
|
|
553
556
|
|
|
554
557
|
if additional_owners:
|
|
555
|
-
|
|
558
|
+
json_data["additional_owners"] = ",".join(additional_owners)
|
|
556
559
|
|
|
560
|
+
logger.debug(f"INIT request: {json_data}")
|
|
557
561
|
response = await self.client.post(
|
|
558
|
-
f"{self.base_url}/
|
|
559
|
-
|
|
562
|
+
f"{self.base_url}/initialize",
|
|
563
|
+
json=json_data,
|
|
560
564
|
)
|
|
561
565
|
response.raise_for_status()
|
|
562
566
|
result = response.json()
|
|
563
|
-
|
|
567
|
+
logger.debug(f"INIT response: {result}")
|
|
568
|
+
# v2 API returns {"data": {"id": "..."}}
|
|
569
|
+
return str(result["data"]["id"])
|
|
564
570
|
|
|
565
571
|
return await _do_init()
|
|
566
572
|
|
|
@@ -582,19 +588,18 @@ class TwitterMediaManager:
|
|
|
582
588
|
|
|
583
589
|
@retry_async(config=STANDARD_BACKOFF)
|
|
584
590
|
async def _do_append() -> None:
|
|
591
|
+
# v2 API uses /{media_id}/append endpoint with form data
|
|
585
592
|
files = {"media": (filename, chunk_data)}
|
|
586
|
-
|
|
587
|
-
"command": "APPEND",
|
|
588
|
-
"media_id": media_id,
|
|
589
|
-
"segment_index": segment_index,
|
|
590
|
-
}
|
|
593
|
+
data = {"segment_index": str(segment_index)}
|
|
591
594
|
|
|
592
595
|
response = await self.client.post(
|
|
593
|
-
f"{self.base_url}/
|
|
594
|
-
params=params,
|
|
596
|
+
f"{self.base_url}/{media_id}/append",
|
|
595
597
|
files=files,
|
|
598
|
+
data=data,
|
|
596
599
|
)
|
|
600
|
+
logger.debug(f"APPEND response status: {response.status_code}")
|
|
597
601
|
response.raise_for_status()
|
|
602
|
+
# APPEND returns empty body on success
|
|
598
603
|
|
|
599
604
|
await _do_append()
|
|
600
605
|
|
|
@@ -610,24 +615,23 @@ class TwitterMediaManager:
|
|
|
610
615
|
|
|
611
616
|
@retry_async(config=STANDARD_BACKOFF)
|
|
612
617
|
async def _do_finalize() -> MediaUploadResult:
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
"media_id": media_id,
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
+
# v2 API uses /{media_id}/finalize endpoint
|
|
619
|
+
logger.debug(f"Finalizing chunked upload for media_id: {media_id}")
|
|
618
620
|
response = await self.client.post(
|
|
619
|
-
f"{self.base_url}/
|
|
620
|
-
params=params,
|
|
621
|
+
f"{self.base_url}/{media_id}/finalize",
|
|
621
622
|
)
|
|
622
623
|
response.raise_for_status()
|
|
623
624
|
result = response.json()
|
|
625
|
+
logger.debug(f"FINALIZE response: {result}")
|
|
624
626
|
|
|
627
|
+
# v2 API returns {"data": {...}}
|
|
628
|
+
media_data = result["data"]
|
|
625
629
|
return MediaUploadResult(
|
|
626
|
-
media_id=str(
|
|
627
|
-
media_key=
|
|
628
|
-
size=
|
|
629
|
-
expires_after_secs=
|
|
630
|
-
processing_info=
|
|
630
|
+
media_id=str(media_data["id"]),
|
|
631
|
+
media_key=media_data.get("media_key"),
|
|
632
|
+
size=media_data.get("size"),
|
|
633
|
+
expires_after_secs=media_data.get("expires_after_secs"),
|
|
634
|
+
processing_info=media_data.get("processing_info"),
|
|
631
635
|
)
|
|
632
636
|
|
|
633
637
|
return await _do_finalize()
|
|
@@ -644,24 +648,28 @@ class TwitterMediaManager:
|
|
|
644
648
|
|
|
645
649
|
@retry_async(config=STANDARD_BACKOFF)
|
|
646
650
|
async def _do_status() -> MediaUploadResult:
|
|
651
|
+
# v2 API uses GET with media_id and command params
|
|
647
652
|
params = {
|
|
648
|
-
"command": "STATUS",
|
|
649
653
|
"media_id": media_id,
|
|
654
|
+
"command": "STATUS",
|
|
650
655
|
}
|
|
651
656
|
|
|
652
657
|
response = await self.client.get(
|
|
653
|
-
|
|
658
|
+
self.base_url,
|
|
654
659
|
params=params,
|
|
655
660
|
)
|
|
656
661
|
response.raise_for_status()
|
|
657
662
|
result = response.json()
|
|
663
|
+
logger.debug(f"STATUS response: {result}")
|
|
658
664
|
|
|
665
|
+
# v2 API returns {"data": {...}}
|
|
666
|
+
media_data = result["data"]
|
|
659
667
|
return MediaUploadResult(
|
|
660
|
-
media_id=str(
|
|
661
|
-
media_key=
|
|
662
|
-
size=
|
|
663
|
-
expires_after_secs=
|
|
664
|
-
processing_info=
|
|
668
|
+
media_id=str(media_data["id"]),
|
|
669
|
+
media_key=media_data.get("media_key"),
|
|
670
|
+
size=media_data.get("size"),
|
|
671
|
+
expires_after_secs=media_data.get("expires_after_secs"),
|
|
672
|
+
processing_info=media_data.get("processing_info"),
|
|
665
673
|
)
|
|
666
674
|
|
|
667
675
|
return await _do_status()
|
|
@@ -22,9 +22,9 @@ marqetive/platforms/tiktok/exceptions.py,sha256=vxwyAKujMGZJh0LetG1QsLF95QfUs_kR
|
|
|
22
22
|
marqetive/platforms/tiktok/media.py,sha256=aa47EDRA7woKGqKZkl_5XWu7kcRp2nT93Ol2skEQJpY,28592
|
|
23
23
|
marqetive/platforms/tiktok/models.py,sha256=WWdjuFqhTIR8SnHkz-8UaNc5Mm2PrGomwQ3W7pJcQFg,2962
|
|
24
24
|
marqetive/platforms/twitter/__init__.py,sha256=dvcgVT-v-JOtjSz-OUvxGrn_43OI6w_ep42Wx_nHTSM,217
|
|
25
|
-
marqetive/platforms/twitter/client.py,sha256=
|
|
25
|
+
marqetive/platforms/twitter/client.py,sha256=ATjNOLV3LLVAPo7An_O1G0Sd0NX4t2hljn_EF7foUaY,23345
|
|
26
26
|
marqetive/platforms/twitter/exceptions.py,sha256=eZ-dJKOXH_-bAMg29zWKbEqMFud29piEJ5IWfC9wFts,8926
|
|
27
|
-
marqetive/platforms/twitter/media.py,sha256=
|
|
27
|
+
marqetive/platforms/twitter/media.py,sha256=KpPxnLCas8NhnsEvaXSJZ7To4wW4FY0YqQvUkJIIr7g,28010
|
|
28
28
|
marqetive/platforms/twitter/models.py,sha256=yPQlx40SlNmz7YGasXUqdx7rEDEgrQ64aYovlPKo6oc,2126
|
|
29
29
|
marqetive/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
marqetive/utils/__init__.py,sha256=bSrNajbxYBSKQayrPviLz8JeGjplnyK8y_NGDtgb7yQ,977
|
|
@@ -34,6 +34,6 @@ marqetive/utils/media.py,sha256=O1rISYdaP3CuuPxso7kqvxWXNfe2jjioNkaBc4cpwkY,1466
|
|
|
34
34
|
marqetive/utils/oauth.py,sha256=1SkYCE6dcyPvcDqbjRFSSBcKTwLJy8u3jAANPdftVmo,13108
|
|
35
35
|
marqetive/utils/retry.py,sha256=lAniJLMNWp9XsHrvU0XBNifpNEjfde4MGfd5hlFTPfA,7636
|
|
36
36
|
marqetive/utils/token_validator.py,sha256=dNvDeHs2Du5UyMMH2ZOW6ydR7OwOEKA4c9e-rG0f9-0,6698
|
|
37
|
-
marqetive_lib-0.1.
|
|
38
|
-
marqetive_lib-0.1.
|
|
39
|
-
marqetive_lib-0.1.
|
|
37
|
+
marqetive_lib-0.1.13.dist-info/METADATA,sha256=1yExpmKMETEBworXCnaoIVuU9UaDH9nJFbEy8DgMqeI,7876
|
|
38
|
+
marqetive_lib-0.1.13.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
39
|
+
marqetive_lib-0.1.13.dist-info/RECORD,,
|
|
File without changes
|