marqetive-lib 0.1.9__py3-none-any.whl → 0.1.11__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/instagram/client.py +197 -36
- marqetive/platforms/twitter/media.py +7 -7
- {marqetive_lib-0.1.9.dist-info → marqetive_lib-0.1.11.dist-info}/METADATA +1 -1
- {marqetive_lib-0.1.9.dist-info → marqetive_lib-0.1.11.dist-info}/RECORD +5 -5
- {marqetive_lib-0.1.9.dist-info → marqetive_lib-0.1.11.dist-info}/WHEEL +0 -0
|
@@ -216,12 +216,19 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
216
216
|
async def create_post(self, request: PostCreateRequest) -> Post:
|
|
217
217
|
"""Create and publish an Instagram post.
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
Automatically routes to the appropriate handler based on content type:
|
|
220
|
+
- IMAGE: Single image feed post
|
|
221
|
+
- CAROUSEL: Multiple images (2-10)
|
|
222
|
+
- REEL/VIDEO: Video content as Reel
|
|
223
|
+
- STORY: Story (image or video)
|
|
224
|
+
|
|
225
|
+
Content type can be specified via:
|
|
226
|
+
1. InstagramPostRequest.media_type field
|
|
227
|
+
2. PostCreateRequest.additional_data["media_type"]
|
|
228
|
+
3. Auto-detected from media_urls count (1 = single, 2+ = carousel)
|
|
222
229
|
|
|
223
230
|
Args:
|
|
224
|
-
request: Post creation request.
|
|
231
|
+
request: Post creation request (PostCreateRequest or InstagramPostRequest).
|
|
225
232
|
|
|
226
233
|
Returns:
|
|
227
234
|
Created Post object.
|
|
@@ -229,6 +236,22 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
229
236
|
Raises:
|
|
230
237
|
ValidationError: If request is invalid.
|
|
231
238
|
MediaUploadError: If media upload fails.
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
>>> # Single image post
|
|
242
|
+
>>> request = PostCreateRequest(
|
|
243
|
+
... content="Hello Instagram!",
|
|
244
|
+
... media_urls=["https://example.com/image.jpg"]
|
|
245
|
+
... )
|
|
246
|
+
>>> post = await client.create_post(request)
|
|
247
|
+
|
|
248
|
+
>>> # Reel via additional_data
|
|
249
|
+
>>> request = PostCreateRequest(
|
|
250
|
+
... content="Check out this video!",
|
|
251
|
+
... media_urls=["https://example.com/video.mp4"],
|
|
252
|
+
... additional_data={"media_type": "reel"}
|
|
253
|
+
... )
|
|
254
|
+
>>> post = await client.create_post(request)
|
|
232
255
|
"""
|
|
233
256
|
if not self.api_client:
|
|
234
257
|
raise RuntimeError("Client must be used as async context manager")
|
|
@@ -240,47 +263,185 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
240
263
|
field="media",
|
|
241
264
|
)
|
|
242
265
|
|
|
243
|
-
#
|
|
244
|
-
|
|
245
|
-
"access_token": self.credentials.access_token,
|
|
246
|
-
}
|
|
266
|
+
# Determine content type from request
|
|
267
|
+
media_type = self._get_media_type(request)
|
|
247
268
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
)
|
|
253
|
-
|
|
269
|
+
# Route to appropriate handler based on content type
|
|
270
|
+
if media_type == MediaType.STORY:
|
|
271
|
+
return await self._create_story_post(request)
|
|
272
|
+
elif media_type in (MediaType.REEL, MediaType.VIDEO):
|
|
273
|
+
return await self._create_reel_post(request)
|
|
274
|
+
elif media_type == MediaType.CAROUSEL or len(request.media_urls) > 1:
|
|
275
|
+
return await self._create_carousel_post(request)
|
|
276
|
+
else:
|
|
277
|
+
return await self._create_single_image_post(request)
|
|
254
278
|
|
|
255
|
-
|
|
256
|
-
|
|
279
|
+
def _get_media_type(self, request: PostCreateRequest) -> MediaType:
|
|
280
|
+
"""Extract media type from request.
|
|
257
281
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
Checks in order:
|
|
283
|
+
1. InstagramPostRequest.media_type (if using platform-specific model)
|
|
284
|
+
2. PostCreateRequest.additional_data["media_type"]
|
|
285
|
+
3. Auto-detect from media count
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
request: Post creation request.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
MediaType enum value.
|
|
292
|
+
"""
|
|
293
|
+
# Check if it's an InstagramPostRequest with media_type
|
|
294
|
+
# Use getattr to avoid type checker issues with duck typing
|
|
295
|
+
media_type_attr = getattr(request, "media_type", None)
|
|
296
|
+
if media_type_attr is not None and isinstance(media_type_attr, MediaType):
|
|
297
|
+
return media_type_attr
|
|
298
|
+
|
|
299
|
+
# Check additional_data for media_type
|
|
300
|
+
if request.additional_data:
|
|
301
|
+
media_type_str = request.additional_data.get("media_type")
|
|
302
|
+
if media_type_str:
|
|
303
|
+
# Normalize and convert to enum
|
|
304
|
+
media_type_str = media_type_str.lower()
|
|
305
|
+
type_map = {
|
|
306
|
+
"image": MediaType.IMAGE,
|
|
307
|
+
"video": MediaType.VIDEO,
|
|
308
|
+
"reel": MediaType.REEL,
|
|
309
|
+
"reels": MediaType.REEL,
|
|
310
|
+
"story": MediaType.STORY,
|
|
311
|
+
"stories": MediaType.STORY,
|
|
312
|
+
"carousel": MediaType.CAROUSEL,
|
|
313
|
+
}
|
|
314
|
+
if media_type_str in type_map:
|
|
315
|
+
return type_map[media_type_str]
|
|
316
|
+
|
|
317
|
+
# Auto-detect: multiple URLs = carousel, single = image
|
|
318
|
+
if len(request.media_urls) > 1:
|
|
319
|
+
return MediaType.CAROUSEL
|
|
320
|
+
|
|
321
|
+
return MediaType.IMAGE
|
|
322
|
+
|
|
323
|
+
async def _create_single_image_post(self, request: PostCreateRequest) -> Post:
|
|
324
|
+
"""Create a single image feed post.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
request: Post creation request.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Created Post object.
|
|
331
|
+
"""
|
|
332
|
+
if not self._media_manager:
|
|
333
|
+
raise RuntimeError("Client must be used as async context manager")
|
|
334
|
+
|
|
335
|
+
# Extract options from additional_data
|
|
336
|
+
location_id = request.additional_data.get("location_id") or request.location
|
|
337
|
+
share_to_feed = request.additional_data.get("share_to_feed", True)
|
|
338
|
+
|
|
339
|
+
# Validate URL
|
|
340
|
+
validated_url = validate_media_url(
|
|
341
|
+
request.media_urls[0], platform=self.platform_name
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Create media item
|
|
345
|
+
media_item = MediaItem(url=validated_url, type="image")
|
|
346
|
+
|
|
347
|
+
# Get alt_texts if provided
|
|
348
|
+
alt_texts = request.additional_data.get("alt_texts", [])
|
|
349
|
+
if alt_texts:
|
|
350
|
+
media_item = MediaItem(
|
|
351
|
+
url=validated_url, type="image", alt_text=alt_texts[0]
|
|
263
352
|
)
|
|
264
|
-
container_id = container_response.data["id"]
|
|
265
353
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
354
|
+
# Create container and publish
|
|
355
|
+
container_ids = await self._media_manager.create_feed_containers(
|
|
356
|
+
[media_item],
|
|
357
|
+
caption=request.content,
|
|
358
|
+
location_id=location_id,
|
|
359
|
+
share_to_feed=share_to_feed,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
result = await self._media_manager.publish_container(container_ids[0])
|
|
363
|
+
return await self.get_post(result.media_id)
|
|
364
|
+
|
|
365
|
+
async def _create_carousel_post(self, request: PostCreateRequest) -> Post:
|
|
366
|
+
"""Create a carousel post (2-10 images).
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
request: Post creation request.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Created Post object.
|
|
373
|
+
"""
|
|
374
|
+
# Extract options from additional_data
|
|
375
|
+
alt_texts = request.additional_data.get("alt_texts")
|
|
376
|
+
location_id = request.additional_data.get("location_id") or request.location
|
|
377
|
+
|
|
378
|
+
return await self.create_carousel(
|
|
379
|
+
media_urls=request.media_urls,
|
|
380
|
+
caption=request.content,
|
|
381
|
+
alt_texts=alt_texts,
|
|
382
|
+
location_id=location_id,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
async def _create_reel_post(self, request: PostCreateRequest) -> Post:
|
|
386
|
+
"""Create a Reel (video post).
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
request: Post creation request.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Created Post object.
|
|
393
|
+
"""
|
|
394
|
+
if not request.media_urls:
|
|
395
|
+
raise ValidationError(
|
|
396
|
+
"Reel requires a video URL",
|
|
397
|
+
platform=self.platform_name,
|
|
398
|
+
field="media_urls",
|
|
273
399
|
)
|
|
274
|
-
post_id = publish_response.data["id"]
|
|
275
400
|
|
|
276
|
-
|
|
277
|
-
|
|
401
|
+
# Extract reel-specific options from additional_data
|
|
402
|
+
cover_url = request.additional_data.get("cover_url")
|
|
403
|
+
share_to_feed = request.additional_data.get("share_to_feed", True)
|
|
278
404
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
405
|
+
return await self.create_reel(
|
|
406
|
+
video_url=request.media_urls[0],
|
|
407
|
+
caption=request.content,
|
|
408
|
+
cover_url=cover_url,
|
|
409
|
+
share_to_feed=share_to_feed,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
async def _create_story_post(self, request: PostCreateRequest) -> Post:
|
|
413
|
+
"""Create an Instagram Story.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
request: Post creation request.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Created Post object.
|
|
420
|
+
"""
|
|
421
|
+
if not request.media_urls:
|
|
422
|
+
raise ValidationError(
|
|
423
|
+
"Story requires a media URL",
|
|
282
424
|
platform=self.platform_name,
|
|
283
|
-
|
|
425
|
+
field="media_urls",
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Determine if it's image or video from additional_data or file extension
|
|
429
|
+
story_media_type: Literal["image", "video"] = "image"
|
|
430
|
+
|
|
431
|
+
# Check additional_data for explicit type
|
|
432
|
+
if request.additional_data.get("story_media_type"):
|
|
433
|
+
story_media_type = request.additional_data["story_media_type"]
|
|
434
|
+
else:
|
|
435
|
+
# Auto-detect from URL extension
|
|
436
|
+
url_lower = request.media_urls[0].lower()
|
|
437
|
+
video_extensions = (".mp4", ".mov", ".avi", ".webm")
|
|
438
|
+
if any(url_lower.endswith(ext) for ext in video_extensions):
|
|
439
|
+
story_media_type = "video"
|
|
440
|
+
|
|
441
|
+
return await self.create_story(
|
|
442
|
+
media_url=request.media_urls[0],
|
|
443
|
+
media_type=story_media_type,
|
|
444
|
+
)
|
|
284
445
|
|
|
285
446
|
async def get_post(self, post_id: str) -> Post:
|
|
286
447
|
"""Retrieve an Instagram post by ID.
|
|
@@ -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://
|
|
55
|
+
MEDIA_UPLOAD_BASE_URL = "https://api.x.com/2/media"
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class MediaCategory(str, Enum):
|
|
@@ -364,7 +364,7 @@ class TwitterMediaManager:
|
|
|
364
364
|
|
|
365
365
|
# Upload
|
|
366
366
|
response = await self.client.post(
|
|
367
|
-
f"{self.base_url}/upload
|
|
367
|
+
f"{self.base_url}/upload",
|
|
368
368
|
files=files,
|
|
369
369
|
data=data,
|
|
370
370
|
)
|
|
@@ -555,7 +555,7 @@ class TwitterMediaManager:
|
|
|
555
555
|
params["additional_owners"] = ",".join(additional_owners)
|
|
556
556
|
|
|
557
557
|
response = await self.client.post(
|
|
558
|
-
f"{self.base_url}/upload
|
|
558
|
+
f"{self.base_url}/upload",
|
|
559
559
|
params=params,
|
|
560
560
|
)
|
|
561
561
|
response.raise_for_status()
|
|
@@ -590,7 +590,7 @@ class TwitterMediaManager:
|
|
|
590
590
|
}
|
|
591
591
|
|
|
592
592
|
response = await self.client.post(
|
|
593
|
-
f"{self.base_url}/upload
|
|
593
|
+
f"{self.base_url}/upload",
|
|
594
594
|
params=params,
|
|
595
595
|
files=files,
|
|
596
596
|
)
|
|
@@ -616,7 +616,7 @@ class TwitterMediaManager:
|
|
|
616
616
|
}
|
|
617
617
|
|
|
618
618
|
response = await self.client.post(
|
|
619
|
-
f"{self.base_url}/upload
|
|
619
|
+
f"{self.base_url}/upload",
|
|
620
620
|
params=params,
|
|
621
621
|
)
|
|
622
622
|
response.raise_for_status()
|
|
@@ -650,7 +650,7 @@ class TwitterMediaManager:
|
|
|
650
650
|
}
|
|
651
651
|
|
|
652
652
|
response = await self.client.get(
|
|
653
|
-
f"{self.base_url}/upload
|
|
653
|
+
f"{self.base_url}/upload",
|
|
654
654
|
params=params,
|
|
655
655
|
)
|
|
656
656
|
response.raise_for_status()
|
|
@@ -684,7 +684,7 @@ class TwitterMediaManager:
|
|
|
684
684
|
|
|
685
685
|
try:
|
|
686
686
|
response = await self.client.post(
|
|
687
|
-
f"{self.base_url}/metadata/create
|
|
687
|
+
f"{self.base_url}/metadata/create",
|
|
688
688
|
json={
|
|
689
689
|
"media_id": media_id,
|
|
690
690
|
"alt_text": {"text": alt_text},
|
|
@@ -7,7 +7,7 @@ marqetive/core/models.py,sha256=L2gA4FhW0feAXQFsz2ce1ttd0vScMRhatoTclhDGCU0,1472
|
|
|
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=_ccxn3RUZDooR2w-vQocsxhctRB_MAhn3ZKQroJMv8c,32217
|
|
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
|
|
@@ -24,7 +24,7 @@ marqetive/platforms/tiktok/models.py,sha256=WWdjuFqhTIR8SnHkz-8UaNc5Mm2PrGomwQ3W
|
|
|
24
24
|
marqetive/platforms/twitter/__init__.py,sha256=dvcgVT-v-JOtjSz-OUvxGrn_43OI6w_ep42Wx_nHTSM,217
|
|
25
25
|
marqetive/platforms/twitter/client.py,sha256=08jV2hQVmGOpnG3C05u7bCqL7KapWn7bSsG0wbN_t5M,23270
|
|
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=UlXKGt1DSz5gkDgV20ezz2rfb3NOvXiLULxthdoPsK0,27117
|
|
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.11.dist-info/METADATA,sha256=RZfg5vQ_7QReP15Xb3j2DScA6e-c5DA2de_xDiPbr4Q,7876
|
|
38
|
+
marqetive_lib-0.1.11.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
39
|
+
marqetive_lib-0.1.11.dist-info/RECORD,,
|
|
File without changes
|