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
marqetive/core/base.py
CHANGED
|
@@ -119,7 +119,10 @@ class SocialMediaPlatform(ABC):
|
|
|
119
119
|
return self
|
|
120
120
|
|
|
121
121
|
async def __aexit__(
|
|
122
|
-
self,
|
|
122
|
+
self,
|
|
123
|
+
exc_type: type[Exception] | None,
|
|
124
|
+
exc_val: Exception | None,
|
|
125
|
+
exc_tb: TracebackType | None,
|
|
123
126
|
) -> None:
|
|
124
127
|
"""Async context manager exit."""
|
|
125
128
|
if self.api_client:
|
|
@@ -228,6 +231,35 @@ class SocialMediaPlatform(ABC):
|
|
|
228
231
|
if inspect.iscoroutine(result):
|
|
229
232
|
await result
|
|
230
233
|
|
|
234
|
+
# ==================== Validation Helpers ====================
|
|
235
|
+
|
|
236
|
+
@abstractmethod
|
|
237
|
+
def _validate_create_post_request(self, request: PostCreateRequest) -> None:
|
|
238
|
+
"""Validate a post creation request.
|
|
239
|
+
|
|
240
|
+
This base implementation performs no validation. Subclasses should
|
|
241
|
+
override this method to implement platform-specific validation rules.
|
|
242
|
+
|
|
243
|
+
The validation method should raise ValidationError with descriptive
|
|
244
|
+
messages including platform name and field name for consistency.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
request: The post creation request to validate.
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
ValidationError: If validation fails.
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> def _validate_create_post_request(self, request):
|
|
254
|
+
... if not request.content:
|
|
255
|
+
... raise ValidationError(
|
|
256
|
+
... "Content is required",
|
|
257
|
+
... platform=self.platform_name,
|
|
258
|
+
... field="content",
|
|
259
|
+
... )
|
|
260
|
+
"""
|
|
261
|
+
pass
|
|
262
|
+
|
|
231
263
|
# ==================== Abstract Authentication Methods ====================
|
|
232
264
|
|
|
233
265
|
@abstractmethod
|
marqetive/core/models.py
CHANGED
|
@@ -217,6 +217,7 @@ class AuthCredentials(BaseModel):
|
|
|
217
217
|
expires_at: Timestamp when access token expires
|
|
218
218
|
scope: List of permission scopes granted
|
|
219
219
|
user_id: ID of the authenticated user
|
|
220
|
+
username: Username/handle of the authenticated user
|
|
220
221
|
status: Current status of the account credentials
|
|
221
222
|
additional_data: Platform-specific auth data
|
|
222
223
|
|
|
@@ -237,6 +238,7 @@ class AuthCredentials(BaseModel):
|
|
|
237
238
|
expires_at: datetime | None = None
|
|
238
239
|
scope: list[str] = Field(default_factory=list)
|
|
239
240
|
user_id: str | None = None
|
|
241
|
+
username: str | None = None
|
|
240
242
|
status: AccountStatus = AccountStatus.VALID
|
|
241
243
|
additional_data: dict[str, Any] = Field(default_factory=dict)
|
|
242
244
|
|
|
@@ -211,6 +211,39 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
211
211
|
except httpx.HTTPError:
|
|
212
212
|
return False
|
|
213
213
|
|
|
214
|
+
# ==================== Validation ====================
|
|
215
|
+
|
|
216
|
+
def _validate_create_post_request(self, request: PostCreateRequest) -> None:
|
|
217
|
+
"""Validate Instagram post creation request.
|
|
218
|
+
|
|
219
|
+
Instagram Requirements:
|
|
220
|
+
- Media is ALWAYS required (Instagram is a visual platform)
|
|
221
|
+
- For carousels: 2-10 images
|
|
222
|
+
- For reels: 1 video (3 sec - 15 min)
|
|
223
|
+
- Caption max 2200 characters
|
|
224
|
+
- Media must be publicly accessible URLs
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
request: Post creation request to validate.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
ValidationError: If validation fails.
|
|
231
|
+
"""
|
|
232
|
+
if not request.media_urls and not request.media_ids:
|
|
233
|
+
raise ValidationError(
|
|
234
|
+
"Instagram posts require at least one media attachment. "
|
|
235
|
+
"Instagram is a visual platform - text-only posts are not supported.",
|
|
236
|
+
platform=self.platform_name,
|
|
237
|
+
field="media",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if request.content and len(request.content) > 2200:
|
|
241
|
+
raise ValidationError(
|
|
242
|
+
f"Caption exceeds 2200 characters ({len(request.content)} characters)",
|
|
243
|
+
platform=self.platform_name,
|
|
244
|
+
field="content",
|
|
245
|
+
)
|
|
246
|
+
|
|
214
247
|
# ==================== Post CRUD Methods ====================
|
|
215
248
|
|
|
216
249
|
async def create_post(self, request: PostCreateRequest) -> Post:
|
|
@@ -256,12 +289,8 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
256
289
|
if not self.api_client:
|
|
257
290
|
raise RuntimeError("Client must be used as async context manager")
|
|
258
291
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
"Instagram posts require at least one media attachment",
|
|
262
|
-
platform=self.platform_name,
|
|
263
|
-
field="media",
|
|
264
|
-
)
|
|
292
|
+
# Validate request
|
|
293
|
+
self._validate_create_post_request(request)
|
|
265
294
|
|
|
266
295
|
# Determine content type from request
|
|
267
296
|
media_type = self._get_media_type(request)
|
|
@@ -375,7 +404,9 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
375
404
|
content=request.content,
|
|
376
405
|
status=PostStatus.PUBLISHED,
|
|
377
406
|
created_at=datetime.now(),
|
|
407
|
+
author_id=self.instagram_account_id,
|
|
378
408
|
url=cast(HttpUrl, result.permalink) if result.permalink else None,
|
|
409
|
+
raw_data={"container_id": container_ids[0]},
|
|
379
410
|
)
|
|
380
411
|
|
|
381
412
|
async def _create_carousel_post(self, request: PostCreateRequest) -> Post:
|
|
@@ -826,7 +857,9 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
826
857
|
content=caption,
|
|
827
858
|
status=PostStatus.PUBLISHED,
|
|
828
859
|
created_at=datetime.now(),
|
|
860
|
+
author_id=self.instagram_account_id,
|
|
829
861
|
url=cast(HttpUrl, result.permalink) if result.permalink else None,
|
|
862
|
+
raw_data={"container_id": container_ids[0]},
|
|
830
863
|
)
|
|
831
864
|
|
|
832
865
|
async def create_reel(
|
|
@@ -890,7 +923,9 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
890
923
|
content=caption,
|
|
891
924
|
status=PostStatus.PUBLISHED,
|
|
892
925
|
created_at=datetime.now(),
|
|
926
|
+
author_id=self.instagram_account_id,
|
|
893
927
|
url=cast(HttpUrl, result.permalink) if result.permalink else None,
|
|
928
|
+
raw_data={"container_id": container_id},
|
|
894
929
|
)
|
|
895
930
|
|
|
896
931
|
async def create_story(
|
|
@@ -941,7 +976,9 @@ class InstagramClient(SocialMediaPlatform):
|
|
|
941
976
|
content=None, # Stories don't have captions
|
|
942
977
|
status=PostStatus.PUBLISHED,
|
|
943
978
|
created_at=datetime.now(),
|
|
979
|
+
author_id=self.instagram_account_id,
|
|
944
980
|
url=cast(HttpUrl, result.permalink) if result.permalink else None,
|
|
981
|
+
raw_data={"container_id": container_id},
|
|
945
982
|
)
|
|
946
983
|
|
|
947
984
|
# ==================== Helper Methods ====================
|