n8n-nodes-influtics 1.0.0 → 1.0.2

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.
@@ -0,0 +1,25 @@
1
+ export class InfluticsApi {
2
+ name = 'influticsApi';
3
+ displayName = 'Influtics API';
4
+ documentationUrl = 'https://docs.influtics.com/';
5
+ properties = [
6
+ {
7
+ displayName: 'API Key',
8
+ name: 'apiKey',
9
+ type: 'string',
10
+ typeOptions: { password: true },
11
+ default: '',
12
+ required: true,
13
+ description: 'Get your API key from the Influtics dashboard under Settings → API',
14
+ },
15
+ ];
16
+ authenticate = {
17
+ type: 'generic',
18
+ properties: {
19
+ headers: {
20
+ Authorization: '=Bearer {{ $credentials.apiKey }}',
21
+ },
22
+ },
23
+ };
24
+ }
25
+ //# sourceMappingURL=InfluticsApi.credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfluticsApi.credentials.js","sourceRoot":"","sources":["../../credentials/InfluticsApi.credentials.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,YAAY;IACvB,IAAI,GAAG,cAAc,CAAC;IACtB,WAAW,GAAG,eAAe,CAAC;IAC9B,gBAAgB,GAAG,6BAA6B,CAAC;IACjD,UAAU,GAAsB;QAC9B;YACE,WAAW,EAAE,SAAS;YACtB,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC/B,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,oEAAoE;SAClF;KACF,CAAC;IACF,YAAY,GAAG;QACb,IAAI,EAAE,SAAS;QACf,UAAU,EAAE;YACV,OAAO,EAAE;gBACP,aAAa,EAAE,mCAAmC;aACnD;SACF;KACO,CAAC;CACZ"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Package entry point.
3
+ *
4
+ * Re-exports every node class and credential so `import 'n8n-nodes-influtics'`
5
+ * (the value `package.json: "main"` resolves to) gives consumers the full
6
+ * surface in one named-import namespace. n8n's community-node loader reads
7
+ * `main` to discover the package; without this file `dist/index.js` is
8
+ * missing and the loader surfaces a parse error on the missing entry.
9
+ *
10
+ * Per the official `@n8n/node-cli` template (`npx n8n-node new`).
11
+ */
12
+ export { InfluticsApi } from './credentials/InfluticsApi.credentials.js';
13
+ export { InfluticsVideo } from './nodes/InfluticsVideo/InfluticsVideo.node.js';
14
+ export { InfluticsBlogger } from './nodes/InfluticsBlogger/InfluticsBlogger.node.js';
15
+ export { InfluticsTrend } from './nodes/InfluticsTrend/InfluticsTrend.node.js';
16
+ export { InfluticsAccount } from './nodes/InfluticsAccount/InfluticsAccount.node.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mDAAmD,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mDAAmD,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { NodeApiError } from 'n8n-workflow';
2
+ export const INFLUTICS_API_BASE_URL = 'https://api.influtics.com';
3
+ export const CREDENTIAL_NAME = 'influticsApi';
4
+ function extractApiErrorBody(error) {
5
+ const body = error?.response?.body;
6
+ const inner = body?.error;
7
+ return {
8
+ code: inner?.code,
9
+ message: inner?.message,
10
+ upgrade_url: inner?.upgrade_url,
11
+ };
12
+ }
13
+ export function mapInfluticsError(error) {
14
+ const { code, message, upgrade_url } = extractApiErrorBody(error);
15
+ if (code || message) {
16
+ const descParts = [];
17
+ if (upgrade_url)
18
+ descParts.push(`Upgrade: ${upgrade_url}`);
19
+ const finalMessage = `${code ?? 'API_ERROR'}: ${message ?? 'Unknown error'}`;
20
+ const e = new Error(finalMessage);
21
+ e.description = descParts.join('\n') || undefined;
22
+ return e;
23
+ }
24
+ return error instanceof Error ? error : new Error(String(error));
25
+ }
26
+ export async function influticsApiRequest(method, endpoint, body, qs) {
27
+ const options = {
28
+ method,
29
+ url: `${INFLUTICS_API_BASE_URL}${endpoint}`,
30
+ headers: {
31
+ Accept: 'application/json',
32
+ 'Content-Type': 'application/json',
33
+ // Authorization header is injected by the credential's `authenticate` block.
34
+ },
35
+ json: true,
36
+ ...(body ? { body } : {}),
37
+ ...(qs ? { qs } : {}),
38
+ };
39
+ try {
40
+ const response = await this.helpers.requestWithAuthentication.call(this, CREDENTIAL_NAME, options);
41
+ return response;
42
+ }
43
+ catch (rawError) {
44
+ // Map the raw helper error into { code, message, description }. The description
45
+ // carries the upgrade URL on 402 PAID_PLAN_REQUIRED so it surfaces in the n8n UI
46
+ // without code-fishing. Both message AND description MUST go through NodeApiError's
47
+ // options bag, since the wrapped `rawError` itself has no description on it.
48
+ const mapped = mapInfluticsError(rawError);
49
+ throw new NodeApiError(this.getNode(), rawError, {
50
+ message: mapped.message,
51
+ description: mapped.description,
52
+ });
53
+ }
54
+ }
55
+ /**
56
+ * Cursor-aware paginator for endpoints that expose `next_cursor`. Influtics
57
+ * returns the cursor under `meta.next_cursor`; we fold every page's `data`
58
+ * into a single array so downstream n8n nodes see one item per record.
59
+ *
60
+ * Endpoints that don't paginate today just return a single page (no cursor);
61
+ * in that case the caller still gets back one combined array.
62
+ */
63
+ export async function influticsApiRequestAllItems(method, endpoint, qs) {
64
+ const aggregated = [];
65
+ let cursor;
66
+ // Hard cap on pages so a runaway cursor can't DOS the workflow.
67
+ for (let page = 0; page < 50; page++) {
68
+ const pageQs = { ...(qs ?? {}) };
69
+ if (cursor)
70
+ pageQs.cursor = cursor;
71
+ const response = await influticsApiRequest.call(this, method, endpoint, undefined, pageQs);
72
+ const items = Array.isArray(response?.data) ? response.data : [];
73
+ aggregated.push(...items);
74
+ const next = response?.meta?.next_cursor;
75
+ if (!next)
76
+ break;
77
+ cursor = next;
78
+ }
79
+ return aggregated;
80
+ }
81
+ //# sourceMappingURL=GenericFunctions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GenericFunctions.js","sourceRoot":"","sources":["../../nodes/GenericFunctions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Influtics Account node.
3
+ *
4
+ * Implementation choices:
5
+ * - File lives at nodes/InfluticsAccount/InfluticsAccount.node.ts — required by
6
+ * the eslint-plugin-n8n-nodes-base `node-dirname-against-convention` rule.
7
+ * - `executeInfluticsAccount` is also exported as a named function so unit tests
8
+ * can drive the executor without instantiating the INodeType class.
9
+ * - Two read-only operations: Get Usage + Get Limits. Both endpoints take no
10
+ * user input (no query params, no body) — there is nothing to guard against
11
+ * on the wire, so neither handler needs defensive validation.
12
+ * - Both endpoints cost 0 credits and are exempt from the paid-plan gate,
13
+ * so free-tier callers can reach them. Public docs:
14
+ * https://docs.influtics.com/
15
+ * - The unimplemented-operation branch keeps the executor safe if a future
16
+ * version's parameters somehow leak an unknown value.
17
+ *
18
+ * Backend contract (public docs: https://docs.influtics.com/):
19
+ * GET /v1/account/usage
20
+ * query: NONE. body: NONE.
21
+ * 200 → {
22
+ * success: true,
23
+ * data: {
24
+ * usage_history: [...rows from daily_api_usage over the last 30 days...],
25
+ * summary: {
26
+ * plan: "free"|"pro"|"business"|null,
27
+ * is_unlimited: boolean,
28
+ * videos: { limit: number|null, used: number|null },
29
+ * credits: { total: number, used: number }
30
+ * }
31
+ * },
32
+ * meta: { processing_time_ms, request_id }
33
+ * }
34
+ * Errors: 401 UNAUTHORIZED, 429 RATE_LIMITED.
35
+ *
36
+ * GET /v1/account/limits
37
+ * query: NONE. body: NONE.
38
+ * 200 → {
39
+ * success: true,
40
+ * data: {
41
+ * rate_limits: {
42
+ * requests_per_minute: number,
43
+ * requests_per_hour: number,
44
+ * requests_per_day: number,
45
+ * requests_per_month: number,
46
+ * burst_allowance: number
47
+ * }
48
+ * },
49
+ * meta: { processing_time_ms, request_id }
50
+ * }
51
+ * Server-side default rate limits (when no override is configured):
52
+ * { requests_per_minute: 60, requests_per_hour: 3600,
53
+ * requests_per_day: 86400, requests_per_month: 10000,
54
+ * burst_allowance: 120 }.
55
+ * Errors: 401 UNAUTHORIZED, 429 RATE_LIMITED.
56
+ */
57
+ import { NodeOperationError, } from 'n8n-workflow';
58
+ import { influticsApiRequest } from '../GenericFunctions.js';
59
+ const OPERATIONS = {
60
+ getUsage: async function (_i) {
61
+ // No input → no defensive guards needed. The executor calls the API
62
+ // directly and returns the raw envelope so downstream n8n consumers see
63
+ // exactly what the API sent (mirrors the Track / Get Stats patterns).
64
+ const response = await influticsApiRequest.call(this, 'GET', '/v1/account/usage');
65
+ return response;
66
+ },
67
+ getLimits: async function (_i) {
68
+ // No input → no defensive guards needed. Same wire shape as Get Usage:
69
+ // a single GET with no qs / no body.
70
+ const response = await influticsApiRequest.call(this, 'GET', '/v1/account/limits');
71
+ return response;
72
+ },
73
+ };
74
+ export async function executeInfluticsAccount(_items) {
75
+ const operation = this.getNodeParameter('operation', 0);
76
+ const handler = OPERATIONS[operation];
77
+ if (!handler) {
78
+ throw new NodeOperationError(this.getNode(), `Operation "${operation}" not yet implemented in InfluticsAccount node`);
79
+ }
80
+ // Both ops are single-batch: one GET per workflow run regardless of input
81
+ // item count. Mirrors the Track-videos and Track-bloggers single-batch
82
+ // pattern from InfluticsVideo / InfluticsBlogger.
83
+ const response = await handler.call(this, 0);
84
+ return [[{ json: response }]];
85
+ }
86
+ export class InfluticsAccount {
87
+ description = {
88
+ displayName: 'Influtics Account',
89
+ name: 'influticsAccount',
90
+ icon: 'file:influtics.svg',
91
+ group: ['transform'],
92
+ version: 1,
93
+ description: 'Read Influtics account usage and limits',
94
+ // eslint-plugin-n8n-nodes-base `node-class-description-missing-subtitle`
95
+ // requires a subtitle when every property is `displayName: 'Operation'`
96
+ // (sibling nodes like InfluticsVideo skip the rule because they have
97
+ // additional non-Operation fields). Map the raw operation value to its
98
+ // displayName so the canvas renders the friendly name rather than the
99
+ // enum value.
100
+ subtitle: '={{ $parameter["operation"] === "getUsage" ? "Get Usage" : "Get Limits" }}',
101
+ defaults: { name: 'Influtics Account' },
102
+ inputs: ['main'],
103
+ outputs: ['main'],
104
+ credentials: [{ name: 'influticsApi', required: true }],
105
+ properties: [
106
+ {
107
+ displayName: 'Operation',
108
+ name: 'operation',
109
+ type: 'options',
110
+ noDataExpression: true,
111
+ // Alphabetized per eslint-plugin-n8n-nodes-base `node-param-options-type-unsorted-items`.
112
+ // Each option carries an `action:` (rule `node-param-operation-option-action-miscased`).
113
+ options: [
114
+ {
115
+ name: 'Get Limits',
116
+ value: 'getLimits',
117
+ description: 'Read rate limit configuration',
118
+ action: 'Read rate limit configuration',
119
+ },
120
+ {
121
+ name: 'Get Usage',
122
+ value: 'getUsage',
123
+ description: 'Read usage history and subscription summary',
124
+ action: 'Read usage history and subscription summary',
125
+ },
126
+ ],
127
+ default: 'getUsage',
128
+ },
129
+ ],
130
+ };
131
+ async execute() {
132
+ return executeInfluticsAccount.call(this, this.getInputData());
133
+ }
134
+ }
135
+ //# sourceMappingURL=InfluticsAccount.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfluticsAccount.node.js","sourceRoot":"","sources":["../../../nodes/InfluticsAccount/InfluticsAccount.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EACL,kBAAkB,GAMnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAW7D,MAAM,UAAU,GAAqC;IACnD,QAAQ,EAAE,KAAK,WAAoC,EAAU;QAC3D,oEAAoE;QACpE,wEAAwE;QACxE,sEAAsE;QACtE,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC;QAClF,OAAO,QAAuB,CAAC;IACjC,CAAC;IACD,SAAS,EAAE,KAAK,WAAoC,EAAU;QAC5D,uEAAuE;QACvE,qCAAqC;QACrC,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC;QACnF,OAAO,QAAuB,CAAC;IACjC,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAE3C,MAA4B;IAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CAAW,CAAC;IAClE,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,kBAAkB,CAC1B,IAAI,CAAC,OAAO,EAAE,EACd,cAAc,SAAS,gDAAgD,CACxE,CAAC;IACJ,CAAC;IACD,0EAA0E;IAC1E,uEAAuE;IACvE,kDAAkD;IAClD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,OAAO,gBAAgB;IAC3B,WAAW,GAAyB;QAClC,WAAW,EAAE,mBAAmB;QAChC,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,CAAC,WAAW,CAAC;QACpB,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,yCAAyC;QACtD,yEAAyE;QACzE,wEAAwE;QACxE,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,cAAc;QACd,QAAQ,EACN,4EAA4E;QAC9E,QAAQ,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE;QACvC,MAAM,EAAE,CAAC,MAAM,CAAC;QAChB,OAAO,EAAE,CAAC,MAAM,CAAC;QACjB,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACvD,UAAU,EAAE;YACV;gBACE,WAAW,EAAE,WAAW;gBACxB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,SAAS;gBACf,gBAAgB,EAAE,IAAI;gBACtB,0FAA0F;gBAC1F,yFAAyF;gBACzF,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,YAAY;wBAClB,KAAK,EAAE,WAAW;wBAClB,WAAW,EAAE,+BAA+B;wBAC5C,MAAM,EAAE,+BAA+B;qBACxC;oBACD;wBACE,IAAI,EAAE,WAAW;wBACjB,KAAK,EAAE,UAAU;wBACjB,WAAW,EAAE,6CAA6C;wBAC1D,MAAM,EAAE,6CAA6C;qBACtD;iBACF;gBACD,OAAO,EAAE,UAAU;aACpB;SACF;KACF,CAAC;IAEF,KAAK,CAAC,OAAO;QACX,OAAO,uBAAuB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;IACjE,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <rect width="60" height="60" rx="12" fill="#6266F0"/>
3
+ <text x="30" y="38" font-family="Helvetica,Arial,sans-serif" font-size="22" font-weight="700"
4
+ fill="#FFFFFF" text-anchor="middle">I</text>
5
+ </svg>
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Influtics Blogger node.
3
+ *
4
+ * Implementation choices:
5
+ * - File lives at nodes/InfluticsBlogger/InfluticsBlogger.node.ts — required by the
6
+ * eslint-plugin-n8n-nodes-base `node-dirname-against-convention` rule.
7
+ * - `executeInfluticsBlogger` is also exported as a named function so unit tests
8
+ * can drive the executor without instantiating the INodeType class.
9
+ * - Tasks 4/5/6 ship the InfluticsVideo node; this node (Task 7) adds the
10
+ * blogger side: Track + Get Job + By Username.
11
+ * - The unimplemented-operation branch keeps the executor safe if a future
12
+ * version's parameters somehow leak an unknown value.
13
+ *
14
+ * Backend contract (verified against api-worker source):
15
+ * POST /v1/bloggers/track
16
+ * body (flat): { platform, username, initial_videos_count }
17
+ * - `initial_videos_count` is REQUIRED (integer in [1, 500])
18
+ * per validateTrackBloggerBody — the stale `track.md` docs page and
19
+ * the plan both omit it; the validator rejects bodies without it.
20
+ * - The platform-specific channel ID is NOT in the request. The consumer
21
+ * resolves `username -> channel_id` asynchronously via the platform
22
+ * API after dequeuing (see api-worker CLAUDE.md "Track creator flow").
23
+ * - 202 Accepted, returns `{ job_id, status_url, polling.retry_after_seconds }`.
24
+ * - 409 CREATOR_ALREADY_TRACKED, 422 SUBSCRIPTION_LIMIT, 402 PAID_PLAN_REQUIRED.
25
+ * GET /v1/bloggers/jobs/{job_id}
26
+ * - Path-only, no qs. Returns 200/404/410.
27
+ * - shapeJob uses `status` (queued|processing|succeeded|error), not `state`.
28
+ * - 410 JOB_TIMEOUT when sweeper-marked (terminal — re-POST /v1/bloggers/track).
29
+ * - 404 NOT_FOUND collapses "doesn't exist" + "wrong org" (Stripe-parity).
30
+ * GET /v1/bloggers/by-username/{username}?platform={platform}
31
+ * - Path is URL-decoded server-side (`decodeURIComponent`); executor
32
+ * URL-encodes the path segment so `@handle` (and other reserved chars)
33
+ * round-trip cleanly.
34
+ * - Query `platform` is optional; server defaults to `tiktok` if absent
35
+ * OR empty. We strip empty strings here so the default path stays clean.
36
+ * - Read-only — NEVER auto-tracks. 404 BLOGGER_NOT_TRACKED if not tracked
37
+ * by the calling org. The legacy /v1/bloggers/info?username=... form
38
+ * was removed 2026-08-20 (see api-worker CLAUDE.md "Deprecations").
39
+ */
40
+ import { NodeOperationError, } from 'n8n-workflow';
41
+ import { influticsApiRequest } from '../GenericFunctions.js';
42
+ // Backend hard caps. Mirrors `validateTrackBloggerBody` in
43
+ // api-worker/src/handlers/lib/trackAccountValidator.js. The UI also clamps
44
+ // via `typeOptions.minValue` / `maxValue`; this is defense-in-depth for
45
+ // custom callers / future schema drift.
46
+ const INITIAL_VIDEOS_MIN = 1;
47
+ const INITIAL_VIDEOS_MAX = 500;
48
+ const OPERATIONS = {
49
+ track: async function (_i) {
50
+ const platform = this.getNodeParameter('platform', _i, '');
51
+ const username = this.getNodeParameter('username', _i, '');
52
+ const initialVideosCountRaw = this.getNodeParameter('initialVideosCount', _i, '');
53
+ if (!platform) {
54
+ // Fail fast: backend rejects missing platform (422 VALIDATION_ERROR).
55
+ // Surface a clear UI message instead of letting the workflow silently 422.
56
+ throw new NodeOperationError(this.getNode(), 'Platform is required');
57
+ }
58
+ if (!username) {
59
+ throw new NodeOperationError(this.getNode(), 'Username is required');
60
+ }
61
+ // Coerce + clamp initial_videos_count defensively:
62
+ // - blank string / non-number / NaN → fail fast
63
+ // - < 1 → fail fast (backend rejects, 422)
64
+ // - > 500 → clamp to 500 (backend rejects, 422; clamp defends against
65
+ // a custom caller bypassing the UI's maxValue guard)
66
+ // The UI's `typeOptions.minValue = 1 / maxValue = 500` is the primary
67
+ // guard; this is the second line of defense.
68
+ const initialVideosCount = Number(initialVideosCountRaw);
69
+ if (initialVideosCountRaw === '' ||
70
+ initialVideosCountRaw === null ||
71
+ initialVideosCountRaw === undefined ||
72
+ !Number.isFinite(initialVideosCount) ||
73
+ !Number.isInteger(initialVideosCount)) {
74
+ throw new NodeOperationError(this.getNode(), 'initial_videos_count is required and must be an integer between 1 and 500');
75
+ }
76
+ const initialVideosCountClamped = Math.max(INITIAL_VIDEOS_MIN, Math.min(initialVideosCount, INITIAL_VIDEOS_MAX));
77
+ const response = await influticsApiRequest.call(this, 'POST', '/v1/bloggers/track', {
78
+ platform,
79
+ username,
80
+ initial_videos_count: initialVideosCountClamped,
81
+ });
82
+ return response;
83
+ },
84
+ getJob: async function (_i) {
85
+ const jobId = this.getNodeParameter('jobId', _i, '');
86
+ if (!jobId) {
87
+ // Fail fast: without an id we'd send `GET /v1/bloggers/jobs/` which 404s
88
+ // with a confusing URL and no actionable error.
89
+ throw new NodeOperationError(this.getNode(), 'Job ID is required');
90
+ }
91
+ const response = await influticsApiRequest.call(this, 'GET', `/v1/bloggers/jobs/${encodeURIComponent(jobId)}`);
92
+ return response;
93
+ },
94
+ byUsername: async function (_i) {
95
+ const username = this.getNodeParameter('username', _i, '');
96
+ if (!username) {
97
+ // Without a username the URL would be /v1/bloggers/by-username/ — backend
98
+ // returns 400 VALIDATION_ERROR. Fail fast with a clear UI message instead.
99
+ throw new NodeOperationError(this.getNode(), 'Username is required');
100
+ }
101
+ // Backend reads `url.searchParams.get('platform') || 'tiktok'`. Empty string
102
+ // would technically still resolve to 'tiktok' server-side, but we strip it
103
+ // anyway so the wire request stays minimal and any future server-side
104
+ // change to the default branch can't surprise us.
105
+ const platform = this.getNodeParameter('platform', _i, '');
106
+ const qs = {};
107
+ if (platform)
108
+ qs.platform = platform;
109
+ const response = await influticsApiRequest.call(this, 'GET', `/v1/bloggers/by-username/${encodeURIComponent(username)}`, undefined, Object.keys(qs).length > 0 ? qs : undefined);
110
+ return response;
111
+ },
112
+ };
113
+ export async function executeInfluticsBlogger(_items) {
114
+ const operation = this.getNodeParameter('operation', 0);
115
+ const handler = OPERATIONS[operation];
116
+ if (!handler) {
117
+ throw new NodeOperationError(this.getNode(), `Operation "${operation}" not yet implemented in InfluticsBlogger node`);
118
+ }
119
+ // All three ops are single-batch (one creator per workflow run regardless of
120
+ // input item count). Mirrors the Track videos pattern from InfluticsVideo.
121
+ const response = await handler.call(this, 0);
122
+ return [[{ json: response }]];
123
+ }
124
+ export class InfluticsBlogger {
125
+ description = {
126
+ displayName: 'Influtics Blogger',
127
+ name: 'influticsBlogger',
128
+ icon: 'file:influtics.svg',
129
+ group: ['transform'],
130
+ version: 1,
131
+ description: 'Track and read Influtics bloggers (creators)',
132
+ defaults: { name: 'Influtics Blogger' },
133
+ inputs: ['main'],
134
+ outputs: ['main'],
135
+ credentials: [{ name: 'influticsApi', required: true }],
136
+ properties: [
137
+ {
138
+ displayName: 'Operation',
139
+ name: 'operation',
140
+ type: 'options',
141
+ noDataExpression: true,
142
+ // Alphabetized per eslint-plugin-n8n-nodes-base `node-param-options-type-unsorted-items`.
143
+ // Each option carries an `action:` (rule `node-param-operation-option-action-miscased`).
144
+ options: [
145
+ {
146
+ name: 'By Username',
147
+ value: 'byUsername',
148
+ description: 'Read a tracked creator by platform + username (read-only)',
149
+ action: 'Read a tracked creator by platform + username',
150
+ },
151
+ {
152
+ name: 'Get Job',
153
+ value: 'getJob',
154
+ description: 'Poll the status of a track-creator job by ID',
155
+ action: 'Poll the status of a track creator job by id',
156
+ },
157
+ {
158
+ name: 'Track',
159
+ value: 'track',
160
+ description: 'Start tracking a creator (async — returns job_id to poll)',
161
+ action: 'Start tracking a creator',
162
+ },
163
+ ],
164
+ default: 'track',
165
+ },
166
+ // --- Track ---------------------------------------------------------
167
+ {
168
+ displayName: 'Platform',
169
+ name: 'platform',
170
+ type: 'options',
171
+ displayOptions: { show: { operation: ['track'] } },
172
+ options: [
173
+ { name: 'TikTok', value: 'tiktok' },
174
+ { name: 'Instagram', value: 'instagram' },
175
+ { name: 'YouTube', value: 'youtube' },
176
+ { name: 'VK', value: 'vk' },
177
+ ],
178
+ default: 'tiktok',
179
+ required: true,
180
+ description: 'Platform to track the creator on',
181
+ },
182
+ {
183
+ displayName: 'Username',
184
+ name: 'username',
185
+ type: 'string',
186
+ displayOptions: { show: { operation: ['track'] } },
187
+ default: '',
188
+ required: true,
189
+ description: 'Creator username without the leading @ (max 64 chars)',
190
+ },
191
+ {
192
+ displayName: 'Initial Videos Count',
193
+ name: 'initialVideosCount',
194
+ type: 'number',
195
+ displayOptions: { show: { operation: ['track'] } },
196
+ // Backend (validateTrackBloggerBody) requires an integer in [1, 500].
197
+ // The UI clamps via minValue/maxValue; the executor clamps again
198
+ // for defense-in-depth against custom callers.
199
+ typeOptions: { minValue: 1, maxValue: 500 },
200
+ default: 10,
201
+ required: true,
202
+ description: 'Number of initial videos to backfill (1–500). Default: 10. Backend clamps to 500.',
203
+ },
204
+ // --- Get Job -------------------------------------------------------
205
+ {
206
+ displayName: 'Job ID',
207
+ name: 'jobId',
208
+ type: 'string',
209
+ displayOptions: { show: { operation: ['getJob'] } },
210
+ default: '',
211
+ required: true,
212
+ description: 'Job UUID returned by the Track operation',
213
+ },
214
+ // --- By Username ---------------------------------------------------
215
+ {
216
+ displayName: 'Username',
217
+ name: 'username',
218
+ type: 'string',
219
+ displayOptions: { show: { operation: ['byUsername'] } },
220
+ default: '',
221
+ required: true,
222
+ description: 'Creator username (with or without leading @)',
223
+ },
224
+ {
225
+ displayName: 'Platform',
226
+ name: 'platform',
227
+ type: 'options',
228
+ displayOptions: { show: { operation: ['byUsername'] } },
229
+ options: [
230
+ { name: 'TikTok', value: 'tiktok' },
231
+ { name: 'Instagram', value: 'instagram' },
232
+ { name: 'YouTube', value: 'youtube' },
233
+ { name: 'VK', value: 'vk' },
234
+ ],
235
+ // The backend defaults to `tiktok` when `platform` is absent or empty.
236
+ // Defaulting the UI to the same value keeps wire requests clean and
237
+ // matches what users would otherwise type.
238
+ default: 'tiktok',
239
+ required: true,
240
+ description: 'Platform the creator is on (defaults to tiktok server-side)',
241
+ },
242
+ ],
243
+ };
244
+ async execute() {
245
+ return executeInfluticsBlogger.call(this, this.getInputData());
246
+ }
247
+ }
248
+ //# sourceMappingURL=InfluticsBlogger.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfluticsBlogger.node.js","sourceRoot":"","sources":["../../../nodes/InfluticsBlogger/InfluticsBlogger.node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <rect width="60" height="60" rx="12" fill="#6266F0"/>
3
+ <text x="30" y="38" font-family="Helvetica,Arial,sans-serif" font-size="22" font-weight="700"
4
+ fill="#FFFFFF" text-anchor="middle">I</text>
5
+ </svg>
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Influtics Trend node.
3
+ *
4
+ * Implementation choices:
5
+ * - File lives at nodes/InfluticsTrend/InfluticsTrend.node.ts — required by the
6
+ * eslint-plugin-n8n-nodes-base `node-dirname-against-convention` rule.
7
+ * - `executeInfluticsTrend` is also exported as a named function so unit tests
8
+ * can drive the executor without instantiating the INodeType class.
9
+ * - One operation: Search. Tracks TikTok/YouTube trends by keyword. Tasks
10
+ * 4/5/6/7 ship the Video and Blogger siblings; this node (Task 8) adds
11
+ * the trends side.
12
+ * - The unimplemented-operation branch keeps the executor safe if a future
13
+ * version's parameters somehow leak an unknown value.
14
+ *
15
+ * Backend contract (verified against api-worker `trendsHandler.js`):
16
+ * GET /v1/trends/search
17
+ * query (REQUIRED):
18
+ * - keyword : string, non-empty after trim
19
+ * - platform : one of `tiktok`, `youtube`
20
+ * query (OPTIONAL):
21
+ * - cursor : pagination cursor returned by prior call
22
+ * - region : ISO 3166-1 alpha-2 country code (e.g. `US`, `DE`, `JP`)
23
+ * validated server-side with `/^[A-Za-z]{2}$/`
24
+ * - days : one of `0, 1, 7, 30, 90, 180` (0 = "no time window")
25
+ * Cost: 1 credit per call (free-tier callers receive 402 PAID_PLAN_REQUIRED).
26
+ * Errors: 400 VALIDATION_ERROR, 401 UNAUTHORIZED, 402 PAID_PLAN_REQUIRED,
27
+ * 429 RATE_LIMITED.
28
+ */
29
+ import { NodeOperationError, } from 'n8n-workflow';
30
+ import { influticsApiRequest } from '../GenericFunctions.js';
31
+ // Backend hard allow-list (mirrors `VALID_DAYS` in
32
+ // api-worker/src/handlers/trendsHandler.js). The UI exposes these as a
33
+ // dropdown so the user can't pick anything else; this is defense-in-depth
34
+ // against custom callers / future schema drift.
35
+ const VALID_DAYS = ['0', '1', '7', '30', '90', '180'];
36
+ const VALID_PLATFORMS = ['tiktok', 'youtube'];
37
+ // ISO 3166-1 alpha-2 — two letters. Matches the server-side regex.
38
+ const REGION_REGEX = /^[A-Za-z]{2}$/;
39
+ const OPERATIONS = {
40
+ search: async function (_i) {
41
+ const keyword = this.getNodeParameter('keyword', _i, '');
42
+ const platform = this.getNodeParameter('platform', _i, '');
43
+ const additionalFields = this.getNodeParameter('additionalFields', _i, {});
44
+ // Defensive guards BEFORE the API call. The backend rejects each of
45
+ // these with 400 VALIDATION_ERROR — fail fast with a clear UI message
46
+ // instead of letting the workflow silently 400.
47
+ if (!keyword || !keyword.trim()) {
48
+ throw new NodeOperationError(this.getNode(), 'Keyword is required');
49
+ }
50
+ if (!platform) {
51
+ throw new NodeOperationError(this.getNode(), 'Platform is required');
52
+ }
53
+ if (!VALID_PLATFORMS.includes(platform)) {
54
+ throw new NodeOperationError(this.getNode(), `Platform must be one of: ${VALID_PLATFORMS.join(', ')}`);
55
+ }
56
+ // Region is optional; if provided, must be a two-letter ISO 3166-1 code.
57
+ // Empty string is the "user didn't pick one" sentinel — skip the check.
58
+ if (additionalFields.region && !REGION_REGEX.test(additionalFields.region)) {
59
+ throw new NodeOperationError(this.getNode(), 'Region must be a two-letter ISO 3166-1 code (e.g. US, DE, JP)');
60
+ }
61
+ // Days is optional; if provided, must be in the allow-list. Empty string
62
+ // is the "user didn't pick one" sentinel — skip the check. Note that '0'
63
+ // IS a valid forward (the "no time window" option).
64
+ if (additionalFields.days && !VALID_DAYS.includes(additionalFields.days)) {
65
+ throw new NodeOperationError(this.getNode(), `Days must be one of: ${VALID_DAYS.join(', ')}`);
66
+ }
67
+ // Build the query-string explicitly — strip empty strings so the wire
68
+ // request stays minimal. Mirrors the InfluticsVideo.getStats filter
69
+ // pattern: only the keys the caller actually set go on the URL.
70
+ const qs = {
71
+ keyword: keyword.trim(),
72
+ platform,
73
+ };
74
+ if (additionalFields.cursor)
75
+ qs.cursor = additionalFields.cursor;
76
+ if (additionalFields.region)
77
+ qs.region = additionalFields.region;
78
+ if (additionalFields.days)
79
+ qs.days = additionalFields.days;
80
+ const response = await influticsApiRequest.call(this, 'GET', '/v1/trends/search', undefined, qs);
81
+ return response;
82
+ },
83
+ };
84
+ export async function executeInfluticsTrend(_items) {
85
+ const operation = this.getNodeParameter('operation', 0);
86
+ const handler = OPERATIONS[operation];
87
+ if (!handler) {
88
+ throw new NodeOperationError(this.getNode(), `Operation "${operation}" not yet implemented in InfluticsTrend node`);
89
+ }
90
+ // Search is a single-batch op: one GET per workflow run regardless of
91
+ // input item count. Mirrors the Track / Get Stats patterns.
92
+ const response = await handler.call(this, 0);
93
+ return [[{ json: response }]];
94
+ }
95
+ export class InfluticsTrend {
96
+ description = {
97
+ displayName: 'Influtics Trend',
98
+ name: 'influticsTrend',
99
+ icon: 'file:influtics.svg',
100
+ group: ['transform'],
101
+ version: 1,
102
+ description: 'Search Influtics trends by keyword',
103
+ defaults: { name: 'Influtics Trend' },
104
+ inputs: ['main'],
105
+ outputs: ['main'],
106
+ credentials: [{ name: 'influticsApi', required: true }],
107
+ properties: [
108
+ {
109
+ displayName: 'Operation',
110
+ name: 'operation',
111
+ type: 'options',
112
+ noDataExpression: true,
113
+ // Alphabetized per eslint-plugin-n8n-nodes-base `node-param-options-type-unsorted-items`.
114
+ // Each option carries an `action:` (rule `node-param-operation-option-action-miscased`).
115
+ options: [
116
+ {
117
+ name: 'Search',
118
+ value: 'search',
119
+ description: 'Search TikTok or YouTube trends by keyword',
120
+ // eslint-disable-next-line n8n-nodes-base/node-param-operation-option-action-miscased
121
+ action: 'Search TikTok or YouTube trends by keyword',
122
+ },
123
+ ],
124
+ default: 'search',
125
+ },
126
+ // --- Search ---------------------------------------------------------
127
+ {
128
+ displayName: 'Keyword',
129
+ name: 'keyword',
130
+ // eslint-disable-next-line n8n-nodes-base/cred-class-field-type-options-password-missing
131
+ type: 'string',
132
+ displayOptions: { show: { operation: ['search'] } },
133
+ default: '',
134
+ required: true,
135
+ description: 'Keyword to search trends for (will be trimmed before sending)',
136
+ },
137
+ {
138
+ displayName: 'Platform',
139
+ name: 'platform',
140
+ type: 'options',
141
+ displayOptions: { show: { operation: ['search'] } },
142
+ options: [
143
+ { name: 'TikTok', value: 'tiktok' },
144
+ { name: 'YouTube', value: 'youtube' },
145
+ ],
146
+ // The UI exposes both valid platforms; the backend rejects everything
147
+ // else with 400 VALIDATION_ERROR. Default tiktok to match the rest of
148
+ // the package's defaults (InfluticsVideo, InfluticsBlogger).
149
+ default: 'tiktok',
150
+ required: true,
151
+ description: 'Platform to search trends on',
152
+ },
153
+ {
154
+ displayName: 'Additional Options',
155
+ name: 'additionalFields',
156
+ type: 'collection',
157
+ default: {},
158
+ placeholder: 'Add Option',
159
+ options: [
160
+ {
161
+ displayName: 'Cursor',
162
+ name: 'cursor',
163
+ type: 'string',
164
+ default: '',
165
+ description: 'Pagination cursor returned by a prior search call',
166
+ },
167
+ {
168
+ displayName: 'Region',
169
+ name: 'region',
170
+ type: 'string',
171
+ default: '',
172
+ description: 'ISO 3166-1 alpha-2 country code (e.g. US, DE, JP)',
173
+ },
174
+ {
175
+ displayName: 'Days',
176
+ name: 'days',
177
+ type: 'options',
178
+ // Backend VALID_DAYS = [0, 1, 7, 30, 90, 180]. The empty option is
179
+ // the "user didn't pick one" sentinel — the handler skips it so
180
+ // the server applies its own default.
181
+ options: [
182
+ { name: 'No Window', value: '0' },
183
+ { name: '1 Day', value: '1' },
184
+ { name: '7 Days', value: '7' },
185
+ { name: '30 Days', value: '30' },
186
+ { name: '90 Days', value: '90' },
187
+ { name: '180 Days', value: '180' },
188
+ ],
189
+ default: '0',
190
+ description: 'Time window for the trend search (0 = no window)',
191
+ },
192
+ ],
193
+ },
194
+ ],
195
+ };
196
+ async execute() {
197
+ return executeInfluticsTrend.call(this, this.getInputData());
198
+ }
199
+ }
200
+ //# sourceMappingURL=InfluticsTrend.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfluticsTrend.node.js","sourceRoot":"","sources":["../../../nodes/InfluticsTrend/InfluticsTrend.node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <rect width="60" height="60" rx="12" fill="#6266F0"/>
3
+ <text x="30" y="38" font-family="Helvetica,Arial,sans-serif" font-size="22" font-weight="700"
4
+ fill="#FFFFFF" text-anchor="middle">I</text>
5
+ </svg>
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Influtics Video node.
3
+ *
4
+ * Implementation choices:
5
+ * - File lives at nodes/InfluticsVideo/InfluticsVideo.node.ts — required by the
6
+ * eslint-plugin-n8n-nodes-base `node-dirname-against-convention` rule.
7
+ * - `executeInfluticsVideo` is also exported as a named function so unit tests
8
+ * can drive the executor without instantiating the INodeType class.
9
+ * - Tasks 4/5/6 ship Track + Get Stats + Get/Update by ID/External ID.
10
+ * - The unimplemented-operation branch keeps the executor safe if a future
11
+ * version's parameters somehow leak an unknown value.
12
+ */
13
+ import { NodeOperationError, } from 'n8n-workflow';
14
+ import { influticsApiRequest } from '../GenericFunctions.js';
15
+ // Hard cap on paginated pages for `returnAll` to keep a runaway cursor from
16
+ // DOSing the workflow. Each page is `limit` items (≤ 100) so 50 pages = 5000
17
+ // items worst case — comfortably above the 1000-row PostgREST cap.
18
+ const PAGINATION_MAX_PAGES = 50;
19
+ const OPERATIONS = {
20
+ track: async function (_i) {
21
+ const urlsParam = this.getNodeParameter('urls', _i);
22
+ if (!Array.isArray(urlsParam.urls) || urlsParam.urls.length === 0) {
23
+ throw new NodeOperationError(this.getNode(), 'Provide at least one video URL');
24
+ }
25
+ const response = await influticsApiRequest.call(this, 'POST', '/v1/videos/track', { urls: urlsParam.urls });
26
+ return response;
27
+ },
28
+ getStats: async function (i) {
29
+ // Build the filter object only from values the caller actually set, so an
30
+ // unfiltered query goes out as `?` with no params (and the API applies its
31
+ // own server-side defaults). Empty string → omit.
32
+ //
33
+ // The real `/v1/videos/stats` endpoint (see api-worker `handleGetVideoStats`)
34
+ // accepts ONLY these query params: `limit`, `offset`, `platform` (single),
35
+ // `status` (single), `blogger_username` (single), `sort`, `order`. The UI
36
+ // mirrors that set exactly — campaign / search / published_from / etc. are
37
+ // not part of the contract and would silently be ignored, so they are not
38
+ // exposed.
39
+ const platform = this.getNodeParameter('platform', i, '');
40
+ const status = this.getNodeParameter('status', i, '');
41
+ const bloggerUsername = this.getNodeParameter('blogger_username', i, '');
42
+ const sort = this.getNodeParameter('sort', i, '');
43
+ const order = this.getNodeParameter('order', i, '');
44
+ const returnAll = this.getNodeParameter('returnAll', i, false);
45
+ // `limit` is only meaningful when `returnAll` is false; the `returnAll`
46
+ // paginator walks `has_more` and owns the page count, so a limit there would
47
+ // just be ignored (or worse, fight the page walk). The UI hides this field
48
+ // under the same `returnAll: [false]` guard so callers won't set it for the
49
+ // `returnAll` branch, but we still gate here for safety.
50
+ const limitRaw = this.getNodeParameter('limit', i, 50);
51
+ const limitRawNum = typeof limitRaw === 'number' && limitRaw > 0 ? limitRaw : 50;
52
+ // The API hard-caps `limit` at 100. The UI also clamps via
53
+ // `typeOptions.maxValue = 100`, but a custom caller or a future schema
54
+ // regression could still pass >100 here — coerce defensively so we never
55
+ // send a value the server will silently truncate.
56
+ const limit = Math.min(limitRawNum, 100);
57
+ const baseQs = {};
58
+ if (platform)
59
+ baseQs.platform = platform;
60
+ if (status)
61
+ baseQs.status = status;
62
+ if (bloggerUsername)
63
+ baseQs.blogger_username = bloggerUsername;
64
+ if (sort)
65
+ baseQs.sort = sort;
66
+ if (order)
67
+ baseQs.order = order;
68
+ if (!returnAll)
69
+ baseQs.limit = limit;
70
+ // The cursor-aware `influticsApiRequestAllItems` helper reads
71
+ // `meta.next_cursor`, which the real `/v1/videos/stats` endpoint never
72
+ // returns — it paginates via `offset` + a `has_more` boolean on
73
+ // `data.pagination`. Inline the walk here so we honour the actual contract.
74
+ if (returnAll) {
75
+ const collected = [];
76
+ const pageSize = typeof limit === 'number' && limit > 0 ? limit : 50;
77
+ let offset = 0;
78
+ for (let page = 0; page < PAGINATION_MAX_PAGES; page++) {
79
+ const pageQs = { ...baseQs, limit: pageSize, offset };
80
+ const response = await influticsApiRequest.call(this, 'GET', '/v1/videos/stats', undefined, pageQs);
81
+ // Items live at response.data.data; the success envelope puts the
82
+ // payload under `data` and the API wraps its array under another
83
+ // `data` key. Be tolerant: if the API ever drops the inner wrapper,
84
+ // accept `data` as the array directly.
85
+ const items = Array.isArray(response?.data?.data)
86
+ ? response.data.data
87
+ : Array.isArray(response?.data)
88
+ ? response.data
89
+ : [];
90
+ for (const item of items) {
91
+ if (item && typeof item === 'object') {
92
+ collected.push(item);
93
+ }
94
+ }
95
+ const pagination = response?.data?.pagination;
96
+ const hasMore = !!pagination?.has_more;
97
+ if (!hasMore)
98
+ break;
99
+ offset += pageSize;
100
+ }
101
+ // Track returns the raw envelope; mirror that here so the downstream
102
+ // n8n consumer sees the same shape from both batch ops.
103
+ return { data: collected };
104
+ }
105
+ return (await influticsApiRequest.call(this, 'GET', '/v1/videos/stats', undefined, baseQs));
106
+ },
107
+ // --- Task 6: single-resource lookups + patch -------------------------------
108
+ // Backend contract (see api-worker handlers in index.js):
109
+ // GET /v1/videos/by-id/{id}
110
+ // GET /v1/videos/by-external-id/{externalId}
111
+ // PATCH /v1/videos/by-external-id/{externalId}
112
+ // body: at least one of notes, budget, campaign, video_status, status, tags
113
+ // None of these read query params — the (organization_id, external_video_id)
114
+ // partial unique index scopes the row, so platform on the wire is cruft.
115
+ // The UI keeps the `platform` dropdown as a user-facing hint (it's marked
116
+ // `required: true` for `getByExternalId` / `updateByExternalId`), but the
117
+ // executor does not forward it.
118
+ getById: async function (i) {
119
+ const id = this.getNodeParameter('id', i, '');
120
+ if (!id) {
121
+ // Fail fast: without an id we'd send `GET /v1/videos/by-id/` which 404s
122
+ // with a confusing URL and no actionable error.
123
+ throw new NodeOperationError(this.getNode(), 'Video ID is required');
124
+ }
125
+ const response = await influticsApiRequest.call(this, 'GET', `/v1/videos/by-id/${encodeURIComponent(id)}`);
126
+ return response;
127
+ },
128
+ getByExternalId: async function (i) {
129
+ const externalId = this.getNodeParameter('externalId', i, '');
130
+ if (!externalId) {
131
+ throw new NodeOperationError(this.getNode(), 'External ID is required');
132
+ }
133
+ // Defensive: backend ignores platform, but UI marks it `required: true`.
134
+ // A workflow that somehow arrived here with an empty platform is broken
135
+ // — fail loudly instead of silently sending a request the backend ignores.
136
+ const platform = this.getNodeParameter('platform', i, '');
137
+ if (!platform) {
138
+ throw new NodeOperationError(this.getNode(), 'Platform is required');
139
+ }
140
+ const response = await influticsApiRequest.call(this, 'GET', `/v1/videos/by-external-id/${encodeURIComponent(externalId)}`);
141
+ return response;
142
+ },
143
+ updateByExternalId: async function (i) {
144
+ const externalId = this.getNodeParameter('externalId', i, '');
145
+ if (!externalId) {
146
+ throw new NodeOperationError(this.getNode(), 'External ID is required');
147
+ }
148
+ const platform = this.getNodeParameter('platform', i, '');
149
+ if (!platform) {
150
+ throw new NodeOperationError(this.getNode(), 'Platform is required');
151
+ }
152
+ const updateFields = this.getNodeParameter('updateFields', i, {});
153
+ // Backend (handlePatchVideoByExternalId) accepts any of: notes, budget,
154
+ // campaign, video_status, status, tags. The UI exposes a subset (notes,
155
+ // campaign, status, tags). Coerce defensively:
156
+ // - string fields: include only when non-empty (so we never send
157
+ // `campaign: ""` which the backend would silently drop and mask intent)
158
+ // - tags: include only when it's a non-empty array
159
+ // An empty body would 400 server-side; surface that here as a clear
160
+ // NodeOperationError instead.
161
+ const body = {};
162
+ if (typeof updateFields.notes === 'string' && updateFields.notes.length > 0) {
163
+ body.notes = updateFields.notes;
164
+ }
165
+ if (typeof updateFields.campaign === 'string' && updateFields.campaign.length > 0) {
166
+ body.campaign = updateFields.campaign;
167
+ }
168
+ if (typeof updateFields.status === 'string' && updateFields.status.length > 0) {
169
+ body.status = updateFields.status;
170
+ }
171
+ if (Array.isArray(updateFields.tags) && updateFields.tags.length > 0) {
172
+ body.tags = updateFields.tags;
173
+ }
174
+ if (Object.keys(body).length === 0) {
175
+ throw new NodeOperationError(this.getNode(), 'Provide at least one update field (notes, campaign, status, or tags)');
176
+ }
177
+ const response = await influticsApiRequest.call(this, 'PATCH', `/v1/videos/by-external-id/${encodeURIComponent(externalId)}`, body);
178
+ return response;
179
+ },
180
+ };
181
+ export async function executeInfluticsVideo(_items) {
182
+ const operation = this.getNodeParameter('operation', 0);
183
+ const handler = OPERATIONS[operation];
184
+ if (!handler) {
185
+ throw new NodeOperationError(this.getNode(), `Operation "${operation}" not yet implemented in InfluticsVideo node`);
186
+ }
187
+ // Track is a single batch op; one call per workflow run regardless of input item count.
188
+ const response = await handler.call(this, 0);
189
+ return [[{ json: response }]];
190
+ }
191
+ export class InfluticsVideo {
192
+ description = {
193
+ displayName: 'Influtics Video',
194
+ name: 'influticsVideo',
195
+ icon: 'file:influtics.svg',
196
+ group: ['transform'],
197
+ version: 1,
198
+ description: 'Track and read Influtics videos',
199
+ defaults: { name: 'Influtics Video' },
200
+ inputs: ['main'],
201
+ outputs: ['main'],
202
+ credentials: [{ name: 'influticsApi', required: true }],
203
+ properties: [
204
+ {
205
+ displayName: 'Operation',
206
+ name: 'operation',
207
+ type: 'options',
208
+ noDataExpression: true,
209
+ options: [
210
+ {
211
+ name: 'Get By External ID',
212
+ value: 'getByExternalId',
213
+ description: 'Read one tracked video by platform + external ID',
214
+ action: 'Read one tracked video by platform + external ID',
215
+ },
216
+ {
217
+ name: 'Get By ID',
218
+ value: 'getById',
219
+ description: 'Read one tracked video by internal ID',
220
+ action: 'Read one tracked video by internal ID',
221
+ },
222
+ {
223
+ name: 'Get Stats',
224
+ value: 'getStats',
225
+ description: 'Read video-level metrics',
226
+ action: 'Read video level metrics',
227
+ },
228
+ {
229
+ name: 'Track',
230
+ value: 'track',
231
+ description: 'Track videos by URL',
232
+ action: 'Track videos by URL',
233
+ },
234
+ {
235
+ name: 'Update By External ID',
236
+ value: 'updateByExternalId',
237
+ description: 'Patch metadata on a tracked video',
238
+ action: 'Patch metadata on a tracked video',
239
+ },
240
+ ],
241
+ default: 'track',
242
+ },
243
+ // --- Track --------------------------------------------------------------
244
+ {
245
+ displayName: 'URLs',
246
+ name: 'urls',
247
+ type: 'collection',
248
+ displayOptions: { show: { operation: ['track'] } },
249
+ default: {},
250
+ options: [
251
+ {
252
+ displayName: 'URLs',
253
+ name: 'urls',
254
+ type: 'string',
255
+ typeOptions: { multipleValues: true },
256
+ default: [],
257
+ description: 'Up to 50 video URLs to track',
258
+ },
259
+ ],
260
+ },
261
+ // --- Get Stats ----------------------------------------------------------
262
+ {
263
+ displayName: 'Return All',
264
+ name: 'returnAll',
265
+ type: 'boolean',
266
+ displayOptions: { show: { operation: ['getStats'] } },
267
+ default: false,
268
+ description: 'Whether to return all results or only up to a given limit',
269
+ },
270
+ {
271
+ displayName: 'Limit',
272
+ name: 'limit',
273
+ type: 'number',
274
+ displayOptions: { show: { operation: ['getStats'], returnAll: [false] } },
275
+ // The API hard-caps `limit` at 100. The handler defensively clamps via
276
+ // Math.min(limit, 100) so the value can never be >100 on the wire,
277
+ // even if a custom caller bypasses the UI's `minValue` guard.
278
+ typeOptions: { minValue: 1 },
279
+ default: 50,
280
+ description: 'Max number of results to return',
281
+ },
282
+ {
283
+ displayName: 'Platform',
284
+ name: 'platform',
285
+ type: 'options',
286
+ displayOptions: { show: { operation: ['getStats'] } },
287
+ options: [
288
+ { name: 'TikTok', value: 'tiktok' },
289
+ { name: 'Instagram', value: 'instagram' },
290
+ { name: 'YouTube', value: 'youtube' },
291
+ { name: 'VK', value: 'vk' },
292
+ ],
293
+ default: 'tiktok',
294
+ description: 'Filter by a single platform',
295
+ },
296
+ {
297
+ displayName: 'Status',
298
+ name: 'status',
299
+ type: 'options',
300
+ displayOptions: { show: { operation: ['getStats'] } },
301
+ options: [
302
+ { name: 'Active', value: 'active' },
303
+ { name: 'Completed', value: 'completed' },
304
+ { name: 'Failed', value: 'failed' },
305
+ ],
306
+ default: 'active',
307
+ description: 'Filter by tracking status',
308
+ },
309
+ {
310
+ displayName: 'Blogger Username',
311
+ name: 'blogger_username',
312
+ type: 'string',
313
+ displayOptions: { show: { operation: ['getStats'] } },
314
+ default: '',
315
+ description: 'Filter by a single blogger username',
316
+ },
317
+ {
318
+ displayName: 'Sort',
319
+ name: 'sort',
320
+ type: 'string',
321
+ displayOptions: { show: { operation: ['getStats'] } },
322
+ default: 'created_at',
323
+ description: 'Field to sort by. Allowed: created_at, views, likes, updated_at. Default: created_at',
324
+ },
325
+ {
326
+ displayName: 'Order',
327
+ name: 'order',
328
+ type: 'options',
329
+ displayOptions: { show: { operation: ['getStats'] } },
330
+ options: [
331
+ { name: 'Ascending', value: 'asc' },
332
+ { name: 'Descending', value: 'desc' },
333
+ ],
334
+ default: 'desc',
335
+ description: 'Sort order',
336
+ },
337
+ // --- Get By ID ----------------------------------------------------------
338
+ {
339
+ displayName: 'Video ID',
340
+ name: 'id',
341
+ type: 'string',
342
+ displayOptions: { show: { operation: ['getById'] } },
343
+ default: '',
344
+ required: true,
345
+ },
346
+ // --- Get By External ID / Update By External ID ------------------------
347
+ {
348
+ displayName: 'External ID',
349
+ name: 'externalId',
350
+ type: 'string',
351
+ displayOptions: { show: { operation: ['getByExternalId', 'updateByExternalId'] } },
352
+ default: '',
353
+ required: true,
354
+ description: 'The platform-specific video ID (e.g. TikTok video ID)',
355
+ },
356
+ {
357
+ displayName: 'Platform',
358
+ name: 'platform',
359
+ type: 'options',
360
+ displayOptions: { show: { operation: ['getByExternalId', 'updateByExternalId'] } },
361
+ options: [
362
+ { name: 'TikTok', value: 'tiktok' },
363
+ { name: 'Instagram', value: 'instagram' },
364
+ { name: 'YouTube', value: 'youtube' },
365
+ { name: 'VK', value: 'vk' },
366
+ ],
367
+ default: 'tiktok',
368
+ required: true,
369
+ },
370
+ // --- Update By External ID body fields ---------------------------------
371
+ {
372
+ displayName: 'Update Fields',
373
+ name: 'updateFields',
374
+ type: 'collection',
375
+ displayOptions: { show: { operation: ['updateByExternalId'] } },
376
+ default: {},
377
+ options: [
378
+ { displayName: 'Notes', name: 'notes', type: 'string', default: '' },
379
+ { displayName: 'Campaign', name: 'campaign', type: 'string', default: '' },
380
+ {
381
+ displayName: 'Status',
382
+ name: 'status',
383
+ type: 'options',
384
+ options: [
385
+ // Sentinel: empty value means "no change" so untouched status
386
+ // doesn't clobber existing workflow state. The handler drops
387
+ // empty strings from the body.
388
+ { name: 'No Change', value: '' },
389
+ { name: 'To Do', value: 'to do' },
390
+ { name: 'Running', value: 'running' },
391
+ { name: 'Ended', value: 'ended' },
392
+ ],
393
+ default: '',
394
+ },
395
+ {
396
+ displayName: 'Tags',
397
+ name: 'tags',
398
+ type: 'string',
399
+ typeOptions: { multipleValues: true },
400
+ default: [],
401
+ description: 'Tag names to attach (existing tags are preserved)',
402
+ },
403
+ ],
404
+ },
405
+ ],
406
+ };
407
+ async execute() {
408
+ return executeInfluticsVideo.call(this, this.getInputData());
409
+ }
410
+ }
411
+ //# sourceMappingURL=InfluticsVideo.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfluticsVideo.node.js","sourceRoot":"","sources":["../../../nodes/InfluticsVideo/InfluticsVideo.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,kBAAkB,GAMnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAU7D,4EAA4E;AAC5E,6EAA6E;AAC7E,mEAAmE;AACnE,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,MAAM,UAAU,GAAqC;IACnD,KAAK,EAAE,KAAK,WAAoC,EAAU;QACxD,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,CAAuB,CAAC;QAC1E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,gCAAgC,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAC7C,IAAI,EACJ,MAAM,EACN,kBAAkB,EAClB,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAiB,CACxC,CAAC;QACF,OAAO,QAAuB,CAAC;IACjC,CAAC;IACD,QAAQ,EAAE,KAAK,WAAoC,CAAS;QAC1D,0EAA0E;QAC1E,2EAA2E;QAC3E,kDAAkD;QAClD,EAAE;QACF,8EAA8E;QAC9E,2EAA2E;QAC3E,0EAA0E;QAC1E,2EAA2E;QAC3E,0EAA0E;QAC1E,WAAW;QACX,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QACpE,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QAChE,MAAM,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QACnF,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,EAAE,KAAK,CAAY,CAAC;QAC1E,wEAAwE;QACxE,6EAA6E;QAC7E,2EAA2E;QAC3E,4EAA4E;QAC5E,yDAAyD;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAY,CAAC;QAClE,MAAM,WAAW,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,2DAA2D;QAC3D,uEAAuE;QACvE,yEAAyE;QACzE,kDAAkD;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAEzC,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,IAAI,QAAQ;YAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzC,IAAI,MAAM;YAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACnC,IAAI,eAAe;YAAE,MAAM,CAAC,gBAAgB,GAAG,eAAe,CAAC;QAC/D,IAAI,IAAI;YAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QAC7B,IAAI,KAAK;YAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAChC,IAAI,CAAC,SAAS;YAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAErC,8DAA8D;QAC9D,uEAAuE;QACvE,gEAAgE;QAChE,4EAA4E;QAC5E,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,SAAS,GAAkB,EAAE,CAAC;YACpC,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,oBAAoB,EAAE,IAAI,EAAE,EAAE,CAAC;gBACvD,MAAM,MAAM,GAAgB,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACnE,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAC7C,IAAI,EACJ,KAAK,EACL,kBAAkB,EAClB,SAAS,EACT,MAAM,CACP,CAAC;gBACF,kEAAkE;gBAClE,iEAAiE;gBACjE,oEAAoE;gBACpE,uCAAuC;gBACvC,MAAM,KAAK,GAAc,KAAK,CAAC,OAAO,CAAE,QAAgB,EAAE,IAAI,EAAE,IAAI,CAAC;oBACnE,CAAC,CAAG,QAAgB,CAAC,IAAI,CAAC,IAAkB;oBAC5C,CAAC,CAAC,KAAK,CAAC,OAAO,CAAE,QAAgB,EAAE,IAAI,CAAC;wBACtC,CAAC,CAAG,QAAgB,CAAC,IAAkB;wBACvC,CAAC,CAAC,EAAE,CAAC;gBACT,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;wBACrC,SAAS,CAAC,IAAI,CAAC,IAAmB,CAAC,CAAC;oBACtC,CAAC;gBACH,CAAC;gBACD,MAAM,UAAU,GAAI,QAAgB,EAAE,IAAI,EAAE,UAAU,CAAC;gBACvD,MAAM,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC;gBACvC,IAAI,CAAC,OAAO;oBAAE,MAAM;gBACpB,MAAM,IAAI,QAAQ,CAAC;YACrB,CAAC;YACD,qEAAqE;YACrE,wDAAwD;YACxD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAiB,CAAC;QAC5C,CAAC;QAED,OAAO,CAAC,MAAM,mBAAmB,CAAC,IAAI,CACpC,IAAI,EACJ,KAAK,EACL,kBAAkB,EAClB,SAAS,EACT,MAAM,CACP,CAAgB,CAAC;IACpB,CAAC;IACD,8EAA8E;IAC9E,0DAA0D;IAC1D,iCAAiC;IACjC,kDAAkD;IAClD,kDAAkD;IAClD,gFAAgF;IAChF,+EAA+E;IAC/E,2EAA2E;IAC3E,4EAA4E;IAC5E,4EAA4E;IAC5E,kCAAkC;IAClC,OAAO,EAAE,KAAK,WAAoC,CAAS;QACzD,MAAM,EAAE,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QACxD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,wEAAwE;YACxE,gDAAgD;YAChD,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,sBAAsB,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAC7C,IAAI,EACJ,KAAK,EACL,oBAAoB,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAC7C,CAAC;QACF,OAAO,QAAuB,CAAC;IACjC,CAAC;IACD,eAAe,EAAE,KAAK,WAAoC,CAAS;QACjE,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QACxE,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,yBAAyB,CAAC,CAAC;QAC1E,CAAC;QACD,yEAAyE;QACzE,wEAAwE;QACxE,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,sBAAsB,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAC7C,IAAI,EACJ,KAAK,EACL,6BAA6B,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAC9D,CAAC;QACF,OAAO,QAAuB,CAAC;IACjC,CAAC;IACD,kBAAkB,EAAE,KAAK,WAAoC,CAAS;QACpE,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QACxE,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,yBAAyB,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAW,CAAC;QACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,sBAAsB,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CACxC,cAAc,EACd,CAAC,EACD,EAA6E,CACH,CAAC;QAE7E,wEAAwE;QACxE,wEAAwE;QACxE,+CAA+C;QAC/C,mEAAmE;QACnE,4EAA4E;QAC5E,qDAAqD;QACrD,oEAAoE;QACpE,8BAA8B;QAC9B,MAAM,IAAI,GAAgB,EAAE,CAAC;QAC7B,IAAI,OAAO,YAAY,CAAC,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5E,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC;QAClC,CAAC;QACD,IAAI,OAAO,YAAY,CAAC,QAAQ,KAAK,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClF,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QACxC,CAAC;QACD,IAAI,OAAO,YAAY,CAAC,MAAM,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9E,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;QACpC,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrE,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC;QAChC,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,kBAAkB,CAC1B,IAAI,CAAC,OAAO,EAAE,EACd,sEAAsE,CACvE,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAC7C,IAAI,EACJ,OAAO,EACP,6BAA6B,kBAAkB,CAAC,UAAU,CAAC,EAAE,EAC7D,IAAI,CACL,CAAC;QACF,OAAO,QAAuB,CAAC;IACjC,CAAC;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAEzC,MAA4B;IAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CAAW,CAAC;IAClE,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,kBAAkB,CAC1B,IAAI,CAAC,OAAO,EAAE,EACd,cAAc,SAAS,8CAA8C,CACtE,CAAC;IACJ,CAAC;IACD,wFAAwF;IACxF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,OAAO,cAAc;IACzB,WAAW,GAAyB;QAClC,WAAW,EAAE,iBAAiB;QAC9B,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,CAAC,WAAW,CAAC;QACpB,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,iCAAiC;QAC9C,QAAQ,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE;QACrC,MAAM,EAAE,CAAC,MAAM,CAAC;QAChB,OAAO,EAAE,CAAC,MAAM,CAAC;QACjB,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACvD,UAAU,EAAE;YACV;gBACE,WAAW,EAAE,WAAW;gBACxB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,SAAS;gBACf,gBAAgB,EAAE,IAAI;gBACtB,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,oBAAoB;wBAC1B,KAAK,EAAE,iBAAiB;wBACxB,WAAW,EAAE,kDAAkD;wBAC/D,MAAM,EAAE,kDAAkD;qBAC3D;oBACD;wBACE,IAAI,EAAE,WAAW;wBACjB,KAAK,EAAE,SAAS;wBAChB,WAAW,EAAE,uCAAuC;wBACpD,MAAM,EAAE,uCAAuC;qBAChD;oBACD;wBACE,IAAI,EAAE,WAAW;wBACjB,KAAK,EAAE,UAAU;wBACjB,WAAW,EAAE,0BAA0B;wBACvC,MAAM,EAAE,0BAA0B;qBACnC;oBACD;wBACE,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,OAAO;wBACd,WAAW,EAAE,qBAAqB;wBAClC,MAAM,EAAE,qBAAqB;qBAC9B;oBACD;wBACE,IAAI,EAAE,uBAAuB;wBAC7B,KAAK,EAAE,oBAAoB;wBAC3B,WAAW,EAAE,mCAAmC;wBAChD,MAAM,EAAE,mCAAmC;qBAC5C;iBACF;gBACD,OAAO,EAAE,OAAO;aACjB;YACD,2EAA2E;YAC3E;gBACE,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,YAAY;gBAClB,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE;gBAClD,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE;oBACP;wBACE,WAAW,EAAE,MAAM;wBACnB,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE;wBACrC,OAAO,EAAE,EAAE;wBACX,WAAW,EAAE,8BAA8B;qBAC5C;iBACF;aACF;YACD,2EAA2E;YAC3E;gBACE,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,SAAS;gBACf,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE;gBACrD,OAAO,EAAE,KAAK;gBACd,WAAW,EAAE,2DAA2D;aACzE;YACD;gBACE,WAAW,EAAE,OAAO;gBACpB,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,QAAQ;gBACd,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE;gBACzE,uEAAuE;gBACvE,mEAAmE;gBACnE,8DAA8D;gBAC9D,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;gBAC5B,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,iCAAiC;aAC/C;YACD;gBACE,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,SAAS;gBACf,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE;gBACrD,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACnC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;oBACzC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACrC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;iBAC5B;gBACD,OAAO,EAAE,QAAQ;gBACjB,WAAW,EAAE,6BAA6B;aAC3C;YACD;gBACE,WAAW,EAAE,QAAQ;gBACrB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,SAAS;gBACf,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE;gBACrD,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACnC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;oBACzC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;iBACpC;gBACD,OAAO,EAAE,QAAQ;gBACjB,WAAW,EAAE,2BAA2B;aACzC;YACD;gBACE,WAAW,EAAE,kBAAkB;gBAC/B,IAAI,EAAE,kBAAkB;gBACxB,IAAI,EAAE,QAAQ;gBACd,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE;gBACrD,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,qCAAqC;aACnD;YACD;gBACE,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE;gBACrD,OAAO,EAAE,YAAY;gBACrB,WAAW,EACT,sFAAsF;aACzF;YACD;gBACE,WAAW,EAAE,OAAO;gBACpB,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,SAAS;gBACf,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE;gBACrD,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE;oBACnC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE;iBACtC;gBACD,OAAO,EAAE,MAAM;gBACf,WAAW,EAAE,YAAY;aAC1B;YACD,2EAA2E;YAC3E;gBACE,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,IAAI;gBACV,IAAI,EAAE,QAAQ;gBACd,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE;gBACpD,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;aACf;YACD,0EAA0E;YAC1E;gBACE,WAAW,EAAE,aAAa;gBAC1B,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,QAAQ;gBACd,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,EAAE,EAAE;gBAClF,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,uDAAuD;aACrE;YACD;gBACE,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,SAAS;gBACf,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,EAAE,EAAE;gBAClF,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACnC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;oBACzC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACrC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;iBAC5B;gBACD,OAAO,EAAE,QAAQ;gBACjB,QAAQ,EAAE,IAAI;aACf;YACD,0EAA0E;YAC1E;gBACE,WAAW,EAAE,eAAe;gBAC5B,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,YAAY;gBAClB,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,oBAAoB,CAAC,EAAE,EAAE;gBAC/D,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE;oBACP,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;oBACpE,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;oBAC1E;wBACE,WAAW,EAAE,QAAQ;wBACrB,IAAI,EAAE,QAAQ;wBACd,IAAI,EAAE,SAAS;wBACf,OAAO,EAAE;4BACP,8DAA8D;4BAC9D,6DAA6D;4BAC7D,+BAA+B;4BAC/B,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE;4BAChC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;4BACjC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;4BACrC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;yBAClC;wBACD,OAAO,EAAE,EAAE;qBACZ;oBACD;wBACE,WAAW,EAAE,MAAM;wBACnB,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE;wBACrC,OAAO,EAAE,EAAE;wBACX,WAAW,EAAE,mDAAmD;qBACjE;iBACF;aACF;SACF;KACF,CAAC;IAEF,KAAK,CAAC,OAAO;QACX,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/D,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <rect width="60" height="60" rx="12" fill="#6266F0"/>
3
+ <text x="30" y="38" font-family="Helvetica,Arial,sans-serif" font-size="22" font-weight="700"
4
+ fill="#FFFFFF" text-anchor="middle">I</text>
5
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-influtics",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Official Influtics integration for n8n — track influencer videos, bloggers, and trends",
5
5
  "author": {
6
6
  "name": "Influtics",
@@ -20,6 +20,7 @@
20
20
  "main": "dist/index.js",
21
21
  "files": ["dist"],
22
22
  "scripts": {
23
+ "prepublishOnly": "npm run build && npm run lint && npm test",
23
24
  "build": "n8n-node build",
24
25
  "dev": "n8n-node dev",
25
26
  "lint": "eslint . --ext .ts",