@pagecrawl/n8n-nodes-pagecrawl 0.1.1 → 0.1.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.
@@ -16,15 +16,7 @@ class PageCrawlApi {
16
16
  },
17
17
  default: '',
18
18
  required: true,
19
- description: 'Your PageCrawl.io API token. API access requires a paid plan. You can find your token in Settings > API.',
20
- },
21
- {
22
- displayName: 'Base URL',
23
- name: 'baseUrl',
24
- type: 'string',
25
- default: 'https://pagecrawl.io',
26
- description: 'The base URL for the PageCrawl.io API',
27
- hint: 'Use https://pagecrawl.io for production',
19
+ description: 'Your PageCrawl.io API token. API access requires a paid plan. Find your token at Settings > API.',
28
20
  },
29
21
  ];
30
22
  this.authenticate = {
@@ -37,20 +29,10 @@ class PageCrawlApi {
37
29
  };
38
30
  this.test = {
39
31
  request: {
40
- baseURL: '={{$credentials.baseUrl}}',
32
+ baseURL: 'https://pagecrawl.io',
41
33
  url: '/api/user',
42
34
  method: 'GET',
43
35
  },
44
- rules: [
45
- {
46
- type: 'responseSuccessBody',
47
- properties: {
48
- key: 'id',
49
- value: undefined,
50
- message: 'Authentication successful',
51
- },
52
- },
53
- ],
54
36
  };
55
37
  }
56
38
  }
@@ -1,5 +1,10 @@
1
- import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
2
2
  export declare class PageCrawl implements INodeType {
3
3
  description: INodeTypeDescription;
4
+ methods: {
5
+ listSearch: {
6
+ pageSearch(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult>;
7
+ };
8
+ };
4
9
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
10
  }
@@ -28,7 +28,7 @@ class PageCrawl {
28
28
  },
29
29
  ],
30
30
  requestDefaults: {
31
- baseURL: '={{$credentials.baseUrl}}/api',
31
+ baseURL: 'https://pagecrawl.io/api',
32
32
  headers: {
33
33
  Accept: 'application/json',
34
34
  'Content-Type': 'application/json',
@@ -74,12 +74,41 @@ class PageCrawl {
74
74
  ...WebhookDescription_1.webhookFields,
75
75
  ],
76
76
  };
77
+ this.methods = {
78
+ listSearch: {
79
+ async pageSearch(filter) {
80
+ const baseUrl = 'https://pagecrawl.io';
81
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pageCrawlApi', {
82
+ method: 'GET',
83
+ url: `${baseUrl}/api/pages`,
84
+ json: true,
85
+ });
86
+ const pages = response.data || response;
87
+ let results = pages.map((page) => ({
88
+ name: page.name || page.url,
89
+ value: page.slug,
90
+ url: `https://pagecrawl.io/app/pages/${page.slug}`,
91
+ }));
92
+ // Filter results if search term provided
93
+ if (filter) {
94
+ const filterLower = filter.toLowerCase();
95
+ results = results.filter((page) => page.name.toLowerCase().includes(filterLower) ||
96
+ page.value.toLowerCase().includes(filterLower));
97
+ }
98
+ return { results };
99
+ },
100
+ },
101
+ };
77
102
  }
78
103
  async execute() {
79
104
  const items = this.getInputData();
80
105
  const returnData = [];
81
- const credentials = await this.getCredentials('pageCrawlApi');
82
- const baseUrl = (credentials.baseUrl || 'https://pagecrawl.io').replace(/\/+$/, '');
106
+ const baseUrl = 'https://pagecrawl.io';
107
+ // Helper to extract value from resourceLocator
108
+ const getPageId = (index) => {
109
+ const pageLocator = this.getNodeParameter('pageId', index);
110
+ return pageLocator.value || '';
111
+ };
83
112
  for (let i = 0; i < items.length; i++) {
84
113
  const resource = this.getNodeParameter('resource', i);
85
114
  const operation = this.getNodeParameter('operation', i);
@@ -115,7 +144,7 @@ class PageCrawl {
115
144
  }
116
145
  }
117
146
  else if (operation === 'get') {
118
- const pageId = this.getNodeParameter('pageId', i);
147
+ const pageId = getPageId(i);
119
148
  const options = this.getNodeParameter('options', i);
120
149
  const qs = {};
121
150
  if (options.simple) {
@@ -209,7 +238,7 @@ class PageCrawl {
209
238
  });
210
239
  }
211
240
  else if (operation === 'update') {
212
- const pageId = this.getNodeParameter('pageId', i);
241
+ const pageId = getPageId(i);
213
242
  const updateFields = this.getNodeParameter('updateFields', i);
214
243
  const additionalFields = this.getNodeParameter('additionalFields', i);
215
244
  const body = { ...updateFields, ...additionalFields };
@@ -265,7 +294,7 @@ class PageCrawl {
265
294
  });
266
295
  }
