upload-post 2.10.0 → 2.13.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.
- package/README.md +200 -8
- package/index.d.ts +166 -7
- package/index.js +146 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ console.log(response);
|
|
|
37
37
|
- ✅ **Posting Queue** - Add posts to your configured queue
|
|
38
38
|
- ✅ **First Comments** - Auto-post first comment after publishing
|
|
39
39
|
- ✅ **Analytics** - Get engagement metrics
|
|
40
|
+
- ✅ **Audience** - Who follows a profile, per platform
|
|
41
|
+
- ✅ **Suggestions** - Hashtags and searches to post about, per platform
|
|
40
42
|
- ✅ **Full TypeScript Support**
|
|
41
43
|
|
|
42
44
|
## Using this from an AI agent? Use the MCP server instead
|
|
@@ -313,16 +315,204 @@ const found = await client.searchTiktokMusic('my-profile', {
|
|
|
313
315
|
const locations = await client.getTiktokLocations('my-profile', 'Madrid');
|
|
314
316
|
```
|
|
315
317
|
|
|
318
|
+
### Comments
|
|
319
|
+
|
|
320
|
+
The same methods cover every platform that has comments — Instagram, Facebook,
|
|
321
|
+
YouTube, LinkedIn and TikTok. There is no per-network method: the endpoint
|
|
322
|
+
answers one question and `platform` says who to ask.
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
// Read the comments on a post
|
|
326
|
+
const { comments } = await client.getPostComments({
|
|
327
|
+
user: 'my-profile',
|
|
328
|
+
platform: 'tiktok',
|
|
329
|
+
postId: '7412345678901234567', // TikTok has no post-URL lookup, pass the video id
|
|
330
|
+
limit: 20,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Read the replies hanging from one of them — same question, one more parameter
|
|
334
|
+
const { comments: replies } = await client.getPostComments({
|
|
335
|
+
user: 'my-profile',
|
|
336
|
+
platform: 'tiktok',
|
|
337
|
+
postId: '7412345678901234567',
|
|
338
|
+
commentId: comments[0].id,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Comment on the post, or reply to a comment
|
|
342
|
+
await client.createComment({
|
|
343
|
+
user: 'my-profile',
|
|
344
|
+
platform: 'tiktok',
|
|
345
|
+
postId: '7412345678901234567',
|
|
346
|
+
message: 'Thanks for watching!',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await client.createComment({
|
|
350
|
+
user: 'my-profile',
|
|
351
|
+
platform: 'tiktok',
|
|
352
|
+
commentId: comments[0].id,
|
|
353
|
+
message: 'Glad you liked it',
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Delete one
|
|
357
|
+
await client.deleteComment({
|
|
358
|
+
user: 'my-profile',
|
|
359
|
+
platform: 'tiktok',
|
|
360
|
+
commentId: comments[0].id,
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
You can also have the first comment posted for you right after publishing, with
|
|
365
|
+
`firstComment` for every platform or `tiktokFirstComment` for TikTok alone.
|
|
366
|
+
|
|
367
|
+
On TikTok all of this needs the `comments` capability, which the account grants
|
|
368
|
+
when it connects — see
|
|
369
|
+
[What a TikTok connection can do](#what-a-tiktok-connection-can-do-capabilities).
|
|
370
|
+
|
|
371
|
+
#### Moderating a comment: hide, like, pin
|
|
372
|
+
|
|
373
|
+
`commentAction()` does the three and undoes them, on any platform that supports
|
|
374
|
+
it. Each action carries its own inverse, and `postId` is only sent when the
|
|
375
|
+
platform needs it:
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
await client.commentAction({
|
|
379
|
+
user: 'my-profile',
|
|
380
|
+
platform: 'tiktok',
|
|
381
|
+
action: 'hide',
|
|
382
|
+
commentId: '7412345678909999999',
|
|
383
|
+
postId: '7412345678901234567',
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
await client.commentAction({
|
|
387
|
+
user: 'my-profile',
|
|
388
|
+
platform: 'tiktok',
|
|
389
|
+
action: 'like',
|
|
390
|
+
commentId: '7412345678909999999',
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
await client.commentAction({
|
|
394
|
+
user: 'my-profile',
|
|
395
|
+
platform: 'tiktok',
|
|
396
|
+
action: 'unpin',
|
|
397
|
+
commentId: '7412345678909999999',
|
|
398
|
+
postId: '7412345678901234567',
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
| `action` | Undo | `postId` |
|
|
403
|
+
| --- | --- | --- |
|
|
404
|
+
| `hide` | `unhide` | required |
|
|
405
|
+
| `like` | `unlike` | not sent |
|
|
406
|
+
| `pin` | `unpin` | required |
|
|
407
|
+
|
|
408
|
+
### Audience
|
|
409
|
+
|
|
410
|
+
Where the analytics methods answer *how did my posts do*, `getAudience()`
|
|
411
|
+
answers *who is following me*. One endpoint, one `platform` parameter, like
|
|
412
|
+
every other question in the API.
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
const audience = await client.getAudience({
|
|
416
|
+
user: 'my-profile',
|
|
417
|
+
platform: 'tiktok',
|
|
418
|
+
startDate: '2026-07-01',
|
|
419
|
+
endDate: '2026-07-30',
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
console.log(audience.range); // the window actually used
|
|
423
|
+
console.log(audience.audience.countries); // and .cities, .ages, .genders
|
|
424
|
+
console.log(audience.activity_by_hour); // [{ hour: '14', followers_online: 1494 }, ...]
|
|
425
|
+
console.log(audience.followers_daily); // [{ date, total, new, lost }, ...]
|
|
426
|
+
console.log(audience.profile_actions); // bio link, address, email, phone, leads
|
|
427
|
+
console.log(audience.bio_description);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The window is clamped on the server: at most 60 days, and `endDate` always
|
|
431
|
+
before today. A wider window is trimmed to what the platform accepts instead of
|
|
432
|
+
failing.
|
|
433
|
+
|
|
434
|
+
Ask for a `benchmarkCategory` and the same call also returns how the account
|
|
435
|
+
compares with the average of that category. The accepted categories come back in
|
|
436
|
+
`benchmark_categories` on every response, so a picker needs no second call:
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
const { benchmark_categories } = await client.getAudience({
|
|
440
|
+
user: 'my-profile', platform: 'tiktok',
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const { benchmark } = await client.getAudience({
|
|
444
|
+
user: 'my-profile',
|
|
445
|
+
platform: 'tiktok',
|
|
446
|
+
benchmarkCategory: 'SOFTWARE_AND_APPS',
|
|
447
|
+
});
|
|
448
|
+
console.log(benchmark.average_engagement_rate, benchmark.average_video_views);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Suggestions
|
|
452
|
+
|
|
453
|
+
`getSuggestions()` answers *what is worth posting about*: the hashtags or the
|
|
454
|
+
searches a platform suggests around a keyword. One endpoint for both, told apart
|
|
455
|
+
by `type`.
|
|
456
|
+
|
|
457
|
+
```javascript
|
|
458
|
+
const { hashtags } = await client.getSuggestions({
|
|
459
|
+
user: 'my-profile',
|
|
460
|
+
platform: 'tiktok',
|
|
461
|
+
type: 'hashtags',
|
|
462
|
+
q: 'pilates',
|
|
463
|
+
countryCode: 'ES',
|
|
464
|
+
language: 'es',
|
|
465
|
+
});
|
|
466
|
+
console.log(hashtags); // [{ name, view_count }, ...]
|
|
467
|
+
|
|
468
|
+
const { keywords } = await client.getSuggestions({
|
|
469
|
+
user: 'my-profile',
|
|
470
|
+
platform: 'tiktok',
|
|
471
|
+
type: 'keywords',
|
|
472
|
+
q: 'pilates',
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Per-post numbers stay in `getPostAnalytics()`. On TikTok that response carries
|
|
477
|
+
more than the usual counters: `retention` (the curve, second by second),
|
|
478
|
+
`impression_sources` (For You, search, profile...), `audience_types` (followers
|
|
479
|
+
vs non-followers), `new_followers`, `reach` and the watch times.
|
|
480
|
+
|
|
481
|
+
Asking a platform a question it cannot answer fails with
|
|
482
|
+
`platform_not_supported` and the list of the ones that can.
|
|
483
|
+
|
|
484
|
+
## What a TikTok connection can do (`capabilities`)
|
|
485
|
+
|
|
486
|
+
Not every TikTok connection can do the same things. `listUsers()`
|
|
487
|
+
(`GET /api/uploadposts/users`) returns a `capabilities` array on each profile's
|
|
488
|
+
TikTok account; check it before offering a feature.
|
|
489
|
+
|
|
490
|
+
| Capability | What it unlocks |
|
|
491
|
+
| --- | --- |
|
|
492
|
+
| `music` | `tiktokMusicId` and the volume/trim fields, plus `getTiktokTrendingMusic()` and `searchTiktokMusic()` |
|
|
493
|
+
| `location` | `tiktokLocationId` / `tiktokLocationName`, plus `getTiktokLocations()` |
|
|
494
|
+
| `cover_image` | `tiktokCoverImageUrl` |
|
|
495
|
+
| `cover_timestamp` | `tiktokCoverTimestamp` |
|
|
496
|
+
| `draft` | `tiktokUploadToDraft` |
|
|
497
|
+
| `video_privacy` | `tiktokPrivacyLevel` on video |
|
|
498
|
+
| `photo_privacy` | `tiktokPrivacyLevel` on photo posts |
|
|
499
|
+
| `profile_analytics` | `getAudience()` and `getSuggestions({ type: 'hashtags' })` with `platform: 'tiktok'` |
|
|
500
|
+
| `comments` | Comments on TikTok: `getPostComments()` (top-level and replies), `createComment()`, `deleteComment()`, `commentAction()` and `tiktokFirstComment` |
|
|
501
|
+
| `trend_search` | `getSuggestions({ type: 'keywords' })` with `platform: 'tiktok'` |
|
|
502
|
+
|
|
503
|
+
> **`comments` and `trend_search` need the account to be reconnected.** TikTok
|
|
504
|
+
> grants them at connect time, so an account linked before they existed keeps
|
|
505
|
+
> working for everything else but will not list them — reconnect it from Manage
|
|
506
|
+
> Users to enable them.
|
|
507
|
+
|
|
508
|
+
If a connection lacks a capability the upload field is simply ignored: the post
|
|
509
|
+
still publishes and the response carries a per-field `warnings` entry. The
|
|
510
|
+
methods above answer with an error asking for a reconnection.
|
|
511
|
+
|
|
316
512
|
## TikTok music, location, cover and drafts
|
|
317
513
|
|
|
318
|
-
>
|
|
319
|
-
>
|
|
320
|
-
> `capabilities` array of the TikTok account returned by `listUsers()`
|
|
321
|
-
> (other values you may see there: `cover_timestamp`, `photo_privacy`,
|
|
322
|
-
> `video_privacy`, `inbox_fallback`, `profile_analytics`)
|
|
323
|
-
> (`GET /api/uploadposts/users`). If your connection does not have it, the field
|
|
324
|
-
> is ignored, the post still publishes, and the response includes a per-field
|
|
325
|
-
> `warnings` entry — reconnect the TikTok account to enable it.
|
|
514
|
+
> Needs the `music`, `location`, `cover_image` or `draft` capability — see
|
|
515
|
+
> [What a TikTok connection can do](#what-a-tiktok-connection-can-do-capabilities).
|
|
326
516
|
|
|
327
517
|
```javascript
|
|
328
518
|
// 1. Pick a track and a place
|
|
@@ -484,6 +674,8 @@ These options work across all upload methods:
|
|
|
484
674
|
| `user` | Profile name (required) |
|
|
485
675
|
| `platforms` | Target platforms array (required) |
|
|
486
676
|
| `firstComment` | First comment to post |
|
|
677
|
+
| `tiktokFirstComment` | First comment for TikTok only (needs the `comments` capability) |
|
|
678
|
+
| `replyToId` | Publish as a reply to an existing post (X: tweet ID; Bluesky: post URL or AT-URI). Alias: `xReplyToId` |
|
|
487
679
|
| `altText` | Alt text for accessibility |
|
|
488
680
|
| `scheduledDate` | ISO date for scheduling |
|
|
489
681
|
| `timezone` | Timezone for scheduled date |
|
package/index.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ declare module 'upload-post' {
|
|
|
56
56
|
user: string;
|
|
57
57
|
/** First comment to post after publishing */
|
|
58
58
|
firstComment?: string;
|
|
59
|
+
/** Reply to an existing post (X: tweet ID; Bluesky: post URL or AT-URI). */
|
|
60
|
+
replyToId?: string;
|
|
59
61
|
/** Alt text for accessibility */
|
|
60
62
|
altText?: string;
|
|
61
63
|
/** ISO date for scheduling (e.g., "2024-12-25T10:00:00Z") */
|
|
@@ -109,6 +111,11 @@ declare module 'upload-post' {
|
|
|
109
111
|
youtubeFirstComment?: string;
|
|
110
112
|
redditFirstComment?: string;
|
|
111
113
|
blueskyFirstComment?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Needs the `comments` capability on the connection: without it the post
|
|
116
|
+
* still publishes and the response carries a warning instead of the comment.
|
|
117
|
+
*/
|
|
118
|
+
tiktokFirstComment?: string;
|
|
112
119
|
}
|
|
113
120
|
|
|
114
121
|
// ==================== TikTok Options ====================
|
|
@@ -367,6 +374,8 @@ declare module 'upload-post' {
|
|
|
367
374
|
xDirectMessageDeepLink?: string;
|
|
368
375
|
/** Post long text as single post */
|
|
369
376
|
xLongTextAsPost?: boolean;
|
|
377
|
+
/** ID of the tweet to reply to (alias of the generic replyToId). */
|
|
378
|
+
xReplyToId?: string;
|
|
370
379
|
}
|
|
371
380
|
|
|
372
381
|
export interface XMediaOptions extends XBaseOptions {
|
|
@@ -711,6 +720,82 @@ declare module 'upload-post' {
|
|
|
711
720
|
[key: string]: any;
|
|
712
721
|
}
|
|
713
722
|
|
|
723
|
+
// ==================== Audience, Suggestions and Comment Actions ====================
|
|
724
|
+
|
|
725
|
+
/** One row of an audience breakdown: a bucket and its share. */
|
|
726
|
+
export interface AudienceSlice {
|
|
727
|
+
name?: string;
|
|
728
|
+
value?: number;
|
|
729
|
+
percentage?: number;
|
|
730
|
+
[key: string]: any;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** Category averages returned when `benchmarkCategory` is asked for. */
|
|
734
|
+
export interface AudienceBenchmark {
|
|
735
|
+
category?: string;
|
|
736
|
+
average_comments?: number;
|
|
737
|
+
average_engagement_rate?: number;
|
|
738
|
+
average_follower_count?: number;
|
|
739
|
+
average_follower_growth?: number;
|
|
740
|
+
average_likes?: number;
|
|
741
|
+
average_shares?: number;
|
|
742
|
+
average_video_count?: number;
|
|
743
|
+
average_video_views?: number;
|
|
744
|
+
[key: string]: any;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export interface AudienceResponse {
|
|
748
|
+
success: boolean;
|
|
749
|
+
platform?: string;
|
|
750
|
+
/** The window actually used after the 60-day / not-today clamps. */
|
|
751
|
+
range?: { start_date?: string; end_date?: string };
|
|
752
|
+
audience?: {
|
|
753
|
+
countries?: AudienceSlice[];
|
|
754
|
+
cities?: AudienceSlice[];
|
|
755
|
+
ages?: AudienceSlice[];
|
|
756
|
+
genders?: AudienceSlice[];
|
|
757
|
+
};
|
|
758
|
+
/** Followers online per hour of the day, e.g. `{ hour: '14', followers_online: 1494 }`. */
|
|
759
|
+
activity_by_hour?: Array<{ hour?: string; followers_online?: number; [key: string]: any }>;
|
|
760
|
+
/** Follower count per day inside the window. */
|
|
761
|
+
followers_daily?: Array<{ date?: string; total?: number; new?: number; lost?: number; [key: string]: any }>;
|
|
762
|
+
/** Taps on the profile: bio link, address, app download, email, phone, leads. */
|
|
763
|
+
profile_actions?: Record<string, number>;
|
|
764
|
+
bio_description?: string | null;
|
|
765
|
+
/** Every category `benchmarkCategory` accepts, so a picker needs no extra call. */
|
|
766
|
+
benchmark_categories?: string[];
|
|
767
|
+
/** Only when a `benchmarkCategory` was asked for. */
|
|
768
|
+
benchmark?: AudienceBenchmark;
|
|
769
|
+
[key: string]: any;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** Which kind of suggestion `getSuggestions` asks for. */
|
|
773
|
+
export type SuggestionType = 'hashtags' | 'keywords';
|
|
774
|
+
|
|
775
|
+
export interface SuggestionsResponse {
|
|
776
|
+
success: boolean;
|
|
777
|
+
platform?: string;
|
|
778
|
+
type?: SuggestionType;
|
|
779
|
+
query?: string | null;
|
|
780
|
+
/** With `type: 'hashtags'`. */
|
|
781
|
+
hashtags?: Array<{ name?: string; view_count?: number; [key: string]: any }>;
|
|
782
|
+
/** With `type: 'keywords'`. */
|
|
783
|
+
keywords?: Array<Record<string, any>>;
|
|
784
|
+
[key: string]: any;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/** What `commentAction` does to a comment. Each action carries its own inverse. */
|
|
788
|
+
export type CommentActionValue = 'hide' | 'unhide' | 'like' | 'unlike' | 'pin' | 'unpin';
|
|
789
|
+
|
|
790
|
+
export interface CommentActionResponse {
|
|
791
|
+
success: boolean;
|
|
792
|
+
platform?: string;
|
|
793
|
+
action?: CommentActionValue;
|
|
794
|
+
comment_id?: string;
|
|
795
|
+
result?: Record<string, any>;
|
|
796
|
+
[key: string]: any;
|
|
797
|
+
}
|
|
798
|
+
|
|
714
799
|
// ==================== Client Class ====================
|
|
715
800
|
|
|
716
801
|
/**
|
|
@@ -806,7 +891,15 @@ declare module 'upload-post' {
|
|
|
806
891
|
}>;
|
|
807
892
|
|
|
808
893
|
/**
|
|
809
|
-
* Get analytics for a specific post across all platforms
|
|
894
|
+
* Get analytics for a specific post across all platforms.
|
|
895
|
+
*
|
|
896
|
+
* `post_metrics` carries whatever the platform reports, so it is not the
|
|
897
|
+
* same shape everywhere: TikTok adds `retention` (the curve, second by
|
|
898
|
+
* second), `impression_sources` (For You, search, profile...),
|
|
899
|
+
* `audience_types` (followers vs non-followers), `new_followers`, `reach`
|
|
900
|
+
* and the watch times (`average_time_watched`, `total_time_watched`,
|
|
901
|
+
* `full_video_watched_rate`) on top of the usual counters.
|
|
902
|
+
*
|
|
810
903
|
* @param requestId - The request_id from the upload
|
|
811
904
|
*/
|
|
812
905
|
getPostAnalytics(requestId: string): Promise<{
|
|
@@ -816,7 +909,8 @@ declare module 'upload-post' {
|
|
|
816
909
|
success: boolean;
|
|
817
910
|
platform_post_id?: string;
|
|
818
911
|
post_url?: string;
|
|
819
|
-
|
|
912
|
+
/** Counters plus, on TikTok, retention / impression_sources / audience_types and watch times. */
|
|
913
|
+
post_metrics?: Record<string, any>;
|
|
820
914
|
post_metrics_source?: string;
|
|
821
915
|
post_metrics_error?: string;
|
|
822
916
|
profile_snapshot_at_post_date?: Record<string, number>;
|
|
@@ -868,6 +962,47 @@ declare module 'upload-post' {
|
|
|
868
962
|
has_more: boolean;
|
|
869
963
|
}>;
|
|
870
964
|
|
|
965
|
+
/**
|
|
966
|
+
* Get who the audience is: where they are, how old they are, when they are
|
|
967
|
+
* online and what they tap on the profile.
|
|
968
|
+
*
|
|
969
|
+
* One endpoint for every platform, chosen with `platform`; a platform that
|
|
970
|
+
* cannot answer it returns `platform_not_supported` with the list of the
|
|
971
|
+
* ones that can. The window is clamped server-side to at most 60 days
|
|
972
|
+
* ending before today.
|
|
973
|
+
*
|
|
974
|
+
* @param options - Query options
|
|
975
|
+
*/
|
|
976
|
+
getAudience(options: {
|
|
977
|
+
user: string;
|
|
978
|
+
platform: 'tiktok';
|
|
979
|
+
/** Window start, ISO `YYYY-MM-DD`. */
|
|
980
|
+
startDate?: string;
|
|
981
|
+
/** Window end, ISO `YYYY-MM-DD`. Always clamped to before today. */
|
|
982
|
+
endDate?: string;
|
|
983
|
+
/** Compare the account against this category's averages. */
|
|
984
|
+
benchmarkCategory?: string;
|
|
985
|
+
}): Promise<AudienceResponse>;
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Get what to write about: the hashtags or the searches a platform
|
|
989
|
+
* suggests around a keyword.
|
|
990
|
+
*
|
|
991
|
+
* One endpoint for both questions, told apart by `type`, and one endpoint
|
|
992
|
+
* for every platform, chosen with `platform`.
|
|
993
|
+
*
|
|
994
|
+
* @param options - Query options
|
|
995
|
+
*/
|
|
996
|
+
getSuggestions(options: {
|
|
997
|
+
user: string;
|
|
998
|
+
platform: 'tiktok';
|
|
999
|
+
type: SuggestionType;
|
|
1000
|
+
/** Keyword to get suggestions around. */
|
|
1001
|
+
q?: string;
|
|
1002
|
+
countryCode?: string;
|
|
1003
|
+
language?: string;
|
|
1004
|
+
}): Promise<SuggestionsResponse>;
|
|
1005
|
+
|
|
871
1006
|
/**
|
|
872
1007
|
* Get available metrics configuration for all supported platforms
|
|
873
1008
|
*/
|
|
@@ -1019,17 +1154,23 @@ declare module 'upload-post' {
|
|
|
1019
1154
|
*/
|
|
1020
1155
|
testNotifications(): Promise<{ success: boolean; [key: string]: any }>;
|
|
1021
1156
|
|
|
1022
|
-
//
|
|
1157
|
+
// Comments
|
|
1023
1158
|
|
|
1024
1159
|
/**
|
|
1025
|
-
* Get comments on a post
|
|
1160
|
+
* Get comments on a post, or the replies hanging from one of them.
|
|
1161
|
+
*
|
|
1162
|
+
* Pass `commentId` to read that comment's replies instead of the post's
|
|
1163
|
+
* top-level comments.
|
|
1164
|
+
*
|
|
1026
1165
|
* @param options - Query options
|
|
1027
1166
|
*/
|
|
1028
1167
|
getPostComments(options: {
|
|
1029
1168
|
user: string;
|
|
1030
|
-
platform?: 'instagram' | 'facebook' | 'youtube' | 'linkedin';
|
|
1169
|
+
platform?: 'instagram' | 'facebook' | 'youtube' | 'linkedin' | 'tiktok';
|
|
1031
1170
|
postId?: string;
|
|
1032
1171
|
postUrl?: string;
|
|
1172
|
+
/** Read the replies to this comment instead of the post's top-level comments. */
|
|
1173
|
+
commentId?: string;
|
|
1033
1174
|
limit?: number;
|
|
1034
1175
|
after?: string;
|
|
1035
1176
|
}): Promise<{
|
|
@@ -1086,7 +1227,7 @@ declare module 'upload-post' {
|
|
|
1086
1227
|
*/
|
|
1087
1228
|
createComment(options: {
|
|
1088
1229
|
user: string;
|
|
1089
|
-
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin';
|
|
1230
|
+
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin' | 'tiktok';
|
|
1090
1231
|
message: string;
|
|
1091
1232
|
postId?: string;
|
|
1092
1233
|
postUrl?: string;
|
|
@@ -1104,7 +1245,7 @@ declare module 'upload-post' {
|
|
|
1104
1245
|
*/
|
|
1105
1246
|
deleteComment(options: {
|
|
1106
1247
|
user: string;
|
|
1107
|
-
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin';
|
|
1248
|
+
platform: 'instagram' | 'facebook' | 'youtube' | 'linkedin' | 'tiktok';
|
|
1108
1249
|
commentId: string;
|
|
1109
1250
|
/** Post identifier (the post URN for LinkedIn) */
|
|
1110
1251
|
postId?: string;
|
|
@@ -1114,6 +1255,24 @@ declare module 'upload-post' {
|
|
|
1114
1255
|
error?: string;
|
|
1115
1256
|
}>;
|
|
1116
1257
|
|
|
1258
|
+
/**
|
|
1259
|
+
* Moderate a comment: hide, like or pin it — and undo any of the three.
|
|
1260
|
+
*
|
|
1261
|
+
* One method for every platform, chosen with `platform`. `postId` is
|
|
1262
|
+
* required for hide/unhide and pin/unpin; like/unlike take the comment
|
|
1263
|
+
* alone and never send it.
|
|
1264
|
+
*
|
|
1265
|
+
* @param options - Action options
|
|
1266
|
+
*/
|
|
1267
|
+
commentAction(options: {
|
|
1268
|
+
user: string;
|
|
1269
|
+
platform: 'tiktok';
|
|
1270
|
+
commentId: string;
|
|
1271
|
+
action: CommentActionValue;
|
|
1272
|
+
/** Required for hide/unhide and pin/unpin; never sent for like/unlike. */
|
|
1273
|
+
postId?: string;
|
|
1274
|
+
}): Promise<CommentActionResponse>;
|
|
1275
|
+
|
|
1117
1276
|
// Post Management
|
|
1118
1277
|
/**
|
|
1119
1278
|
* Retry a failed post. Provide requestId or jobId.
|
package/index.js
CHANGED
|
@@ -82,6 +82,10 @@ export class UploadPost {
|
|
|
82
82
|
|
|
83
83
|
// Optional common parameters
|
|
84
84
|
if (options.firstComment) form.append('first_comment', options.firstComment);
|
|
85
|
+
// Reply to an existing post (X: tweet ID; Bluesky: post URL or AT-URI). The
|
|
86
|
+
// API has always accepted `reply_to_id`; the SDK simply never mapped it.
|
|
87
|
+
const replyToId = options.replyToId || options.xReplyToId || options.blueskyReplyToId;
|
|
88
|
+
if (replyToId) form.append('reply_to_id', replyToId);
|
|
85
89
|
if (options.altText) form.append('alt_text', options.altText);
|
|
86
90
|
if (options.scheduledDate) form.append('scheduled_date', options.scheduledDate);
|
|
87
91
|
if (options.timezone) form.append('timezone', options.timezone);
|
|
@@ -128,6 +132,10 @@ export class UploadPost {
|
|
|
128
132
|
if (options.redditFirstComment) form.append('reddit_first_comment', options.redditFirstComment);
|
|
129
133
|
if (options.blueskyFirstComment) form.append('bluesky_first_comment', options.blueskyFirstComment);
|
|
130
134
|
if (options.linkedinFirstComment) form.append('linkedin_first_comment', options.linkedinFirstComment);
|
|
135
|
+
// TikTok takes a first comment like everyone else. It needs the `comments`
|
|
136
|
+
// capability on the connection: without it the post still publishes and the
|
|
137
|
+
// response carries a warning instead of the comment.
|
|
138
|
+
if (options.tiktokFirstComment) form.append('tiktok_first_comment', options.tiktokFirstComment);
|
|
131
139
|
|
|
132
140
|
if (options.firstCommentMedia) {
|
|
133
141
|
const mediaItems = Array.isArray(options.firstCommentMedia) ? options.firstCommentMedia : [options.firstCommentMedia];
|
|
@@ -855,6 +863,13 @@ export class UploadPost {
|
|
|
855
863
|
/**
|
|
856
864
|
* Get analytics for a specific post across all platforms it was published to
|
|
857
865
|
*
|
|
866
|
+
* `post_metrics` carries whatever the platform reports, so it is not the same
|
|
867
|
+
* shape everywhere: on TikTok it also brings `retention` (the curve, second
|
|
868
|
+
* by second), `impression_sources` (For You, search, profile...),
|
|
869
|
+
* `audience_types` (followers vs non-followers), `new_followers`, `reach` and
|
|
870
|
+
* the watch times (`average_time_watched`, `total_time_watched`,
|
|
871
|
+
* `full_video_watched_rate`).
|
|
872
|
+
*
|
|
858
873
|
* @param {string} requestId - The request_id from the upload
|
|
859
874
|
* @returns {Promise<Object>} Post analytics with per-platform metrics
|
|
860
875
|
*/
|
|
@@ -905,6 +920,73 @@ export class UploadPost {
|
|
|
905
920
|
return this._request('/uploadposts/post-analytics/cached', 'GET', params);
|
|
906
921
|
}
|
|
907
922
|
|
|
923
|
+
/**
|
|
924
|
+
* Get who the audience is: where they are, how old they are, when they are
|
|
925
|
+
* online and what they tap on the profile.
|
|
926
|
+
*
|
|
927
|
+
* Where the post-analytics methods answer *how did my posts do*, this one
|
|
928
|
+
* answers *who is following me*. It is one endpoint for every platform,
|
|
929
|
+
* chosen with `platform` — a platform that cannot answer it comes back with
|
|
930
|
+
* `platform_not_supported` and the list of the ones that can.
|
|
931
|
+
*
|
|
932
|
+
* The window is clamped server-side: at most 60 days, and `endDate` always
|
|
933
|
+
* before today. A wider window is trimmed rather than rejected.
|
|
934
|
+
*
|
|
935
|
+
* Pass `benchmarkCategory` to also get the averages of that category next to
|
|
936
|
+
* the account's own numbers; the accepted values come back in
|
|
937
|
+
* `benchmark_categories` on every response, so a picker needs no extra call.
|
|
938
|
+
*
|
|
939
|
+
* @param {Object} options - Query options
|
|
940
|
+
* @param {string} options.user - Profile username
|
|
941
|
+
* @param {string} options.platform - Platform to ask (tiktok)
|
|
942
|
+
* @param {string} [options.startDate] - Window start, ISO `YYYY-MM-DD`
|
|
943
|
+
* @param {string} [options.endDate] - Window end, ISO `YYYY-MM-DD`
|
|
944
|
+
* @param {string} [options.benchmarkCategory] - Category to compare the account
|
|
945
|
+
* against, e.g. `SOFTWARE_AND_APPS`
|
|
946
|
+
* @returns {Promise<Object>} `range`, `audience` (countries, cities, ages, genders),
|
|
947
|
+
* `activity_by_hour`, `followers_daily`, `profile_actions`, `bio_description`,
|
|
948
|
+
* `benchmark_categories` and, with a category, `benchmark`
|
|
949
|
+
*/
|
|
950
|
+
async getAudience(options = {}) {
|
|
951
|
+
const params = { user: options.user, platform: options.platform };
|
|
952
|
+
if (options.startDate) params.start_date = options.startDate;
|
|
953
|
+
if (options.endDate) params.end_date = options.endDate;
|
|
954
|
+
if (options.benchmarkCategory) params.benchmark_category = options.benchmarkCategory;
|
|
955
|
+
return this._request('/uploadposts/audience', 'GET', params);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Get what to write about: the hashtags or the searches a platform suggests
|
|
960
|
+
* around a keyword.
|
|
961
|
+
*
|
|
962
|
+
* One endpoint for both questions, told apart by `type`:
|
|
963
|
+
*
|
|
964
|
+
* type answers
|
|
965
|
+
* hashtags `{ hashtags: [{ name, view_count }] }`
|
|
966
|
+
* keywords `{ keywords: [...] }`
|
|
967
|
+
*
|
|
968
|
+
* Chosen with `platform`, like every other question in the API — a platform
|
|
969
|
+
* that cannot answer it comes back with `platform_not_supported` and the list
|
|
970
|
+
* of the ones that can.
|
|
971
|
+
*
|
|
972
|
+
* @param {Object} options - Query options
|
|
973
|
+
* @param {string} options.user - Profile username
|
|
974
|
+
* @param {string} options.platform - Platform to ask (tiktok)
|
|
975
|
+
* @param {('hashtags'|'keywords')} options.type - Which suggestions you want
|
|
976
|
+
* @param {string} [options.q] - Keyword to get suggestions around
|
|
977
|
+
* @param {string} [options.countryCode] - ISO country code to bias the suggestions
|
|
978
|
+
* @param {string} [options.language] - Language code to bias the suggestions
|
|
979
|
+
* @returns {Promise<Object>} `{ success, platform, type, hashtags }` or
|
|
980
|
+
* `{ success, platform, type, keywords }`
|
|
981
|
+
*/
|
|
982
|
+
async getSuggestions(options = {}) {
|
|
983
|
+
const params = { user: options.user, platform: options.platform, type: options.type };
|
|
984
|
+
if (options.q) params.q = options.q;
|
|
985
|
+
if (options.countryCode) params.country_code = options.countryCode;
|
|
986
|
+
if (options.language) params.language = options.language;
|
|
987
|
+
return this._request('/uploadposts/suggestions', 'GET', params);
|
|
988
|
+
}
|
|
989
|
+
|
|
908
990
|
/**
|
|
909
991
|
* Get available metrics configuration for all supported platforms
|
|
910
992
|
*
|
|
@@ -1114,16 +1196,28 @@ export class UploadPost {
|
|
|
1114
1196
|
return this._request('/uploadposts/users/notifications/test', 'POST', {});
|
|
1115
1197
|
}
|
|
1116
1198
|
|
|
1117
|
-
// ====================
|
|
1199
|
+
// ==================== Comments ====================
|
|
1118
1200
|
|
|
1119
1201
|
/**
|
|
1120
|
-
* Get comments on a post
|
|
1202
|
+
* Get comments on a post, or the replies hanging from one of them
|
|
1203
|
+
*
|
|
1204
|
+
* Pass `commentId` to read the replies to that comment instead of the
|
|
1205
|
+
* post's top-level comments. It is the same question — *what was said
|
|
1206
|
+
* here* — so it is the same endpoint with one more parameter, not a
|
|
1207
|
+
* separate method per platform.
|
|
1208
|
+
*
|
|
1209
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1210
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1211
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1121
1212
|
*
|
|
1122
1213
|
* @param {Object} options - Query options
|
|
1123
1214
|
* @param {string} options.user - Profile username
|
|
1124
|
-
* @param {string} [options.platform='instagram'] - Platform (instagram, facebook, youtube, linkedin)
|
|
1125
|
-
* @param {string} [options.postId] - Native post/media ID (provide postId or postUrl)
|
|
1215
|
+
* @param {string} [options.platform='instagram'] - Platform (instagram, facebook, youtube, linkedin, tiktok)
|
|
1216
|
+
* @param {string} [options.postId] - Native post/media ID (provide postId or postUrl).
|
|
1217
|
+
* Required on TikTok, which has no post URL lookup.
|
|
1126
1218
|
* @param {string} [options.postUrl] - Full post URL (provide postId or postUrl)
|
|
1219
|
+
* @param {string} [options.commentId] - Read the replies to this comment instead
|
|
1220
|
+
* of the post's top-level comments
|
|
1127
1221
|
* @param {number} [options.limit] - Max number of comments to return
|
|
1128
1222
|
* @param {string} [options.after] - Pagination cursor for the next page
|
|
1129
1223
|
* @returns {Promise<Object>} Comments data
|
|
@@ -1136,6 +1230,7 @@ export class UploadPost {
|
|
|
1136
1230
|
const params = { platform: options.platform || 'instagram', user: options.user };
|
|
1137
1231
|
if (options.postId) params.post_id = options.postId;
|
|
1138
1232
|
if (options.postUrl) params.post_url = options.postUrl;
|
|
1233
|
+
if (options.commentId) params.comment_id = options.commentId;
|
|
1139
1234
|
if (options.limit !== undefined) params.limit = options.limit;
|
|
1140
1235
|
if (options.after) params.after = options.after;
|
|
1141
1236
|
return this._request('/uploadposts/comments', 'GET', params);
|
|
@@ -1187,9 +1282,13 @@ export class UploadPost {
|
|
|
1187
1282
|
* One of postId, postUrl, or commentId is required. Pass commentId to reply
|
|
1188
1283
|
* to an existing comment; pass postId/postUrl to comment on the post itself.
|
|
1189
1284
|
*
|
|
1285
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1286
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1287
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1288
|
+
*
|
|
1190
1289
|
* @param {Object} options - Comment options
|
|
1191
1290
|
* @param {string} options.user - Profile username
|
|
1192
|
-
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin)
|
|
1291
|
+
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin, tiktok)
|
|
1193
1292
|
* @param {string} options.message - Comment text
|
|
1194
1293
|
* @param {string} [options.postId] - Native post/media ID to comment on
|
|
1195
1294
|
* @param {string} [options.postUrl] - Full post URL to comment on
|
|
@@ -1211,9 +1310,13 @@ export class UploadPost {
|
|
|
1211
1310
|
/**
|
|
1212
1311
|
* Delete a comment on a post
|
|
1213
1312
|
*
|
|
1313
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1314
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1315
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1316
|
+
*
|
|
1214
1317
|
* @param {Object} options - Delete options
|
|
1215
1318
|
* @param {string} options.user - Profile username
|
|
1216
|
-
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin)
|
|
1319
|
+
* @param {string} options.platform - Platform (instagram, facebook, youtube, linkedin, tiktok)
|
|
1217
1320
|
* @param {string} options.commentId - Comment ID to delete
|
|
1218
1321
|
* @param {string} [options.postId] - Post identifier (the post URN for LinkedIn)
|
|
1219
1322
|
* @returns {Promise<Object>} Deletion result
|
|
@@ -1228,6 +1331,43 @@ export class UploadPost {
|
|
|
1228
1331
|
return this._request('/uploadposts/comments/delete', 'DELETE', body);
|
|
1229
1332
|
}
|
|
1230
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Moderate a comment: hide, like or pin it — and undo any of the three
|
|
1336
|
+
*
|
|
1337
|
+
* One method for every platform, because it is one question: *do this to
|
|
1338
|
+
* that comment*. Each action carries its own inverse:
|
|
1339
|
+
*
|
|
1340
|
+
* action post_id
|
|
1341
|
+
* hide / unhide required
|
|
1342
|
+
* like / unlike not sent (the platform likes the comment on its own)
|
|
1343
|
+
* pin / unpin required
|
|
1344
|
+
*
|
|
1345
|
+
* On TikTok this needs the `comments` capability on the connection (see
|
|
1346
|
+
* `capabilities` on the TikTok account returned by listUsers()). TikTok grants
|
|
1347
|
+
* it at connect time, so an account connected earlier has to be reconnected.
|
|
1348
|
+
*
|
|
1349
|
+
* @param {Object} options - Action options
|
|
1350
|
+
* @param {string} options.user - Profile username
|
|
1351
|
+
* @param {string} options.platform - Platform (tiktok)
|
|
1352
|
+
* @param {string} options.commentId - Comment to act on
|
|
1353
|
+
* @param {('hide'|'unhide'|'like'|'unlike'|'pin'|'unpin')} options.action - What to do
|
|
1354
|
+
* @param {string} [options.postId] - Native post ID. Required for hide/unhide and
|
|
1355
|
+
* pin/unpin; never sent for like/unlike.
|
|
1356
|
+
* @returns {Promise<Object>} `{ success, platform, action, comment_id, result }`
|
|
1357
|
+
*/
|
|
1358
|
+
async commentAction(options) {
|
|
1359
|
+
const body = {
|
|
1360
|
+
platform: options.platform,
|
|
1361
|
+
user: options.user,
|
|
1362
|
+
comment_id: options.commentId,
|
|
1363
|
+
action: options.action
|
|
1364
|
+
};
|
|
1365
|
+
// Liking takes the comment alone; hiding and pinning need the post too.
|
|
1366
|
+
const likes = options.action === 'like' || options.action === 'unlike';
|
|
1367
|
+
if (!likes && options.postId) body.post_id = options.postId;
|
|
1368
|
+
return this._request('/uploadposts/comments/action', 'POST', body);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1231
1371
|
// ==================== Post Management ====================
|
|
1232
1372
|
|
|
1233
1373
|
/**
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upload-post",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Official client library for Upload-Post API - Cross-platform social media upload for TikTok, Instagram, YouTube, LinkedIn, Facebook, Pinterest, Threads, Reddit, Bluesky, and X (Twitter)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "
|
|
9
|
+
"test": "node --test test/"
|
|
10
10
|
},
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"keywords": [
|