n8n-nodes-multi-upload-tool 0.1.0 → 0.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.
@@ -9,7 +9,7 @@ const PLATFORMS = [
9
9
  { name: 'Instagram', value: 'instagram' },
10
10
  { name: 'LinkedIn', value: 'linkedin' },
11
11
  { name: 'Pinterest', value: 'pinterest' },
12
- { name: 'Telegram', value: 'telegram' },
12
+ { name: 'Threads', value: 'threads' },
13
13
  { name: 'TikTok', value: 'tiktok' },
14
14
  { name: 'YouTube', value: 'youtube' },
15
15
  ];
@@ -43,6 +43,7 @@ class MultiUploadTool {
43
43
  options: [
44
44
  { name: 'Account', value: 'account' },
45
45
  { name: 'Pinterest', value: 'pinterest' },
46
+ { name: 'Pool', value: 'pool' },
46
47
  { name: 'Short Link', value: 'shortLink' },
47
48
  { name: 'Upload', value: 'upload' },
48
49
  { name: 'Webhook', value: 'webhook' },
@@ -69,6 +70,18 @@ class MultiUploadTool {
69
70
  description: 'Upload to multiple connected accounts at once',
70
71
  action: 'Bulk create uploads',
71
72
  },
73
+ {
74
+ name: 'Upload Text',
75
+ value: 'uploadText',
76
+ description: 'Post text-only content (Facebook, LinkedIn, Bluesky)',
77
+ action: 'Upload text post',
78
+ },
79
+ {
80
+ name: 'Upload Document',
81
+ value: 'uploadDocument',
82
+ description: 'Upload a document (PDF, DOCX) — LinkedIn only',
83
+ action: 'Upload a document',
84
+ },
72
85
  {
73
86
  name: 'Get',
74
87
  value: 'get',
@@ -87,6 +100,24 @@ class MultiUploadTool {
87
100
  description: 'Update the metadata of an existing upload',
88
101
  action: 'Update an upload',
89
102
  },
103
+ {
104
+ name: 'List Scheduled',
105
+ value: 'listScheduled',
106
+ description: 'Retrieve all scheduled (pending) posts',
107
+ action: 'List scheduled posts',
108
+ },
109
+ {
110
+ name: 'Cancel Scheduled',
111
+ value: 'cancelScheduled',
112
+ description: 'Cancel a scheduled post',
113
+ action: 'Cancel a scheduled post',
114
+ },
115
+ {
116
+ name: 'Edit Scheduled',
117
+ value: 'editScheduled',
118
+ description: 'Update the date or content of a scheduled post',
119
+ action: 'Edit a scheduled post',
120
+ },
90
121
  ],
91
122
  default: 'create',
92
123
  },
@@ -142,6 +173,53 @@ class MultiUploadTool {
142
173
  ],
143
174
  default: 'getBoards',
144
175
  },
176
+ // ─── POOL: OPERATIONS ──────────────────────────────────────────────────
177
+ {
178
+ displayName: 'Operation',
179
+ name: 'operation',
180
+ type: 'options',
181
+ noDataExpression: true,
182
+ displayOptions: { show: { resource: ['pool'] } },
183
+ options: [
184
+ {
185
+ name: 'List Pools',
186
+ value: 'listPools',
187
+ description: 'Get all upload pools',
188
+ action: 'List all pools',
189
+ },
190
+ {
191
+ name: 'Get Pool',
192
+ value: 'getPool',
193
+ description: 'Get details of a specific pool',
194
+ action: 'Get a pool',
195
+ },
196
+ {
197
+ name: 'Check Capacity',
198
+ value: 'checkCapacity',
199
+ description: 'Preview how many upcoming slots a pool has',
200
+ action: 'Check pool capacity',
201
+ },
202
+ {
203
+ name: 'Add Video',
204
+ value: 'addVideo',
205
+ description: 'Add a video or photo carousel to a pool queue',
206
+ action: 'Add a video to pool',
207
+ },
208
+ {
209
+ name: 'List Videos',
210
+ value: 'listVideos',
211
+ description: 'Get all queued videos in a pool',
212
+ action: 'List videos in pool',
213
+ },
214
+ {
215
+ name: 'Remove Video',
216
+ value: 'removeVideo',
217
+ description: 'Remove a queued item from a pool',
218
+ action: 'Remove video from pool',
219
+ },
220
+ ],
221
+ default: 'listPools',
222
+ },
145
223
  // ─── WEBHOOK: OPERATIONS ──────────────────────────────────────────────
