upload-post 2.13.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 +110 -9
  2. package/index.d.ts +364 -17
  3. package/index.js +371 -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
@@ -179,14 +244,13 @@ export class UploadPost {
179
244
  ['brand_content_toggle', 'brandContentToggle', 'flag'],
180
245
  ['brand_organic_toggle', 'brandOrganicToggle', 'flag'],
181
246
  ['privacy_level', 'tiktokPrivacyLevel', 'text'],
182
- ['post_mode', 'tiktokPostMode', 'text'],
183
247
  ['tiktok_music_id', 'tiktokMusicId', 'text'],
184
248
  ['tiktok_location_id', 'tiktokLocationId', 'text'],
185
249
  ['tiktok_location_name', 'tiktokLocationName', 'text'],
186
250
  ['tiktok_is_ai_generated', 'tiktokIsAiGenerated', 'flag'],
187
251
  ];
188
252
  // Video-only: TikTok's photo contract takes the track id alone, with no
189
- // volume or trim, and has no custom cover or draft switch.
253
+ // volume or trim, and has no custom cover.
190
254
  const videoOnly = [
191
255
  ['disable_duet', 'tiktokDisableDuet', 'flag'],
192
256
  ['disable_stitch', 'tiktokDisableStitch', 'flag'],
@@ -197,7 +261,6 @@ export class UploadPost {
197
261
  ['tiktok_music_end', 'tiktokMusicEnd', 'flag'],
198
262
  ['tiktok_original_sound_volume', 'tiktokOriginalSoundVolume', 'flag'],
199
263
  ['tiktok_cover_image_url', 'tiktokCoverImageUrl', 'text'],
200
- ['tiktok_upload_to_draft', 'tiktokUploadToDraft', 'flag'],
201
264
  ];
202
265
  const photoOnly = [
203
266
  ['auto_add_music', 'tiktokAutoAddMusic', 'flag'],
@@ -209,6 +272,23 @@ export class UploadPost {
209
272
  if (kind === 'flag' ? value === undefined : !value) continue;
210
273
  form.append(field, String(value));
211
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
+ }
212
292
  }
213
293
 
214
294
  /**
@@ -235,6 +315,8 @@ export class UploadPost {
235
315
  }
236
316
  if (options.instagramAudioName) form.append('audio_name', options.instagramAudioName);
237
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'));
238
320
  }
239
321
  }
240
322
 
@@ -261,6 +343,8 @@ export class UploadPost {
261
343
  if (options.youtubeBlockedCountries) form.append('blockedCountries', options.youtubeBlockedCountries);
262
344
  if (options.youtubeHasPaidProductPlacement !== undefined) form.append('hasPaidProductPlacement', String(options.youtubeHasPaidProductPlacement));
263
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);
264
348
  if (options.youtubePlaylistId) {
265
349
  const playlistIds = Array.isArray(options.youtubePlaylistId)
266
350
  ? options.youtubePlaylistId.join(',')
@@ -297,6 +381,25 @@ export class UploadPost {
297
381
  if (isText && (options.linkedinLinkUrl || options.linkUrl)) {
298
382
  form.append('linkedin_link_url', options.linkedinLinkUrl || options.linkUrl);
299
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'));
300
403
  }
301
404
 
302
405
  /**
@@ -305,16 +408,34 @@ export class UploadPost {
305
408
  */
306
409
  _addFacebookParams(form, options, isVideo = false, isText = false) {
307
410
  if (options.facebookPageId) form.append('facebook_page_id', options.facebookPageId);
308
-
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
+
309
416
  if (isVideo) {
310
417
  if (options.facebookVideoState) form.append('video_state', options.facebookVideoState);
311
418
  if (options.facebookMediaType) form.append('facebook_media_type', options.facebookMediaType);
312
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'));
313
425
  }
314
-
426
+
427
+ if (!isVideo && !isText) {
428
+ this._appendField(form, 'facebook_alt_text', this._pick(options, 'facebookAltText', 'facebook_alt_text'));
429
+ }
430
+
315
431
  if (isText && options.facebookLinkUrl) {
316
432
  form.append('facebook_link_url', options.facebookLinkUrl);
317
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
+ }
318
439
  }
319
440
 
320
441
  /**
@@ -325,6 +446,15 @@ export class UploadPost {
325
446
  if (options.pinterestBoardId) form.append('pinterest_board_id', options.pinterestBoardId);
326
447
  if (options.pinterestAltText) form.append('pinterest_alt_text', options.pinterestAltText);
327
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
+ }
328
458
 
329
459
  if (isVideo) {
330
460
  if (options.pinterestCoverImageUrl) form.append('pinterest_cover_image_url', options.pinterestCoverImageUrl);
@@ -348,6 +478,7 @@ export class UploadPost {
348
478
  if (options.xShareWithFollowers !== undefined) form.append('share_with_followers', String(options.xShareWithFollowers));
349
479
  if (options.xDirectMessageDeepLink) form.append('direct_message_deep_link', options.xDirectMessageDeepLink);
350
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'));
351
482
 
352
483
  if (!isText) {
353
484
  if (options.xTaggedUserIds) {
@@ -356,10 +487,15 @@ export class UploadPost {
356
487
  }
357
488
  if (options.xPlaceId) form.append('place_id', options.xPlaceId);
358
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'));
359
495
  } else {
360
496
  if (options.xPostUrl) form.append('post_url', options.xPostUrl);
361
497
  if (options.xCardUri) form.append('card_uri', options.xCardUri);
362
-
498
+
363
499
  // Poll options
364
500
  if (options.xPollOptions) {
365
501
  const pollOpts = Array.isArray(options.xPollOptions) ? options.xPollOptions : options.xPollOptions.split(',').map(t => t.trim());
@@ -367,6 +503,11 @@ export class UploadPost {
367
503
  if (options.xPollDuration) form.append('poll_duration', options.xPollDuration);
368
504
  if (options.xPollReplySettings) form.append('poll_reply_settings', options.xPollReplySettings);
369
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'));
370
511
  }
371
512
  }
372
513
 
@@ -378,6 +519,13 @@ export class UploadPost {
378
519
  if (options.threadsLongTextAsPost !== undefined) form.append('threads_long_text_as_post', String(options.threadsLongTextAsPost));
379
520
  if (options.threadsThreadMediaLayout) form.append('threads_thread_media_layout', options.threadsThreadMediaLayout);
380
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'));
381
529
  }
382
530
 
383
531
  /**
@@ -408,6 +556,10 @@ export class UploadPost {
408
556
  if (options.gbpOfferCoupon) form.append('gbp_offer_coupon', options.gbpOfferCoupon);
409
557
  if (options.gbpOfferRedeemUrl) form.append('gbp_offer_redeem_url', options.gbpOfferRedeemUrl);
410
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'));
411
563
  }
412
564
 
413
565
  /**
@@ -420,6 +572,200 @@ export class UploadPost {
420
572
  if (isText && (options.redditLinkUrl || options.linkUrl)) {
421
573
  form.append('reddit_link_url', options.redditLinkUrl || options.linkUrl);
422
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);
423
769
  }
424
770
 
425
771
  /**
@@ -444,9 +790,11 @@ export class UploadPost {
444
790
  * @param {boolean} [options.tiktokDisableStitch] - Disable stitch
445
791
  * @param {number} [options.tiktokCoverTimestamp] - Timestamp in ms for video cover
446
792
  * @param {boolean} [options.tiktokIsAigc] - AI-generated content flag
447
- * @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
448
794
  * @param {boolean} [options.brandContentToggle] - Branded content toggle
449
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)
450
798
  *
451
799
  * TikTok music, location, cover and draft options. Available on connections that
452
800
  * declare the matching `capabilities` (music, location, cover_image, draft) on the
@@ -463,7 +811,7 @@ export class UploadPost {
463
811
  * @param {string} [options.tiktokLocationName] - Location name, required whenever tiktokLocationId is set
464
812
  * @param {string} [options.tiktokCoverImageUrl] - Custom cover image URL
465
813
  * @param {boolean} [options.tiktokIsAiGenerated] - AI-generated content disclosure
466
- * @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
467
815
  *
468
816
  * Instagram options:
469
817
  * @param {string} [options.instagramMediaType] - REELS or STORIES
@@ -552,6 +900,7 @@ export class UploadPost {
552
900
  if (platforms.includes('threads')) this._addThreadsParams(form, options);
553
901
  if (platforms.includes('reddit')) this._addRedditParams(form, options);
554
902
  if (platforms.includes('google_business')) this._addGoogleBusinessParams(form, options);
903
+ this._addCredentialPlatformParams(form, options);
555
904
 
556
905
  return this._request('/upload', 'POST', form, true, this._idempotencyHeaders(options));
557
906
  }
@@ -580,16 +929,20 @@ export class UploadPost {
580
929
  * @param {boolean} [options.brandContentToggle] - Branded content toggle
581
930
  * @param {boolean} [options.brandOrganicToggle] - Brand organic toggle
582
931
  *
583
- * TikTok music, location and AI disclosure on photo posts. TikTok's photo contract
584
- * takes the track id alone — no volume/trim, no custom cover, no draft (those are
585
- * video-only). Available on connections that declare the matching `capabilities`
586
- * (music, location) on the TikTok account returned by listUsers(); otherwise the
587
- * field is ignored, the post still publishes, and the response includes a
588
- * 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.
589
939
  * @param {string} [options.tiktokMusicId] - Commercial Music Library track id (see getTiktokTrendingMusic)
590
940
  * @param {string} [options.tiktokLocationId] - Location id (see getTiktokLocations)
591
941
  * @param {string} [options.tiktokLocationName] - Location name, required whenever tiktokLocationId is set
592
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)
593
946
  *
594
947
  * Instagram options:
595
948
  * @param {string} [options.instagramMediaType] - IMAGE or STORIES
@@ -620,7 +973,7 @@ export class UploadPost {
620
973
  * @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
621
974
  * @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
622
975
  *
623
- * Reddit options:
976
+ * Reddit options (currently unavailable: HTTP 503, error_code=reddit_unavailable):
624
977
  * @param {string} [options.redditSubreddit] - Subreddit name (without r/)
625
978
  * @param {string} [options.redditFlairId] - Flair template ID
626
979
  *
@@ -653,6 +1006,7 @@ export class UploadPost {
653
1006
  if (platforms.includes('threads')) this._addThreadsParams(form, options);
654
1007
  if (platforms.includes('reddit')) this._addRedditParams(form, options);
655
1008
  if (platforms.includes('google_business')) this._addGoogleBusinessParams(form, options);
1009
+ this._addCredentialPlatformParams(form, options);
656
1010
 
657
1011
  return this._request('/upload_photos', 'POST', form, true, this._idempotencyHeaders(options));
658
1012
  }
@@ -697,7 +1051,7 @@ export class UploadPost {
697
1051
  * @param {string} [options.threadsThreadMediaLayout] - Comma-separated list of how many media items per Threads post (e.g. "5,5")
698
1052
  * @param {string} [options.threadsTopicTag] - Topic tag for the Threads post (1-50 chars, no periods or ampersands)
699
1053
  *
700
- * Reddit options:
1054
+ * Reddit options (currently unavailable: HTTP 503, error_code=reddit_unavailable):
701
1055
  * @param {string} [options.redditSubreddit] - Subreddit name (without r/)
702
1056
  * @param {string} [options.redditFlairId] - Flair template ID
703
1057
  *
@@ -721,6 +1075,7 @@ export class UploadPost {
721
1075
  form.append('bluesky_link_url', options.blueskyLinkUrl || options.linkUrl);
722
1076
  }
723
1077
  if (platforms.includes('google_business')) this._addGoogleBusinessParams(form, options);
1078
+ this._addCredentialPlatformParams(form, options);
724
1079
 
725
1080
  return this._request('/upload_text', 'POST', form, true, this._idempotencyHeaders(options));
726
1081
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "upload-post",
3
- "version": "2.13.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": [