@seranking/n8n-nodes-seranking 1.3.9 → 1.4.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.
@@ -90,31 +90,11 @@ exports.aiSearchFields = [
90
90
  },
91
91
  },
92
92
  options: [
93
- {
94
- name: 'AI Overview',
95
- value: 'ai-overview',
96
- description: 'Google AI Overview results',
97
- },
98
- {
99
- name: 'ChatGPT',
100
- value: 'chatgpt',
101
- description: 'OpenAI ChatGPT results',
102
- },
103
- {
104
- name: 'Perplexity',
105
- value: 'perplexity',
106
- description: 'Perplexity AI results',
107
- },
108
- {
109
- name: 'Gemini',
110
- value: 'gemini',
111
- description: 'Google Gemini results',
112
- },
113
- {
114
- name: 'AI Mode',
115
- value: 'ai-mode',
116
- description: 'AI Mode results',
117
- },
93
+ { name: 'AI Overview', value: 'ai-overview', description: 'Google AI Overview results' },
94
+ { name: 'ChatGPT', value: 'chatgpt', description: 'OpenAI ChatGPT results' },
95
+ { name: 'Perplexity', value: 'perplexity', description: 'Perplexity AI results' },
96
+ { name: 'Gemini', value: 'gemini', description: 'Google Gemini results' },
97
+ { name: 'AI Mode', value: 'ai-mode', description: 'AI Mode results' },
118
98
  ],
119
99
  default: 'chatgpt',
120
100
  description: 'The LLM engine to query',
@@ -144,24 +124,35 @@ exports.aiSearchFields = [
144
124
  },
145
125
  },
146
126
  options: [
147
- {
148
- name: 'Base Domain',
149
- value: 'base_domain',
150
- description: 'Aggregate by registrable domain, includes all subdomains',
151
- },
152
- {
153
- name: 'Domain',
154
- value: 'domain',
155
- description: 'Exact host only, no subdomain aggregation',
127
+ { name: 'Base Domain', value: 'base_domain', description: 'Aggregate by registrable domain, includes all subdomains' },
128
+ { name: 'Domain', value: 'domain', description: 'Exact host only, no subdomain aggregation' },
129
+ { name: 'URL', value: 'url', description: 'Exact URL including path and query' },
130
+ ],
131
+ default: 'base_domain',
132
+ description: 'Scope of analysis - base_domain is recommended for most use cases',
133
+ },
134
+ {
135
+ displayName: 'Additional Fields',
136
+ name: 'additionalFields',
137
+ type: 'collection',
138
+ placeholder: 'Add Field',
139
+ default: {},
140
+ displayOptions: {
141
+ show: {
142
+ resource: ['aiSearch'],
143
+ operation: ['getOverview'],
156
144
  },
145
+ },
146
+ options: [
157
147
  {
158
- name: 'URL',
159
- value: 'url',
160
- description: 'Exact URL including path and query',
148
+ displayName: 'Brand',
149
+ name: 'brand',
150
+ type: 'string',
151
+ default: '',
152
+ placeholder: 'SE Ranking',
153
+ description: 'Brand name to filter the brand presence analysis',
161
154
  },
162
155
  ],
163
- default: 'base_domain',
164
- description: 'Scope of analysis - base_domain is recommended for most use cases',
165
156
  },
