upload-post 2.0.4 → 2.1.2
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/.github/workflows/publish.yml +5 -5
- package/README.md +11 -0
- package/index.d.ts +146 -9
- package/index.js +272 -18
- package/package.json +1 -1
|
@@ -46,10 +46,6 @@ jobs:
|
|
|
46
46
|
echo "🚀 New version detected: $REMOTE → $LOCAL"
|
|
47
47
|
fi
|
|
48
48
|
|
|
49
|
-
- name: Update npm to latest
|
|
50
|
-
if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
|
|
51
|
-
run: npm install -g npm@latest && npm --version
|
|
52
|
-
|
|
53
49
|
- name: Install dependencies
|
|
54
50
|
if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
|
|
55
51
|
run: npm ci
|
|
@@ -61,7 +57,11 @@ jobs:
|
|
|
61
57
|
- name: Publish to npm (dry-run)
|
|
62
58
|
if: ${{ inputs.dry_run == true }}
|
|
63
59
|
run: npm publish --access public --dry-run
|
|
60
|
+
env:
|
|
61
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
64
62
|
|
|
65
63
|
- name: Publish to npm
|
|
66
64
|
if: (steps.version_check.outputs.new == 'true' && inputs.dry_run != true) || github.event_name == 'release'
|
|
67
|
-
run: npm publish --access public
|
|
65
|
+
run: npm publish --access public
|
|
66
|
+
env:
|
|
67
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
CHANGED
|
@@ -123,6 +123,13 @@ const status = await client.getStatus('request_id_from_upload');
|
|
|
123
123
|
console.log(status);
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
+
For scheduled or queued posts, check the status using the job_id:
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const status = await client.getJobStatus('job_id_from_scheduled_post');
|
|
130
|
+
console.log(status);
|
|
131
|
+
```
|
|
132
|
+
|
|
126
133
|
### Get Upload History
|
|
127
134
|
|
|
128
135
|
```javascript
|
|
@@ -267,6 +274,7 @@ const boards = await client.getPinterestBoards('my-profile');
|
|
|
267
274
|
### Threads
|
|
268
275
|
- `threadsLongTextAsPost` - Post long text as single post (vs thread)
|
|
269
276
|
- `threadsThreadMediaLayout` - Comma-separated list of how many media items to include in each Threads post. Each value must be 1-10, and the total must equal the number of files. Example: '5,5' splits 10 items into 2 posts with 5 each. If omitted and more than 10 items are provided, auto-chunks into groups of 10.
|
|
277
|
+
- `threadsTopicTag` - Topic tag for the Threads post (1-50 characters, no periods or ampersands). One tag per post. Helps increase reach.
|
|
270
278
|
|
|
271
279
|
### Reddit
|
|
272
280
|
- `redditSubreddit` - Subreddit name (without r/)
|
|
@@ -286,6 +294,7 @@ These options work across all upload methods:
|
|
|
286
294
|
| `scheduledDate` | ISO date for scheduling |
|
|
287
295
|
| `timezone` | Timezone for scheduled date |
|
|
288
296
|
| `addToQueue` | Add to posting queue |
|
|
297
|
+
| `maxPostsPerSlot` | Max posts per queue slot (overrides profile setting) |
|
|
289
298
|
| `asyncUpload` | Process asynchronously (default: true) |
|
|
290
299
|
|
|
291
300
|
## TypeScript Support
|
|
@@ -327,3 +336,5 @@ try {
|
|
|
327
336
|
## License
|
|
328
337
|
|
|
329
338
|
MIT
|
|
339
|
+
|
|
340
|
+
<!-- deployed 2026-03-16 17:49 UTC -->
|
package/index.d.ts
CHANGED
|
@@ -2,13 +2,13 @@ declare module 'upload-post' {
|
|
|
2
2
|
// ==================== Common Types ====================
|
|
3
3
|
|
|
4
4
|
/** Supported platforms for video upload */
|
|
5
|
-
export type VideoPlatform = 'tiktok' | 'instagram' | 'youtube' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'bluesky' | 'x';
|
|
5
|
+
export type VideoPlatform = 'tiktok' | 'instagram' | 'youtube' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'bluesky' | 'x' | 'google_business';
|
|
6
6
|
|
|
7
7
|
/** Supported platforms for photo upload */
|
|
8
|
-
export type PhotoPlatform = 'tiktok' | 'instagram' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'reddit' | 'bluesky' | 'x';
|
|
8
|
+
export type PhotoPlatform = 'tiktok' | 'instagram' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'reddit' | 'bluesky' | 'x' | 'google_business';
|
|
9
9
|
|
|
10
10
|
/** Supported platforms for text upload */
|
|
11
|
-
export type TextPlatform = 'x' | 'linkedin' | 'facebook' | 'threads' | 'reddit' | 'bluesky';
|
|
11
|
+
export type TextPlatform = 'x' | 'linkedin' | 'facebook' | 'threads' | 'reddit' | 'bluesky' | 'google_business';
|
|
12
12
|
|
|
13
13
|
/** TikTok privacy levels */
|
|
14
14
|
export type TikTokPrivacyLevel = 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY';
|
|
@@ -43,8 +43,8 @@ declare module 'upload-post' {
|
|
|
43
43
|
// ==================== Common Options ====================
|
|
44
44
|
|
|
45
45
|
export interface CommonUploadOptions {
|
|
46
|
-
/** Post title/caption */
|
|
47
|
-
title
|
|
46
|
+
/** Post title/caption. Required for YouTube, Reddit, and text posts. Optional for TikTok, Instagram, Facebook, LinkedIn, X, Threads, Bluesky, Pinterest. */
|
|
47
|
+
title?: string;
|
|
48
48
|
/** User identifier (profile name) */
|
|
49
49
|
user: string;
|
|
50
50
|
/** First comment to post after publishing */
|
|
@@ -57,6 +57,8 @@ declare module 'upload-post' {
|
|
|
57
57
|
timezone?: string;
|
|
58
58
|
/** Add to posting queue instead of immediate post */
|
|
59
59
|
addToQueue?: boolean;
|
|
60
|
+
/** Maximum number of posts allowed per queue slot (overrides profile setting). Only used when addToQueue is true. */
|
|
61
|
+
maxPostsPerSlot?: number;
|
|
60
62
|
/** Process upload asynchronously (default: true) */
|
|
61
63
|
asyncUpload?: boolean;
|
|
62
64
|
|
|
@@ -134,8 +136,8 @@ declare module 'upload-post' {
|
|
|
134
136
|
instagramShareToFeed?: boolean;
|
|
135
137
|
/** Comma-separated collaborator usernames */
|
|
136
138
|
instagramCollaborators?: string;
|
|
137
|
-
/** Custom cover URL */
|
|
138
|
-
instagramCoverUrl?: string;
|
|
139
|
+
/** Custom cover: URL string, file path, Buffer, or ReadableStream */
|
|
140
|
+
instagramCoverUrl?: string | Buffer | NodeJS.ReadableStream;
|
|
139
141
|
/** Audio track name */
|
|
140
142
|
instagramAudioName?: string;
|
|
141
143
|
/** Comma-separated user tags */
|
|
@@ -343,12 +345,22 @@ declare module 'upload-post' {
|
|
|
343
345
|
platforms: PhotoPlatform[];
|
|
344
346
|
}
|
|
345
347
|
|
|
348
|
+
export interface LinkPreviewOptions {
|
|
349
|
+
/** Generic URL for link preview card (works for LinkedIn, Bluesky, Facebook). Platform-specific params take priority. */
|
|
350
|
+
linkUrl?: string;
|
|
351
|
+
/** URL for link preview card on LinkedIn */
|
|
352
|
+
linkedinLinkUrl?: string;
|
|
353
|
+
/** URL for external embed link preview on Bluesky */
|
|
354
|
+
blueskyLinkUrl?: string;
|
|
355
|
+
}
|
|
356
|
+
|
|
346
357
|
export interface UploadTextOptions extends CommonUploadOptions,
|
|
347
358
|
LinkedInOptions,
|
|
348
359
|
FacebookTextOptions,
|
|
349
360
|
XTextOptions,
|
|
350
361
|
ThreadsOptions,
|
|
351
|
-
RedditOptions
|
|
362
|
+
RedditOptions,
|
|
363
|
+
LinkPreviewOptions {
|
|
352
364
|
/** Target platforms */
|
|
353
365
|
platforms: TextPlatform[];
|
|
354
366
|
}
|
|
@@ -370,6 +382,8 @@ declare module 'upload-post' {
|
|
|
370
382
|
timezone?: string;
|
|
371
383
|
/** Add to posting queue */
|
|
372
384
|
addToQueue?: boolean;
|
|
385
|
+
/** Maximum number of posts allowed per queue slot (overrides profile setting). Only used when addToQueue is true. */
|
|
386
|
+
maxPostsPerSlot?: number;
|
|
373
387
|
/** Process upload asynchronously */
|
|
374
388
|
asyncUpload?: boolean;
|
|
375
389
|
}
|
|
@@ -501,6 +515,12 @@ declare module 'upload-post' {
|
|
|
501
515
|
*/
|
|
502
516
|
getStatus(requestId: string): Promise<StatusResponse>;
|
|
503
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Get the status of a scheduled or queued upload by job ID
|
|
520
|
+
* @param jobId - The job_id from a scheduled or queued upload
|
|
521
|
+
*/
|
|
522
|
+
getJobStatus(jobId: string): Promise<StatusResponse>;
|
|
523
|
+
|
|
504
524
|
/**
|
|
505
525
|
* Get upload history
|
|
506
526
|
* @param options - Query options
|
|
@@ -512,7 +532,62 @@ declare module 'upload-post' {
|
|
|
512
532
|
* @param profileUsername - Profile username
|
|
513
533
|
* @param options - Query options
|
|
514
534
|
*/
|
|
515
|
-
getAnalytics(profileUsername: string, options?: { platforms?: string[] }): Promise<AnalyticsResponse>;
|
|
535
|
+
getAnalytics(profileUsername: string, options?: { platforms?: string[]; pageId?: string; pageUrn?: string }): Promise<AnalyticsResponse>;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get total impressions for a profile from daily snapshots
|
|
539
|
+
* @param profileUsername - Profile username
|
|
540
|
+
* @param options - Query options
|
|
541
|
+
*/
|
|
542
|
+
getTotalImpressions(profileUsername: string, options?: {
|
|
543
|
+
period?: 'last_day' | 'last_week' | 'last_month' | 'last_3months' | 'last_year';
|
|
544
|
+
startDate?: string;
|
|
545
|
+
endDate?: string;
|
|
546
|
+
date?: string;
|
|
547
|
+
platforms?: string[];
|
|
548
|
+
breakdown?: boolean;
|
|
549
|
+
metrics?: string[];
|
|
550
|
+
}): Promise<{
|
|
551
|
+
success: boolean;
|
|
552
|
+
profile_username: string;
|
|
553
|
+
start_date: string;
|
|
554
|
+
end_date: string;
|
|
555
|
+
total_impressions?: number;
|
|
556
|
+
metrics?: Record<string, number>;
|
|
557
|
+
per_platform?: Record<string, number | Record<string, number>>;
|
|
558
|
+
per_day?: Record<string, number | Record<string, number>>;
|
|
559
|
+
platforms_filter?: string[];
|
|
560
|
+
}>;
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Get analytics for a specific post across all platforms
|
|
564
|
+
* @param requestId - The request_id from the upload
|
|
565
|
+
*/
|
|
566
|
+
getPostAnalytics(requestId: string): Promise<{
|
|
567
|
+
success: boolean;
|
|
568
|
+
post: { request_id: string; profile_username: string; post_title: string; post_caption: string; media_type: string; upload_timestamp: string };
|
|
569
|
+
platforms: Record<string, {
|
|
570
|
+
success: boolean;
|
|
571
|
+
platform_post_id?: string;
|
|
572
|
+
post_url?: string;
|
|
573
|
+
post_metrics?: Record<string, number>;
|
|
574
|
+
post_metrics_source?: string;
|
|
575
|
+
post_metrics_error?: string;
|
|
576
|
+
profile_snapshot_at_post_date?: Record<string, number>;
|
|
577
|
+
profile_snapshot_latest?: Record<string, number>;
|
|
578
|
+
profile_snapshot_latest_date?: string;
|
|
579
|
+
error_message?: string;
|
|
580
|
+
}>;
|
|
581
|
+
}>;
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Get available metrics configuration for all supported platforms
|
|
585
|
+
*/
|
|
586
|
+
getPlatformMetrics(): Promise<Record<string, {
|
|
587
|
+
primary_impressions_field: string;
|
|
588
|
+
available_metrics: string[];
|
|
589
|
+
metric_labels: Record<string, string>;
|
|
590
|
+
}>>;
|
|
516
591
|
|
|
517
592
|
// Scheduled Posts
|
|
518
593
|
/**
|
|
@@ -573,6 +648,68 @@ declare module 'upload-post' {
|
|
|
573
648
|
*/
|
|
574
649
|
validateJwt(jwt: string): Promise<{ success: boolean; valid?: boolean }>;
|
|
575
650
|
|
|
651
|
+
/**
|
|
652
|
+
* Get user preferences (including calendar settings)
|
|
653
|
+
*/
|
|
654
|
+
getUserPreferences(): Promise<{ success: boolean; preferences?: { week_start_day?: number; [key: string]: any } }>;
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Update user preferences (including calendar settings)
|
|
658
|
+
* @param options - Preferences options
|
|
659
|
+
*/
|
|
660
|
+
updateUserPreferences(options?: { weekStartDay?: number }): Promise<{ success: boolean; preferences?: { week_start_day?: number; [key: string]: any } }>;
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Get notification configuration (including webhook settings)
|
|
664
|
+
*/
|
|
665
|
+
getNotificationConfig(): Promise<{ success: boolean; config?: { webhook_events?: string[]; webhook_url?: string; [key: string]: any } }>;
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Update notification configuration (including webhook settings)
|
|
669
|
+
* @param options - Notification config options
|
|
670
|
+
*/
|
|
671
|
+
updateNotificationConfig(options?: { webhookEvents?: string[]; webhookUrl?: string }): Promise<{ success: boolean; config?: { webhook_events?: string[]; webhook_url?: string; [key: string]: any } }>;
|
|
672
|
+
|
|
673
|
+
// Instagram Comments
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Get comments on an Instagram post
|
|
677
|
+
* @param user - Profile username
|
|
678
|
+
* @param options - Query options
|
|
679
|
+
*/
|
|
680
|
+
getPostComments(user: string, options?: { postId?: string; postUrl?: string }): Promise<{
|
|
681
|
+
success: boolean;
|
|
682
|
+
comments?: Array<{
|
|
683
|
+
id: string;
|
|
684
|
+
text: string;
|
|
685
|
+
timestamp: string;
|
|
686
|
+
user: { id: string; username: string };
|
|
687
|
+
}>;
|
|
688
|
+
}>;
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Send a private reply (DM) to the author of an Instagram comment
|
|
692
|
+
* @param options - Reply options
|
|
693
|
+
*/
|
|
694
|
+
replyToComment(options: { user: string; commentId: string; message: string }): Promise<{
|
|
695
|
+
success: boolean;
|
|
696
|
+
recipient_id?: string;
|
|
697
|
+
message_id?: string;
|
|
698
|
+
message?: string;
|
|
699
|
+
error?: string;
|
|
700
|
+
}>;
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Post a public reply to an Instagram comment (visible under the original comment)
|
|
704
|
+
* @param options - Reply options
|
|
705
|
+
*/
|
|
706
|
+
publicReplyToComment(options: { user: string; commentId: string; message: string }): Promise<{
|
|
707
|
+
success: boolean;
|
|
708
|
+
id?: string;
|
|
709
|
+
message?: string;
|
|
710
|
+
error?: string;
|
|
711
|
+
}>;
|
|
712
|
+
|
|
576
713
|
// Helper Endpoints
|
|
577
714
|
/**
|
|
578
715
|
* Get Facebook pages for a profile
|
package/index.js
CHANGED
|
@@ -29,7 +29,8 @@ export class UploadPost {
|
|
|
29
29
|
method,
|
|
30
30
|
url: `${this.baseUrl}${endpoint}`,
|
|
31
31
|
headers: {
|
|
32
|
-
'Authorization': `Apikey ${this.apiKey}
|
|
32
|
+
'Authorization': `Apikey ${this.apiKey}`,
|
|
33
|
+
'X-Upload-Post-Source': 'npm',
|
|
33
34
|
},
|
|
34
35
|
maxContentLength: Infinity,
|
|
35
36
|
maxBodyLength: Infinity
|
|
@@ -62,7 +63,7 @@ export class UploadPost {
|
|
|
62
63
|
*/
|
|
63
64
|
_addCommonParams(form, options) {
|
|
64
65
|
form.append('user', options.user);
|
|
65
|
-
form.append('title', options.title);
|
|
66
|
+
if (options.title) form.append('title', options.title);
|
|
66
67
|
|
|
67
68
|
// Platforms
|
|
68
69
|
const platforms = Array.isArray(options.platforms) ? options.platforms : [options.platforms];
|
|
@@ -74,6 +75,7 @@ export class UploadPost {
|
|
|
74
75
|
if (options.scheduledDate) form.append('scheduled_date', options.scheduledDate);
|
|
75
76
|
if (options.timezone) form.append('timezone', options.timezone);
|
|
76
77
|
if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
|
|
78
|
+
if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
|
|
77
79
|
if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
|
|
78
80
|
|
|
79
81
|
// Platform-specific title overrides
|
|
@@ -103,6 +105,24 @@ export class UploadPost {
|
|
|
103
105
|
if (options.youtubeFirstComment) form.append('youtube_first_comment', options.youtubeFirstComment);
|
|
104
106
|
if (options.redditFirstComment) form.append('reddit_first_comment', options.redditFirstComment);
|
|
105
107
|
if (options.blueskyFirstComment) form.append('bluesky_first_comment', options.blueskyFirstComment);
|
|
108
|
+
if (options.linkedinFirstComment) form.append('linkedin_first_comment', options.linkedinFirstComment);
|
|
109
|
+
|
|
110
|
+
if (options.firstCommentMedia) {
|
|
111
|
+
const mediaItems = Array.isArray(options.firstCommentMedia) ? options.firstCommentMedia : [options.firstCommentMedia];
|
|
112
|
+
for (const mediaItem of mediaItems) {
|
|
113
|
+
if (typeof mediaItem === 'string') {
|
|
114
|
+
if (mediaItem.toLowerCase().startsWith('http://') || mediaItem.toLowerCase().startsWith('https://')) {
|
|
115
|
+
form.append('first_comment_media[]', mediaItem);
|
|
116
|
+
} else {
|
|
117
|
+
if (fs.existsSync(mediaItem)) {
|
|
118
|
+
form.append('first_comment_media[]', createReadStream(mediaItem));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
form.append('first_comment_media[]', mediaItem);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
106
126
|
}
|
|
107
127
|
|
|
108
128
|
/**
|
|
@@ -140,7 +160,16 @@ export class UploadPost {
|
|
|
140
160
|
|
|
141
161
|
if (isVideo) {
|
|
142
162
|
if (options.instagramShareToFeed !== undefined) form.append('share_to_feed', String(options.instagramShareToFeed));
|
|
143
|
-
if (options.instagramCoverUrl)
|
|
163
|
+
if (options.instagramCoverUrl) {
|
|
164
|
+
const coverVal = options.instagramCoverUrl;
|
|
165
|
+
if (typeof coverVal === 'string' && (coverVal.toLowerCase().startsWith('http://') || coverVal.toLowerCase().startsWith('https://'))) {
|
|
166
|
+
form.append('cover_url', coverVal);
|
|
167
|
+
} else if (typeof coverVal === 'string') {
|
|
168
|
+
form.append('cover_image', createReadStream(coverVal));
|
|
169
|
+
} else {
|
|
170
|
+
form.append('cover_image', coverVal);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
144
173
|
if (options.instagramAudioName) form.append('audio_name', options.instagramAudioName);
|
|
145
174
|
if (options.instagramThumbOffset) form.append('thumb_offset', options.instagramThumbOffset);
|
|
146
175
|
}
|
|
@@ -175,9 +204,12 @@ export class UploadPost {
|
|
|
175
204
|
* Add LinkedIn-specific parameters
|
|
176
205
|
* @private
|
|
177
206
|
*/
|
|
178
|
-
_addLinkedinParams(form, options) {
|
|
207
|
+
_addLinkedinParams(form, options, isText = false) {
|
|
179
208
|
if (options.linkedinVisibility) form.append('visibility', options.linkedinVisibility);
|
|
180
209
|
if (options.targetLinkedinPageId) form.append('target_linkedin_page_id', options.targetLinkedinPageId);
|
|
210
|
+
if (isText && (options.linkedinLinkUrl || options.linkUrl)) {
|
|
211
|
+
form.append('linkedin_link_url', options.linkedinLinkUrl || options.linkUrl);
|
|
212
|
+
}
|
|
181
213
|
}
|
|
182
214
|
|
|
183
215
|
/**
|
|
@@ -258,15 +290,19 @@ export class UploadPost {
|
|
|
258
290
|
_addThreadsParams(form, options) {
|
|
259
291
|
if (options.threadsLongTextAsPost !== undefined) form.append('threads_long_text_as_post', String(options.threadsLongTextAsPost));
|
|
260
292
|
if (options.threadsThreadMediaLayout) form.append('threads_thread_media_layout', options.threadsThreadMediaLayout);
|
|
293
|
+
if (options.threadsTopicTag) form.append('threads_topic_tag', options.threadsTopicTag);
|
|
261
294
|
}
|
|
262
295
|
|
|
263
296
|
/**
|
|
264
297
|
* Add Reddit-specific parameters
|
|
265
298
|
* @private
|
|
266
299
|
*/
|
|
267
|
-
_addRedditParams(form, options) {
|
|
300
|
+
_addRedditParams(form, options, isText = false) {
|
|
268
301
|
if (options.redditSubreddit) form.append('subreddit', options.redditSubreddit);
|
|
269
302
|
if (options.redditFlairId) form.append('flair_id', options.redditFlairId);
|
|
303
|
+
if (isText && (options.redditLinkUrl || options.linkUrl)) {
|
|
304
|
+
form.append('reddit_link_url', options.redditLinkUrl || options.linkUrl);
|
|
305
|
+
}
|
|
270
306
|
}
|
|
271
307
|
|
|
272
308
|
/**
|
|
@@ -299,7 +335,7 @@ export class UploadPost {
|
|
|
299
335
|
* @param {string} [options.instagramMediaType] - REELS or STORIES
|
|
300
336
|
* @param {boolean} [options.instagramShareToFeed] - Share to feed
|
|
301
337
|
* @param {string} [options.instagramCollaborators] - Comma-separated collaborator usernames
|
|
302
|
-
* @param {string} [options.instagramCoverUrl] - Custom cover URL
|
|
338
|
+
* @param {string|Buffer|ReadableStream} [options.instagramCoverUrl] - Custom cover: URL string, file path, Buffer, or ReadableStream
|
|
303
339
|
* @param {string} [options.instagramAudioName] - Audio track name
|
|
304
340
|
* @param {string} [options.instagramUserTags] - Comma-separated user tags
|
|
305
341
|
* @param {string} [options.instagramLocationId] - Location ID
|
|
@@ -352,6 +388,7 @@ export class UploadPost {
|
|
|
352
388
|
* Threads options:
|
|
353
389
|
* @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post (vs thread)
|
|
354
390
|
* @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
|
|
391
|
+
* @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
|
|
355
392
|
*
|
|
356
393
|
* @returns {Promise<Object>} API response with request_id for async uploads
|
|
357
394
|
*/
|
|
@@ -359,7 +396,7 @@ export class UploadPost {
|
|
|
359
396
|
const form = new FormData();
|
|
360
397
|
|
|
361
398
|
// Handle video (file path or URL)
|
|
362
|
-
if (videoPathOrUrl.toLowerCase().startsWith('http://') || videoPathOrUrl.toLowerCase().startsWith('https://')) {
|
|
399
|
+
if (typeof videoPathOrUrl === 'string' && (videoPathOrUrl.toLowerCase().startsWith('http://') || videoPathOrUrl.toLowerCase().startsWith('https://'))) {
|
|
363
400
|
form.append('video', videoPathOrUrl);
|
|
364
401
|
} else {
|
|
365
402
|
if (!fs.existsSync(videoPathOrUrl)) {
|
|
@@ -433,6 +470,7 @@ export class UploadPost {
|
|
|
433
470
|
* Threads options:
|
|
434
471
|
* @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post
|
|
435
472
|
* @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
|
|
473
|
+
* @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
|
|
436
474
|
*
|
|
437
475
|
* Reddit options:
|
|
438
476
|
* @param {string} [options.redditSubreddit] - Subreddit name (without r/)
|
|
@@ -482,13 +520,18 @@ export class UploadPost {
|
|
|
482
520
|
* @param {string} [options.timezone] - Timezone for scheduled date
|
|
483
521
|
* @param {boolean} [options.addToQueue] - Add to posting queue
|
|
484
522
|
* @param {boolean} [options.asyncUpload=true] - Process upload asynchronously
|
|
485
|
-
*
|
|
523
|
+
* @param {string} [options.linkUrl] - Generic URL for link preview card (works for LinkedIn, Bluesky, Facebook). Platform-specific params take priority.
|
|
524
|
+
*
|
|
486
525
|
* LinkedIn options:
|
|
487
526
|
* @param {string} [options.targetLinkedinPageId] - Page ID for organization posts
|
|
488
|
-
*
|
|
527
|
+
* @param {string} [options.linkedinLinkUrl] - URL to attach as link preview on LinkedIn
|
|
528
|
+
*
|
|
529
|
+
* Bluesky options:
|
|
530
|
+
* @param {string} [options.blueskyLinkUrl] - URL to attach as external embed link preview on Bluesky
|
|
531
|
+
*
|
|
489
532
|
* Facebook options:
|
|
490
533
|
* @param {string} [options.facebookPageId] - Facebook Page ID
|
|
491
|
-
* @param {string} [options.facebookLinkUrl] - URL to attach as link preview
|
|
534
|
+
* @param {string} [options.facebookLinkUrl] - URL to attach as link preview on Facebook
|
|
492
535
|
*
|
|
493
536
|
* X (Twitter) options:
|
|
494
537
|
* @param {string} [options.xReplySettings] - Who can reply
|
|
@@ -503,6 +546,7 @@ export class UploadPost {
|
|
|
503
546
|
* Threads options:
|
|
504
547
|
* @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post
|
|
505
548
|
* @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
|
|
549
|
+
* @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
|
|
506
550
|
*
|
|
507
551
|
* Reddit options:
|
|
508
552
|
* @param {string} [options.redditSubreddit] - Subreddit name (without r/)
|
|
@@ -512,15 +556,21 @@ export class UploadPost {
|
|
|
512
556
|
*/
|
|
513
557
|
async uploadText(options) {
|
|
514
558
|
const form = new FormData();
|
|
515
|
-
|
|
559
|
+
|
|
516
560
|
this._addCommonParams(form, options);
|
|
517
|
-
|
|
561
|
+
|
|
562
|
+
// Generic link_url support
|
|
563
|
+
if (options.linkUrl) form.append('link_url', options.linkUrl);
|
|
564
|
+
|
|
518
565
|
const platforms = Array.isArray(options.platforms) ? options.platforms : [options.platforms];
|
|
519
|
-
if (platforms.includes('linkedin')) this._addLinkedinParams(form, options);
|
|
566
|
+
if (platforms.includes('linkedin')) this._addLinkedinParams(form, options, true);
|
|
520
567
|
if (platforms.includes('facebook')) this._addFacebookParams(form, options, false, true);
|
|
521
568
|
if (platforms.includes('x')) this._addXParams(form, options, true);
|
|
522
569
|
if (platforms.includes('threads')) this._addThreadsParams(form, options);
|
|
523
|
-
if (platforms.includes('reddit')) this._addRedditParams(form, options);
|
|
570
|
+
if (platforms.includes('reddit')) this._addRedditParams(form, options, true);
|
|
571
|
+
if (platforms.includes('bluesky') && (options.blueskyLinkUrl || options.linkUrl)) {
|
|
572
|
+
form.append('bluesky_link_url', options.blueskyLinkUrl || options.linkUrl);
|
|
573
|
+
}
|
|
524
574
|
|
|
525
575
|
return this._request('/upload_text', 'POST', form, true);
|
|
526
576
|
}
|
|
@@ -546,7 +596,7 @@ export class UploadPost {
|
|
|
546
596
|
const form = new FormData();
|
|
547
597
|
|
|
548
598
|
// Handle document (file path or URL)
|
|
549
|
-
if (documentPathOrUrl.toLowerCase().startsWith('http://') || documentPathOrUrl.toLowerCase().startsWith('https://')) {
|
|
599
|
+
if (typeof documentPathOrUrl === 'string' && (documentPathOrUrl.toLowerCase().startsWith('http://') || documentPathOrUrl.toLowerCase().startsWith('https://'))) {
|
|
550
600
|
form.append('document', documentPathOrUrl);
|
|
551
601
|
} else {
|
|
552
602
|
if (!fs.existsSync(documentPathOrUrl)) {
|
|
@@ -564,6 +614,7 @@ export class UploadPost {
|
|
|
564
614
|
if (options.scheduledDate) form.append('scheduled_date', options.scheduledDate);
|
|
565
615
|
if (options.timezone) form.append('timezone', options.timezone);
|
|
566
616
|
if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
|
|
617
|
+
if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
|
|
567
618
|
if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
|
|
568
619
|
|
|
569
620
|
this._addLinkedinParams(form, options);
|
|
@@ -583,6 +634,16 @@ export class UploadPost {
|
|
|
583
634
|
return this._request('/uploadposts/status', 'GET', { request_id: requestId });
|
|
584
635
|
}
|
|
585
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Get the status of a scheduled or queued upload by job ID
|
|
639
|
+
*
|
|
640
|
+
* @param {string} jobId - The job_id from a scheduled or queued upload
|
|
641
|
+
* @returns {Promise<Object>} Upload status
|
|
642
|
+
*/
|
|
643
|
+
async getJobStatus(jobId) {
|
|
644
|
+
return this._request('/uploadposts/status', 'GET', { job_id: jobId });
|
|
645
|
+
}
|
|
646
|
+
|
|
586
647
|
/**
|
|
587
648
|
* Get upload history
|
|
588
649
|
*
|
|
@@ -600,20 +661,89 @@ export class UploadPost {
|
|
|
600
661
|
|
|
601
662
|
/**
|
|
602
663
|
* Get analytics for a profile
|
|
603
|
-
*
|
|
664
|
+
*
|
|
604
665
|
* @param {string} profileUsername - Profile username
|
|
605
666
|
* @param {Object} [options] - Query options
|
|
606
|
-
* @param {string[]} [options.platforms] - Filter by platforms (instagram, linkedin, facebook, x)
|
|
607
|
-
* @
|
|
667
|
+
* @param {string[]} [options.platforms] - Filter by platforms (instagram, linkedin, facebook, x, youtube, tiktok, threads, pinterest, reddit)
|
|
668
|
+
* @param {string} [options.pageId] - Facebook Page ID (required for Facebook analytics)
|
|
669
|
+
* @param {string} [options.pageUrn] - LinkedIn page URN (defaults to "me" for personal profile)
|
|
670
|
+
* @returns {Promise<Object>} Analytics data per platform
|
|
608
671
|
*/
|
|
609
672
|
async getAnalytics(profileUsername, options = {}) {
|
|
610
673
|
const params = {};
|
|
611
674
|
if (options.platforms && options.platforms.length > 0) {
|
|
612
675
|
params.platforms = options.platforms.join(',');
|
|
613
676
|
}
|
|
677
|
+
if (options.pageId) params.page_id = options.pageId;
|
|
678
|
+
if (options.pageUrn) params.page_urn = options.pageUrn;
|
|
614
679
|
return this._request(`/analytics/${encodeURIComponent(profileUsername)}`, 'GET', params);
|
|
615
680
|
}
|
|
616
681
|
|
|
682
|
+
/**
|
|
683
|
+
* Get total impressions for a profile from daily snapshots
|
|
684
|
+
*
|
|
685
|
+
* @param {string} profileUsername - Profile username
|
|
686
|
+
* @param {Object} [options] - Query options
|
|
687
|
+
* @param {string} [options.period] - Period shortcut: last_day, last_week, last_month, last_3months, last_year
|
|
688
|
+
* @param {string} [options.startDate] - Start date in YYYY-MM-DD format
|
|
689
|
+
* @param {string} [options.endDate] - End date in YYYY-MM-DD format
|
|
690
|
+
* @param {string} [options.date] - Single date in YYYY-MM-DD format
|
|
691
|
+
* @param {string[]} [options.platforms] - Filter by platforms
|
|
692
|
+
* @param {boolean} [options.breakdown] - Include per-platform and per-day breakdown
|
|
693
|
+
* @param {string[]} [options.metrics] - Specific metrics to aggregate (e.g., ['likes', 'comments', 'shares'])
|
|
694
|
+
* @returns {Promise<Object>} Total impressions data
|
|
695
|
+
*/
|
|
696
|
+
async getTotalImpressions(profileUsername, options = {}) {
|
|
697
|
+
const params = {};
|
|
698
|
+
if (options.period) params.period = options.period;
|
|
699
|
+
if (options.startDate) params.start_date = options.startDate;
|
|
700
|
+
if (options.endDate) params.end_date = options.endDate;
|
|
701
|
+
if (options.date) params.date = options.date;
|
|
702
|
+
if (options.platforms && options.platforms.length > 0) {
|
|
703
|
+
params.platform = options.platforms.join(',');
|
|
704
|
+
}
|
|
705
|
+
if (options.breakdown) params.breakdown = 'true';
|
|
706
|
+
if (options.metrics && options.metrics.length > 0) {
|
|
707
|
+
params.metrics = options.metrics.join(',');
|
|
708
|
+
}
|
|
709
|
+
return this._request(`/uploadposts/total-impressions/${encodeURIComponent(profileUsername)}`, 'GET', params);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Get analytics for a specific post across all platforms it was published to
|
|
714
|
+
*
|
|
715
|
+
* @param {string} requestId - The request_id from the upload
|
|
716
|
+
* @returns {Promise<Object>} Post analytics with per-platform metrics
|
|
717
|
+
*/
|
|
718
|
+
async getPostAnalytics(requestId) {
|
|
719
|
+
return this._request(`/uploadposts/post-analytics/${encodeURIComponent(requestId)}`, 'GET');
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Get analytics for any post (including organic posts) using its native platform ID
|
|
724
|
+
*
|
|
725
|
+
* @param {string} platformPostId - The native post ID on the platform (e.g., Instagram media ID)
|
|
726
|
+
* @param {string} platform - The platform to query (instagram, youtube, tiktok, facebook, linkedin, x, threads, pinterest, reddit)
|
|
727
|
+
* @param {string} user - The profile_username that owns the social account
|
|
728
|
+
* @returns {Promise<Object>} Post analytics with live per-post metrics from the platform API
|
|
729
|
+
*/
|
|
730
|
+
async getPostAnalyticsByPlatformId(platformPostId, platform, user) {
|
|
731
|
+
return this._request('/uploadposts/post-analytics', 'GET', {
|
|
732
|
+
platform_post_id: platformPostId,
|
|
733
|
+
platform,
|
|
734
|
+
user,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Get available metrics configuration for all supported platforms
|
|
740
|
+
*
|
|
741
|
+
* @returns {Promise<Object>} Platform metrics config (primary fields, available metrics, labels)
|
|
742
|
+
*/
|
|
743
|
+
async getPlatformMetrics() {
|
|
744
|
+
return this._request('/uploadposts/platform-metrics', 'GET');
|
|
745
|
+
}
|
|
746
|
+
|
|
617
747
|
// ==================== Scheduled Posts ====================
|
|
618
748
|
|
|
619
749
|
/**
|
|
@@ -721,6 +851,106 @@ export class UploadPost {
|
|
|
721
851
|
return this._request('/uploadposts/users/validate-jwt', 'POST', { jwt });
|
|
722
852
|
}
|
|
723
853
|
|
|
854
|
+
/**
|
|
855
|
+
* Get user preferences (including calendar settings)
|
|
856
|
+
*
|
|
857
|
+
* @returns {Promise<Object>} User preferences
|
|
858
|
+
*/
|
|
859
|
+
async getUserPreferences() {
|
|
860
|
+
return this._request('/uploadposts/users/preferences', 'GET');
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Update user preferences (including calendar settings)
|
|
865
|
+
*
|
|
866
|
+
* @param {Object} options - Preferences options
|
|
867
|
+
* @param {number} [options.weekStartDay] - Week start day (0=Sunday, 1=Monday)
|
|
868
|
+
* @returns {Promise<Object>} Updated preferences
|
|
869
|
+
*/
|
|
870
|
+
async updateUserPreferences(options = {}) {
|
|
871
|
+
const body = {};
|
|
872
|
+
if (options.weekStartDay !== undefined) body.week_start_day = options.weekStartDay;
|
|
873
|
+
return this._request('/uploadposts/users/preferences', 'POST', body);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Get notification configuration (including webhook settings)
|
|
878
|
+
*
|
|
879
|
+
* @returns {Promise<Object>} Notification config
|
|
880
|
+
*/
|
|
881
|
+
async getNotificationConfig() {
|
|
882
|
+
return this._request('/uploadposts/notification-config', 'GET');
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Update notification configuration (including webhook settings)
|
|
887
|
+
*
|
|
888
|
+
* @param {Object} options - Notification config options
|
|
889
|
+
* @param {string[]} [options.webhookEvents] - Webhook event types to subscribe to (upload_completed, social_account_connected, social_account_disconnected, social_account_reauth_required)
|
|
890
|
+
* @param {string} [options.webhookUrl] - Webhook URL for notifications
|
|
891
|
+
* @returns {Promise<Object>} Updated notification config
|
|
892
|
+
*/
|
|
893
|
+
async updateNotificationConfig(options = {}) {
|
|
894
|
+
const body = {};
|
|
895
|
+
if (options.webhookEvents) body.webhook_events = options.webhookEvents;
|
|
896
|
+
if (options.webhookUrl) body.webhook_url = options.webhookUrl;
|
|
897
|
+
return this._request('/uploadposts/notification-config', 'POST', body);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// ==================== Instagram Comments ====================
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Get comments on an Instagram post
|
|
904
|
+
*
|
|
905
|
+
* @param {string} user - Profile username
|
|
906
|
+
* @param {Object} options - Query options
|
|
907
|
+
* @param {string} [options.postId] - Numeric media ID (provide postId or postUrl)
|
|
908
|
+
* @param {string} [options.postUrl] - Full Instagram post URL (provide postId or postUrl)
|
|
909
|
+
* @returns {Promise<Object>} Comments data
|
|
910
|
+
*/
|
|
911
|
+
async getPostComments(user, options = {}) {
|
|
912
|
+
const params = { platform: 'instagram', user };
|
|
913
|
+
if (options.postId) params.post_id = options.postId;
|
|
914
|
+
if (options.postUrl) params.post_url = options.postUrl;
|
|
915
|
+
return this._request('/uploadposts/comments', 'GET', params);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Send a private reply (DM) to the author of an Instagram comment
|
|
920
|
+
*
|
|
921
|
+
* @param {Object} options - Reply options
|
|
922
|
+
* @param {string} options.user - Profile username
|
|
923
|
+
* @param {string} options.commentId - Comment ID from getPostComments
|
|
924
|
+
* @param {string} options.message - Reply message text
|
|
925
|
+
* @returns {Promise<Object>} Reply result
|
|
926
|
+
*/
|
|
927
|
+
async replyToComment(options) {
|
|
928
|
+
return this._request('/uploadposts/comments/reply', 'POST', {
|
|
929
|
+
platform: 'instagram',
|
|
930
|
+
user: options.user,
|
|
931
|
+
comment_id: options.commentId,
|
|
932
|
+
message: options.message
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Post a public reply to an Instagram comment (visible under the original comment)
|
|
938
|
+
*
|
|
939
|
+
* @param {Object} options - Reply options
|
|
940
|
+
* @param {string} options.user - Profile username
|
|
941
|
+
* @param {string} options.commentId - Comment ID from getPostComments
|
|
942
|
+
* @param {string} options.message - Reply message text
|
|
943
|
+
* @returns {Promise<Object>} Reply result with the new comment ID
|
|
944
|
+
*/
|
|
945
|
+
async publicReplyToComment(options) {
|
|
946
|
+
return this._request('/uploadposts/comments/public-reply', 'POST', {
|
|
947
|
+
platform: 'instagram',
|
|
948
|
+
user: options.user,
|
|
949
|
+
comment_id: options.commentId,
|
|
950
|
+
message: options.message
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
724
954
|
// ==================== Helper Endpoints ====================
|
|
725
955
|
|
|
726
956
|
/**
|
|
@@ -755,6 +985,30 @@ export class UploadPost {
|
|
|
755
985
|
const params = profile ? { profile } : {};
|
|
756
986
|
return this._request('/uploadposts/pinterest/boards', 'GET', params);
|
|
757
987
|
}
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Get Google Business Profile locations for a connected account
|
|
991
|
+
*
|
|
992
|
+
* @param {string} [profile] - Profile username
|
|
993
|
+
* @returns {Promise<Object>} List of Google Business locations
|
|
994
|
+
*/
|
|
995
|
+
async getGoogleBusinessLocations(profile) {
|
|
996
|
+
const params = profile ? { profile } : {};
|
|
997
|
+
return this._request('/uploadposts/google-business/locations', 'GET', params);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Select a specific Google Business Profile location for a profile
|
|
1002
|
+
*
|
|
1003
|
+
* @param {string} locationId - The location ID to select (e.g. "accounts/123/locations/456")
|
|
1004
|
+
* @param {string} [profile] - Profile username
|
|
1005
|
+
* @returns {Promise<Object>} Selection result with google_business_id and display_name
|
|
1006
|
+
*/
|
|
1007
|
+
async selectGoogleBusinessLocation(locationId, profile) {
|
|
1008
|
+
const data = { location_id: locationId };
|
|
1009
|
+
if (profile) data.profile = profile;
|
|
1010
|
+
return this._request('/uploadposts/google-business/locations/select', 'POST', data);
|
|
1011
|
+
}
|
|
758
1012
|
}
|
|
759
1013
|
|
|
760
1014
|
// Default export for CommonJS compatibility
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upload-post",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Official client library for Upload-Post API - Cross-platform social media upload for TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, and X (Twitter)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|