upload-post 1.0.0 → 1.0.3

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 (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -11
  3. package/index.d.ts +148 -1
  4. package/index.js +285 -1
  5. package/package.json +5 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Upload-Post
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -23,13 +23,44 @@ import { UploadPost } from 'upload-post';
23
23
  const uploader = new UploadPost('your-api-key-here');
24
24
 
25
25
  // Upload video with options
26
- const result = await uploader.upload('/path/to/video.mp4', {
27
- title: 'My Awesome Video',
28
- user: 'test-user',
29
- platforms: ['tiktok'] // Currently supported platforms
30
- });
26
+ try {
27
+ const result = await uploader.upload(
28
+ '/path/to/your/local/video.mp4', // or "https://example.com/remote/video.mp4"
29
+ {
30
+ title: 'My Awesome Video',
31
+ user: 'test-user',
32
+ platforms: ['tiktok', 'youtube'],
33
+ // TikTok specific
34
+ privacy_level: 'PUBLIC_TO_EVERYONE',
35
+ disable_comment: false,
36
+ // YouTube specific
37
+ description: 'This is a great video about coding.',
38
+ tags: ['coding', 'nodejs', 'api'],
39
+ privacyStatus: 'public'
40
+ }
41
+ );
42
+ console.log('Upload successful:', result);
43
+ } catch (error) {
44
+ console.error('Upload failed:', error.message);
45
+ }
31
46
 
32
- console.log('Upload successful:', result);
47
+ // Example: Upload video from a URL
48
+ try {
49
+ const resultFromUrl = await uploader.upload(
50
+ null, // videoPath can be null or undefined if options.video is a URL
51
+ {
52
+ video: "https://example.com/remote/another_video.mp4",
53
+ title: 'Remote Video Fun',
54
+ user: 'test-user-url',
55
+ platforms: ['instagram'],
56
+ media_type: 'REELS',
57
+ share_to_feed: true
58
+ }
59
+ );
60
+ console.log('Upload from URL successful:', resultFromUrl);
61
+ } catch (error) {
62
+ console.error('Upload from URL failed:', error.message);
63
+ }
33
64
  ```
34
65
 
35
66
  ## API Documentation
@@ -40,11 +71,183 @@ console.log('Upload successful:', result);
40
71
  ### upload()
41
72
  `upload(videoPath: string, options: UploadOptions): Promise<object>`
42
73
 
43
- #### Options
44
- - `videoPath`: Path to video file (MP4, MOV, AVI, etc.)
45
- - `options.title`: Video title
46
- - `options.user`: User identifier
47
- - `options.platforms`: Array of target platforms (currently supports 'tiktok')
74
+ #### Video Upload Options (`UploadOptions`)
75
+ **Common Parameters:**
76
+ - `videoPath`: (Required) Path to your local video file (e.g., MP4, MOV) OR a public URL to a video file. If providing a URL, you can also use the `options.video` field.
77
+ - `options.title`: (Required) Title of the video.
78
+ - `options.user`: (Required) User identifier.
79
+ - `options.platforms`: (Required) Array of target platforms (e.g., `['tiktok', 'youtube', 'instagram']`). Supported: `tiktok`, `instagram`, `linkedin`, `youtube`, `facebook`, `x` (Twitter), `threads`, `pinterest`.
80
+ - `options.video`: (Optional) Public URL of the video file. Use this if `videoPath` is not a local file.
81
+
82
+ **Platform-Specific Parameters (all optional):**
83
+
84
+ - **TikTok:**
85
+ - `options.privacy_level`: `'PUBLIC_TO_EVERYONE'`, `'MUTUAL_FOLLOW_FRIENDS'`, `'FOLLOWER_OF_CREATOR'`, or `'SELF_ONLY'`.
86
+ - `options.disable_duet`: `boolean`.
87
+ - `options.disable_comment`: `boolean`.
88
+ - `options.disable_stitch`: `boolean`.
89
+ - `options.cover_timestamp`: `number` (milliseconds for video cover).
90
+ - `options.brand_content_toggle`: `boolean`.
91
+ - `options.brand_organic`: `boolean`.
92
+ - `options.branded_content`: `boolean`.
93
+ - `options.brand_organic_toggle`: `boolean`.
94
+ - `options.is_aigc`: `boolean` (AI-generated content).
95
+ - **Instagram:**
96
+ - `options.media_type`: `'REELS'` or `'STORIES'`. Default: `'REELS'`.
97
+ - `options.share_to_feed`: `boolean`.
98
+ - `options.collaborators`: `string` (comma-separated usernames).
99
+ - `options.cover_url`: `string` (URL for custom video cover).
100
+ - `options.audio_name`: `string`.
101
+ - `options.user_tags`: `string` (comma-separated user tags).
102
+ - `options.location_id`: `string`.
103
+ - `options.thumb_offset`: `string` (timestamp offset for thumbnail).
104
+ - **LinkedIn:**
105
+ - `options.description`: `string` (post commentary, defaults to `title`).
106
+ - `options.visibility`: `'CONNECTIONS'`, `'PUBLIC'`, `'LOGGED_IN'`, or `'CONTAINER'`. Default: `'PUBLIC'`.
107
+ - `options.target_linkedin_page_id`: `string` (LinkedIn page ID for organization uploads).
108
+ - **YouTube:**
109
+ - `options.description`: `string` (video description, defaults to `title`).
110
+ - `options.tags`: `string[]` (array of tags).
111
+ - `options.categoryId`: `string` (video category ID, default: `"22"`).
112
+ - `options.privacyStatus`: `'public'`, `'unlisted'`, or `'private'`. Default: `'public'`.
113
+ - `options.embeddable`: `boolean`.
114
+ - `options.license`: `'youtube'` or `'creativeCommon'`.
115
+ - `options.publicStatsViewable`: `boolean`.
116
+ - `options.madeForKids`: `boolean`.
117
+ - **Facebook:**
118
+ - `options.facebook_page_id`: (Required if `facebook` in `platforms`) Facebook Page ID.
119
+ - `options.description`: `string` (video description, defaults to `title`).
120
+ - `options.video_state`: `'DRAFT'`, `'PUBLISHED'`, or `'SCHEDULED'`. Default: `'PUBLISHED'`.
121
+ - **Threads:**
122
+ - `options.description`: `string` (post commentary, defaults to `title`).
123
+ - **X (Twitter):**
124
+ - `options.tagged_user_ids`: `string[]` (array of user IDs to tag).
125
+ - `options.reply_settings`: `'following'`, `'mentionedUsers'`, or `'everyone'`.
126
+ - `options.nullcast`: `boolean` (publish without broadcasting).
127
+ - `options.place_id`: `string` (location place ID).
128
+ - `options.poll_duration`: `number` (poll duration in minutes).
129
+ - `options.poll_options`: `string[]` (array of poll options).
130
+ - `options.poll_reply_settings`: `'following'`, `'mentionedUsers'`, or `'everyone'` (for poll replies).
131
+ - **Pinterest:**
132
+ - `options.pinterest_board_id`: (Required if `pinterest` in `platforms`) Pinterest board ID.
133
+ - `options.pinterest_link`: `string` (destination link for the Pin).
134
+ - `options.pinterest_cover_image_url`: `string` (URL for cover image).
135
+ - `options.pinterest_cover_image_content_type`: `string` (e.g., `image/jpeg`).
136
+ - `options.pinterest_cover_image_data`: `string` (Base64 encoded cover image).
137
+ - `options.pinterest_cover_image_key_frame_time`: `number` (milliseconds for video frame to use as cover).
138
+
139
+ ### uploadPhotos()
140
+ `uploadPhotos(photos: string[], options: UploadPhotosOptions): Promise<object>`
141
+
142
+ Uploads photos to various social media platforms.
143
+
144
+ #### Photo Upload Options (`UploadPhotosOptions`)
145
+ - `photos`: Array of strings, where each string is a local file path to a photo or a public URL of a photo.
146
+ - `options.user`: (Required) User identifier.
147
+ - `options.platforms`: (Required) Array of target platforms. Supported: `tiktok`, `instagram`, `linkedin`, `facebook`, `x`, `threads`, `pinterest`.
148
+ - `options.title`: (Required) Title of the post.
149
+ - `options.caption`: (Optional) Caption/description for the photos. This will be used as the post commentary.
150
+
151
+ ##### Platform-Specific Parameters (Optional unless stated otherwise):
152
+
153
+ - **LinkedIn:**
154
+ - `options.visibility`: `'PUBLIC'` - Visibility setting for the post.
155
+ - `options.target_linkedin_page_id`: LinkedIn page ID to upload photos to an organization.
156
+ - **Facebook:**
157
+ - `options.facebook_page_id`: (Required if `facebook` in `platforms`) Facebook Page ID.
158
+ - **TikTok:**
159
+ - `options.auto_add_music`: `boolean` - Automatically add background music.
160
+ - `options.disable_comment`: `boolean` - Disable comments.
161
+ - `options.branded_content`: `boolean` - Indicate branded content (requires `disclose_commercial` to be `true`).
162
+ - `options.disclose_commercial`: `boolean` - Disclose commercial nature.
163
+ - `options.photo_cover_index`: `number` - Index (0-based) of the photo to use as cover.
164
+ - `options.description`: `string` - Description for TikTok (defaults to `title`).
165
+ - **Instagram:**
166
+ - `options.media_type`: `'IMAGE'` or `'STORIES'` - Type of media. Defaults to `'IMAGE'`.
167
+ - **Pinterest:**
168
+ - `options.pinterest_board_id`: (Required if `pinterest` in `platforms`) Pinterest board ID.
169
+ - `options.pinterest_alt_text`: `string` - Alt text for the image.
170
+ - `options.pinterest_link`: `string` - Destination link for the Pin.
171
+
172
+ **X (Twitter) and Threads** do not require additional parameters.
173
+
174
+ #### Example: Uploading Photos
175
+
176
+ ```javascript
177
+ import { UploadPost } from 'upload-post';
178
+
179
+ const uploader = new UploadPost('your-api-key-here');
180
+
181
+ // Example: Upload photos to Instagram and Facebook
182
+ try {
183
+ const photoResult = await uploader.uploadPhotos(
184
+ ['/path/to/your/image1.jpg', 'https://example.com/images/photo2.jpg'],
185
+ {
186
+ user: 'test-user',
187
+ platforms: ['instagram', 'facebook'],
188
+ title: 'My Beautiful Photos',
189
+ caption: 'Check out these amazing shots!',
190
+ facebook_page_id: 'your-facebook-page-id', // Required for Facebook
191
+ media_type: 'IMAGE' // Optional for Instagram
192
+ }
193
+ );
194
+ console.log('Photo upload successful:', photoResult);
195
+ } catch (error) {
196
+ console.error('Photo upload failed:', error.message);
197
+ }
198
+ ```
199
+
200
+ ### uploadText()
201
+ `uploadText(options: UploadTextOptions): Promise<object>`
202
+
203
+ Uploads text-only posts to supported social media platforms.
204
+
205
+ #### Text Upload Options (`UploadTextOptions`)
206
+ - `options.user`: (Required) User identifier.
207
+ - `options.platforms`: (Required) Array of target platforms. Supported: `'linkedin'`, `'x'` (Twitter), `'facebook'`, `'threads'`.
208
+ - `options.title`: (Required) The text content for the post. This field is used as the main content for all specified platforms.
209
+
210
+ ##### Platform-Specific Parameters:
211
+ - **LinkedIn:**
212
+ - `options.target_linkedin_page_id`: (Optional) LinkedIn page ID to upload text to an organization's page. If not provided, posts to the user's personal profile.
213
+ - **Facebook:**
214
+ - `options.facebook_page_id`: (Required if `'facebook'` is in `platforms`) Facebook Page ID where the text will be posted.
215
+
216
+ **X (Twitter) and Threads** use the common `options.title` for the text content and do not require additional specific parameters for text posts.
217
+
218
+ #### Example: Uploading a Text Post
219
+
220
+ ```javascript
221
+ import { UploadPost } from 'upload-post';
222
+
223
+ const uploader = new UploadPost('your-api-key-here');
224
+
225
+ // Example: Upload a text post to X (Twitter) and LinkedIn
226
+ try {
227
+ const textResult = await uploader.uploadText({
228
+ user: 'test-user',
229
+ platforms: ['x', 'linkedin'],
230
+ title: 'This is my awesome text post for X and LinkedIn!',
231
+ target_linkedin_page_id: 'your-linkedin-org-page-id' // Optional: for LinkedIn organization page
232
+ });
233
+ console.log('Text post successful:', textResult);
234
+ } catch (error) {
235
+ console.error('Text post failed:', error.message);
236
+ }
237
+
238
+ // Example: Upload a text post to a Facebook Page
239
+ try {
240
+ const fbTextResult = await uploader.uploadText({
241
+ user: 'test-user-fb',
242
+ platforms: ['facebook'],
243
+ title: 'Hello Facebook Page!',
244
+ facebook_page_id: 'your-facebook-page-id' // Required for Facebook
245
+ });
246
+ console.log('Facebook text post successful:', fbTextResult);
247
+ } catch (error) {
248
+ console.error('Facebook text post failed:', error.message);
249
+ }
250
+ ```
48
251
 
49
252
  ## Error Handling
50
253
  The library throws errors for:
package/index.d.ts CHANGED
@@ -4,8 +4,140 @@ declare module 'upload-post' {
4
4
  title: string;
5
5
  /** User identifier */
6
6
  user: string;
7
- /** Array of target platforms */
7
+ /** Array of target platforms. Supported: tiktok, instagram, linkedin, youtube, facebook, twitter, threads, pinterest */
8
8
  platforms: string[];
9
+ /** The video file to upload (can be a file upload or a video URL) */
10
+ video?: string; // Already handled by videoPath parameter, but good to have for consistency if API supports 'video' form field directly for URLs
11
+
12
+ // TikTok specific
13
+ privacy_level?: 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY';
14
+ disable_duet?: boolean;
15
+ disable_comment?: boolean; // Note: also a common param for photos on TikTok
16
+ disable_stitch?: boolean;
17
+ cover_timestamp?: number;
18
+ brand_content_toggle?: boolean;
19
+ brand_organic?: boolean;
20
+ branded_content?: boolean; // Note: also a common param for photos on TikTok
21
+ brand_organic_toggle?: boolean;
22
+ is_aigc?: boolean;
23
+
24
+ // Instagram specific
25
+ media_type?: 'REELS' | 'STORIES'; // Note: also a param for photos, but with different values
26
+ share_to_feed?: boolean;
27
+ collaborators?: string; // Comma-separated list
28
+ cover_url?: string;
29
+ audio_name?: string;
30
+ user_tags?: string; // Comma-separated list
31
+ location_id?: string;
32
+ thumb_offset?: string;
33
+
34
+ // LinkedIn specific
35
+ description?: string; // Note: also a common param for photos on TikTok & FB videos
36
+ visibility?: 'CONNECTIONS' | 'PUBLIC' | 'LOGGED_IN' | 'CONTAINER'; // Note: different values than photo visibility
37
+ target_linkedin_page_id?: string; // Note: also a param for photos
38
+
39
+ // YouTube specific
40
+ // description?: string; // Already listed under LinkedIn, will be used if provided
41
+ tags?: string[];
42
+ categoryId?: string;
43
+ privacyStatus?: 'public' | 'unlisted' | 'private';
44
+ embeddable?: boolean;
45
+ license?: 'youtube' | 'creativeCommon';
46
+ publicStatsViewable?: boolean;
47
+ madeForKids?: boolean;
48
+
49
+ // Facebook specific
50
+ facebook_page_id?: string; // Note: also a param for photos
51
+ // description?: string; // Already listed under LinkedIn
52
+ video_state?: 'DRAFT' | 'PUBLISHED' | 'SCHEDULED';
53
+
54
+ // Threads specific
55
+ // description?: string; // Already listed under LinkedIn
56
+
57
+ // X (Twitter) specific
58
+ tagged_user_ids?: string[];
59
+ reply_settings?: 'following' | 'mentionedUsers' | 'everyone';
60
+ nullcast?: boolean;
61
+ place_id?: string;
62
+ poll_duration?: number; // in minutes
63
+ poll_options?: string[];
64
+ poll_reply_settings?: 'following' | 'mentionedUsers' | 'everyone';
65
+
66
+ // Pinterest specific
67
+ pinterest_board_id?: string; // Note: also a param for photos
68
+ pinterest_link?: string; // Note: also a param for photos
69
+ pinterest_cover_image_url?: string;
70
+ pinterest_cover_image_content_type?: string;
71
+ pinterest_cover_image_data?: string; // Base64 encoded
72
+ pinterest_cover_image_key_frame_time?: number; // in milliseconds
73
+ }
74
+
75
+ export interface UploadPhotosOptions {
76
+ /** User identifier */
77
+ user: string;
78
+ /** Array of target platforms. Supported: tiktok, instagram, linkedin, facebook, x, threads, pinterest */
79
+ platforms: string[];
80
+ /** Array of photo file paths or URLs */
81
+ photos: string[];
82
+ /** Title of the post */
83
+ title: string;
84
+ /** Caption/description for the photos (this will be used as the post commentary) */
85
+ caption?: string;
86
+
87
+ // LinkedIn specific
88
+ /** Visibility setting for the LinkedIn post (accepted value: "PUBLIC") */
89
+ visibility?: 'PUBLIC';
90
+ /** LinkedIn page ID to upload photos to an organization */
91
+ target_linkedin_page_id?: string;
92
+
93
+ // Facebook specific
94
+ /** Facebook Page ID where the photos will be posted */
95
+ facebook_page_id?: string;
96
+
97
+ // TikTok specific
98
+ /** Automatically add background music to photos */
99
+ auto_add_music?: boolean;
100
+ /** Disable comments on the post */
101
+ disable_comment?: boolean;
102
+ /** Indicate if the post is branded content (requires disclose_commercial=true) */
103
+ branded_content?: boolean;
104
+ /** Disclose the commercial nature of the post (used with branded_content) */
105
+ disclose_commercial?: boolean;
106
+ /** Index (starting at 0) of the photo to use as the cover/thumbnail for the TikTok photo post */
107
+ photo_cover_index?: number;
108
+ /** Description for the TikTok post. If not provided, the title value will be used by default. */
109
+ description?: string;
110
+
111
+ // Instagram specific
112
+ /** Type of media ("IMAGE" or "STORIES") */
113
+ media_type?: 'IMAGE' | 'STORIES';
114
+
115
+ // Pinterest specific
116
+ /** Pinterest board ID to publish the photo to. */
117
+ pinterest_board_id?: string;
118
+ /** Alt text for the image. */
119
+ pinterest_alt_text?: string;
120
+ /** Destination link for the photo Pin. */
121
+ pinterest_link?: string;
122
+ }
123
+
124
+ export interface UploadTextOptions {
125
+ /** User identifier */
126
+ user: string;
127
+ /** Array of target platforms. Supported: linkedin, x, facebook, threads */
128
+ platforms: ('linkedin' | 'x' | 'facebook' | 'threads')[];
129
+ /** The text content for the post. This will be used as the 'title' parameter for all supported platforms. */
130
+ title: string;
131
+
132
+ // LinkedIn specific
133
+ /** LinkedIn page ID to upload text to an organization's page. */
134
+ target_linkedin_page_id?: string;
135
+
136
+ // Facebook specific
137
+ /** Facebook Page ID where the text will be posted. Required if 'facebook' is in platforms. */
138
+ facebook_page_id?: string;
139
+
140
+ // X (Twitter) and Threads do not have additional specific parameters beyond 'title' for the text content.
9
141
  }
10
142
 
11
143
  /**
@@ -24,5 +156,20 @@ declare module 'upload-post' {
24
156
  * @returns Promise with API response
25
157
  */
26
158
  upload(videoPath: string, options: UploadOptions): Promise<object>;
159
+
160
+ /**
161
+ * Upload photos to social media platforms
162
+ * @param photos - Array of photo file paths or URLs
163
+ * @param options - Upload photos options
164
+ * @returns Promise with API response
165
+ */
166
+ uploadPhotos(photos: string[], options: UploadPhotosOptions): Promise<object>;
167
+
168
+ /**
169
+ * Upload text posts to social media platforms
170
+ * @param options - Upload text options
171
+ * @returns Promise with API response
172
+ */
173
+ uploadText(options: UploadTextOptions): Promise<object>;
27
174
  }
28
175
  }
package/index.js CHANGED
@@ -7,7 +7,89 @@ import { createReadStream } from 'fs';
7
7
  * @typedef {Object} UploadOptions
8
8
  * @property {string} title - Video title
9
9
  * @property {string} user - User identifier
10
- * @property {string[]} platforms - Array of platforms (e.g. ['tiktok'])
10
+ * @property {string[]} platforms - Array of platforms (e.g. ['tiktok', 'youtube', 'instagram'])
11
+ * @property {string} [video] - The video file to upload (can be a file upload or a video URL) - typically handled by videoPath param.
12
+ *
13
+ * @property {'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY'} [privacy_level] - TikTok: Privacy setting
14
+ * @property {boolean} [disable_duet] - TikTok: Disable duet feature
15
+ * @property {boolean} [disable_comment] - TikTok: Disable comments
16
+ * @property {boolean} [disable_stitch] - TikTok: Disable stitch feature
17
+ * @property {number} [cover_timestamp] - TikTok: Timestamp in milliseconds for video cover
18
+ * @property {boolean} [brand_content_toggle] - TikTok: Enable branded content
19
+ * @property {boolean} [brand_organic] - TikTok: Enable organic branded content
20
+ * @property {boolean} [branded_content] - TikTok: Enable branded content with disclosure
21
+ * @property {boolean} [brand_organic_toggle] - TikTok: Enable organic branded content toggle
22
+ * @property {boolean} [is_aigc] - TikTok: Indicates if content is AI-generated
23
+ *
24
+ * @property {'REELS' | 'STORIES'} [media_type] - Instagram: Type of media
25
+ * @property {boolean} [share_to_feed] - Instagram: Whether to share to feed
26
+ * @property {string} [collaborators] - Instagram: Comma-separated list of collaborator usernames
27
+ * @property {string} [cover_url] - Instagram: URL for custom video cover
28
+ * @property {string} [audio_name] - Instagram: Name of the audio track
29
+ * @property {string} [user_tags] - Instagram: Comma-separated list of user tags
30
+ * @property {string} [location_id] - Instagram: Instagram location ID
31
+ * @property {string} [thumb_offset] - Instagram: Timestamp offset for video thumbnail
32
+ *
33
+ * @property {string} [description] - LinkedIn, YouTube, Facebook, Threads: Description or commentary
34
+ * @property {'CONNECTIONS' | 'PUBLIC' | 'LOGGED_IN' | 'CONTAINER'} [visibility] - LinkedIn: Visibility setting
35
+ * @property {string} [target_linkedin_page_id] - LinkedIn: Page ID for organization uploads
36
+ *
37
+ * @property {string[]} [tags] - YouTube: Array of tags
38
+ * @property {string} [categoryId] - YouTube: Video category ID
39
+ * @property {'public' | 'unlisted' | 'private'} [privacyStatus] - YouTube: Privacy setting
40
+ * @property {boolean} [embeddable] - YouTube: Whether video is embeddable
41
+ * @property {'youtube' | 'creativeCommon'} [license] - YouTube: Video license
42
+ * @property {boolean} [publicStatsViewable] - YouTube: Whether public stats are viewable
43
+ * @property {boolean} [madeForKids] - YouTube: Whether video is made for kids
44
+ *
45
+ * @property {string} [facebook_page_id] - Facebook: Page ID
46
+ * @property {'DRAFT' | 'PUBLISHED' | 'SCHEDULED'} [video_state] - Facebook: Desired state of the video
47
+ *
48
+ * @property {string[]} [tagged_user_ids] - X (Twitter): Array of user IDs to tag
49
+ * @property {'following' | 'mentionedUsers' | 'everyone'} [reply_settings] - X (Twitter): Who can reply
50
+ * @property {boolean} [nullcast] - X (Twitter): Whether to publish without broadcasting
51
+ * @property {string} [place_id] - X (Twitter): Location place ID
52
+ * @property {number} [poll_duration] - X (Twitter): Poll duration in minutes
53
+ * @property {string[]} [poll_options] - X (Twitter): Array of poll options
54
+ * @property {'following' | 'mentionedUsers' | 'everyone'} [poll_reply_settings] - X (Twitter): Who can reply to poll
55
+ *
56
+ * @property {string} [pinterest_board_id] - Pinterest: Board ID
57
+ * @property {string} [pinterest_link] - Pinterest: Destination link
58
+ * @property {string} [pinterest_cover_image_url] - Pinterest: URL of an image to use as the video cover
59
+ * @property {string} [pinterest_cover_image_content_type] - Pinterest: Content type of the cover image
60
+ * @property {string} [pinterest_cover_image_data] - Pinterest: Base64 encoded cover image data
61
+ * @property {number} [pinterest_cover_image_key_frame_time] - Pinterest: Time in ms of video frame for cover
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} UploadPhotosOptions
66
+ * @property {string} user - User identifier
67
+ * @property {string[]} platforms - Array of target platforms. Supported: tiktok, instagram, linkedin, facebook, x, threads, pinterest
68
+ * @property {string[]} photos - Array of photo file paths or URLs
69
+ * @property {string} title - Title of the post
70
+ * @property {string} [caption] - Caption/description for the photos
71
+ * @property {'PUBLIC'} [visibility] - LinkedIn: Visibility setting ("PUBLIC")
72
+ * @property {string} [target_linkedin_page_id] - LinkedIn: Page ID for organization uploads
73
+ * @property {string} [facebook_page_id] - Facebook: Page ID
74
+ * @property {boolean} [auto_add_music] - TikTok: Automatically add background music
75
+ * @property {boolean} [disable_comment] - TikTok: Disable comments
76
+ * @property {boolean} [branded_content] - TikTok: Branded content (requires disclose_commercial=true)
77
+ * @property {boolean} [disclose_commercial] - TikTok: Disclose commercial nature
78
+ * @property {number} [photo_cover_index] - TikTok: Index of photo for cover
79
+ * @property {string} [description] - TikTok: Description (defaults to title)
80
+ * @property {'IMAGE' | 'STORIES'} [media_type] - Instagram: Media type ("IMAGE" or "STORIES")
81
+ * @property {string} [pinterest_board_id] - Pinterest: Board ID
82
+ * @property {string} [pinterest_alt_text] - Pinterest: Alt text
83
+ * @property {string} [pinterest_link] - Pinterest: Destination link
84
+ */
85
+
86
+ /**
87
+ * @typedef {Object} UploadTextOptions
88
+ * @property {string} user - User identifier
89
+ * @property {('linkedin' | 'x' | 'facebook' | 'threads')[]} platforms - Array of target platforms.
90
+ * @property {string} title - The text content for the post.
91
+ * @property {string} [target_linkedin_page_id] - LinkedIn: Page ID for organization posts.
92
+ * @property {string} [facebook_page_id] - Facebook: Page ID. Required if 'facebook' is in platforms.
11
93
  */
12
94
 
13
95
  /**
@@ -38,6 +120,70 @@ export class UploadPost {
38
120
  form.append('platform[]', platform);
39
121
  });
40
122
 
123
+ // Add platform-specific parameters if they exist in options
124
+ // TikTok
125
+ if (options.privacy_level) form.append('privacy_level', options.privacy_level);
126
+ if (options.disable_duet !== undefined) form.append('disable_duet', options.disable_duet.toString());
127
+ if (options.disable_comment !== undefined) form.append('disable_comment', options.disable_comment.toString());
128
+ if (options.disable_stitch !== undefined) form.append('disable_stitch', options.disable_stitch.toString());
129
+ if (options.cover_timestamp !== undefined) form.append('cover_timestamp', options.cover_timestamp.toString());
130
+ if (options.brand_content_toggle !== undefined) form.append('brand_content_toggle', options.brand_content_toggle.toString());
131
+ if (options.brand_organic !== undefined) form.append('brand_organic', options.brand_organic.toString());
132
+ if (options.branded_content !== undefined) form.append('branded_content', options.branded_content.toString());
133
+ if (options.brand_organic_toggle !== undefined) form.append('brand_organic_toggle', options.brand_organic_toggle.toString());
134
+ if (options.is_aigc !== undefined) form.append('is_aigc', options.is_aigc.toString());
135
+
136
+ // Instagram
137
+ if (options.media_type) form.append('media_type', options.media_type); // Used by photos and videos
138
+ if (options.share_to_feed !== undefined) form.append('share_to_feed', options.share_to_feed.toString());
139
+ if (options.collaborators) form.append('collaborators', options.collaborators);
140
+ if (options.cover_url) form.append('cover_url', options.cover_url);
141
+ if (options.audio_name) form.append('audio_name', options.audio_name);
142
+ if (options.user_tags) form.append('user_tags', options.user_tags);
143
+ if (options.location_id) form.append('location_id', options.location_id);
144
+ if (options.thumb_offset) form.append('thumb_offset', options.thumb_offset);
145
+
146
+ // LinkedIn
147
+ if (options.description) form.append('description', options.description); // Used by multiple platforms
148
+ if (options.visibility) form.append('visibility', options.visibility); // Used by multiple platforms
149
+ if (options.target_linkedin_page_id) form.append('target_linkedin_page_id', options.target_linkedin_page_id);
150
+
151
+ // YouTube
152
+ if (options.tags) options.tags.forEach(tag => form.append('tags[]', tag));
153
+ if (options.categoryId) form.append('categoryId', options.categoryId);
154
+ if (options.privacyStatus) form.append('privacyStatus', options.privacyStatus);
155
+ if (options.embeddable !== undefined) form.append('embeddable', options.embeddable.toString());
156
+ if (options.license) form.append('license', options.license);
157
+ if (options.publicStatsViewable !== undefined) form.append('publicStatsViewable', options.publicStatsViewable.toString());
158
+ if (options.madeForKids !== undefined) form.append('madeForKids', options.madeForKids.toString());
159
+
160
+ // Facebook
161
+ if (options.facebook_page_id) form.append('facebook_page_id', options.facebook_page_id);
162
+ if (options.video_state) form.append('video_state', options.video_state);
163
+
164
+ // X (Twitter)
165
+ if (options.tagged_user_ids) options.tagged_user_ids.forEach(id => form.append('tagged_user_ids[]', id));
166
+ if (options.reply_settings) form.append('reply_settings', options.reply_settings);
167
+ if (options.nullcast !== undefined) form.append('nullcast', options.nullcast.toString());
168
+ if (options.place_id) form.append('place_id', options.place_id);
169
+ if (options.poll_duration !== undefined) form.append('poll_duration', options.poll_duration.toString());
170
+ if (options.poll_options) options.poll_options.forEach(opt => form.append('poll_options[]', opt));
171
+ if (options.poll_reply_settings) form.append('poll_reply_settings', options.poll_reply_settings);
172
+
173
+ // Pinterest
174
+ if (options.pinterest_board_id) form.append('pinterest_board_id', options.pinterest_board_id);
175
+ if (options.pinterest_link) form.append('pinterest_link', options.pinterest_link);
176
+ if (options.pinterest_cover_image_url) form.append('pinterest_cover_image_url', options.pinterest_cover_image_url);
177
+ if (options.pinterest_cover_image_content_type) form.append('pinterest_cover_image_content_type', options.pinterest_cover_image_content_type);
178
+ if (options.pinterest_cover_image_data) form.append('pinterest_cover_image_data', options.pinterest_cover_image_data);
179
+ if (options.pinterest_cover_image_key_frame_time !== undefined) form.append('pinterest_cover_image_key_frame_time', options.pinterest_cover_image_key_frame_time.toString());
180
+
181
+ // If video is passed as a URL in options.video (as per API docs for URL uploads)
182
+ if (options.video && (options.video.startsWith('http://') || options.video.startsWith('https://'))) {
183
+ form.append('video', options.video);
184
+ }
185
+
186
+
41
187
  try {
42
188
  const response = await axios.post(`${this.baseUrl}/upload`, form, {
43
189
  headers: {
@@ -57,4 +203,142 @@ export class UploadPost {
57
203
  throw new Error(`Upload failed: ${error.message}`);
58
204
  }
59
205
  }
206
+
207
+ /**
208
+ * Upload photos to social media platforms
209
+ * @param {string[]} photosPathsOrUrls - Array of photo file paths or URLs
210
+ * @param {UploadPhotosOptions} options - Upload photos options
211
+ * @returns {Promise<Object>} API response
212
+ */
213
+ async uploadPhotos(photosPathsOrUrls, options) {
214
+ const form = new FormData();
215
+
216
+ // Common parameters
217
+ form.append('user', options.user);
218
+ form.append('title', options.title);
219
+ if (options.caption) {
220
+ form.append('caption', options.caption);
221
+ }
222
+ options.platforms.forEach(platform => {
223
+ form.append('platform[]', platform);
224
+ });
225
+
226
+ photosPathsOrUrls.forEach(photoItem => {
227
+ if (typeof photoItem === 'string' && (photoItem.startsWith('http://') || photoItem.startsWith('https://'))) {
228
+ form.append('photos[]', photoItem); // Pass URL as string
229
+ } else if (typeof photoItem === 'string') {
230
+ if (!fs.existsSync(photoItem)) {
231
+ throw new Error(`Photo file not found: ${photoItem}`);
232
+ }
233
+ form.append('photos[]', createReadStream(photoItem)); // Pass file path as stream
234
+ } else {
235
+ throw new Error(`Invalid photo item: ${photoItem}. Must be a file path or URL string.`);
236
+ }
237
+ });
238
+
239
+ // Platform-specific parameters
240
+ // LinkedIn
241
+ if (options.visibility) form.append('visibility', options.visibility);
242
+ if (options.target_linkedin_page_id) form.append('target_linkedin_page_id', options.target_linkedin_page_id);
243
+
244
+ // Facebook
245
+ if (options.facebook_page_id) form.append('facebook_page_id', options.facebook_page_id);
246
+
247
+ // TikTok
248
+ if (options.auto_add_music !== undefined) form.append('auto_add_music', options.auto_add_music.toString());
249
+ if (options.disable_comment !== undefined) form.append('disable_comment', options.disable_comment.toString());
250
+ if (options.branded_content !== undefined) form.append('branded_content', options.branded_content.toString());
251
+ if (options.disclose_commercial !== undefined) form.append('disclose_commercial', options.disclose_commercial.toString());
252
+ if (options.photo_cover_index !== undefined) form.append('photo_cover_index', options.photo_cover_index.toString());
253
+ if (options.description) form.append('description', options.description);
254
+
255
+ // Instagram
256
+ if (options.media_type) form.append('media_type', options.media_type);
257
+
258
+ // Pinterest
259
+ if (options.pinterest_board_id) form.append('pinterest_board_id', options.pinterest_board_id);
260
+ if (options.pinterest_alt_text) form.append('pinterest_alt_text', options.pinterest_alt_text);
261
+ if (options.pinterest_link) form.append('pinterest_link', options.pinterest_link);
262
+
263
+ try {
264
+ const response = await axios.post(`${this.baseUrl}/upload_photos`, form, {
265
+ headers: {
266
+ ...form.getHeaders(),
267
+ 'Authorization': `Apikey ${this.apiKey}`
268
+ },
269
+ maxContentLength: Infinity,
270
+ maxBodyLength: Infinity
271
+ });
272
+
273
+ if (response.status !== 200) {
274
+ throw new Error(`API request failed with status ${response.status}`);
275
+ }
276
+
277
+ return response.data;
278
+ } catch (error) {
279
+ let errorMessage = `Photo upload failed: ${error.message}`;
280
+ if (error.response && error.response.data && error.response.data.message) {
281
+ errorMessage += ` - ${error.response.data.message}`;
282
+ } else if (error.response && error.response.data && error.response.data.detail) {
283
+ errorMessage += ` - ${error.response.data.detail}`;
284
+ }
285
+ throw new Error(errorMessage);
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Upload text posts to social media platforms
291
+ * @param {UploadTextOptions} options - Upload text options
292
+ * @returns {Promise<Object>} API response
293
+ */
294
+ async uploadText(options) {
295
+ const form = new FormData();
296
+
297
+ // Common parameters
298
+ form.append('user', options.user);
299
+ form.append('title', options.title); // 'title' is used as the text content field for all supported platforms
300
+
301
+ options.platforms.forEach(platform => {
302
+ form.append('platform[]', platform);
303
+ });
304
+
305
+ // Platform-specific parameters
306
+ // LinkedIn
307
+ if (options.target_linkedin_page_id && options.platforms.includes('linkedin')) {
308
+ form.append('target_linkedin_page_id', options.target_linkedin_page_id);
309
+ }
310
+
311
+ // Facebook
312
+ if (options.facebook_page_id && options.platforms.includes('facebook')) {
313
+ form.append('facebook_page_id', options.facebook_page_id);
314
+ }
315
+ // X (Twitter) and Threads use the common 'title' parameter for their text content.
316
+
317
+ try {
318
+ const response = await axios.post(`${this.baseUrl}/upload_text`, form, {
319
+ headers: {
320
+ ...form.getHeaders(),
321
+ 'Authorization': `Apikey ${this.apiKey}`
322
+ },
323
+ });
324
+
325
+ if (response.status !== 200) {
326
+ throw new Error(`API request failed with status ${response.status}`);
327
+ }
328
+
329
+ return response.data;
330
+ } catch (error) {
331
+ let errorMessage = `Text post upload failed: ${error.message}`;
332
+ if (error.response && error.response.data) {
333
+ if (error.response.data.message) {
334
+ errorMessage += ` - ${error.response.data.message}`;
335
+ } else if (error.response.data.detail) {
336
+ errorMessage += ` - ${error.response.data.detail}`;
337
+ } else {
338
+ errorMessage += ` - ${JSON.stringify(error.response.data)}`;
339
+ }
340
+ }
341
+ throw new Error(errorMessage);
342
+ }
343
+ }
60
344
  }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "upload-post",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "Official client library for Upload-Post API - cross-platform social media video upload",
5
- "main": "index.d.ts",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
6
7
  "type": "module",
7
8
  "scripts": {
8
9
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -17,7 +18,8 @@
17
18
  ],
18
19
  "dependencies": {
19
20
  "axios": "^1.6.2",
20
- "form-data": "^4.0.0"
21
+ "form-data": "^4.0.0",
22
+ "upload-post": "^1.0.0"
21
23
  },
22
24
  "devDependencies": {
23
25
  "@types/node": "^20.11.0",