upload-post 2.0.3 → 2.1.0
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 +24 -2
- package/README.md +15 -1
- package/index.d.ts +93 -8
- package/index.js +169 -25
- package/package.json +1 -1
|
@@ -3,6 +3,9 @@ name: Publish to npm
|
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
5
5
|
types: [published]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
paths: ['package.json']
|
|
6
9
|
workflow_dispatch:
|
|
7
10
|
inputs:
|
|
8
11
|
dry_run:
|
|
@@ -16,7 +19,7 @@ jobs:
|
|
|
16
19
|
runs-on: ubuntu-latest
|
|
17
20
|
permissions:
|
|
18
21
|
contents: read
|
|
19
|
-
id-token: write
|
|
22
|
+
id-token: write
|
|
20
23
|
steps:
|
|
21
24
|
- name: Checkout
|
|
22
25
|
uses: actions/checkout@v4
|
|
@@ -27,13 +30,32 @@ jobs:
|
|
|
27
30
|
node-version: '22'
|
|
28
31
|
registry-url: 'https://registry.npmjs.org'
|
|
29
32
|
|
|
33
|
+
- name: Check if version is new
|
|
34
|
+
id: version_check
|
|
35
|
+
run: |
|
|
36
|
+
LOCAL=$(node -p "require('./package.json').version")
|
|
37
|
+
PKG_NAME=$(node -p "require('./package.json').name")
|
|
38
|
+
REMOTE=$(npm view "$PKG_NAME" version 2>/dev/null || echo "0.0.0")
|
|
39
|
+
echo "local=$LOCAL" >> $GITHUB_OUTPUT
|
|
40
|
+
echo "remote=$REMOTE" >> $GITHUB_OUTPUT
|
|
41
|
+
if [ "$LOCAL" = "$REMOTE" ]; then
|
|
42
|
+
echo "new=false" >> $GITHUB_OUTPUT
|
|
43
|
+
echo "⏭️ Version $LOCAL already published, skipping."
|
|
44
|
+
else
|
|
45
|
+
echo "new=true" >> $GITHUB_OUTPUT
|
|
46
|
+
echo "🚀 New version detected: $REMOTE → $LOCAL"
|
|
47
|
+
fi
|
|
48
|
+
|
|
30
49
|
- name: Update npm to latest
|
|
50
|
+
if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
|
|
31
51
|
run: npm install -g npm@latest && npm --version
|
|
32
52
|
|
|
33
53
|
- name: Install dependencies
|
|
54
|
+
if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
|
|
34
55
|
run: npm ci
|
|
35
56
|
|
|
36
57
|
- name: Build (if needed)
|
|
58
|
+
if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
|
|
37
59
|
run: npm run build --if-present
|
|
38
60
|
|
|
39
61
|
- name: Publish to npm (dry-run)
|
|
@@ -41,5 +63,5 @@ jobs:
|
|
|
41
63
|
run: npm publish --access public --dry-run
|
|
42
64
|
|
|
43
65
|
- name: Publish to npm
|
|
44
|
-
if:
|
|
66
|
+
if: (steps.version_check.outputs.new == 'true' && inputs.dry_run != true) || github.event_name == 'release'
|
|
45
67
|
run: npm publish --access public --provenance
|
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
|
|
@@ -238,7 +245,8 @@ const boards = await client.getPinterestBoards('my-profile');
|
|
|
238
245
|
### Facebook
|
|
239
246
|
- `facebookPageId` - Facebook Page ID (required)
|
|
240
247
|
- `facebookVideoState` - PUBLISHED, DRAFT
|
|
241
|
-
- `facebookMediaType` - REELS, STORIES
|
|
248
|
+
- `facebookMediaType` - REELS, STORIES, VIDEO (VIDEO for normal page videos with no 9:16 restriction)
|
|
249
|
+
- `thumbnailUrl` - URL for custom video thumbnail (only when facebookMediaType is VIDEO)
|
|
242
250
|
- `facebookLinkUrl` - URL for text posts
|
|
243
251
|
|
|
244
252
|
### Pinterest
|
|
@@ -261,9 +269,12 @@ const boards = await client.getPinterestBoards('my-profile');
|
|
|
261
269
|
- `xShareWithFollowers` - Share community post with followers
|
|
262
270
|
- `xCardUri` - Card URI for Twitter Cards
|
|
263
271
|
- `xLongTextAsPost` - Post long text as single post
|
|
272
|
+
- `xThreadImageLayout` - Comma-separated image layout for thread (e.g. "4,4" or "2,3,1")
|
|
264
273
|
|
|
265
274
|
### Threads
|
|
266
275
|
- `threadsLongTextAsPost` - Post long text as single post (vs thread)
|
|
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.
|
|
267
278
|
|
|
268
279
|
### Reddit
|
|
269
280
|
- `redditSubreddit` - Subreddit name (without r/)
|
|
@@ -283,6 +294,7 @@ These options work across all upload methods:
|
|
|
283
294
|
| `scheduledDate` | ISO date for scheduling |
|
|
284
295
|
| `timezone` | Timezone for scheduled date |
|
|
285
296
|
| `addToQueue` | Add to posting queue |
|
|
297
|
+
| `maxPostsPerSlot` | Max posts per queue slot (overrides profile setting) |
|
|
286
298
|
| `asyncUpload` | Process asynchronously (default: true) |
|
|
287
299
|
|
|
288
300
|
## TypeScript Support
|
|
@@ -324,3 +336,5 @@ try {
|
|
|
324
336
|
## License
|
|
325
337
|
|
|
326
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';
|
|
@@ -35,7 +35,7 @@ declare module 'upload-post' {
|
|
|
35
35
|
export type FacebookVideoState = 'PUBLISHED' | 'DRAFT';
|
|
36
36
|
|
|
37
37
|
/** Facebook media type */
|
|
38
|
-
export type FacebookMediaType = 'REELS' | 'STORIES';
|
|
38
|
+
export type FacebookMediaType = 'REELS' | 'STORIES' | 'VIDEO';
|
|
39
39
|
|
|
40
40
|
/** X (Twitter) reply settings */
|
|
41
41
|
export type XReplySettings = 'everyone' | 'following' | 'mentionedUsers' | 'subscribers' | 'verified';
|
|
@@ -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
|
|
|
@@ -210,6 +212,8 @@ declare module 'upload-post' {
|
|
|
210
212
|
facebookVideoState?: FacebookVideoState;
|
|
211
213
|
/** Media type */
|
|
212
214
|
facebookMediaType?: FacebookMediaType;
|
|
215
|
+
/** Thumbnail URL for normal page videos (only when facebookMediaType is 'VIDEO') */
|
|
216
|
+
thumbnailUrl?: string;
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
export interface FacebookPhotoOptions {
|
|
@@ -276,6 +280,8 @@ declare module 'upload-post' {
|
|
|
276
280
|
xTaggedUserIds?: string | string[];
|
|
277
281
|
/** Location place ID */
|
|
278
282
|
xPlaceId?: string;
|
|
283
|
+
/** Comma-separated image layout for thread (e.g. "4,4" or "2,3,1"). Each value 1-4, total must equal image count. */
|
|
284
|
+
xThreadImageLayout?: string;
|
|
279
285
|
}
|
|
280
286
|
|
|
281
287
|
export interface XTextOptions extends XBaseOptions {
|
|
@@ -298,6 +304,8 @@ declare module 'upload-post' {
|
|
|
298
304
|
export interface ThreadsOptions {
|
|
299
305
|
/** Post long text as single post (vs thread) */
|
|
300
306
|
threadsLongTextAsPost?: boolean;
|
|
307
|
+
/** Comma-separated list of how many media items per Threads post (e.g. "5,5"). Each value 1-10, total must equal file count. */
|
|
308
|
+
threadsThreadMediaLayout?: string;
|
|
301
309
|
}
|
|
302
310
|
|
|
303
311
|
// ==================== Reddit Options ====================
|
|
@@ -337,12 +345,22 @@ declare module 'upload-post' {
|
|
|
337
345
|
platforms: PhotoPlatform[];
|
|
338
346
|
}
|
|
339
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
|
+
|
|
340
357
|
export interface UploadTextOptions extends CommonUploadOptions,
|
|
341
358
|
LinkedInOptions,
|
|
342
359
|
FacebookTextOptions,
|
|
343
360
|
XTextOptions,
|
|
344
361
|
ThreadsOptions,
|
|
345
|
-
RedditOptions
|
|
362
|
+
RedditOptions,
|
|
363
|
+
LinkPreviewOptions {
|
|
346
364
|
/** Target platforms */
|
|
347
365
|
platforms: TextPlatform[];
|
|
348
366
|
}
|
|
@@ -364,6 +382,8 @@ declare module 'upload-post' {
|
|
|
364
382
|
timezone?: string;
|
|
365
383
|
/** Add to posting queue */
|
|
366
384
|
addToQueue?: boolean;
|
|
385
|
+
/** Maximum number of posts allowed per queue slot (overrides profile setting). Only used when addToQueue is true. */
|
|
386
|
+
maxPostsPerSlot?: number;
|
|
367
387
|
/** Process upload asynchronously */
|
|
368
388
|
asyncUpload?: boolean;
|
|
369
389
|
}
|
|
@@ -495,6 +515,12 @@ declare module 'upload-post' {
|
|
|
495
515
|
*/
|
|
496
516
|
getStatus(requestId: string): Promise<StatusResponse>;
|
|
497
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
|
+
|
|
498
524
|
/**
|
|
499
525
|
* Get upload history
|
|
500
526
|
* @param options - Query options
|
|
@@ -506,7 +532,62 @@ declare module 'upload-post' {
|
|
|
506
532
|
* @param profileUsername - Profile username
|
|
507
533
|
* @param options - Query options
|
|
508
534
|
*/
|
|
509
|
-
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
|
+
}>>;
|
|
510
591
|
|
|
511
592
|
// Scheduled Posts
|
|
512
593
|
/**
|
|
@@ -555,6 +636,10 @@ declare module 'upload-post' {
|
|
|
555
636
|
logoImage?: string;
|
|
556
637
|
redirectButtonText?: string;
|
|
557
638
|
platforms?: string[];
|
|
639
|
+
showCalendar?: boolean;
|
|
640
|
+
readonlyCalendar?: boolean;
|
|
641
|
+
connectTitle?: string;
|
|
642
|
+
connectDescription?: string;
|
|
558
643
|
}): Promise<JwtResponse>;
|
|
559
644
|
|
|
560
645
|
/**
|
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
|
/**
|
|
@@ -175,9 +195,12 @@ export class UploadPost {
|
|
|
175
195
|
* Add LinkedIn-specific parameters
|
|
176
196
|
* @private
|
|
177
197
|
*/
|
|
178
|
-
_addLinkedinParams(form, options) {
|
|
198
|
+
_addLinkedinParams(form, options, isText = false) {
|
|
179
199
|
if (options.linkedinVisibility) form.append('visibility', options.linkedinVisibility);
|
|
180
200
|
if (options.targetLinkedinPageId) form.append('target_linkedin_page_id', options.targetLinkedinPageId);
|
|
201
|
+
if (isText && (options.linkedinLinkUrl || options.linkUrl)) {
|
|
202
|
+
form.append('linkedin_link_url', options.linkedinLinkUrl || options.linkUrl);
|
|
203
|
+
}
|
|
181
204
|
}
|
|
182
205
|
|
|
183
206
|
/**
|
|
@@ -190,6 +213,7 @@ export class UploadPost {
|
|
|
190
213
|
if (isVideo) {
|
|
191
214
|
if (options.facebookVideoState) form.append('video_state', options.facebookVideoState);
|
|
192
215
|
if (options.facebookMediaType) form.append('facebook_media_type', options.facebookMediaType);
|
|
216
|
+
if (options.thumbnailUrl) form.append('thumbnail_url', options.thumbnailUrl);
|
|
193
217
|
}
|
|
194
218
|
|
|
195
219
|
if (isText && options.facebookLinkUrl) {
|
|
@@ -235,6 +259,7 @@ export class UploadPost {
|
|
|
235
259
|
ids.forEach(id => form.append('tagged_user_ids[]', id));
|
|
236
260
|
}
|
|
237
261
|
if (options.xPlaceId) form.append('place_id', options.xPlaceId);
|
|
262
|
+
if (options.xThreadImageLayout) form.append('x_thread_image_layout', options.xThreadImageLayout);
|
|
238
263
|
} else {
|
|
239
264
|
if (options.xPostUrl) form.append('post_url', options.xPostUrl);
|
|
240
265
|
if (options.xCardUri) form.append('card_uri', options.xCardUri);
|
|
@@ -255,15 +280,20 @@ export class UploadPost {
|
|
|
255
280
|
*/
|
|
256
281
|
_addThreadsParams(form, options) {
|
|
257
282
|
if (options.threadsLongTextAsPost !== undefined) form.append('threads_long_text_as_post', String(options.threadsLongTextAsPost));
|
|
283
|
+
if (options.threadsThreadMediaLayout) form.append('threads_thread_media_layout', options.threadsThreadMediaLayout);
|
|
284
|
+
if (options.threadsTopicTag) form.append('threads_topic_tag', options.threadsTopicTag);
|
|
258
285
|
}
|
|
259
286
|
|
|
260
287
|
/**
|
|
261
288
|
* Add Reddit-specific parameters
|
|
262
289
|
* @private
|
|
263
290
|
*/
|
|
264
|
-
_addRedditParams(form, options) {
|
|
291
|
+
_addRedditParams(form, options, isText = false) {
|
|
265
292
|
if (options.redditSubreddit) form.append('subreddit', options.redditSubreddit);
|
|
266
293
|
if (options.redditFlairId) form.append('flair_id', options.redditFlairId);
|
|
294
|
+
if (isText && (options.redditLinkUrl || options.linkUrl)) {
|
|
295
|
+
form.append('reddit_link_url', options.redditLinkUrl || options.linkUrl);
|
|
296
|
+
}
|
|
267
297
|
}
|
|
268
298
|
|
|
269
299
|
/**
|
|
@@ -326,8 +356,9 @@ export class UploadPost {
|
|
|
326
356
|
* Facebook options:
|
|
327
357
|
* @param {string} [options.facebookPageId] - Facebook Page ID
|
|
328
358
|
* @param {string} [options.facebookVideoState] - PUBLISHED or DRAFT
|
|
329
|
-
* @param {string} [options.facebookMediaType] - REELS or
|
|
330
|
-
*
|
|
359
|
+
* @param {string} [options.facebookMediaType] - REELS, STORIES, or VIDEO
|
|
360
|
+
* @param {string} [options.thumbnailUrl] - Thumbnail URL for normal page videos (VIDEO type only)
|
|
361
|
+
*
|
|
331
362
|
* Pinterest options:
|
|
332
363
|
* @param {string} [options.pinterestBoardId] - Board ID
|
|
333
364
|
* @param {string} [options.pinterestLink] - Destination link
|
|
@@ -347,14 +378,16 @@ export class UploadPost {
|
|
|
347
378
|
*
|
|
348
379
|
* Threads options:
|
|
349
380
|
* @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post (vs thread)
|
|
350
|
-
*
|
|
381
|
+
* @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
|
|
382
|
+
* @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
|
|
383
|
+
*
|
|
351
384
|
* @returns {Promise<Object>} API response with request_id for async uploads
|
|
352
385
|
*/
|
|
353
386
|
async upload(videoPathOrUrl, options) {
|
|
354
387
|
const form = new FormData();
|
|
355
388
|
|
|
356
389
|
// Handle video (file path or URL)
|
|
357
|
-
if (videoPathOrUrl.toLowerCase().startsWith('http://') || videoPathOrUrl.toLowerCase().startsWith('https://')) {
|
|
390
|
+
if (typeof videoPathOrUrl === 'string' && (videoPathOrUrl.toLowerCase().startsWith('http://') || videoPathOrUrl.toLowerCase().startsWith('https://'))) {
|
|
358
391
|
form.append('video', videoPathOrUrl);
|
|
359
392
|
} else {
|
|
360
393
|
if (!fs.existsSync(videoPathOrUrl)) {
|
|
@@ -424,14 +457,16 @@ export class UploadPost {
|
|
|
424
457
|
* @param {boolean} [options.xNullcast] - Promoted-only post
|
|
425
458
|
* @param {string|string[]} [options.xTaggedUserIds] - User IDs to tag
|
|
426
459
|
* @param {boolean} [options.xLongTextAsPost] - Post long text as single post
|
|
427
|
-
*
|
|
460
|
+
*
|
|
428
461
|
* Threads options:
|
|
429
462
|
* @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post
|
|
430
|
-
*
|
|
463
|
+
* @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
|
|
464
|
+
* @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
|
|
465
|
+
*
|
|
431
466
|
* Reddit options:
|
|
432
467
|
* @param {string} [options.redditSubreddit] - Subreddit name (without r/)
|
|
433
468
|
* @param {string} [options.redditFlairId] - Flair template ID
|
|
434
|
-
*
|
|
469
|
+
*
|
|
435
470
|
* @returns {Promise<Object>} API response
|
|
436
471
|
*/
|
|
437
472
|
async uploadPhotos(photosPathsOrUrls, options) {
|
|
@@ -476,13 +511,18 @@ export class UploadPost {
|
|
|
476
511
|
* @param {string} [options.timezone] - Timezone for scheduled date
|
|
477
512
|
* @param {boolean} [options.addToQueue] - Add to posting queue
|
|
478
513
|
* @param {boolean} [options.asyncUpload=true] - Process upload asynchronously
|
|
479
|
-
*
|
|
514
|
+
* @param {string} [options.linkUrl] - Generic URL for link preview card (works for LinkedIn, Bluesky, Facebook). Platform-specific params take priority.
|
|
515
|
+
*
|
|
480
516
|
* LinkedIn options:
|
|
481
517
|
* @param {string} [options.targetLinkedinPageId] - Page ID for organization posts
|
|
482
|
-
*
|
|
518
|
+
* @param {string} [options.linkedinLinkUrl] - URL to attach as link preview on LinkedIn
|
|
519
|
+
*
|
|
520
|
+
* Bluesky options:
|
|
521
|
+
* @param {string} [options.blueskyLinkUrl] - URL to attach as external embed link preview on Bluesky
|
|
522
|
+
*
|
|
483
523
|
* Facebook options:
|
|
484
524
|
* @param {string} [options.facebookPageId] - Facebook Page ID
|
|
485
|
-
* @param {string} [options.facebookLinkUrl] - URL to attach as link preview
|
|
525
|
+
* @param {string} [options.facebookLinkUrl] - URL to attach as link preview on Facebook
|
|
486
526
|
*
|
|
487
527
|
* X (Twitter) options:
|
|
488
528
|
* @param {string} [options.xReplySettings] - Who can reply
|
|
@@ -493,27 +533,35 @@ export class UploadPost {
|
|
|
493
533
|
* @param {string} [options.xPollReplySettings] - Who can reply to poll
|
|
494
534
|
* @param {string} [options.xCardUri] - Card URI for Twitter Cards
|
|
495
535
|
* @param {boolean} [options.xLongTextAsPost] - Post long text as single post
|
|
496
|
-
*
|
|
536
|
+
*
|
|
497
537
|
* Threads options:
|
|
498
538
|
* @param {boolean} [options.threadsLongTextAsPost] - Post long text as single post
|
|
499
|
-
*
|
|
539
|
+
* @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
|
|
540
|
+
* @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
|
|
541
|
+
*
|
|
500
542
|
* Reddit options:
|
|
501
543
|
* @param {string} [options.redditSubreddit] - Subreddit name (without r/)
|
|
502
544
|
* @param {string} [options.redditFlairId] - Flair template ID
|
|
503
|
-
*
|
|
545
|
+
*
|
|
504
546
|
* @returns {Promise<Object>} API response
|
|
505
547
|
*/
|
|
506
548
|
async uploadText(options) {
|
|
507
549
|
const form = new FormData();
|
|
508
|
-
|
|
550
|
+
|
|
509
551
|
this._addCommonParams(form, options);
|
|
510
|
-
|
|
552
|
+
|
|
553
|
+
// Generic link_url support
|
|
554
|
+
if (options.linkUrl) form.append('link_url', options.linkUrl);
|
|
555
|
+
|
|
511
556
|
const platforms = Array.isArray(options.platforms) ? options.platforms : [options.platforms];
|
|
512
|
-
if (platforms.includes('linkedin')) this._addLinkedinParams(form, options);
|
|
557
|
+
if (platforms.includes('linkedin')) this._addLinkedinParams(form, options, true);
|
|
513
558
|
if (platforms.includes('facebook')) this._addFacebookParams(form, options, false, true);
|
|
514
559
|
if (platforms.includes('x')) this._addXParams(form, options, true);
|
|
515
560
|
if (platforms.includes('threads')) this._addThreadsParams(form, options);
|
|
516
|
-
if (platforms.includes('reddit')) this._addRedditParams(form, options);
|
|
561
|
+
if (platforms.includes('reddit')) this._addRedditParams(form, options, true);
|
|
562
|
+
if (platforms.includes('bluesky') && (options.blueskyLinkUrl || options.linkUrl)) {
|
|
563
|
+
form.append('bluesky_link_url', options.blueskyLinkUrl || options.linkUrl);
|
|
564
|
+
}
|
|
517
565
|
|
|
518
566
|
return this._request('/upload_text', 'POST', form, true);
|
|
519
567
|
}
|
|
@@ -539,7 +587,7 @@ export class UploadPost {
|
|
|
539
587
|
const form = new FormData();
|
|
540
588
|
|
|
541
589
|
// Handle document (file path or URL)
|
|
542
|
-
if (documentPathOrUrl.toLowerCase().startsWith('http://') || documentPathOrUrl.toLowerCase().startsWith('https://')) {
|
|
590
|
+
if (typeof documentPathOrUrl === 'string' && (documentPathOrUrl.toLowerCase().startsWith('http://') || documentPathOrUrl.toLowerCase().startsWith('https://'))) {
|
|
543
591
|
form.append('document', documentPathOrUrl);
|
|
544
592
|
} else {
|
|
545
593
|
if (!fs.existsSync(documentPathOrUrl)) {
|
|
@@ -557,6 +605,7 @@ export class UploadPost {
|
|
|
557
605
|
if (options.scheduledDate) form.append('scheduled_date', options.scheduledDate);
|
|
558
606
|
if (options.timezone) form.append('timezone', options.timezone);
|
|
559
607
|
if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
|
|
608
|
+
if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
|
|
560
609
|
if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
|
|
561
610
|
|
|
562
611
|
this._addLinkedinParams(form, options);
|
|
@@ -576,6 +625,16 @@ export class UploadPost {
|
|
|
576
625
|
return this._request('/uploadposts/status', 'GET', { request_id: requestId });
|
|
577
626
|
}
|
|
578
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Get the status of a scheduled or queued upload by job ID
|
|
630
|
+
*
|
|
631
|
+
* @param {string} jobId - The job_id from a scheduled or queued upload
|
|
632
|
+
* @returns {Promise<Object>} Upload status
|
|
633
|
+
*/
|
|
634
|
+
async getJobStatus(jobId) {
|
|
635
|
+
return this._request('/uploadposts/status', 'GET', { job_id: jobId });
|
|
636
|
+
}
|
|
637
|
+
|
|
579
638
|
/**
|
|
580
639
|
* Get upload history
|
|
581
640
|
*
|
|
@@ -593,20 +652,73 @@ export class UploadPost {
|
|
|
593
652
|
|
|
594
653
|
/**
|
|
595
654
|
* Get analytics for a profile
|
|
596
|
-
*
|
|
655
|
+
*
|
|
597
656
|
* @param {string} profileUsername - Profile username
|
|
598
657
|
* @param {Object} [options] - Query options
|
|
599
|
-
* @param {string[]} [options.platforms] - Filter by platforms (instagram, linkedin, facebook, x)
|
|
600
|
-
* @
|
|
658
|
+
* @param {string[]} [options.platforms] - Filter by platforms (instagram, linkedin, facebook, x, youtube, tiktok, threads, pinterest, reddit)
|
|
659
|
+
* @param {string} [options.pageId] - Facebook Page ID (required for Facebook analytics)
|
|
660
|
+
* @param {string} [options.pageUrn] - LinkedIn page URN (defaults to "me" for personal profile)
|
|
661
|
+
* @returns {Promise<Object>} Analytics data per platform
|
|
601
662
|
*/
|
|
602
663
|
async getAnalytics(profileUsername, options = {}) {
|
|
603
664
|
const params = {};
|
|
604
665
|
if (options.platforms && options.platforms.length > 0) {
|
|
605
666
|
params.platforms = options.platforms.join(',');
|
|
606
667
|
}
|
|
668
|
+
if (options.pageId) params.page_id = options.pageId;
|
|
669
|
+
if (options.pageUrn) params.page_urn = options.pageUrn;
|
|
607
670
|
return this._request(`/analytics/${encodeURIComponent(profileUsername)}`, 'GET', params);
|
|
608
671
|
}
|
|
609
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Get total impressions for a profile from daily snapshots
|
|
675
|
+
*
|
|
676
|
+
* @param {string} profileUsername - Profile username
|
|
677
|
+
* @param {Object} [options] - Query options
|
|
678
|
+
* @param {string} [options.period] - Period shortcut: last_day, last_week, last_month, last_3months, last_year
|
|
679
|
+
* @param {string} [options.startDate] - Start date in YYYY-MM-DD format
|
|
680
|
+
* @param {string} [options.endDate] - End date in YYYY-MM-DD format
|
|
681
|
+
* @param {string} [options.date] - Single date in YYYY-MM-DD format
|
|
682
|
+
* @param {string[]} [options.platforms] - Filter by platforms
|
|
683
|
+
* @param {boolean} [options.breakdown] - Include per-platform and per-day breakdown
|
|
684
|
+
* @param {string[]} [options.metrics] - Specific metrics to aggregate (e.g., ['likes', 'comments', 'shares'])
|
|
685
|
+
* @returns {Promise<Object>} Total impressions data
|
|
686
|
+
*/
|
|
687
|
+
async getTotalImpressions(profileUsername, options = {}) {
|
|
688
|
+
const params = {};
|
|
689
|
+
if (options.period) params.period = options.period;
|
|
690
|
+
if (options.startDate) params.start_date = options.startDate;
|
|
691
|
+
if (options.endDate) params.end_date = options.endDate;
|
|
692
|
+
if (options.date) params.date = options.date;
|
|
693
|
+
if (options.platforms && options.platforms.length > 0) {
|
|
694
|
+
params.platform = options.platforms.join(',');
|
|
695
|
+
}
|
|
696
|
+
if (options.breakdown) params.breakdown = 'true';
|
|
697
|
+
if (options.metrics && options.metrics.length > 0) {
|
|
698
|
+
params.metrics = options.metrics.join(',');
|
|
699
|
+
}
|
|
700
|
+
return this._request(`/uploadposts/total-impressions/${encodeURIComponent(profileUsername)}`, 'GET', params);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Get analytics for a specific post across all platforms it was published to
|
|
705
|
+
*
|
|
706
|
+
* @param {string} requestId - The request_id from the upload
|
|
707
|
+
* @returns {Promise<Object>} Post analytics with per-platform metrics
|
|
708
|
+
*/
|
|
709
|
+
async getPostAnalytics(requestId) {
|
|
710
|
+
return this._request(`/uploadposts/post-analytics/${encodeURIComponent(requestId)}`, 'GET');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Get available metrics configuration for all supported platforms
|
|
715
|
+
*
|
|
716
|
+
* @returns {Promise<Object>} Platform metrics config (primary fields, available metrics, labels)
|
|
717
|
+
*/
|
|
718
|
+
async getPlatformMetrics() {
|
|
719
|
+
return this._request('/uploadposts/platform-metrics', 'GET');
|
|
720
|
+
}
|
|
721
|
+
|
|
610
722
|
// ==================== Scheduled Posts ====================
|
|
611
723
|
|
|
612
724
|
/**
|
|
@@ -685,6 +797,10 @@ export class UploadPost {
|
|
|
685
797
|
* @param {string} [options.logoImage] - Logo image URL for the linking page
|
|
686
798
|
* @param {string} [options.redirectButtonText] - Text for redirect button
|
|
687
799
|
* @param {string[]} [options.platforms] - Platforms to show for connection
|
|
800
|
+
* @param {boolean} [options.showCalendar] - Whether to show the calendar view
|
|
801
|
+
* @param {boolean} [options.readonlyCalendar] - Show only a read-only calendar (no editing, no account connection)
|
|
802
|
+
* @param {string} [options.connectTitle] - Custom title for the connection page
|
|
803
|
+
* @param {string} [options.connectDescription] - Custom description for the connection page
|
|
688
804
|
* @returns {Promise<Object>} JWT and connection URL
|
|
689
805
|
*/
|
|
690
806
|
async generateJwt(username, options = {}) {
|
|
@@ -693,6 +809,10 @@ export class UploadPost {
|
|
|
693
809
|
if (options.logoImage) body.logo_image = options.logoImage;
|
|
694
810
|
if (options.redirectButtonText) body.redirect_button_text = options.redirectButtonText;
|
|
695
811
|
if (options.platforms && options.platforms.length > 0) body.platforms = options.platforms;
|
|
812
|
+
if (options.showCalendar !== undefined) body.show_calendar = options.showCalendar;
|
|
813
|
+
if (options.readonlyCalendar !== undefined) body.readonly_calendar = options.readonlyCalendar;
|
|
814
|
+
if (options.connectTitle) body.connect_title = options.connectTitle;
|
|
815
|
+
if (options.connectDescription) body.connect_description = options.connectDescription;
|
|
696
816
|
return this._request('/uploadposts/users/generate-jwt', 'POST', body);
|
|
697
817
|
}
|
|
698
818
|
|
|
@@ -740,6 +860,30 @@ export class UploadPost {
|
|
|
740
860
|
const params = profile ? { profile } : {};
|
|
741
861
|
return this._request('/uploadposts/pinterest/boards', 'GET', params);
|
|
742
862
|
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Get Google Business Profile locations for a connected account
|
|
866
|
+
*
|
|
867
|
+
* @param {string} [profile] - Profile username
|
|
868
|
+
* @returns {Promise<Object>} List of Google Business locations
|
|
869
|
+
*/
|
|
870
|
+
async getGoogleBusinessLocations(profile) {
|
|
871
|
+
const params = profile ? { profile } : {};
|
|
872
|
+
return this._request('/uploadposts/google-business/locations', 'GET', params);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Select a specific Google Business Profile location for a profile
|
|
877
|
+
*
|
|
878
|
+
* @param {string} locationId - The location ID to select (e.g. "accounts/123/locations/456")
|
|
879
|
+
* @param {string} [profile] - Profile username
|
|
880
|
+
* @returns {Promise<Object>} Selection result with google_business_id and display_name
|
|
881
|
+
*/
|
|
882
|
+
async selectGoogleBusinessLocation(locationId, profile) {
|
|
883
|
+
const data = { location_id: locationId };
|
|
884
|
+
if (profile) data.profile = profile;
|
|
885
|
+
return this._request('/uploadposts/google-business/locations/select', 'POST', data);
|
|
886
|
+
}
|
|
743
887
|
}
|
|
744
888
|
|
|
745
889
|
// Default export for CommonJS compatibility
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upload-post",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
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",
|