marqetive-lib 0.1.17__py3-none-any.whl → 0.1.20__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/core/base.py +33 -1
- marqetive/core/models.py +2 -0
- marqetive/platforms/instagram/client.py +43 -6
- marqetive/platforms/linkedin/client.py +432 -42
- marqetive/platforms/linkedin/media.py +320 -211
- marqetive/platforms/tiktok/client.py +51 -10
- marqetive/platforms/twitter/client.py +66 -8
- {marqetive_lib-0.1.17.dist-info → marqetive_lib-0.1.20.dist-info}/METADATA +1 -1
- {marqetive_lib-0.1.17.dist-info → marqetive_lib-0.1.20.dist-info}/RECORD +10 -10
- {marqetive_lib-0.1.17.dist-info → marqetive_lib-0.1.20.dist-info}/WHEEL +0 -0
|
@@ -166,6 +166,32 @@ class TikTokClient(SocialMediaPlatform):
|
|
|
166
166
|
except PlatformError:
|
|
167
167
|
return False
|
|
168
168
|
|
|
169
|
+
# ==================== Validation ====================
|
|
170
|
+
|
|
171
|
+
def _validate_create_post_request(self, request: PostCreateRequest) -> None:
|
|
172
|
+
"""Validate TikTok post creation request.
|
|
173
|
+
|
|
174
|
+
TikTok Requirements:
|
|
175
|
+
- Video URL is ALWAYS required (TikTok is a video-only platform)
|
|
176
|
+
- Video duration: 3 seconds to 10 minutes
|
|
177
|
+
- open_id must be in credentials.additional_data
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
request: Post creation request to validate.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
ValidationError: If validation fails.
|
|
184
|
+
"""
|
|
185
|
+
if not request.media_urls:
|
|
186
|
+
raise ValidationError(
|
|
187
|
+
"A video URL is required to create a TikTok post. "
|
|
188
|
+
"TikTok is a video platform - image-only or text-only posts are not supported.",
|
|
189
|
+
platform=self.platform_name,
|
|
190
|
+
field="media_urls",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# ==================== TikTok-specific Methods ====================
|
|
194
|
+
|
|
169
195
|
async def query_creator_info(self) -> CreatorInfo:
|
|
170
196
|
"""Query creator info before posting.
|
|
171
197
|
|
|
@@ -203,11 +229,8 @@ class TikTokClient(SocialMediaPlatform):
|
|
|
203
229
|
if not self._media_manager or not self.api_client:
|
|
204
230
|
raise RuntimeError("Client must be used as async context manager")
|
|
205
231
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
"A video URL is required to create a TikTok post.",
|
|
209
|
-
platform=self.platform_name,
|
|
210
|
-
)
|
|
232
|
+
# Validate request
|
|
233
|
+
self._validate_create_post_request(request)
|
|
211
234
|
|
|
212
235
|
# 1. Query creator info (required before posting)
|
|
213
236
|
if not self._creator_info:
|
|
@@ -232,12 +255,21 @@ class TikTokClient(SocialMediaPlatform):
|
|
|
232
255
|
)
|
|
233
256
|
|
|
234
257
|
# 4. Return minimal Post object without fetching details
|
|
258
|
+
video_id = upload_result.video_id or upload_result.publish_id
|
|
259
|
+
url = (
|
|
260
|
+
HttpUrl(f"https://www.tiktok.com/@{self.credentials.username}/video/{video_id}")
|
|
261
|
+
if self.credentials.username and video_id
|
|
262
|
+
else None
|
|
263
|
+
)
|
|
264
|
+
|
|
235
265
|
return Post(
|
|
236
|
-
post_id=
|
|
266
|
+
post_id=video_id,
|
|
237
267
|
platform=self.platform_name,
|
|
238
268
|
content=request.content,
|
|
239
269
|
status=PostStatus.PUBLISHED,
|
|
240
270
|
created_at=datetime.now(),
|
|
271
|
+
author_id=self.credentials.additional_data.get("open_id"),
|
|
272
|
+
url=url,
|
|
241
273
|
raw_data={
|
|
242
274
|
"publish_id": upload_result.publish_id,
|
|
243
275
|
"video_id": upload_result.video_id,
|
|
@@ -458,16 +490,25 @@ class TikTokClient(SocialMediaPlatform):
|
|
|
458
490
|
# TikTok uses 'id' field for video ID
|
|
459
491
|
video_id = video_data.get("id", video_data.get("video_id", ""))
|
|
460
492
|
|
|
461
|
-
# Handle share_url -
|
|
493
|
+
# Handle share_url - construct URL from username if share_url not available
|
|
462
494
|
share_url = video_data.get("share_url", "")
|
|
463
|
-
|
|
464
|
-
HttpUrl
|
|
465
|
-
|
|
495
|
+
if share_url:
|
|
496
|
+
post_url: HttpUrl | None = HttpUrl(share_url)
|
|
497
|
+
elif self.credentials.username and video_id:
|
|
498
|
+
post_url = HttpUrl(
|
|
499
|
+
f"https://www.tiktok.com/@{self.credentials.username}/video/{video_id}"
|
|
500
|
+
)
|
|
501
|
+
else:
|
|
502
|
+
post_url = None
|
|
503
|
+
|
|
504
|
+
# MediaAttachment.url requires a non-null HttpUrl, use placeholder if needed
|
|
505
|
+
media_url = post_url if post_url else HttpUrl("https://www.tiktok.com/")
|
|
466
506
|
|
|
467
507
|
return Post(
|
|
468
508
|
post_id=video_id,
|
|
469
509
|
platform=self.platform_name,
|
|
470
510
|
content=video_data.get("title", video_data.get("video_description", "")),
|
|
511
|
+
url=post_url,
|
|
471
512
|
media=[
|
|
472
513
|
MediaAttachment(
|
|
473
514
|
media_id=video_id,
|
|
@@ -185,29 +185,68 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
185
185
|
except tweepy.TweepyException:
|
|
186
186
|
return False
|
|
187
187
|
|
|
188
|
+
# ==================== Validation ====================
|
|
189
|
+
|
|
190
|
+
def _validate_create_post_request(self, request: PostCreateRequest) -> None:
|
|
191
|
+
"""Validate Twitter post creation request.
|
|
192
|
+
|
|
193
|
+
Twitter Requirements:
|
|
194
|
+
- Must have content OR media (at least one)
|
|
195
|
+
- Content max 280 characters (enforced by Twitter API)
|
|
196
|
+
- Max 4 images or 1 video per tweet
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
request: Post creation request to validate.
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
ValidationError: If validation fails.
|
|
203
|
+
"""
|
|
204
|
+
if not request.content and not request.media_urls and not request.media_ids:
|
|
205
|
+
raise ValidationError(
|
|
206
|
+
"Tweet must contain content or media",
|
|
207
|
+
platform=self.platform_name,
|
|
208
|
+
field="content",
|
|
209
|
+
)
|
|
210
|
+
|
|
188
211
|
# ==================== Post CRUD Methods ====================
|
|
189
212
|
|
|
190
213
|
async def create_post(self, request: PostCreateRequest) -> Post:
|
|
191
214
|
"""Create and publish a tweet.
|
|
192
215
|
|
|
216
|
+
Twitter Requirements:
|
|
217
|
+
- Must have content OR media (at least one)
|
|
218
|
+
- Content max 280 characters (enforced by Twitter API)
|
|
219
|
+
- Max 4 images or 1 video per tweet
|
|
220
|
+
|
|
193
221
|
Args:
|
|
194
|
-
request: Post creation request.
|
|
222
|
+
request: Post creation request. Supports:
|
|
223
|
+
- content: Tweet text (max 280 chars)
|
|
224
|
+
- media_urls: URLs of media to attach
|
|
225
|
+
- reply_to_post_id: Tweet ID to reply to (for threads)
|
|
226
|
+
- quote_post_id: Tweet ID to quote
|
|
195
227
|
|
|
196
228
|
Returns:
|
|
197
|
-
|
|
229
|
+
Post object with:
|
|
230
|
+
- post_id: Twitter tweet ID
|
|
231
|
+
- platform: "twitter"
|
|
232
|
+
- content: Tweet text
|
|
233
|
+
- status: PostStatus.PUBLISHED
|
|
234
|
+
- created_at: Creation timestamp
|
|
235
|
+
- author_id: None (not available without extra API call)
|
|
236
|
+
- url: None (not returned in create response)
|
|
237
|
+
- raw_data: {"tweet_id": ...}
|
|
198
238
|
|
|
199
239
|
Raises:
|
|
200
240
|
ValidationError: If request is invalid.
|
|
201
241
|
MediaUploadError: If media upload fails.
|
|
242
|
+
RateLimitError: If Twitter rate limit is exceeded.
|
|
243
|
+
PlatformError: For other Twitter API errors.
|
|
202
244
|
"""
|
|
203
245
|
if not self._tweepy_client:
|
|
204
246
|
raise RuntimeError("Client must be used as async context manager")
|
|
205
247
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
"Tweet must contain content or media",
|
|
209
|
-
platform=self.platform_name,
|
|
210
|
-
)
|
|
248
|
+
# Validate request
|
|
249
|
+
self._validate_create_post_request(request)
|
|
211
250
|
|
|
212
251
|
try:
|
|
213
252
|
media_ids = []
|
|
@@ -241,6 +280,13 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
241
280
|
response = self._tweepy_client.create_tweet(**tweet_params, user_auth=False)
|
|
242
281
|
tweet_id = response.data["id"] # type: ignore[index]
|
|
243
282
|
|
|
283
|
+
# Construct Twitter URL if username is available
|
|
284
|
+
url = (
|
|
285
|
+
HttpUrl(f"https://x.com/{self.credentials.username}/status/{tweet_id}")
|
|
286
|
+
if self.credentials.username
|
|
287
|
+
else HttpUrl(f"https://x.com/web/status/{tweet_id}")
|
|
288
|
+
)
|
|
289
|
+
|
|
244
290
|
# Return minimal Post object without fetching details
|
|
245
291
|
return Post(
|
|
246
292
|
post_id=tweet_id,
|
|
@@ -248,6 +294,9 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
248
294
|
content=request.content or "",
|
|
249
295
|
status=PostStatus.PUBLISHED,
|
|
250
296
|
created_at=datetime.now(),
|
|
297
|
+
author_id=None, # Not available without extra API call
|
|
298
|
+
url=url,
|
|
299
|
+
raw_data={"tweet_id": tweet_id},
|
|
251
300
|
)
|
|
252
301
|
|
|
253
302
|
except tweepy.TweepyException as e:
|
|
@@ -741,10 +790,19 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
741
790
|
|
|
742
791
|
metrics = tweet.public_metrics or {}
|
|
743
792
|
|
|
793
|
+
# Construct Twitter URL if username is available
|
|
794
|
+
tweet_id = str(tweet.id)
|
|
795
|
+
url = (
|
|
796
|
+
HttpUrl(f"https://x.com/{self.credentials.username}/status/{tweet_id}")
|
|
797
|
+
if self.credentials.username
|
|
798
|
+
else HttpUrl(f"https://x.com/web/status/{tweet_id}")
|
|
799
|
+
)
|
|
800
|
+
|
|
744
801
|
return Post(
|
|
745
|
-
post_id=
|
|
802
|
+
post_id=tweet_id,
|
|
746
803
|
platform=self.platform_name,
|
|
747
804
|
content=tweet.text,
|
|
805
|
+
url=url,
|
|
748
806
|
media=media,
|
|
749
807
|
status=PostStatus.PUBLISHED,
|
|
750
808
|
created_at=tweet.created_at or datetime.now(),
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
marqetive/__init__.py,sha256=pW77CUnzOQ0X1pb-GTcRgrrvsSaJBdVhGZLnvCD_4q4,3032
|
|
2
2
|
marqetive/core/__init__.py,sha256=0_0vzxJ619YIJkz1yzSvhnGDJRkrErs_QSg2q3Bloss,1172
|
|
3
|
-
marqetive/core/base.py,sha256=
|
|
3
|
+
marqetive/core/base.py,sha256=A1RgZZLX5aMkFVmjNdJak9O0WFWOl1PPMIU8-ngEjxA,17225
|
|
4
4
|
marqetive/core/client.py,sha256=eCtvL100dkxYQHC_TJzHbs3dGjgsa_me9VTHo-CUN2M,3900
|
|
5
5
|
marqetive/core/exceptions.py,sha256=Xyj0bzNiZm5VTErmzXgVW8T6IQnOpF92-HJiKPKjIio,7076
|
|
6
|
-
marqetive/core/models.py,sha256=
|
|
6
|
+
marqetive/core/models.py,sha256=HmUSKZgJFw4o6Orjn4SGaCwLN6s8_FCsFkK1ptvnad8,14819
|
|
7
7
|
marqetive/factory.py,sha256=irZ5oN8a__kXZH70UN2uI7TzqTXu66d4QZ1FoxSoiK8,14092
|
|
8
8
|
marqetive/platforms/__init__.py,sha256=RBxlQSGyELsulSnwf5uaE1ohxFc7jC61OO9CrKaZp48,1312
|
|
9
9
|
marqetive/platforms/instagram/__init__.py,sha256=c1Gs0ozG6D7Z-Uz_UQ7S3joL0qUTT9eUZPWcePyESk8,229
|
|
10
|
-
marqetive/platforms/instagram/client.py,sha256=
|
|
10
|
+
marqetive/platforms/instagram/client.py,sha256=fyVDAcee28rbINa0jPB9W9GF745UHQ8BbKeOj3UXRoo,35239
|
|
11
11
|
marqetive/platforms/instagram/exceptions.py,sha256=TcD_pX4eSx_k4yW8DgfA6SGPiAz3VW7cMqM8DmiXIhg,8978
|
|
12
12
|
marqetive/platforms/instagram/media.py,sha256=0ZbUbpwJ025_hccL9X8qced_-LJGoL_-NdS84Op97VE,23228
|
|
13
13
|
marqetive/platforms/instagram/models.py,sha256=20v3m1037y3b_WlsKF8zAOgV23nFu63tfmmUN1CefOI,2769
|
|
14
14
|
marqetive/platforms/linkedin/__init__.py,sha256=_FrdZpqcXjcUW6C-25zGV7poGih9yzs6y1AFnuizTUQ,1384
|
|
15
|
-
marqetive/platforms/linkedin/client.py,sha256=
|
|
15
|
+
marqetive/platforms/linkedin/client.py,sha256=lMHvb5dpDozHDKu6g27tSZPDHgZuQzrNCWZhZIzWmpc,74249
|
|
16
16
|
marqetive/platforms/linkedin/exceptions.py,sha256=i5fARUkZik46bS3htZBwUInVzetsZx1APpKEXLrCKf0,9762
|
|
17
|
-
marqetive/platforms/linkedin/media.py,sha256
|
|
17
|
+
marqetive/platforms/linkedin/media.py,sha256=-kpkL5brYhx8cZlFS5ueDgY4eSZWqHoB6xp8jZU2cyI,25931
|
|
18
18
|
marqetive/platforms/linkedin/models.py,sha256=n7DqwVxYSbGYBmeEJ1woCZ6XhUIHcLx8Gpm8uCBACzI,12620
|
|
19
19
|
marqetive/platforms/tiktok/__init__.py,sha256=BqjkXTZDyBlcY3lvREy13yP9h3RcDga8E6Rl6f5KPp8,238
|
|
20
|
-
marqetive/platforms/tiktok/client.py,sha256=
|
|
20
|
+
marqetive/platforms/tiktok/client.py,sha256=IhLCphzu_qxTSP6CDFqLBQqM3zJ7bLy7uHDSErc-qA8,18968
|
|
21
21
|
marqetive/platforms/tiktok/exceptions.py,sha256=vxwyAKujMGZJh0LetG1QsLF95QfUs_kR6ujsWSHGqL0,10124
|
|
22
22
|
marqetive/platforms/tiktok/media.py,sha256=NmvzvzfaZMmzIx88wkXI5tWLd4vYN1VJXN-IkaEAO2c,28638
|
|
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=x4czyl1TnP-JmxRKl19fEE0gcYCppqK2Xdwpyf7G2E8,29035
|
|
26
26
|
marqetive/platforms/twitter/exceptions.py,sha256=eZ-dJKOXH_-bAMg29zWKbEqMFud29piEJ5IWfC9wFts,8926
|
|
27
27
|
marqetive/platforms/twitter/media.py,sha256=KpPxnLCas8NhnsEvaXSJZ7To4wW4FY0YqQvUkJIIr7g,28010
|
|
28
28
|
marqetive/platforms/twitter/models.py,sha256=yPQlx40SlNmz7YGasXUqdx7rEDEgrQ64aYovlPKo6oc,2126
|
|
@@ -33,6 +33,6 @@ marqetive/utils/helpers.py,sha256=Sh5HZD6AOJig_6T84n6JsKLosIkKIkpkiYTl69rnOOw,13
|
|
|
33
33
|
marqetive/utils/media.py,sha256=reVousdueG-h5jeI6uLGqVCfjYxlsMiWhx6XZwg-iHY,14664
|
|
34
34
|
marqetive/utils/oauth.py,sha256=x30XAW5VlND6TAPBsw9kZShko_Jsmn_NE-KOZjnBxGo,14359
|
|
35
35
|
marqetive/utils/retry.py,sha256=UcgrmVBVG5zd30_11mZnRnTaSFrbUYXBO1DrXPR0f8E,7627
|
|
36
|
-
marqetive_lib-0.1.
|
|
37
|
-
marqetive_lib-0.1.
|
|
38
|
-
marqetive_lib-0.1.
|
|
36
|
+
marqetive_lib-0.1.20.dist-info/METADATA,sha256=p-ktKzsiPP2g2pP4nQtFEeqRJv3e7UCV0vfk2aSfUCY,7876
|
|
37
|
+
marqetive_lib-0.1.20.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
38
|
+
marqetive_lib-0.1.20.dist-info/RECORD,,
|
|
File without changes
|