upload-post 0.1.1__py3-none-any.whl → 0.1.2__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.
upload_post/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.0"
1
+ __version__ = "0.1.2"
2
2
  from .api_client import UploadPostClient, UploadPostError
3
3
 
4
4
  __all__ = ['UploadPostClient', 'UploadPostError']
upload_post/api_client.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Dict, List, Union
2
+ from typing import Dict, List, Union, Optional
3
3
 
4
4
  import requests
5
5
 
@@ -24,43 +24,88 @@ class UploadPostClient:
24
24
  video_path: Union[str, Path],
25
25
  title: str,
26
26
  user: str,
27
- platforms: List[str]
27
+ platforms: List[str],
28
+ **kwargs
28
29
  ) -> Dict:
29
30
  """
30
- Upload a video to specified social media platforms
31
-
31
+ Upload a video to specified social media platforms.
32
+
32
33
  Args:
33
- video_path: Path to video file
34
- title: Video title
35
- user: User identifier
36
- platforms: List of platforms (e.g. ["tiktok", "instagram"])
37
-
34
+ video_path: Path to video file (str or Path) or video URL (str).
35
+ title: Video title.
36
+ user: User identifier.
37
+ platforms: List of platforms (e.g., ["tiktok", "instagram", "linkedin"]).
38
+ **kwargs: Platform-specific parameters.
39
+ tiktok: privacy_level (str), disable_duet (bool), disable_comment (bool),
40
+ disable_stitch (bool), cover_timestamp (int), brand_content_toggle (bool),
41
+ brand_organic (bool), branded_content (bool), brand_organic_toggle (bool),
42
+ is_aigc (bool)
43
+ instagram: media_type (str), share_to_feed (bool), collaborators (str),
44
+ cover_url (str), audio_name (str), user_tags (str),
45
+ location_id (str), thumb_offset (str)
46
+ linkedin: description (str), visibility (str), target_linkedin_page_id (str)
47
+ youtube: description (str), tags (List[str]), categoryId (str),
48
+ privacyStatus (str), embeddable (bool), license (str),
49
+ publicStatsViewable (bool), madeForKids (bool)
50
+ facebook: facebook_page_id (str), description (str), video_state (str)
51
+ threads: description (str)
52
+ x: tagged_user_ids (List[str]), reply_settings (str), nullcast (bool),
53
+ place_id (str), poll_duration (int), poll_options (List[str]),
54
+ poll_reply_settings (str)
55
+ pinterest: pinterest_board_id (str), pinterest_link (str),
56
+ pinterest_cover_image_url (str),
57
+ pinterest_cover_image_content_type (str),
58
+ pinterest_cover_image_data (str),
59
+ pinterest_cover_image_key_frame_time (int)
60
+
38
61
  Returns:
39
- API response JSON
40
-
62
+ API response JSON.
63
+
41
64
  Raises:
42
- UploadPostError: If upload fails
65
+ UploadPostError: If upload fails or video file not found.
43
66
  """
44
- video_path = Path(video_path)
45
- if not video_path.exists():
46
- raise UploadPostError(f"Video file not found: {video_path}")
67
+ data_payload: List[tuple] = []
68
+ files_payload: List[tuple] = []
69
+ video_file_obj = None # To keep track of the opened file
47
70
 
48
71
  try:
49
- with video_path.open("rb") as video_file:
50
- files = {"video": video_file}
51
- data = {
52
- "title": title,
53
- "user": user,
54
- "platform[]": platforms
55
- }
72
+ # Prepare video
73
+ if isinstance(video_path, str) and \
74
+ (video_path.startswith('http://') or video_path.startswith('https://')):
75
+ # It's a URL
76
+ data_payload.append(('video', video_path))
77
+ else:
78
+ # It's a file path
79
+ video_p = Path(video_path)
80
+ if not video_p.exists():
81
+ raise UploadPostError(f"Video file not found: {video_p}")
56
82
 