146
224
  {
147
225
  displayName: 'Operation',
@@ -219,7 +297,7 @@ class MultiUploadTool {
219
297
  default: 'create',
220
298
  },
221
299
  // ═══════════════════════════════════════════════════════════════════════
222
- // UPLOAD › CREATE
300
+ // UPLOAD › CREATE / BULK CREATE
223
301
  // ═══════════════════════════════════════════════════════════════════════
224
302
  {
225
303
  displayName: 'Account ID',
@@ -299,8 +377,54 @@ class MultiUploadTool {
299
377
  default: '',
300
378
  description: 'Comma-separated hashtags or tags (YouTube, TikTok). Do not include the # symbol.',
301
379
  },
380
+ // ── Universal scheduling ──────────────────────────────────────────
381
+ {
382
+ displayName: 'Scheduled Date',
383
+ name: 'scheduledDate',
384
+ type: 'dateTime',
385
+ default: '',
386
+ description: 'Schedule the post for a future date/time. Applied to all platforms (TikTok, Instagram, YouTube, etc.).',
387
+ },
388
+ {
389
+ displayName: 'Timezone',
390
+ name: 'timezone',
391
+ type: 'string',
392
+ default: 'UTC',
393
+ description: 'IANA timezone for the scheduled date (e.g. America/New_York). Stored as metadata.',
394
+ },
395
+ {
396
+ displayName: 'Wait For Completion',
397
+ name: 'waitForCompletion',
398
+ type: 'boolean',
399
+ default: false,
400
+ description: 'Whether to wait synchronously for the upload job to finish (up to 10 min). If false, returns a job ID immediately.',
401
+ },
402
+ {
403
+ displayName: 'First Comment',
404
+ name: 'firstComment',
405
+ type: 'string',
406
+ typeOptions: { rows: 3 },
407
+ default: '',
408
+ description: 'First comment to post after publishing. NOTE: stored in metadata, not yet processed by the backend — reserved for future use.',
409
+ },
410
+ // ── YouTube ───────────────────────────────────────────────────────
411
+ {
412
+ displayName: 'YouTube Title',
413
+ name: 'youtube_title',
414
+ type: 'string',
415
+ default: '',
416
+ description: 'Override the generic title for YouTube only',
417
+ },
302
418
  {
303
- displayName: 'Privacy Status',
419
+ displayName: 'YouTube Description',
420
+ name: 'youtube_description',
421
+ type: 'string',
422
+ typeOptions: { rows: 4 },
423
+ default: '',
424
+ description: 'Override the generic description for YouTube only',
425
+ },
426
+ {
427
+ displayName: 'YouTube Privacy Status',
304
428
  name: 'privacyStatus',
305
429
  type: 'options',
306
430
  options: [
@@ -309,7 +433,7 @@ class MultiUploadTool {
309
433
  { name: 'Unlisted', value: 'unlisted' },
310
434
  ],
311
435
  default: 'public',
312
- description: 'Privacy level of the post (YouTube)',
436
+ description: 'Privacy level (YouTube)',
313
437
  },
314
438
  {
315
439
  displayName: 'YouTube Category ID',
@@ -319,12 +443,45 @@ class MultiUploadTool {
319
443
  description: 'YouTube category ID (e.g. 22 for People & Blogs). See the YouTube API for the full list.',
320
444
  },
321
445
  {
322
- displayName: 'Schedule Date',
323
- name: 'schedule_date',
324
- type: 'dateTime',
446
+ displayName: 'YouTube Thumbnail URL',
447
+ name: 'thumbnail_url',
448
+ type: 'string',
325
449
  default: '',
326
- description: 'Schedule the post for a future date/time (TikTok, Instagram, YouTube)',
450
+ description: 'Public URL of the thumbnail image (YouTube)',
327
451
  },
452
+ {
453
+ displayName: 'YouTube Embeddable',
454
+ name: 'embeddable',
455
+ type: 'boolean',
456
+ default: true,
457
+ description: 'Whether to allow the video to be embedded (YouTube)',
458
+ },
459
+ {
460
+ displayName: 'YouTube Notify Subscribers',
461
+ name: 'notifySubscribers',
462
+ type: 'boolean',
463
+ default: true,
464
+ description: 'Whether to notify subscribers when the video is published (YouTube)',
465
+ },
466
+ {
467
+ displayName: 'YouTube Made For Kids',
468
+ name: 'madeForKids',
469
+ type: 'boolean',
470
+ default: false,
471
+ description: 'Whether the content is made for kids — COPPA compliance (YouTube)',
472
+ },
473
+ {
474
+ displayName: 'YouTube License',
475
+ name: 'license',
476
+ type: 'options',
477
+ options: [
478
+ { name: 'Standard YouTube License', value: 'youtube' },
479
+ { name: 'Creative Commons', value: 'creativeCommon' },
480
+ ],
481
+ default: 'youtube',
482
+ description: 'License type (YouTube)',
483
+ },
484
+ // ── TikTok ────────────────────────────────────────────────────────
328
485
  {
329
486
  displayName: 'TikTok Privacy Level',
330
487
  name: 'privacy_level',
@@ -338,6 +495,106 @@ class MultiUploadTool {
338
495
  default: 'PUBLIC_TO_EVERYONE',
339
496
  description: 'Audience visibility (TikTok only)',
340
497
  },
498
+ {
499
+ displayName: 'TikTok Disable Comment',
500
+ name: 'disable_comment',
501
+ type: 'boolean',
502
+ default: false,
503
+ description: 'Whether to disable comments (TikTok only)',
504
+ },
505
+ {
506
+ displayName: 'TikTok Disable Duet',
507
+ name: 'disable_duet',
508
+ type: 'boolean',
509
+ default: false,
510
+ description: 'Whether to disable Duet (TikTok only)',
511
+ },
512
+ {
513
+ displayName: 'TikTok Disable Stitch',
514
+ name: 'disable_stitch',
515
+ type: 'boolean',
516
+ default: false,
517
+ description: 'Whether to disable Stitch (TikTok only)',
518
+ },
519
+ {
520
+ displayName: 'TikTok Brand Content',
521
+ name: 'brand_content_toggle',
522
+ type: 'boolean',
523
+ default: false,
524
+ description: 'Whether to mark as branded content / paid partnership (TikTok only)',
525
+ },
526
+ {
527
+ displayName: 'TikTok Brand Organic',
528
+ name: 'brand_organic_toggle',
529
+ type: 'boolean',
530
+ default: false,
531
+ description: 'Whether to mark as promoting own brand organically (TikTok only)',
532
+ },
533
+ {
534
+ displayName: 'TikTok AI Generated Content',
535
+ name: 'is_aigc',
536
+ type: 'boolean',
537
+ default: false,
538
+ description: 'Whether to declare the content as AI-generated (TikTok only)',
539
+ },
540
+ // ── Instagram ─────────────────────────────────────────────────────
541
+ {
542
+ displayName: 'Instagram Media Type',
543
+ name: 'media_type',
544
+ type: 'options',
545
+ options: [
546
+ { name: 'Auto Detect', value: '' },
547
+ { name: 'Image', value: 'IMAGE' },
548
+ { name: 'Reels', value: 'REELS' },
549
+ { name: 'Stories', value: 'STORIES' },
550
+ { name: 'Carousel', value: 'CAROUSEL' },
551
+ ],
552
+ default: '',
553
+ description: 'Override media type for Instagram posts. Leave as Auto Detect in most cases.',
554
+ },
555
+ {
556
+ displayName: 'Instagram Share to Feed',
557
+ name: 'share_to_feed',
558
+ type: 'boolean',
559
+ default: true,
560
+ description: 'Whether to share Reels/Stories to the main feed grid (Instagram only)',
561
+ },
562
+ {
563
+ displayName: 'Instagram Cover URL',
564
+ name: 'cover_url',
565
+ type: 'string',
566
+ default: '',
567
+ description: 'Cover image URL for Instagram video posts',
568
+ },
569
+ {
570
+ displayName: 'Instagram Audio Name',
571
+ name: 'audio_name',
572
+ type: 'string',
573
+ default: '',
574
+ description: 'Audio track name (Instagram only)',
575
+ },
576
+ {
577
+ displayName: 'Instagram Thumbnail Offset (ms)',
578
+ name: 'thumb_offset',
579
+ type: 'string',
580
+ default: '',
581
+ description: 'Thumbnail frame offset in milliseconds (Instagram only)',
582
+ },
583
+ {
584
+ displayName: 'Instagram Collaborators',
585
+ name: 'collaborators',
586
+ type: 'string',
587
+ default: '',
588
+ description: 'Comma-separated Instagram usernames to tag as collaborators',
589
+ },
590
+ {
591
+ displayName: 'Instagram Location ID',
592
+ name: 'location_id',
593
+ type: 'string',
594
+ default: '',
595
+ description: 'Location ID for Instagram posts',
596
+ },
597
+ // ── LinkedIn ──────────────────────────────────────────────────────
341
598
  {
342
599
  displayName: 'LinkedIn Visibility',
343
600
  name: 'visibility',
@@ -349,6 +606,22 @@ class MultiUploadTool {
349
606
  default: 'PUBLIC',
350
607
  description: 'Post visibility (LinkedIn only)',
351
608
  },
609
+ {
610
+ displayName: 'LinkedIn Target Page ID',
611
+ name: 'targetPageId',
612
+ type: 'string',
613
+ default: '',
614
+ description: 'Post as a LinkedIn Page/Organization instead of the personal profile. Get the ID from the Get LinkedIn Pages operation.',
615
+ },
616
+ // ── Facebook ──────────────────────────────────────────────────────
617
+ {
618
+ displayName: 'Facebook / Pinterest Link',
619
+ name: 'link',
620
+ type: 'string',
621
+ default: '',
622
+ description: 'URL to attach to a Facebook link post or Pinterest pin',
623
+ },
624
+ // ── Pinterest ─────────────────────────────────────────────────────
352
625
  {
353
626
  displayName: 'Pinterest Board ID',
354
627
  name: 'board_id',
@@ -356,24 +629,225 @@ class MultiUploadTool {
356
629
  default: '',
357
630
  description: 'Board ID to pin to — required for Pinterest posts',
358
631
  },
632
+ {
633
+ displayName: 'Pinterest Alt Text',
634
+ name: 'alt_text',
635
+ type: 'string',
636
+ default: '',
637
+ description: 'Alt text for the Pinterest pin image',
638
+ },
639
+ {
640
+ displayName: 'Pinterest Cover Image URL',
641
+ name: 'cover_image_url',
642
+ type: 'string',
643
+ default: '',
644
+ description: 'Cover image URL for Pinterest video pins',
645
+ },
646
+ // ── Bluesky ───────────────────────────────────────────────────────
647
+ {
648
+ displayName: 'Bluesky Languages',
649
+ name: 'bluesky_langs',
650
+ type: 'string',
651
+ default: '',
652
+ description: 'Comma-separated BCP-47 language codes (e.g. en,fr) (Bluesky only)',
653
+ },
654
+ {
655
+ displayName: 'Bluesky Content Labels',
656
+ name: 'bluesky_labels',
657
+ type: 'string',
658
+ default: '',
659
+ description: 'Comma-separated content warning labels (e.g. nsfw,nudity) (Bluesky only)',
660
+ },
661
+ // ── Threads ───────────────────────────────────────────────────────
662
+ {
663
+ displayName: 'Threads Reply Control',
664
+ name: 'threads_reply_control',
665
+ type: 'options',
666
+ default: '',
667
+ description: 'Who can reply to this Threads post (Threads only)',
668
+ options: [
669
+ { name: 'Everyone', value: 'everyone' },
670
+ { name: 'Accounts You Follow', value: 'accounts_you_follow' },
671
+ { name: 'Mentioned Only', value: 'mentioned_only' },
672
+ { name: 'Followers Only', value: 'followers_only' },
673
+ { name: 'Author Only', value: 'parent_post_author_only' },
674
+ ],
675
+ },
676
+ {
677
+ displayName: 'Threads Alt Text',
678
+ name: 'threads_alt_text',
679
+ type: 'string',
680
+ default: '',
681
+ description: 'Alt text for the media (Threads only)',
682
+ },
683
+ ],
684
+ },
685
+ // ═══════════════════════════════════════════════════════════════════════
686
+ // UPLOAD › UPLOAD TEXT
687
+ // ═══════════════════════════════════════════════════════════════════════
688
+ {
689
+ displayName: 'Account ID',
690
+ name: 'accountId',
691
+ type: 'string',
692
+ required: true,
693
+ displayOptions: { show: { resource: ['upload'], operation: ['uploadText'] } },
694
+ default: '',
695
+ description: 'ID of the connected account to post to',
696
+ },
697
+ {
698
+ displayName: 'Text Content',
699
+ name: 'textContent',
700
+ type: 'string',
701
+ required: true,
702
+ typeOptions: { rows: 6 },
703
+ displayOptions: { show: { resource: ['upload'], operation: ['uploadText'] } },
704
+ default: '',
705
+ description: 'The text body of the post',
706
+ },
707
+ {
708
+ displayName: 'Additional Fields',
709
+ name: 'additionalFields',
710
+ type: 'collection',
711
+ placeholder: 'Add Field',
712
+ displayOptions: { show: { resource: ['upload'], operation: ['uploadText'] } },
713
+ default: {},
714
+ options: [
715
+ {
716
+ displayName: 'Scheduled Date',
717
+ name: 'scheduledDate',
718
+ type: 'dateTime',
719
+ default: '',
720
+ description: 'Schedule the post for a future date/time',
721
+ },
722
+ {
723
+ displayName: 'Timezone',
724
+ name: 'timezone',
725
+ type: 'string',
726
+ default: 'UTC',
727
+ description: 'IANA timezone (e.g. America/New_York)',
728
+ },
729
+ {
730
+ displayName: 'LinkedIn Visibility',
731
+ name: 'visibility',
732
+ type: 'options',
733
+ options: [
734
+ { name: 'Public', value: 'PUBLIC' },
735
+ { name: 'Connections Only', value: 'CONNECTIONS' },
736
+ ],
737
+ default: 'PUBLIC',
738
+ },
739
+ {
740
+ displayName: 'LinkedIn Target Page ID',
741
+ name: 'targetPageId',
742
+ type: 'string',
743
+ default: '',
744
+ description: 'Post as a LinkedIn Page instead of personal profile',
745
+ },
359
746
  {
360
747
  displayName: 'Facebook / Pinterest Link',
361
748
  name: 'link',
362
749
  type: 'string',
363
750
  default: '',
364
- description: 'URL to attach to a Facebook link post or Pinterest pin',
751
+ description: 'URL to attach as a link preview (Facebook)',
752
+ },
753
+ {
754
+ displayName: 'Wait For Completion',
755
+ name: 'waitForCompletion',
756
+ type: 'boolean',
757
+ default: false,
758
+ description: 'Whether to wait synchronously for the post to be processed (up to 10 min)',
759
+ },
760
+ ],
761
+ },
762
+ // ═══════════════════════════════════════════════════════════════════════
763
+ // UPLOAD › UPLOAD DOCUMENT
764
+ // ═══════════════════════════════════════════════════════════════════════
765
+ {
766
+ displayName: 'Account ID',
767
+ name: 'accountId',
768
+ type: 'string',
769
+ required: true,
770
+ displayOptions: { show: { resource: ['upload'], operation: ['uploadDocument'] } },
771
+ default: '',
772
+ description: 'ID of the connected account to post to',
773
+ },
774
+ {
775
+ displayName: 'Binary Property',
776
+ name: 'binaryPropertyName',
777
+ type: 'string',
778
+ required: true,
779
+ displayOptions: { show: { resource: ['upload'], operation: ['uploadDocument'] } },
780
+ default: 'data',
781
+ description: 'Name of the binary property containing the document file (PDF, DOCX, etc.)',
782
+ },
783
+ {
784
+ displayName: 'Title',
785
+ name: 'title',
786
+ type: 'string',
787
+ required: true,
788
+ displayOptions: { show: { resource: ['upload'], operation: ['uploadDocument'] } },
789
+ default: '',
790
+ description: 'Title / filename for the document post (required for LinkedIn)',
791
+ },
792
+ {
793
+ displayName: 'Additional Fields',
794
+ name: 'additionalFields',
795
+ type: 'collection',
796
+ placeholder: 'Add Field',
797
+ displayOptions: { show: { resource: ['upload'], operation: ['uploadDocument'] } },
798
+ default: {},
799
+ options: [
800
+ {
801
+ displayName: 'Description / Caption',
802
+ name: 'description',
803
+ type: 'string',
804
+ typeOptions: { rows: 4 },
805
+ default: '',
806
+ },
807
+ {
808
+ displayName: 'LinkedIn Visibility',
809
+ name: 'visibility',
810
+ type: 'options',
811
+ options: [
812
+ { name: 'Public', value: 'PUBLIC' },
813
+ { name: 'Connections Only', value: 'CONNECTIONS' },
814
+ ],
815
+ default: 'PUBLIC',
816
+ },
817
+ {
818
+ displayName: 'LinkedIn Target Page ID',
819
+ name: 'targetPageId',
820
+ type: 'string',
821
+ default: '',
822
+ },
823
+ {
824
+ displayName: 'Scheduled Date',
825
+ name: 'scheduledDate',
826
+ type: 'dateTime',
827
+ default: '',
828
+ },
829
+ {
830
+ displayName: 'Wait For Completion',
831
+ name: 'waitForCompletion',
832
+ type: 'boolean',
833
+ default: false,
365
834
  },
366
835
  ],
367
836
  },
368
837
  // ═══════════════════════════════════════════════════════════════════════
369
- // UPLOAD › GET / UPDATE
838
+ // UPLOAD › GET / UPDATE / CANCEL SCHEDULED / EDIT SCHEDULED
370
839
  // ═══════════════════════════════════════════════════════════════════════
371
840
  {
372
841
  displayName: 'Upload ID',
373
842
  name: 'uploadId',
374
843
  type: 'string',
375
844
  required: true,
376
- displayOptions: { show: { resource: ['upload'], operation: ['get', 'update'] } },
845
+ displayOptions: {
846
+ show: {
847
+ resource: ['upload'],
848
+ operation: ['get', 'update', 'cancelScheduled', 'editScheduled'],
849
+ },
850
+ },
377
851
  default: '',
378
852
  description: 'ID of the upload',
379
853
  },
@@ -406,15 +880,45 @@ class MultiUploadTool {
406
880
  },
407
881
  ],
408
882
  },
883
+ {
884
+ displayName: 'Update Fields',
885
+ name: 'scheduleUpdateFields',
886
+ type: 'collection',
887
+ placeholder: 'Add Field',
888
+ displayOptions: { show: { resource: ['upload'], operation: ['editScheduled'] } },
889
+ default: {},
890
+ options: [
891
+ {
892
+ displayName: 'New Scheduled Date',
893
+ name: 'scheduledFor',
894
+ type: 'dateTime',
895
+ default: '',
896
+ description: 'New date/time for the scheduled post',
897
+ },
898
+ {
899
+ displayName: 'Title',
900
+ name: 'title',
901
+ type: 'string',
902
+ default: '',
903
+ },
904
+ {
905
+ displayName: 'Description',
906
+ name: 'description',
907
+ type: 'string',
908
+ typeOptions: { rows: 4 },
909
+ default: '',
910
+ },
911
+ ],
912
+ },
409
913
  // ═══════════════════════════════════════════════════════════════════════
410
- // UPLOAD › LIST
914
+ // UPLOAD › LIST / LIST SCHEDULED
411
915
  // ═══════════════════════════════════════════════════════════════════════
412
916
  {
413
917
  displayName: 'Filters',
414
918
  name: 'filters',
415
919
  type: 'collection',
416
920
  placeholder: 'Add Filter',
417
- displayOptions: { show: { resource: ['upload'], operation: ['list'] } },
921
+ displayOptions: { show: { resource: ['upload'], operation: ['list', 'listScheduled'] } },
418
922
  default: {},
419
923
  options: [
420
924
  {
@@ -436,6 +940,7 @@ class MultiUploadTool {
436
940
  { name: 'Processing', value: 'processing' },
437
941
  ],
438
942
  default: '',
943
+ description: 'Not used for List Scheduled (always filters to scheduled)',
439
944
  },
440
945
  {
441
946
  displayName: 'Search',
@@ -496,6 +1001,13 @@ class MultiUploadTool {
496
1001
  ],
497
1002
  default: '',
498
1003
  },
1004
+ {
1005
+ displayName: 'Tags',
1006
+ name: 'tags',
1007
+ type: 'string',
1008
+ default: '',
1009
+ description: 'Filter accounts by tag (partial match)',
1010
+ },
499
1011
  ],
500
1012
  },
501
1013
  // ═══════════════════════════════════════════════════════════════════════
@@ -511,6 +1023,104 @@ class MultiUploadTool {
511
1023
  description: 'ID of the Pinterest connected account',
512
1024
  },
513
1025
  // ═══════════════════════════════════════════════════════════════════════
1026
+ // POOL › FIELDS
1027
+ // ═══════════════════════════════════════════════════════════════════════
1028
+ {
1029
+ displayName: 'Pool ID',
1030
+ name: 'poolId',
1031
+ type: 'string',
1032
+ required: true,
1033
+ displayOptions: {
1034
+ show: {
1035
+ resource: ['pool'],
1036
+ operation: ['getPool', 'checkCapacity', 'addVideo', 'listVideos', 'removeVideo'],
1037
+ },
1038
+ },
1039
+ default: '',
1040
+ description: 'ID of the pool',
1041
+ },
1042
+ {
1043
+ displayName: 'Count',
1044
+ name: 'count',
1045
+ type: 'number',
1046
+ displayOptions: { show: { resource: ['pool'], operation: ['checkCapacity'] } },
1047
+ default: 1,
1048
+ description: 'Number of videos to check capacity for',
1049
+ },
1050
+ {
1051
+ displayName: 'Title',
1052
+ name: 'title',
1053
+ type: 'string',
1054
+ required: true,
1055
+ displayOptions: { show: { resource: ['pool'], operation: ['addVideo'] } },
1056
+ default: '',
1057
+ description: 'Title / caption for the content',
1058
+ },
1059
+ {
1060
+ displayName: 'Media',
1061
+ name: 'media',
1062
+ type: 'string',
1063
+ displayOptions: { show: { resource: ['pool'], operation: ['addVideo'] } },
1064
+ default: 'data',
1065
+ description: 'Comma-separated list of binary property names and/or public URLs. For a single file: <code>data</code>. For a photo carousel: <code>data0,data1,data2</code>. You can also mix binary names and URLs: <code>data0,https://cdn.example.com/slide2.jpg</code>.',
1066
+ },
1067
+ {
1068
+ displayName: 'Additional Fields',
1069
+ name: 'additionalFields',
1070
+ type: 'collection',
1071
+ placeholder: 'Add Field',
1072
+ displayOptions: { show: { resource: ['pool'], operation: ['addVideo'] } },
1073
+ default: {},
1074
+ options: [
1075
+ {
1076
+ displayName: 'Description',
1077
+ name: 'description',
1078
+ type: 'string',
1079
+ typeOptions: { rows: 4 },
1080
+ default: '',
1081
+ },
1082
+ {
1083
+ displayName: 'Metadata (JSON)',
1084
+ name: 'metadata',
1085
+ type: 'string',
1086
+ typeOptions: { rows: 4 },
1087
+ default: '',
1088
+ description: 'JSON object with platform-specific options, e.g. <code>{"privacy_level":"PUBLIC_TO_EVERYONE","disable_comment":false}</code>',
1089
+ },
1090
+ ],
1091
+ },
1092
+ {
1093
+ displayName: 'Item ID',
1094
+ name: 'itemId',
1095
+ type: 'string',
1096
+ required: true,
1097
+ displayOptions: { show: { resource: ['pool'], operation: ['removeVideo'] } },
1098
+ default: '',
1099
+ description: 'ID of the queued item to remove',
1100
+ },
1101
+ {
1102
+ displayName: 'Filters',
1103
+ name: 'filters',
1104
+ type: 'collection',
1105
+ placeholder: 'Add Filter',
1106
+ displayOptions: { show: { resource: ['pool'], operation: ['listVideos'] } },
1107
+ default: {},
1108
+ options: [
1109
+ {
1110
+ displayName: 'Status',
1111
+ name: 'status',
1112
+ type: 'options',
1113
+ options: [
1114
+ { name: 'All', value: '' },
1115
+ { name: 'Queued', value: 'queued' },
1116
+ { name: 'Posted', value: 'posted' },
1117
+ { name: 'Failed', value: 'failed' },
1118
+ ],
1119
+ default: '',
1120
+ },
1121
+ ],
1122
+ },
1123
+ // ═══════════════════════════════════════════════════════════════════════
514
1124
  // WEBHOOK › CREATE
515
1125
  // ═══════════════════════════════════════════════════════════════════════
516
1126
  {
@@ -820,14 +1430,14 @@ class MultiUploadTool {
820
1430
  };
821
1431
  }
822
1432
  async execute() {
823
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
1433
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
824
1434
  const items = this.getInputData();
825
1435
  const returnData = [];
826
1436
  const credentials = await this.getCredentials('multiUploadToolApi');
827
1437
  const apiToken = credentials.apiToken;
828
1438
  const defaultHeaders = {
829
1439
  'x-api-key': apiToken,
830
- 'User-Agent': 'n8n-nodes-multi-upload-tool/0.1.0',
1440
+ 'User-Agent': 'n8n-nodes-multi-upload-tool/0.2.0',
831
1441
  };
832
1442
  // ── JSON request helper ───────────────────────────────────────────────────
833
1443
  const apiRequest = async (method, endpoint, body, qs) => {
@@ -888,15 +1498,22 @@ class MultiUploadTool {
888
1498
  const blob = new Blob([fileBuffer], { type: mimeType });
889
1499
  formData.set(fieldName, blob, fileName);
890
1500
  }
1501
+ const waitForCompletion = additionalFields.waitForCompletion === true;
891
1502
  for (const [key, value] of Object.entries(additionalFields)) {
892
- if (value !== undefined && value !== '') {
893
- formData.set(key, String(value));
894
- // YouTube uses scheduledDate while TikTok/Instagram use schedule_date
895
- if (key === 'schedule_date')
896
- formData.set('scheduledDate', String(value));
1503
+ if (value === undefined || value === '' || value === null)
1504
+ continue;
1505
+ if (key === 'waitForCompletion')
1506
+ continue; // handled via query param
1507
+ if (key === 'scheduledDate') {
1508
+ // Send to both field names the backend uses
1509
+ formData.set('scheduledDate', String(value)); // YouTube
1510
+ formData.set('schedule_date', String(value)); // TikTok / Instagram
1511
+ continue;
897
1512
  }
1513
+ formData.set(key, String(value));
898
1514
  }
899
- const endpoint = operation === 'create' ? '/upload' : '/upload/bulk';
1515
+ const qs = waitForCompletion ? '?async=false' : '';
1516
+ const endpoint = operation === 'create' ? `/upload${qs}` : '/upload/bulk';
900
1517
  responseData = (await this.helpers.httpRequest({
901
1518
  method: 'POST',
902
1519
  url: `${BASE_URL}${endpoint}`,
@@ -905,6 +1522,69 @@ class MultiUploadTool {
905
1522
  json: true,
906
1523
  }));
907
1524
  }
1525
+ else if (operation === 'uploadText') {
1526
+ const accountId = this.getNodeParameter('accountId', i);
1527
+ const textContent = this.getNodeParameter('textContent', i);
1528
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
1529
+ const formData = new FormData();
1530
+ formData.set('accountId', accountId);
1531
+ formData.set('description', textContent);
1532
+ formData.set('media_type', 'TEXT');
1533
+ if (additionalFields.scheduledDate) {
1534
+ formData.set('scheduledDate', String(additionalFields.scheduledDate));
1535
+ formData.set('schedule_date', String(additionalFields.scheduledDate));
1536
+ }
1537
+ if (additionalFields.visibility)
1538
+ formData.set('visibility', String(additionalFields.visibility));
1539
+ if (additionalFields.targetPageId)
1540
+ formData.set('targetPageId', String(additionalFields.targetPageId));
1541
+ if (additionalFields.link)
1542
+ formData.set('link', String(additionalFields.link));
1543
+ const waitForCompletion = additionalFields.waitForCompletion === true;
1544
+ const qs = waitForCompletion ? '?async=false' : '';
1545
+ responseData = (await this.helpers.httpRequest({
1546
+ method: 'POST',
1547
+ url: `${BASE_URL}/upload${qs}`,
1548
+ headers: { 'x-api-key': apiToken },
1549
+ body: formData,
1550
+ json: true,
1551
+ }));
1552
+ }
1553
+ else if (operation === 'uploadDocument') {
1554
+ const accountId = this.getNodeParameter('accountId', i);
1555
+ const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
1556
+ const title = this.getNodeParameter('title', i);
1557
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
1558
+ const binaryItem = items[i].binary[binaryPropertyName];
1559
+ const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
1560
+ const fileName = (_d = binaryItem.fileName) !== null && _d !== void 0 ? _d : 'document';
1561
+ const mimeType = (_e = binaryItem.mimeType) !== null && _e !== void 0 ? _e : 'application/octet-stream';
1562
+ const formData = new FormData();
1563
+ formData.set('accountId', accountId);
1564
+ formData.set('title', title);
1565
+ formData.set('media_type', 'DOCUMENT');
1566
+ const blob = new Blob([fileBuffer], { type: mimeType });
1567
+ formData.set('file', blob, fileName);
1568
+ if (additionalFields.description)
1569
+ formData.set('description', String(additionalFields.description));
1570
+ if (additionalFields.visibility)
1571
+ formData.set('visibility', String(additionalFields.visibility));
1572
+ if (additionalFields.targetPageId)
1573
+ formData.set('targetPageId', String(additionalFields.targetPageId));
1574
+ if (additionalFields.scheduledDate) {
1575
+ formData.set('scheduledDate', String(additionalFields.scheduledDate));
1576
+ formData.set('schedule_date', String(additionalFields.scheduledDate));
1577
+ }
1578
+ const waitForCompletion = additionalFields.waitForCompletion === true;
1579
+ const qs = waitForCompletion ? '?async=false' : '';
1580
+ responseData = (await this.helpers.httpRequest({
1581
+ method: 'POST',
1582
+ url: `${BASE_URL}/upload${qs}`,
1583
+ headers: { 'x-api-key': apiToken },
1584
+ body: formData,
1585
+ json: true,
1586
+ }));
1587
+ }
908
1588
  else if (operation === 'list') {
909
1589
  const filters = this.getNodeParameter('filters', i, {});
910
1590
  const qs = {};
@@ -919,12 +1599,26 @@ class MultiUploadTool {
919
1599
  if (filters.limit)
920
1600
  qs.limit = filters.limit;
921
1601
  const res = await apiRequest('GET', '/upload', undefined, qs);
922
- responseData = (_d = res.data) !== null && _d !== void 0 ? _d : [];
1602
+ responseData = (_f = res.data) !== null && _f !== void 0 ? _f : [];
1603
+ }
1604
+ else if (operation === 'listScheduled') {
1605
+ const filters = this.getNodeParameter('filters', i, {});
1606
+ const qs = { status: 'scheduled' };
1607
+ if (filters.platform)
1608
+ qs.platform = filters.platform;
1609
+ if (filters.search)
1610
+ qs.search = filters.search;
1611
+ if (filters.page)
1612
+ qs.page = filters.page;
1613
+ if (filters.limit)
1614
+ qs.limit = filters.limit;
1615
+ const res = await apiRequest('GET', '/upload', undefined, qs);
1616
+ responseData = (_g = res.data) !== null && _g !== void 0 ? _g : [];
923
1617
  }
924
1618
  else if (operation === 'get') {
925
1619
  const uploadId = this.getNodeParameter('uploadId', i);
926
1620
  const res = await apiRequest('GET', `/upload/${uploadId}`);
927
- responseData = (_e = res.data) !== null && _e !== void 0 ? _e : {};
1621
+ responseData = (_h = res.data) !== null && _h !== void 0 ? _h : {};
928
1622
  }
929
1623
  else if (operation === 'update') {
930
1624
  const uploadId = this.getNodeParameter('uploadId', i);
@@ -937,7 +1631,25 @@ class MultiUploadTool {
937
1631
  if (updateFields.scheduledFor !== undefined)
938
1632
  body.scheduledFor = updateFields.scheduledFor;
939
1633
  const res = await apiRequest('PATCH', `/upload/${uploadId}`, body);
940
- responseData = (_f = res.data) !== null && _f !== void 0 ? _f : {};
1634
+ responseData = (_j = res.data) !== null && _j !== void 0 ? _j : {};
1635
+ }
1636
+ else if (operation === 'cancelScheduled') {
1637
+ const uploadId = this.getNodeParameter('uploadId', i);
1638
+ const res = await apiRequest('PATCH', `/upload/${uploadId}`, { status: 'cancelled' });
1639
+ responseData = (_k = res.data) !== null && _k !== void 0 ? _k : {};
1640
+ }
1641
+ else if (operation === 'editScheduled') {
1642
+ const uploadId = this.getNodeParameter('uploadId', i);
1643
+ const scheduleUpdateFields = this.getNodeParameter('scheduleUpdateFields', i, {});
1644
+ const body = {};
1645
+ if (scheduleUpdateFields.scheduledFor)
1646
+ body.scheduledFor = scheduleUpdateFields.scheduledFor;
1647
+ if (scheduleUpdateFields.title)
1648
+ body.title = scheduleUpdateFields.title;
1649
+ if (scheduleUpdateFields.description !== undefined)
1650
+ body.description = scheduleUpdateFields.description;
1651
+ const res = await apiRequest('PATCH', `/upload/${uploadId}`, body);
1652
+ responseData = (_l = res.data) !== null && _l !== void 0 ? _l : {};
941
1653
  }
942
1654
  }
943
1655
  // ══════════════════════════════════════════════════════════════════
@@ -946,9 +1658,8 @@ class MultiUploadTool {
946
1658
  else if (resource === 'pinterest') {
947
1659
  if (operation === 'getBoards') {
948
1660
  const accountId = this.getNodeParameter('accountId', i);
949
- // Endpoint: /pinterest/boards (returns raw data or data wrapper)
950
1661
  const res = await apiRequest('GET', '/pinterest/boards', undefined, { accountId });
951
- responseData = (_g = res.data) !== null && _g !== void 0 ? _g : [];
1662
+ responseData = (_m = res.data) !== null && _m !== void 0 ? _m : [];
952
1663
  }
953
1664
  }
954
1665
  // ══════════════════════════════════════════════════════════════════
@@ -962,13 +1673,15 @@ class MultiUploadTool {
962
1673
  qs.platform = filters.platform;
963
1674
  if (filters.status)
964
1675
  qs.status = filters.status;
1676
+ if (filters.tags)
1677
+ qs.tags = filters.tags;
965
1678
  const res = await apiRequest('GET', '/accounts', undefined, qs);
966
- responseData = (_h = res.data) !== null && _h !== void 0 ? _h : [];
1679
+ responseData = (_o = res.data) !== null && _o !== void 0 ? _o : [];
967
1680
  }
968
1681
  else if (operation === 'get') {
969
1682
  const accountId = this.getNodeParameter('accountId', i);
970
1683
  const res = await apiRequest('GET', `/accounts/${accountId}`);
971
- responseData = (_j = res.data) !== null && _j !== void 0 ? _j : {};
1684
+ responseData = (_p = res.data) !== null && _p !== void 0 ? _p : {};
972
1685
  }
973
1686
  else if (operation === 'delete') {
974
1687
  const accountId = this.getNodeParameter('accountId', i);
@@ -977,7 +1690,82 @@ class MultiUploadTool {
977
1690
  else if (operation === 'linkedinPages') {
978
1691
  const accountId = this.getNodeParameter('accountId', i);
979
1692
  const res = await apiRequest('GET', '/accounts/linkedin/pages', undefined, { accountId });
980
- responseData = (_k = res.pages) !== null && _k !== void 0 ? _k : [];
1693
+ responseData = (_q = res.pages) !== null && _q !== void 0 ? _q : [];
1694
+ }
1695
+ }
1696
+ // ══════════════════════════════════════════════════════════════════
1697
+ // POOL
1698
+ // ══════════════════════════════════════════════════════════════════
1699
+ else if (resource === 'pool') {
1700
+ if (operation === 'listPools') {
1701
+ const res = await apiRequest('GET', '/pools');
1702
+ responseData = (_r = res.pools) !== null && _r !== void 0 ? _r : [];
1703
+ }
1704
+ else if (operation === 'getPool') {
1705
+ const poolId = this.getNodeParameter('poolId', i);
1706
+ const res = await apiRequest('GET', `/pools/${poolId}`);
1707
+ responseData = (_s = res.pool) !== null && _s !== void 0 ? _s : {};
1708
+ }
1709
+ else if (operation === 'checkCapacity') {
1710
+ const poolId = this.getNodeParameter('poolId', i);
1711
+ const count = this.getNodeParameter('count', i, 1);
1712
+ const res = await apiRequest('GET', `/pools/${poolId}/capacity`, undefined, { count: String(count) });
1713
+ responseData = res;
1714
+ }
1715
+ else if (operation === 'listVideos') {
1716
+ const poolId = this.getNodeParameter('poolId', i);
1717
+ const filters = this.getNodeParameter('filters', i, {});
1718
+ const qs = {};
1719
+ if (filters.status)
1720
+ qs.status = filters.status;
1721
+ const res = await apiRequest('GET', `/pools/${poolId}/videos`, undefined, qs);
1722
+ responseData = (_t = res.items) !== null && _t !== void 0 ? _t : [];
1723
+ }
1724
+ else if (operation === 'removeVideo') {
1725
+ const poolId = this.getNodeParameter('poolId', i);
1726
+ const itemId = this.getNodeParameter('itemId', i);
1727
+ responseData = await apiRequest('DELETE', `/pools/${poolId}/videos/${itemId}`);
1728
+ }
1729
+ else if (operation === 'addVideo') {
1730
+ const poolId = this.getNodeParameter('poolId', i);
1731
+ const title = this.getNodeParameter('title', i);
1732
+ const media = this.getNodeParameter('media', i, 'data');
1733
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
1734
+ const formData = new FormData();
1735
+ formData.set('title', title);
1736
+ if (additionalFields.description)
1737
+ formData.set('description', String(additionalFields.description));
1738
+ if (additionalFields.metadata)
1739
+ formData.set('metadata', String(additionalFields.metadata));
1740
+ // Parse comma-separated list — each entry is either a binary property name or a URL.
1741
+ // Mirrors the Upload-Post approach: mix of binary keys and URLs is fully supported.
1742
+ const mediaItems = media.split(',').map(s => s.trim()).filter(Boolean);
1743
+ for (const entry of mediaItems) {
1744
+ const isUrl = entry.startsWith('http://') || entry.startsWith('https://');
1745
+ if (isUrl) {
1746
+ const isVideo = /\.(mp4|mov|avi|mkv|webm)$/i.test(entry.split('?')[0]);
1747
+ formData.append(isVideo ? 'video' : 'photo', entry);
1748
+ }
1749
+ else {
1750
+ // Binary property name
1751
+ if (!((_u = items[i].binary) === null || _u === void 0 ? void 0 : _u[entry]))
1752
+ continue;
1753
+ const binaryItem = items[i].binary[entry];
1754
+ const fileBuffer = await this.helpers.getBinaryDataBuffer(i, entry);
1755
+ const fileName = (_v = binaryItem.fileName) !== null && _v !== void 0 ? _v : entry;
1756
+ const fileMime = (_w = binaryItem.mimeType) !== null && _w !== void 0 ? _w : 'application/octet-stream';
1757
+ const isVideo = fileMime.startsWith('video/');
1758
+ const blob = new Blob([fileBuffer], { type: fileMime });
1759
+ formData.append(isVideo ? 'video' : 'photo', blob, fileName);
1760
+ }
1761
+ }
1762
+ responseData = (await this.helpers.httpRequest({
1763
+ method: 'POST',
1764
+ url: `${BASE_URL}/pools/${poolId}/videos`,
1765
+ headers: { 'x-api-key': apiToken },
1766
+ body: formData,
1767
+ json: true,
1768
+ }));
981
1769
  }
982
1770
  }
983
1771
  // ══════════════════════════════════════════════════════════════════
@@ -992,11 +1780,11 @@ class MultiUploadTool {
992
1780
  if (description)
993
1781
  body.description = description;
994
1782
  const res = await apiRequest('POST', '/webhooks', body);
995
- responseData = (_l = res.data) !== null && _l !== void 0 ? _l : {};
1783
+ responseData = (_x = res.data) !== null && _x !== void 0 ? _x : {};
996
1784
  }
997
1785
  else if (operation === 'list') {
998
1786
  const res = await apiRequest('GET', '/webhooks');
999
- responseData = (_m = res.data) !== null && _m !== void 0 ? _m : [];
1787
+ responseData = (_y = res.data) !== null && _y !== void 0 ? _y : [];
1000
1788
  }
1001
1789
  else if (operation === 'delete') {
1002
1790
  const webhookId = this.getNodeParameter('webhookId', i);
@@ -1013,7 +1801,6 @@ class MultiUploadTool {
1013
1801
  else if (resource === 'shortLink') {
1014
1802
  if (operation === 'create') {
1015
1803
  const url = this.getNodeParameter('url', i);
1016
- // Handle rules
1017
1804
  const rulesContainer = this.getNodeParameter('rules', i, {});
1018
1805
  const rules = rulesContainer.rule || undefined;
1019
1806
  const additionalFields = this.getNodeParameter('additionalFields', i, {});
@@ -1036,20 +1823,18 @@ class MultiUploadTool {
1036
1823
  if (additionalFields.safeUrl)
1037
1824
  body.safeUrl = additionalFields.safeUrl;
1038
1825
  const res = await apiRequest('POST', '/short-links', body);
1039
- responseData = (_o = res.data) !== null && _o !== void 0 ? _o : {};
1826
+ responseData = (_z = res.data) !== null && _z !== void 0 ? _z : {};
1040
1827
  }
1041
1828
  else if (operation === 'list') {
1042
1829
  const res = await apiRequest('GET', '/short-links');
1043
- responseData = (_p = res.data) !== null && _p !== void 0 ? _p : [];
1830
+ responseData = (_0 = res.data) !== null && _0 !== void 0 ? _0 : [];
1044
1831
  }
1045
1832
  else if (operation === 'get') {
1046
1833
  const linkId = this.getNodeParameter('linkId', i);
1047
- // Backend returns the link directly (no { data: ... } wrapper)
1048
1834
  responseData = await apiRequest('GET', `/short-links/${linkId}`);
1049
1835
  }
1050
1836
  else if (operation === 'update') {
1051
1837
  const linkId = this.getNodeParameter('linkId', i);
1052
- // Handle rules
1053
1838
  const rulesContainer = this.getNodeParameter('rules', i, {});
1054
1839
  const rules = rulesContainer.rule || undefined;
1055
1840
  const updateFields = this.getNodeParameter('updateFields', i, {});
@@ -1073,7 +1858,6 @@ class MultiUploadTool {
1073
1858
  body.botProtection = updateFields.botProtection;
1074
1859
  if (updateFields.safeUrl)
1075
1860
  body.safeUrl = updateFields.safeUrl;
1076
- // Backend returns the link directly (no { data: ... } wrapper)
1077
1861
  responseData = await apiRequest('PUT', `/short-links/${linkId}`, body);
1078
1862
  }
1079
1863
  else if (operation === 'delete') {