upload-post 2.1.0 → 2.2.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.
@@ -46,10 +46,6 @@ jobs:
46
46
  echo "🚀 New version detected: $REMOTE → $LOCAL"
47
47
  fi
48
48
 
49
- - name: Update npm to latest
50
- if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
51
- run: npm install -g npm@latest && npm --version
52
-
53
49
  - name: Install dependencies
54
50
  if: steps.version_check.outputs.new == 'true' || github.event_name == 'workflow_dispatch'
55
51
  run: npm ci
@@ -61,7 +57,11 @@ jobs:
61
57
  - name: Publish to npm (dry-run)
62
58
  if: ${{ inputs.dry_run == true }}
63
59
  run: npm publish --access public --dry-run
60
+ env:
61
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
64
62
 
65
63
  - name: Publish to npm
66
64
  if: (steps.version_check.outputs.new == 'true' && inputs.dry_run != true) || github.event_name == 'release'
67
- run: npm publish --access public --provenance
65
+ run: npm publish --access public
66
+ env:
67
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md CHANGED
@@ -181,6 +181,22 @@ const analytics = await client.getAnalytics('my-profile', {
181
181
  console.log(analytics);
182
182
  ```
183
183
 
184
+ ### Get Media
185
+
186
+ Retrieve recent posts from a connected social account. Supported platforms:
187
+ `instagram`, `tiktok`, `youtube`, `linkedin`, `facebook`, `x`, `threads`,
188
+ `pinterest`, `bluesky`, `reddit`.
189
+
190
+ ```javascript
191
+ const { media } = await client.getMedia('linkedin', 'my-profile');
192
+
193
+ // Force the personal LinkedIn profile of an account connected as an org admin:
194
+ await client.getMedia('linkedin', 'my-profile', { pageUrn: 'me' });
195
+
196
+ // Target a specific LinkedIn organization page:
197
+ await client.getMedia('linkedin', 'my-profile', { pageUrn: '12345' });
198
+ ```
199
+
184
200
  ### Helper Methods
185
201
 
186
202
  ```javascript
package/index.d.ts CHANGED
@@ -136,8 +136,8 @@ declare module 'upload-post' {
136
136
  instagramShareToFeed?: boolean;
137
137
  /** Comma-separated collaborator usernames */
138
138
  instagramCollaborators?: string;
139
- /** Custom cover URL */
140
- instagramCoverUrl?: string;
139
+ /** Custom cover: URL string, file path, Buffer, or ReadableStream */
140
+ instagramCoverUrl?: string | Buffer | NodeJS.ReadableStream;
141
141
  /** Audio track name */
142
142
  instagramAudioName?: string;
143
143
  /** Comma-separated user tags */
@@ -192,6 +192,17 @@ declare module 'upload-post' {
192
192
  youtubeHasPaidProductPlacement?: boolean;
193
193
  /** Recording date (ISO 8601) */
194
194
  youtubeRecordingDate?: string;
195
+ /** Subtitle/caption files to upload. Each entry needs a language code and either a file path or URL. */
196
+ youtubeSubtitles?: Array<{
197
+ /** BCP-47 language code (e.g. "en", "es") */
198
+ language: string;
199
+ /** Display name for the subtitle track (e.g. "English", "Español") */
200
+ name?: string;
201
+ /** Path to the subtitle file (SRT, VTT, SBV, SUB, ASS, SSA, TTML) */
202
+ file?: string;
203
+ /** URL to the subtitle file */
204
+ url?: string;
205
+ }>;
195
206
  }
196
207
 
197
208
  // ==================== LinkedIn Options ====================
@@ -589,6 +600,28 @@ declare module 'upload-post' {
589
600
  metric_labels: Record<string, string>;
590
601
  }>>;
591
602
 
603
+ /**
604
+ * Get recent media from a connected social account.
605
+ *
606
+ * @param options.pageUrn - LinkedIn only. Numeric org ID, full org URN, or "me" to force the personal profile.
607
+ */
608
+ getMedia(
609
+ platform: 'instagram' | 'tiktok' | 'youtube' | 'linkedin' | 'facebook' | 'x' | 'threads' | 'pinterest' | 'bluesky' | 'reddit' | string,
610
+ user: string,
611
+ options?: { pageUrn?: string }
612
+ ): Promise<{
613
+ success: boolean;
614
+ media: Array<{
615
+ id: string;
616
+ caption: string | null;
617
+ media_type: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM' | 'TEXT' | string;
618
+ media_url: string | null;
619
+ permalink: string | null;
620
+ timestamp: string | null;
621
+ thumbnail_url: string | null;
622
+ }>;
623
+ }>;
624
+
592
625
  // Scheduled Posts
593
626
  /**
594
627
  * List scheduled posts
@@ -648,6 +681,68 @@ declare module 'upload-post' {
648
681
  */
649
682
  validateJwt(jwt: string): Promise<{ success: boolean; valid?: boolean }>;
650
683
 
684
+ /**
685
+ * Get user preferences (including calendar settings)
686
+ */
687
+ getUserPreferences(): Promise<{ success: boolean; preferences?: { week_start_day?: number; [key: string]: any } }>;
688
+
689
+ /**
690
+ * Update user preferences (including calendar settings)
691
+ * @param options - Preferences options
692
+ */
693
+ updateUserPreferences(options?: { weekStartDay?: number }): Promise<{ success: boolean; preferences?: { week_start_day?: number; [key: string]: any } }>;
694
+
695
+ /**
696
+ * Get notification configuration (including webhook settings)
697
+ */
698
+ getNotificationConfig(): Promise<{ success: boolean; config?: { webhook_events?: string[]; webhook_url?: string; [key: string]: any } }>;
699
+
700
+ /**
701
+ * Update notification configuration (including webhook settings)
702
+ * @param options - Notification config options
703
+ */
704
+ updateNotificationConfig(options?: { webhookEvents?: string[]; webhookUrl?: string }): Promise<{ success: boolean; config?: { webhook_events?: string[]; webhook_url?: string; [key: string]: any } }>;
705
+
706
+ // Instagram Comments
707
+
708
+ /**
709
+ * Get comments on an Instagram post
710
+ * @param user - Profile username
711
+ * @param options - Query options
712
+ */
713
+ getPostComments(user: string, options?: { postId?: string; postUrl?: string }): Promise<{
714
+ success: boolean;
715
+ comments?: Array<{
716
+ id: string;
717
+ text: string;
718
+ timestamp: string;
719
+ user: { id: string; username: string };
720
+ }>;
721
+ }>;
722
+
723
+ /**
724
+ * Send a private reply (DM) to the author of an Instagram comment
725
+ * @param options - Reply options
726
+ */
727
+ replyToComment(options: { user: string; commentId: string; message: string }): Promise<{
728
+ success: boolean;
729
+ recipient_id?: string;
730
+ message_id?: string;
731
+ message?: string;
732
+ error?: string;
733
+ }>;
734
+
735
+ /**
736
+ * Post a public reply to an Instagram comment (visible under the original comment)
737
+ * @param options - Reply options
738
+ */
739
+ publicReplyToComment(options: { user: string; commentId: string; message: string }): Promise<{
740
+ success: boolean;
741
+ id?: string;
742
+ message?: string;
743
+ error?: string;
744
+ }>;
745
+
651
746
  // Helper Endpoints
652
747
  /**
653
748
  * Get Facebook pages for a profile
package/index.js CHANGED
@@ -160,7 +160,16 @@ export class UploadPost {
160
160
 
161
161
  if (isVideo) {
162
162
  if (options.instagramShareToFeed !== undefined) form.append('share_to_feed', String(options.instagramShareToFeed));
163
- if (options.instagramCoverUrl) form.append('cover_url', options.instagramCoverUrl);
163
+ if (options.instagramCoverUrl) {
164
+ const coverVal = options.instagramCoverUrl;
165
+ if (typeof coverVal === 'string' && (coverVal.toLowerCase().startsWith('http://') || coverVal.toLowerCase().startsWith('https://'))) {
166
+ form.append('cover_url', coverVal);
167
+ } else if (typeof coverVal === 'string') {
168
+ form.append('cover_image', createReadStream(coverVal));
169
+ } else {
170
+ form.append('cover_image', coverVal);
171
+ }
172
+ }
164
173
  if (options.instagramAudioName) form.append('audio_name', options.instagramAudioName);
165
174
  if (options.instagramThumbOffset) form.append('thumb_offset', options.instagramThumbOffset);
166
175
  }
@@ -189,6 +198,24 @@ export class UploadPost {
189
198
  if (options.youtubeBlockedCountries) form.append('blockedCountries', options.youtubeBlockedCountries);
190
199
  if (options.youtubeHasPaidProductPlacement !== undefined) form.append('hasPaidProductPlacement', String(options.youtubeHasPaidProductPlacement));
191
200
  if (options.youtubeRecordingDate) form.append('recordingDate', options.youtubeRecordingDate);
201
+ if (options.youtubeSubtitles && Array.isArray(options.youtubeSubtitles)) {
202
+ options.youtubeSubtitles.forEach((sub, idx) => {
203
+ if (sub.language) {
204
+ form.append(`youtube_subtitle_language_${idx}`, sub.language);
205
+ if (sub.name) form.append(`youtube_subtitle_name_${idx}`, sub.name);
206
+ if (sub.file) {
207
+ // file can be a string path or a ReadableStream
208
+ if (typeof sub.file === 'string') {
209
+ form.append(`youtube_subtitle_file_${idx}`, createReadStream(sub.file));
210
+ } else {
211
+ form.append(`youtube_subtitle_file_${idx}`, sub.file);
212
+ }
213
+ } else if (sub.url) {
214
+ form.append(`youtube_subtitle_file_${idx}`, sub.url);
215
+ }
216
+ }
217
+ });
218
+ }
192
219
  }
193
220
 
194
221
  /**
@@ -326,7 +353,7 @@ export class UploadPost {
326
353
  * @param {string} [options.instagramMediaType] - REELS or STORIES
327
354
  * @param {boolean} [options.instagramShareToFeed] - Share to feed
328
355
  * @param {string} [options.instagramCollaborators] - Comma-separated collaborator usernames
329
- * @param {string} [options.instagramCoverUrl] - Custom cover URL
356
+ * @param {string|Buffer|ReadableStream} [options.instagramCoverUrl] - Custom cover: URL string, file path, Buffer, or ReadableStream
330
357
  * @param {string} [options.instagramAudioName] - Audio track name
331
358
  * @param {string} [options.instagramUserTags] - Comma-separated user tags
332
359
  * @param {string} [options.instagramLocationId] - Location ID
@@ -710,6 +737,22 @@ export class UploadPost {
710
737
  return this._request(`/uploadposts/post-analytics/${encodeURIComponent(requestId)}`, 'GET');
711
738
  }
712
739
 
740
+ /**
741
+ * Get analytics for any post (including organic posts) using its native platform ID
742
+ *
743
+ * @param {string} platformPostId - The native post ID on the platform (e.g., Instagram media ID)
744
+ * @param {string} platform - The platform to query (instagram, youtube, tiktok, facebook, linkedin, x, threads, pinterest, reddit)
745
+ * @param {string} user - The profile_username that owns the social account
746
+ * @returns {Promise<Object>} Post analytics with live per-post metrics from the platform API
747
+ */
748
+ async getPostAnalyticsByPlatformId(platformPostId, platform, user) {
749
+ return this._request('/uploadposts/post-analytics', 'GET', {
750
+ platform_post_id: platformPostId,
751
+ platform,
752
+ user,
753
+ });
754
+ }
755
+
713
756
  /**
714
757
  * Get available metrics configuration for all supported platforms
715
758
  *
@@ -719,6 +762,21 @@ export class UploadPost {
719
762
  return this._request('/uploadposts/platform-metrics', 'GET');
720
763
  }
721
764
 
765
+ /**
766
+ * Get recent media from a connected social account.
767
+ *
768
+ * @param {string} platform - instagram, tiktok, youtube, linkedin, facebook, x, threads, pinterest, bluesky, reddit
769
+ * @param {string} user - Profile username
770
+ * @param {Object} [options]
771
+ * @param {string} [options.pageUrn] - LinkedIn only. Numeric org ID, full URN, or "me" to force the personal profile.
772
+ * @returns {Promise<Object>}
773
+ */
774
+ async getMedia(platform, user, options = {}) {
775
+ const params = { platform, user };
776
+ if (options.pageUrn) params.page_urn = options.pageUrn;
777
+ return this._request('/uploadposts/media', 'GET', params);
778
+ }
779
+
722
780
  // ==================== Scheduled Posts ====================
723
781
 
724
782
  /**
@@ -826,6 +884,106 @@ export class UploadPost {
826
884
  return this._request('/uploadposts/users/validate-jwt', 'POST', { jwt });
827
885
  }
828
886
 
887
+ /**
888
+ * Get user preferences (including calendar settings)
889
+ *
890
+ * @returns {Promise<Object>} User preferences
891
+ */
892
+ async getUserPreferences() {
893
+ return this._request('/uploadposts/users/preferences', 'GET');
894
+ }
895
+
896
+ /**
897
+ * Update user preferences (including calendar settings)
898
+ *
899
+ * @param {Object} options - Preferences options
900
+ * @param {number} [options.weekStartDay] - Week start day (0=Sunday, 1=Monday)
901
+ * @returns {Promise<Object>} Updated preferences
902
+ */
903
+ async updateUserPreferences(options = {}) {
904
+ const body = {};
905
+ if (options.weekStartDay !== undefined) body.week_start_day = options.weekStartDay;
906
+ return this._request('/uploadposts/users/preferences', 'POST', body);
907
+ }
908
+
909
+ /**
910
+ * Get notification configuration (including webhook settings)
911
+ *
912
+ * @returns {Promise<Object>} Notification config
913
+ */
914
+ async getNotificationConfig() {
915
+ return this._request('/uploadposts/notification-config', 'GET');
916
+ }
917
+
918
+ /**
919
+ * Update notification configuration (including webhook settings)
920
+ *
921
+ * @param {Object} options - Notification config options
922
+ * @param {string[]} [options.webhookEvents] - Webhook event types to subscribe to (upload_completed, social_account_connected, social_account_disconnected, social_account_reauth_required)
923
+ * @param {string} [options.webhookUrl] - Webhook URL for notifications
924
+ * @returns {Promise<Object>} Updated notification config
925
+ */
926
+ async updateNotificationConfig(options = {}) {
927
+ const body = {};
928
+ if (options.webhookEvents) body.webhook_events = options.webhookEvents;
929
+ if (options.webhookUrl) body.webhook_url = options.webhookUrl;
930
+ return this._request('/uploadposts/notification-config', 'POST', body);
931
+ }
932
+
933
+ // ==================== Instagram Comments ====================
934
+
935
+ /**
936
+ * Get comments on an Instagram post
937
+ *
938
+ * @param {string} user - Profile username
939
+ * @param {Object} options - Query options
940
+ * @param {string} [options.postId] - Numeric media ID (provide postId or postUrl)
941
+ * @param {string} [options.postUrl] - Full Instagram post URL (provide postId or postUrl)
942
+ * @returns {Promise<Object>} Comments data
943
+ */
944
+ async getPostComments(user, options = {}) {
945
+ const params = { platform: 'instagram', user };
946
+ if (options.postId) params.post_id = options.postId;
947
+ if (options.postUrl) params.post_url = options.postUrl;
948
+ return this._request('/uploadposts/comments', 'GET', params);
949
+ }
950
+
951
+ /**
952
+ * Send a private reply (DM) to the author of an Instagram comment
953
+ *
954
+ * @param {Object} options - Reply options
955
+ * @param {string} options.user - Profile username
956
+ * @param {string} options.commentId - Comment ID from getPostComments
957
+ * @param {string} options.message - Reply message text
958
+ * @returns {Promise<Object>} Reply result
959
+ */
960
+ async replyToComment(options) {
961
+ return this._request('/uploadposts/comments/reply', 'POST', {
962
+ platform: 'instagram',
963
+ user: options.user,
964
+ comment_id: options.commentId,
965
+ message: options.message
966
+ });
967
+ }
968
+
969
+ /**
970
+ * Post a public reply to an Instagram comment (visible under the original comment)
971
+ *
972
+ * @param {Object} options - Reply options
973
+ * @param {string} options.user - Profile username
974
+ * @param {string} options.commentId - Comment ID from getPostComments
975
+ * @param {string} options.message - Reply message text
976
+ * @returns {Promise<Object>} Reply result with the new comment ID
977
+ */
978
+ async publicReplyToComment(options) {
979
+ return this._request('/uploadposts/comments/public-reply', 'POST', {
980
+ platform: 'instagram',
981
+ user: options.user,
982
+ comment_id: options.commentId,
983
+ message: options.message
984
+ });
985
+ }
986
+
829
987
  // ==================== Helper Endpoints ====================
830
988
 
831
989
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "upload-post",
3
- "version": "2.1.0",
3
+ "version": "2.2.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",