marqetive-lib 0.1.6__py3-none-any.whl → 0.1.8__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.
@@ -0,0 +1,413 @@
1
+ """LinkedIn-specific models for the Community Management API.
2
+
3
+ This module defines LinkedIn-specific data models that extend the core models
4
+ for features unique to LinkedIn's Community Management API, including
5
+ reactions, social metadata, and organization management.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from enum import StrEnum
10
+ from typing import Any, Literal
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+
15
+ class ReactionType(StrEnum):
16
+ """LinkedIn reaction types.
17
+
18
+ These correspond to the UI labels users see when reacting to content.
19
+
20
+ Attributes:
21
+ LIKE: Standard like reaction
22
+ PRAISE: "Celebrate" reaction
23
+ EMPATHY: "Love" reaction
24
+ INTEREST: "Insightful" reaction
25
+ APPRECIATION: "Support" reaction
26
+ ENTERTAINMENT: "Funny" reaction
27
+ """
28
+
29
+ LIKE = "LIKE"
30
+ PRAISE = "PRAISE" # Celebrate
31
+ EMPATHY = "EMPATHY" # Love
32
+ INTEREST = "INTEREST" # Insightful
33
+ APPRECIATION = "APPRECIATION" # Support
34
+ ENTERTAINMENT = "ENTERTAINMENT" # Funny
35
+
36
+
37
+ class CallToActionLabel(StrEnum):
38
+ """LinkedIn call-to-action button labels for posts.
39
+
40
+ These can be used with articles and sponsored content to add
41
+ clickable action buttons.
42
+ """
43
+
44
+ APPLY = "APPLY"
45
+ DOWNLOAD = "DOWNLOAD"
46
+ VIEW_QUOTE = "VIEW_QUOTE"
47
+ LEARN_MORE = "LEARN_MORE"
48
+ SIGN_UP = "SIGN_UP"
49
+ SUBSCRIBE = "SUBSCRIBE"
50
+ REGISTER = "REGISTER"
51
+ JOIN = "JOIN"
52
+ ATTEND = "ATTEND"
53
+ REQUEST_DEMO = "REQUEST_DEMO"
54
+ SEE_MORE = "SEE_MORE"
55
+ BUY_NOW = "BUY_NOW"
56
+ SHOP_NOW = "SHOP_NOW"
57
+
58
+
59
+ class FeedDistribution(StrEnum):
60
+ """LinkedIn feed distribution options for posts.
61
+
62
+ Controls how and where posts are distributed in feeds.
63
+ """
64
+
65
+ MAIN_FEED = "MAIN_FEED"
66
+ NONE = "NONE"
67
+
68
+
69
+ class PostVisibility(StrEnum):
70
+ """LinkedIn post visibility options.
71
+
72
+ Controls who can see the post.
73
+ """
74
+
75
+ PUBLIC = "PUBLIC"
76
+ CONNECTIONS = "CONNECTIONS"
77
+ LOGGED_IN = "LOGGED_IN"
78
+ CONTAINER = "CONTAINER"
79
+
80
+
81
+ class CommentsState(StrEnum):
82
+ """State of comments on a LinkedIn post.
83
+
84
+ OPEN: Comments are enabled
85
+ CLOSED: Comments are disabled (deletes existing comments)
86
+ """
87
+
88
+ OPEN = "OPEN"
89
+ CLOSED = "CLOSED"
90
+
91
+
92
+ class OrganizationType(StrEnum):
93
+ """LinkedIn organization types."""
94
+
95
+ COMPANY = "COMPANY"
96
+ SCHOOL = "SCHOOL"
97
+ GROUP = "GROUP"
98
+ SHOWCASE = "SHOWCASE"
99
+
100
+
101
+ class Reaction(BaseModel):
102
+ """Represents a reaction on a LinkedIn post or comment.
103
+
104
+ Attributes:
105
+ actor: URN of the person or organization that reacted
106
+ entity: URN of the entity (post/comment) that was reacted to
107
+ reaction_type: Type of reaction (LIKE, PRAISE, etc.)
108
+ created_at: Timestamp when the reaction was created
109
+
110
+ Example:
111
+ >>> reaction = Reaction(
112
+ ... actor="urn:li:person:abc123",
113
+ ... entity="urn:li:share:12345",
114
+ ... reaction_type=ReactionType.LIKE
115
+ ... )
116
+ """
117
+
118
+ actor: str
119
+ entity: str
120
+ reaction_type: ReactionType
121
+ created_at: datetime | None = None
122
+ raw_data: dict[str, Any] = Field(default_factory=dict)
123
+
124
+
125
+ class ReactionSummary(BaseModel):
126
+ """Summary of reactions by type.
127
+
128
+ Attributes:
129
+ reaction_type: Type of reaction
130
+ count: Number of reactions of this type
131
+ """
132
+
133
+ reaction_type: ReactionType
134
+ count: int
135
+
136
+
137
+ class CommentSummary(BaseModel):
138
+ """Summary of comments on a post.
139
+
140
+ Attributes:
141
+ count: Total number of comments (including replies)
142
+ top_level_count: Number of top-level comments (excluding replies)
143
+ """
144
+
145
+ count: int
146
+ top_level_count: int
147
+
148
+
149
+ class SocialMetadata(BaseModel):
150
+ """Social metadata for a LinkedIn post or comment.
151
+
152
+ Contains engagement metrics like reactions and comment counts.
153
+
154
+ Attributes:
155
+ entity: URN of the entity (post/comment)
156
+ reaction_summaries: Dictionary mapping reaction types to counts
157
+ comment_count: Total number of comments
158
+ top_level_comment_count: Number of top-level comments
159
+ comments_state: Whether comments are enabled (OPEN) or disabled (CLOSED)
160
+
161
+ Example:
162
+ >>> metadata = SocialMetadata(
163
+ ... entity="urn:li:share:12345",
164
+ ... reaction_summaries={ReactionType.LIKE: 10, ReactionType.PRAISE: 5},
165
+ ... comment_count=3,
166
+ ... top_level_comment_count=2,
167
+ ... comments_state=CommentsState.OPEN
168
+ ... )
169
+ """
170
+
171
+ entity: str
172
+ reaction_summaries: dict[ReactionType, int] = Field(default_factory=dict)
173
+ comment_count: int = 0
174
+ top_level_comment_count: int = 0
175
+ comments_state: CommentsState = CommentsState.OPEN
176
+ raw_data: dict[str, Any] = Field(default_factory=dict)
177
+
178
+
179
+ class Organization(BaseModel):
180
+ """Represents a LinkedIn organization (Company Page).
181
+
182
+ Attributes:
183
+ id: Organization URN (e.g., urn:li:organization:12345)
184
+ name: Organization name
185
+ localized_name: Localized organization name
186
+ vanity_name: URL-friendly name (e.g., "linkedin" for linkedin.com/company/linkedin)
187
+ logo_url: URL of the organization's logo
188
+ follower_count: Number of followers
189
+ primary_type: Type of organization (COMPANY, SCHOOL, etc.)
190
+ website_url: Organization's website
191
+ description: Organization description
192
+ industry: Industry category
193
+
194
+ Example:
195
+ >>> org = Organization(
196
+ ... id="urn:li:organization:12345",
197
+ ... name="Example Corp",
198
+ ... localized_name="Example Corp",
199
+ ... vanity_name="examplecorp"
200
+ ... )
201
+ """
202
+
203
+ id: str
204
+ name: str
205
+ localized_name: str
206
+ vanity_name: str | None = None
207
+ logo_url: str | None = None
208
+ follower_count: int | None = None
209
+ primary_type: OrganizationType | None = None
210
+ website_url: str | None = None
211
+ description: str | None = None
212
+ industry: str | None = None
213
+ raw_data: dict[str, Any] = Field(default_factory=dict)
214
+
215
+
216
+ class LinkedInPostContent(BaseModel):
217
+ """Content attachment for a LinkedIn post.
218
+
219
+ Can contain media (image/video/document) or an article link.
220
+
221
+ Attributes:
222
+ media: Media content details
223
+ article: Article link details
224
+ """
225
+
226
+ media: "MediaContent | None" = None
227
+ article: "ArticleContent | None" = None
228
+
229
+
230
+ class MediaContent(BaseModel):
231
+ """Media content for a LinkedIn post.
232
+
233
+ Attributes:
234
+ id: Media URN (e.g., urn:li:image:xxx, urn:li:video:xxx)
235
+ title: Optional title for the media
236
+ alt_text: Alternative text for accessibility
237
+ """
238
+
239
+ id: str
240
+ title: str | None = None
241
+ alt_text: str | None = None
242
+
243
+
244
+ class ArticleContent(BaseModel):
245
+ """Article content for a LinkedIn post.
246
+
247
+ Attributes:
248
+ source: URL of the article
249
+ title: Article title
250
+ description: Article description
251
+ thumbnail: URN of the thumbnail image
252
+ """
253
+
254
+ source: str
255
+ title: str | None = None
256
+ description: str | None = None
257
+ thumbnail: str | None = None
258
+
259
+
260
+ class LinkedInPostDistribution(BaseModel):
261
+ """Distribution settings for a LinkedIn post.
262
+
263
+ Attributes:
264
+ feed_distribution: Where to distribute the post
265
+ target_entities: Audience targeting criteria
266
+ third_party_distribution_channels: External distribution channels
267
+ """
268
+
269
+ feed_distribution: FeedDistribution = FeedDistribution.MAIN_FEED
270
+ target_entities: list[dict[str, Any]] = Field(default_factory=list)
271
+ third_party_distribution_channels: list[str] = Field(default_factory=list)
272
+
273
+
274
+ class CommentMention(BaseModel):
275
+ """Mention in a comment.
276
+
277
+ Attributes:
278
+ start: Start position in the text
279
+ length: Length of the mention text
280
+ person_urn: URN of the mentioned person (if person mention)
281
+ organization_urn: URN of the mentioned organization (if org mention)
282
+ """
283
+
284
+ start: int
285
+ length: int
286
+ person_urn: str | None = None
287
+ organization_urn: str | None = None
288
+
289
+
290
+ class LinkedInCommentRequest(BaseModel):
291
+ """Request model for creating or updating a LinkedIn comment.
292
+
293
+ Attributes:
294
+ content: Text content of the comment
295
+ parent_comment_id: URN of parent comment (for nested replies)
296
+ mentions: List of mentions to include
297
+ image_id: URN of image to attach to comment
298
+
299
+ Example:
300
+ >>> request = LinkedInCommentRequest(
301
+ ... content="Great post! @[John Doe](urn:li:person:abc123)",
302
+ ... mentions=[CommentMention(start=12, length=8, person_urn="urn:li:person:abc123")]
303
+ ... )
304
+ """
305
+
306
+ content: str
307
+ parent_comment_id: str | None = None
308
+ mentions: list[CommentMention] = Field(default_factory=list)
309
+ image_id: str | None = None
310
+
311
+
312
+ # Type alias for sort options
313
+ type PostSortBy = Literal["LAST_MODIFIED", "CREATED"]
314
+ type ReactionSortBy = Literal["CHRONOLOGICAL", "REVERSE_CHRONOLOGICAL", "RELEVANCE"]
315
+
316
+
317
+ class LinkedInPostRequest(BaseModel):
318
+ """LinkedIn-specific post creation request.
319
+
320
+ Supports text posts, articles, media (images/videos/documents), and
321
+ rich formatting options like CTAs and visibility controls.
322
+ LinkedIn has a 3000 character limit for post content.
323
+
324
+ Attributes:
325
+ content: Post text/commentary (max 3000 characters)
326
+ media_urls: List of media URLs to upload
327
+ media_ids: List of pre-uploaded media URNs
328
+ link: Article/link URL to share
329
+ visibility: Post visibility (PUBLIC, CONNECTIONS, LOGGED_IN)
330
+ feed_distribution: Feed distribution (MAIN_FEED, NONE)
331
+ target_entities: Audience targeting criteria
332
+ call_to_action: CTA button label
333
+ landing_page: URL for CTA button
334
+ article_title: Title for link preview
335
+ article_description: Description for link preview
336
+ article_thumbnail: Thumbnail URN for article
337
+ media_title: Title for media attachment
338
+ media_alt_text: Alt text for media (accessibility)
339
+ disable_reshare: Prevent resharing
340
+ disable_comments: Disable comments
341
+
342
+ Example:
343
+ >>> # Simple post
344
+ >>> request = LinkedInPostRequest(
345
+ ... content="Excited to share our latest update!"
346
+ ... )
347
+
348
+ >>> # Article post with CTA
349
+ >>> request = LinkedInPostRequest(
350
+ ... content="Check out our new blog post!",
351
+ ... link="https://example.com/blog",
352
+ ... article_title="Our Latest Update",
353
+ ... article_description="Learn about new features",
354
+ ... call_to_action=CallToActionLabel.LEARN_MORE,
355
+ ... landing_page="https://example.com/learn-more"
356
+ ... )
357
+
358
+ >>> # Post with media and visibility control
359
+ >>> request = LinkedInPostRequest(
360
+ ... content="New product launch!",
361
+ ... media_urls=["https://example.com/product.jpg"],
362
+ ... visibility=PostVisibility.PUBLIC,
363
+ ... media_alt_text="Product image"
364
+ ... )
365
+ """
366
+
367
+ content: str
368
+ media_urls: list[str] = Field(default_factory=list)
369
+ media_ids: list[str] = Field(default_factory=list)
370
+ link: str | None = None
371
+ visibility: PostVisibility = PostVisibility.PUBLIC
372
+ feed_distribution: FeedDistribution = FeedDistribution.MAIN_FEED
373
+ target_entities: list[dict[str, Any]] = Field(default_factory=list)
374
+ call_to_action: CallToActionLabel | None = None
375
+ landing_page: str | None = None
376
+ article_title: str | None = None
377
+ article_description: str | None = None
378
+ article_thumbnail: str | None = None
379
+ media_title: str | None = None
380
+ media_alt_text: str | None = None
381
+ disable_reshare: bool = False
382
+ disable_comments: bool = False
383
+
384
+
385
+ class LinkedInPostUpdateRequest(BaseModel):
386
+ """LinkedIn-specific post update request.
387
+
388
+ LinkedIn supports updating certain fields of published posts:
389
+ - commentary (post text)
390
+ - call-to-action label and landing page
391
+ - lifecycle state
392
+ - ad context (for sponsored content)
393
+
394
+ Attributes:
395
+ content: Updated post text/commentary
396
+ call_to_action: Updated CTA button label
397
+ landing_page: Updated URL for CTA button
398
+ lifecycle_state: Updated lifecycle state (e.g., PUBLISHED, DRAFT)
399
+ ad_context: Ad context updates for sponsored content
400
+
401
+ Example:
402
+ >>> request = LinkedInPostUpdateRequest(
403
+ ... content="Updated post content!",
404
+ ... call_to_action=CallToActionLabel.SIGN_UP,
405
+ ... landing_page="https://example.com/signup"
406
+ ... )
407
+ """
408
+
409
+ content: str | None = None
410
+ call_to_action: CallToActionLabel | None = None
411
+ landing_page: str | None = None
412
+ lifecycle_state: str | None = None
413
+ ad_context: dict[str, Any] | None = None
@@ -1,5 +1,6 @@
1
1
  """TikTok platform integration."""
2
2
 
3
3
  from marqetive.platforms.tiktok.client import TikTokClient
4
+ from marqetive.platforms.tiktok.models import PrivacyLevel, TikTokPostRequest
4
5
 
5
- __all__ = ["TikTokClient"]
6
+ __all__ = ["TikTokClient", "TikTokPostRequest", "PrivacyLevel"]
@@ -33,9 +33,9 @@ from marqetive.platforms.tiktok.exceptions import TikTokErrorCode, map_tiktok_er
33
33
  from marqetive.platforms.tiktok.media import (
34
34
  CreatorInfo,
35
35
  MediaUploadResult,
36
- PrivacyLevel,
37
36
  TikTokMediaManager,
38
37
  )
38
+ from marqetive.platforms.tiktok.models import PrivacyLevel
39
39
 
40
40
  # TikTok API base URL
41
41
  TIKTOK_API_BASE = "https://open.tiktokapis.com/v2"
@@ -107,6 +107,7 @@ class TikTokClient(SocialMediaPlatform):
107
107
  open_id=self.credentials.additional_data["open_id"],
108
108
  timeout=self.timeout,
109
109
  )
110
+ await self._media_manager.__aenter__()
110
111
 
111
112
  async def _cleanup_managers(self) -> None:
112
113
  """Cleanup media manager."""
@@ -213,7 +214,7 @@ class TikTokClient(SocialMediaPlatform):
213
214
  await self.query_creator_info()
214
215
 
215
216
  # 2. Determine privacy level
216
- privacy_level = PrivacyLevel.SELF_ONLY # Default for unaudited apps
217
+ privacy_level = PrivacyLevel.PRIVATE # Default for unaudited apps
217
218
  requested_privacy = request.additional_data.get("privacy_level")
218
219
  if requested_privacy and self._creator_info:
219
220
  # Check if requested privacy is available
@@ -293,7 +294,7 @@ class TikTokClient(SocialMediaPlatform):
293
294
 
294
295
  async def update_post(
295
296
  self,
296
- post_id: str,
297
+ post_id: str, # noqa: ARG002
297
298
  request: PostUpdateRequest, # noqa: ARG002
298
299
  ) -> Post:
299
300
  """Update a TikTok video.
@@ -340,7 +341,7 @@ class TikTokClient(SocialMediaPlatform):
340
341
 
341
342
  async def create_comment(
342
343
  self,
343
- post_id: str,
344
+ post_id: str, # noqa: ARG002
344
345
  content: str, # noqa: ARG002
345
346
  ) -> Comment:
346
347
  """Create a comment on a TikTok video.