267
296
  else if (operation === 'delete') {
268
- const pageId = this.getNodeParameter('pageId', i);
297
+ const pageId = getPageId(i);
269
298
  responseData = await this.helpers.httpRequestWithAuthentication.call(this, 'pageCrawlApi', {
270
299
  method: 'DELETE',
271
300
  url: `${baseUrl}/api/pages/${pageId}`,
@@ -274,7 +303,7 @@ class PageCrawl {
274
303
  responseData = { success: true, deleted: pageId };
275
304
  }
276
305
  else if (operation === 'runCheckNow') {
277
- const pageId = this.getNodeParameter('pageId', i);
306
+ const pageId = getPageId(i);
278
307
  const options = this.getNodeParameter('runCheckOptions', i);
279
308
  const qs = {};
280
309
  if (options.skip_first_notification) {
@@ -290,7 +319,7 @@ class PageCrawl {
290
319
  }
291
320
  }
292
321
  else if (resource === 'check') {
293
- const pageId = this.getNodeParameter('pageId', i);
322
+ const pageId = getPageId(i);
294
323
  if (operation === 'getHistory') {
295
324
  const options = this.getNodeParameter('options', i);
296
325
  const qs = {};
@@ -351,7 +380,7 @@ class PageCrawl {
351
380
  }
352
381
  }
353
382
  else if (resource === 'screenshot') {
354
- const pageId = this.getNodeParameter('pageId', i);
383
+ const pageId = getPageId(i);
355
384
  let endpoint = '';
356
385
  if (operation === 'getLatest') {
357
386
  endpoint = `/pages/${pageId}/checks/latest/screenshot`;
@@ -46,17 +46,58 @@ exports.checkFields = [
46
46
  // check:getHistory
47
47
  // ========================================
48
48
  {
49
- displayName: 'Page ID',
49
+ displayName: 'Page',
50
50
  name: 'pageId',
51
- type: 'string',
51
+ type: 'resourceLocator',
52
52
  required: true,
53
53
  displayOptions: {
54
54
  show: {
55
55
  resource: ['check'],
56
56
  },
57
57
  },
58
- default: '',
59
- description: 'The ID or slug of the page',
58
+ default: { mode: 'list', value: '' },
59
+ description: 'Select a page or enter slug/ID. <a href="https://pagecrawl.io/app/pages" target="_blank">View pages</a>.',
60
+ modes: [
61
+ {
62
+ displayName: 'From List',
63
+ name: 'list',
64
+ type: 'list',
65
+ typeOptions: {
66
+ searchListMethod: 'pageSearch',
67
+ searchable: true,
68
+ },
69
+ },
70
+ {
71
+ displayName: 'By Slug',
72
+ name: 'slug',
73
+ type: 'string',
74
+ placeholder: 'e.g. my-page-name',
75
+ validation: [
76
+ {
77
+ type: 'regex',
78
+ properties: {
79
+ regex: '^[a-z0-9-]+$',
80
+ errorMessage: 'Slug must contain only lowercase letters, numbers, and hyphens',
81
+ },
82
+ },
83
+ ],
84
+ },
85
+ {
86
+ displayName: 'By ID',
87
+ name: 'id',
88
+ type: 'string',
89
+ placeholder: 'e.g. 12345',
90
+ validation: [
91
+ {
92
+ type: 'regex',
93
+ properties: {
94
+ regex: '^[0-9]+$',
95
+ errorMessage: 'ID must be a number',
96
+ },
97
+ },
98
+ ],
99
+ },
100
+ ],
60
101
  },
61
102
  {
62
103
  displayName: 'Options',
@@ -15,15 +15,15 @@ exports.pageOperations = [
15
15
  options: [
16
16
  {
17
17
  name: 'Create',
18
- value: 'create',
19
- description: 'Create a new tracked page',
18
+ value: 'createSimple',
19
+ description: 'Create a new tracked page with guided options',
20
20
  action: 'Create a page',
21
21
  },
22
22
  {
23
- name: 'Create Simple',
24
- value: 'createSimple',
25
- description: 'Create a page with simplified options',
26
- action: 'Create a simple page',
23
+ name: 'Create (Advanced)',
24
+ value: 'create',
25
+ description: 'Create a page with full configuration options',
26
+ action: 'Create an advanced page',
27
27
  },
28
28
  {
29
29
  name: 'Delete',
@@ -133,9 +133,9 @@ exports.pageFields = [
133
133
  // page:get
134
134
  // ========================================
135
135
  {
136
- displayName: 'Page ID',
136
+ displayName: 'Page',
137
137
  name: 'pageId',
138
- type: 'string',
138
+ type: 'resourceLocator',
139
139
  required: true,
140
140
  displayOptions: {
141
141
  show: {
@@ -143,8 +143,49 @@ exports.pageFields = [
143
143
  operation: ['get', 'update', 'delete', 'runCheckNow'],
144
144
  },
145
145
  },
146
- default: '',
147
- description: 'The ID or slug of the page',
146
+ default: { mode: 'list', value: '' },
147
+ description: 'Select a page or enter slug/ID. <a href="https://pagecrawl.io/app/pages" target="_blank">View pages</a>.',
148
+ modes: [
149
+ {
150
+ displayName: 'From List',
151
+ name: 'list',
152
+ type: 'list',
153
+ typeOptions: {
154
+ searchListMethod: 'pageSearch',
155
+ searchable: true,
156
+ },
157
+ },
158
+ {
159
+ displayName: 'By Slug',
160
+ name: 'slug',
161
+ type: 'string',
162
+ placeholder: 'e.g. my-page-name',
163
+ validation: [
164
+ {
165
+ type: 'regex',
166
+ properties: {
167
+ regex: '^[a-z0-9-]+$',
168
+ errorMessage: 'Slug must contain only lowercase letters, numbers, and hyphens',
169
+ },
170
+ },
171
+ ],
172
+ },
173
+ {
174
+ displayName: 'By ID',
175
+ name: 'id',
176
+ type: 'string',
177
+ placeholder: 'e.g. 12345',
178
+ validation: [
179
+ {
180
+ type: 'regex',
181
+ properties: {
182
+ regex: '^[0-9]+$',
183
+ errorMessage: 'ID must be a number',
184
+ },
185
+ },
186
+ ],
187
+ },
188
+ ],
148
189
  },
149
190
  {
150
191
  displayName: 'Options',
@@ -215,6 +256,89 @@ exports.pageFields = [
215
256
  placeholder: 'https://example.com',
216
257
  description: 'The URL to track',
217
258
  },
259
+ {
260
+ displayName: 'Tracking Type',
261
+ name: 'trackingType',
262
+ type: 'options',
263
+ required: true,
264
+ displayOptions: {
265
+ show: {
266
+ resource: ['page'],
267
+ operation: ['createSimple'],
268
+ },
269
+ },
270
+ options: [
271
+ {
272
+ name: 'Full Page',
273
+ value: 'fullpage',
274
+ description: 'Track the entire page for changes',
275
+ },
276
+ {
277
+ name: 'Selected Area',
278
+ value: 'text',
279
+ description: 'Track a specific area using CSS/XPath selector',
280
+ },
281
+ {
282
+ name: 'Number',
283
+ value: 'number',
284
+ description: 'Track a numeric value (e.g., stock count, ratings)',
285
+ },
286
+ {
287
+ name: 'Price',
288
+ value: 'price',
289
+ description: 'Auto-detect and track price changes',
290
+ },
291
+ ],
292
+ default: 'fullpage',
293
+ description: 'What type of content to track',
294
+ },
295
+ {
296
+ displayName: 'Full Page Mode',
297
+ name: 'fullpageMode',
298
+ type: 'options',
299
+ displayOptions: {
300
+ show: {
301
+ resource: ['page'],
302
+ operation: ['createSimple'],
303
+ trackingType: ['fullpage'],
304
+ },
305
+ },
306
+ options: [
307
+ {
308
+ name: 'Everything On Page',
309
+ value: 'everything',
310
+ description: 'Track all visible content on the page',
311
+ },
312
+ {
313
+ name: 'Content Only',
314
+ value: 'content',
315
+ description: 'Track main content, ignoring navigation and sidebars',
316
+ },
317
+ {
318
+ name: 'Reader Mode',
319
+ value: 'reader',
320
+ description: 'Extract and track article content only',
321
+ },
322
+ ],
323
+ default: 'everything',
324
+ description: 'How to extract page content',
325
+ },
326
+ {
327
+ displayName: 'CSS/XPath Selector',
328
+ name: 'selector',
329
+ type: 'string',
330
+ required: true,
331
+ displayOptions: {
332
+ show: {
333
+ resource: ['page'],
334
+ operation: ['createSimple'],
335
+ trackingType: ['text', 'number'],
336
+ },
337
+ },
338
+ default: '',
339
+ placeholder: 'e.g. .price, #stock-count, //div[@class="value"]',
340
+ description: 'CSS selector or XPath expression to locate the element',
341
+ },
218
342
  {
219
343
  displayName: 'Additional Fields',
220
344
  name: 'additionalFields',
@@ -228,13 +352,6 @@ exports.pageFields = [
228
352
  },
229
353
  },
230
354
  options: [
231
- {
232
- displayName: 'CSS/XPath Selector',
233
- name: 'selector',
234
- type: 'string',
235
- default: '',
236
- description: 'CSS or XPath selector. If empty, tracks full page.',
237
- },
238
355
  {
239
356
  displayName: 'Frequency',
240
357
  name: 'frequency',
@@ -46,17 +46,58 @@ exports.screenshotFields = [
46
46
  // screenshot:all
47
47
  // ========================================
48
48
  {
49
- displayName: 'Page ID',
49
+ displayName: 'Page',
50
50
  name: 'pageId',
51
- type: 'string',
51
+ type: 'resourceLocator',
52
52
  required: true,
53
53
  displayOptions: {
54
54
  show: {
55
55
  resource: ['screenshot'],
56
56
  },
57
57
  },
58
- default: '',
59
- description: 'The ID or slug of the page',
58
+ default: { mode: 'list', value: '' },
59
+ description: 'Select a page or enter slug/ID. <a href="https://pagecrawl.io/app/pages" target="_blank">View pages</a>.',
60
+ modes: [
61
+ {
62
+ displayName: 'From List',
63
+ name: 'list',
64
+ type: 'list',
65
+ typeOptions: {
66
+ searchListMethod: 'pageSearch',
67
+ searchable: true,
68
+ },
69
+ },
70
+ {
71
+ displayName: 'By Slug',
72
+ name: 'slug',
73
+ type: 'string',
74
+ placeholder: 'e.g. my-page-name',
75
+ validation: [
76
+ {
77
+ type: 'regex',
78
+ properties: {
79
+ regex: '^[a-z0-9-]+$',
80
+ errorMessage: 'Slug must contain only lowercase letters, numbers, and hyphens',
81
+ },
82
+ },
83
+ ],
84
+ },
85
+ {
86
+ displayName: 'By ID',
87
+ name: 'id',
88
+ type: 'string',
89
+ placeholder: 'e.g. 12345',
90
+ validation: [
91
+ {
92
+ type: 'regex',
93
+ properties: {
94
+ regex: '^[0-9]+$',
95
+ errorMessage: 'ID must be a number',
96
+ },
97
+ },
98
+ ],
99
+ },
100
+ ],
60
101
  },
61
102
  // ========================================
62
103
  // screenshot:getCheckScreenshot, getCheckDiff
@@ -37,7 +37,6 @@ exports.WEBHOOK_PAYLOAD_FIELDS = [
37
37
  'visual_diff',
38
38
  'changed_at',
39
39
  'contents',
40
- 'original',
41
40
  'difference',
42
41
  'human_difference',
43
42
  'page_screenshot_image',
@@ -1,6 +1,11 @@
1
- import { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
1
+ import { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData, ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
2
2
  export declare class PageCrawlTrigger implements INodeType {
3
3
  description: INodeTypeDescription;
4
+ methods: {
5
+ listSearch: {
6
+ pageSearch(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult>;
7
+ };
8
+ };
4
9
  webhookMethods: {
5
10
  default: {
6
11
  checkExists(this: IHookFunctions): Promise<boolean>;
@@ -54,10 +54,51 @@ class PageCrawlTrigger {
54
54
  },
55
55
  {
56
56
  displayName: 'Page',
57
- name: 'pageId',
58
- type: 'string',
59
- default: '',
60
- description: 'Specific page ID to monitor (leave empty for all pages)',
57
+ name: 'page',
58
+ type: 'resourceLocator',
59
+ default: { mode: 'list', value: '' },
60
+ description: 'Select a page to monitor or leave empty for all pages. Don\'t see your page? <a href="https://pagecrawl.io/app/pages" target="_blank">Create one on PageCrawl</a>.',
61
+ modes: [
62
+ {
63
+ displayName: 'From List',
64
+ name: 'list',
65
+ type: 'list',
66
+ typeOptions: {
67
+ searchListMethod: 'pageSearch',
68
+ searchable: true,
69
+ },
70
+ },
71
+ {
72
+ displayName: 'By Slug',
73
+ name: 'slug',
74
+ type: 'string',
75
+ placeholder: 'e.g. my-page-name',
76
+ validation: [
77
+ {
78
+ type: 'regex',
79
+ properties: {
80
+ regex: '^[a-z0-9-]+$',
81
+ errorMessage: 'Slug must contain only lowercase letters, numbers, and hyphens',
82
+ },
83
+ },
84
+ ],
85
+ },
86
+ {
87
+ displayName: 'By ID',
88
+ name: 'id',
89
+ type: 'string',
90
+ placeholder: 'e.g. 12345',
91
+ validation: [
92
+ {
93
+ type: 'regex',
94
+ properties: {
95
+ regex: '^[0-9]+$',
96
+ errorMessage: 'ID must be a number',
97
+ },
98
+ },
99
+ ],
100
+ },
101
+ ],
61
102
  },
62
103
  {
63
104
  displayName: 'Payload Fields',
@@ -67,7 +108,7 @@ class PageCrawlTrigger {
67
108
  name: field.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
68
109
  value: field,
69
110
  })),
70
- default: ['id', 'title', 'status', 'changed_at', 'difference', 'page'],
111
+ default: ['id', 'title', 'status', 'changed_at', 'difference', 'page', 'contents', 'html_difference'],
71
112
  description: 'Fields to include in the webhook payload',
72
113
  },
73
114
  {
@@ -88,13 +129,37 @@ class PageCrawlTrigger {
88
129
  },
89
130
  ],
90
131
  };
132
+ this.methods = {
133
+ listSearch: {
134
+ async pageSearch(filter) {
135
+ const baseUrl = 'https://pagecrawl.io';
136
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pageCrawlApi', {
137
+ method: 'GET',
138
+ url: `${baseUrl}/api/pages`,
139
+ json: true,
140
+ });
141
+ const pages = response.data || response;
142
+ let results = pages.map((page) => ({
143
+ name: page.name || page.url,
144
+ value: page.slug,
145
+ url: `https://pagecrawl.io/app/pages/${page.slug}`,
146
+ }));
147
+ // Filter results if search term provided
148
+ if (filter) {
149
+ const filterLower = filter.toLowerCase();
150
+ results = results.filter((page) => page.name.toLowerCase().includes(filterLower) ||
151
+ page.value.toLowerCase().includes(filterLower));
152
+ }
153
+ return { results };
154
+ },
155
+ },
156
+ };
91
157
  this.webhookMethods = {
92
158
  default: {
93
159
  async checkExists() {
94
160
  const webhookData = this.getWorkflowStaticData('node');
95
161
  const webhookUrl = this.getNodeWebhookUrl('default');
96
- const credentials = await this.getCredentials('pageCrawlApi');
97
- const baseUrl = (credentials.baseUrl || 'https://pagecrawl.io').replace(/\/+$/, '');
162
+ const baseUrl = 'https://pagecrawl.io';
98
163
  if (!webhookData.webhookId) {
99
164
  return false;
100
165
  }
@@ -117,16 +182,16 @@ class PageCrawlTrigger {
117
182
  async create() {
118
183
  const webhookUrl = this.getNodeWebhookUrl('default');
119
184
  const webhookData = this.getWorkflowStaticData('node');
120
- const pageId = this.getNodeParameter('pageId', '');
185
+ const pageLocator = this.getNodeParameter('page', { mode: 'list', value: '' });
186
+ const pageValue = pageLocator.value || '';
121
187
  const payloadFields = this.getNodeParameter('payloadFields', []);
122
- const credentials = await this.getCredentials('pageCrawlApi');
123
- const baseUrl = (credentials.baseUrl || 'https://pagecrawl.io').replace(/\/+$/, '');
188
+ const baseUrl = 'https://pagecrawl.io';
124
189
  const body = {
125
190
  target_url: webhookUrl,
126
191
  event_type: 'n8n',
127
192
  };
128
- if (pageId) {
129
- body.change_id = pageId;
193
+ if (pageValue) {
194
+ body.change_id = pageValue;
130
195
  }
131
196
  if (payloadFields.length > 0) {
132
197
  body.payload_fields = payloadFields;
@@ -151,8 +216,7 @@ class PageCrawlTrigger {
151
216
  },
152
217
  async delete() {
153
218
  const webhookData = this.getWorkflowStaticData('node');
154
- const credentials = await this.getCredentials('pageCrawlApi');
155
- const baseUrl = (credentials.baseUrl || 'https://pagecrawl.io').replace(/\/+$/, '');
219
+ const baseUrl = 'https://pagecrawl.io';
156
220
  if (!webhookData.webhookId) {
157
221
  return true;
158
222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagecrawl/n8n-nodes-pagecrawl",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "n8n node for PageCrawl.io - Website monitoring and change detection",
5
5
  "keywords": [
6
6
  "n8n",