upload-post 2.4.0__tar.gz → 2.6.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: upload-post
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Cross-platform social media upload for TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, Discord, Telegram, and X (Twitter)
5
5
  Home-page: https://www.upload-post.com/
6
6
  Author: Upload-Post
@@ -300,6 +300,7 @@ boards = client.get_pinterest_boards("my-profile")
300
300
  - `allowedCountries` / `blockedCountries` - Country restrictions
301
301
  - `hasPaidProductPlacement` - Paid placement flag
302
302
  - `recordingDate` - Recording date (ISO 8601)
303
+ - `youtube_playlist_id` - Playlist ID (single string, list, or comma-separated) to add the uploaded video to after publishing
303
304
 
304
305
  ### LinkedIn
305
306
  - `visibility` - PUBLIC, CONNECTIONS, LOGGED_IN, CONTAINER
@@ -262,6 +262,7 @@ boards = client.get_pinterest_boards("my-profile")
262
262
  - `allowedCountries` / `blockedCountries` - Country restrictions
263
263
  - `hasPaidProductPlacement` - Paid placement flag
264
264
  - `recordingDate` - Recording date (ISO 8601)
265
+ - `youtube_playlist_id` - Playlist ID (single string, list, or comma-separated) to add the uploaded video to after publishing
265
266
 
266
267
  ### LinkedIn
267
268
  - `visibility` - PUBLIC, CONNECTIONS, LOGGED_IN, CONTAINER
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="upload-post",
8
- version="2.4.0",
8
+ version="2.6.0",
9
9
  author="Upload-Post",
10
10
  author_email="hi@img2html.com",
11
11
  description="Cross-platform social media upload for TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, Discord, Telegram, and X (Twitter)",
@@ -15,7 +15,7 @@ Example:
15
15
  ... )
16
16
  """
17
17
 
18
- __version__ = "2.4.0"
18
+ __version__ = "2.6.0"
19
19
 
20
20
  from .api_client import UploadPostClient, UploadPostError
21
21
 
@@ -262,6 +262,12 @@ class UploadPostClient:
262
262
  data.append(("hasPaidProductPlacement", str(kwargs["hasPaidProductPlacement"]).lower()))
263
263
  if kwargs.get("recordingDate"):
264
264
  data.append(("recordingDate", kwargs["recordingDate"]))
265
+ playlist_value = kwargs.get("youtube_playlist_id")
266
+ if playlist_value:
267
+ if isinstance(playlist_value, (list, tuple)):
268
+ playlist_value = ",".join(str(p).strip() for p in playlist_value if str(p).strip())
269
+ if playlist_value:
270
+ data.append(("youtube_playlist_id", str(playlist_value)))
265
271
  if kwargs.get("subtitles"):
266
272
  for idx, sub in enumerate(kwargs["subtitles"]):
267
273
  if sub.get("language"):
@@ -464,6 +470,8 @@ class UploadPostClient:
464
470
  blockedCountries: Comma-separated country codes
465
471
  hasPaidProductPlacement: Paid placement flag
466
472
  recordingDate: Recording date (ISO 8601)
473
+ youtube_playlist_id: Playlist ID (or list/comma-separated of IDs) to add
474
+ the uploaded video to after publishing
467
475
  subtitles: List of subtitle dicts with keys: language (BCP-47),
468
476
  name (display name), file (path to SRT/VTT file), url (subtitle URL)
469
477
 
@@ -904,6 +912,53 @@ class UploadPostClient:
904
912
  """
905
913
  return self._request("/uploadposts/history", "GET", params={"page": page, "limit": limit})
906
914
 