166
157
  {
167
158
  displayName: 'Additional Fields',
@@ -181,21 +172,9 @@ exports.aiSearchFields = [
181
172
  name: 'sort',
182
173
  type: 'options',
183
174
  options: [
184
- {
185
- name: 'Volume',
186
- value: 'volume',
187
- description: 'Sort by search volume',
188
- },
189
- {
190
- name: 'Type',
191
- value: 'type',
192
- description: 'Sort by appearance type (Link, Brand, etc.)',
193
- },
194
- {
195
- name: 'Snippet Length',
196
- value: 'snippet_length',
197
- description: 'Sort by length of the LLM snippet',
198
- },
175
+ { name: 'Volume', value: 'volume', description: 'Sort by search volume' },
176
+ { name: 'Type', value: 'type', description: 'Sort by appearance type (Link, Brand, etc.)' },
177
+ { name: 'Snippet Length', value: 'snippet_length', description: 'Sort by length of the LLM snippet' },
199
178
  ],
200
179
  default: 'volume',
201
180
  description: 'Field to sort results by',
@@ -205,16 +184,8 @@ exports.aiSearchFields = [
205
184
  name: 'sortOrder',
206
185
  type: 'options',
207
186
  options: [
208
- {
209
- name: 'Descending',
210
- value: 'desc',
211
- description: 'Highest to lowest',
212
- },
213
- {
214
- name: 'Ascending',
215
- value: 'asc',
216
- description: 'Lowest to highest',
217
- },
187
+ { name: 'Descending', value: 'desc', description: 'Highest to lowest' },
188
+ { name: 'Ascending', value: 'asc', description: 'Lowest to highest' },
218
189
  ],
219
190
  default: 'desc',
220
191
  description: 'Order of sorting',
@@ -225,10 +196,7 @@ exports.aiSearchFields = [
225
196
  type: 'number',
226
197
  default: 100,
227
198
  description: 'Maximum number of results to return per page (max 1000)',
228
- typeOptions: {
229
- minValue: 1,
230
- maxValue: 1000,
231
- },
199
+ typeOptions: { minValue: 1, maxValue: 1000 },
232
200
  },
233
201
  {
234
202
  displayName: 'Offset',
@@ -236,9 +204,67 @@ exports.aiSearchFields = [
236
204
  type: 'number',
237
205
  default: 0,
238
206
  description: 'Number of results to skip for pagination',
239
- typeOptions: {
240
- minValue: 0,
241
- },
207
+ typeOptions: { minValue: 0 },
208
+ },
209
+ {
210
+ displayName: 'Volume From',
211
+ name: 'volumeFrom',
212
+ type: 'number',
213
+ default: 0,
214
+ description: 'Minimum monthly search volume for prompts',
215
+ },
216
+ {
217
+ displayName: 'Volume To',
218
+ name: 'volumeTo',
219
+ type: 'number',
220
+ default: 0,
221
+ description: 'Maximum monthly search volume (0 = no limit)',
222
+ },
223
+ {
224
+ displayName: 'Keyword Word Count From',
225
+ name: 'keywordCountFrom',
226
+ type: 'number',
227
+ default: 0,
228
+ description: 'Minimum number of words in a prompt',
229
+ typeOptions: { minValue: 1 },
230
+ },
231
+ {
232
+ displayName: 'Keyword Word Count To',
233
+ name: 'keywordCountTo',
234
+ type: 'number',
235
+ default: 0,
236
+ description: 'Maximum number of words in a prompt (0 = no limit)',
237
+ },
238
+ {
239
+ displayName: 'Characters Count From',
240
+ name: 'charactersCountFrom',
241
+ type: 'number',
242
+ default: 0,
243
+ description: 'Minimum character length of a prompt',
244
+ typeOptions: { minValue: 1 },
245
+ },
246
+ {
247
+ displayName: 'Characters Count To',
248
+ name: 'charactersCountTo',
249
+ type: 'number',
250
+ default: 0,
251
+ description: 'Maximum character length of a prompt (0 = no limit)',
252
+ },
253
+ {
254
+ displayName: 'Include Keywords Containing',
255
+ name: 'multiKeywordIncluded',
256
+ type: 'string',
257
+ default: '',
258
+ placeholder: 'best, top, review',
259
+ description: 'Comma-separated words that must appear in the prompt text',
260
+ },
261
+ {
262
+ displayName: 'Exclude Keywords Containing',
263
+ name: 'multiKeywordExcluded',
264
+ type: 'string',
265
+ default: '',
266
+ placeholder: 'free, cheap',
267
+ description: 'Comma-separated words that must NOT appear in the prompt text',
242
268
  },
243
269
  ],
244
270
  },
@@ -261,7 +287,7 @@ exports.aiSearchFields = [
261
287
  displayName: 'Primary Brand',
262
288
  name: 'primaryBrand',
263
289
  type: 'string',
264
- required: true,
290
+ required: false,
265
291
  displayOptions: {
266
292
  show: {
267
293
  resource: ['aiSearch'],
@@ -306,7 +332,7 @@ exports.aiSearchFields = [
306
332
  type: 'string',
307
333
  default: '',
308
334
  placeholder: 'Semrush',
309
- description: 'Competitor brand name',
335
+ description: 'Competitor brand name (optional)',
310
336
  },
311
337
  ],
312
338
  },
@@ -325,16 +351,9 @@ exports.aiSearchFields = [
325
351
  },
326
352
  },
327
353
  options: [
328
- {
329
- name: 'Base Domain',
330
- value: 'base_domain',
331
- description: 'Aggregate by registrable domain',
332
- },
333
- {
334
- name: 'Exact URL',
335
- value: 'exact_url',
336
- description: 'Exact URL matching',
337
- },
354
+ { name: 'Base Domain', value: 'base_domain', description: 'Aggregate by registrable domain, includes all subdomains' },
355
+ { name: 'Domain', value: 'domain', description: 'Exact host only, no subdomain aggregation' },
356
+ { name: 'URL', value: 'url', description: 'Exact URL including path and query' },
338
357
  ],
339
358
  default: 'base_domain',
340
359
  description: 'Scope of analysis',
@@ -351,26 +370,11 @@ exports.aiSearchFields = [
351
370
  },
352
371
  },
353
372
  options: [
354
- {
355
- name: 'AI Overview (Google)',
356
- value: 'ai-overview',
357
- },
358
- {
359
- name: 'ChatGPT',
360
- value: 'chatgpt',
361
- },
362
- {
363
- name: 'Perplexity',
364
- value: 'perplexity',
365
- },
366
- {
367
- name: 'Gemini',
368
- value: 'gemini',
369
- },
370
- {
371
- name: 'AI Mode',
372
- value: 'ai-mode',
373
- },
373
+ { name: 'AI Overview (Google)', value: 'ai-overview' },
374
+ { name: 'ChatGPT', value: 'chatgpt' },
375
+ { name: 'Perplexity', value: 'perplexity' },
376
+ { name: 'Gemini', value: 'gemini' },
377
+ { name: 'AI Mode', value: 'ai-mode' },
374
378
  ],
375
379
  default: ['ai-overview', 'chatgpt', 'perplexity'],
376
380
  description: 'LLM engines to compare across',
@@ -3,6 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AiSearchOperations = AiSearchOperations;
4
4
  const apiRequest_1 = require("../../utils/apiRequest");
5
5
  const validators_1 = require("../../utils/validators");
6
+ function buildMultiKeywordFilter(csv) {
7
+ return csv
8
+ .split(',')
9
+ .map((w) => w.trim())
10
+ .filter(Boolean)
11
+ .map((word) => [{ type: 'contains', value: word }]);
12
+ }
6
13
  async function AiSearchOperations(index) {
7
14
  const operation = this.getNodeParameter('operation', index);
8
15
  let endpoint = '';
@@ -13,11 +20,14 @@ async function AiSearchOperations(index) {
13
20
  const engine = this.getNodeParameter('engine', index);
14
21
  const source = this.getNodeParameter('source', index);
15
22
  const scope = this.getNodeParameter('scope', index, 'base_domain');
23
+ const additionalFields = this.getNodeParameter('additionalFields', index, {});
16
24
  endpoint = '/ai-search/overview/by-engine/time-series';
17
25
  params.target = (0, validators_1.validateDomain)(domain);
18
26
  params.engine = engine;
19
27
  params.source = (0, validators_1.validateSource)(source);
20
28
  params.scope = scope;
29
+ if (additionalFields.brand)
30
+ params.brand = additionalFields.brand;
21
31
  break;
22
32
  }
23
33
  case 'discoverBrand': {
@@ -49,6 +59,24 @@ async function AiSearchOperations(index) {
49
59
  params.limit = additionalFields.limit;
50
60
  if (additionalFields.offset)
51
61
  params.offset = additionalFields.offset;
62
+ if (additionalFields.volumeFrom)
63
+ params['filter[volume][from]'] = additionalFields.volumeFrom;
64
+ if (additionalFields.volumeTo)
65
+ params['filter[volume][to]'] = additionalFields.volumeTo;
66
+ if (additionalFields.keywordCountFrom)
67
+ params['filter[keyword_count][from]'] = additionalFields.keywordCountFrom;
68
+ if (additionalFields.keywordCountTo)
69
+ params['filter[keyword_count][to]'] = additionalFields.keywordCountTo;
70
+ if (additionalFields.charactersCountFrom)
71
+ params['filter[characters_count][from]'] = additionalFields.charactersCountFrom;
72
+ if (additionalFields.charactersCountTo)
73
+ params['filter[characters_count][to]'] = additionalFields.charactersCountTo;
74
+ if (additionalFields.multiKeywordIncluded) {
75
+ params['filter[multi_keyword_included]'] = JSON.stringify(buildMultiKeywordFilter(additionalFields.multiKeywordIncluded));
76
+ }
77
+ if (additionalFields.multiKeywordExcluded) {
78
+ params['filter[multi_keyword_excluded]'] = JSON.stringify(buildMultiKeywordFilter(additionalFields.multiKeywordExcluded));
79
+ }
52
80
  break;
53
81
  }
54
82
  case 'getPromptsByBrand': {
@@ -71,6 +99,24 @@ async function AiSearchOperations(index) {
71
99
  params.limit = additionalFields.limit;
72
100
  if (additionalFields.offset)
73
101
  params.offset = additionalFields.offset;
102
+ if (additionalFields.volumeFrom)
103
+ params['filter[volume][from]'] = additionalFields.volumeFrom;
104
+ if (additionalFields.volumeTo)
105
+ params['filter[volume][to]'] = additionalFields.volumeTo;
106
+ if (additionalFields.keywordCountFrom)
107
+ params['filter[keyword_count][from]'] = additionalFields.keywordCountFrom;
108
+ if (additionalFields.keywordCountTo)
109
+ params['filter[keyword_count][to]'] = additionalFields.keywordCountTo;
110
+ if (additionalFields.charactersCountFrom)
111
+ params['filter[characters_count][from]'] = additionalFields.charactersCountFrom;
112
+ if (additionalFields.charactersCountTo)
113
+ params['filter[characters_count][to]'] = additionalFields.charactersCountTo;
114
+ if (additionalFields.multiKeywordIncluded) {
115
+ params['filter[multi_keyword_included]'] = JSON.stringify(buildMultiKeywordFilter(additionalFields.multiKeywordIncluded));
116
+ }
117
+ if (additionalFields.multiKeywordExcluded) {
118
+ params['filter[multi_keyword_excluded]'] = JSON.stringify(buildMultiKeywordFilter(additionalFields.multiKeywordExcluded));
119
+ }
74
120
  break;
75
121
  }
76
122
  case 'getLeaderboard': {
@@ -83,30 +129,30 @@ async function AiSearchOperations(index) {
83
129
  if (!primaryTarget || primaryTarget.trim() === '') {
84
130
  throw new Error('Primary target domain is required');
85
131
  }
86
- if (!primaryBrand || primaryBrand.trim() === '') {
87
- throw new Error('Primary brand name is required');
88
- }
89
132
  const competitors = [];
90
133
  if (competitorsData.competitorValues && Array.isArray(competitorsData.competitorValues)) {
91
134
  for (const comp of competitorsData.competitorValues) {
92
- if (comp.target && comp.brand) {
93
- competitors.push({
135
+ if (comp.target) {
136
+ const entry = {
94
137
  target: (0, validators_1.validateDomain)(comp.target),
95
- brand: comp.brand.trim(),
96
- });
138
+ };
139
+ if (comp.brand)
140
+ entry.brand = comp.brand.trim();
141
+ competitors.push(entry);
97
142
  }
98
143
  }
99
144
  }
100
145
  const body = {
101
146
  primary: {
102
147
  target: (0, validators_1.validateDomain)(primaryTarget),
103
- brand: primaryBrand.trim(),
104
148
  },
105
149
  competitors,
106
150
  scope,
107
151
  source: (0, validators_1.validateSource)(source),
108
152
  engines,
109
153
  };
154
+ if (primaryBrand)
155
+ body.primary.brand = primaryBrand.trim();
110
156
  return await apiRequest_1.apiRequest.call(this, 'POST', '/ai-search/overview/leaderboard', body, {}, index);
111
157
  }
112
158
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seranking/n8n-nodes-seranking",
3
- "version": "1.3.9",
3
+ "version": "1.4.0",
4
4
  "description": "n8n connector for SE Ranking API - AI Search, Backlinks, Domain Analysis, Keyword Research, and Website Audit",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/seranking/n8n-nodes-seranking",