@adobe/mysticat-shared-seo-client 1.2.2 → 1.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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/client.js +63 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [@adobe/mysticat-shared-seo-client-v1.3.0](https://github.com/adobe/spacecat-shared/compare/@adobe/mysticat-shared-seo-client-v1.2.3...@adobe/mysticat-shared-seo-client-v1.3.0) (2026-04-28)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **seo-client:** return keywords[] array from getPaidPages() ([#1564](https://github.com/adobe/spacecat-shared/issues/1564)) ([71be968](https://github.com/adobe/spacecat-shared/commit/71be968ac133fc3c0d547d70e9663fcf2fe90437))
|
|
6
|
+
|
|
7
|
+
## [@adobe/mysticat-shared-seo-client-v1.2.3](https://github.com/adobe/spacecat-shared/compare/@adobe/mysticat-shared-seo-client-v1.2.2...@adobe/mysticat-shared-seo-client-v1.2.3) (2026-04-15)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **seo-client:** omit display_date to avoid 5x historical pricing ([#1537](https://github.com/adobe/spacecat-shared/issues/1537)) ([4ffc683](https://github.com/adobe/spacecat-shared/commit/4ffc683db492d10414c2bc5c23804ec42d88b2c5))
|
|
12
|
+
|
|
1
13
|
## [@adobe/mysticat-shared-seo-client-v1.2.2](https://github.com/adobe/spacecat-shared/compare/@adobe/mysticat-shared-seo-client-v1.2.1...@adobe/mysticat-shared-seo-client-v1.2.2) (2026-04-14)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -15,7 +15,7 @@ import { context as h2, h1 } from '@adobe/fetch';
|
|
|
15
15
|
|
|
16
16
|
import { ENDPOINTS } from './endpoints.js';
|
|
17
17
|
import {
|
|
18
|
-
parseCsvResponse, coerceValue, getLimit, toApiDate, fromApiDate,
|
|
18
|
+
parseCsvResponse, coerceValue, getLimit, toApiDate, fromApiDate, buildFilter,
|
|
19
19
|
extractBrand, INTENT_CODES,
|
|
20
20
|
} from './utils.js';
|
|
21
21
|
|
|
@@ -340,11 +340,28 @@ export default class SeoClient {
|
|
|
340
340
|
};
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Fetch paid (PPC) pages and their keyword data from Semrush.
|
|
345
|
+
*
|
|
346
|
+
* Per-keyword CPC is returned as float dollars (e.g., 3.95) with null for unknown.
|
|
347
|
+
* This deliberately differs from getOrganicKeywords() which returns CPC as integer
|
|
348
|
+
* cents (e.g., 395) with 0 for unknown. The float-dollars convention was chosen for
|
|
349
|
+
* the keywords[] array because downstream consumers (import-worker, Mystique) expect
|
|
350
|
+
* dollars and the per-keyword CPC should not go through the cents round-trip used by
|
|
351
|
+
* the aggregate `value` field.
|
|
352
|
+
*
|
|
353
|
+
* @param {string} url - Domain to query
|
|
354
|
+
* @param {Object} [opts] - Options
|
|
355
|
+
* @param {string} [opts.date] - Report date
|
|
356
|
+
* @param {number} [opts.limit=200] - Max pages to return
|
|
357
|
+
* @param {string} [opts.region] - Limit to specific region
|
|
358
|
+
* @returns {Promise<{result: {pages: Array}, fullAuditRef: string}>}
|
|
359
|
+
*/
|
|
343
360
|
async getPaidPages(url, opts = {}) {
|
|
344
361
|
if (typeof opts !== 'object' || opts === null) {
|
|
345
362
|
throw new Error('Second argument must be an options object, not a positional value');
|
|
346
363
|
}
|
|
347
|
-
const { date
|
|
364
|
+
const { date, limit = 200, region } = opts;
|
|
348
365
|
if (!hasText(url)) {
|
|
349
366
|
throw new Error(`Invalid URL: ${url}`);
|
|
350
367
|
}
|
|
@@ -358,15 +375,23 @@ export default class SeoClient {
|
|
|
358
375
|
// Capped at MAX_PAID_KEYWORDS_FETCH to bound cost.
|
|
359
376
|
const fetchLimit = Math.min(effectiveLimit * 10, MAX_PAID_KEYWORDS_FETCH);
|
|
360
377
|
|
|
361
|
-
const dbResults = await this.fanOut(databases, (db) =>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
378
|
+
const dbResults = await this.fanOut(databases, (db) => {
|
|
379
|
+
const params = {
|
|
380
|
+
type: ep.type,
|
|
381
|
+
domain: url,
|
|
382
|
+
database: db,
|
|
383
|
+
display_limit: fetchLimit,
|
|
384
|
+
export_columns: ep.columns,
|
|
385
|
+
...ep.defaultParams,
|
|
386
|
+
};
|
|
387
|
+
// Omitting display_date returns the provider's latest snapshot at live pricing
|
|
388
|
+
// (5x cheaper). The returned data may differ slightly from a pinned monthly
|
|
389
|
+
// snapshot, which is acceptable for our use case.
|
|
390
|
+
if (date) {
|
|
391
|
+
params.display_date = toApiDate(date);
|
|
392
|
+
}
|
|
393
|
+
return this.sendRawRequest(params, ep.path);
|
|
394
|
+
}, 'getPaidPages');
|
|
370
395
|
|
|
371
396
|
// Group keywords by URL across all databases
|
|
372
397
|
const pageMap = new Map();
|
|
@@ -412,6 +437,15 @@ export default class SeoClient {
|
|
|
412
437
|
),
|
|
413
438
|
0,
|
|
414
439
|
),
|
|
440
|
+
keywords: page.keywords.map((kw) => ({
|
|
441
|
+
keyword: kw.Ph,
|
|
442
|
+
traffic: kw.kwTraffic,
|
|
443
|
+
cpc: coerceValue(kw.Cp, 'float') ?? null,
|
|
444
|
+
serp_title: kw.Tt || null,
|
|
445
|
+
position: coerceValue(kw.Po, 'int') ?? null,
|
|
446
|
+
volume: coerceValue(kw.Nq, 'int') ?? null,
|
|
447
|
+
country: kw.db.toUpperCase(),
|
|
448
|
+
})),
|
|
415
449
|
};
|
|
416
450
|
});
|
|
417
451
|
|
|
@@ -425,7 +459,7 @@ export default class SeoClient {
|
|
|
425
459
|
if (typeof opts !== 'object' || opts === null) {
|
|
426
460
|
throw new Error('Second argument must be an options object, not a positional value');
|
|
427
461
|
}
|
|
428
|
-
const { date
|
|
462
|
+
const { date, region } = opts;
|
|
429
463
|
if (!hasText(url)) {
|
|
430
464
|
throw new Error(`Invalid URL: ${url}`);
|
|
431
465
|
}
|
|
@@ -433,14 +467,22 @@ export default class SeoClient {
|
|
|
433
467
|
const ep = ENDPOINTS.metrics;
|
|
434
468
|
const databases = getDatabases(region);
|
|
435
469
|
|
|
436
|
-
const dbResults = await this.fanOut(databases, (db) =>
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
470
|
+
const dbResults = await this.fanOut(databases, (db) => {
|
|
471
|
+
const params = {
|
|
472
|
+
type: ep.type,
|
|
473
|
+
domain: url,
|
|
474
|
+
database: db,
|
|
475
|
+
export_columns: ep.columns,
|
|
476
|
+
...ep.defaultParams,
|
|
477
|
+
};
|
|
478
|
+
// Omitting display_date returns the provider's latest snapshot at live pricing
|
|
479
|
+
// (5x cheaper). The returned data may differ slightly from a pinned monthly
|
|
480
|
+
// snapshot, which is acceptable for our use case.
|
|
481
|
+
if (date) {
|
|
482
|
+
params.display_date = toApiDate(date);
|
|
483
|
+
}
|
|
484
|
+
return this.sendRawRequest(params, ep.path);
|
|
485
|
+
}, 'getMetrics');
|
|
444
486
|
|
|
445
487
|
const metrics = {
|
|
446
488
|
org_keywords: 0,
|
|
@@ -758,7 +800,7 @@ export default class SeoClient {
|
|
|
758
800
|
}
|
|
759
801
|
|
|
760
802
|
// eslint-disable-next-line no-unused-vars, class-methods-use-this
|
|
761
|
-
async getMetricsByCountry(url, date
|
|
803
|
+
async getMetricsByCountry(url, date) {
|
|
762
804
|
return STUB_RESPONSE;
|
|
763
805
|
}
|
|
764
806
|
}
|