915
+ def retry_post(
916
+ self,
917
+ request_id: Optional[str] = None,
918
+ job_id: Optional[str] = None
919
+ ) -> Dict:
920
+ """
921
+ Retry a failed post.
922
+
923
+ Args:
924
+ request_id: Request ID of the post to retry.
925
+ job_id: Job ID of the post to retry.
926
+
927
+ One of request_id or job_id is required.
928
+
929
+ Returns:
930
+ Retry result.
931
+ """
932
+ json_data = {}
933
+ if request_id is not None:
934
+ json_data["request_id"] = request_id
935
+ if job_id is not None:
936
+ json_data["job_id"] = job_id
937
+ return self._request("/uploadposts/posts/retry", "POST", json_data=json_data)
938
+
939
+ def unpublish_post(
940
+ self,
941
+ user: str,
942
+ platform: str,
943
+ post_id: str
944
+ ) -> Dict:
945
+ """
946
+ Unpublish (delete) a previously published post.
947
+
948
+ Args:
949
+ user: Profile username.
950
+ platform: Platform name (facebook, youtube, x, linkedin, threads).
951
+ post_id: ID of the published post to unpublish.
952
+
953
+ Returns:
954
+ Unpublish result.
955
+ """
956
+ return self._request("/uploadposts/posts/unpublish", "POST", json_data={
957
+ "user": user,
958
+ "platform": platform,
959
+ "post_id": post_id
960
+ })
961
+
907
962
  def get_analytics(self, profile_username: str, platforms: Optional[List[str]] = None,
908
963
  page_id: Optional[str] = None, page_urn: Optional[str] = None) -> Dict:
909
964
  """
@@ -913,7 +968,10 @@ class UploadPostClient:
913
968
  profile_username: Profile username.
914
969
  platforms: Filter by platforms (instagram, linkedin, facebook, x, youtube, tiktok, threads, pinterest, reddit).
915
970
  page_id: Facebook Page ID (required for Facebook analytics).
916
- page_urn: LinkedIn page URN (defaults to "me" for personal profile).
971
+ page_urn: LinkedIn organization/company page URN or numeric ID. LinkedIn
972
+ analytics are only available for pages you administer; personal profiles
973
+ are not supported (LinkedIn's API exposes no member-level analytics). If
974
+ omitted, the first administered organization page is used.
917
975
 
918
976
  Returns:
919
977
  Analytics data per platform. For Instagram, the response includes both
@@ -1291,32 +1349,43 @@ class UploadPostClient:
1291
1349
  def get_post_comments(
1292
1350
  self,
1293
1351
  user: str,
1352
+ platform: str = "instagram",
1294
1353
  post_id: Optional[str] = None,
1295
- post_url: Optional[str] = None
1354
+ post_url: Optional[str] = None,
1355
+ limit: Optional[int] = None,
1356
+ after: Optional[str] = None
1296
1357
  ) -> Dict:
1297
1358
  """
1298
- Get comments on an Instagram post.
1359
+ Get comments on a post.
1299
1360
 
1300
1361
  Args:
1301
1362
  user: Profile username.
1302
- post_id: Numeric media ID (provide post_id or post_url).
1303
- post_url: Full Instagram post URL (provide post_id or post_url).
1363
+ platform: Platform name (instagram, facebook, youtube, linkedin).
1364
+ post_id: Post/media ID (provide post_id or post_url).
1365
+ post_url: Full post URL (provide post_id or post_url).
1366
+ limit: Maximum number of comments to return.
1367
+ after: Pagination cursor for the next page of comments.
1304
1368
 
1305
1369
  Returns:
1306
1370
  Comments data including comment IDs, text, timestamps, and user info.
1307
1371
  """
1308
- params = {"platform": "instagram", "user": user}
1372
+ params = {"platform": platform, "user": user}
1309
1373
  if post_id:
1310
1374
  params["post_id"] = post_id
1311
1375
  if post_url:
1312
1376
  params["post_url"] = post_url
1377
+ if limit is not None:
1378
+ params["limit"] = limit
1379
+ if after is not None:
1380
+ params["after"] = after
1313
1381
  return self._request("/uploadposts/comments", "GET", params=params)
1314
1382
 
1315
1383
  def reply_to_comment(
1316
1384
  self,
1317
1385
  user: str,
1318
1386
  comment_id: str,
1319
- message: str
1387
+ message: str,
1388
+ buttons: Optional[List[Dict]] = None
1320
1389
  ) -> Dict:
1321
1390
  """
1322
1391
  Send a private reply (DM) to the author of an Instagram comment.
@@ -1325,16 +1394,21 @@ class UploadPostClient:
1325
1394
  user: Profile username.
1326
1395
  comment_id: Comment ID from get_post_comments.
1327
1396
  message: Reply message text.
