marqetive-lib 0.1.0__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/__init__.py +113 -0
- marqetive/core/__init__.py +5 -0
- marqetive/core/account_factory.py +212 -0
- marqetive/core/base_manager.py +303 -0
- marqetive/core/client.py +108 -0
- marqetive/core/progress.py +291 -0
- marqetive/core/registry.py +257 -0
- marqetive/platforms/__init__.py +55 -0
- marqetive/platforms/base.py +390 -0
- marqetive/platforms/exceptions.py +238 -0
- marqetive/platforms/instagram/__init__.py +7 -0
- marqetive/platforms/instagram/client.py +786 -0
- marqetive/platforms/instagram/exceptions.py +311 -0
- marqetive/platforms/instagram/factory.py +106 -0
- marqetive/platforms/instagram/manager.py +112 -0
- marqetive/platforms/instagram/media.py +669 -0
- marqetive/platforms/linkedin/__init__.py +7 -0
- marqetive/platforms/linkedin/client.py +733 -0
- marqetive/platforms/linkedin/exceptions.py +335 -0
- marqetive/platforms/linkedin/factory.py +130 -0
- marqetive/platforms/linkedin/manager.py +119 -0
- marqetive/platforms/linkedin/media.py +549 -0
- marqetive/platforms/models.py +345 -0
- marqetive/platforms/tiktok/__init__.py +0 -0
- marqetive/platforms/twitter/__init__.py +7 -0
- marqetive/platforms/twitter/client.py +647 -0
- marqetive/platforms/twitter/exceptions.py +311 -0
- marqetive/platforms/twitter/factory.py +151 -0
- marqetive/platforms/twitter/manager.py +121 -0
- marqetive/platforms/twitter/media.py +779 -0
- marqetive/platforms/twitter/threads.py +442 -0
- marqetive/py.typed +0 -0
- marqetive/registry_init.py +66 -0
- marqetive/utils/__init__.py +45 -0
- marqetive/utils/file_handlers.py +438 -0
- marqetive/utils/helpers.py +99 -0
- marqetive/utils/media.py +399 -0
- marqetive/utils/oauth.py +265 -0
- marqetive/utils/retry.py +239 -0
- marqetive/utils/token_validator.py +240 -0
- marqetive_lib-0.1.0.dist-info/METADATA +261 -0
- marqetive_lib-0.1.0.dist-info/RECORD +43 -0
- marqetive_lib-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Common Pydantic models for social media platform integrations.
|
|
2
|
+
|
|
3
|
+
This module defines universal data models that provide a consistent interface
|
|
4
|
+
across different social media platforms. All models use Pydantic for validation
|
|
5
|
+
and type safety.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import UTC, datetime, timedelta
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field, HttpUrl
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MediaType(str, Enum):
|
|
16
|
+
"""Supported media types for platform posts."""
|
|
17
|
+
|
|
18
|
+
IMAGE = "image"
|
|
19
|
+
VIDEO = "video"
|
|
20
|
+
CAROUSEL = "carousel"
|
|
21
|
+
DOCUMENT = "document"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PostStatus(str, Enum):
|
|
25
|
+
"""Status of a post on a platform."""
|
|
26
|
+
|
|
27
|
+
DRAFT = "draft"
|
|
28
|
+
PUBLISHED = "published"
|
|
29
|
+
SCHEDULED = "scheduled"
|
|
30
|
+
FAILED = "failed"
|
|
31
|
+
DELETED = "deleted"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CommentStatus(str, Enum):
|
|
35
|
+
"""Status of a comment."""
|
|
36
|
+
|
|
37
|
+
VISIBLE = "visible"
|
|
38
|
+
HIDDEN = "hidden"
|
|
39
|
+
DELETED = "deleted"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AccountStatus(str, Enum):
|
|
43
|
+
"""Status of a platform account's authentication.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
VALID: Account credentials are valid and working.
|
|
47
|
+
EXPIRED: Access token has expired but can be refreshed.
|
|
48
|
+
RECONNECTION_REQUIRED: OAuth error, user needs to reconnect account.
|
|
49
|
+
ERROR: Temporary error occurred during authentication check.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
VALID = "valid"
|
|
53
|
+
EXPIRED = "expired"
|
|
54
|
+
RECONNECTION_REQUIRED = "reconnection_required"
|
|
55
|
+
ERROR = "error"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class MediaAttachment(BaseModel):
|
|
59
|
+
"""Represents a media attachment (image, video, etc.).
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
media_id: Platform-specific media identifier
|
|
63
|
+
media_type: Type of media (image, video, etc.)
|
|
64
|
+
url: URL where the media is hosted
|
|
65
|
+
thumbnail_url: URL of the thumbnail (for videos)
|
|
66
|
+
width: Width in pixels
|
|
67
|
+
height: Height in pixels
|
|
68
|
+
size_bytes: File size in bytes
|
|
69
|
+
duration_seconds: Duration for video/audio media
|
|
70
|
+
alt_text: Alternative text for accessibility
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> media = MediaAttachment(
|
|
74
|
+
... media_id="12345",
|
|
75
|
+
... media_type=MediaType.IMAGE,
|
|
76
|
+
... url="https://example.com/image.jpg",
|
|
77
|
+
... width=1080,
|
|
78
|
+
... height=1080
|
|
79
|
+
... )
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
media_id: str
|
|
83
|
+
media_type: MediaType
|
|
84
|
+
url: HttpUrl
|
|
85
|
+
thumbnail_url: HttpUrl | None = None
|
|
86
|
+
width: int | None = None
|
|
87
|
+
height: int | None = None
|
|
88
|
+
size_bytes: int | None = None
|
|
89
|
+
duration_seconds: float | None = None
|
|
90
|
+
alt_text: str | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Post(BaseModel):
|
|
94
|
+
"""Universal representation of a social media post.
|
|
95
|
+
|
|
96
|
+
Attributes:
|
|
97
|
+
post_id: Platform-specific post identifier
|
|
98
|
+
platform: Name of the platform (instagram, twitter, linkedin)
|
|
99
|
+
content: Text content of the post
|
|
100
|
+
media: List of media attachments
|
|
101
|
+
status: Current status of the post
|
|
102
|
+
url: Public URL of the post
|
|
103
|
+
created_at: Timestamp when post was created
|
|
104
|
+
updated_at: Timestamp when post was last updated
|
|
105
|
+
scheduled_at: Timestamp when post is scheduled to publish
|
|
106
|
+
author_id: ID of the user who created the post
|
|
107
|
+
likes_count: Number of likes/reactions
|
|
108
|
+
comments_count: Number of comments
|
|
109
|
+
shares_count: Number of shares/retweets
|
|
110
|
+
views_count: Number of views/impressions
|
|
111
|
+
raw_data: Original platform-specific response data
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> post = Post(
|
|
115
|
+
... post_id="abc123",
|
|
116
|
+
... platform="instagram",
|
|
117
|
+
... content="Hello world!",
|
|
118
|
+
... status=PostStatus.PUBLISHED,
|
|
119
|
+
... created_at=datetime.now()
|
|
120
|
+
... )
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
post_id: str
|
|
124
|
+
platform: str
|
|
125
|
+
content: str | None = None
|
|
126
|
+
media: list[MediaAttachment] = Field(default_factory=list)
|
|
127
|
+
status: PostStatus = PostStatus.PUBLISHED
|
|
128
|
+
url: HttpUrl | None = None
|
|
129
|
+
created_at: datetime
|
|
130
|
+
updated_at: datetime | None = None
|
|
131
|
+
scheduled_at: datetime | None = None
|
|
132
|
+
author_id: str | None = None
|
|
133
|
+
likes_count: int = 0
|
|
134
|
+
comments_count: int = 0
|
|
135
|
+
shares_count: int = 0
|
|
136
|
+
views_count: int = 0
|
|
137
|
+
raw_data: dict[str, Any] = Field(default_factory=dict)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Comment(BaseModel):
|
|
141
|
+
"""Represents a comment on a post.
|
|
142
|
+
|
|
143
|
+
Attributes:
|
|
144
|
+
comment_id: Platform-specific comment identifier
|
|
145
|
+
post_id: ID of the post this comment belongs to
|
|
146
|
+
platform: Name of the platform
|
|
147
|
+
content: Text content of the comment
|
|
148
|
+
author_id: ID of the user who created the comment
|
|
149
|
+
author_username: Username of the comment author
|
|
150
|
+
created_at: Timestamp when comment was created
|
|
151
|
+
updated_at: Timestamp when comment was last updated
|
|
152
|
+
likes_count: Number of likes on the comment
|
|
153
|
+
replies_count: Number of replies to this comment
|
|
154
|
+
parent_comment_id: ID of parent comment if this is a reply
|
|
155
|
+
status: Current status of the comment
|
|
156
|
+
raw_data: Original platform-specific response data
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> comment = Comment(
|
|
160
|
+
... comment_id="comment123",
|
|
161
|
+
... post_id="post456",
|
|
162
|
+
... platform="twitter",
|
|
163
|
+
... content="Great post!",
|
|
164
|
+
... author_id="user789",
|
|
165
|
+
... created_at=datetime.now()
|
|
166
|
+
... )
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
comment_id: str
|
|
170
|
+
post_id: str
|
|
171
|
+
platform: str
|
|
172
|
+
content: str
|
|
173
|
+
author_id: str
|
|
174
|
+
author_username: str | None = None
|
|
175
|
+
created_at: datetime
|
|
176
|
+
updated_at: datetime | None = None
|
|
177
|
+
likes_count: int = 0
|
|
178
|
+
replies_count: int = 0
|
|
179
|
+
parent_comment_id: str | None = None
|
|
180
|
+
status: CommentStatus = CommentStatus.VISIBLE
|
|
181
|
+
raw_data: dict[str, Any] = Field(default_factory=dict)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class AuthCredentials(BaseModel):
|
|
185
|
+
"""Container for platform authentication credentials.
|
|
186
|
+
|
|
187
|
+
Attributes:
|
|
188
|
+
platform: Name of the platform
|
|
189
|
+
access_token: OAuth access token or API key
|
|
190
|
+
refresh_token: OAuth refresh token
|
|
191
|
+
token_type: Type of token (Bearer, OAuth, etc.)
|
|
192
|
+
expires_at: Timestamp when access token expires
|
|
193
|
+
scope: List of permission scopes granted
|
|
194
|
+
user_id: ID of the authenticated user
|
|
195
|
+
status: Current status of the account credentials
|
|
196
|
+
additional_data: Platform-specific auth data
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
>>> creds = AuthCredentials(
|
|
200
|
+
... platform="instagram",
|
|
201
|
+
... access_token="abc123",
|
|
202
|
+
... token_type="Bearer",
|
|
203
|
+
... expires_at=datetime(2025, 12, 31),
|
|
204
|
+
... status=AccountStatus.VALID
|
|
205
|
+
... )
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
platform: str
|
|
209
|
+
access_token: str
|
|
210
|
+
refresh_token: str | None = None
|
|
211
|
+
token_type: str = "Bearer"
|
|
212
|
+
expires_at: datetime | None = None
|
|
213
|
+
scope: list[str] = Field(default_factory=list)
|
|
214
|
+
user_id: str | None = None
|
|
215
|
+
status: AccountStatus = AccountStatus.VALID
|
|
216
|
+
additional_data: dict[str, Any] = Field(default_factory=dict)
|
|
217
|
+
|
|
218
|
+
def is_expired(self, threshold_minutes: int = 5) -> bool:
|
|
219
|
+
"""Check if the access token has expired or will expire soon.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
threshold_minutes: Consider token expired if it expires within
|
|
223
|
+
this many minutes (default: 5).
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if token is expired or will expire soon, False otherwise.
|
|
227
|
+
"""
|
|
228
|
+
if self.expires_at is None:
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
threshold = datetime.now(UTC) + timedelta(minutes=threshold_minutes)
|
|
232
|
+
return self.expires_at.astimezone(UTC) <= threshold
|
|
233
|
+
|
|
234
|
+
def is_valid(self) -> bool:
|
|
235
|
+
"""Check if credentials are valid and not expired.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
True if status is VALID and token is not expired, False otherwise.
|
|
239
|
+
"""
|
|
240
|
+
return self.status == AccountStatus.VALID and not self.is_expired()
|
|
241
|
+
|
|
242
|
+
def needs_refresh(self) -> bool:
|
|
243
|
+
"""Check if credentials need to be refreshed.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
True if token is expired or status is EXPIRED, False otherwise.
|
|
247
|
+
"""
|
|
248
|
+
return self.status == AccountStatus.EXPIRED or self.is_expired()
|
|
249
|
+
|
|
250
|
+
def mark_expired(self) -> None:
|
|
251
|
+
"""Mark credentials as expired."""
|
|
252
|
+
self.status = AccountStatus.EXPIRED
|
|
253
|
+
|
|
254
|
+
def mark_valid(self) -> None:
|
|
255
|
+
"""Mark credentials as valid."""
|
|
256
|
+
self.status = AccountStatus.VALID
|
|
257
|
+
|
|
258
|
+
def mark_reconnection_required(self) -> None:
|
|
259
|
+
"""Mark credentials as requiring reconnection (OAuth error)."""
|
|
260
|
+
self.status = AccountStatus.RECONNECTION_REQUIRED
|
|
261
|
+
|
|
262
|
+
def mark_error(self) -> None:
|
|
263
|
+
"""Mark credentials as having a temporary error."""
|
|
264
|
+
self.status = AccountStatus.ERROR
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class PlatformResponse(BaseModel):
|
|
268
|
+
"""Wrapper for platform API responses.
|
|
269
|
+
|
|
270
|
+
Attributes:
|
|
271
|
+
success: Whether the API call was successful
|
|
272
|
+
platform: Name of the platform
|
|
273
|
+
data: Response data (can be any type)
|
|
274
|
+
error_message: Error message if success is False
|
|
275
|
+
status_code: HTTP status code
|
|
276
|
+
rate_limit_remaining: Remaining API calls in current window
|
|
277
|
+
rate_limit_reset: Timestamp when rate limit resets
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
>>> response = PlatformResponse(
|
|
281
|
+
... success=True,
|
|
282
|
+
... platform="twitter",
|
|
283
|
+
... data={"tweet_id": "123"},
|
|
284
|
+
... status_code=200
|
|
285
|
+
... )
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
success: bool
|
|
289
|
+
platform: str
|
|
290
|
+
data: Any = None
|
|
291
|
+
error_message: str | None = None
|
|
292
|
+
status_code: int | None = None
|
|
293
|
+
rate_limit_remaining: int | None = None
|
|
294
|
+
rate_limit_reset: datetime | None = None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class PostCreateRequest(BaseModel):
|
|
298
|
+
"""Request model for creating a new post.
|
|
299
|
+
|
|
300
|
+
Attributes:
|
|
301
|
+
content: Text content of the post
|
|
302
|
+
media_urls: List of URLs to media files to attach
|
|
303
|
+
media_ids: List of pre-uploaded media IDs
|
|
304
|
+
schedule_at: Optional timestamp to schedule the post
|
|
305
|
+
link: URL to include in the post
|
|
306
|
+
tags: List of hashtags or user tags
|
|
307
|
+
location: Location/place tag for the post
|
|
308
|
+
|
|
309
|
+
Example:
|
|
310
|
+
>>> request = PostCreateRequest(
|
|
311
|
+
... content="Check out our new product!",
|
|
312
|
+
... media_urls=["https://example.com/image.jpg"],
|
|
313
|
+
... tags=["#newproduct", "#launch"]
|
|
314
|
+
... )
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
content: str | None = None
|
|
318
|
+
media_urls: list[str] = Field(default_factory=list)
|
|
319
|
+
media_ids: list[str] = Field(default_factory=list)
|
|
320
|
+
schedule_at: datetime | None = None
|
|
321
|
+
link: str | None = None
|
|
322
|
+
tags: list[str] = Field(default_factory=list)
|
|
323
|
+
location: str | None = None
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class PostUpdateRequest(BaseModel):
|
|
327
|
+
"""Request model for updating an existing post.
|
|
328
|
+
|
|
329
|
+
Note: Not all platforms support editing posts. Fields that can be
|
|
330
|
+
updated vary by platform.
|
|
331
|
+
|
|
332
|
+
Attributes:
|
|
333
|
+
content: Updated text content
|
|
334
|
+
tags: Updated list of tags
|
|
335
|
+
location: Updated location
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
>>> request = PostUpdateRequest(
|
|
339
|
+
... content="Updated content here"
|
|
340
|
+
... )
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
content: str | None = None
|
|
344
|
+
tags: list[str] | None = None
|
|
345
|
+
location: str | None = None
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Twitter/X platform integration."""
|
|
2
|
+
|
|
3
|
+
from marqetive.platforms.twitter.client import TwitterClient
|
|
4
|
+
from marqetive.platforms.twitter.factory import TwitterAccountFactory
|
|
5
|
+
from marqetive.platforms.twitter.manager import TwitterPostManager
|
|
6
|
+
|
|
7
|
+
__all__ = ["TwitterClient", "TwitterAccountFactory", "TwitterPostManager"]
|