n8n-nodes-captapi 0.1.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 ADDED
@@ -0,0 +1,57 @@
1
+ # n8n-nodes-captapi
2
+
3
+ This is an [n8n](https://n8n.io) community node. It lets you use **[Captapi](https://captapi.com)** in your n8n workflows.
4
+
5
+ Captapi turns YouTube, TikTok, Instagram and Facebook URLs into clean, structured JSON — transcripts, AI summaries, comments, video/post details, profile & channel stats, search, hashtag/music lookups and downloads. One API key, **62 operations**.
6
+
7
+ [Installation](#installation) · [Credentials](#credentials) · [Operations](#operations) · [Usage](#usage) · [Resources](#resources)
8
+
9
+ ## Installation
10
+
11
+ Follow the [community nodes installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n docs.
12
+
13
+ In your n8n instance:
14
+
15
+ 1. Go to **Settings → Community Nodes**.
16
+ 2. Select **Install**.
17
+ 3. Enter `n8n-nodes-captapi` as the npm package name.
18
+ 4. Agree to the risks and select **Install**.
19
+
20
+ The **Captapi** node and **Captapi API** credential will then be available.
21
+
22
+ ## Credentials
23
+
24
+ You need a Captapi API key:
25
+
26
+ 1. Create a free account at [captapi.com](https://captapi.com).
27
+ 2. Open **Dashboard → API Keys** and copy a key.
28
+ 3. In n8n, create a new **Captapi API** credential and paste the key.
29
+
30
+ The credential test calls `/v1/account/limits` to confirm the key works. `Base URL` defaults to `https://api.captapi.com` and rarely needs changing.
31
+
32
+ ## Operations
33
+
34
+ Pick a **Platform** (YouTube, TikTok, Instagram, Facebook), then an **Operation**. Highlights per platform:
35
+
36
+ - **YouTube** — Transcript, Summarizer, Video/Channel Details, Comments, Search, Channel Videos/Shorts/Streams/Playlists, Community Posts, Comment Replies, Downloads.
37
+ - **TikTok** — Transcript, Summarizer, Video/Channel Details, Comments, Search, Channel Posts, Followers/Followings, Music Posts, Song Details, Trending Feed, Popular Hashtags, Downloads.
38
+ - **Instagram** — Transcript, Summarizer, Post/Reel Details, Comments, Channel Posts/Reels, Reels/Hashtag/Profile Search, Story Highlights, Tagged & Music Posts, Embed, Downloads.
39
+ - **Facebook** — Details, Transcript, Summarizer, Comments, Page Details, Profile Posts/Reels, Group Posts, Comment Replies.
40
+
41
+ Each operation maps 1:1 to a Captapi REST endpoint and exposes exactly the inputs that endpoint accepts (e.g. `URL`, `Query`, `Limit`, `Language`, `Comment ID`).
42
+
43
+ ## Usage
44
+
45
+ A minimal flow: **Manual Trigger → Captapi**.
46
+
47
+ 1. Add the **Captapi** node.
48
+ 2. Platform: `YouTube`, Operation: `YouTube Transcript`.
49
+ 3. URL: `https://youtube.com/watch?v=dQw4w9WgXcQ`.
50
+ 4. Execute — the node returns the transcript JSON.
51
+
52
+ Pricing is credit-based and per result; cached responses are free and failed requests are never charged. See [captapi.com/pricing](https://captapi.com/pricing).
53
+
54
+ ## Resources
55
+
56
+ - [Captapi documentation](https://captapi.com/docs)
57
+ - [n8n community nodes docs](https://docs.n8n.io/integrations/community-nodes/)
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ // Auto-maintained catalog of every Captapi endpoint exposed as an MCP tool.
3
+ // Each endpoint declares its EXACT input parameters (matching the REST API),
4
+ // so agents know precisely what to pass. Mirrors the backend routers.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ENDPOINTS = void 0;
7
+ exports.describe = describe;
8
+ // --- Param builders (keep declarations terse + consistent) -----------------
9
+ const url = (description) => ({
10
+ name: "url",
11
+ type: "string",
12
+ required: true,
13
+ description,
14
+ });
15
+ const q = (description = "Search query or keywords (min 2 chars).") => ({
16
+ name: "q",
17
+ type: "string",
18
+ required: true,
19
+ description,
20
+ });
21
+ const limit = (def, max) => ({
22
+ name: "limit",
23
+ type: "number",
24
+ required: false,
25
+ description: `Max items to return. Default ${def}, max ${max}. Billed per result.`,
26
+ });
27
+ const language = () => ({
28
+ name: "language",
29
+ type: "string",
30
+ required: false,
31
+ description: 'Preferred caption language as an ISO code, e.g. "en". Defaults to auto-detect.',
32
+ });
33
+ const commentId = () => ({
34
+ name: "comment_id",
35
+ type: "string",
36
+ required: true,
37
+ description: "ID of the parent comment to fetch replies for (from the comments endpoint).",
38
+ });
39
+ const YT_VIDEO = "Public YouTube video URL, e.g. https://youtube.com/watch?v=ID.";
40
+ const YT_SHORTS = "Public YouTube Shorts URL, e.g. https://youtube.com/shorts/ID.";
41
+ const YT_CHANNEL = "YouTube channel URL, e.g. https://youtube.com/@handle or /channel/UC...";
42
+ const TT_VIDEO = "Public TikTok video URL, e.g. https://tiktok.com/@user/video/ID.";
43
+ const TT_PROFILE = "TikTok profile URL, e.g. https://tiktok.com/@username.";
44
+ const TT_MUSIC = "TikTok music/sound URL, e.g. https://tiktok.com/music/name-ID.";
45
+ const IG_POST = "Instagram post or reel URL, e.g. https://instagram.com/reel/ID/.";
46
+ const IG_REEL = "Instagram Reel URL, e.g. https://instagram.com/reel/ID/.";
47
+ const IG_PROFILE = "Instagram profile URL, e.g. https://instagram.com/username/.";
48
+ const FB_VIDEO = "Public Facebook video or post URL.";
49
+ const YOUTUBE = [
50
+ { tool: "youtube_transcript", name: "YouTube Transcript", path: "/v1/youtube/transcript", credits: 2, summary: "Extract the full timestamped transcript of a YouTube video.", params: [url(YT_VIDEO), language()] },
51
+ { tool: "youtube_summarize", name: "YouTube Summarizer", path: "/v1/youtube/summarize", credits: 4, summary: "AI summary (key points, topics, sentiment) of a YouTube video.", params: [url(YT_VIDEO), language()] },
52
+ { tool: "youtube_video_details", name: "YouTube Video Details", path: "/v1/youtube/video-details", credits: 1, summary: "Metadata + engagement stats for a YouTube video.", params: [url(YT_VIDEO)] },
53
+ { tool: "youtube_comments", name: "YouTube Comments", path: "/v1/youtube/comments", credits: 20, summary: "Comments on a YouTube video.", params: [url(YT_VIDEO), limit(50, 500)] },
54
+ { tool: "youtube_channel_details", name: "YouTube Channel Details", path: "/v1/youtube/channel-details", credits: 1, summary: "Channel info & subscriber/stats for a YouTube channel.", params: [url(YT_CHANNEL)] },
55
+ { tool: "youtube_search", name: "YouTube Search", path: "/v1/youtube/search", credits: 20, summary: "Search YouTube videos by keyword.", params: [q(), limit(20, 200)] },
56
+ { tool: "youtube_channel_videos", name: "YouTube Channel Videos", path: "/v1/youtube/channel-videos", credits: 20, summary: "List a channel's uploaded videos.", params: [url(YT_CHANNEL), limit(20, 200)] },
57
+ { tool: "youtube_playlist_videos", name: "YouTube Playlist Videos", path: "/v1/youtube/playlist-videos", credits: 50, summary: "List videos in a YouTube playlist.", params: [url("YouTube playlist URL, e.g. https://youtube.com/playlist?list=ID."), limit(50, 500)] },
58
+ { tool: "youtube_video_download", name: "YouTube Video Download", path: "/v1/youtube/video-download", credits: 3, summary: "Direct download URLs for a YouTube video.", params: [url(YT_VIDEO)] },
59
+ { tool: "youtube_shorts_transcript", name: "YouTube Shorts Transcript", path: "/v1/youtube/shorts/transcript", credits: 2, summary: "Transcript of a YouTube Short.", params: [url(YT_SHORTS), language()] },
60
+ { tool: "youtube_shorts_summarize", name: "YouTube Shorts Summarizer", path: "/v1/youtube/shorts/summarize", credits: 4, summary: "AI summary of a YouTube Short.", params: [url(YT_SHORTS), language()] },
61
+ { tool: "youtube_shorts_details", name: "YouTube Shorts Stats", path: "/v1/youtube/shorts/video-details", credits: 1, summary: "Metadata + stats for a YouTube Short.", params: [url(YT_SHORTS)] },
62
+ { tool: "youtube_shorts_comments", name: "YouTube Shorts Comments", path: "/v1/youtube/shorts/comments", credits: 20, summary: "Comments on a YouTube Short.", params: [url(YT_SHORTS), limit(50, 500)] },
63
+ { tool: "youtube_channel_shorts", name: "YouTube Channel Shorts", path: "/v1/youtube/channel-shorts", credits: 20, summary: "List a channel's Shorts.", params: [url(YT_CHANNEL), limit(20, 200)] },
64
+ { tool: "youtube_channel_streams", name: "YouTube Channel Streams", path: "/v1/youtube/channel-streams", credits: 20, summary: "List a channel's live/past streams.", params: [url(YT_CHANNEL), limit(20, 200)] },
65
+ { tool: "youtube_hashtag_search", name: "YouTube Hashtag Search", path: "/v1/youtube/hashtag-search", credits: 20, summary: "Search YouTube videos by hashtag.", params: [q("Hashtag with or without the # (min 2 chars)."), limit(20, 200)] },
66
+ { tool: "youtube_comment_replies", name: "YouTube Comment Replies", path: "/v1/youtube/comment-replies", credits: 20, summary: "Replies to a specific YouTube comment.", params: [url(YT_VIDEO), commentId(), limit(50, 500)] },
67
+ { tool: "youtube_channel_playlists", name: "YouTube Channel Playlists", path: "/v1/youtube/channel-playlists", credits: 20, summary: "List a channel's playlists.", params: [url(YT_CHANNEL), limit(20, 200)] },
68
+ { tool: "youtube_community_posts", name: "YouTube Community Posts", path: "/v1/youtube/community-posts", credits: 10, summary: "List a channel's community (posts) tab.", params: [url(YT_CHANNEL), limit(20, 200)] },
69
+ ];
70
+ const TIKTOK = [
71
+ { tool: "tiktok_transcript", name: "TikTok Transcript", path: "/v1/tiktok/transcript", credits: 2, summary: "Transcript of a TikTok video (via captions).", params: [url(TT_VIDEO)] },
72
+ { tool: "tiktok_summarize", name: "TikTok Summarizer", path: "/v1/tiktok/summarize", credits: 4, summary: "AI summary of a TikTok video.", params: [url(TT_VIDEO)] },
73
+ { tool: "tiktok_video_details", name: "TikTok Video Details", path: "/v1/tiktok/video-details", credits: 1, summary: "Metadata + stats for a TikTok video.", params: [url(TT_VIDEO)] },
74
+ { tool: "tiktok_comments", name: "TikTok Comments", path: "/v1/tiktok/comments", credits: 10, summary: "Comments on a TikTok video.", params: [url(TT_VIDEO), limit(50, 500)] },
75
+ { tool: "tiktok_channel_details", name: "TikTok Channel Details", path: "/v1/tiktok/channel-details", credits: 1, summary: "Profile info & stats for a TikTok user.", params: [url(TT_PROFILE)] },
76
+ { tool: "tiktok_search", name: "TikTok Search", path: "/v1/tiktok/search", credits: 14, summary: "Search TikTok videos by keyword/hashtag.", params: [q(), limit(20, 200)] },
77
+ { tool: "tiktok_video_download", name: "TikTok Video Download", path: "/v1/tiktok/video-download", credits: 3, summary: "No-watermark download URL for a TikTok video.", params: [url(TT_VIDEO)] },
78
+ { tool: "tiktok_channel_posts", name: "TikTok Channel Posts", path: "/v1/tiktok/channel-posts", credits: 14, summary: "Latest posts from a TikTok profile.", params: [url(TT_PROFILE), limit(20, 200)] },
79
+ { tool: "tiktok_comment_replies", name: "TikTok Comment Replies", path: "/v1/tiktok/comment-replies", credits: 50, summary: "Replies to a specific TikTok comment.", params: [url(TT_VIDEO), commentId(), limit(50, 500)] },
80
+ { tool: "tiktok_user_followers", name: "TikTok User Followers", path: "/v1/tiktok/user-followers", credits: 20, summary: "List a TikTok user's followers.", params: [url(TT_PROFILE), limit(50, 500)] },
81
+ { tool: "tiktok_user_followings", name: "TikTok User Followings", path: "/v1/tiktok/user-followings", credits: 20, summary: "List who a TikTok user follows.", params: [url(TT_PROFILE), limit(50, 500)] },
82
+ { tool: "tiktok_music_posts", name: "TikTok Music Posts", path: "/v1/tiktok/music-posts", credits: 32, summary: "Posts using a specific TikTok sound/music.", params: [url(TT_MUSIC), limit(20, 200)] },
83
+ { tool: "tiktok_hashtag_search", name: "TikTok Hashtag Search", path: "/v1/tiktok/hashtag-search", credits: 14, summary: "Search TikTok videos by hashtag.", params: [q("Hashtag with or without the # (min 2 chars)."), limit(20, 200)] },
84
+ { tool: "tiktok_top_search", name: "TikTok Top Search", path: "/v1/tiktok/top-search", credits: 14, summary: "Top mixed TikTok results for a keyword.", params: [q(), limit(20, 200)] },
85
+ { tool: "tiktok_user_search", name: "TikTok User Search", path: "/v1/tiktok/user-search", credits: 8, summary: "Search TikTok users/creators by keyword.", params: [q(), limit(20, 100)] },
86
+ { tool: "tiktok_song_details", name: "TikTok Song Details", path: "/v1/tiktok/song-details", credits: 2, summary: "Details of a TikTok sound/song.", params: [url(TT_MUSIC)] },
87
+ { tool: "tiktok_trending_feed", name: "TikTok Trending Feed", path: "/v1/tiktok/trending-feed", credits: 14, summary: "TikTok trending (For You) videos by region.", params: [{ name: "country", type: "string", required: false, description: "Two-letter ISO country code, e.g. US, GB, TR. Default US." }, limit(20, 200)] },
88
+ { tool: "tiktok_popular_hashtags", name: "TikTok Popular Hashtags", path: "/v1/tiktok/popular-hashtags", credits: 14, summary: "Trending TikTok hashtags for a topic/keyword.", params: [{ name: "query", type: "string", required: false, description: 'Topic or keyword to discover trending hashtags for. Default "trending".' }, limit(20, 100)] },
89
+ ];
90
+ const INSTAGRAM = [
91
+ { tool: "instagram_transcript", name: "Instagram Transcript", path: "/v1/instagram/transcript", credits: 2, summary: "Transcript of an Instagram Reel.", params: [url(IG_REEL)] },
92
+ { tool: "instagram_summarize", name: "Instagram Summarizer", path: "/v1/instagram/summarize", credits: 4, summary: "AI summary of an Instagram Reel.", params: [url(IG_REEL)] },
93
+ { tool: "instagram_details", name: "Instagram Details", path: "/v1/instagram/details", credits: 1, summary: "Details for an Instagram post or reel.", params: [url(IG_POST)] },
94
+ { tool: "instagram_comments", name: "Instagram Comments", path: "/v1/instagram/comments", credits: 45, summary: "Comments on an Instagram post or reel.", params: [url(IG_POST), limit(50, 500)] },
95
+ { tool: "instagram_channel_details", name: "Instagram Channel Details", path: "/v1/instagram/channel-details", credits: 1, summary: "Profile info & stats for an Instagram account.", params: [url(IG_PROFILE)] },
96
+ { tool: "instagram_channel_posts", name: "Instagram Channel Posts", path: "/v1/instagram/channel-posts", credits: 12, summary: "Latest posts from an Instagram profile.", params: [url(IG_PROFILE), limit(20, 200)] },
97
+ { tool: "instagram_channel_reels", name: "Instagram Channel Reels", path: "/v1/instagram/channel-reels", credits: 12, summary: "Latest Reels from an Instagram profile.", params: [url(IG_PROFILE), limit(20, 200)] },
98
+ { tool: "instagram_reels_search", name: "Instagram Reels Search", path: "/v1/instagram/reels-search", credits: 12, summary: "Search Instagram Reels by hashtag/keyword.", params: [q("Hashtag (without #) or keyword (min 2 chars)."), limit(20, 200)] },
99
+ { tool: "instagram_video_download", name: "Instagram Video Download", path: "/v1/instagram/video-download", credits: 3, summary: "Direct video URL for an Instagram Reel.", params: [url(IG_REEL)] },
100
+ { tool: "instagram_tagged_posts", name: "Instagram Tagged Posts", path: "/v1/instagram/tagged-posts", credits: 18, summary: "Posts an Instagram user is tagged in.", params: [url(IG_PROFILE), limit(20, 200)] },
101
+ { tool: "instagram_music_posts", name: "Instagram Music Posts", path: "/v1/instagram/music-posts", credits: 18, summary: "Posts/Reels using an Instagram audio.", params: [url("Instagram audio/music page URL."), limit(20, 200)] },
102
+ { tool: "instagram_hashtag_search", name: "Instagram Hashtag Search", path: "/v1/instagram/hashtag-search", credits: 12, summary: "Search Instagram posts by hashtag.", params: [q("Hashtag without the # (min 2 chars)."), limit(20, 200)] },
103
+ { tool: "instagram_profile_search", name: "Instagram Profile Search", path: "/v1/instagram/profile-search", credits: 12, summary: "Search Instagram profiles by keyword.", params: [q(), limit(20, 100)] },
104
+ { tool: "instagram_story_highlights", name: "Instagram Story Highlights", path: "/v1/instagram/story-highlights", credits: 5, summary: "List a profile's story highlight covers.", params: [url(IG_PROFILE)] },
105
+ { tool: "instagram_highlights_details", name: "Instagram Highlights Details", path: "/v1/instagram/highlights-details", credits: 9, summary: "Items inside a profile's story highlights.", params: [url(IG_PROFILE), { name: "limit", type: "number", required: false, description: "Max highlights to expand. Default 10, max 50." }] },
106
+ { tool: "instagram_embed", name: "Instagram Embed", path: "/v1/instagram/embed", credits: 1, summary: "Embed HTML for an Instagram post/reel.", params: [url(IG_POST)] },
107
+ ];
108
+ const FACEBOOK = [
109
+ { tool: "facebook_details", name: "Facebook Details", path: "/v1/facebook/details", credits: 1, summary: "Details for a Facebook video or post.", params: [url(FB_VIDEO)] },
110
+ { tool: "facebook_transcript", name: "Facebook Transcript", path: "/v1/facebook/transcript", credits: 2, summary: "Transcript of a Facebook video.", params: [url(FB_VIDEO)] },
111
+ { tool: "facebook_summarize", name: "Facebook Summarizer", path: "/v1/facebook/summarize", credits: 4, summary: "AI summary of a Facebook video or post.", params: [url(FB_VIDEO)] },
112
+ { tool: "facebook_comments", name: "Facebook Comments", path: "/v1/facebook/comments", credits: 30, summary: "Comments on a Facebook post.", params: [url(FB_VIDEO), limit(50, 500)] },
113
+ { tool: "facebook_page_details", name: "Facebook Page Details", path: "/v1/facebook/page-details", credits: 1, summary: "Info & stats for a Facebook page.", params: [url("Facebook page URL, e.g. https://facebook.com/PageName.")] },
114
+ { tool: "facebook_profile_posts", name: "Facebook Profile Posts", path: "/v1/facebook/profile-posts", credits: 12, summary: "Latest posts from a Facebook profile/page.", params: [url("Facebook profile or page URL."), limit(20, 200)] },
115
+ { tool: "facebook_profile_reels", name: "Facebook Profile Reels", path: "/v1/facebook/profile-reels", credits: 36, summary: "Latest Reels from a Facebook profile/page.", params: [url("Facebook profile or page URL."), limit(20, 200)] },
116
+ { tool: "facebook_group_posts", name: "Facebook Group Posts", path: "/v1/facebook/group-posts", credits: 12, summary: "Posts from a public Facebook group.", params: [url("Public Facebook group URL, e.g. https://facebook.com/groups/ID."), limit(20, 200)] },
117
+ { tool: "facebook_comment_replies", name: "Facebook Comment Replies", path: "/v1/facebook/comment-replies", credits: 30, summary: "Replies to a specific Facebook comment.", params: [url("Facebook post URL the comment belongs to."), commentId(), limit(50, 500)] },
118
+ ];
119
+ function withPlatform(list, platform) {
120
+ return list.map((e) => ({ ...e, platform }));
121
+ }
122
+ exports.ENDPOINTS = [
123
+ ...withPlatform(YOUTUBE, "youtube"),
124
+ ...withPlatform(TIKTOK, "tiktok"),
125
+ ...withPlatform(INSTAGRAM, "instagram"),
126
+ ...withPlatform(FACEBOOK, "facebook"),
127
+ ];
128
+ /** A concise, agent-facing description (summary + cost) for an endpoint. */
129
+ function describe(e) {
130
+ return `${e.summary} Costs ~${e.credits} credit${e.credits === 1 ? "" : "s"}; cached results are free, failures are never charged.`;
131
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CaptapiApi = void 0;
4
+ class CaptapiApi {
5
+ constructor() {
6
+ this.name = 'captapiApi';
7
+ this.displayName = 'Captapi API';
8
+ this.documentationUrl = 'https://captapi.com/docs';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: { password: true },
15
+ default: '',
16
+ required: true,
17
+ description: 'Your Captapi API key. Create one at https://captapi.com/dashboard/api-keys',
18
+ },
19
+ {
20
+ displayName: 'Base URL',
21
+ name: 'baseUrl',
22
+ type: 'string',
23
+ default: 'https://api.captapi.com',
24
+ description: 'Captapi API base URL. Only change this if you use a custom or self-hosted gateway.',
25
+ },
26
+ ];
27
+ this.authenticate = {
28
+ type: 'generic',
29
+ properties: {
30
+ headers: {
31
+ Authorization: '=Bearer {{$credentials.apiKey}}',
32
+ },
33
+ },
34
+ };
35
+ this.test = {
36
+ request: {
37
+ baseURL: '={{$credentials.baseUrl}}',
38
+ url: '/v1/account/limits',
39
+ },
40
+ };
41
+ }
42
+ }
43
+ exports.CaptapiApi = CaptapiApi;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Captapi = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const catalog_1 = require("../../catalog");
6
+ const VERSION = '0.1.0';
7
+ const PLATFORMS = [
8
+ { value: 'youtube', name: 'YouTube' },
9
+ { value: 'tiktok', name: 'TikTok' },
10
+ { value: 'instagram', name: 'Instagram' },
11
+ { value: 'facebook', name: 'Facebook' },
12
+ ];
13
+ // Friendly field labels for the known parameter names in the catalog.
14
+ const PARAM_LABELS = {
15
+ url: 'URL',
16
+ q: 'Query',
17
+ query: 'Query',
18
+ limit: 'Limit',
19
+ language: 'Language',
20
+ comment_id: 'Comment ID',
21
+ country: 'Country',
22
+ };
23
+ function labelFor(name) {
24
+ if (PARAM_LABELS[name])
25
+ return PARAM_LABELS[name];
26
+ return name
27
+ .split('_')
28
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
29
+ .join(' ');
30
+ }
31
+ // Build the full property list (resource + per-platform operations + per-endpoint
32
+ // fields) directly from the shared catalog, so the node always matches the API.
33
+ function buildProperties() {
34
+ const props = [];
35
+ props.push({
36
+ displayName: 'Platform',
37
+ name: 'resource',
38
+ type: 'options',
39
+ noDataExpression: true,
40
+ options: PLATFORMS.map((p) => ({ name: p.name, value: p.value })),
41
+ default: 'youtube',
42
+ });
43
+ for (const p of PLATFORMS) {
44
+ const eps = catalog_1.ENDPOINTS.filter((e) => e.platform === p.value);
45
+ props.push({
46
+ displayName: 'Operation',
47
+ name: 'operation',
48
+ type: 'options',
49
+ noDataExpression: true,
50
+ displayOptions: { show: { resource: [p.value] } },
51
+ options: eps.map((e) => ({
52
+ name: e.name,
53
+ value: e.tool,
54
+ description: e.summary,
55
+ action: e.name,
56
+ })),
57
+ default: eps.length ? eps[0].tool : '',
58
+ });
59
+ }
60
+ for (const e of catalog_1.ENDPOINTS) {
61
+ for (const param of e.params) {
62
+ props.push({
63
+ displayName: labelFor(param.name),
64
+ name: param.name,
65
+ type: param.type === 'number' ? 'number' : 'string',
66
+ required: param.required,
67
+ default: param.type === 'number' ? 0 : '',
68
+ description: param.description,
69
+ displayOptions: {
70
+ show: {
71
+ resource: [e.platform],
72
+ operation: [e.tool],
73
+ },
74
+ },
75
+ });
76
+ }
77
+ }
78
+ return props;
79
+ }
80
+ class Captapi {
81
+ constructor() {
82
+ this.description = {
83
+ displayName: 'Captapi',
84
+ name: 'captapi',
85
+ icon: 'fa:photo-video',
86
+ iconColor: 'purple',
87
+ group: ['transform'],
88
+ version: 1,
89
+ subtitle: '={{$parameter["operation"]}}',
90
+ description: 'Structured social media data from YouTube, TikTok, Instagram & Facebook — transcripts, AI summaries, comments, stats, search and downloads.',
91
+ defaults: {
92
+ name: 'Captapi',
93
+ },
94
+ inputs: ['main'],
95
+ outputs: ['main'],
96
+ credentials: [
97
+ {
98
+ name: 'captapiApi',
99
+ required: true,
100
+ },
101
+ ],
102
+ requestDefaults: {
103
+ baseURL: '={{$credentials.baseUrl}}',
104
+ },
105
+ properties: buildProperties(),
106
+ };
107
+ }
108
+ async execute() {
109
+ const items = this.getInputData();
110
+ const returnData = [];
111
+ const credentials = await this.getCredentials('captapiApi');
112
+ const baseUrl = String(credentials.baseUrl || 'https://api.captapi.com').replace(/\/+$/, '');
113
+ const byTool = new Map(catalog_1.ENDPOINTS.map((e) => [e.tool, e]));
114
+ for (let i = 0; i < items.length; i++) {
115
+ try {
116
+ const operation = this.getNodeParameter('operation', i);
117
+ const endpoint = byTool.get(operation);
118
+ if (!endpoint) {
119
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
120
+ itemIndex: i,
121
+ });
122
+ }
123
+ const qs = {};
124
+ for (const param of endpoint.params) {
125
+ const value = this.getNodeParameter(param.name, i, '');
126
+ const isEmpty = value === '' ||
127
+ value === undefined ||
128
+ value === null ||
129
+ (param.type === 'number' && Number(value) === 0);
130
+ if (!isEmpty)
131
+ qs[param.name] = value;
132
+ }
133
+ const responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'captapiApi', {
134
+ method: 'GET',
135
+ baseURL: baseUrl,
136
+ url: endpoint.path,
137
+ qs,
138
+ json: true,
139
+ headers: {
140
+ 'User-Agent': `n8n-nodes-captapi/${VERSION}`,
141
+ },
142
+ });
143
+ returnData.push({
144
+ json: (responseData !== null && responseData !== void 0 ? responseData : {}),
145
+ pairedItem: { item: i },
146
+ });
147
+ }
148
+ catch (error) {
149
+ if (this.continueOnFail()) {
150
+ returnData.push({
151
+ json: { error: error.message },
152
+ pairedItem: { item: i },
153
+ });
154
+ continue;
155
+ }
156
+ if (error instanceof n8n_workflow_1.NodeOperationError)
157
+ throw error;
158
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error, { itemIndex: i });
159
+ }
160
+ }
161
+ return [returnData];
162
+ }
163
+ }
164
+ exports.Captapi = Captapi;
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "n8n-nodes-captapi",
3
+ "version": "0.1.0",
4
+ "description": "n8n community node for Captapi — structured social media data from YouTube, TikTok, Instagram & Facebook (transcripts, AI summaries, comments, stats, search, downloads). 62 operations, one API key.",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "n8n",
8
+ "captapi",
9
+ "youtube",
10
+ "tiktok",
11
+ "instagram",
12
+ "facebook",
13
+ "social-media",
14
+ "transcript",
15
+ "scraper"
16
+ ],
17
+ "license": "MIT",
18
+ "homepage": "https://captapi.com",
19
+ "author": {
20
+ "name": "Captapi",
21
+ "email": "support@captapi.com"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/CDCStream/captapi.git",
26
+ "directory": "packages/captapi-n8n"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.10"
30
+ },
31
+ "main": "index.js",
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "n8n": {
40
+ "n8nNodesApiVersion": 1,
41
+ "credentials": [
42
+ "dist/credentials/CaptapiApi.credentials.js"
43
+ ],
44
+ "nodes": [
45
+ "dist/nodes/Captapi/Captapi.node.js"
46
+ ]
47
+ },
48
+ "peerDependencies": {
49
+ "n8n-workflow": "*"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.0.0",
53
+ "n8n-workflow": "^1.66.0",
54
+ "typescript": "^5.6.0"
55
+ }
56
+ }