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.
Files changed (43) hide show
  1. marqetive/__init__.py +113 -0
  2. marqetive/core/__init__.py +5 -0
  3. marqetive/core/account_factory.py +212 -0
  4. marqetive/core/base_manager.py +303 -0
  5. marqetive/core/client.py +108 -0
  6. marqetive/core/progress.py +291 -0
  7. marqetive/core/registry.py +257 -0
  8. marqetive/platforms/__init__.py +55 -0
  9. marqetive/platforms/base.py +390 -0
  10. marqetive/platforms/exceptions.py +238 -0
  11. marqetive/platforms/instagram/__init__.py +7 -0
  12. marqetive/platforms/instagram/client.py +786 -0
  13. marqetive/platforms/instagram/exceptions.py +311 -0
  14. marqetive/platforms/instagram/factory.py +106 -0
  15. marqetive/platforms/instagram/manager.py +112 -0
  16. marqetive/platforms/instagram/media.py +669 -0
  17. marqetive/platforms/linkedin/__init__.py +7 -0
  18. marqetive/platforms/linkedin/client.py +733 -0
  19. marqetive/platforms/linkedin/exceptions.py +335 -0
  20. marqetive/platforms/linkedin/factory.py +130 -0
  21. marqetive/platforms/linkedin/manager.py +119 -0
  22. marqetive/platforms/linkedin/media.py +549 -0
  23. marqetive/platforms/models.py +345 -0
  24. marqetive/platforms/tiktok/__init__.py +0 -0
  25. marqetive/platforms/twitter/__init__.py +7 -0
  26. marqetive/platforms/twitter/client.py +647 -0
  27. marqetive/platforms/twitter/exceptions.py +311 -0
  28. marqetive/platforms/twitter/factory.py +151 -0
  29. marqetive/platforms/twitter/manager.py +121 -0
  30. marqetive/platforms/twitter/media.py +779 -0
  31. marqetive/platforms/twitter/threads.py +442 -0
  32. marqetive/py.typed +0 -0
  33. marqetive/registry_init.py +66 -0
  34. marqetive/utils/__init__.py +45 -0
  35. marqetive/utils/file_handlers.py +438 -0
  36. marqetive/utils/helpers.py +99 -0
  37. marqetive/utils/media.py +399 -0
  38. marqetive/utils/oauth.py +265 -0
  39. marqetive/utils/retry.py +239 -0
  40. marqetive/utils/token_validator.py +240 -0
  41. marqetive_lib-0.1.0.dist-info/METADATA +261 -0
  42. marqetive_lib-0.1.0.dist-info/RECORD +43 -0
  43. 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"]