n8n-nodes-apivault-whatsapp-validator 0.2.0 → 0.3.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 +6 -39
- package/dist/credentials/ApifyApi.credentials.d.ts +4 -0
- package/dist/credentials/ApifyApi.credentials.js +20 -22
- package/dist/credentials/apivault.svg +1 -0
- package/dist/nodes/WhatsappValidator/WhatsappValidator.node.js +118 -84
- package/dist/nodes/helpers/actorTransport.d.ts +9 -0
- package/dist/nodes/helpers/actorTransport.js +155 -0
- package/package.json +16 -12
- package/dist/credentials/ApifyApi.credentials.js.map +0 -1
- package/dist/nodes/WhatsappValidator/WhatsappValidator.node.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,44 +1,11 @@
|
|
|
1
|
-
# n8n
|
|
1
|
+
# WhatsApp Number Validator — n8n node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Calls the hosted [Apify Actor](https://apify.com/apivault_labs/whatsapp-number-validator). Requires your own Apify credential; Apify run charges apply.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Install `n8n-nodes-apivault-whatsapp-validator` through Settings → Community nodes → Install in self-hosted n8n.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Upgrade compatibility
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Existing node names, credential type and input parameter names are retained. Existing version 1 workflows keep their previous charge behavior unless an event cap is selected. New version 2 nodes default to a $0.05 event charge cap and 1,000 downloaded rows; platform usage can be charged separately. Select Read Existing Run and provide the Run ID to resume without starting a paid duplicate. Full output includes run metadata, SUMMARY and ERRORS when available. Dataset rows retain their original fields and paired input item. Actor build is latest.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
2. Select **Install**.
|
|
13
|
-
3. Enter `n8n-nodes-apivault-whatsapp-validator` and confirm.
|
|
14
|
-
|
|
15
|
-
## Credentials
|
|
16
|
-
|
|
17
|
-
Create an **Apify API** credential in n8n and paste your personal token from [Apify Console → Integrations](https://console.apify.com/account/integrations). The token is sent to Apify as a bearer credential and is never bundled with this package.
|
|
18
|
-
|
|
19
|
-
## Usage
|
|
20
|
-
|
|
21
|
-
Add **WhatsApp Number Validator** to a workflow, fill the public Actor inputs below, and execute the node. Every Dataset result becomes one n8n item, so it can flow into Sheets, databases, CRMs, alerts or your own code. The node respects n8n's **Continue On Fail** behavior.
|
|
22
|
-
|
|
23
|
-
| Input | Type | Description |
|
|
24
|
-
|---|---|---|
|
|
25
|
-
| `workflow` | `string` | Choose how numbers are supplied. Auto merges both input fields and preserves existing API behavior. |
|
|
26
|
-
| `phoneNumbers` | `array` | Phone numbers to validate (one per line). Include the country code with a leading + when possible, e.g. +14155552671. Numbers without a + are parsed using the Default country below |
|
|
27
|
-
| `numbersText` | `string` | Optional. Paste many numbers at once (one per line, or comma/semicolon separated — e.g. a copied spreadsheet column). They are merged with the list above and de-duplicated. |
|
|
28
|
-
| `defaultCountry` | `string` | ISO 3166 alpha-2 region code (US, GB, BR, IN, DE, ...) used to interpret numbers that don't start with +. Leave empty to require + on every number. |
|
|
29
|
-
| `outputPreset` | `string` | Full is best for AI and APIs. CSV is flat for spreadsheets. Both keeps full JSON and adds a nested CSV-friendly record. |
|
|
30
|
-
| `exportFormat` | `string` | Backwards-compatible API option. Leave at default to use Output preset above. |
|
|
31
|
-
|
|
32
|
-
## Pricing
|
|
33
|
-
|
|
34
|
-
The package is free. Actor runs are billed by Apify using the pricing shown on the [Actor page](https://apify.com/apivault_labs/whatsapp-number-validator); platform usage may also apply.
|
|
35
|
-
|
|
36
|
-
## Resources
|
|
37
|
-
|
|
38
|
-
- [Actor and live input schema](https://apify.com/apivault_labs/whatsapp-number-validator)
|
|
39
|
-
- [Source repository](https://github.com/apivault-labs/n8n-nodes-apivault-whatsapp-validator)
|
|
40
|
-
- [n8n community-node documentation](https://docs.n8n.io/integrations/community-nodes/)
|
|
41
|
-
|
|
42
|
-
## License
|
|
43
|
-
|
|
44
|
-
MIT. The hosted Actor is a separate paid service governed by Apify terms.
|
|
11
|
+
Runtime dependencies: none. n8n Cloud availability requires separate n8n verification.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
2
|
export declare class ApifyApi implements ICredentialType {
|
|
3
3
|
name: string;
|
|
4
|
+
icon: {
|
|
5
|
+
readonly light: "file:apivault.svg";
|
|
6
|
+
readonly dark: "file:apivault.svg";
|
|
7
|
+
};
|
|
4
8
|
displayName: string;
|
|
5
9
|
documentationUrl: string;
|
|
6
10
|
properties: INodeProperties[];
|
|
@@ -2,27 +2,25 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ApifyApi = void 0;
|
|
4
4
|
class ApifyApi {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
5
|
+
name = 'apifyApi';
|
|
6
|
+
icon = { light: 'file:apivault.svg', dark: 'file:apivault.svg' };
|
|
7
|
+
displayName = 'Apify API';
|
|
8
|
+
documentationUrl = 'https://docs.apify.com/platform/integrations/api';
|
|
9
|
+
properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'API Token',
|
|
12
|
+
name: 'apiToken',
|
|
13
|
+
type: 'string',
|
|
14
|
+
typeOptions: { password: true },
|
|
15
|
+
default: '',
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Your personal Apify API token (Apify Console -> Settings -> Integrations).',
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
authenticate = {
|
|
21
|
+
type: 'generic',
|
|
22
|
+
properties: { headers: { Authorization: '=Bearer {{$credentials.apiToken}}' } },
|
|
23
|
+
};
|
|
24
|
+
test = { request: { baseURL: 'https://api.apify.com/v2', url: '/users/me' } };
|
|
26
25
|
}
|
|
27
26
|
exports.ApifyApi = ApifyApi;
|
|
28
|
-
//# sourceMappingURL=ApifyApi.credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#4f46e5"/><path d="M16 48 28 16h8l12 32H38l-2-7H26l-2 7zm12-15h6l-3-10z" fill="white"/></svg>
|
|
@@ -2,26 +2,47 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WhatsappValidator = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
-
const
|
|
5
|
+
const actorTransport_1 = require("../helpers/actorTransport");
|
|
6
|
+
const ACTOR_ID = 'LP2X15E8c4ebfnPRC';
|
|
6
7
|
class WhatsappValidator {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
description = {
|
|
9
|
+
"displayName": "WhatsApp Number Validator",
|
|
10
|
+
"name": "whatsappValidator",
|
|
11
|
+
"icon": {
|
|
12
|
+
"light": "file:whatsappvalidator.svg",
|
|
13
|
+
"dark": "file:whatsappvalidator.svg"
|
|
14
|
+
},
|
|
15
|
+
"subtitle": "={{$parameter[\"apivaultMode\"]}}",
|
|
16
|
+
"group": [
|
|
17
|
+
"transform"
|
|
18
|
+
],
|
|
19
|
+
"version": [
|
|
20
|
+
1,
|
|
21
|
+
2
|
|
22
|
+
],
|
|
23
|
+
"description": "Run WhatsApp Number Validator through the hosted Apify Actor and read its results",
|
|
24
|
+
"defaults": {
|
|
25
|
+
"name": "WhatsApp Number Validator"
|
|
26
|
+
},
|
|
27
|
+
"inputs": [
|
|
28
|
+
n8n_workflow_1.NodeConnectionTypes.Main
|
|
29
|
+
],
|
|
30
|
+
"outputs": [
|
|
31
|
+
n8n_workflow_1.NodeConnectionTypes.Main
|
|
32
|
+
],
|
|
33
|
+
"usableAsTool": true,
|
|
34
|
+
"credentials": [
|
|
35
|
+
{
|
|
36
|
+
"name": "apifyApi",
|
|
37
|
+
"required": true
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"defaultVersion": 2,
|
|
41
|
+
properties: [...actorTransport_1.executionProperties, ...[
|
|
21
42
|
{
|
|
22
43
|
"displayName": "Workflow",
|
|
23
44
|
"name": "workflow",
|
|
24
|
-
"description": "
|
|
45
|
+
"description": "Actor input: Workflow",
|
|
25
46
|
"type": "options",
|
|
26
47
|
"options": [
|
|
27
48
|
{
|
|
@@ -37,33 +58,61 @@ class WhatsappValidator {
|
|
|
37
58
|
"value": "text"
|
|
38
59
|
}
|
|
39
60
|
],
|
|
40
|
-
"default": "auto"
|
|
61
|
+
"default": "auto",
|
|
62
|
+
"displayOptions": {
|
|
63
|
+
"show": {
|
|
64
|
+
"apivaultMode": [
|
|
65
|
+
"run"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
41
69
|
},
|
|
42
70
|
{
|
|
43
71
|
"displayName": "Phone numbers",
|
|
44
72
|
"name": "phoneNumbers",
|
|
45
|
-
"description": "Phone numbers
|
|
73
|
+
"description": "Actor input: Phone numbers",
|
|
46
74
|
"type": "string",
|
|
47
|
-
"default": ""
|
|
75
|
+
"default": "",
|
|
76
|
+
"displayOptions": {
|
|
77
|
+
"show": {
|
|
78
|
+
"apivaultMode": [
|
|
79
|
+
"run"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
48
83
|
},
|
|
49
84
|
{
|
|
50
85
|
"displayName": "…or paste a list / CSV column",
|
|
51
86
|
"name": "numbersText",
|
|
52
|
-
"description": "
|
|
87
|
+
"description": "Actor input: …or paste a list / CSV column",
|
|
53
88
|
"type": "string",
|
|
54
|
-
"default": ""
|
|
89
|
+
"default": "",
|
|
90
|
+
"displayOptions": {
|
|
91
|
+
"show": {
|
|
92
|
+
"apivaultMode": [
|
|
93
|
+
"run"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
55
97
|
},
|
|
56
98
|
{
|
|
57
99
|
"displayName": "Default country (for numbers without +)",
|
|
58
100
|
"name": "defaultCountry",
|
|
59
|
-
"description": "
|
|
101
|
+
"description": "Actor input: Default country (for numbers without +)",
|
|
60
102
|
"type": "string",
|
|
61
|
-
"default": "US"
|
|
103
|
+
"default": "US",
|
|
104
|
+
"displayOptions": {
|
|
105
|
+
"show": {
|
|
106
|
+
"apivaultMode": [
|
|
107
|
+
"run"
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
62
111
|
},
|
|
63
112
|
{
|
|
64
113
|
"displayName": "Output preset",
|
|
65
114
|
"name": "outputPreset",
|
|
66
|
-
"description": "
|
|
115
|
+
"description": "Actor input: Output preset",
|
|
67
116
|
"type": "options",
|
|
68
117
|
"options": [
|
|
69
118
|
{
|
|
@@ -79,12 +128,19 @@ class WhatsappValidator {
|
|
|
79
128
|
"value": "both"
|
|
80
129
|
}
|
|
81
130
|
],
|
|
82
|
-
"default": "full"
|
|
131
|
+
"default": "full",
|
|
132
|
+
"displayOptions": {
|
|
133
|
+
"show": {
|
|
134
|
+
"apivaultMode": [
|
|
135
|
+
"run"
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
83
139
|
},
|
|
84
140
|
{
|
|
85
141
|
"displayName": "Legacy export format override",
|
|
86
142
|
"name": "exportFormat",
|
|
87
|
-
"description": "
|
|
143
|
+
"description": "Actor input: Legacy export format override",
|
|
88
144
|
"type": "options",
|
|
89
145
|
"options": [
|
|
90
146
|
{
|
|
@@ -100,71 +156,49 @@ class WhatsappValidator {
|
|
|
100
156
|
"value": "both"
|
|
101
157
|
}
|
|
102
158
|
],
|
|
103
|
-
"default": "default"
|
|
159
|
+
"default": "default",
|
|
160
|
+
"displayOptions": {
|
|
161
|
+
"show": {
|
|
162
|
+
"apivaultMode": [
|
|
163
|
+
"run"
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
104
167
|
}
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
}
|
|
168
|
+
]]
|
|
169
|
+
};
|
|
108
170
|
async execute() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const returnData = [];
|
|
112
|
-
for (let i = 0; i < items.length; i++) {
|
|
171
|
+
const output = [];
|
|
172
|
+
for (let i = 0; i < this.getInputData().length; i++) {
|
|
113
173
|
try {
|
|
114
|
-
const body = {}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const started = await this.helpers.requestWithAuthentication.call(this, 'apifyApi', options);
|
|
133
|
-
const runId = (_b = started === null || started === void 0 ? void 0 : started.data) === null || _b === void 0 ? void 0 : _b.id;
|
|
134
|
-
if (!runId)
|
|
135
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Apify did not return a run ID', { itemIndex: i });
|
|
136
|
-
let run = started.data;
|
|
137
|
-
const deadline = Date.now() + 60 * 60 * 1000;
|
|
138
|
-
while (!['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT'].includes(run.status)) {
|
|
139
|
-
if (Date.now() >= deadline)
|
|
140
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Waiting timed out; check the existing run in Apify before retrying', { itemIndex: i });
|
|
141
|
-
const polled = await this.helpers.requestWithAuthentication.call(this, 'apifyApi', { method: 'GET', url: `https://api.apify.com/v2/actor-runs/${runId}?waitForFinish=20`, json: true });
|
|
142
|
-
run = polled.data;
|
|
143
|
-
}
|
|
144
|
-
if (run.status !== 'SUCCEEDED')
|
|
145
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Apify run ended with status ' + run.status, { itemIndex: i });
|
|
146
|
-
let offset = 0;
|
|
147
|
-
while (true) {
|
|
148
|
-
const page = await this.helpers.requestWithAuthentication.call(this, 'apifyApi', { method: 'GET', url: `https://api.apify.com/v2/datasets/${run.defaultDatasetId}/items?clean=1&limit=1000&offset=${offset}`, json: true });
|
|
149
|
-
if (!Array.isArray(page))
|
|
150
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Unexpected Dataset response', { itemIndex: i });
|
|
151
|
-
for (const result of page)
|
|
152
|
-
returnData.push({ json: result, pairedItem: { item: i } });
|
|
153
|
-
offset += page.length;
|
|
154
|
-
if (page.length < 1000)
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
174
|
+
const body = this.getNodeParameter('apivaultMode', i, 'run') === 'read' ? {} : (() => {
|
|
175
|
+
const body = {};
|
|
176
|
+
body["workflow"] = this.getNodeParameter("workflow", i);
|
|
177
|
+
{
|
|
178
|
+
const _v = this.getNodeParameter("phoneNumbers", i, '');
|
|
179
|
+
const _a = _v.split(/[,\n]/).map(s => s.trim()).filter(s => s.length > 0);
|
|
180
|
+
if (_a.length)
|
|
181
|
+
body["phoneNumbers"] = _a;
|
|
182
|
+
}
|
|
183
|
+
body["numbersText"] = this.getNodeParameter("numbersText", i);
|
|
184
|
+
body["defaultCountry"] = this.getNodeParameter("defaultCountry", i);
|
|
185
|
+
body["outputPreset"] = this.getNodeParameter("outputPreset", i);
|
|
186
|
+
body["exportFormat"] = this.getNodeParameter("exportFormat", i);
|
|
187
|
+
return body;
|
|
188
|
+
})();
|
|
189
|
+
const results = await (0, actorTransport_1.runActor)(this, i, ACTOR_ID, body);
|
|
190
|
+
for (const result of results)
|
|
191
|
+
output.push({ json: result, pairedItem: { item: i } });
|
|
157
192
|
}
|
|
158
193
|
catch (error) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
const message = error instanceof actorTransport_1.SafeActorError ? error.message : 'Could not finish the Actor request. Check inputs and credentials; use Read Existing Run if a run was already started';
|
|
195
|
+
if (this.continueOnFail())
|
|
196
|
+
output.push({ json: { error: message }, pairedItem: { item: i } });
|
|
197
|
+
else
|
|
198
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), message, { itemIndex: i });
|
|
164
199
|
}
|
|
165
200
|
}
|
|
166
|
-
return [
|
|
201
|
+
return [output];
|
|
167
202
|
}
|
|
168
203
|
}
|
|
169
204
|
exports.WhatsappValidator = WhatsappValidator;
|
|
170
|
-
//# sourceMappingURL=WhatsappValidator.node.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeProperties, IDataObject } from 'n8n-workflow';
|
|
2
|
+
import { NodeOperationError } from 'n8n-workflow';
|
|
3
|
+
export declare class SafeActorError extends NodeOperationError {
|
|
4
|
+
readonly runId: string;
|
|
5
|
+
constructor(context: IExecuteFunctions, runId: string, item: number);
|
|
6
|
+
}
|
|
7
|
+
export declare const executionProperties: INodeProperties[];
|
|
8
|
+
export declare function runActor(context: IExecuteFunctions, item: number, actorId: string, body: Record<string, unknown>): Promise<IDataObject[]>;
|
|
9
|
+
export declare function legacyRequest(context: IExecuteFunctions, item: number, raw: unknown): Promise<any>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executionProperties = exports.SafeActorError = void 0;
|
|
4
|
+
exports.runActor = runActor;
|
|
5
|
+
exports.legacyRequest = legacyRequest;
|
|
6
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
7
|
+
const API = 'https://api.apify.com/v2';
|
|
8
|
+
const id = (value) => {
|
|
9
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{1,100}$/.test(value))
|
|
10
|
+
throw new Error('Invalid Apify identifier');
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
class SafeActorError extends n8n_workflow_1.NodeOperationError {
|
|
14
|
+
runId;
|
|
15
|
+
constructor(context, runId, item) {
|
|
16
|
+
super(context.getNode(), runId ? `Could not finish reading run ${runId}; select Read Existing Run to resume` : 'Could not start or read the Actor. Check inputs and credentials before retrying', { itemIndex: item });
|
|
17
|
+
this.runId = runId;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.SafeActorError = SafeActorError;
|
|
21
|
+
exports.executionProperties = [
|
|
22
|
+
{ displayName: 'Run Mode', name: 'apivaultMode', type: 'options', noDataExpression: true,
|
|
23
|
+
options: [{ name: 'Run Actor', value: 'run', action: 'Run the actor' }, { name: 'Read Existing Run', value: 'read', action: 'Read an existing run' }], default: 'run' },
|
|
24
|
+
{ displayName: 'Execution Options', name: 'apivaultExecution', type: 'collection', placeholder: 'Add Option', default: {}, options: [
|
|
25
|
+
{ displayName: 'Run ID', name: 'runId', type: 'string', default: '', description: 'Existing Apify run to resume without starting a paid duplicate' },
|
|
26
|
+
{ displayName: 'Maximum Event Charge (USD)', name: 'maxTotalChargeUsd', type: 'number', default: 0.05,
|
|
27
|
+
typeOptions: { minValue: 0.001, maxValue: 1000, numberPrecision: 3 }, description: 'Event charge cap. Platform usage may be billed separately.' },
|
|
28
|
+
{ displayName: 'Maximum Rows to Read', name: 'maxRows', type: 'number', default: 1000,
|
|
29
|
+
typeOptions: { minValue: 1, maxValue: 100000, numberPrecision: 0 }, description: 'Download cap only. Actor input limits control paid collection.' },
|
|
30
|
+
{ displayName: 'Output', name: 'output', type: 'options', default: 'rows',
|
|
31
|
+
options: [{ name: 'Dataset Rows', value: 'rows' }, { name: 'Run, Rows and Diagnostics', value: 'full' }], description: 'Include SUMMARY and ERRORS to inspect partial or empty results' },
|
|
32
|
+
] },
|
|
33
|
+
];
|
|
34
|
+
async function runActor(context, item, actorId, body) {
|
|
35
|
+
let runId = '';
|
|
36
|
+
try {
|
|
37
|
+
const mode = context.getNodeParameter('apivaultMode', item, 'run');
|
|
38
|
+
const options = context.getNodeParameter('apivaultExecution', item, {});
|
|
39
|
+
const legacy = context.getNode().typeVersion < 2;
|
|
40
|
+
const maxRows = options.maxRows ?? (legacy ? 100000 : 1000);
|
|
41
|
+
if (!Number.isInteger(maxRows) || maxRows < 1 || maxRows > 100000)
|
|
42
|
+
throw new Error('Invalid row limit');
|
|
43
|
+
const request = async (requestOptions) => context.helpers.httpRequestWithAuthentication.call(context, 'apifyApi', requestOptions);
|
|
44
|
+
if (mode === 'run') {
|
|
45
|
+
const cap = options.maxTotalChargeUsd ?? (legacy ? undefined : 0.05);
|
|
46
|
+
if (cap !== undefined && (!Number.isFinite(cap) || cap <= 0 || cap > 1000))
|
|
47
|
+
throw new Error('Invalid event charge cap');
|
|
48
|
+
// Legacy workflows retain their existing charge behavior unless a cap is selected.
|
|
49
|
+
const qs = { build: 'latest' };
|
|
50
|
+
if (cap !== undefined)
|
|
51
|
+
qs.maxTotalChargeUsd = cap;
|
|
52
|
+
// Never retry POST: a duplicate could create a second paid Actor run.
|
|
53
|
+
const started = await request({ method: 'POST', url: `${API}/acts/${id(actorId)}/runs`, body: body, qs, json: true });
|
|
54
|
+
runId = id(started?.data?.id);
|
|
55
|
+
}
|
|
56
|
+
else if (mode === 'read')
|
|
57
|
+
runId = id(options.runId);
|
|
58
|
+
else
|
|
59
|
+
throw new Error('Unsupported run mode');
|
|
60
|
+
const deadline = Date.now() + 10 * 60 * 1000;
|
|
61
|
+
let run;
|
|
62
|
+
while (true) {
|
|
63
|
+
if (Date.now() >= deadline)
|
|
64
|
+
throw new Error('Waiting timed out');
|
|
65
|
+
const response = await request({ method: 'GET', url: `${API}/actor-runs/${runId}`, qs: { waitForFinish: 20 }, json: true });
|
|
66
|
+
run = response.data;
|
|
67
|
+
if (run.status === 'SUCCEEDED')
|
|
68
|
+
break;
|
|
69
|
+
if (['FAILED', 'TIMED-OUT', 'ABORTED'].includes(run.status))
|
|
70
|
+
throw new Error('Run did not succeed');
|
|
71
|
+
}
|
|
72
|
+
const datasetId = id(run.defaultDatasetId);
|
|
73
|
+
const dataset = await request({ method: 'GET', url: `${API}/datasets/${datasetId}`, json: true });
|
|
74
|
+
const rows = [];
|
|
75
|
+
while (rows.length < maxRows) {
|
|
76
|
+
const limit = Math.min(1000, maxRows - rows.length);
|
|
77
|
+
const page = await request({ method: 'GET', url: `${API}/datasets/${datasetId}/items`, qs: { clean: true, offset: rows.length, limit }, json: true });
|
|
78
|
+
if (!Array.isArray(page) || page.length > limit)
|
|
79
|
+
throw new Error('Invalid Dataset response');
|
|
80
|
+
rows.push(...page);
|
|
81
|
+
if (page.length < limit)
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
const truncated = (dataset.data?.itemCount ?? rows.length) > rows.length;
|
|
85
|
+
if (options.output === 'full') {
|
|
86
|
+
const storeId = id(run.defaultKeyValueStoreId);
|
|
87
|
+
const records = {};
|
|
88
|
+
for (const key of ['SUMMARY', 'ERRORS']) {
|
|
89
|
+
try {
|
|
90
|
+
records[key] = await request({ method: 'GET', url: `${API}/key-value-stores/${storeId}/records/${key}`, json: true });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const e = error;
|
|
94
|
+
if (e.statusCode === 404 || e.response?.status === 404)
|
|
95
|
+
records[key] = null;
|
|
96
|
+
else
|
|
97
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Diagnostics unavailable');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return [{ runId, datasetId, status: run.status, rows, summary: records.SUMMARY ?? null, errors: records.ERRORS ?? null, truncated }];
|
|
101
|
+
}
|
|
102
|
+
return rows.map(row => ({ ...row, _apifyRunId: runId, _apifyTruncated: truncated }));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
const safeError = new SafeActorError(context, runId, item);
|
|
106
|
+
throw safeError;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Keep specialized legacy output modes (including asynchronous start and MCP_RESPONSE).
|
|
110
|
+
async function legacyRequest(context, item, raw) {
|
|
111
|
+
const options = raw;
|
|
112
|
+
const controls = context.getNodeParameter('apivaultExecution', item, {});
|
|
113
|
+
const request = async (value) => context.helpers.httpRequestWithAuthentication.call(context, 'apifyApi', value);
|
|
114
|
+
if (options.method === 'POST' && options.url.endsWith('/runs')) {
|
|
115
|
+
const qs = { ...(options.qs ?? {}) };
|
|
116
|
+
const wait = qs.waitForFinish;
|
|
117
|
+
delete qs.waitForFinish;
|
|
118
|
+
const cap = controls.maxTotalChargeUsd ?? (context.getNode().typeVersion < 2 ? undefined : 0.05);
|
|
119
|
+
if (cap !== undefined) {
|
|
120
|
+
if (!Number.isFinite(cap) || cap <= 0 || cap > 1000)
|
|
121
|
+
throw new Error('Invalid event charge cap');
|
|
122
|
+
qs.maxTotalChargeUsd = cap;
|
|
123
|
+
}
|
|
124
|
+
const started = await request({ ...options, qs });
|
|
125
|
+
if (!wait)
|
|
126
|
+
return started;
|
|
127
|
+
const runId = id(started?.data?.id);
|
|
128
|
+
const deadline = Date.now() + 10 * 60 * 1000;
|
|
129
|
+
while (Date.now() < deadline) {
|
|
130
|
+
const result = await request({ method: 'GET', url: `${API}/actor-runs/${runId}`, qs: { waitForFinish: 20 }, json: true });
|
|
131
|
+
if (result.data.status === 'SUCCEEDED')
|
|
132
|
+
return result;
|
|
133
|
+
if (['FAILED', 'TIMED-OUT', 'ABORTED'].includes(result.data.status))
|
|
134
|
+
throw new Error('Run did not succeed');
|
|
135
|
+
}
|
|
136
|
+
throw new Error('Waiting timed out');
|
|
137
|
+
}
|
|
138
|
+
if (options.method === 'GET' && /\/datasets\/[A-Za-z0-9_-]+\/items$/.test(options.url)) {
|
|
139
|
+
const maxRows = controls.maxRows ?? (context.getNode().typeVersion < 2 ? 100000 : 1000);
|
|
140
|
+
if (!Number.isInteger(maxRows) || maxRows < 1 || maxRows > 100000)
|
|
141
|
+
throw new Error('Invalid row limit');
|
|
142
|
+
const rows = [];
|
|
143
|
+
while (rows.length < maxRows) {
|
|
144
|
+
const limit = Math.min(1000, maxRows - rows.length);
|
|
145
|
+
const page = await request({ ...options, qs: { ...(options.qs ?? {}), offset: rows.length, limit } });
|
|
146
|
+
if (!Array.isArray(page) || page.length > limit)
|
|
147
|
+
throw new Error('Invalid Dataset response');
|
|
148
|
+
rows.push(...page);
|
|
149
|
+
if (page.length < limit)
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
return rows;
|
|
153
|
+
}
|
|
154
|
+
return request(options);
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-apivault-whatsapp-validator",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Run WhatsApp Number Validator through the hosted Apify Actor and read its results",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://apify.com/apivault_labs/whatsapp-number-validator",
|
|
7
7
|
"keywords": [
|
|
@@ -15,20 +15,24 @@
|
|
|
15
15
|
"workflow-automation"
|
|
16
16
|
],
|
|
17
17
|
"author": {
|
|
18
|
-
"name": "
|
|
18
|
+
"name": "ApiVault Labs",
|
|
19
|
+
"email": "68965192+apivault-labs@users.noreply.github.com"
|
|
19
20
|
},
|
|
20
21
|
"repository": {
|
|
21
22
|
"type": "git",
|
|
22
|
-
"url": "https://github.com/apivault-labs/
|
|
23
|
+
"url": "git+https://github.com/apivault-labs/apivault-labs.git",
|
|
24
|
+
"directory": "community-nodes/n8n-nodes-apivault-whatsapp-validator"
|
|
23
25
|
},
|
|
24
26
|
"main": "index.js",
|
|
25
27
|
"scripts": {
|
|
26
|
-
"build": "tsc &&
|
|
27
|
-
"
|
|
28
|
+
"build": "tsc && node scripts/copy-icons.cjs",
|
|
29
|
+
"lint": "n8n-node lint",
|
|
30
|
+
"test": "node test/contract.cjs",
|
|
28
31
|
"prepublishOnly": "npm run build"
|
|
29
32
|
},
|
|
30
33
|
"files": [
|
|
31
|
-
"dist"
|
|
34
|
+
"dist",
|
|
35
|
+
"examples"
|
|
32
36
|
],
|
|
33
37
|
"n8n": {
|
|
34
38
|
"n8nNodesApiVersion": 1,
|
|
@@ -40,12 +44,12 @@
|
|
|
40
44
|
]
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
43
|
-
"@
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
+
"@n8n/node-cli": "0.52.2",
|
|
48
|
+
"n8n-workflow": "2.16.0",
|
|
49
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
50
|
+
"@types/node": "^22.0.0"
|
|
47
51
|
},
|
|
48
52
|
"peerDependencies": {
|
|
49
53
|
"n8n-workflow": "*"
|
|
50
54
|
}
|
|
51
|
-
}
|
|
55
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ApifyApi.credentials.js","sourceRoot":"","sources":["../../credentials/ApifyApi.credentials.ts"],"names":[],"mappings":";;;AAOA,MAAa,QAAQ;IAArB;QACC,SAAI,GAAG,UAAU,CAAC;QAClB,gBAAW,GAAG,WAAW,CAAC;QAC1B,qBAAgB,GAAG,kDAAkD,CAAC;QACtE,eAAU,GAAsB;YAC/B;gBACC,WAAW,EAAE,WAAW;gBACxB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,4EAA4E;aACzF;SACD,CAAC;QACF,iBAAY,GAAyB;YACpC,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,mCAAmC,EAAE,EAAE;SAC/E,CAAC;QACF,SAAI,GAA2B,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,0BAA0B,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE,CAAC;IACvG,CAAC;CAAA;AApBD,4BAoBC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WhatsappValidator.node.js","sourceRoot":"","sources":["../../../nodes/WhatsappValidator/WhatsappValidator.node.ts"],"names":[],"mappings":"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"}
|