upload-post 2.1.2 → 2.4.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 +19 -8
- package/README.md +50 -4
- package/index.d.ts +77 -4
- package/index.js +87 -14
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
permissions:
|
|
21
21
|
contents: read
|
|
22
|
-
id-token: write
|
|
22
|
+
id-token: write # mints the OIDC token npm trades for publish rights
|
|
23
23
|
steps:
|
|
24
24
|
- name: Checkout
|
|
25
25
|
uses: actions/checkout@v4
|
|
@@ -27,9 +27,22 @@ jobs:
|
|
|
27
27
|
- name: Setup Node.js
|
|
28
28
|
uses: actions/setup-node@v4
|
|
29
29
|
with:
|
|
30
|
-
node-version: '
|
|
30
|
+
node-version: '24'
|
|
31
31
|
registry-url: 'https://registry.npmjs.org'
|
|
32
32
|
|
|
33
|
+
# Trusted publishing needs npm >= 11.5.1, which Node 24 already bundles.
|
|
34
|
+
# Do NOT `npm install -g npm@latest`: it leaves npm's own tree short of
|
|
35
|
+
# `sigstore`, and `--provenance` then dies on it.
|
|
36
|
+
- name: Check npm supports trusted publishing
|
|
37
|
+
run: |
|
|
38
|
+
NPM_VERSION=$(npm --version)
|
|
39
|
+
echo "npm $NPM_VERSION"
|
|
40
|
+
MIN=11.5.1
|
|
41
|
+
if [ "$(printf '%s\n%s\n' "$MIN" "$NPM_VERSION" | sort -V | head -1)" != "$MIN" ]; then
|
|
42
|
+
echo "::error::npm $NPM_VERSION is older than $MIN, which trusted publishing requires"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
33
46
|
- name: Check if version is new
|
|
34
47
|
id: version_check
|
|
35
48
|
run: |
|
|
@@ -54,14 +67,12 @@ jobs:
|
|
|
54
67
|
if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
|
|
55
68
|
run: npm run build --if-present
|
|
56
69
|
|
|
70
|
+
# No NODE_AUTH_TOKEN: npm authenticates via the OIDC token, and the
|
|
71
|
+
# trusted publisher is configured on npmjs.com against this repo+workflow.
|
|
57
72
|
- name: Publish to npm (dry-run)
|
|
58
73
|
if: ${{ inputs.dry_run == true }}
|
|
59
|
-
run: npm publish --access public --dry-run
|
|
60
|
-
env:
|
|
61
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
74
|
+
run: npm publish --provenance --access public --dry-run
|
|
62
75
|
|
|
63
76
|
- name: Publish to npm
|
|
64
77
|
if: (steps.version_check.outputs.new == 'true' && inputs.dry_run != true) || github.event_name == 'release'
|
|
65
|
-
run: npm publish --access public
|
|
66
|
-
env:
|
|
67
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
78
|
+
run: npm publish --provenance --access public
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Official Node.js client for the [Upload-Post API](https://www.upload-post.com) - Cross-platform social media upload.
|
|
4
4
|
|
|
5
|
-
Upload videos, photos, text posts, and documents to **TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky,
|
|
5
|
+
Upload videos, photos, text posts, and documents to **TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, X (Twitter), Discord, and Telegram** with a single API.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -29,9 +29,9 @@ console.log(response);
|
|
|
29
29
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
|
-
- ✅ **Video Upload** - TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Bluesky, X
|
|
33
|
-
- ✅ **Photo Upload** - TikTok, Instagram, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, X
|
|
34
|
-
- ✅ **Text Posts** - X, LinkedIn, Facebook, Threads, Reddit, Bluesky
|
|
32
|
+
- ✅ **Video Upload** - TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Bluesky, X, Discord, Telegram
|
|
33
|
+
- ✅ **Photo Upload** - TikTok, Instagram, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, X, Discord, Telegram
|
|
34
|
+
- ✅ **Text Posts** - X, LinkedIn, Facebook, Threads, Reddit, Bluesky, Discord, Telegram
|
|
35
35
|
- ✅ **Document Upload** - LinkedIn (PDF, PPT, PPTX, DOC, DOCX)
|
|
36
36
|
- ✅ **Scheduling** - Schedule posts for later
|
|
37
37
|
- ✅ **Posting Queue** - Add posts to your configured queue
|
|
@@ -169,6 +169,10 @@ await client.deleteUser('old-profile');
|
|
|
169
169
|
const jwt = await client.generateJwt('my-profile', {
|
|
170
170
|
redirectUrl: 'https://yourapp.com/callback',
|
|
171
171
|
platforms: ['tiktok', 'instagram'],
|
|
172
|
+
// Optional: force the connection page language for this profile.
|
|
173
|
+
// Supported: 'en' | 'es' | 'de' | 'fr' | 'pt'. When omitted, the page
|
|
174
|
+
// auto-detects the visitor's browser language and falls back to English.
|
|
175
|
+
language: 'es',
|
|
172
176
|
});
|
|
173
177
|
```
|
|
174
178
|
|
|
@@ -181,6 +185,22 @@ const analytics = await client.getAnalytics('my-profile', {
|
|
|
181
185
|
console.log(analytics);
|
|
182
186
|
```
|
|
183
187
|
|
|
188
|
+
### Get Media
|
|
189
|
+
|
|
190
|
+
Retrieve recent posts from a connected social account. Supported platforms:
|
|
191
|
+
`instagram`, `tiktok`, `youtube`, `linkedin`, `facebook`, `x`, `threads`,
|
|
192
|
+
`pinterest`, `bluesky`, `reddit`.
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const { media } = await client.getMedia('linkedin', 'my-profile');
|
|
196
|
+
|
|
197
|
+
// Force the personal LinkedIn profile of an account connected as an org admin:
|
|
198
|
+
await client.getMedia('linkedin', 'my-profile', { pageUrn: 'me' });
|
|
199
|
+
|
|
200
|
+
// Target a specific LinkedIn organization page:
|
|
201
|
+
await client.getMedia('linkedin', 'my-profile', { pageUrn: '12345' });
|
|
202
|
+
```
|
|
203
|
+
|
|
184
204
|
### Helper Methods
|
|
185
205
|
|
|
186
206
|
```javascript
|
|
@@ -296,6 +316,32 @@ These options work across all upload methods:
|
|
|
296
316
|
| `addToQueue` | Add to posting queue |
|
|
297
317
|
| `maxPostsPerSlot` | Max posts per queue slot (overrides profile setting) |
|
|
298
318
|
| `asyncUpload` | Process asynchronously (default: true) |
|
|
319
|
+
| `autogenerate` | If true, AI generates native per-platform title/description from the media and fills any field left empty |
|
|
320
|
+
| `autogenerateTitle` / `autogenerateDescription` | Generate only the title or only the description with AI |
|
|
321
|
+
| `autogenerateLanguage` | Force the AI output language (ISO code); omit to auto-detect from the media |
|
|
322
|
+
| `idempotencyKey` | Collapses duplicate uploads within 24h. Reuse the same value when retrying. Alias: `requestId` |
|
|
323
|
+
|
|
324
|
+
## Retrying an upload safely
|
|
325
|
+
|
|
326
|
+
An upload that times out may still have been accepted by the API. Retrying it without an idempotency key publishes the post a second time.
|
|
327
|
+
|
|
328
|
+
Pass the same `idempotencyKey` on every attempt and the API returns the original job instead of creating a new one:
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
import { randomUUID } from 'crypto';
|
|
332
|
+
|
|
333
|
+
const idempotencyKey = randomUUID(); // generate ONCE, outside the retry loop
|
|
334
|
+
|
|
335
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
336
|
+
try {
|
|
337
|
+
return await client.upload('video.mp4', { user, platforms: ['tiktok'], title, idempotencyKey });
|
|
338
|
+
} catch (err) {
|
|
339
|
+
if (attempt === 2) throw err;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Generating the key inside the loop defeats the mechanism: each attempt would look like a new upload.
|
|
299
345
|
|
|
300
346
|
## TypeScript Support
|
|
301
347
|
|
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' | 'google_business';
|
|
5
|
+
export type VideoPlatform = 'tiktok' | 'instagram' | 'youtube' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'reddit' | 'bluesky' | 'x' | 'google_business' | 'discord' | 'telegram';
|
|
6
6
|
|
|
7
7
|
/** Supported platforms for photo upload */
|
|
8
|
-
export type PhotoPlatform = 'tiktok' | 'instagram' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'reddit' | 'bluesky' | 'x' | 'google_business';
|
|
8
|
+
export type PhotoPlatform = 'tiktok' | 'instagram' | 'linkedin' | 'facebook' | 'pinterest' | 'threads' | 'reddit' | 'bluesky' | 'x' | 'google_business' | 'discord' | 'telegram';
|
|
9
9
|
|
|
10
10
|
/** Supported platforms for text upload */
|
|
11
|
-
export type TextPlatform = 'x' | 'linkedin' | 'facebook' | 'threads' | 'reddit' | 'bluesky' | 'google_business';
|
|
11
|
+
export type TextPlatform = 'x' | 'linkedin' | 'facebook' | 'threads' | 'reddit' | 'bluesky' | 'google_business' | 'discord' | 'telegram';
|
|
12
12
|
|
|
13
13
|
/** TikTok privacy levels */
|
|
14
14
|
export type TikTokPrivacyLevel = 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY';
|
|
@@ -45,6 +45,13 @@ declare module 'upload-post' {
|
|
|
45
45
|
export interface CommonUploadOptions {
|
|
46
46
|
/** Post title/caption. Required for YouTube, Reddit, and text posts. Optional for TikTok, Instagram, Facebook, LinkedIn, X, Threads, Bluesky, Pinterest. */
|
|
47
47
|
title?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Key the API uses to collapse duplicate uploads within a 24-hour window.
|
|
50
|
+
* Pass the same value when retrying a failed upload, otherwise the retry publishes a second post.
|
|
51
|
+
*/
|
|
52
|
+
idempotencyKey?: string;
|
|
53
|
+
/** Alias for {@link CommonUploadOptions.idempotencyKey}. */
|
|
54
|
+
requestId?: string;
|
|
48
55
|
/** User identifier (profile name) */
|
|
49
56
|
user: string;
|
|
50
57
|
/** First comment to post after publishing */
|
|
@@ -61,6 +68,14 @@ declare module 'upload-post' {
|
|
|
61
68
|
maxPostsPerSlot?: number;
|
|
62
69
|
/** Process upload asynchronously (default: true) */
|
|
63
70
|
asyncUpload?: boolean;
|
|
71
|
+
/** If true, AI generates native per-platform title/description from the media and fills any field left empty */
|
|
72
|
+
autogenerate?: boolean;
|
|
73
|
+
/** Generate only the title with AI */
|
|
74
|
+
autogenerateTitle?: boolean;
|
|
75
|
+
/** Generate only the description with AI */
|
|
76
|
+
autogenerateDescription?: boolean;
|
|
77
|
+
/** Force the AI output language (ISO code); omit to auto-detect from the media */
|
|
78
|
+
autogenerateLanguage?: string;
|
|
64
79
|
|
|
65
80
|
// Platform-specific title overrides
|
|
66
81
|
blueskyTitle?: string;
|
|
@@ -192,6 +207,19 @@ declare module 'upload-post' {
|
|
|
192
207
|
youtubeHasPaidProductPlacement?: boolean;
|
|
193
208
|
/** Recording date (ISO 8601) */
|
|
194
209
|
youtubeRecordingDate?: string;
|
|
210
|
+
/** Playlist ID(s) to add the uploaded video to — one ID, an array, or a comma-separated list */
|
|
211
|
+
youtubePlaylistId?: string | string[];
|
|
212
|
+
/** Subtitle/caption files to upload. Each entry needs a language code and either a file path or URL. */
|
|
213
|
+
youtubeSubtitles?: Array<{
|
|
214
|
+
/** BCP-47 language code (e.g. "en", "es") */
|
|
215
|
+
language: string;
|
|
216
|
+
/** Display name for the subtitle track (e.g. "English", "Español") */
|
|
217
|
+
name?: string;
|
|
218
|
+
/** Path to the subtitle file (SRT, VTT, SBV, SUB, ASS, SSA, TTML) */
|
|
219
|
+
file?: string;
|
|
220
|
+
/** URL to the subtitle file */
|
|
221
|
+
url?: string;
|
|
222
|
+
}>;
|
|
195
223
|
}
|
|
196
224
|
|
|
197
225
|
// ==================== LinkedIn Options ====================
|
|
@@ -306,6 +334,8 @@ declare module 'upload-post' {
|
|
|
306
334
|
threadsLongTextAsPost?: boolean;
|
|
307
335
|
/** 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
336
|
threadsThreadMediaLayout?: string;
|
|
337
|
+
/** Topic tag for the Threads post */
|
|
338
|
+
threadsTopicTag?: string;
|
|
309
339
|
}
|
|
310
340
|
|
|
311
341
|
// ==================== Reddit Options ====================
|
|
@@ -471,7 +501,7 @@ declare module 'upload-post' {
|
|
|
471
501
|
* Upload-Post API client
|
|
472
502
|
*
|
|
473
503
|
* Supports uploading to: TikTok, Instagram, YouTube, LinkedIn, Facebook,
|
|
474
|
-
* Pinterest, Threads, Reddit, Bluesky, X (Twitter)
|
|
504
|
+
* Pinterest, Threads, Reddit, Bluesky, X (Twitter), Discord, Telegram
|
|
475
505
|
*/
|
|
476
506
|
export class UploadPost {
|
|
477
507
|
/**
|
|
@@ -589,6 +619,28 @@ declare module 'upload-post' {
|
|
|
589
619
|
metric_labels: Record<string, string>;
|
|
590
620
|
}>>;
|
|
591
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Get recent media from a connected social account.
|
|
624
|
+
*
|
|
625
|
+
* @param options.pageUrn - LinkedIn only. Numeric org ID, full org URN, or "me" to force the personal profile.
|
|
626
|
+
*/
|
|
627
|
+
getMedia(
|
|
628
|
+
platform: 'instagram' | 'tiktok' | 'youtube' | 'linkedin' | 'facebook' | 'x' | 'threads' | 'pinterest' | 'bluesky' | 'reddit' | string,
|
|
629
|
+
user: string,
|
|
630
|
+
options?: { pageUrn?: string }
|
|
631
|
+
): Promise<{
|
|
632
|
+
success: boolean;
|
|
633
|
+
media: Array<{
|
|
634
|
+
id: string;
|
|
635
|
+
caption: string | null;
|
|
636
|
+
media_type: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM' | 'TEXT' | string;
|
|
637
|
+
media_url: string | null;
|
|
638
|
+
permalink: string | null;
|
|
639
|
+
timestamp: string | null;
|
|
640
|
+
thumbnail_url: string | null;
|
|
641
|
+
}>;
|
|
642
|
+
}>;
|
|
643
|
+
|
|
592
644
|
// Scheduled Posts
|
|
593
645
|
/**
|
|
594
646
|
* List scheduled posts
|
|
@@ -640,6 +692,7 @@ declare module 'upload-post' {
|
|
|
640
692
|
readonlyCalendar?: boolean;
|
|
641
693
|
connectTitle?: string;
|
|
642
694
|
connectDescription?: string;
|
|
695
|
+
language?: 'en' | 'es' | 'de' | 'fr' | 'pt';
|
|
643
696
|
}): Promise<JwtResponse>;
|
|
644
697
|
|
|
645
698
|
/**
|
|
@@ -728,6 +781,26 @@ declare module 'upload-post' {
|
|
|
728
781
|
* @param profile - Profile username
|
|
729
782
|
*/
|
|
730
783
|
getPinterestBoards(profile?: string): Promise<BoardsResponse>;
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Get analytics for a post published outside Upload-Post, by its native platform ID
|
|
787
|
+
* @param platformPostId - The post ID assigned by the platform
|
|
788
|
+
* @param platform - Platform the post lives on
|
|
789
|
+
* @param user - Profile username that owns the connected account
|
|
790
|
+
*/
|
|
791
|
+
getPostAnalyticsByPlatformId(platformPostId: string, platform: string, user: string): Promise<Record<string, unknown>>;
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Get detailed Reddit posts with full media information
|
|
795
|
+
* @param profileUsername - Profile username
|
|
796
|
+
*/
|
|
797
|
+
getRedditDetailedPosts(profileUsername: string): Promise<Record<string, unknown>>;
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Get available Google Business locations for a profile
|
|
801
|
+
* @param profile - Profile username
|
|
802
|
+
*/
|
|
803
|
+
getGoogleBusinessLocations(profile?: string): Promise<Record<string, unknown>>;
|
|
731
804
|
}
|
|
732
805
|
|
|
733
806
|
export default UploadPost;
|
package/index.js
CHANGED
|
@@ -8,8 +8,8 @@ const API_BASE_URL = 'https://api.upload-post.com/api';
|
|
|
8
8
|
/**
|
|
9
9
|
* Upload-Post API client
|
|
10
10
|
*
|
|
11
|
-
* Supports uploading to: TikTok, Instagram, YouTube, LinkedIn, Facebook,
|
|
12
|
-
* Pinterest, Threads, Reddit, Bluesky, X (Twitter)
|
|
11
|
+
* Supports uploading to: TikTok, Instagram, YouTube, LinkedIn, Facebook,
|
|
12
|
+
* Pinterest, Threads, Reddit, Bluesky, X (Twitter), Discord, Telegram
|
|
13
13
|
*/
|
|
14
14
|
export class UploadPost {
|
|
15
15
|
/**
|
|
@@ -24,13 +24,14 @@ export class UploadPost {
|
|
|
24
24
|
* Make an API request
|
|
25
25
|
* @private
|
|
26
26
|
*/
|
|
27
|
-
async _request(endpoint, method = 'GET', data = null, isFormData = false) {
|
|
27
|
+
async _request(endpoint, method = 'GET', data = null, isFormData = false, extraHeaders = {}) {
|
|
28
28
|
const config = {
|
|
29
29
|
method,
|
|
30
30
|
url: `${this.baseUrl}${endpoint}`,
|
|
31
31
|
headers: {
|
|
32
32
|
'Authorization': `Apikey ${this.apiKey}`,
|
|
33
33
|
'X-Upload-Post-Source': 'npm',
|
|
34
|
+
...extraHeaders,
|
|
34
35
|
},
|
|
35
36
|
maxContentLength: Infinity,
|
|
36
37
|
maxBodyLength: Infinity
|
|
@@ -57,6 +58,16 @@ export class UploadPost {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* The API collapses two uploads carrying the same key within 24 hours into a
|
|
63
|
+
* single post. Retrying an upload without one publishes it twice.
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
_idempotencyHeaders(options) {
|
|
67
|
+
const key = options.idempotencyKey || options.requestId;
|
|
68
|
+
return key ? { 'Idempotency-Key': String(key) } : {};
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
/**
|
|
61
72
|
* Add common upload parameters to form
|
|
62
73
|
* @private
|
|
@@ -77,6 +88,11 @@ export class UploadPost {
|
|
|
77
88
|
if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
|
|
78
89
|
if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
|
|
79
90
|
if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
|
|
91
|
+
// AI auto-generation of native per-platform copy from the media (fills blank fields)
|
|
92
|
+
if (options.autogenerate !== undefined) form.append('autogenerate', String(options.autogenerate));
|
|
93
|
+
if (options.autogenerateTitle !== undefined) form.append('autogenerate_title', String(options.autogenerateTitle));
|
|
94
|
+
if (options.autogenerateDescription !== undefined) form.append('autogenerate_description', String(options.autogenerateDescription));
|
|
95
|
+
if (options.autogenerateLanguage) form.append('autogenerate_language', String(options.autogenerateLanguage));
|
|
80
96
|
|
|
81
97
|
// Platform-specific title overrides
|
|
82
98
|
if (options.blueskyTitle) form.append('bluesky_title', options.blueskyTitle);
|
|
@@ -198,6 +214,30 @@ export class UploadPost {
|
|
|
198
214
|
if (options.youtubeBlockedCountries) form.append('blockedCountries', options.youtubeBlockedCountries);
|
|
199
215
|
if (options.youtubeHasPaidProductPlacement !== undefined) form.append('hasPaidProductPlacement', String(options.youtubeHasPaidProductPlacement));
|
|
200
216
|
if (options.youtubeRecordingDate) form.append('recordingDate', options.youtubeRecordingDate);
|
|
217
|
+
if (options.youtubePlaylistId) {
|
|
218
|
+
const playlistIds = Array.isArray(options.youtubePlaylistId)
|
|
219
|
+
? options.youtubePlaylistId.join(',')
|
|
220
|
+
: options.youtubePlaylistId;
|
|
221
|
+
form.append('youtube_playlist_id', playlistIds);
|
|
222
|
+
}
|
|
223
|
+
if (options.youtubeSubtitles && Array.isArray(options.youtubeSubtitles)) {
|
|
224
|
+
options.youtubeSubtitles.forEach((sub, idx) => {
|
|
225
|
+
if (sub.language) {
|
|
226
|
+
form.append(`youtube_subtitle_language_${idx}`, sub.language);
|
|
227
|
+
if (sub.name) form.append(`youtube_subtitle_name_${idx}`, sub.name);
|
|
228
|
+
if (sub.file) {
|
|
229
|
+
// file can be a string path or a ReadableStream
|
|
230
|
+
if (typeof sub.file === 'string') {
|
|
231
|
+
form.append(`youtube_subtitle_file_${idx}`, createReadStream(sub.file));
|
|
232
|
+
} else {
|
|
233
|
+
form.append(`youtube_subtitle_file_${idx}`, sub.file);
|
|
234
|
+
}
|
|
235
|
+
} else if (sub.url) {
|
|
236
|
+
form.append(`youtube_subtitle_file_${idx}`, sub.url);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
201
241
|
}
|
|
202
242
|
|
|
203
243
|
/**
|
|
@@ -312,7 +352,7 @@ export class UploadPost {
|
|
|
312
352
|
* @param {Object} options - Upload options
|
|
313
353
|
* @param {string} options.title - Video title/caption
|
|
314
354
|
* @param {string} options.user - User identifier (profile name)
|
|
315
|
-
* @param {string[]} options.platforms - Target platforms (tiktok, instagram, youtube, linkedin, facebook, pinterest, threads, bluesky, x)
|
|
355
|
+
* @param {string[]} options.platforms - Target platforms (tiktok, instagram, youtube, linkedin, facebook, pinterest, threads, bluesky, x, discord, telegram)
|
|
316
356
|
* @param {string} [options.description] - Video description
|
|
317
357
|
* @param {string} [options.firstComment] - First comment to post
|
|
318
358
|
* @param {string} [options.scheduledDate] - ISO date for scheduling (e.g., "2024-12-25T10:00:00Z")
|
|
@@ -416,8 +456,9 @@ export class UploadPost {
|
|
|
416
456
|
if (platforms.includes('pinterest')) this._addPinterestParams(form, options, true);
|
|
417
457
|
if (platforms.includes('x')) this._addXParams(form, options, false);
|
|
418
458
|
if (platforms.includes('threads')) this._addThreadsParams(form, options);
|
|
459
|
+
if (platforms.includes('reddit')) this._addRedditParams(form, options);
|
|
419
460
|
|
|
420
|
-
return this._request('/upload', 'POST', form, true);
|
|
461
|
+
return this._request('/upload', 'POST', form, true, this._idempotencyHeaders(options));
|
|
421
462
|
}
|
|
422
463
|
|
|
423
464
|
/**
|
|
@@ -427,7 +468,7 @@ export class UploadPost {
|
|
|
427
468
|
* @param {Object} options - Upload options
|
|
428
469
|
* @param {string} options.title - Post title/caption
|
|
429
470
|
* @param {string} options.user - User identifier (profile name)
|
|
430
|
-
* @param {string[]} options.platforms - Target platforms (tiktok, instagram, linkedin, facebook, x, threads, pinterest, reddit, bluesky)
|
|
471
|
+
* @param {string[]} options.platforms - Target platforms (tiktok, instagram, linkedin, facebook, x, threads, pinterest, reddit, bluesky, discord, telegram)
|
|
431
472
|
* @param {string} [options.description] - Photo description
|
|
432
473
|
* @param {string} [options.firstComment] - First comment to post
|
|
433
474
|
* @param {string} [options.altText] - Alt text for accessibility
|
|
@@ -505,7 +546,7 @@ export class UploadPost {
|
|
|
505
546
|
if (platforms.includes('threads')) this._addThreadsParams(form, options);
|
|
506
547
|
if (platforms.includes('reddit')) this._addRedditParams(form, options);
|
|
507
548
|
|
|
508
|
-
return this._request('/upload_photos', 'POST', form, true);
|
|
549
|
+
return this._request('/upload_photos', 'POST', form, true, this._idempotencyHeaders(options));
|
|
509
550
|
}
|
|
510
551
|
|
|
511
552
|
/**
|
|
@@ -514,7 +555,7 @@ export class UploadPost {
|
|
|
514
555
|
* @param {Object} options - Upload options
|
|
515
556
|
* @param {string} options.title - Text content for the post
|
|
516
557
|
* @param {string} options.user - User identifier (profile name)
|
|
517
|
-
* @param {string[]} options.platforms - Target platforms (x, linkedin, facebook, threads, reddit, bluesky)
|
|
558
|
+
* @param {string[]} options.platforms - Target platforms (x, linkedin, facebook, threads, reddit, bluesky, discord, telegram)
|
|
518
559
|
* @param {string} [options.firstComment] - First comment to post
|
|
519
560
|
* @param {string} [options.scheduledDate] - ISO date for scheduling
|
|
520
561
|
* @param {string} [options.timezone] - Timezone for scheduled date
|
|
@@ -572,7 +613,7 @@ export class UploadPost {
|
|
|
572
613
|
form.append('bluesky_link_url', options.blueskyLinkUrl || options.linkUrl);
|
|
573
614
|
}
|
|
574
615
|
|
|
575
|
-
return this._request('/upload_text', 'POST', form, true);
|
|
616
|
+
return this._request('/upload_text', 'POST', form, true, this._idempotencyHeaders(options));
|
|
576
617
|
}
|
|
577
618
|
|
|
578
619
|
/**
|
|
@@ -616,10 +657,15 @@ export class UploadPost {
|
|
|
616
657
|
if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
|
|
617
658
|
if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
|
|
618
659
|
if (options.asyncUpload !== undefined) form.append('async_upload', String(options.asyncUpload));
|
|
660
|
+
// AI auto-generation of native per-platform copy from the media (fills blank fields)
|
|
661
|
+
if (options.autogenerate !== undefined) form.append('autogenerate', String(options.autogenerate));
|
|
662
|
+
if (options.autogenerateTitle !== undefined) form.append('autogenerate_title', String(options.autogenerateTitle));
|
|
663
|
+
if (options.autogenerateDescription !== undefined) form.append('autogenerate_description', String(options.autogenerateDescription));
|
|
664
|
+
if (options.autogenerateLanguage) form.append('autogenerate_language', String(options.autogenerateLanguage));
|
|
619
665
|
|
|
620
666
|
this._addLinkedinParams(form, options);
|
|
621
667
|
|
|
622
|
-
return this._request('/upload_document', 'POST', form, true);
|
|
668
|
+
return this._request('/upload_document', 'POST', form, true, this._idempotencyHeaders(options));
|
|
623
669
|
}
|
|
624
670
|
|
|
625
671
|
// ==================== Status & History ====================
|
|
@@ -744,6 +790,31 @@ export class UploadPost {
|
|
|
744
790
|
return this._request('/uploadposts/platform-metrics', 'GET');
|
|
745
791
|
}
|
|
746
792
|
|
|
793
|
+
/**
|
|
794
|
+
* Get detailed Reddit posts with full media information
|
|
795
|
+
*
|
|
796
|
+
* @param {string} profileUsername - Profile username
|
|
797
|
+
* @returns {Promise<Object>} Detailed Reddit posts
|
|
798
|
+
*/
|
|
799
|
+
async getRedditDetailedPosts(profileUsername) {
|
|
800
|
+
return this._request('/uploadposts/reddit/detailed-posts/', 'GET', { profile_username: profileUsername });
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Get recent media from a connected social account.
|
|
805
|
+
*
|
|
806
|
+
* @param {string} platform - instagram, tiktok, youtube, linkedin, facebook, x, threads, pinterest, bluesky, reddit
|
|
807
|
+
* @param {string} user - Profile username
|
|
808
|
+
* @param {Object} [options]
|
|
809
|
+
* @param {string} [options.pageUrn] - LinkedIn only. Numeric org ID, full URN, or "me" to force the personal profile.
|
|
810
|
+
* @returns {Promise<Object>}
|
|
811
|
+
*/
|
|
812
|
+
async getMedia(platform, user, options = {}) {
|
|
813
|
+
const params = { platform, user };
|
|
814
|
+
if (options.pageUrn) params.page_urn = options.pageUrn;
|
|
815
|
+
return this._request('/uploadposts/media', 'GET', params);
|
|
816
|
+
}
|
|
817
|
+
|
|
747
818
|
// ==================== Scheduled Posts ====================
|
|
748
819
|
|
|
749
820
|
/**
|
|
@@ -778,7 +849,7 @@ export class UploadPost {
|
|
|
778
849
|
const body = {};
|
|
779
850
|
if (options.scheduledDate) body.scheduled_date = options.scheduledDate;
|
|
780
851
|
if (options.timezone) body.timezone = options.timezone;
|
|
781
|
-
return this._request(`/uploadposts/schedule/${jobId}`, '
|
|
852
|
+
return this._request(`/uploadposts/schedule/${jobId}`, 'PATCH', body);
|
|
782
853
|
}
|
|
783
854
|
|
|
784
855
|
// ==================== User Management ====================
|
|
@@ -826,6 +897,7 @@ export class UploadPost {
|
|
|
826
897
|
* @param {boolean} [options.readonlyCalendar] - Show only a read-only calendar (no editing, no account connection)
|
|
827
898
|
* @param {string} [options.connectTitle] - Custom title for the connection page
|
|
828
899
|
* @param {string} [options.connectDescription] - Custom description for the connection page
|
|
900
|
+
* @param {('en'|'es'|'de'|'fr'|'pt')} [options.language] - Force the connection page language for this profile. When omitted, the page auto-detects the visitor's browser language and falls back to English.
|
|
829
901
|
* @returns {Promise<Object>} JWT and connection URL
|
|
830
902
|
*/
|
|
831
903
|
async generateJwt(username, options = {}) {
|
|
@@ -838,6 +910,7 @@ export class UploadPost {
|
|
|
838
910
|
if (options.readonlyCalendar !== undefined) body.readonly_calendar = options.readonlyCalendar;
|
|
839
911
|
if (options.connectTitle) body.connect_title = options.connectTitle;
|
|
840
912
|
if (options.connectDescription) body.connect_description = options.connectDescription;
|
|
913
|
+
if (options.language) body.language = options.language;
|
|
841
914
|
return this._request('/uploadposts/users/generate-jwt', 'POST', body);
|
|
842
915
|
}
|
|
843
916
|
|
|
@@ -848,7 +921,7 @@ export class UploadPost {
|
|
|
848
921
|
* @returns {Promise<Object>} Validation result
|
|
849
922
|
*/
|
|
850
923
|
async validateJwt(jwt) {
|
|
851
|
-
return this._request('/uploadposts/users/validate-jwt', '
|
|
924
|
+
return this._request('/uploadposts/users/validate-jwt', 'GET', null, false, { Authorization: `Bearer ${jwt}` });
|
|
852
925
|
}
|
|
853
926
|
|
|
854
927
|
/**
|
|
@@ -879,7 +952,7 @@ export class UploadPost {
|
|
|
879
952
|
* @returns {Promise<Object>} Notification config
|
|
880
953
|
*/
|
|
881
954
|
async getNotificationConfig() {
|
|
882
|
-
return this._request('/uploadposts/
|
|
955
|
+
return this._request('/uploadposts/users/notifications', 'GET');
|
|
883
956
|
}
|
|
884
957
|
|
|
885
958
|
/**
|
|
@@ -894,7 +967,7 @@ export class UploadPost {
|
|
|
894
967
|
const body = {};
|
|
895
968
|
if (options.webhookEvents) body.webhook_events = options.webhookEvents;
|
|
896
969
|
if (options.webhookUrl) body.webhook_url = options.webhookUrl;
|
|
897
|
-
return this._request('/uploadposts/
|
|
970
|
+
return this._request('/uploadposts/users/notifications', 'POST', body);
|
|
898
971
|
}
|
|
899
972
|
|
|
900
973
|
// ==================== Instagram Comments ====================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upload-post",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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",
|