n8n-nodes-upload-post 0.1.17 → 0.1.19

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.
@@ -2,6 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.UploadPost = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
+ const MANUAL_USER_VALUE = '__manual_user__';
6
+ const MANUAL_FACEBOOK_VALUE = '__manual_facebook__';
7
+ const MANUAL_LINKEDIN_VALUE = '__manual_linkedin__';
8
+ const MANUAL_PINTEREST_VALUE = '__manual_pinterest__';
5
9
  class UploadPost {
6
10
  constructor() {
7
11
  this.description = {
@@ -81,12 +85,14 @@ class UploadPost {
81
85
  displayOptions: { show: { resource: ['users'] } },
82
86
  },
83
87
  {
84
- displayName: 'User Identifier',
88
+ displayName: 'User Identifier Name or ID',
85
89
  name: 'user',
86
- type: 'string',
90
+ type: 'options',
91
+ noDataExpression: true,
87
92
  required: true,
88
93
  default: '',
89
- description: 'The Profile Name you created in your upload-post.com account. You can find it in the \'Manage Users\' section (e.g., app.upload-post.com/manage-users).',
94
+ description: 'Choose from your created profiles, or specify a profile name using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
95
+ typeOptions: { loadOptionsMethod: 'getUserProfiles' },
90
96
  displayOptions: {
91
97
  show: {
92
98
  resource: ['uploads', 'users'],
@@ -95,22 +101,28 @@ class UploadPost {
95
101
  },
96
102
  },
97
103
  {
98
- displayName: 'Platform(s)',
104
+ displayName: 'User Identifier (Manual Entry)',
105
+ name: 'userManual',
106
+ type: 'string',
107
+ required: true,
108
+ default: '',
109
+ description: 'Provide a profile name or ID when it does not appear in the list',
110
+ displayOptions: {
111
+ show: {
112
+ resource: ['uploads', 'users'],
113
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText', 'generateJwt'],
114
+ user: [MANUAL_USER_VALUE]
115
+ }
116
+ },
117
+ },
118
+ {
119
+ displayName: 'Platform Names or IDs',
99
120
  name: 'platform',
100
121
  type: 'multiOptions',
101
122
  required: true,
102
- options: [
103
- { name: 'Facebook', value: 'facebook' },
104
- { name: 'Instagram', value: 'instagram' },
105
- { name: 'LinkedIn', value: 'linkedin' },
106
- { name: 'Pinterest', value: 'pinterest' },
107
- { name: 'Threads', value: 'threads' },
108
- { name: 'TikTok', value: 'tiktok' },
109
- { name: 'X (Twitter)', value: 'x' },
110
- { name: 'YouTube', value: 'youtube' },
111
- ],
123
+ typeOptions: { loadOptionsMethod: 'getPlatforms' },
112
124
  default: [],
113
- description: 'Platform(s) to upload to. Supported platforms vary by operation.',
125
+ description: 'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
114
126
  displayOptions: { show: { resource: ['uploads'], operation: ['uploadPhotos', 'uploadVideo', 'uploadText'] } },
115
127
  },
116
128
  {
@@ -179,44 +191,32 @@ class UploadPost {
179
191
  displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo', 'uploadText'], platform: ['pinterest'] } },
180
192
  },
181
193
  {
182
- displayName: 'Photos (Files or URLs)',
183
- name: 'photos',
194
+ displayName: 'Threads Title (Override)',
195
+ name: 'threadsTitle',
184
196
  type: 'string',
185
- required: true,
186
197
  default: '',
187
- description: 'Provide photo files or URLs as a comma-separated list (e.g., data,https://example.com/image.jpg,otherImage). For files, enter the binary property name (e.g., data, myImage). For URLs, provide direct HTTP/HTTPS URLs.',
188
- displayOptions: {
189
- show: {
190
- operation: ['uploadPhotos'],
191
- },
192
- },
198
+ description: 'Optional override for Threads title',
199
+ displayOptions: { show: { operation: ['uploadText'], platform: ['threads'] } },
193
200
  },
194
201
  {
195
202
  displayName: 'Description (Optional)',
196
203
  name: 'description',
197
204
  type: 'string',
198
205
  default: '',
199
- description: 'Generic description to use across platforms when supported. Platform-specific overrides below take precedence.',
206
+ description: 'Optional extended description used for LinkedIn commentary, Facebook description/message, YouTube video description, Pinterest description, and TikTok photo captions. Other platforms ignore it. When empty we fall back to the main title where a description is required. Platform-specific overrides below take precedence.',
200
207
  displayOptions: {
201
208
  show: {
202
- operation: ['uploadPhotos', 'uploadVideo']
209
+ operation: ['uploadPhotos', 'uploadVideo'],
210
+ platform: ['linkedin', 'facebook', 'youtube', 'pinterest', 'tiktok']
203
211
  }
204
212
  },
205
213
  },
206
- {
207
- displayName: 'Instagram Description (Override)',
208
- name: 'instagramDescription',
209
- type: 'string',
210
- default: '',
211
- description: 'Optional description override for Instagram',
212
- displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo'], platform: ['instagram'] } },
213
- },
214
214
  {
215
215
  displayName: 'Facebook Description (Override)',
216
216
  name: 'facebookDescription',
217
217
  type: 'string',
218
218
  default: '',
219
- description: 'Optional description override for Facebook',
219
+ description: 'Override for Facebook description/message when supported (Reels/feed, albums). Falls back to the main title when empty.',
220
220
  displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo'], platform: ['facebook'] } },
221
221
  },
222
222
  {
@@ -224,31 +224,23 @@ class UploadPost {
224
224
  name: 'tiktokDescription',
225
225
  type: 'string',
226
226
  default: '',
227
- description: 'Optional description override for TikTok',
228
- displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo'], platform: ['tiktok'] } },
227
+ description: 'Override for TikTok photo post description. Video uploads ignore this value.',
228
+ displayOptions: { show: { operation: ['uploadPhotos'], platform: ['tiktok'] } },
229
229
  },
230
230
  {
231
231
  displayName: 'LinkedIn Description (Override)',
232
232
  name: 'linkedinDescription',
233
233
  type: 'string',
234
234
  default: '',
235
- description: 'Optional description override for LinkedIn',
235
+ description: 'Override for LinkedIn post commentary. When empty we repeat the main title.',
236
236
  displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo'], platform: ['linkedin'] } },
237
237
  },
238
- {
239
- displayName: 'X Description (Override)',
240
- name: 'xDescription',
241
- type: 'string',
242
- default: '',
243
- description: 'Optional description override for X',
244
- displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo'], platform: ['x'] } },
245
- },
246
238
  {
247
239
  displayName: 'YouTube Description (Override)',
248
240
  name: 'youtubeDescription',
249
241
  type: 'string',
250
242
  default: '',
251
- description: 'Optional description override for YouTube',
243
+ description: 'Override for YouTube video description. When empty we default to the main title.',
252
244
  displayOptions: { show: { operation: ['uploadVideo'], platform: ['youtube'] } },
253
245
  },
254
246
  {
@@ -256,16 +248,21 @@ class UploadPost {
256
248
  name: 'pinterestDescription',
257
249
  type: 'string',
258
250
  default: '',
259
- description: 'Optional description override for Pinterest',
251
+ description: 'Override for Pinterest pin description (and alt text fallback). When empty we re-use the main title.',
260
252
  displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo'], platform: ['pinterest'] } },
261
253
  },
262
254
  {
263
- displayName: 'Threads Description (Override)',
264
- name: 'threadsDescription',
255
+ displayName: 'Photos (Files or URLs)',
256
+ name: 'photos',
265
257
  type: 'string',
258
+ required: true,
266
259
  default: '',
267
- description: 'Optional description override for Threads',
268
- displayOptions: { show: { operation: ['uploadVideo'], platform: ['threads'] } },
260
+ description: 'Provide photo files or URLs as a comma-separated list (e.g., data,https://example.com/image.jpg,otherImage). For files, enter the binary property name (e.g., data, myImage). For URLs, provide direct HTTP/HTTPS URLs.',
261
+ displayOptions: {
262
+ show: {
263
+ operation: ['uploadPhotos'],
264
+ },
265
+ },
269
266
  },
270
267
  {
271
268
  displayName: 'Video (File or URL)',
@@ -304,7 +301,7 @@ class UploadPost {
304
301
  displayName: 'Wait for Completion',
305
302
  name: 'waitForCompletion',
306
303
  type: 'boolean',
307
- default: false,
304
+ default: true,
308
305
  description: 'Whether to perform best-effort sleeping between status checks within this node. Not guaranteed to finish; for reliable long polling use a separate Wait node plus Get Upload Status.',
309
306
  displayOptions: {
310
307
  show: {
@@ -510,9 +507,10 @@ class UploadPost {
510
507
  displayName: 'Target LinkedIn Page Name or ID',
511
508
  name: 'targetLinkedinPageId',
512
509
  type: 'options',
510
+ noDataExpression: true,
513
511
  default: '',
514
512
  description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
515
- typeOptions: { loadOptionsMethod: 'getLinkedinPages' },
513
+ typeOptions: { loadOptionsMethod: 'getLinkedinPages', loadOptionsDependsOn: ['user'] },
516
514
  displayOptions: {
517
515
  show: {
518
516
  operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
@@ -520,6 +518,21 @@ class UploadPost {
520
518
  }
521
519
  },
522
520
  },
521
+ {
522
+ displayName: 'LinkedIn Page Name or ID (Manual Entry)',
523
+ name: 'targetLinkedinPageIdManual',
524
+ type: 'string',
525
+ required: true,
526
+ default: '',
527
+ description: 'Provide the LinkedIn page identifier when it does not appear in the list',
528
+ displayOptions: {
529
+ show: {
530
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
531
+ platform: ['linkedin'],
532
+ targetLinkedinPageId: [MANUAL_LINKEDIN_VALUE]
533
+ }
534
+ },
535
+ },
523
536
  {
524
537
  displayName: 'LinkedIn Video Description',
525
538
  name: 'linkedinDescription',
@@ -537,10 +550,11 @@ class UploadPost {
537
550
  displayName: 'Facebook Page Name or ID',
538
551
  name: 'facebookPageId',
539
552
  type: 'options',
553
+ noDataExpression: true,
540
554
  required: true,
541
555
  default: '',
542
556
  description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
543
- typeOptions: { loadOptionsMethod: 'getFacebookPages' },
557
+ typeOptions: { loadOptionsMethod: 'getFacebookPages', loadOptionsDependsOn: ['user'] },
544
558
  displayOptions: {
545
559
  show: {
546
560
  operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
@@ -548,6 +562,21 @@ class UploadPost {
548
562
  }
549
563
  },
550
564
  },
565
+ {
566
+ displayName: 'Facebook Page Name or ID (Manual Entry)',
567
+ name: 'facebookPageIdManual',
568
+ type: 'string',
569
+ required: true,
570
+ default: '',
571
+ description: 'Provide the Facebook page identifier when it does not appear in the list',
572
+ displayOptions: {
573
+ show: {
574
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
575
+ platform: ['facebook'],
576
+ facebookPageId: [MANUAL_FACEBOOK_VALUE]
577
+ }
578
+ },
579
+ },
551
580
  {
552
581
  displayName: 'Facebook Link (Text)',
553
582
  name: 'facebookLink',
@@ -581,10 +610,9 @@ class UploadPost {
581
610
  options: [
582
611
  { name: 'Published', value: 'PUBLISHED' },
583
612
  { name: 'Draft', value: 'DRAFT' },
584
- { name: 'Scheduled', value: 'SCHEDULED' },
585
613
  ],
586
614
  default: 'PUBLISHED',
587
- description: 'State for Facebook Video (DRAFT, PUBLISHED, SCHEDULED). Not for Photos/Text.',
615
+ description: 'State for Facebook Video (DRAFT or PUBLISHED). Use Scheduled Date field for scheduling.',
588
616
  displayOptions: {
589
617
  show: {
590
618
  operation: ['uploadVideo'],
@@ -636,11 +664,12 @@ class UploadPost {
636
664
  },
637
665
  },
638
666
  {
639
- displayName: 'TikTok Branded Content (Photo)',
640
- name: 'tiktokBrandedContentPhoto',
641
- type: 'boolean',
642
- default: false,
643
- description: 'Whether to indicate photo post is branded content (requires Disclose Commercial). Only for Upload Photos.',
667
+ displayName: 'TikTok Photo Cover Index',
668
+ name: 'tiktokPhotoCoverIndex',
669
+ type: 'number',
670
+ default: 0,
671
+ typeOptions: { minValue: 0 },
672
+ description: 'Index (0-based) of photo to use as cover for TikTok photo post. Only for Upload Photos.',
644
673
  displayOptions: {
645
674
  show: {
646
675
  operation: ['uploadPhotos'],
@@ -649,11 +678,11 @@ class UploadPost {
649
678
  },
650
679
  },
651
680
  {
652
- displayName: 'TikTok Disclose Commercial (Photo)',
653
- name: 'tiktokDiscloseCommercialPhoto',
654
- type: 'boolean',
655
- default: false,
656
- description: 'Whether to disclose commercial nature of photo post. Only for Upload Photos.',
681
+ displayName: 'TikTok Photo Description',
682
+ name: 'tiktokPhotoDescription',
683
+ type: 'string',
684
+ default: '',
685
+ description: 'Description for TikTok photo post. If not provided, Title is used. Only for Upload Photos.',
657
686
  displayOptions: {
658
687
  show: {
659
688
  operation: ['uploadPhotos'],
@@ -662,12 +691,11 @@ class UploadPost {
662
691
  },
663
692
  },
664
693
  {
665
- displayName: 'TikTok Photo Cover Index',
666
- name: 'tiktokPhotoCoverIndex',
667
- type: 'number',
668
- default: 0,
669
- typeOptions: { minValue: 0 },
670
- description: 'Index (0-based) of photo to use as cover for TikTok photo post. Only for Upload Photos.',
694
+ displayName: 'TikTok Brand Content Toggle (Photo)',
695
+ name: 'brand_content_toggle',
696
+ type: 'boolean',
697
+ default: false,
698
+ description: 'Whether to set as true for paid partnerships that promote third-party brands',
671
699
  displayOptions: {
672
700
  show: {
673
701
  operation: ['uploadPhotos'],
@@ -676,11 +704,11 @@ class UploadPost {
676
704
  },
677
705
  },
678
706
  {
679
- displayName: 'TikTok Photo Description',
680
- name: 'tiktokPhotoDescription',
681
- type: 'string',
682
- default: '',
683
- description: 'Description for TikTok photo post. If not provided, Title is used. Only for Upload Photos.',
707
+ displayName: 'TikTok Brand Organic Toggle (Photo)',
708
+ name: 'brand_organic_toggle',
709
+ type: 'boolean',
710
+ default: false,
711
+ description: 'Whether to set as true when promoting the creator\'s own business',
684
712
  displayOptions: {
685
713
  show: {
686
714
  operation: ['uploadPhotos'],
@@ -748,36 +776,10 @@ class UploadPost {
748
776
  },
749
777
  {
750
778
  displayName: 'TikTok Brand Content Toggle (Video)',
751
- name: 'tiktokBrandContentToggle',
752
- type: 'boolean',
753
- default: false,
754
- description: 'Whether to enable branded content for TikTok video. Only for Upload Video.',
755
- displayOptions: {
756
- show: {
757
- operation: ['uploadVideo'],
758
- platform: ['tiktok']
759
- },
760
- },
761
- },
762
- {
763
- displayName: 'TikTok Brand Organic (Video)',
764
- name: 'tiktokBrandOrganic',
779
+ name: 'brand_content_toggle',
765
780
  type: 'boolean',
766
781
  default: false,
767
- description: 'Whether to enable organic branded content for TikTok video. Only for Upload Video.',
768
- displayOptions: {
769
- show: {
770
- operation: ['uploadVideo'],
771
- platform: ['tiktok']
772
- },
773
- },
774
- },
775
- {
776
- displayName: 'TikTok Branded Content (Video)',
777
- name: 'tiktokBrandedContentVideo',
778
- type: 'boolean',
779
- default: false,
780
- description: 'Whether to enable branded content with disclosure for TikTok video. Only for Upload Video.',
782
+ description: 'Whether to enable brand content toggle for paid partnerships that promote third-party brands',
781
783
  displayOptions: {
782
784
  show: {
783
785
  operation: ['uploadVideo'],
@@ -787,10 +789,10 @@ class UploadPost {
787
789
  },
788
790
  {
789
791
  displayName: 'TikTok Brand Organic Toggle (Video)',
790
- name: 'tiktokBrandOrganicToggle',
792
+ name: 'brand_organic_toggle',
791
793
  type: 'boolean',
792
794
  default: false,
793
- description: 'Whether to enable organic branded content toggle for TikTok video. Only for Upload Video.',
795
+ description: 'Whether to enable brand organic toggle when promoting the creator\'s own business',
794
796
  displayOptions: {
795
797
  show: {
796
798
  operation: ['uploadVideo'],
@@ -938,17 +940,29 @@ class UploadPost {
938
940
  },
939
941
  },
940
942
  {
941
- displayName: 'YouTube Video Description',
942
- name: 'youtubeDescription',
943
+ displayName: 'Threads Long Text as Single Post',
944
+ name: 'threadsLongTextAsPost',
945
+ type: 'boolean',
946
+ default: false,
947
+ description: 'Whether long text is published as a single post. If false (default), a thread is created if the text exceeds 500 characters.',
948
+ displayOptions: { show: { operation: ['uploadText'], platform: ['threads'] } },
949
+ },
950
+ {
951
+ displayName: 'Reddit Subreddit',
952
+ name: 'redditSubreddit',
943
953
  type: 'string',
954
+ required: true,
944
955
  default: '',
945
- description: 'Description of the video for YouTube. If not provided, Title is used. Only for Upload Video.',
946
- displayOptions: {
947
- show: {
948
- operation: ['uploadVideo'],
949
- platform: ['youtube']
950
- },
951
- },
956
+ description: 'Destination subreddit, without r/ (e.g., python)',
957
+ displayOptions: { show: { operation: ['uploadText'] } },
958
+ },
959
+ {
960
+ displayName: 'Reddit Flair ID',
961
+ name: 'redditFlairId',
962
+ type: 'string',
963
+ default: '',
964
+ description: 'ID of the flair template to apply to the post',
965
+ displayOptions: { show: { operation: ['uploadText'], platform: ['reddit'] } },
952
966
  },
953
967
  {
954
968
  displayName: 'YouTube Tags',
@@ -1063,20 +1077,142 @@ class UploadPost {
1063
1077
  },
1064
1078
  },
1065
1079
  },
1080
+ {
1081
+ displayName: 'YouTube Self Declared Made For Kids',
1082
+ name: 'youtubeSelfDeclaredMadeForKids',
1083
+ type: 'boolean',
1084
+ default: false,
1085
+ description: 'Whether this is an explicit declaration for children content (COPPA compliance). Only for Upload Video.',
1086
+ displayOptions: {
1087
+ show: {
1088
+ operation: ['uploadVideo'],
1089
+ platform: ['youtube']
1090
+ },
1091
+ },
1092
+ },
1093
+ {
1094
+ displayName: 'YouTube Contains Synthetic Media',
1095
+ name: 'youtubeContainsSyntheticMedia',
1096
+ type: 'boolean',
1097
+ default: false,
1098
+ description: 'Whether this is a declaration for AI/synthetic content transparency. Only for Upload Video.',
1099
+ displayOptions: {
1100
+ show: {
1101
+ operation: ['uploadVideo'],
1102
+ platform: ['youtube']
1103
+ },
1104
+ },
1105
+ },
1106
+ {
1107
+ displayName: 'YouTube Default Language',
1108
+ name: 'youtubeDefaultLanguage',
1109
+ type: 'string',
1110
+ default: '',
1111
+ description: 'Title/description language (BCP-47 codes like "es", "en"). Only for Upload Video.',
1112
+ displayOptions: {
1113
+ show: {
1114
+ operation: ['uploadVideo'],
1115
+ platform: ['youtube']
1116
+ },
1117
+ },
1118
+ },
1119
+ {
1120
+ displayName: 'YouTube Default Audio Language',
1121
+ name: 'youtubeDefaultAudioLanguage',
1122
+ type: 'string',
1123
+ default: '',
1124
+ description: 'Video audio language (BCP-47 codes like "es-ES", "en-US"). Only for Upload Video.',
1125
+ displayOptions: {
1126
+ show: {
1127
+ operation: ['uploadVideo'],
1128
+ platform: ['youtube']
1129
+ },
1130
+ },
1131
+ },
1132
+ {
1133
+ displayName: 'YouTube Allowed Countries',
1134
+ name: 'youtubeAllowedCountries',
1135
+ type: 'string',
1136
+ default: '',
1137
+ description: 'Comma-separated country codes for allowed regions (ISO 3166-1 alpha-2). Cannot be used with blocked countries. Only for Upload Video.',
1138
+ displayOptions: {
1139
+ show: {
1140
+ operation: ['uploadVideo'],
1141
+ platform: ['youtube']
1142
+ },
1143
+ },
1144
+ },
1145
+ {
1146
+ displayName: 'YouTube Blocked Countries',
1147
+ name: 'youtubeBlockedCountries',
1148
+ type: 'string',
1149
+ default: '',
1150
+ description: 'Comma-separated country codes for blocked regions (ISO 3166-1 alpha-2). Cannot be used with allowed countries. Only for Upload Video.',
1151
+ displayOptions: {
1152
+ show: {
1153
+ operation: ['uploadVideo'],
1154
+ platform: ['youtube']
1155
+ },
1156
+ },
1157
+ },
1158
+ {
1159
+ displayName: 'YouTube Has Paid Product Placement',
1160
+ name: 'youtubeHasPaidProductPlacement',
1161
+ type: 'boolean',
1162
+ default: false,
1163
+ description: 'Whether this is a declaration for paid product placements (FTC compliance). Only for Upload Video.',
1164
+ displayOptions: {
1165
+ show: {
1166
+ operation: ['uploadVideo'],
1167
+ platform: ['youtube']
1168
+ },
1169
+ },
1170
+ },
1171
+ {
1172
+ displayName: 'YouTube Recording Date',
1173
+ name: 'youtubeRecordingDate',
1174
+ type: 'dateTime',
1175
+ default: '',
1176
+ description: 'Recording timestamp (ISO 8601 format). Only for Upload Video.',
1177
+ displayOptions: {
1178
+ show: {
1179
+ operation: ['uploadVideo'],
1180
+ platform: ['youtube']
1181
+ },
1182
+ },
1183
+ },
1066
1184
  {
1067
1185
  displayName: 'Pinterest Board Name or ID',
1068
1186
  name: 'pinterestBoardId',
1069
1187
  type: 'options',
1188
+ noDataExpression: true,
1070
1189
  default: '',
1071
1190
  description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
1072
- typeOptions: { loadOptionsMethod: 'getPinterestBoards' },
1191
+ typeOptions: { loadOptionsMethod: 'getPinterestBoards', loadOptionsDependsOn: ['user'] },
1073
1192
  displayOptions: {
1074
1193
  show: {
1075
1194
  resource: ['uploads'],
1195
+ operation: ['uploadPhotos', 'uploadVideo'],
1076
1196
  platform: ['pinterest']
1077
1197
  },
1078
1198
  },
1079
1199
  },
1200
+ {
1201
+ displayName: 'Pinterest Board Name or ID (Manual Entry)',
1202
+ name: 'pinterestBoardIdManual',
1203
+ type: 'string',
1204
+ required: true,
1205
+ default: '',
1206
+ description: 'Provide the Pinterest board identifier when it does not appear in the list',
1207
+ displayOptions: {
1208
+ show: {
1209
+ resource: ['uploads'],
1210
+ operation: ['uploadPhotos', 'uploadVideo'],
1211
+ platform: ['pinterest'],
1212
+ pinterestBoardId: [MANUAL_PINTEREST_VALUE]
1213
+ },
1214
+ },
1215
+ },
1080
1216
  {
1081
1217
  displayName: 'Pinterest Link (Photo/Video)',
1082
1218
  name: 'pinterestLink',
@@ -1149,58 +1285,47 @@ class UploadPost {
1149
1285
  },
1150
1286
  },
1151
1287
  {
1152
- displayName: 'Threads Video Description',
1153
- name: 'threadsDescription',
1154
- type: 'string',
1155
- default: '',
1156
- description: 'User commentary for Threads video. If not provided, Title is used. Not for Photos/Text.',
1157
- displayOptions: {
1158
- show: {
1159
- operation: ['uploadVideo'],
1160
- platform: ['threads']
1161
- },
1162
- },
1163
- },
1164
- {
1165
- displayName: 'X Tagged User IDs (Video/Text)',
1288
+ displayName: 'X Tagged User IDs',
1166
1289
  name: 'xTaggedUserIds',
1167
1290
  type: 'string',
1168
1291
  default: '',
1169
- description: 'Comma-separated list of user IDs to tag for X (Twitter). Not for Photos.',
1292
+ description: 'Comma-separated list of user IDs to tag for X (Twitter)',
1170
1293
  displayOptions: {
1171
1294
  show: {
1172
- operation: ['uploadVideo', 'uploadText'],
1295
+ operation: ['uploadPhotos', 'uploadVideo'],
1173
1296
  platform: ['x']
1174
1297
  },
1175
1298
  },
1176
1299
  },
1177
1300
  {
1178
- displayName: 'X Reply Settings (Video/Text)',
1301
+ displayName: 'X Reply Settings',
1179
1302
  name: 'xReplySettings',
1180
1303
  type: 'options',
1181
1304
  options: [
1305
+ { name: 'Everyone', value: 'everyone' },
1182
1306
  { name: 'Following', value: 'following' },
1183
1307
  { name: 'Mentioned Users', value: 'mentionedUsers' },
1184
- { name: 'Everyone', value: 'everyone' },
1308
+ { name: 'Subscribers', value: 'subscribers' },
1309
+ { name: 'Verified', value: 'verified' },
1185
1310
  ],
1186
- default: 'following',
1187
- description: 'Who can reply to the post on X (Twitter) (following, mentionedUsers, everyone). Not for Photos.',
1311
+ default: 'everyone',
1312
+ description: 'Who can reply to the post on X (Twitter)',
1188
1313
  displayOptions: {
1189
1314
  show: {
1190
- operation: ['uploadVideo', 'uploadText'],
1315
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
1191
1316
  platform: ['x']
1192
1317
  },
1193
1318
  },
1194
1319
  },
1195
1320
  {
1196
- displayName: 'X Nullcast (Video)',
1321
+ displayName: 'X Nullcast',
1197
1322
  name: 'xNullcastVideo',
1198
1323
  type: 'boolean',
1199
1324
  default: false,
1200
- description: 'Whether to publish X (Twitter) video without broadcasting. Not for Text/Photos.',
1325
+ description: 'Whether to publish X (Twitter) post without broadcasting (promoted-only posts)',
1201
1326
  displayOptions: {
1202
1327
  show: {
1203
- operation: ['uploadVideo'],
1328
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
1204
1329
  platform: ['x']
1205
1330
  },
1206
1331
  },
@@ -1219,47 +1344,48 @@ class UploadPost {
1219
1344
  },
1220
1345
  },
1221
1346
  {
1222
- displayName: 'X Poll Duration (Minutes, Video)',
1223
- name: 'xPollDurationVideo',
1347
+ displayName: 'X Poll Duration (Minutes)',
1348
+ name: 'xPollDuration',
1224
1349
  type: 'number',
1225
1350
  default: 1440,
1226
- description: 'Poll duration in minutes for X (Twitter) video post (requires Poll Options). Not for Text/Photos.',
1351
+ description: 'Poll duration in minutes for X (Twitter) post (requires Poll Options)',
1227
1352
  displayOptions: {
1228
1353
  show: {
1229
- operation: ['uploadVideo'],
1354
+ operation: ['uploadText'],
1230
1355
  platform: ['x']
1231
- },
1356
+ }
1232
1357
  },
1233
1358
  },
1234
1359
  {
1235
- displayName: 'X Poll Options (Video)',
1236
- name: 'xPollOptionsVideo',
1360
+ displayName: 'X Poll Options',
1361
+ name: 'xPollOptions',
1237
1362
  type: 'string',
1238
1363
  default: '',
1239
- description: 'Comma-separated list of poll options for X (Twitter) video post. Will be sent as an array. Not for Text/Photos.',
1364
+ description: 'Comma-separated list of poll options for X (Twitter) post',
1240
1365
  displayOptions: {
1241
1366
  show: {
1242
- operation: ['uploadVideo'],
1367
+ operation: ['uploadText'],
1243
1368
  platform: ['x']
1244
- },
1369
+ }
1245
1370
  },
1246
1371
  },
1247
1372
  {
1248
- displayName: 'X Poll Reply Settings (Video)',
1249
- name: 'xPollReplySettingsVideo',
1373
+ displayName: 'X Poll Reply Settings',
1374
+ name: 'xPollReplySettings',
1250
1375
  type: 'options',
1251
1376
  options: [
1252
1377
  { name: 'Following', value: 'following' },
1253
1378
  { name: 'Mentioned Users', value: 'mentionedUsers' },
1254
1379
  { name: 'Everyone', value: 'everyone' },
1380
+ { name: 'Subscribers', value: 'subscribers' },
1255
1381
  ],
1256
1382
  default: 'following',
1257
- description: 'Who can reply to the poll in X (Twitter) video post (following, mentionedUsers, everyone). Not for Text/Photos.',
1383
+ description: 'Who can reply to the poll in X (Twitter) post',
1258
1384
  displayOptions: {
1259
1385
  show: {
1260
- operation: ['uploadVideo'],
1386
+ operation: ['uploadText'],
1261
1387
  platform: ['x']
1262
- },
1388
+ }
1263
1389
  },
1264
1390
  },
1265
1391
  {
@@ -1276,111 +1402,257 @@ class UploadPost {
1276
1402
  },
1277
1403
  },
1278
1404
  {
1279
- displayName: 'X Long Text as Single Post',
1280
- name: 'xLongTextAsPost',
1405
+ displayName: 'X Quote Tweet ID',
1406
+ name: 'xQuoteTweetId',
1407
+ type: 'string',
1408
+ default: '',
1409
+ description: 'ID of the tweet to quote in a quote tweet',
1410
+ displayOptions: {
1411
+ show: {
1412
+ operation: ['uploadText'],
1413
+ platform: ['x']
1414
+ }
1415
+ },
1416
+ },
1417
+ {
1418
+ displayName: 'X Geo Place ID',
1419
+ name: 'xGeoPlaceId',
1420
+ type: 'string',
1421
+ default: '',
1422
+ description: 'Geographic place ID to add location to the tweet',
1423
+ displayOptions: {
1424
+ show: {
1425
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
1426
+ platform: ['x']
1427
+ },
1428
+ },
1429
+ },
1430
+ {
1431
+ displayName: 'X For Super Followers Only',
1432
+ name: 'xForSuperFollowersOnly',
1281
1433
  type: 'boolean',
1282
1434
  default: false,
1283
- description: 'Whether to post long text as a single post instead of splitting into a thread (if supported)',
1435
+ description: 'Whether the tweet is exclusive for super followers',
1284
1436
  displayOptions: {
1285
1437
  show: {
1286
- operation: ['uploadVideo', 'uploadText'],
1438
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
1287
1439
  platform: ['x']
1288
1440
  },
1289
1441
  },
1290
1442
  },
1291
1443
  {
1292
- displayName: 'Description (Optional)',
1293
- name: 'description',
1444
+ displayName: 'X Community ID',
1445
+ name: 'xCommunityId',
1294
1446
  type: 'string',
1295
1447
  default: '',
1296
- description: 'Generic description to use across platforms when supported. Platform-specific overrides below take precedence.',
1448
+ description: 'Community ID for posting in specific communities',
1297
1449
  displayOptions: {
1298
1450
  show: {
1299
- operation: ['uploadPhotos', 'uploadVideo']
1300
- }
1451
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
1452
+ platform: ['x']
1453
+ },
1301
1454
  },
1302
1455
  },
1303
1456
  {
1304
- displayName: 'LinkedIn Description (Override)',
1305
- name: 'linkedinDescription',
1306
- type: 'string',
1307
- default: '',
1308
- description: 'Optional description override for LinkedIn',
1309
- displayOptions: { show: { operation: ['uploadPhotos', 'uploadVideo'], platform: ['linkedin'] } },
1457
+ displayName: 'X Share with Followers',
1458
+ name: 'xShareWithFollowers',
1459
+ type: 'boolean',
1460
+ default: false,
1461
+ description: 'Whether to share community post with followers',
1462
+ displayOptions: {
1463
+ show: {
1464
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
1465
+ platform: ['x']
1466
+ },
1467
+ },
1310
1468
  },
1311
1469
  {
1312
- displayName: 'YouTube Description (Override)',
1313
- name: 'youtubeDescription',
1470
+ displayName: 'X Direct Message Deep Link',
1471
+ name: 'xDirectMessageDeepLink',
1314
1472
  type: 'string',
1315
1473
  default: '',
1316
- description: 'Optional description override for YouTube',
1317
- displayOptions: { show: { operation: ['uploadVideo'], platform: ['youtube'] } },
1474
+ description: 'Link to take the conversation from public timeline to private Direct Message',
1475
+ displayOptions: {
1476
+ show: {
1477
+ operation: ['uploadPhotos', 'uploadVideo', 'uploadText'],
1478
+ platform: ['x']
1479
+ }
1480
+ },
1318
1481
  },
1319
1482
  {
1320
- displayName: 'Threads Description (Override)',
1321
- name: 'threadsDescription',
1483
+ displayName: 'X Card URI',
1484
+ name: 'xCardUri',
1322
1485
  type: 'string',
1323
1486
  default: '',
1324
- description: 'Optional description override for Threads',
1325
- displayOptions: { show: { operation: ['uploadVideo'], platform: ['threads'] } },
1487
+ description: 'URI of card (for Twitter Cards/ads/promoted content)',
1488
+ displayOptions: {
1489
+ show: {
1490
+ operation: ['uploadText'],
1491
+ platform: ['x']
1492
+ }
1493
+ },
1494
+ },
1495
+ {
1496
+ displayName: 'X Long Text as Single Post',
1497
+ name: 'xLongTextAsPost',
1498
+ type: 'boolean',
1499
+ default: false,
1500
+ description: 'Whether to post long text as a single post instead of splitting into a thread (if supported)',
1501
+ displayOptions: {
1502
+ show: {
1503
+ operation: ['uploadVideo', 'uploadText'],
1504
+ platform: ['x']
1505
+ },
1506
+ },
1326
1507
  },
1327
1508
  ],
1328
1509
  };
1329
1510
  this.methods = {
1330
1511
  loadOptions: {
1331
- async getFacebookPages() {
1332
- const credentials = await this.getCredentials('uploadPostApi');
1333
- const apiKey = credentials.apiKey;
1334
- const profile = this.getCurrentNodeParameter('user') || '';
1335
- const qs = {};
1336
- if (profile)
1337
- qs.profile = profile;
1338
- const options = {
1339
- uri: 'https://api.upload-post.com/api/uploadposts/facebook/pages',
1340
- method: 'GET',
1341
- headers: { Authorization: `ApiKey ${apiKey}` },
1342
- qs,
1343
- json: true,
1512
+ async getPlatforms() {
1513
+ const operation = this.getCurrentNodeParameter('operation');
1514
+ const allPlatforms = [
1515
+ { name: 'Facebook', value: 'facebook' },
1516
+ { name: 'Instagram', value: 'instagram' },
1517
+ { name: 'LinkedIn', value: 'linkedin' },
1518
+ { name: 'Pinterest', value: 'pinterest' },
1519
+ { name: 'Reddit', value: 'reddit' },
1520
+ { name: 'Threads', value: 'threads' },
1521
+ { name: 'TikTok', value: 'tiktok' },
1522
+ { name: 'X (Twitter)', value: 'x' },
1523
+ { name: 'YouTube', value: 'youtube' },
1524
+ ];
1525
+ const platformSupport = {
1526
+ uploadPhotos: ['facebook', 'instagram', 'linkedin', 'pinterest', 'threads', 'tiktok', 'x'],
1527
+ uploadVideo: ['facebook', 'instagram', 'linkedin', 'pinterest', 'threads', 'tiktok', 'x', 'youtube'],
1528
+ uploadText: ['facebook', 'linkedin', 'reddit', 'threads', 'x'],
1344
1529
  };
1345
- const resp = await this.helpers.request(options);
1346
- const pages = (resp && (resp.pages || resp.data || []));
1347
- return (pages || []).map(p => ({ name: p.name ? `${p.name} (${p.id})` : p.id, value: p.id }));
1530
+ const supportedPlatforms = platformSupport[operation] || [];
1531
+ return allPlatforms.filter(platform => supportedPlatforms.includes(platform.value));
1532
+ },
1533
+ async getFacebookPages() {
1534
+ try {
1535
+ const credentials = await this.getCredentials('uploadPostApi');
1536
+ const apiKey = credentials.apiKey;
1537
+ const profile = this.getCurrentNodeParameter('user') || '';
1538
+ const qs = {};
1539
+ if (profile)
1540
+ qs.profile = profile;
1541
+ const options = {
1542
+ uri: 'https://api.upload-post.com/api/uploadposts/facebook/pages',
1543
+ method: 'GET',
1544
+ headers: { Authorization: `ApiKey ${apiKey}` },
1545
+ qs,
1546
+ json: true,
1547
+ };
1548
+ const resp = await this.helpers.request(options);
1549
+ const pages = (resp && (resp.pages || resp.data || []));
1550
+ const pageOptions = (pages || []).map(p => ({ name: p.name ? `${p.name} (${p.id})` : p.id, value: p.id }));
1551
+ return [
1552
+ { name: 'Manual entry...', value: MANUAL_FACEBOOK_VALUE },
1553
+ ...pageOptions
1554
+ ];
1555
+ }
1556
+ catch (error) {
1557
+ return [
1558
+ { name: 'Manual entry...', value: MANUAL_FACEBOOK_VALUE }
1559
+ ];
1560
+ }
1348
1561
  },
1349
1562
  async getLinkedinPages() {
1350
- const credentials = await this.getCredentials('uploadPostApi');
1351
- const apiKey = credentials.apiKey;
1352
- const profile = this.getCurrentNodeParameter('user') || '';
1353
- const qs = {};
1354
- if (profile)
1355
- qs.profile = profile;
1356
- const options = {
1357
- uri: 'https://api.upload-post.com/api/uploadposts/linkedin/pages',
1358
- method: 'GET',
1359
- headers: { Authorization: `ApiKey ${apiKey}` },
1360
- qs,
1361
- json: true,
1362
- };
1363
- const resp = await this.helpers.request(options);
1364
- const pages = (resp && (resp.pages || resp.data || []));
1365
- return (pages || []).map(p => ({ name: p.name ? `${p.name} (${p.id})` : p.id, value: p.id }));
1563
+ try {
1564
+ const credentials = await this.getCredentials('uploadPostApi');
1565
+ const apiKey = credentials.apiKey;
1566
+ const profile = this.getCurrentNodeParameter('user') || '';
1567
+ const qs = {};
1568
+ if (profile)
1569
+ qs.profile = profile;
1570
+ const options = {
1571
+ uri: 'https://api.upload-post.com/api/uploadposts/linkedin/pages',
1572
+ method: 'GET',
1573
+ headers: { Authorization: `ApiKey ${apiKey}` },
1574
+ qs,
1575
+ json: true,
1576
+ };
1577
+ const resp = await this.helpers.request(options);
1578
+ const pages = (resp && (resp.pages || resp.data || []));
1579
+ const pageOptions = (pages || []).map(p => ({ name: p.name ? `${p.name} (${p.id})` : p.id, value: p.id }));
1580
+ return [
1581
+ { name: 'Manual entry...', value: MANUAL_LINKEDIN_VALUE },
1582
+ { name: 'Me (Personal Profile)', value: 'me' },
1583
+ ...pageOptions
1584
+ ];
1585
+ }
1586
+ catch (error) {
1587
+ return [
1588
+ { name: 'Manual entry...', value: MANUAL_LINKEDIN_VALUE },
1589
+ { name: 'Me (Personal Profile)', value: 'me' }
1590
+ ];
1591
+ }
1366
1592
  },
1367
1593
  async getPinterestBoards() {
1368
- const credentials = await this.getCredentials('uploadPostApi');
1369
- const apiKey = credentials.apiKey;
1370
- const profile = this.getCurrentNodeParameter('user') || '';
1371
- const qs = {};
1372
- if (profile)
1373
- qs.profile = profile;
1374
- const options = {
1375
- uri: 'https://api.upload-post.com/api/uploadposts/pinterest/boards',
1376
- method: 'GET',
1377
- headers: { Authorization: `ApiKey ${apiKey}` },
1378
- qs,
1379
- json: true,
1380
- };
1381
- const resp = await this.helpers.request(options);
1382
- const boards = (resp && (resp.boards || resp.data || []));
1383
- return (boards || []).map(b => ({ name: b.name ? `${b.name} (${b.id})` : b.id, value: b.id }));
1594
+ try {
1595
+ const credentials = await this.getCredentials('uploadPostApi');
1596
+ const apiKey = credentials.apiKey;
1597
+ const profile = this.getCurrentNodeParameter('user') || '';
1598
+ const qs = {};
1599
+ if (profile)
1600
+ qs.profile = profile;
1601
+ const options = {
1602
+ uri: 'https://api.upload-post.com/api/uploadposts/pinterest/boards',
1603
+ method: 'GET',
1604
+ headers: { Authorization: `ApiKey ${apiKey}` },
1605
+ qs,
1606
+ json: true,
1607
+ };
1608
+ const resp = await this.helpers.request(options);
1609
+ const boards = (resp && (resp.boards || resp.data || []));
1610
+ const boardOptions = (boards || []).map(b => ({ name: b.name ? `${b.name} (${b.id})` : b.id, value: b.id }));
1611
+ return [
1612
+ { name: 'Manual entry...', value: MANUAL_PINTEREST_VALUE },
1613
+ ...boardOptions
1614
+ ];
1615
+ }
1616
+ catch (error) {
1617
+ return [
1618
+ { name: 'Manual entry...', value: MANUAL_PINTEREST_VALUE }
1619
+ ];
1620
+ }
1621
+ },
1622
+ async getUserProfiles() {
1623
+ try {
1624
+ const credentials = await this.getCredentials('uploadPostApi');
1625
+ const apiKey = credentials.apiKey;
1626
+ const options = {
1627
+ uri: 'https://api.upload-post.com/api/uploadposts/users',
1628
+ method: 'GET',
1629
+ headers: { Authorization: `ApiKey ${apiKey}` },
1630
+ json: true,
1631
+ };
1632
+ const resp = await this.helpers.request(options);
1633
+ const profiles = (resp && resp.profiles);
1634
+ const profileOptions = (profiles || []).map(profile => {
1635
+ const connectedPlatforms = Object.keys(profile.social_accounts || {})
1636
+ .filter(platform => profile.social_accounts[platform] && typeof profile.social_accounts[platform] === 'object')
1637
+ .join(', ');
1638
+ const displayName = connectedPlatforms
1639
+ ? `${profile.username} (${connectedPlatforms})`
1640
+ : profile.username;
1641
+ return {
1642
+ name: displayName,
1643
+ value: profile.username
1644
+ };
1645
+ });
1646
+ return [
1647
+ { name: 'Manual entry...', value: MANUAL_USER_VALUE },
1648
+ ...profileOptions
1649
+ ];
1650
+ }
1651
+ catch (error) {
1652
+ return [
1653
+ { name: 'Manual entry...', value: MANUAL_USER_VALUE }
1654
+ ];
1655
+ }
1384
1656
  },
1385
1657
  },
1386
1658
  };
@@ -1393,7 +1665,13 @@ class UploadPost {
1393
1665
  const operation = this.getNodeParameter('operation', i);
1394
1666
  const isUploadOperation = ['uploadPhotos', 'uploadVideo', 'uploadText'].includes(operation);
1395
1667
  const needsUser = isUploadOperation || operation === 'generateJwt';
1396
- const user = needsUser ? this.getNodeParameter('user', i) : '';
1668
+ const userSelection = needsUser ? this.getNodeParameter('user', i) : '';
1669
+ const userManualValue = needsUser && userSelection === MANUAL_USER_VALUE
1670
+ ? this.getNodeParameter('userManual', i)
1671
+ : '';
1672
+ const user = needsUser
1673
+ ? (userSelection === MANUAL_USER_VALUE ? userManualValue : userSelection)
1674
+ : '';
1397
1675
  let platforms = isUploadOperation ? this.getNodeParameter('platform', i) : [];
1398
1676
  const title = isUploadOperation ? this.getNodeParameter('title', i) : '';
1399
1677
  let endpoint = '';
@@ -1478,8 +1756,10 @@ class UploadPost {
1478
1756
  }
1479
1757
  if (isUploadOperation) {
1480
1758
  const genericDescription = this.getNodeParameter('description', i, '');
1481
- if (genericDescription)
1759
+ const descriptionPlatforms = new Set(['linkedin', 'facebook', 'youtube', 'pinterest', 'tiktok']);
1760
+ if (genericDescription && platforms.some(p => descriptionPlatforms.has(p))) {
1482
1761
  formData.description = genericDescription;
1762
+ }
1483
1763
  try {
1484
1764
  if (platforms.includes('linkedin')) {
1485
1765
  const linkedinDescription = this.getNodeParameter('linkedinDescription', i, '');
@@ -1496,14 +1776,6 @@ class UploadPost {
1496
1776
  }
1497
1777
  }
1498
1778
  catch { }
1499
- try {
1500
- if (platforms.includes('threads')) {
1501
- const threadsDescription = this.getNodeParameter('threadsDescription', i, '');
1502
- if (threadsDescription)
1503
- formData.threads_description = threadsDescription;
1504
- }
1505
- }
1506
- catch { }
1507
1779
  try {
1508
1780
  if (platforms.includes('facebook')) {
1509
1781
  const facebookDescription = this.getNodeParameter('facebookDescription', i, '');
@@ -1512,14 +1784,6 @@ class UploadPost {
1512
1784
  }
1513
1785
  }
1514
1786
  catch { }
1515
- try {
1516
- if (platforms.includes('instagram')) {
1517
- const instagramDescription = this.getNodeParameter('instagramDescription', i, '');
1518
- if (instagramDescription)
1519
- formData.instagram_description = instagramDescription;
1520
- }
1521
- }
1522
- catch { }
1523
1787
  try {
1524
1788
  if (platforms.includes('tiktok')) {
1525
1789
  const tiktokDescription = this.getNodeParameter('tiktokDescription', i, '');
@@ -1528,14 +1792,6 @@ class UploadPost {
1528
1792
  }
1529
1793
  }
1530
1794
  catch { }
1531
- try {
1532
- if (platforms.includes('x')) {
1533
- const xDescription = this.getNodeParameter('xDescription', i, '');
1534
- if (xDescription)
1535
- formData.x_description = xDescription;
1536
- }
1537
- }
1538
- catch { }
1539
1795
  try {
1540
1796
  if (platforms.includes('pinterest')) {
1541
1797
  const pinterestDescription = this.getNodeParameter('pinterestDescription', i, '');
@@ -1621,7 +1877,7 @@ class UploadPost {
1621
1877
  break;
1622
1878
  case 'uploadText':
1623
1879
  endpoint = '/upload_text';
1624
- const allowedTextPlatforms = ['x', 'linkedin', 'facebook', 'threads'];
1880
+ const allowedTextPlatforms = ['x', 'linkedin', 'facebook', 'threads', 'reddit'];
1625
1881
  platforms = platforms.filter(p => allowedTextPlatforms.includes(p));
1626
1882
  formData['platform[]'] = platforms;
1627
1883
  break;
@@ -1713,7 +1969,11 @@ class UploadPost {
1713
1969
  break;
1714
1970
  }
1715
1971
  if (isUploadOperation && platforms.includes('pinterest')) {
1716
- const pinterestBoardId = this.getNodeParameter('pinterestBoardId', i);
1972
+ const pinterestSelection = this.getNodeParameter('pinterestBoardId', i);
1973
+ const pinterestManual = pinterestSelection === MANUAL_PINTEREST_VALUE
1974
+ ? this.getNodeParameter('pinterestBoardIdManual', i)
1975
+ : '';
1976
+ const pinterestBoardId = pinterestSelection === MANUAL_PINTEREST_VALUE ? pinterestManual : pinterestSelection;
1717
1977
  if (pinterestBoardId)
1718
1978
  formData.pinterest_board_id = pinterestBoardId;
1719
1979
  const pinterestLink = this.getNodeParameter('pinterestLink', i);
@@ -1742,8 +2002,12 @@ class UploadPost {
1742
2002
  }
1743
2003
  }
1744
2004
  if (isUploadOperation && platforms.includes('linkedin')) {
1745
- const targetLinkedinPageId = this.getNodeParameter('targetLinkedinPageId', i);
1746
- if (targetLinkedinPageId) {
2005
+ const targetLinkedinSelection = this.getNodeParameter('targetLinkedinPageId', i);
2006
+ const targetLinkedinManual = targetLinkedinSelection === MANUAL_LINKEDIN_VALUE
2007
+ ? this.getNodeParameter('targetLinkedinPageIdManual', i)
2008
+ : '';
2009
+ const targetLinkedinPageId = targetLinkedinSelection === MANUAL_LINKEDIN_VALUE ? targetLinkedinManual : targetLinkedinSelection;
2010
+ if (targetLinkedinPageId && targetLinkedinPageId !== 'me') {
1747
2011
  const match = targetLinkedinPageId.match(/(\d+)$/);
1748
2012
  if (match) {
1749
2013
  formData.target_linkedin_page_id = match[1];
@@ -1764,7 +2028,11 @@ class UploadPost {
1764
2028
  }
1765
2029
  }
1766
2030
  if (isUploadOperation && platforms.includes('facebook')) {
1767
- const facebookPageId = this.getNodeParameter('facebookPageId', i);
2031
+ const facebookPageSelection = this.getNodeParameter('facebookPageId', i);
2032
+ const facebookPageManual = facebookPageSelection === MANUAL_FACEBOOK_VALUE
2033
+ ? this.getNodeParameter('facebookPageIdManual', i)
2034
+ : '';
2035
+ const facebookPageId = facebookPageSelection === MANUAL_FACEBOOK_VALUE ? facebookPageManual : facebookPageSelection;
1768
2036
  formData.facebook_page_id = facebookPageId;
1769
2037
  if (operation === 'uploadVideo') {
1770
2038
  const facebookVideoState = this.getNodeParameter('facebookVideoState', i);
@@ -1780,25 +2048,25 @@ class UploadPost {
1780
2048
  else if (operation === 'uploadText') {
1781
2049
  const facebookLink = this.getNodeParameter('facebookLink', i);
1782
2050
  if (facebookLink)
1783
- formData.link = facebookLink;
2051
+ formData.facebook_link_url = facebookLink;
1784
2052
  }
1785
2053
  }
1786
2054
  if (isUploadOperation && platforms.includes('tiktok')) {
1787
2055
  if (operation === 'uploadPhotos') {
1788
2056
  const tiktokAutoAddMusic = this.getNodeParameter('tiktokAutoAddMusic', i);
1789
2057
  const tiktokDisableComment = this.getNodeParameter('tiktokDisableComment', i);
1790
- const tiktokBrandedContentPhoto = this.getNodeParameter('tiktokBrandedContentPhoto', i);
1791
- const tiktokDiscloseCommercialPhoto = this.getNodeParameter('tiktokDiscloseCommercialPhoto', i);
2058
+ const brandContentToggle = this.getNodeParameter('brand_content_toggle', i);
2059
+ const brandOrganicToggle = this.getNodeParameter('brand_organic_toggle', i);
1792
2060
  const tiktokPhotoCoverIndex = this.getNodeParameter('tiktokPhotoCoverIndex', i);
1793
2061
  const tiktokPhotoDescription = this.getNodeParameter('tiktokPhotoDescription', i);
1794
2062
  if (tiktokAutoAddMusic !== undefined)
1795
2063
  formData.auto_add_music = String(tiktokAutoAddMusic);
1796
2064
  if (tiktokDisableComment !== undefined)
1797
2065
  formData.disable_comment = String(tiktokDisableComment);
1798
- if (tiktokBrandedContentPhoto !== undefined)
1799
- formData.branded_content = String(tiktokBrandedContentPhoto);
1800
- if (tiktokDiscloseCommercialPhoto !== undefined)
1801
- formData.disclose_commercial = String(tiktokDiscloseCommercialPhoto);
2066
+ if (brandContentToggle !== undefined)
2067
+ formData.brand_content_toggle = String(brandContentToggle);
2068
+ if (brandOrganicToggle !== undefined)
2069
+ formData.brand_organic_toggle = String(brandOrganicToggle);
1802
2070
  if (tiktokPhotoCoverIndex !== undefined)
1803
2071
  formData.photo_cover_index = tiktokPhotoCoverIndex;
1804
2072
  if (tiktokPhotoDescription && formData.description === undefined)
@@ -1810,10 +2078,8 @@ class UploadPost {
1810
2078
  const tiktokDisableComment = this.getNodeParameter('tiktokDisableComment', i);
1811
2079
  const tiktokDisableStitch = this.getNodeParameter('tiktokDisableStitch', i);
1812
2080
  const tiktokCoverTimestamp = this.getNodeParameter('tiktokCoverTimestamp', i);
1813
- const tiktokBrandContentToggle = this.getNodeParameter('tiktokBrandContentToggle', i);
1814
- const tiktokBrandOrganic = this.getNodeParameter('tiktokBrandOrganic', i);
1815
- const tiktokBrandedContentVideo = this.getNodeParameter('tiktokBrandedContentVideo', i);
1816
- const tiktokBrandOrganicToggle = this.getNodeParameter('tiktokBrandOrganicToggle', i);
2081
+ const brandContentToggle = this.getNodeParameter('brand_content_toggle', i);
2082
+ const brandOrganicToggle = this.getNodeParameter('brand_organic_toggle', i);
1817
2083
  const tiktokIsAigc = this.getNodeParameter('tiktokIsAigc', i);
1818
2084
  const tiktokPostMode = this.getNodeParameter('tiktokPostMode', i);
1819
2085
  if (tiktokPrivacyLevel)
@@ -1826,14 +2092,10 @@ class UploadPost {
1826
2092
  formData.disable_stitch = String(tiktokDisableStitch);
1827
2093
  if (tiktokCoverTimestamp !== undefined)
1828
2094
  formData.cover_timestamp = tiktokCoverTimestamp;
1829
- if (tiktokBrandContentToggle !== undefined)
1830
- formData.brand_content_toggle = String(tiktokBrandContentToggle);
1831
- if (tiktokBrandOrganic !== undefined)
1832
- formData.brand_organic = String(tiktokBrandOrganic);
1833
- if (tiktokBrandedContentVideo !== undefined)
1834
- formData.branded_content = String(tiktokBrandedContentVideo);
1835
- if (tiktokBrandOrganicToggle !== undefined)
1836
- formData.brand_organic_toggle = String(tiktokBrandOrganicToggle);
2095
+ if (brandContentToggle !== undefined)
2096
+ formData.brand_content_toggle = String(brandContentToggle);
2097
+ if (brandOrganicToggle !== undefined)
2098
+ formData.brand_organic_toggle = String(brandOrganicToggle);
1837
2099
  if (tiktokIsAigc !== undefined)
1838
2100
  formData.is_aigc = String(tiktokIsAigc);
1839
2101
  if (tiktokPostMode)
@@ -1889,7 +2151,7 @@ class UploadPost {
1889
2151
  const youtubeMadeForKids = this.getNodeParameter('youtubeMadeForKids', i);
1890
2152
  const youtubeThumbnail = this.getNodeParameter('youtubeThumbnail', i);
1891
2153
  if (youtubeTagsRaw)
1892
- formData.tags = youtubeTagsRaw.split(',').map(tag => tag.trim());
2154
+ formData['tags[]'] = youtubeTagsRaw.split(',').map(tag => tag.trim());
1893
2155
  if (youtubeCategoryId)
1894
2156
  formData.categoryId = youtubeCategoryId;
1895
2157
  if (youtubePrivacyStatus)
@@ -1924,18 +2186,96 @@ class UploadPost {
1924
2186
  }
1925
2187
  }
1926
2188
  }
2189
+ const youtubeSelfDeclaredMadeForKids = this.getNodeParameter('youtubeSelfDeclaredMadeForKids', i);
2190
+ const youtubeContainsSyntheticMedia = this.getNodeParameter('youtubeContainsSyntheticMedia', i);
2191
+ const youtubeDefaultLanguage = this.getNodeParameter('youtubeDefaultLanguage', i);
2192
+ const youtubeDefaultAudioLanguage = this.getNodeParameter('youtubeDefaultAudioLanguage', i);
2193
+ const youtubeAllowedCountries = this.getNodeParameter('youtubeAllowedCountries', i);
2194
+ const youtubeBlockedCountries = this.getNodeParameter('youtubeBlockedCountries', i);
2195
+ const youtubeHasPaidProductPlacement = this.getNodeParameter('youtubeHasPaidProductPlacement', i);
2196
+ const youtubeRecordingDate = this.getNodeParameter('youtubeRecordingDate', i);
2197
+ if (youtubeSelfDeclaredMadeForKids !== undefined)
2198
+ formData.selfDeclaredMadeForKids = String(youtubeSelfDeclaredMadeForKids);
2199
+ if (youtubeContainsSyntheticMedia !== undefined)
2200
+ formData.containsSyntheticMedia = String(youtubeContainsSyntheticMedia);
2201
+ if (youtubeDefaultLanguage)
2202
+ formData.defaultLanguage = youtubeDefaultLanguage;
2203
+ if (youtubeDefaultAudioLanguage)
2204
+ formData.defaultAudioLanguage = youtubeDefaultAudioLanguage;
2205
+ if (youtubeAllowedCountries)
2206
+ formData.allowedCountries = youtubeAllowedCountries;
2207
+ if (youtubeBlockedCountries)
2208
+ formData.blockedCountries = youtubeBlockedCountries;
2209
+ if (youtubeHasPaidProductPlacement !== undefined)
2210
+ formData.hasPaidProductPlacement = String(youtubeHasPaidProductPlacement);
2211
+ if (youtubeRecordingDate)
2212
+ formData.recordingDate = youtubeRecordingDate;
1927
2213
  }
1928
2214
  if (isUploadOperation && platforms.includes('x')) {
2215
+ const xQuoteTweetId = this.getNodeParameter('xQuoteTweetId', i, '');
2216
+ const xGeoPlaceId = this.getNodeParameter('xGeoPlaceId', i, '');
2217
+ const xForSuperFollowersOnly = this.getNodeParameter('xForSuperFollowersOnly', i, false);
2218
+ const xCommunityId = this.getNodeParameter('xCommunityId', i, '');
2219
+ const xShareWithFollowers = this.getNodeParameter('xShareWithFollowers', i, false);
2220
+ const xDirectMessageDeepLink = this.getNodeParameter('xDirectMessageDeepLink', i, '');
2221
+ const xCardUri = this.getNodeParameter('xCardUri', i, '');
2222
+ if (xQuoteTweetId)
2223
+ formData.quote_tweet_id = xQuoteTweetId;
2224
+ if (xGeoPlaceId)
2225
+ formData.geo_place_id = xGeoPlaceId;
2226
+ if (xForSuperFollowersOnly)
2227
+ formData.for_super_followers_only = String(xForSuperFollowersOnly);
2228
+ if (xCommunityId)
2229
+ formData.community_id = xCommunityId;
2230
+ if (xShareWithFollowers)
2231
+ formData.share_with_followers = String(xShareWithFollowers);
2232
+ if (xDirectMessageDeepLink)
2233
+ formData.direct_message_deep_link = xDirectMessageDeepLink;
2234
+ if (xCardUri)
2235
+ formData.card_uri = xCardUri;
1929
2236
  if (operation === 'uploadText') {
1930
2237
  const xPostUrlText = this.getNodeParameter('xPostUrlText', i);
1931
2238
  if (xPostUrlText)
1932
2239
  formData.post_url = xPostUrlText;
1933
- const xTaggedUserIdsText = this.getNodeParameter('xTaggedUserIds', i);
1934
2240
  const xReplySettingsText = this.getNodeParameter('xReplySettings', i);
1935
- if (xTaggedUserIdsText)
1936
- formData.tagged_user_ids = xTaggedUserIdsText.split(',').map(id => id.trim());
1937
- if (xReplySettingsText)
2241
+ if (xReplySettingsText && xReplySettingsText !== 'everyone')
1938
2242
  formData.reply_settings = xReplySettingsText;
2243
+ const xPollDuration = this.getNodeParameter('xPollDuration', i, 1440);
2244
+ const xPollOptionsRaw = this.getNodeParameter('xPollOptions', i, '');
2245
+ const xPollReplySettings = this.getNodeParameter('xPollReplySettings', i, 'following');
2246
+ const xCardUri = this.getNodeParameter('xCardUri', i, '');
2247
+ const xQuoteTweetId = this.getNodeParameter('xQuoteTweetId', i, '');
2248
+ const xDirectMessageDeepLink = this.getNodeParameter('xDirectMessageDeepLink', i, '');
2249
+ const hasPollOptions = xPollOptionsRaw && xPollOptionsRaw.trim();
2250
+ const hasCardUri = xCardUri && xCardUri.trim();
2251
+ const hasQuoteTweetId = xQuoteTweetId && xQuoteTweetId.trim();
2252
+ const hasDirectMessageDeepLink = xDirectMessageDeepLink && xDirectMessageDeepLink.trim();
2253
+ if (hasPollOptions && (hasCardUri || hasQuoteTweetId || hasDirectMessageDeepLink)) {
2254
+ const conflictingFields = [];
2255
+ if (hasCardUri)
2256
+ conflictingFields.push('X Card URI');
2257
+ if (hasQuoteTweetId)
2258
+ conflictingFields.push('X Quote Tweet ID');
2259
+ if (hasDirectMessageDeepLink)
2260
+ conflictingFields.push('X Direct Message Deep Link');
2261
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `X Poll Options cannot be used with: ${conflictingFields.join(', ')}. These fields are mutually exclusive.`);
2262
+ }
2263
+ if (hasPollOptions) {
2264
+ const pollOptions = xPollOptionsRaw.split(',').map(opt => opt.trim()).filter(opt => opt.length > 0);
2265
+ if (pollOptions.length < 2 || pollOptions.length > 4) {
2266
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `X Poll Options must contain between 2 and 4 non-empty options. Found: ${pollOptions.length}`);
2267
+ }
2268
+ const invalidOptions = pollOptions.filter(opt => opt.length > 25);
2269
+ if (invalidOptions.length > 0) {
2270
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `X Poll Options cannot exceed 25 characters each. Invalid options: ${invalidOptions.join(', ')}`);
2271
+ }
2272
+ if (xPollDuration < 5 || xPollDuration > 10080) {
2273
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `X Poll Duration must be between 5 and 10080 minutes (5 minutes to 7 days). Provided: ${xPollDuration}`);
2274
+ }
2275
+ formData['poll_options[]'] = pollOptions;
2276
+ formData.poll_duration = xPollDuration;
2277
+ formData.poll_reply_settings = xPollReplySettings;
2278
+ }
1939
2279
  try {
1940
2280
  const xLongTextAsPostText = this.getNodeParameter('xLongTextAsPost', i, false);
1941
2281
  if (xLongTextAsPostText)
@@ -1944,35 +2284,44 @@ class UploadPost {
1944
2284
  catch { }
1945
2285
  delete formData.nullcast;
1946
2286
  delete formData.place_id;
1947
- delete formData.poll_duration;
1948
- delete formData.poll_options;
1949
- delete formData.poll_reply_settings;
1950
2287
  }
1951
- else if (operation === 'uploadVideo') {
2288
+ else if (operation === 'uploadVideo' || operation === 'uploadPhotos') {
1952
2289
  const xTaggedUserIds = this.getNodeParameter('xTaggedUserIds', i);
1953
2290
  const xReplySettings = this.getNodeParameter('xReplySettings', i);
1954
2291
  const xNullcastVideo = this.getNodeParameter('xNullcastVideo', i);
1955
- const xPlaceIdVideo = this.getNodeParameter('xPlaceIdVideo', i);
1956
- const xPollDurationVideo = this.getNodeParameter('xPollDurationVideo', i);
1957
- const xPollOptionsVideoRaw = this.getNodeParameter('xPollOptionsVideo', i);
1958
- const xPollReplySettingsVideo = this.getNodeParameter('xPollReplySettingsVideo', i);
1959
- const xLongTextAsPost = this.getNodeParameter('xLongTextAsPost', i, false);
1960
2292
  if (xTaggedUserIds)
1961
- formData.tagged_user_ids = xTaggedUserIds.split(',').map(id => id.trim());
1962
- if (xReplySettings)
2293
+ formData['tagged_user_ids[]'] = xTaggedUserIds.split(',').map(id => id.trim());
2294
+ if (xReplySettings && xReplySettings !== 'everyone')
1963
2295
  formData.reply_settings = xReplySettings;
1964
2296
  if (xNullcastVideo !== undefined)
1965
2297
  formData.nullcast = String(xNullcastVideo);
1966
- if (xPlaceIdVideo)
1967
- formData.place_id = xPlaceIdVideo;
1968
- if (xPollDurationVideo !== undefined)
1969
- formData.poll_duration = xPollDurationVideo;
1970
- if (xPollOptionsVideoRaw)
1971
- formData.poll_options = xPollOptionsVideoRaw.split(',').map(opt => opt.trim());
1972
- if (xPollReplySettingsVideo)
1973
- formData.poll_reply_settings = xPollReplySettingsVideo;
1974
- if (xLongTextAsPost)
1975
- formData.x_long_text_as_post = String(xLongTextAsPost);
2298
+ if (operation === 'uploadVideo') {
2299
+ try {
2300
+ const xLongTextAsPost = this.getNodeParameter('xLongTextAsPost', i, false);
2301
+ if (xLongTextAsPost)
2302
+ formData.x_long_text_as_post = String(xLongTextAsPost);
2303
+ }
2304
+ catch { }
2305
+ }
2306
+ }
2307
+ }
2308
+ if (isUploadOperation && platforms.includes('threads')) {
2309
+ if (operation === 'uploadText') {
2310
+ const threadsTitle = this.getNodeParameter('threadsTitle', i, '');
2311
+ if (threadsTitle)
2312
+ formData.threads_title = threadsTitle;
2313
+ const threadsLongTextAsPost = this.getNodeParameter('threadsLongTextAsPost', i, false);
2314
+ if (threadsLongTextAsPost)
2315
+ formData.threads_long_text_as_post = String(threadsLongTextAsPost);
2316
+ }
2317
+ }
2318
+ if (isUploadOperation && platforms.includes('reddit')) {
2319
+ if (operation === 'uploadText') {
2320
+ const redditSubreddit = this.getNodeParameter('redditSubreddit', i);
2321
+ formData.subreddit = redditSubreddit;
2322
+ const redditFlairId = this.getNodeParameter('redditFlairId', i, '');
2323
+ if (redditFlairId)
2324
+ formData.flair_id = redditFlairId;
1976
2325
  }
1977
2326
  }
1978
2327
  const credentials = await this.getCredentials('uploadPostApi');
@@ -2006,6 +2355,8 @@ class UploadPost {
2006
2355
  options.qs = qs;
2007
2356
  }
2008
2357
  }
2358
+ this.logger.info(`Operation: ${operation}, Is Upload Operation: ${isUploadOperation}`);
2359
+ this.logger.info('Complete Form Data being sent (JSON): ' + JSON.stringify(formData, null, 2));
2009
2360
  const responseData = await this.helpers.request(options);
2010
2361
  const shouldConsiderPolling = operation === 'uploadPhotos' || operation === 'uploadVideo' || operation === 'uploadText';
2011
2362
  const waitForCompletion = shouldConsiderPolling ? this.getNodeParameter('waitForCompletion', i, false) : false;