marqetive-lib 0.2.1__tar.gz → 0.2.3__tar.gz
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_lib-0.2.1 → marqetive_lib-0.2.3}/PKG-INFO +1 -1
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/pyproject.toml +1 -1
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/__init__.py +1 -1
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/core/base.py +4 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/linkedin/client.py +14 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/twitter/__init__.py +2 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/twitter/client.py +46 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/twitter/exceptions.py +35 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/README.md +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/core/__init__.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/core/client.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/core/exceptions.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/core/models.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/factory.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/__init__.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/instagram/__init__.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/instagram/client.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/instagram/exceptions.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/instagram/media.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/instagram/models.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/linkedin/__init__.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/linkedin/exceptions.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/linkedin/media.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/linkedin/models.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/tiktok/__init__.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/tiktok/client.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/tiktok/exceptions.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/tiktok/media.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/tiktok/models.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/twitter/media.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/platforms/twitter/models.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/py.typed +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/utils/__init__.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/utils/file_handlers.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/utils/helpers.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/utils/media.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/utils/oauth.py +0 -0
- {marqetive_lib-0.2.1 → marqetive_lib-0.2.3}/src/marqetive/utils/retry.py +0 -0
|
@@ -389,6 +389,7 @@ class SocialMediaPlatform(ABC):
|
|
|
389
389
|
async def create_thread(
|
|
390
390
|
self,
|
|
391
391
|
posts: list[PostCreateRequest],
|
|
392
|
+
cancellation_check: Callable[[], Awaitable[bool]] | None = None,
|
|
392
393
|
) -> list[Post]:
|
|
393
394
|
"""Create a thread of connected posts.
|
|
394
395
|
|
|
@@ -400,6 +401,8 @@ class SocialMediaPlatform(ABC):
|
|
|
400
401
|
posts: List of post requests to create as a thread.
|
|
401
402
|
Each post can have its own content, media, etc.
|
|
402
403
|
First post is the head of the thread.
|
|
404
|
+
cancellation_check: Optional async callback that returns True if the
|
|
405
|
+
thread creation should be cancelled. Called before each post.
|
|
403
406
|
|
|
404
407
|
Returns:
|
|
405
408
|
List of Post objects for each post in the thread.
|
|
@@ -408,6 +411,7 @@ class SocialMediaPlatform(ABC):
|
|
|
408
411
|
NotImplementedError: If platform doesn't support threads.
|
|
409
412
|
ValidationError: If posts list is empty.
|
|
410
413
|
PlatformAuthError: If not authenticated.
|
|
414
|
+
ThreadCancelledException: If cancelled mid-thread (includes posted items).
|
|
411
415
|
|
|
412
416
|
Example:
|
|
413
417
|
>>> # Twitter thread example
|
|
@@ -612,6 +612,20 @@ class LinkedInClient(SocialMediaPlatform):
|
|
|
612
612
|
raw_data=response.data,
|
|
613
613
|
)
|
|
614
614
|
|
|
615
|
+
except httpx.HTTPStatusError as e:
|
|
616
|
+
# Extract error details from LinkedIn's response body
|
|
617
|
+
error_details = ""
|
|
618
|
+
try:
|
|
619
|
+
error_body = e.response.json()
|
|
620
|
+
error_details = f" | Details: {error_body}"
|
|
621
|
+
except Exception:
|
|
622
|
+
if e.response.text:
|
|
623
|
+
error_details = f" | Response: {e.response.text[:500]}"
|
|
624
|
+
|
|
625
|
+
raise PlatformError(
|
|
626
|
+
f"Failed to create LinkedIn post: {e}{error_details}",
|
|
627
|
+
platform=self.platform_name,
|
|
628
|
+
) from e
|
|
615
629
|
except httpx.HTTPError as e:
|
|
616
630
|
raise PlatformError(
|
|
617
631
|
f"Failed to create LinkedIn post: {e}",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Twitter/X platform integration."""
|
|
2
2
|
|
|
3
3
|
from marqetive.platforms.twitter.client import TwitterClient
|
|
4
|
+
from marqetive.platforms.twitter.exceptions import TwitterUnauthorizedError
|
|
4
5
|
from marqetive.platforms.twitter.models import (
|
|
5
6
|
TwitterDMRequest,
|
|
6
7
|
TwitterGroupDMRequest,
|
|
@@ -12,4 +13,5 @@ __all__ = [
|
|
|
12
13
|
"TwitterPostRequest",
|
|
13
14
|
"TwitterDMRequest",
|
|
14
15
|
"TwitterGroupDMRequest",
|
|
16
|
+
"TwitterUnauthorizedError",
|
|
15
17
|
]
|
|
@@ -40,6 +40,7 @@ from marqetive.core.models import (
|
|
|
40
40
|
PostUpdateRequest,
|
|
41
41
|
ProgressStatus,
|
|
42
42
|
)
|
|
43
|
+
from marqetive.platforms.twitter.exceptions import TwitterUnauthorizedError
|
|
43
44
|
from marqetive.platforms.twitter.media import MediaCategory, TwitterMediaManager
|
|
44
45
|
from marqetive.platforms.twitter.models import TwitterDMRequest, TwitterPostRequest
|
|
45
46
|
|
|
@@ -312,6 +313,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
312
313
|
platform=self.platform_name,
|
|
313
314
|
status_code=429,
|
|
314
315
|
) from e
|
|
316
|
+
if "401" in str(e):
|
|
317
|
+
raise TwitterUnauthorizedError(
|
|
318
|
+
"Unauthorized: Invalid or expired access token",
|
|
319
|
+
error_code=89,
|
|
320
|
+
) from e
|
|
315
321
|
raise PlatformError(
|
|
316
322
|
f"Failed to create tweet: {e}",
|
|
317
323
|
platform=self.platform_name,
|
|
@@ -362,6 +368,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
362
368
|
status_code=404,
|
|
363
369
|
) from e
|
|
364
370
|
except tweepy.TweepyException as e:
|
|
371
|
+
if "401" in str(e):
|
|
372
|
+
raise TwitterUnauthorizedError(
|
|
373
|
+
"Unauthorized: Invalid or expired access token",
|
|
374
|
+
error_code=89,
|
|
375
|
+
) from e
|
|
365
376
|
raise PlatformError(
|
|
366
377
|
f"Failed to fetch tweet: {e}",
|
|
367
378
|
platform=self.platform_name,
|
|
@@ -415,6 +426,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
415
426
|
status_code=404,
|
|
416
427
|
) from e
|
|
417
428
|
except tweepy.TweepyException as e:
|
|
429
|
+
if "401" in str(e):
|
|
430
|
+
raise TwitterUnauthorizedError(
|
|
431
|
+
"Unauthorized: Invalid or expired access token",
|
|
432
|
+
error_code=89,
|
|
433
|
+
) from e
|
|
418
434
|
raise PlatformError(
|
|
419
435
|
f"Failed to delete tweet: {e}",
|
|
420
436
|
platform=self.platform_name,
|
|
@@ -458,6 +474,16 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
458
474
|
|
|
459
475
|
return comments
|
|
460
476
|
|
|
477
|
+
except httpx.HTTPStatusError as e:
|
|
478
|
+
if e.response.status_code == 401:
|
|
479
|
+
raise TwitterUnauthorizedError(
|
|
480
|
+
"Unauthorized: Invalid or expired access token",
|
|
481
|
+
error_code=89,
|
|
482
|
+
) from e
|
|
483
|
+
raise PlatformError(
|
|
484
|
+
f"Failed to fetch replies: {e}",
|
|
485
|
+
platform=self.platform_name,
|
|
486
|
+
) from e
|
|
461
487
|
except httpx.HTTPError as e:
|
|
462
488
|
raise PlatformError(
|
|
463
489
|
f"Failed to fetch replies: {e}",
|
|
@@ -515,6 +541,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
515
541
|
)
|
|
516
542
|
|
|
517
543
|
except tweepy.TweepyException as e:
|
|
544
|
+
if "401" in str(e):
|
|
545
|
+
raise TwitterUnauthorizedError(
|
|
546
|
+
"Unauthorized: Invalid or expired access token",
|
|
547
|
+
error_code=89,
|
|
548
|
+
) from e
|
|
518
549
|
raise PlatformError(
|
|
519
550
|
f"Failed to create reply: {e}",
|
|
520
551
|
platform=self.platform_name,
|
|
@@ -633,6 +664,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
633
664
|
platform=self.platform_name,
|
|
634
665
|
status_code=429,
|
|
635
666
|
) from e
|
|
667
|
+
if "401" in str(e):
|
|
668
|
+
raise TwitterUnauthorizedError(
|
|
669
|
+
"Unauthorized: Invalid or expired access token",
|
|
670
|
+
error_code=89,
|
|
671
|
+
) from e
|
|
636
672
|
raise PlatformError(
|
|
637
673
|
f"Failed to retweet: {e}",
|
|
638
674
|
platform=self.platform_name,
|
|
@@ -673,6 +709,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
673
709
|
platform=self.platform_name,
|
|
674
710
|
status_code=429,
|
|
675
711
|
) from e
|
|
712
|
+
if "401" in str(e):
|
|
713
|
+
raise TwitterUnauthorizedError(
|
|
714
|
+
"Unauthorized: Invalid or expired access token",
|
|
715
|
+
error_code=89,
|
|
716
|
+
) from e
|
|
676
717
|
raise PlatformError(
|
|
677
718
|
f"Failed to unretweet: {e}",
|
|
678
719
|
platform=self.platform_name,
|
|
@@ -1108,6 +1149,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
1108
1149
|
platform=self.platform_name,
|
|
1109
1150
|
status_code=429,
|
|
1110
1151
|
) from e
|
|
1152
|
+
if "401" in str(e):
|
|
1153
|
+
raise TwitterUnauthorizedError(
|
|
1154
|
+
"Unauthorized: Invalid or expired access token",
|
|
1155
|
+
error_code=89,
|
|
1156
|
+
) from e
|
|
1111
1157
|
raise PlatformError(
|
|
1112
1158
|
f"Failed to create group conversation: {e}",
|
|
1113
1159
|
platform=self.platform_name,
|
|
@@ -270,6 +270,41 @@ def get_retry_delay(
|
|
|
270
270
|
return 5.0
|
|
271
271
|
|
|
272
272
|
|
|
273
|
+
class TwitterUnauthorizedError(PlatformAuthError):
|
|
274
|
+
"""Raised when Twitter API returns 401 Unauthorized.
|
|
275
|
+
|
|
276
|
+
Common causes:
|
|
277
|
+
- Invalid or expired access token
|
|
278
|
+
- Revoked app permissions
|
|
279
|
+
- Invalid bearer token
|
|
280
|
+
|
|
281
|
+
Attributes:
|
|
282
|
+
error_code: Twitter-specific error code (e.g., 89 for invalid token).
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(
|
|
286
|
+
self,
|
|
287
|
+
message: str,
|
|
288
|
+
*,
|
|
289
|
+
status_code: int = 401,
|
|
290
|
+
error_code: int | None = None,
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Initialize Twitter unauthorized error.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
message: Error message.
|
|
296
|
+
status_code: HTTP status code (default 401).
|
|
297
|
+
error_code: Twitter-specific error code.
|
|
298
|
+
"""
|
|
299
|
+
super().__init__(
|
|
300
|
+
message,
|
|
301
|
+
platform="twitter",
|
|
302
|
+
status_code=status_code,
|
|
303
|
+
requires_reconnection=True,
|
|
304
|
+
)
|
|
305
|
+
self.error_code = error_code
|
|
306
|
+
|
|
307
|
+
|
|
273
308
|
class TwitterAPIError(PlatformError):
|
|
274
309
|
"""Twitter API specific error with detailed information.
|
|
275
310
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|