upload-post 1.0.1 → 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.
- package/LICENSE +21 -0
- package/README.md +214 -11
- package/index.d.ts +148 -1
- package/index.js +285 -1
- package/package.json +1 -1
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
- `options.
|
|
46
|
-
- `options.
|
|
47
|
-
- `options.
|
|
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
|
}
|