upload-post 2.2.0 → 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 +34 -4
- package/index.d.ts +44 -4
- package/index.js +54 -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
|
|
|
@@ -312,6 +316,32 @@ These options work across all upload methods:
|
|
|
312
316
|
| `addToQueue` | Add to posting queue |
|
|
313
317
|
| `maxPostsPerSlot` | Max posts per queue slot (overrides profile setting) |
|
|
314
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.
|
|
315
345
|
|
|
316
346
|
## TypeScript Support
|
|
317
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,8 @@ 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[];
|
|
195
212
|
/** Subtitle/caption files to upload. Each entry needs a language code and either a file path or URL. */
|
|
196
213
|
youtubeSubtitles?: Array<{
|
|
197
214
|
/** BCP-47 language code (e.g. "en", "es") */
|
|
@@ -317,6 +334,8 @@ declare module 'upload-post' {
|
|
|
317
334
|
threadsLongTextAsPost?: boolean;
|
|
318
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. */
|
|
319
336
|
threadsThreadMediaLayout?: string;
|
|
337
|
+
/** Topic tag for the Threads post */
|
|
338
|
+
threadsTopicTag?: string;
|
|
320
339
|
}
|
|
321
340
|
|
|
322
341
|
// ==================== Reddit Options ====================
|
|
@@ -482,7 +501,7 @@ declare module 'upload-post' {
|
|
|
482
501
|
* Upload-Post API client
|
|
483
502
|
*
|
|
484
503
|
* Supports uploading to: TikTok, Instagram, YouTube, LinkedIn, Facebook,
|
|
485
|
-
* Pinterest, Threads, Reddit, Bluesky, X (Twitter)
|
|
504
|
+
* Pinterest, Threads, Reddit, Bluesky, X (Twitter), Discord, Telegram
|
|
486
505
|
*/
|
|
487
506
|
export class UploadPost {
|
|
488
507
|
/**
|
|
@@ -673,6 +692,7 @@ declare module 'upload-post' {
|
|
|
673
692
|
readonlyCalendar?: boolean;
|
|
674
693
|
connectTitle?: string;
|
|
675
694
|
connectDescription?: string;
|
|
695
|
+
language?: 'en' | 'es' | 'de' | 'fr' | 'pt';
|
|
676
696
|
}): Promise<JwtResponse>;
|
|
677
697
|
|
|
678
698
|
/**
|
|
@@ -761,6 +781,26 @@ declare module 'upload-post' {
|
|
|
761
781
|
* @param profile - Profile username
|
|
762
782
|
*/
|
|
763
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>>;
|
|
764
804
|
}
|
|
765
805
|
|
|
766
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,12 @@ 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
|
+
}
|
|
201
223
|
if (options.youtubeSubtitles && Array.isArray(options.youtubeSubtitles)) {
|
|
202
224
|
options.youtubeSubtitles.forEach((sub, idx) => {
|
|
203
225
|
if (sub.language) {
|
|
@@ -330,7 +352,7 @@ export class UploadPost {
|
|
|
330
352
|
* @param {Object} options - Upload options
|
|
331
353
|
* @param {string} options.title - Video title/caption
|
|
332
354
|
* @param {string} options.user - User identifier (profile name)
|
|
333
|
-
* @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)
|
|
334
356
|
* @param {string} [options.description] - Video description
|
|
335
357
|
* @param {string} [options.firstComment] - First comment to post
|
|
336
358
|
* @param {string} [options.scheduledDate] - ISO date for scheduling (e.g., "2024-12-25T10:00:00Z")
|
|
@@ -434,8 +456,9 @@ export class UploadPost {
|
|
|
434
456
|
if (platforms.includes('pinterest')) this._addPinterestParams(form, options, true);
|
|
435
457
|
if (platforms.includes('x')) this._addXParams(form, options, false);
|
|
436
458
|
if (platforms.includes('threads')) this._addThreadsParams(form, options);
|
|
459
|
+
if (platforms.includes('reddit')) this._addRedditParams(form, options);
|
|
437
460
|
|
|
438
|
-
return this._request('/upload', 'POST', form, true);
|
|
461
|
+
return this._request('/upload', 'POST', form, true, this._idempotencyHeaders(options));
|
|
439
462
|
}
|
|
440
463
|
|
|
441
464
|
/**
|
|
@@ -445,7 +468,7 @@ export class UploadPost {
|
|
|
445
468
|
* @param {Object} options - Upload options
|
|
446
469
|
* @param {string} options.title - Post title/caption
|
|
447
470
|
* @param {string} options.user - User identifier (profile name)
|
|
448
|
-
* @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)
|
|
449
472
|
* @param {string} [options.description] - Photo description
|
|
450
473
|
* @param {string} [options.firstComment] - First comment to post
|
|
451
474
|
* @param {string} [options.altText] - Alt text for accessibility
|
|
@@ -523,7 +546,7 @@ export class UploadPost {
|
|
|
523
546
|
if (platforms.includes('threads')) this._addThreadsParams(form, options);
|
|
524
547
|
if (platforms.includes('reddit')) this._addRedditParams(form, options);
|
|
525
548
|
|
|
526
|
-
return this._request('/upload_photos', 'POST', form, true);
|
|
549
|
+
return this._request('/upload_photos', 'POST', form, true, this._idempotencyHeaders(options));
|
|
527
550
|
}
|
|
528
551
|
|
|
529
552
|
/**
|
|
@@ -532,7 +555,7 @@ export class UploadPost {
|
|
|
532
555
|
* @param {Object} options - Upload options
|
|
533
556
|
* @param {string} options.title - Text content for the post
|
|
534
557
|
* @param {string} options.user - User identifier (profile name)
|
|
535
|
-
* @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)
|
|
536
559
|
* @param {string} [options.firstComment] - First comment to post
|
|
537
560
|
* @param {string} [options.scheduledDate] - ISO date for scheduling
|
|
538
561
|
* @param {string} [options.timezone] - Timezone for scheduled date
|
|
@@ -590,7 +613,7 @@ export class UploadPost {
|
|
|
590
613
|
form.append('bluesky_link_url', options.blueskyLinkUrl || options.linkUrl);
|
|
591
614
|
}
|
|
592
615
|
|
|
593
|
-
return this._request('/upload_text', 'POST', form, true);
|
|
616
|
+
return this._request('/upload_text', 'POST', form, true, this._idempotencyHeaders(options));
|
|
594
617
|
}
|
|
595
618
|
|
|
596
619
|
/**
|
|
@@ -634,10 +657,15 @@ export class UploadPost {
|
|
|
634
657
|
if (options.addToQueue !== undefined) form.append('add_to_queue', String(options.addToQueue));
|
|
635
658
|
if (options.maxPostsPerSlot !== undefined) form.append('max_posts_per_slot', String(options.maxPostsPerSlot));
|
|
636
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));
|
|
637
665
|
|
|
638
666
|
this._addLinkedinParams(form, options);
|
|
639
667
|
|
|
640
|
-
return this._request('/upload_document', 'POST', form, true);
|
|
668
|
+
return this._request('/upload_document', 'POST', form, true, this._idempotencyHeaders(options));
|
|
641
669
|
}
|
|
642
670
|
|
|
643
671
|
// ==================== Status & History ====================
|
|
@@ -762,6 +790,16 @@ export class UploadPost {
|
|
|
762
790
|
return this._request('/uploadposts/platform-metrics', 'GET');
|
|
763
791
|
}
|
|
764
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
|
+
|
|
765
803
|
/**
|
|
766
804
|
* Get recent media from a connected social account.
|
|
767
805
|
*
|
|
@@ -811,7 +849,7 @@ export class UploadPost {
|
|
|
811
849
|
const body = {};
|
|
812
850
|
if (options.scheduledDate) body.scheduled_date = options.scheduledDate;
|
|
813
851
|
if (options.timezone) body.timezone = options.timezone;
|
|
814
|
-
return this._request(`/uploadposts/schedule/${jobId}`, '
|
|
852
|
+
return this._request(`/uploadposts/schedule/${jobId}`, 'PATCH', body);
|
|
815
853
|
}
|
|
816
854
|
|
|
817
855
|
// ==================== User Management ====================
|
|
@@ -859,6 +897,7 @@ export class UploadPost {
|
|
|
859
897
|
* @param {boolean} [options.readonlyCalendar] - Show only a read-only calendar (no editing, no account connection)
|
|
860
898
|
* @param {string} [options.connectTitle] - Custom title for the connection page
|
|
861
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.
|
|
862
901
|
* @returns {Promise<Object>} JWT and connection URL
|
|
863
902
|
*/
|
|
864
903
|
async generateJwt(username, options = {}) {
|
|
@@ -871,6 +910,7 @@ export class UploadPost {
|
|
|
871
910
|
if (options.readonlyCalendar !== undefined) body.readonly_calendar = options.readonlyCalendar;
|
|
872
911
|
if (options.connectTitle) body.connect_title = options.connectTitle;
|
|
873
912
|
if (options.connectDescription) body.connect_description = options.connectDescription;
|
|
913
|
+
if (options.language) body.language = options.language;
|
|
874
914
|
return this._request('/uploadposts/users/generate-jwt', 'POST', body);
|
|
875
915
|
}
|
|
876
916
|
|
|
@@ -881,7 +921,7 @@ export class UploadPost {
|
|
|
881
921
|
* @returns {Promise<Object>} Validation result
|
|
882
922
|
*/
|
|
883
923
|
async validateJwt(jwt) {
|
|
884
|
-
return this._request('/uploadposts/users/validate-jwt', '
|
|
924
|
+
return this._request('/uploadposts/users/validate-jwt', 'GET', null, false, { Authorization: `Bearer ${jwt}` });
|
|
885
925
|
}
|
|
886
926
|
|
|
887
927
|
/**
|
|
@@ -912,7 +952,7 @@ export class UploadPost {
|
|
|
912
952
|
* @returns {Promise<Object>} Notification config
|
|
913
953
|
*/
|
|
914
954
|
async getNotificationConfig() {
|
|
915
|
-
return this._request('/uploadposts/
|
|
955
|
+
return this._request('/uploadposts/users/notifications', 'GET');
|
|
916
956
|
}
|
|
917
957
|
|
|
918
958
|
/**
|
|
@@ -927,7 +967,7 @@ export class UploadPost {
|
|
|
927
967
|
const body = {};
|
|
928
968
|
if (options.webhookEvents) body.webhook_events = options.webhookEvents;
|
|
929
969
|
if (options.webhookUrl) body.webhook_url = options.webhookUrl;
|
|
930
|
-
return this._request('/uploadposts/
|
|
970
|
+
return this._request('/uploadposts/users/notifications', 'POST', body);
|
|
931
971
|
}
|
|
932
972
|
|
|
933
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",
|