upload-post 2.12.0 → 2.14.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.
Files changed (4) hide show
  1. package/README.md +111 -9
  2. package/index.d.ts +368 -17
  3. package/index.js +375 -16
  4. package/package.json +2 -2
package/index.js CHANGED
@@ -68,6 +68,71 @@ export class UploadPost {
68
68
  return key ? { 'Idempotency-Key': String(key) } : {};
69
69
  }
70
70
 
71
+ /**
72
+ * First defined alias. Upload option keys are camelCase; a few fields also
73
+ * accept the snake_case name the API uses.
74
+ * @private
75
+ */
76
+ _pick(options, ...keys) {
77
+ for (const key of keys) {
78
+ if (options[key] !== undefined) return options[key];
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Append a whitelist field. Objects/arrays go as JSON; booleans and numbers
84
+ * as strings. Empty strings are omitted.
85
+ * @private
86
+ */
87
+ _appendField(form, field, value) {
88
+ if (value === undefined || value === null) return;
89
+ if (typeof value === 'boolean' || typeof value === 'number') {
90
+ form.append(field, String(value));
91
+ return;
92
+ }
93
+ if (Array.isArray(value)) {
94
+ form.append(field, JSON.stringify(value));
95
+ return;
96
+ }
97
+ if (typeof value === 'object' && typeof value.pipe !== 'function' && !Buffer.isBuffer(value)) {
98
+ form.append(field, JSON.stringify(value));
99
+ return;
100
+ }
101
+ if (value === '') return;
102
+ form.append(field, value);
103
+ }
104
+
105
+ /**
106
+ * Local path becomes a file stream; http(s) URLs and other values pass through.
107
+ * @private
108
+ */
109
+ _appendFileField(form, field, value) {
110
+ if (value === undefined || value === null || value === '') return;
111
+ if (typeof value === 'string') {
112
+ const lower = value.toLowerCase();
113
+ if (lower.startsWith('http://') || lower.startsWith('https://') || !fs.existsSync(value)) {
114
+ form.append(field, value);
115
+ } else {
116
+ form.append(field, createReadStream(value));
117
+ }
118
+ return;
119
+ }
120
+ form.append(field, value);
121
+ }
122
+
123
+ /**
124
+ * Repeat `field[]` once per item (carousel titles, alt-text lists, …).
125
+ * @private
126
+ */
127
+ _appendRepeated(form, field, value) {
128
+ if (value === undefined || value === null || value === '') return;
129
+ const items = Array.isArray(value) ? value : [value];
130
+ for (const item of items) {
131
+ if (item === undefined || item === null || item === '') continue;
132
+ form.append(field, typeof item === 'object' ? JSON.stringify(item) : String(item));
133
+ }
134
+ }
135
+
71
136
  /**
72
137
  * Add common upload parameters to form
73
138
  * @private
@@ -82,6 +147,10 @@ export class UploadPost {
82
147
 
83
148
  // Optional common parameters
84
149
  if (options.firstComment) form.append('first_comment', options.firstComment);
150
+ // Reply to an existing post (X: tweet ID; Bluesky: post URL or AT-URI). The
151
+ // API has always accepted `reply_to_id`; the SDK simply never mapped it.
152
+ const replyToId = options.replyToId || options.xReplyToId || options.blueskyReplyToId;
153
+ if (replyToId) form.append('reply_to_id', replyToId);
85
154
  if (options.altText) form.append('alt_text', options.altText);
86
155
  if (options.scheduledDate) form.append('scheduled_date', options.scheduledDate);
87
156
  if (options.timezone) form.append('timezone', options.timezone);
@@ -175,14 +244,13 @@ export class UploadPost {
175
244
  ['brand_content_toggle', 'brandContentToggle', 'flag'],
176
245
  ['brand_organic_toggle', 'brandOrganicToggle', 'flag'],
177
246
  ['privacy_level', 'tiktokPrivacyLevel', 'text'],
178
- ['post_mode', 'tiktokPostMode', 'text'],
179
247
  ['tiktok_music_id', 'tiktokMusicId', 'text'],
180
248
  ['tiktok_location_id', 'tiktokLocationId', 'text'],
181
249
  ['tiktok_location_name', 'tiktokLocationName', 'text'],
182
250
  ['tiktok_is_ai_generated', 'tiktokIsAiGenerated', 'flag'],
183
251
  ];
184
252
  // Video-only: TikTok's photo contract takes the track id alone, with no
185
- // volume or trim, and has no custom cover or draft switch.
253
+ // volume or trim, and has no custom cover.
186
254
  const videoOnly = [
187
255
  ['disable_duet', 'tiktokDisableDuet', 'flag'],
188
256
  ['disable_stitch', 'tiktokDisableStitch', 'flag'],
@@ -193,7 +261,6 @@ export class UploadPost {
193
261
  ['tiktok_music_end', 'tiktokMusicEnd', 'flag'],
194
262
  ['tiktok_original_sound_volume', 'tiktokOriginalSoundVolume', 'flag'],
195
263
  ['tiktok_cover_image_url', 'tiktokCoverImageUrl', 'text'],
196
- ['tiktok_upload_to_draft', 'tiktokUploadToDraft', 'flag'],
197
264
  ];
198
265
  const photoOnly = [
199
266
  ['auto_add_music', 'tiktokAutoAddMusic', 'flag'],
@@ -205,6 +272,23 @@ export class UploadPost {
205
272
  if (kind === 'flag' ? value === undefined : !value) continue;
206
273
  form.append(field, String(value));
207
274
  }
275
+
276
+ // Draft: postMode MEDIA_UPLOAD and tiktokUploadToDraft are the same draft.
277
+ // Pass both through (camelCase and snake_case). Do not collapse them.
278
+ const postMode = this._pick(options, 'tiktokPostMode', 'postMode', 'post_mode');
279
+ if (postMode) form.append('post_mode', String(postMode));
280
+ const uploadToDraft = this._pick(
281
+ options,
282
+ 'tiktokUploadToDraft', 'uploadToDraft', 'tiktok_upload_to_draft', 'upload_to_draft'
283
+ );
284
+ if (uploadToDraft !== undefined) form.append('tiktok_upload_to_draft', String(uploadToDraft));
285
+
286
+ if (isVideo) {
287
+ const adsOnly = this._pick(options, 'tiktokIsAdsOnly', 'isAdsOnly', 'tiktok_is_ads_only', 'is_ads_only');
288
+ if (adsOnly !== undefined) form.append('tiktok_is_ads_only', String(adsOnly));
289
+ const tto = this._pick(options, 'tiktokTtoInviteLink', 'ttoInviteLink', 'tiktok_tto_invite_link', 'tto_invite_link');
290
+ if (tto) form.append('tiktok_tto_invite_link', String(tto));
291
+ }
208
292
  }
209
293
 
210
294
  /**
@@ -231,6 +315,8 @@ export class UploadPost {
231
315
  }
232
316
  if (options.instagramAudioName) form.append('audio_name', options.instagramAudioName);
233
317
  if (options.instagramThumbOffset) form.append('thumb_offset', options.instagramThumbOffset);
318
+ } else {
319
+ this._appendField(form, 'instagram_alt_text', this._pick(options, 'instagramAltText', 'instagram_alt_text'));
234
320
  }
235
321
  }
236
322
 
@@ -257,6 +343,8 @@ export class UploadPost {
257
343
  if (options.youtubeBlockedCountries) form.append('blockedCountries', options.youtubeBlockedCountries);
258
344
  if (options.youtubeHasPaidProductPlacement !== undefined) form.append('hasPaidProductPlacement', String(options.youtubeHasPaidProductPlacement));
259
345
  if (options.youtubeRecordingDate) form.append('recordingDate', options.youtubeRecordingDate);
346
+ if (options.youtubeNotifySubscribers !== undefined) form.append('youtube_notify_subscribers', String(options.youtubeNotifySubscribers));
347
+ if (options.youtubePublishAt) form.append('youtube_publish_at', options.youtubePublishAt);
260
348
  if (options.youtubePlaylistId) {
261
349
  const playlistIds = Array.isArray(options.youtubePlaylistId)
262
350
  ? options.youtubePlaylistId.join(',')
@@ -293,6 +381,25 @@ export class UploadPost {
293
381
  if (isText && (options.linkedinLinkUrl || options.linkUrl)) {
294
382
  form.append('linkedin_link_url', options.linkedinLinkUrl || options.linkUrl);
295
383
  }
384
+ this._appendField(form, 'linkedin_alt_text', this._pick(options, 'linkedinAltText', 'linkedin_alt_text'));
385
+ this._appendField(form, 'linkedin_disable_reshare', this._pick(options, 'linkedinDisableReshare', 'linkedin_disable_reshare'));
386
+ this._appendField(form, 'linkedin_link_title', this._pick(options, 'linkedinLinkTitle', 'linkedin_link_title'));
387
+ this._appendField(form, 'linkedin_link_description', this._pick(options, 'linkedinLinkDescription', 'linkedin_link_description'));
388
+ this._appendField(form, 'linkedin_thumbnail_alt_text', this._pick(options, 'linkedinThumbnailAltText', 'linkedin_thumbnail_alt_text'));
389
+ this._appendField(form, 'linkedin_target_geo_locations', this._pick(options, 'linkedinTargetGeoLocations', 'linkedin_target_geo_locations'));
390
+ this._appendField(form, 'linkedin_target_industries', this._pick(options, 'linkedinTargetIndustries', 'linkedin_target_industries'));
391
+ this._appendField(form, 'linkedin_target_seniorities', this._pick(options, 'linkedinTargetSeniorities', 'linkedin_target_seniorities'));
392
+ this._appendField(form, 'linkedin_target_job_functions', this._pick(options, 'linkedinTargetJobFunctions', 'linkedin_target_job_functions'));
393
+ this._appendField(form, 'linkedin_target_staff_count_ranges', this._pick(options, 'linkedinTargetStaffCountRanges', 'linkedin_target_staff_count_ranges'));
394
+ this._appendField(form, 'linkedin_target_interface_locales', this._pick(options, 'linkedinTargetInterfaceLocales', 'linkedin_target_interface_locales'));
395
+ this._appendField(form, 'linkedin_target_degrees', this._pick(options, 'linkedinTargetDegrees', 'linkedin_target_degrees'));
396
+ this._appendField(form, 'linkedin_target_fields_of_study', this._pick(options, 'linkedinTargetFieldsOfStudy', 'linkedin_target_fields_of_study'));
397
+ this._appendField(form, 'linkedin_target_organizations', this._pick(options, 'linkedinTargetOrganizations', 'linkedin_target_organizations'));
398
+ this._appendField(form, 'linkedin_target_entities', this._pick(options, 'linkedinTargetEntities', 'linkedin_target_entities'));
399
+ this._appendField(form, 'linkedin_target_check_audience', this._pick(options, 'linkedinTargetCheckAudience', 'linkedin_target_check_audience'));
400
+ this._appendFileField(form, 'linkedin_subtitles', this._pick(options, 'linkedinSubtitles', 'linkedin_subtitles'));
401
+ this._appendField(form, 'linkedin_subtitles_url', this._pick(options, 'linkedinSubtitlesUrl', 'linkedin_subtitles_url'));
402
+ this._appendField(form, 'linkedin_subtitles_text', this._pick(options, 'linkedinSubtitlesText', 'linkedin_subtitles_text'));
296
403
  }
297
404
 
298
405
  /**
@@ -301,16 +408,34 @@ export class UploadPost {
301
408
  */
302
409
  _addFacebookParams(form, options, isVideo = false, isText = false) {
303
410
  if (options.facebookPageId) form.append('facebook_page_id', options.facebookPageId);
304
-
411
+
412
+ this._appendField(form, 'facebook_place_id', this._pick(options, 'facebookPlaceId', 'facebook_place_id'));
413
+ this._appendField(form, 'facebook_targeting', this._pick(options, 'facebookTargeting', 'facebook_targeting'));
414
+ this._appendField(form, 'facebook_feed_targeting', this._pick(options, 'facebookFeedTargeting', 'facebook_feed_targeting'));
415
+
305
416
  if (isVideo) {
306
417
  if (options.facebookVideoState) form.append('video_state', options.facebookVideoState);
307
418
  if (options.facebookMediaType) form.append('facebook_media_type', options.facebookMediaType);
308
419
  if (options.thumbnailUrl) form.append('thumbnail_url', options.thumbnailUrl);
420
+ this._appendField(form, 'facebook_is_ai_generated', this._pick(options, 'facebookIsAiGenerated', 'facebook_is_ai_generated'));
421
+ this._appendField(form, 'facebook_unpublished_content_type', this._pick(options, 'facebookUnpublishedContentType', 'facebook_unpublished_content_type'));
422
+ this._appendField(form, 'facebook_no_story', this._pick(options, 'facebookNoStory', 'facebook_no_story'));
423
+ this._appendField(form, 'facebook_secret', this._pick(options, 'facebookSecret', 'facebook_secret'));
424
+ this._appendField(form, 'facebook_collaborators', this._pick(options, 'facebookCollaborators', 'facebook_collaborators'));
309
425
  }
310
-
426
+
427
+ if (!isVideo && !isText) {
428
+ this._appendField(form, 'facebook_alt_text', this._pick(options, 'facebookAltText', 'facebook_alt_text'));
429
+ }
430
+
311
431
  if (isText && options.facebookLinkUrl) {
312
432
  form.append('facebook_link_url', options.facebookLinkUrl);
313
433
  }
434
+ if (isText) {
435
+ this._appendField(form, 'facebook_call_to_action', this._pick(options, 'facebookCallToAction', 'facebook_call_to_action'));
436
+ this._appendField(form, 'facebook_child_attachments', this._pick(options, 'facebookChildAttachments', 'facebook_child_attachments'));
437
+ this._appendField(form, 'facebook_multi_share_end_card', this._pick(options, 'facebookMultiShareEndCard', 'facebook_multi_share_end_card'));
438
+ }
314
439
  }
315
440
 
316
441
  /**
@@ -321,6 +446,15 @@ export class UploadPost {
321
446
  if (options.pinterestBoardId) form.append('pinterest_board_id', options.pinterestBoardId);
322
447
  if (options.pinterestAltText) form.append('pinterest_alt_text', options.pinterestAltText);
323
448
  if (options.pinterestLink) form.append('pinterest_link', options.pinterestLink);
449
+ this._appendField(form, 'pinterest_board_section_id', this._pick(options, 'pinterestBoardSectionId', 'pinterest_board_section_id'));
450
+ this._appendField(form, 'pinterest_ai_disclosures', this._pick(options, 'pinterestAiDisclosures', 'pinterest_ai_disclosures'));
451
+
452
+ if (!isVideo) {
453
+ this._appendRepeated(form, 'pinterest_carousel_titles[]', this._pick(options, 'pinterestCarouselTitles', 'pinterest_carousel_titles'));
454
+ this._appendRepeated(form, 'pinterest_carousel_descriptions[]', this._pick(options, 'pinterestCarouselDescriptions', 'pinterest_carousel_descriptions'));
455
+ this._appendRepeated(form, 'pinterest_carousel_links[]', this._pick(options, 'pinterestCarouselLinks', 'pinterest_carousel_links'));
456
+ this._appendField(form, 'pinterest_carousel_index', this._pick(options, 'pinterestCarouselIndex', 'pinterest_carousel_index'));
457
+ }
324
458
 
325
459
  if (isVideo) {
326
460
  if (options.pinterestCoverImageUrl) form.append('pinterest_cover_image_url', options.pinterestCoverImageUrl);
@@ -344,6 +478,7 @@ export class UploadPost {
344
478
  if (options.xShareWithFollowers !== undefined) form.append('share_with_followers', String(options.xShareWithFollowers));
345
479
  if (options.xDirectMessageDeepLink) form.append('direct_message_deep_link', options.xDirectMessageDeepLink);
346
480
  if (options.xLongTextAsPost !== undefined) form.append('x_long_text_as_post', String(options.xLongTextAsPost));
481
+ this._appendField(form, 'x_paid_partnership', this._pick(options, 'xPaidPartnership', 'x_paid_partnership'));
347
482
 
348
483
  if (!isText) {
349
484
  if (options.xTaggedUserIds) {
@@ -352,10 +487,15 @@ export class UploadPost {
352
487
  }
353
488
  if (options.xPlaceId) form.append('place_id', options.xPlaceId);
354
489
  if (options.xThreadImageLayout) form.append('x_thread_image_layout', options.xThreadImageLayout);
490
+ this._appendField(form, 'x_alt_text', this._pick(options, 'xAltText', 'x_alt_text'));
491
+ this._appendField(form, 'x_subtitles_url', this._pick(options, 'xSubtitlesUrl', 'x_subtitles_url'));
492
+ this._appendField(form, 'x_subtitles', this._pick(options, 'xSubtitles', 'x_subtitles'));
493
+ this._appendField(form, 'x_subtitles_language', this._pick(options, 'xSubtitlesLanguage', 'x_subtitles_language'));
494
+ this._appendField(form, 'x_subtitles_name', this._pick(options, 'xSubtitlesName', 'x_subtitles_name'));
355
495
  } else {
356
496
  if (options.xPostUrl) form.append('post_url', options.xPostUrl);
357
497
  if (options.xCardUri) form.append('card_uri', options.xCardUri);
358
-
498
+
359
499
  // Poll options
360
500
  if (options.xPollOptions) {
361
501
  const pollOpts = Array.isArray(options.xPollOptions) ? options.xPollOptions : options.xPollOptions.split(',').map(t => t.trim());
@@ -363,6 +503,11 @@ export class UploadPost {
363
503
  if (options.xPollDuration) form.append('poll_duration', options.xPollDuration);
364
504
  if (options.xPollReplySettings) form.append('poll_reply_settings', options.xPollReplySettings);
365
505
  }
506
+ this._appendField(form, 'x_article_title', this._pick(options, 'xArticleTitle', 'x_article_title'));
507
+ this._appendField(form, 'x_article_body', this._pick(options, 'xArticleBody', 'x_article_body'));
508
+ this._appendField(form, 'x_article_content_state', this._pick(options, 'xArticleContentState', 'x_article_content_state'));
509
+ this._appendField(form, 'x_article_draft', this._pick(options, 'xArticleDraft', 'x_article_draft'));
510
+ this._appendFileField(form, 'x_article_cover_media', this._pick(options, 'xArticleCoverMedia', 'x_article_cover_media'));
366
511
  }
367
512
  }
368
513
 
@@ -374,6 +519,13 @@ export class UploadPost {
374
519
  if (options.threadsLongTextAsPost !== undefined) form.append('threads_long_text_as_post', String(options.threadsLongTextAsPost));
375
520
  if (options.threadsThreadMediaLayout) form.append('threads_thread_media_layout', options.threadsThreadMediaLayout);
376
521
  if (options.threadsTopicTag) form.append('threads_topic_tag', options.threadsTopicTag);
522
+ this._appendField(form, 'threads_reply_control', this._pick(options, 'threadsReplyControl', 'threads_reply_control'));
523
+ this._appendField(form, 'threads_alt_text', this._pick(options, 'threadsAltText', 'threads_alt_text'));
524
+ this._appendField(form, 'threads_reply_to_id', this._pick(options, 'threadsReplyToId', 'threads_reply_to_id'));
525
+ this._appendField(form, 'threads_quote_post_id', this._pick(options, 'threadsQuotePostId', 'threads_quote_post_id'));
526
+ this._appendField(form, 'threads_link_attachment', this._pick(options, 'threadsLinkAttachment', 'threads_link_attachment'));
527
+ this._appendField(form, 'threads_poll_options', this._pick(options, 'threadsPollOptions', 'threads_poll_options'));
528
+ this._appendField(form, 'threads_auto_publish_text', this._pick(options, 'threadsAutoPublishText', 'threads_auto_publish_text'));
377
529
  }
378
530
 
379
531
  /**
@@ -404,6 +556,10 @@ export class UploadPost {
404
556
  if (options.gbpOfferCoupon) form.append('gbp_offer_coupon', options.gbpOfferCoupon);
405
557
  if (options.gbpOfferRedeemUrl) form.append('gbp_offer_redeem_url', options.gbpOfferRedeemUrl);
406
558
  if (options.gbpOfferTerms) form.append('gbp_offer_terms', options.gbpOfferTerms);
559
+ this._appendField(form, 'gbp_language_code', this._pick(options, 'gbpLanguageCode', 'gbp_language_code'));
560
+ this._appendField(form, 'gbp_coupon_code', this._pick(options, 'gbpCouponCode', 'gbp_coupon_code'));
561
+ this._appendField(form, 'gbp_redeem_url', this._pick(options, 'gbpRedeemUrl', 'gbp_redeem_url'));
562
+ this._appendField(form, 'gbp_terms', this._pick(options, 'gbpTerms', 'gbp_terms'));
407
563
  }
408
564
 
409
565
  /**
@@ -416,6 +572,200 @@ export class UploadPost {
416
572
  if (isText && (options.redditLinkUrl || options.linkUrl)) {
417
573
  form.append('reddit_link_url', options.redditLinkUrl || options.linkUrl);
418
574
  }
575
+ this._appendField(form, 'reddit_nsfw', this._pick(options, 'redditNsfw', 'reddit_nsfw'));
576
+ this._appendField(form, 'reddit_spoiler', this._pick(options, 'redditSpoiler', 'reddit_spoiler'));
577
+ this._appendField(form, 'reddit_resubmit', this._pick(options, 'redditResubmit', 'reddit_resubmit'));
578
+ this._appendField(form, 'reddit_send_replies', this._pick(options, 'redditSendReplies', 'reddit_send_replies'));
579
+ this._appendField(form, 'reddit_flair_text', this._pick(options, 'redditFlairText', 'reddit_flair_text'));
580
+ this._appendField(form, 'reddit_gallery_captions', this._pick(options, 'redditGalleryCaptions', 'reddit_gallery_captions'));
581
+ this._appendField(form, 'reddit_gallery_urls', this._pick(options, 'redditGalleryUrls', 'reddit_gallery_urls'));
582
+ }
583
+
584
+ /**
585
+ * Add Bluesky-specific parameters
586
+ * @private
587
+ */
588
+ _addBlueskyParams(form, options) {
589
+ this._appendField(form, 'bluesky_alt_text', this._pick(options, 'blueskyAltText', 'bluesky_alt_text'));
590
+ this._appendField(form, 'bluesky_langs', this._pick(options, 'blueskyLangs', 'bluesky_langs'));
591
+ this._appendField(form, 'bluesky_labels', this._pick(options, 'blueskyLabels', 'bluesky_labels'));
592
+ this._appendField(form, 'bluesky_gallery', this._pick(options, 'blueskyGallery', 'bluesky_gallery'));
593
+ this._appendField(form, 'bluesky_threadgate', this._pick(options, 'blueskyThreadgate', 'bluesky_threadgate', 'blueskyReplySettings', 'bluesky_reply_settings'));
594
+ this._appendField(form, 'bluesky_postgate', this._pick(options, 'blueskyPostgate', 'bluesky_postgate', 'blueskyQuoteSettings', 'bluesky_quote_settings'));
595
+ this._appendField(form, 'bluesky_quote_uri', this._pick(options, 'blueskyQuoteUri', 'bluesky_quote_uri', 'blueskyQuoteId', 'bluesky_quote_id', 'blueskyQuoteUrl', 'bluesky_quote_url'));
596
+ }
597
+
598
+ /**
599
+ * Add Discord-specific parameters
600
+ * @private
601
+ */
602
+ _addDiscordParams(form, options) {
603
+ this._appendField(form, 'discord_thread_id', this._pick(options, 'discordThreadId', 'discord_thread_id'));
604
+ this._appendField(form, 'discord_thread_name', this._pick(options, 'discordThreadName', 'discord_thread_name'));
605
+ this._appendField(form, 'discord_applied_tags', this._pick(options, 'discordAppliedTags', 'discord_applied_tags'));
606
+ this._appendField(form, 'discord_embeds', this._pick(options, 'discordEmbeds', 'discord_embeds'));
607
+ this._appendField(form, 'discord_username', this._pick(options, 'discordUsername', 'discord_username'));
608
+ this._appendField(form, 'discord_avatar_url', this._pick(options, 'discordAvatarUrl', 'discord_avatar_url'));
609
+ this._appendField(form, 'discord_allowed_mentions', this._pick(options, 'discordAllowedMentions', 'discord_allowed_mentions'));
610
+ this._appendField(form, 'discord_alt_text', this._pick(options, 'discordAltText', 'discord_alt_text'));
611
+ this._appendField(form, 'discord_flags', this._pick(options, 'discordFlags', 'discord_flags'));
612
+ this._appendField(form, 'discord_tts', this._pick(options, 'discordTts', 'discord_tts'));
613
+ this._appendField(form, 'discord_poll', this._pick(options, 'discordPoll', 'discord_poll'));
614
+ this._appendField(form, 'discord_max_file_mb', this._pick(options, 'discordMaxFileMb', 'discord_max_file_mb'));
615
+ }
616
+
617
+ /**
618
+ * Add Telegram-specific parameters
619
+ * @private
620
+ */
621
+ _addTelegramParams(form, options) {
622
+ this._appendField(form, 'telegram_parse_mode', this._pick(options, 'telegramParseMode', 'telegram_parse_mode'));
623
+ this._appendField(form, 'telegram_message_thread_id', this._pick(options, 'telegramMessageThreadId', 'telegram_message_thread_id'));
624
+ this._appendField(form, 'telegram_disable_notification', this._pick(options, 'telegramDisableNotification', 'telegram_disable_notification'));
625
+ this._appendField(form, 'telegram_protect_content', this._pick(options, 'telegramProtectContent', 'telegram_protect_content'));
626
+ this._appendField(form, 'telegram_has_spoiler', this._pick(options, 'telegramHasSpoiler', 'telegram_has_spoiler'));
627
+ this._appendField(form, 'telegram_link_preview', this._pick(options, 'telegramLinkPreview', 'telegram_link_preview'));
628
+ this._appendField(form, 'telegram_reply_markup', this._pick(options, 'telegramReplyMarkup', 'telegram_reply_markup'));
629
+ this._appendField(form, 'telegram_caption_overflow', this._pick(options, 'telegramCaptionOverflow', 'telegram_caption_overflow'));
630
+ this._appendField(form, 'telegram_as_document', this._pick(options, 'telegramAsDocument', 'telegram_as_document'));
631
+ this._appendField(form, 'telegram_media_urls', this._pick(options, 'telegramMediaUrls', 'telegram_media_urls'));
632
+ }
633
+
634
+ /**
635
+ * Add Mastodon-specific parameters
636
+ * @private
637
+ */
638
+ _addMastodonParams(form, options) {
639
+ this._appendField(form, 'mastodon_visibility', this._pick(options, 'mastodonVisibility', 'mastodon_visibility'));
640
+ this._appendField(form, 'mastodon_sensitive', this._pick(options, 'mastodonSensitive', 'mastodon_sensitive'));
641
+ this._appendField(form, 'mastodon_spoiler_text', this._pick(options, 'mastodonSpoilerText', 'mastodon_spoiler_text'));
642
+ this._appendField(form, 'mastodon_language', this._pick(options, 'mastodonLanguage', 'mastodon_language'));
643
+ this._appendField(form, 'mastodon_alt_text', this._pick(options, 'mastodonAltText', 'mastodon_alt_text'));
644
+ this._appendField(form, 'mastodon_poll_options', this._pick(options, 'mastodonPollOptions', 'mastodon_poll_options'));
645
+ this._appendField(form, 'mastodon_poll_expires_in', this._pick(options, 'mastodonPollExpiresIn', 'mastodon_poll_expires_in'));
646
+ this._appendField(form, 'mastodon_poll_multiple', this._pick(options, 'mastodonPollMultiple', 'mastodon_poll_multiple'));
647
+ this._appendField(form, 'mastodon_scheduled_at', this._pick(options, 'mastodonScheduledAt', 'mastodon_scheduled_at'));
648
+ }
649
+
650
+ /**
651
+ * Add WordPress-specific parameters
652
+ * @private
653
+ */
654
+ _addWordpressParams(form, options) {
655
+ this._appendField(form, 'wordpress_status', this._pick(options, 'wordpressStatus', 'wordpress_status'));
656
+ this._appendField(form, 'wordpress_date', this._pick(options, 'wordpressDate', 'wordpress_date'));
657
+ this._appendField(form, 'wordpress_categories', this._pick(options, 'wordpressCategories', 'wordpress_categories'));
658
+ this._appendField(form, 'wordpress_tags', this._pick(options, 'wordpressTags', 'wordpress_tags'));
659
+ this._appendField(form, 'wordpress_excerpt', this._pick(options, 'wordpressExcerpt', 'wordpress_excerpt'));
660
+ this._appendField(form, 'wordpress_slug', this._pick(options, 'wordpressSlug', 'wordpress_slug'));
661
+ this._appendField(form, 'wordpress_alt_text', this._pick(options, 'wordpressAltText', 'wordpress_alt_text'));
662
+ this._appendField(form, 'wordpress_media_caption', this._pick(options, 'wordpressMediaCaption', 'wordpress_media_caption'));
663
+ this._appendField(form, 'wordpress_block_format', this._pick(options, 'wordpressBlockFormat', 'wordpress_block_format'));
664
+ }
665
+
666
+ /**
667
+ * Add Lemmy-specific parameters
668
+ * @private
669
+ */
670
+ _addLemmyParams(form, options) {
671
+ this._appendField(form, 'lemmy_url', this._pick(options, 'lemmyUrl', 'lemmy_url'));
672
+ this._appendField(form, 'lemmy_community', this._pick(options, 'lemmyCommunity', 'lemmy_community'));
673
+ this._appendField(form, 'lemmy_nsfw', this._pick(options, 'lemmyNsfw', 'lemmy_nsfw'));
674
+ this._appendField(form, 'lemmy_language_id', this._pick(options, 'lemmyLanguageId', 'lemmy_language_id'));
675
+ this._appendField(form, 'lemmy_alt_text', this._pick(options, 'lemmyAltText', 'lemmy_alt_text'));
676
+ }
677
+
678
+ /**
679
+ * Add Slack-specific parameters
680
+ * @private
681
+ */
682
+ _addSlackParams(form, options) {
683
+ this._appendField(form, 'slack_markdown', this._pick(options, 'slackMarkdown', 'slack_markdown'));
684
+ this._appendField(form, 'slack_blocks', this._pick(options, 'slackBlocks', 'slack_blocks'));
685
+ this._appendField(form, 'slack_mrkdwn', this._pick(options, 'slackMrkdwn', 'slack_mrkdwn'));
686
+ this._appendField(form, 'slack_alt_text', this._pick(options, 'slackAltText', 'slack_alt_text'));
687
+ this._appendField(form, 'slack_first_comment_mode', this._pick(options, 'slackFirstCommentMode', 'slack_first_comment_mode'));
688
+ }
689
+
690
+ /**
691
+ * Add Nostr-specific parameters
692
+ * @private
693
+ */
694
+ _addNostrParams(form, options) {
695
+ this._appendField(form, 'nostr_kind', this._pick(options, 'nostrKind', 'nostr_kind'));
696
+ this._appendField(form, 'nostr_long_form', this._pick(options, 'nostrLongForm', 'nostr_long_form'));
697
+ }
698
+
699
+ /**
700
+ * Add Dev.to-specific parameters
701
+ * @private
702
+ */
703
+ _addDevtoParams(form, options) {
704
+ this._appendField(form, 'devto_tags', this._pick(options, 'devtoTags', 'devto_tags'));
705
+ this._appendField(form, 'devto_canonical_url', this._pick(options, 'devtoCanonicalUrl', 'devto_canonical_url'));
706
+ this._appendField(form, 'devto_description', this._pick(options, 'devtoDescription', 'devto_description'));
707
+ this._appendField(form, 'devto_main_image', this._pick(options, 'devtoMainImage', 'devto_main_image'));
708
+ this._appendField(form, 'devto_series', this._pick(options, 'devtoSeries', 'devto_series'));
709
+ this._appendField(form, 'devto_published', this._pick(options, 'devtoPublished', 'devto_published'));
710
+ }
711
+
712
+ /**
713
+ * Add Hashnode-specific parameters
714
+ * @private
715
+ */
716
+ _addHashnodeParams(form, options) {
717
+ this._appendField(form, 'hashnode_tags', this._pick(options, 'hashnodeTags', 'hashnode_tags'));
718
+ this._appendField(form, 'hashnode_original_article_url', this._pick(options, 'hashnodeOriginalArticleUrl', 'hashnode_original_article_url'));
719
+ this._appendField(form, 'hashnode_subtitle', this._pick(options, 'hashnodeSubtitle', 'hashnode_subtitle'));
720
+ this._appendField(form, 'hashnode_cover_image_url', this._pick(options, 'hashnodeCoverImageUrl', 'hashnode_cover_image_url'));
721
+ this._appendField(form, 'hashnode_draft', this._pick(options, 'hashnodeDraft', 'hashnode_draft'));
722
+ this._appendField(form, 'hashnode_body', this._pick(options, 'hashnodeBody', 'hashnode_body', 'content'));
723
+ }
724
+
725
+ /**
726
+ * Add Whop-specific parameters
727
+ * @private
728
+ */
729
+ _addWhopParams(form, options) {
730
+ this._appendField(form, 'whop_body', this._pick(options, 'whopBody', 'whop_body'));
731
+ this._appendField(form, 'whop_pinned', this._pick(options, 'whopPinned', 'whop_pinned'));
732
+ this._appendField(form, 'whop_is_mention', this._pick(options, 'whopIsMention', 'whop_is_mention'));
733
+ this._appendField(form, 'whop_paywall_amount', this._pick(options, 'whopPaywallAmount', 'whop_paywall_amount'));
734
+ this._appendField(form, 'whop_paywall_currency', this._pick(options, 'whopPaywallCurrency', 'whop_paywall_currency'));
735
+ this._appendField(form, 'whop_attachment_ids', this._pick(options, 'whopAttachmentIds', 'whop_attachment_ids'));
736
+ }
737
+
738
+ /**
739
+ * Add Listmonk-specific parameters
740
+ * @private
741
+ */
742
+ _addListmonkParams(form, options) {
743
+ this._appendField(form, 'listmonk_content_type', this._pick(options, 'listmonkContentType', 'listmonk_content_type'));
744
+ this._appendField(form, 'listmonk_send_at', this._pick(options, 'listmonkSendAt', 'listmonk_send_at'));
745
+ this._appendField(form, 'listmonk_lists', this._pick(options, 'listmonkLists', 'listmonk_lists'));
746
+ this._appendField(form, 'listmonk_template_id', this._pick(options, 'listmonkTemplateId', 'listmonk_template_id'));
747
+ this._appendField(form, 'listmonk_media_ids', this._pick(options, 'listmonkMediaIds', 'listmonk_media_ids'));
748
+ }
749
+
750
+ /**
751
+ * Credential-channel and Bluesky helpers. Called from every upload verb so
752
+ * unlisted fields are not dropped when those platforms are in `platforms`.
753
+ * @private
754
+ */
755
+ _addCredentialPlatformParams(form, options) {
756
+ const platforms = Array.isArray(options.platforms) ? options.platforms : [options.platforms];
757
+ if (platforms.includes('bluesky')) this._addBlueskyParams(form, options);
758
+ if (platforms.includes('discord')) this._addDiscordParams(form, options);
759
+ if (platforms.includes('telegram')) this._addTelegramParams(form, options);
760
+ if (platforms.includes('mastodon')) this._addMastodonParams(form, options);
761
+ if (platforms.includes('wordpress')) this._addWordpressParams(form, options);
762
+ if (platforms.includes('lemmy')) this._addLemmyParams(form, options);
763
+ if (platforms.includes('slack')) this._addSlackParams(form, options);
764
+ if (platforms.includes('nostr')) this._addNostrParams(form, options);
765
+ if (platforms.includes('devto')) this._addDevtoParams(form, options);
766
+ if (platforms.includes('hashnode')) this._addHashnodeParams(form, options);
767
+ if (platforms.includes('whop')) this._addWhopParams(form, options);
768
+ if (platforms.includes('listmonk')) this._addListmonkParams(form, options);
419
769
  }
420
770
 
421
771
  /**
@@ -440,9 +790,11 @@ export class UploadPost {
440
790
  * @param {boolean} [options.tiktokDisableStitch] - Disable stitch
441
791
  * @param {number} [options.tiktokCoverTimestamp] - Timestamp in ms for video cover
442
792
  * @param {boolean} [options.tiktokIsAigc] - AI-generated content flag
443
- * @param {string} [options.tiktokPostMode] - DIRECT_POST or MEDIA_UPLOAD
793
+ * @param {string} [options.tiktokPostMode] - DIRECT_POST or MEDIA_UPLOAD. MEDIA_UPLOAD is the same draft as tiktokUploadToDraft
444
794
  * @param {boolean} [options.brandContentToggle] - Branded content toggle
445
795
  * @param {boolean} [options.brandOrganicToggle] - Brand organic toggle
796
+ * @param {boolean} [options.tiktokIsAdsOnly] - Only show the video in ads
797
+ * @param {string} [options.tiktokTtoInviteLink] - TikTok One invite link (requires branded content)
446
798
  *
447
799
  * TikTok music, location, cover and draft options. Available on connections that
448
800
  * declare the matching `capabilities` (music, location, cover_image, draft) on the
@@ -459,7 +811,7 @@ export class UploadPost {
459
811
  * @param {string} [options.tiktokLocationName] - Location name, required whenever tiktokLocationId is set
460
812
  * @param {string} [options.tiktokCoverImageUrl] - Custom cover image URL
461
813
  * @param {boolean} [options.tiktokIsAiGenerated] - AI-generated content disclosure
462
- * @param {boolean} [options.tiktokUploadToDraft] - Publish to drafts. When true TikTok ignores the rest of the post settings
814
+ * @param {boolean} [options.tiktokUploadToDraft] - Publish to drafts (same as postMode MEDIA_UPLOAD). When true TikTok ignores the rest of the post settings
463
815
  *
464
816
  * Instagram options:
465
817
  * @param {string} [options.instagramMediaType] - REELS or STORIES
@@ -548,6 +900,7 @@ export class UploadPost {
548
900
  if (platforms.includes('threads')) this._addThreadsParams(form, options);
549
901
  if (platforms.includes('reddit')) this._addRedditParams(form, options);
550
902
  if (platforms.includes('google_business')) this._addGoogleBusinessParams(form, options);
903
+ this._addCredentialPlatformParams(form, options);
551
904
 
552
905
  return this._request('/upload', 'POST', form, true, this._idempotencyHeaders(options));
553
906
  }
@@ -576,16 +929,20 @@ export class UploadPost {
576
929
  * @param {boolean} [options.brandContentToggle] - Branded content toggle
577
930
  * @param {boolean} [options.brandOrganicToggle] - Brand organic toggle
578
931
  *
579
- * TikTok music, location and AI disclosure on photo posts. TikTok's photo contract
580
- * takes the track id alone — no volume/trim, no custom cover, no draft (those are
581
- * video-only). Available on connections that declare the matching `capabilities`
582
- * (music, location) on the TikTok account returned by listUsers(); otherwise the
583
- * field is ignored, the post still publishes, and the response includes a
584
- * per-field `warnings` entry.
932
+ * TikTok music, location, AI disclosure and draft on photo posts. TikTok's
933
+ * photo contract takes the track id alone — no volume/trim, no custom cover
934
+ * (those are video-only). `tiktokUploadToDraft` and `postMode: 'MEDIA_UPLOAD'`
935
+ * are the same draft; both are passed through. Available on connections that
936
+ * declare the matching `capabilities` (music, location, draft) on the TikTok
937
+ * account returned by listUsers(); otherwise the field is ignored, the post
938
+ * still publishes, and the response includes a per-field `warnings` entry.
585
939
  * @param {string} [options.tiktokMusicId] - Commercial Music Library track id (see getTiktokTrendingMusic)
586
940
  * @param {string} [options.tiktokLocationId] - Location id (see getTiktokLocations)
587
941
  * @param {string} [options.tiktokLocationName] - Location name, required whenever tiktokLocationId is set
588
942
  * @param {boolean} [options.tiktokIsAiGenerated] - AI-generated content disclosure
943
+ * @param {string} [options.tiktokPostMode] - DIRECT_POST or MEDIA_UPLOAD. MEDIA_UPLOAD is the same draft as tiktokUploadToDraft
944
+ * @param {boolean} [options.tiktokUploadToDraft] - Publish to drafts. Same draft as postMode MEDIA_UPLOAD
945
+ * @param {string} [options.instagramAltText] - Alt text for photos (string or list, ≤1000 chars each)
589
946
  *
590
947
  * Instagram options:
591
948
  * @param {string} [options.instagramMediaType] - IMAGE or STORIES
@@ -616,7 +973,7 @@ export class UploadPost {
616
973
  * @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
617
974
  * @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
618
975
  *
619
- * Reddit options:
976
+ * Reddit options (currently unavailable: HTTP 503, error_code=reddit_unavailable):
620
977
  * @param {string} [options.redditSubreddit] - Subreddit name (without r/)
621
978
  * @param {string} [options.redditFlairId] - Flair template ID
622
979
  *
@@ -649,6 +1006,7 @@ export class UploadPost {
649
1006
  if (platforms.includes('threads')) this._addThreadsParams(form, options);
650
1007
  if (platforms.includes('reddit')) this._addRedditParams(form, options);
651
1008
  if (platforms.includes('google_business')) this._addGoogleBusinessParams(form, options);
1009
+ this._addCredentialPlatformParams(form, options);
652
1010
 
653
1011
  return this._request('/upload_photos', 'POST', form, true, this._idempotencyHeaders(options));
654
1012
  }
@@ -693,7 +1051,7 @@ export class UploadPost {
693
1051
  * @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
694
1052
  * @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
695
1053
  *
696
- * Reddit options:
1054
+ * Reddit options (currently unavailable: HTTP 503, error_code=reddit_unavailable):
697
1055
  * @param {string} [options.redditSubreddit] - Subreddit name (without r/)
698
1056
  * @param {string} [options.redditFlairId] - Flair template ID
699
1057
  *
@@ -717,6 +1075,7 @@ export class UploadPost {
717
1075
  form.append('bluesky_link_url', options.blueskyLinkUrl || options.linkUrl);
718
1076
  }
719
1077
  if (platforms.includes('google_business')) this._addGoogleBusinessParams(form, options);
1078
+ this._addCredentialPlatformParams(form, options);
720
1079
 
721
1080
  return this._request('/upload_text', 'POST', form, true, this._idempotencyHeaders(options));
722
1081
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "upload-post",
3
- "version": "2.12.0",
3
+ "version": "2.14.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",
7
7
  "type": "module",
8
8
  "scripts": {
9
- "test": "node --test test/"
9
+ "test": "node --test test/*.test.js"
10
10
  },
11
11
  "license": "MIT",
12
12
  "keywords": [