57
- response = self.session.post(
58
- f"{self.BASE_URL}/upload",
59
- files=files,
60
- data=data
61
- )
62
- response.raise_for_status()
63
- return response.json()
83
+ video_file_obj = video_p.open("rb")
84
+ files_payload.append(('video', (video_p.name, video_file_obj)))
85
+
86
+ # Prepare common parameters
87
+ data_payload.append(('title', title))
88
+ data_payload.append(('user', user))
89
+ for p in platforms:
90
+ data_payload.append(('platform[]', p))
91
+
92
+ # Add platform-specific parameters from kwargs
93
+ for key, value in kwargs.items():
94
+ if isinstance(value, bool):
95
+ data_payload.append((key, str(value).lower())) # 'true' or 'false'
96
+ elif isinstance(value, list):
97
+ for v_item in value: # Handles array parameters like 'tags' or 'tagged_user_ids'
98
+ data_payload.append((f'{key}[]' if key.endswith('s') else key, str(v_item))) # API expects tags[] for YouTube, etc.
99
+ else:
100
+ data_payload.append((key, str(value)))
101
+
102
+ response = self.session.post(
103
+ f"{self.BASE_URL}/upload", # Endpoint for video is /upload
104
+ files=files_payload if files_payload else None,
105
+ data=data_payload
106
+ )
107
+ response.raise_for_status()
108
+ return response.json()
64
109
 
65
110
  except requests.exceptions.RequestException as e:
66
111
  raise UploadPostError(
@@ -70,3 +115,154 @@ class UploadPostClient:
70
115
  raise UploadPostError(
71
116
  f"Invalid response format: {str(e)}"
72
117
  ) from e
118
+ finally:
119
+ if video_file_obj:
120
+ video_file_obj.close()
121
+
122
+ def upload_photos(
123
+ self,
124
+ photos: List[Union[str, Path]],
125
+ user: str,
126
+ platforms: List[str],
127
+ title: str,
128
+ caption: Optional[str] = None,
129
+ **kwargs
130
+ ) -> Dict:
131
+ """
132
+ Upload photos to specified social media platforms.
133
+
134
+ Args:
135
+ photos: List of photo file paths (str or Path) or photo URLs (str).
136
+ user: User identifier.
137
+ platforms: List of platforms (e.g., ["tiktok", "instagram"]).
138
+ title: Title of the post.
139
+ caption: Optional caption/description for the photos.
140
+ **kwargs: Platform-specific parameters.
141
+ linkedin: visibility (str), target_linkedin_page_id (str)
142
+ facebook: facebook_page_id (str)
143
+ tiktok: auto_add_music (bool), disable_comment (bool),
144
+ branded_content (bool), disclose_commercial (bool),
145
+ photo_cover_index (int), description (str)
146
+ instagram: media_type (str)
147
+ pinterest: pinterest_board_id (str), pinterest_alt_text (str),
148
+ pinterest_link (str)
149
+
150
+ Returns:
151
+ API response JSON.
152
+
153
+ Raises:
154
+ UploadPostError: If upload fails or any photo file not found.
155
+ """
156
+ data_payload: List[tuple] = []
157
+ files_payload: List[tuple] = []
158
+ opened_files: List[object] = [] # To keep track of files to close them later
159
+
160
+ try:
161
+ # Prepare photos
162
+ for photo_item in photos:
163
+ if isinstance(photo_item, str) and \
164
+ (photo_item.startswith('http://') or photo_item.startswith('https://')):
165
+ # It's a URL
166
+ data_payload.append(('photos[]', photo_item))
167
+ else:
168
+ # It's a file path
169
+ photo_path = Path(photo_item)
170
+ if not photo_path.exists():
171
+ raise UploadPostError(f"Photo file not found: {photo_path}")
172
+
173
+ photo_file_obj = photo_path.open("rb")
174
+ opened_files.append(photo_file_obj)
175
+ files_payload.append(('photos[]', (photo_path.name, photo_file_obj)))
176
+
177
+ # Prepare common parameters
178
+ data_payload.append(('user', user))
179
+ data_payload.append(('title', title))
180
+ if caption is not None:
181
+ data_payload.append(('caption', caption))
182
+
183
+ for p in platforms:
184
+ data_payload.append(('platform[]', p))
185
+
186
+ # Add platform-specific parameters from kwargs
187
+ for key, value in kwargs.items():
188
+ if isinstance(value, bool):
189
+ data_payload.append((key, str(value).lower())) # 'true' or 'false'
190
+ elif isinstance(value, list):
191
+ for v_item in value: # Handles cases where a kwarg value is a list
192
+ data_payload.append((key, str(v_item)))
193
+ else:
194
+ data_payload.append((key, str(value)))
195
+
196
+ response = self.session.post(
197
+ f"{self.BASE_URL}/upload_photos",
198
+ files=files_payload if files_payload else None,
199
+ data=data_payload
200
+ )
201
+ response.raise_for_status()
202
+ return response.json()
203
+
204
+ except requests.exceptions.RequestException as e:
205
+ raise UploadPostError(f"API request failed: {str(e)}") from e
206
+ except (ValueError, TypeError) as e: # ValueError for json parsing, TypeError for bad args
207
+ raise UploadPostError(f"Data or response format error: {str(e)}") from e
208
+ finally:
209
+ for f_obj in opened_files:
210
+ f_obj.close()
211
+
212
+ def upload_text(
213
+ self,
214
+ user: str,
215
+ platforms: List[str],
216
+ title: str, # As per API docs, 'title' is used for the text content
217
+ **kwargs
218
+ ) -> Dict:
219
+ """
220
+ Upload text posts to specified social media platforms.
221
+
222
+ Args:
223
+ user: User identifier.
224
+ platforms: List of platforms (e.g., ["x", "linkedin"]).
225
+ Supported: "linkedin", "x", "facebook", "threads".
226
+ title: The text content for the post.
227
+ **kwargs: Platform-specific parameters.
228
+ linkedin: target_linkedin_page_id (str)
229
+ facebook: facebook_page_id (str)
230
+
231
+ Returns:
232
+ API response JSON.
233
+
234
+ Raises:
235
+ UploadPostError: If upload fails.
236
+ """
237
+ data_payload: List[tuple] = []
238
+
239
+ try:
240
+ # Prepare common parameters
241
+ data_payload.append(('user', user))
242
+ data_payload.append(('title', title)) # 'title' carries the text content
243
+
244
+ for p in platforms:
245
+ data_payload.append(('platform[]', p))
246
+
247
+ # Add platform-specific parameters from kwargs
248
+ # (e.g., target_linkedin_page_id, facebook_page_id)
249
+ for key, value in kwargs.items():
250
+ if isinstance(value, bool): # Should not happen based on current docs for text
251
+ data_payload.append((key, str(value).lower()))
252
+ elif isinstance(value, list): # Should not happen based on current docs for text
253
+ for v_item in value:
254
+ data_payload.append((key, str(v_item)))
255
+ else:
256
+ data_payload.append((key, str(value)))
257
+
258
+ response = self.session.post(
259
+ f"{self.BASE_URL}/upload_text",
260
+ data=data_payload
261
+ )
262
+ response.raise_for_status()
263
+ return response.json()
264
+
265
+ except requests.exceptions.RequestException as e:
266
+ raise UploadPostError(f"API request failed: {str(e)}") from e
267
+ except (ValueError, TypeError) as e:
268
+ raise UploadPostError(f"Data or response format error: {str(e)}") from e
upload_post/cli.py CHANGED
@@ -18,8 +18,8 @@ def main():
18
18
  "--platforms",
19
19
  nargs="+",
20
20
  required=True,
21
- choices=["tiktok", "instagram", "facebook", "youtube"],
22
- help="Platforms to upload to"
21
+ choices=["tiktok", "instagram", "linkedin", "youtube", "facebook", "x", "threads", "pinterest"],
22
+ help="Platforms to upload to. For platform-specific parameters, please use the Python API."
23
23
  )
