@pagecrawl/n8n-nodes-pagecrawl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,467 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PageCrawl = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const PageDescription_1 = require("./descriptions/PageDescription");
6
+ const CheckDescription_1 = require("./descriptions/CheckDescription");
7
+ const ScreenshotDescription_1 = require("./descriptions/ScreenshotDescription");
8
+ const WebhookDescription_1 = require("./descriptions/WebhookDescription");
9
+ class PageCrawl {
10
+ constructor() {
11
+ this.description = {
12
+ displayName: 'PageCrawl',
13
+ name: 'pageCrawl',
14
+ icon: 'file:pagecrawl.svg',
15
+ group: ['transform'],
16
+ version: 1,
17
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
18
+ description: 'Interact with PageCrawl.io API for website monitoring',
19
+ defaults: {
20
+ name: 'PageCrawl',
21
+ },
22
+ inputs: ['main'],
23
+ outputs: ['main'],
24
+ credentials: [
25
+ {
26
+ name: 'pageCrawlApi',
27
+ required: true,
28
+ },
29
+ ],
30
+ requestDefaults: {
31
+ baseURL: '={{$credentials.baseUrl}}/api',
32
+ headers: {
33
+ Accept: 'application/json',
34
+ 'Content-Type': 'application/json',
35
+ },
36
+ },
37
+ properties: [
38
+ {
39
+ displayName: 'Resource',
40
+ name: 'resource',
41
+ type: 'options',
42
+ noDataExpression: true,
43
+ options: [
44
+ {
45
+ name: 'Page',
46
+ value: 'page',
47
+ description: 'Manage tracked pages',
48
+ },
49
+ {
50
+ name: 'Check',
51
+ value: 'check',
52
+ description: 'Access check history and diffs',
53
+ },
54
+ {
55
+ name: 'Screenshot',
56
+ value: 'screenshot',
57
+ description: 'Get page screenshots',
58
+ },
59
+ {
60
+ name: 'Webhook',
61
+ value: 'webhook',
62
+ description: 'Manage webhooks',
63
+ },
64
+ ],
65
+ default: 'page',
66
+ },
67
+ ...PageDescription_1.pageOperations,
68
+ ...PageDescription_1.pageFields,
69
+ ...CheckDescription_1.checkOperations,
70
+ ...CheckDescription_1.checkFields,
71
+ ...ScreenshotDescription_1.screenshotOperations,
72
+ ...ScreenshotDescription_1.screenshotFields,
73
+ ...WebhookDescription_1.webhookOperations,
74
+ ...WebhookDescription_1.webhookFields,
75
+ ],
76
+ };
77
+ }
78
+ async execute() {
79
+ const items = this.getInputData();
80
+ const returnData = [];
81
+ const credentials = await this.getCredentials('pageCrawlApi');
82
+ const baseUrl = (credentials.baseUrl || 'https://pagecrawl.io').replace(/\/+$/, '');
83
+ for (let i = 0; i < items.length; i++) {
84
+ const resource = this.getNodeParameter('resource', i);
85
+ const operation = this.getNodeParameter('operation', i);
86
+ try {
87
+ let responseData;
88
+ // Handle different resources and operations
89
+ if (resource === 'page') {
90
+ if (operation === 'getAll') {
91
+ const returnAll = this.getNodeParameter('returnAll', i);
92
+ const options = this.getNodeParameter('options', i);
93
+ const endpoint = '/pages';
94
+ const qs = {};
95
+ if (options.simple) {
96
+ qs.simple = 1;
97
+ }
98
+ if (options.take) {
99
+ qs.take = options.take;
100
+ }
101
+ if (options.folder) {
102
+ qs.folder = options.folder;
103
+ }
104
+ if (!returnAll) {
105
+ qs.limit = this.getNodeParameter('limit', i);
106
+ }
107
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
108
+ method: 'GET',
109
+ url: `${baseUrl}/api${endpoint}`,
110
+ qs,
111
+ json: true,
112
+ });
113
+ if (!returnAll && Array.isArray(responseData)) {
114
+ responseData = responseData.slice(0, qs.limit);
115
+ }
116
+ }
117
+ else if (operation === 'get') {
118
+ const pageId = this.getNodeParameter('pageId', i);
119
+ const options = this.getNodeParameter('options', i);
120
+ const qs = {};
121
+ if (options.simple) {
122
+ qs.simple = 1;
123
+ }
124
+ if (options.take) {
125
+ qs.take = options.take;
126
+ }
127
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
128
+ method: 'GET',
129
+ url: `${baseUrl}/api/pages/${pageId}`,
130
+ qs,
131
+ json: true,
132
+ });
133
+ }
134
+ else if (operation === 'createSimple') {
135
+ const url = this.getNodeParameter('url', i);
136
+ const additionalFields = this.getNodeParameter('additionalFields', i);
137
+ const body = { url };
138
+ if (additionalFields.selector) {
139
+ body.selector = additionalFields.selector;
140
+ }
141
+ if (additionalFields.frequency !== undefined) {
142
+ body.frequency = additionalFields.frequency;
143
+ }
144
+ if (additionalFields.ignore_duplicates !== undefined) {
145
+ body.ignore_duplicates = additionalFields.ignore_duplicates;
146
+ }
147
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
148
+ method: 'POST',
149
+ url: `${baseUrl}/api/track-simple`,
150
+ body,
151
+ json: true,
152
+ });
153
+ }
154
+ else if (operation === 'create') {
155
+ const url = this.getNodeParameter('url', i);
156
+ const name = this.getNodeParameter('name', i);
157
+ const frequency = this.getNodeParameter('frequency', i);
158
+ const elements = this.getNodeParameter('elements', i);
159
+ const additionalFields = this.getNodeParameter('additionalFields', i);
160
+ const body = {
161
+ url,
162
+ name,
163
+ frequency,
164
+ elements: elements.element || [],
165
+ };
166
+ // Add all additional fields
167
+ Object.assign(body, additionalFields);
168
+ // Convert tags from comma-separated string to array
169
+ if (typeof body.tags === 'string' && body.tags) {
170
+ body.tags = body.tags.split(',').map((tag) => tag.trim()).filter(Boolean);
171
+ }
172
+ // Remove ID fields if they're 0 (not set)
173
+ if (body.folder_id === 0)
174
+ delete body.folder_id;
175
+ if (body.template_id === 0)
176
+ delete body.template_id;
177
+ if (body.auth_id === 0)
178
+ delete body.auth_id;
179
+ // Parse JSON fields if they're strings
180
+ if (typeof body.actions === 'string') {
181
+ try {
182
+ body.actions = JSON.parse(body.actions);
183
+ }
184
+ catch (e) {
185
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in actions field', { itemIndex: i });
186
+ }
187
+ }
188
+ if (typeof body.rules === 'string') {
189
+ try {
190
+ body.rules = JSON.parse(body.rules);
191
+ }
192
+ catch (e) {
193
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in rules field', { itemIndex: i });
194
+ }
195
+ }
196
+ if (typeof body.headers === 'string') {
197
+ try {
198
+ body.headers = JSON.parse(body.headers);
199
+ }
200
+ catch (e) {
201
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in headers field', { itemIndex: i });
202
+ }
203
+ }
204
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
205
+ method: 'POST',
206
+ url: `${baseUrl}/api/pages`,
207
+ body,
208
+ json: true,
209
+ });
210
+ }
211
+ else if (operation === 'update') {
212
+ const pageId = this.getNodeParameter('pageId', i);
213
+ const updateFields = this.getNodeParameter('updateFields', i);
214
+ const additionalFields = this.getNodeParameter('additionalFields', i);
215
+ const body = { ...updateFields, ...additionalFields };
216
+ // Convert tags from comma-separated string to array
217
+ if (typeof body.tags === 'string' && body.tags) {
218
+ body.tags = body.tags.split(',').map((tag) => tag.trim()).filter(Boolean);
219
+ }
220
+ // Remove ID fields if they're 0 (not set)
221
+ if (body.folder_id === 0)
222
+ delete body.folder_id;
223
+ if (body.template_id === 0)
224
+ delete body.template_id;
225
+ if (body.auth_id === 0)
226
+ delete body.auth_id;
227
+ // Parse JSON fields if they're strings
228
+ if (typeof body.elements === 'string') {
229
+ try {
230
+ body.elements = JSON.parse(body.elements);
231
+ }
232
+ catch (e) {
233
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in elements field', { itemIndex: i });
234
+ }
235
+ }
236
+ if (typeof body.actions === 'string') {
237
+ try {
238
+ body.actions = JSON.parse(body.actions);
239
+ }
240
+ catch (e) {
241
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in actions field', { itemIndex: i });
242
+ }
243
+ }
244
+ if (typeof body.rules === 'string') {
245
+ try {
246
+ body.rules = JSON.parse(body.rules);
247
+ }
248
+ catch (e) {
249
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in rules field', { itemIndex: i });
250
+ }
251
+ }
252
+ if (typeof body.headers === 'string') {
253
+ try {
254
+ body.headers = JSON.parse(body.headers);
255
+ }
256
+ catch (e) {
257
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in headers field', { itemIndex: i });
258
+ }
259
+ }
260
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
261
+ method: 'PUT',
262
+ url: `${baseUrl}/api/pages/${pageId}`,
263
+ body,
264
+ json: true,
265
+ });
266
+ }
267
+ else if (operation === 'delete') {
268
+ const pageId = this.getNodeParameter('pageId', i);
269
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
270
+ method: 'DELETE',
271
+ url: `${baseUrl}/api/pages/${pageId}`,
272
+ json: true,
273
+ });
274
+ responseData = { success: true, deleted: pageId };
275
+ }
276
+ else if (operation === 'runCheckNow') {
277
+ const pageId = this.getNodeParameter('pageId', i);
278
+ const options = this.getNodeParameter('runCheckOptions', i);
279
+ const qs = {};
280
+ if (options.skip_first_notification) {
281
+ qs.skip_first_notification = 1;
282
+ }
283
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
284
+ method: 'PUT',
285
+ url: `${baseUrl}/api/pages/${pageId}/check`,
286
+ qs,
287
+ json: true,
288
+ });
289
+ responseData = { success: true, message: 'Check triggered', pageId };
290
+ }
291
+ }
292
+ else if (resource === 'check') {
293
+ const pageId = this.getNodeParameter('pageId', i);
294
+ if (operation === 'getHistory') {
295
+ const options = this.getNodeParameter('options', i);
296
+ const qs = {};
297
+ if (options.simple) {
298
+ qs.simple = 1;
299
+ }
300
+ if (options.take) {
301
+ qs.take = options.take;
302
+ }
303
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
304
+ method: 'GET',
305
+ url: `${baseUrl}/api/pages/${pageId}/history`,
306
+ qs,
307
+ json: true,
308
+ });
309
+ }
310
+ else if (operation === 'getDiffImage') {
311
+ const checkId = this.getNodeParameter('checkId', i);
312
+ const response = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
313
+ method: 'GET',
314
+ url: `${baseUrl}/api/pages/${pageId}/checks/${checkId}/diff.png`,
315
+ encoding: 'arraybuffer',
316
+ });
317
+ const binaryData = await this.helpers.prepareBinaryData(response, `diff-${pageId}-${checkId}.png`, 'image/png');
318
+ const executionData = this.helpers.constructExecutionMetaData([{ json: {}, binary: { data: binaryData } }], { itemData: { item: i } });
319
+ returnData.push(...executionData);
320
+ continue;
321
+ }
322
+ else if (operation === 'getDiffHtml') {
323
+ const checkId = this.getNodeParameter('checkId', i);
324
+ const htmlContent = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
325
+ method: 'GET',
326
+ url: `${baseUrl}/api/pages/${pageId}/checks/${checkId}/diff.html`,
327
+ headers: {
328
+ Accept: 'text/html',
329
+ },
330
+ });
331
+ responseData = {
332
+ html: htmlContent,
333
+ pageId,
334
+ checkId,
335
+ };
336
+ }
337
+ else if (operation === 'getDiffMarkdown') {
338
+ const checkId = this.getNodeParameter('checkId', i);
339
+ const markdownContent = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
340
+ method: 'GET',
341
+ url: `${baseUrl}/api/pages/${pageId}/checks/${checkId}/diff.markdown`,
342
+ headers: {
343
+ Accept: 'text/markdown',
344
+ },
345
+ });
346
+ responseData = {
347
+ markdown: markdownContent,
348
+ pageId,
349
+ checkId,
350
+ };
351
+ }
352
+ }
353
+ else if (resource === 'screenshot') {
354
+ const pageId = this.getNodeParameter('pageId', i);
355
+ let endpoint = '';
356
+ if (operation === 'getLatest') {
357
+ endpoint = `/pages/${pageId}/checks/latest/screenshot`;
358
+ }
359
+ else if (operation === 'getLatestDiff') {
360
+ endpoint = `/pages/${pageId}/checks/latest/diff`;
361
+ }
362
+ else if (operation === 'getCheckScreenshot') {
363
+ const checkId = this.getNodeParameter('checkId', i);
364
+ endpoint = `/pages/${pageId}/checks/${checkId}/screenshot`;
365
+ }
366
+ else if (operation === 'getCheckDiff') {
367
+ const checkId = this.getNodeParameter('checkId', i);
368
+ endpoint = `/pages/${pageId}/checks/${checkId}/diff`;
369
+ }
370
+ const response = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
371
+ method: 'GET',
372
+ url: `${baseUrl}/api${endpoint}`,
373
+ encoding: 'arraybuffer',
374
+ });
375
+ const binaryData = await this.helpers.prepareBinaryData(response, `screenshot-${pageId}.png`, 'image/png');
376
+ const executionData = this.helpers.constructExecutionMetaData([{ json: {}, binary: { data: binaryData } }], { itemData: { item: i } });
377
+ returnData.push(...executionData);
378
+ continue;
379
+ }
380
+ else if (resource === 'webhook') {
381
+ if (operation === 'getAll') {
382
+ const returnAll = this.getNodeParameter('returnAll', i);
383
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
384
+ method: 'GET',
385
+ url: `${baseUrl}/api/hooks`,
386
+ json: true,
387
+ });
388
+ if (!returnAll && Array.isArray(responseData)) {
389
+ const limit = this.getNodeParameter('limit', i);
390
+ responseData = responseData.slice(0, limit);
391
+ }
392
+ }
393
+ else if (operation === 'create') {
394
+ const target_url = this.getNodeParameter('target_url', i);
395
+ const additionalFields = this.getNodeParameter('additionalFields', i);
396
+ const body = { target_url };
397
+ if (additionalFields.change_id) {
398
+ body.change_id = additionalFields.change_id;
399
+ }
400
+ if (additionalFields.payload_fields) {
401
+ body.payload_fields = additionalFields.payload_fields;
402
+ }
403
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
404
+ method: 'POST',
405
+ url: `${baseUrl}/api/hooks`,
406
+ body,
407
+ json: true,
408
+ });
409
+ }
410
+ else if (operation === 'update') {
411
+ const webhookId = this.getNodeParameter('webhookId', i);
412
+ const updateFields = this.getNodeParameter('updateFields', i);
413
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
414
+ method: 'PUT',
415
+ url: `${baseUrl}/api/hooks/${webhookId}`,
416
+ body: updateFields,
417
+ json: true,
418
+ });
419
+ }
420
+ else if (operation === 'delete') {
421
+ const webhookId = this.getNodeParameter('webhookId', i);
422
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
423
+ method: 'DELETE',
424
+ url: `${baseUrl}/api/hooks/${webhookId}`,
425
+ json: true,
426
+ });
427
+ responseData = { success: true, deleted: webhookId };
428
+ }
429
+ else if (operation === 'test') {
430
+ const webhookId = this.getNodeParameter('webhookId', i);
431
+ responseData = await this.helpers.requestWithAuthentication.call(this, 'pageCrawlApi', {
432
+ method: 'PUT',
433
+ url: `${baseUrl}/api/hooks/${webhookId}/test`,
434
+ json: true,
435
+ });
436
+ responseData = { success: true, message: 'Test webhook sent' };
437
+ }
438
+ }
439
+ const executionData = this.helpers.constructExecutionMetaData([{ json: responseData }], { itemData: { item: i } });
440
+ returnData.push(...executionData);
441
+ }
442
+ catch (error) {
443
+ if (this.continueOnFail()) {
444
+ // Extract detailed error message from API response if available
445
+ const errorMessage = error.response?.body?.message
446
+ || error.response?.body?.error
447
+ || error.message
448
+ || 'An error occurred';
449
+ const statusCode = error.statusCode || error.response?.statusCode;
450
+ const executionData = this.helpers.constructExecutionMetaData([{
451
+ json: {
452
+ error: errorMessage,
453
+ statusCode,
454
+ resource,
455
+ operation,
456
+ },
457
+ }], { itemData: { item: i } });
458
+ returnData.push(...executionData);
459
+ continue;
460
+ }
461
+ throw error;
462
+ }
463
+ }
464
+ return [returnData];
465
+ }
466
+ }
467
+ exports.PageCrawl = PageCrawl;
@@ -0,0 +1,22 @@
1
+ {
2
+ "node": "n8n-nodes-pagecrawl.PageCrawl",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["Marketing & Content"],
6
+ "resources": {
7
+ "credentialDocumentation": [
8
+ {
9
+ "url": "https://pagecrawl.io/docs/api"
10
+ }
11
+ ],
12
+ "primaryDocumentation": [
13
+ {
14
+ "url": "https://pagecrawl.io/docs"
15
+ }
16
+ ]
17
+ },
18
+ "alias": ["Monitor", "Track", "Website", "Change Detection", "Web Scraping"],
19
+ "subcategories": {
20
+ "Marketing & Content": ["Automation", "Analytics"]
21
+ }
22
+ }
@@ -0,0 +1,3 @@
1
+ import { INodeProperties } from 'n8n-workflow';
2
+ export declare const checkOperations: INodeProperties[];
3
+ export declare const checkFields: INodeProperties[];
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkFields = exports.checkOperations = void 0;
4
+ exports.checkOperations = [
5
+ {
6
+ displayName: 'Operation',
7
+ name: 'operation',
8
+ type: 'options',
9
+ noDataExpression: true,
10
+ displayOptions: {
11
+ show: {
12
+ resource: ['check'],
13
+ },
14
+ },
15
+ options: [
16
+ {
17
+ name: 'Get History',
18
+ value: 'getHistory',
19
+ description: 'Get check history for a page',
20
+ action: 'Get check history',
21
+ },
22
+ {
23
+ name: 'Get Text Diff HTML',
24
+ value: 'getDiffHtml',
25
+ description: 'Get text difference as HTML',
26
+ action: 'Get text diff HTML',
27
+ },
28
+ {
29
+ name: 'Get Text Diff Image',
30
+ value: 'getDiffImage',
31
+ description: 'Get text difference as image',
32
+ action: 'Get text diff image',
33
+ },
34
+ {
35
+ name: 'Get Text Diff Markdown',
36
+ value: 'getDiffMarkdown',
37
+ description: 'Get text difference as Markdown',
38
+ action: 'Get text diff markdown',
39
+ },
40
+ ],
41
+ default: 'getHistory',
42
+ },
43
+ ];
44
+ exports.checkFields = [
45
+ // ========================================
46
+ // check:getHistory
47
+ // ========================================
48
+ {
49
+ displayName: 'Page ID',
50
+ name: 'pageId',
51
+ type: 'string',
52
+ required: true,
53
+ displayOptions: {
54
+ show: {
55
+ resource: ['check'],
56
+ },
57
+ },
58
+ default: '',
59
+ description: 'The ID or slug of the page',
60
+ },
61
+ {
62
+ displayName: 'Options',
63
+ name: 'options',
64
+ type: 'collection',
65
+ placeholder: 'Add Option',
66
+ default: {},
67
+ displayOptions: {
68
+ show: {
69
+ resource: ['check'],
70
+ operation: ['getHistory'],
71
+ },
72
+ },
73
+ options: [
74
+ {
75
+ displayName: 'Simple',
76
+ name: 'simple',
77
+ type: 'boolean',
78
+ default: false,
79
+ description: 'Whether to return simplified response',
80
+ },
81
+ {
82
+ displayName: 'Take',
83
+ name: 'take',
84
+ type: 'number',
85
+ default: 0,
86
+ description: 'Limit number of checks retrieved',
87
+ typeOptions: {
88
+ minValue: 0,
89
+ },
90
+ },
91
+ ],
92
+ },
93
+ // ========================================
94
+ // check:getDiff*
95
+ // ========================================
96
+ {
97
+ displayName: 'Check ID',
98
+ name: 'checkId',
99
+ type: 'string',
100
+ required: true,
101
+ displayOptions: {
102
+ show: {
103
+ resource: ['check'],
104
+ operation: ['getDiffHtml', 'getDiffImage', 'getDiffMarkdown'],
105
+ },
106
+ },
107
+ default: '',
108
+ description: 'The check ID (use "latest" for most recent)',
109
+ },
110
+ ];
@@ -0,0 +1,3 @@
1
+ import { INodeProperties } from 'n8n-workflow';
2
+ export declare const pageOperations: INodeProperties[];
3
+ export declare const pageFields: INodeProperties[];