1397
+ buttons: Optional list of web_url buttons (max 3) rendered in the Instagram
1398
+ DM. Each item is a dict like {"title": "Open", "url": "https://..."}.
1328
1399
 
1329
1400
  Returns:
1330
1401
  Reply result with recipient_id and message_id.
1331
1402
  """
1332
- return self._request("/uploadposts/comments/reply", "POST", json_data={
1403
+ json_data = {
1333
1404
  "platform": "instagram",
1334
1405
  "user": user,
1335
1406
  "comment_id": comment_id,
1336
1407
  "message": message
1337
- })
1408
+ }
1409
+ if buttons is not None:
1410
+ json_data["buttons"] = buttons
1411
+ return self._request("/uploadposts/comments/reply", "POST", json_data=json_data)
1338
1412
 
1339
1413
  def public_reply_to_comment(
1340
1414
  self,
@@ -1360,6 +1434,72 @@ class UploadPostClient:
1360
1434
  "message": message
1361
1435
  })
1362
1436
 
1437
+ def create_comment(
1438
+ self,
1439
+ user: str,
1440
+ platform: str,
1441
+ message: str,
1442
+ post_id: Optional[str] = None,
1443
+ post_url: Optional[str] = None,
1444
+ comment_id: Optional[str] = None
1445
+ ) -> Dict:
1446
+ """
1447
+ Create a comment on a post, or reply to an existing comment.
1448
+
1449
+ Args:
1450
+ user: Profile username.
1451
+ platform: Platform name (instagram, facebook, youtube, linkedin).
1452
+ message: Comment text.
1453
+ post_id: Post/media ID to comment on.
1454
+ post_url: Full post URL to comment on.
1455
+ comment_id: Existing comment ID to reply to.
1456
+
1457
+ One of post_id, post_url or comment_id is required.
1458
+
1459
+ Returns:
1460
+ Creation result with the new comment ID.
1461
+ """
1462
+ json_data = {
1463
+ "platform": platform,
1464
+ "user": user,
1465
+ "message": message
1466
+ }
1467
+ if post_id is not None:
1468
+ json_data["post_id"] = post_id
1469
+ if post_url is not None:
1470
+ json_data["post_url"] = post_url
1471
+ if comment_id is not None:
1472
+ json_data["comment_id"] = comment_id
1473
+ return self._request("/uploadposts/comments/create", "POST", json_data=json_data)
1474
+
1475
+ def delete_comment(
1476
+ self,
1477
+ user: str,
1478
+ platform: str,
1479
+ comment_id: str,
1480
+ post_id: Optional[str] = None
1481
+ ) -> Dict:
1482
+ """
1483
+ Delete a comment on a post.
1484
+
1485
+ Args:
1486
+ user: Profile username.
1487
+ platform: Platform name (instagram, facebook, youtube, linkedin).
1488
+ comment_id: Comment ID to delete.
1489
+ post_id: Post URN (required for LinkedIn).
1490
+
1491
+ Returns:
1492
+ Deletion result.
1493
+ """
1494
+ json_data = {
1495
+ "platform": platform,
1496
+ "user": user,
1497
+ "comment_id": comment_id
1498
+ }
1499
+ if post_id is not None:
1500
+ json_data["post_id"] = post_id
1501
+ return self._request("/uploadposts/comments/delete", "DELETE", json_data=json_data)
1502
+
1363
1503
  # ==================== Google Business ====================
1364
1504
 
1365
1505
  def get_google_business_locations(self, profile: Optional[str] = None) -> Dict:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: upload-post
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Cross-platform social media upload for TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, Discord, Telegram, and X (Twitter)
5
5
  Home-page: https://www.upload-post.com/
6
6
  Author: Upload-Post
@@ -300,6 +300,7 @@ boards = client.get_pinterest_boards("my-profile")
300
300
  - `allowedCountries` / `blockedCountries` - Country restrictions
301
301
  - `hasPaidProductPlacement` - Paid placement flag
302
302
  - `recordingDate` - Recording date (ISO 8601)
303
+ - `youtube_playlist_id` - Playlist ID (single string, list, or comma-separated) to add the uploaded video to after publishing
303
304
 
304
305
  ### LinkedIn
305
306
  - `visibility` - PUBLIC, CONNECTIONS, LOGGED_IN, CONTAINER
File without changes