24
24
  parser.add_argument(
25
25
  "--verbose",
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: upload-post
3
+ Version: 0.1.2
4
+ Summary: Python client for Upload-Post.com API
5
+ Home-page: https://www.upload-post.com/
6
+ Author: Manuel Gracia
7
+ Author-email: hi@img2html.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: requests>=2.25.1
14
+ Requires-Dist: python-dotenv>=0.19.0
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: requires-dist
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # upload-post Python Client
26
+
27
+ A Python client for the [Upload-Post.com](https://www.upload-post.com/) API, designed to facilitate interaction with the service. Upload-Post.com allows you to upload videos to multiple social media platforms simultaneously.
28
+
29
+ [![PyPI version](https://img.shields.io/pypi/v/upload-post.svg)](https://pypi.org/project/upload-post/)
30
+ [![Python Versions](https://img.shields.io/pypi/pyversions/upload-post.svg)](https://pypi.org/project/upload-post/)
31
+
32
+ ## Features
33
+
34
+ - 🚀 Upload videos to TikTok, Instagram, LinkedIn, YouTube, Facebook, X (Twitter), Threads, and Pinterest (platform support based on API availability)
35
+ - 🖼️ Upload photos to TikTok, Instagram, LinkedIn, Facebook, X (Twitter), Threads, and Pinterest
36
+ - ✍️ Upload text posts to LinkedIn, X (Twitter), Facebook, and Threads
37
+ - 🔒 Secure API key authentication
38
+ - 📁 File validation and error handling
39
+ - 📊 Detailed logging
40
+ - 🤖 Both CLI and Python API interfaces
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install upload-post
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Command Line Interface
51
+
52
+ ```bash
53
+ upload-post \
54
+ --api-key "your_api_key_here" \
55
+ --video "/path/to/video.mp4" \
56
+ --title "My Awesome Video" \
57
+ --user "testuser" \
58
+ --platforms tiktok instagram
59
+ ```
60
+
61
+ ### Python API
62
+
63
+ ```python
64
+ from upload_post import UploadPostClient, UploadPostError
65
+ from pathlib import Path
66
+
67
+ # Initialize client
68
+ client = UploadPostClient(api_key="your_api_key_here")
69
+
70
+ # Example: Upload a video
71
+ try:
72
+ response_video = client.upload_video(
73
+ video_path="/path/to/video.mp4", # Can also be a URL: "https://example.com/video.mp4"
74
+ title="My Awesome Video",
75
+ user="testuser",
76
+ platforms=["tiktok", "youtube", "linkedin"],
77
+ # TikTok specific
78
+ privacy_level="PUBLIC_TO_EVERYONE",
79
+ disable_comment=False,
80
+ # YouTube specific
81
+ description="Detailed description for YouTube",
82
+ tags=["tutorial", "python", "api"],
83
+ categoryId="22", # "People & Blogs"
84
+ privacyStatus="public",
85
+ # LinkedIn specific
86
+ visibility="PUBLIC", # Required for LinkedIn if not using default
87
+ description="Post commentary for LinkedIn" # Optional, uses title if not set
88
+ )
89
+ print(f"Video upload successful: {response_video}")
90
+ except UploadPostError as e:
91
+ print(f"Video upload failed: {e}")
92
+
93
+ # Example: Upload photos
94
+ try:
95
+ response_photos = client.upload_photos(
96
+ photos=["/path/to/image1.jpg", Path("/path/to/image2.png"), "https://example.com/photo3.jpg"],
97
+ user="testuser",
98
+ platforms=["instagram", "facebook"],
99
+ title="My Photo Album",
100
+ caption="Check out these cool photos!",
101
+ # Platform-specific parameters
102
+ facebook_page_id="your_facebook_page_id", # Required for Facebook
103
+ media_type="IMAGE" # For Instagram, "IMAGE" or "STORIES"
104
+ )
105
+ print(f"Photo upload successful: {response_photos}")
106
+ except UploadPostError as e:
107
+ print(f"Photo upload failed: {e}")
108
+
109
+ # Example: Upload a text post
110
+ try:
111
+ response_text = client.upload_text(
112
+ user="testuser",
113
+ platforms=["x", "linkedin"],
114
+ title="This is my awesome text post! #Python #API",
115
+ # Platform-specific parameters
116
+ # For LinkedIn, if posting to an organization page:
117
+ # target_linkedin_page_id="your_linkedin_page_id",
118
+ # For Facebook, facebook_page_id is required:
119
+ # facebook_page_id="your_facebook_page_id"
120
+ # (add 'facebook' to platforms list too)
121
+ )
122
+ print(f"Text post successful: {response_text}")
123
+ except UploadPostError as e:
124
+ print(f"Text post failed: {e}")
125
+ ```
126
+
127
+ ## Error Handling
128
+
129
+ The client raises `UploadPostError` exceptions for API errors. Common error scenarios:
130
+
131
+ - Invalid API key
132
+ - Missing required parameters
133
+ - File not found
134
+ - Platform not supported
135
+ - API rate limits exceeded
136
+
137
+ ## Documentation
138
+
139
+ For full API documentation and platform availability, see the official [Upload-Post.com documentation](https://www.upload-post.com/).
140
+
141
+ ## License
142
+
143
+ MIT License - See [LICENSE](LICENSE) for details.
@@ -0,0 +1,8 @@
1
+ upload_post/__init__.py,sha256=WtahEoLXqDhFCLBEYmWJjNIvwTcPeBk0IeZ8yABUmA4,131
2
+ upload_post/api_client.py,sha256=uF0vQzY4t2nIdvLSi8NF3HBImNrpFaSCA_vSFXLKy1E,11153
3
+ upload_post/cli.py,sha256=Ruyl0sOpRVXnRnT3lUcObIM700pUFHJtWfIIXBsQSNY,1708
4
+ upload_post-0.1.2.dist-info/METADATA,sha256=ZpLE62psTCDowYiSLsy9CFwGscysLJ96RkZ5VK_e5nM,4707
5
+ upload_post-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ upload_post-0.1.2.dist-info/entry_points.txt,sha256=D_MqrtgTSJQkRIwPLVCvKcwxNnaoAS8my7_gBtU1X_I,53
7
+ upload_post-0.1.2.dist-info/top_level.txt,sha256=c5Jpx2u159BqOkC2P3Gp3dSotri2_XAADesx3h9pCRU,12
8
+ upload_post-0.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,90 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: upload-post
3
- Version: 0.1.1
4
- Summary: Python client for Upload-Post.com API
5
- Home-page: https://www.upload-post.com/
6
- Author: Manuel Gracia
7
- Author-email: hi@img2html.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
12
- Description-Content-Type: text/markdown
13
- Requires-Dist: requests>=2.25.1
14
- Requires-Dist: python-dotenv>=0.19.0
15
- Dynamic: author
16
- Dynamic: author-email
17
- Dynamic: classifier
18
- Dynamic: description
19
- Dynamic: description-content-type
20
- Dynamic: home-page
21
- Dynamic: requires-dist
22
- Dynamic: requires-python
23
- Dynamic: summary
24
-
25
- # upload-post Python Client
26
-
27
- A Python client for the [Upload-Post.com](https://www.upload-post.com/) API, designed to facilitate interaction with the service. Upload-Post.com allows you to upload videos to multiple social media platforms simultaneously.
28
-
29
- [![PyPI version](https://img.shields.io/pypi/v/upload-post.svg)](https://pypi.org/project/upload-post/)
30
- [![Python Versions](https://img.shields.io/pypi/pyversions/upload-post.svg)](https://pypi.org/project/upload-post/)
31
-
32
- ## Features
33
-
34
- - 🚀 Upload videos to TikTok, Instagram, Facebook, and YouTube (platform support based on API availability)
35
- - 🔒 Secure API key authentication
36
- - 📁 File validation and error handling
37
- - 📊 Detailed logging
38
- - 🤖 Both CLI and Python API interfaces
39
-
40
- ## Installation
41
-
42
- ```bash
43
- pip install upload-post
44
- ```
45
-
46
- ## Usage
47
-
48
- ### Command Line Interface
49
-
50
- ```bash
51
- upload-post \
52
- --api-key "your_api_key_here" \
53
- --video "/path/to/video.mp4" \
54
- --title "My Awesome Video" \
55
- --user "testuser" \
56
- --platforms tiktok instagram
57
- ```
58
-
59
- ### Python API
60
-
61
- ```python
62
- from upload_post import UploadPostClient
63
-
64
- client = UploadPostClient(api_key="your_api_key_here")
65
-
66
- response = client.upload_video(
67
- video_path="/path/to/video.mp4",
68
- title="My Awesome Video",
69
- user="testuser",
70
- platforms=["tiktok", "instagram"]
71
- )
72
- ```
73
-
74
- ## Error Handling
75
-
76
- The client raises `UploadPostError` exceptions for API errors. Common error scenarios:
77
-
78
- - Invalid API key
79
- - Missing required parameters
80
- - File not found
81
- - Platform not supported
82
- - API rate limits exceeded
83
-
84
- ## Documentation
85
-
86
- For full API documentation and platform availability, see the official [Upload-Post.com documentation](https://www.upload-post.com/).
87
-
88
- ## License
89
-
90
- MIT License - See [LICENSE](LICENSE) for details.
@@ -1,8 +0,0 @@
1
- upload_post/__init__.py,sha256=qV2u_RV8hHd0WBkAyNe611kqJlPqy1bK-75grZg-cLA,131
2
- upload_post/api_client.py,sha256=lGfEQnCpZOPuabKduzS-k5Qc4t295S45heV_mXdXsIE,2134
3
- upload_post/cli.py,sha256=wb037f9A62Y4w4Awa3pLxuqekVbnFDlqzL8Uuu_tOlI,1605
4
- upload_post-0.1.1.dist-info/METADATA,sha256=CdS2fq78fvFIHDZXENxdES1lZvxVuA_Azy8b3OmU4CY,2414
5
- upload_post-0.1.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
- upload_post-0.1.1.dist-info/entry_points.txt,sha256=D_MqrtgTSJQkRIwPLVCvKcwxNnaoAS8my7_gBtU1X_I,53
7
- upload_post-0.1.1.dist-info/top_level.txt,sha256=c5Jpx2u159BqOkC2P3Gp3dSotri2_XAADesx3h9pCRU,12
8
- upload_post-0.1.1.dist-info/RECORD,,