recker 1.0.75 → 1.0.76-next.c8fd7f9
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/dist/browser/search/google.d.ts +70 -0
- package/dist/browser/search/google.js +617 -0
- package/dist/browser/seo/index.d.ts +2 -0
- package/dist/browser/seo/index.js +1 -0
- package/dist/browser/seo/keyword-campaign.d.ts +107 -0
- package/dist/browser/seo/keyword-campaign.js +380 -0
- package/dist/search/google.d.ts +3 -0
- package/dist/search/google.js +137 -0
- package/dist/search/index.d.ts +1 -1
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/dist/seo/keyword-campaign.d.ts +107 -0
- package/dist/seo/keyword-campaign.js +380 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { SearchTransport } from '../search/google.js';
|
|
2
|
+
import type { SeoReport } from './types.js';
|
|
3
|
+
export type KeywordCampaignSource = 'discovered' | 'preset';
|
|
4
|
+
export type CampaignResultPlacement = 'ad' | 'organic' | 'unknown';
|
|
5
|
+
export interface KeywordCampaignSeedInput {
|
|
6
|
+
keyword: string;
|
|
7
|
+
source?: KeywordCampaignSource;
|
|
8
|
+
sourcePage?: string;
|
|
9
|
+
weight?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface KeywordCampaignSeed {
|
|
12
|
+
keyword: string;
|
|
13
|
+
normalizedKeyword: string;
|
|
14
|
+
source: KeywordCampaignSource;
|
|
15
|
+
sourcePage?: string;
|
|
16
|
+
weight: number;
|
|
17
|
+
}
|
|
18
|
+
export interface KeywordCampaignOptions {
|
|
19
|
+
targetUrl: string;
|
|
20
|
+
discoveredKeywords?: KeywordCampaignSeedInput[] | string[];
|
|
21
|
+
presetKeywords?: string[];
|
|
22
|
+
minKeywordLength?: number;
|
|
23
|
+
maxQueries?: number;
|
|
24
|
+
maxResultsPerQuery?: number;
|
|
25
|
+
transport?: SearchTransport;
|
|
26
|
+
timeout?: number;
|
|
27
|
+
country?: string;
|
|
28
|
+
gl?: string;
|
|
29
|
+
hl?: string;
|
|
30
|
+
minWeight?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface KeywordCampaignCompetitorResult {
|
|
33
|
+
domain: string;
|
|
34
|
+
rank: number;
|
|
35
|
+
placement: CampaignResultPlacement;
|
|
36
|
+
placementHint?: string;
|
|
37
|
+
url: string;
|
|
38
|
+
title: string;
|
|
39
|
+
}
|
|
40
|
+
export interface KeywordCampaignResult {
|
|
41
|
+
keyword: string;
|
|
42
|
+
source: KeywordCampaignSource;
|
|
43
|
+
sourcePage?: string;
|
|
44
|
+
sourceWeight: number;
|
|
45
|
+
found: boolean;
|
|
46
|
+
bestPosition: number | null;
|
|
47
|
+
totalChecked: number;
|
|
48
|
+
matchedUrl?: string;
|
|
49
|
+
matchedTitle?: string;
|
|
50
|
+
matchedDisplayUrl?: string;
|
|
51
|
+
placement: CampaignResultPlacement;
|
|
52
|
+
placementHint?: string;
|
|
53
|
+
searchUrl: string;
|
|
54
|
+
searchTransport: SearchTransport;
|
|
55
|
+
competitors: KeywordCampaignCompetitorResult[];
|
|
56
|
+
}
|
|
57
|
+
export interface KeywordCampaignPageStats {
|
|
58
|
+
pageUrl: string;
|
|
59
|
+
tracked: number;
|
|
60
|
+
found: number;
|
|
61
|
+
avgPosition: number | null;
|
|
62
|
+
top3: number;
|
|
63
|
+
top10: number;
|
|
64
|
+
}
|
|
65
|
+
export interface KeywordCampaignSummary {
|
|
66
|
+
queriesRequested: number;
|
|
67
|
+
queriesExecuted: number;
|
|
68
|
+
queriesFound: number;
|
|
69
|
+
avgTopPosition: number | null;
|
|
70
|
+
top3Count: number;
|
|
71
|
+
top10Count: 0 | number;
|
|
72
|
+
topOrganicCompetitors: KeywordCampaignCompetitorSummary[];
|
|
73
|
+
topPaidCompetitors: KeywordCampaignCompetitorSummary[];
|
|
74
|
+
competitorCoverage: {
|
|
75
|
+
organicUniqueDomains: number;
|
|
76
|
+
paidUniqueDomains: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export interface KeywordCampaignCompetitorSummary {
|
|
80
|
+
domain: string;
|
|
81
|
+
organicQueries: number;
|
|
82
|
+
paidQueries: number;
|
|
83
|
+
bestOrganicRank: number | null;
|
|
84
|
+
bestPaidRank: number | null;
|
|
85
|
+
matchedKeywords: number;
|
|
86
|
+
}
|
|
87
|
+
export interface CampaignActivitySignal {
|
|
88
|
+
active: boolean;
|
|
89
|
+
confidence: 'high' | 'medium' | 'low';
|
|
90
|
+
evidence: string[];
|
|
91
|
+
}
|
|
92
|
+
export interface KeywordCampaignReport {
|
|
93
|
+
targetUrl: string;
|
|
94
|
+
targetDomain: string;
|
|
95
|
+
results: KeywordCampaignResult[];
|
|
96
|
+
summary: KeywordCampaignSummary;
|
|
97
|
+
pageComparison: KeywordCampaignPageStats[];
|
|
98
|
+
campaign: CampaignActivitySignal;
|
|
99
|
+
}
|
|
100
|
+
export interface KeywordCampaignExtractionOptions {
|
|
101
|
+
maxKeywords?: number;
|
|
102
|
+
minKeywordLength?: number;
|
|
103
|
+
}
|
|
104
|
+
export declare function extractKeywordCampaignSeedsFromReport(report: SeoReport, options?: KeywordCampaignExtractionOptions & {
|
|
105
|
+
sourcePage?: string;
|
|
106
|
+
}): KeywordCampaignSeed[];
|
|
107
|
+
export declare function analyzeKeywordCampaign(options: KeywordCampaignOptions): Promise<KeywordCampaignReport>;
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { searchGoogleAdvanced } from '../search/google.js';
|
|
2
|
+
function normalizeKeyword(value) {
|
|
3
|
+
return value
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.replace(/[^\p{L}\p{N}\s-]+/gu, ' ')
|
|
6
|
+
.replace(/\s+/g, ' ')
|
|
7
|
+
.trim();
|
|
8
|
+
}
|
|
9
|
+
function normalizeHost(target) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = new URL(target);
|
|
12
|
+
return parsed.hostname.replace(/^www\./, '').toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return target.replace(/^www\./, '').toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function clampNumber(value, fallback) {
|
|
19
|
+
if (value === undefined || !Number.isFinite(value) || value <= 0)
|
|
20
|
+
return fallback;
|
|
21
|
+
return Math.max(1, Math.floor(value));
|
|
22
|
+
}
|
|
23
|
+
function normalizePlacement(value) {
|
|
24
|
+
if (value === 'ad')
|
|
25
|
+
return 'ad';
|
|
26
|
+
if (value === 'organic')
|
|
27
|
+
return 'organic';
|
|
28
|
+
return 'unknown';
|
|
29
|
+
}
|
|
30
|
+
function toSeedArray(seed = []) {
|
|
31
|
+
return seed.map((item) => {
|
|
32
|
+
if (typeof item === 'string') {
|
|
33
|
+
return {
|
|
34
|
+
keyword: item,
|
|
35
|
+
normalizedKeyword: normalizeKeyword(item),
|
|
36
|
+
source: 'preset',
|
|
37
|
+
weight: 1,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
keyword: item.keyword,
|
|
42
|
+
normalizedKeyword: normalizeKeyword(item.keyword),
|
|
43
|
+
source: item.source ?? 'discovered',
|
|
44
|
+
sourcePage: item.sourcePage,
|
|
45
|
+
weight: item.weight ?? 1,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function extractKeywordCampaignSeedsFromReport(report, options = {}) {
|
|
50
|
+
const maxKeywords = options.maxKeywords ?? 10;
|
|
51
|
+
const minKeywordLength = options.minKeywordLength ?? 2;
|
|
52
|
+
return (report.keywords?.topKeywords ?? [])
|
|
53
|
+
.map((item) => ({
|
|
54
|
+
keyword: item.word,
|
|
55
|
+
normalizedKeyword: normalizeKeyword(item.word),
|
|
56
|
+
source: 'discovered',
|
|
57
|
+
sourcePage: options.sourcePage,
|
|
58
|
+
weight: Math.max(1, Math.round(item.count)),
|
|
59
|
+
}))
|
|
60
|
+
.filter(seed => seed.keyword.trim().length >= minKeywordLength
|
|
61
|
+
&& seed.normalizedKeyword.length >= minKeywordLength
|
|
62
|
+
&& seed.weight > 0)
|
|
63
|
+
.slice(0, maxKeywords);
|
|
64
|
+
}
|
|
65
|
+
function sortSeedsByWeight(a, b) {
|
|
66
|
+
if (a.weight === b.weight) {
|
|
67
|
+
return a.normalizedKeyword.localeCompare(b.normalizedKeyword);
|
|
68
|
+
}
|
|
69
|
+
return b.weight - a.weight;
|
|
70
|
+
}
|
|
71
|
+
function mergeKeywordSeeds(discovered, preset, minKeywordLength = 2, minWeight = 1) {
|
|
72
|
+
const merged = new Map();
|
|
73
|
+
const upsert = (seed) => {
|
|
74
|
+
if (!seed.normalizedKeyword || seed.normalizedKeyword.length < minKeywordLength)
|
|
75
|
+
return;
|
|
76
|
+
if (seed.weight < minWeight)
|
|
77
|
+
return;
|
|
78
|
+
const existing = merged.get(seed.normalizedKeyword);
|
|
79
|
+
if (!existing) {
|
|
80
|
+
merged.set(seed.normalizedKeyword, seed);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (existing.source === 'preset' && seed.source === 'discovered') {
|
|
84
|
+
merged.set(seed.normalizedKeyword, seed);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (seed.source === existing.source && seed.weight > existing.weight) {
|
|
88
|
+
merged.set(seed.normalizedKeyword, seed);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
for (const seed of discovered)
|
|
92
|
+
upsert(seed);
|
|
93
|
+
for (const seed of preset)
|
|
94
|
+
upsert(seed);
|
|
95
|
+
return [...merged.values()].sort(sortSeedsByWeight);
|
|
96
|
+
}
|
|
97
|
+
function buildEmptyReport(targetUrl, targetDomain) {
|
|
98
|
+
return {
|
|
99
|
+
targetUrl,
|
|
100
|
+
targetDomain,
|
|
101
|
+
results: [],
|
|
102
|
+
summary: {
|
|
103
|
+
queriesRequested: 0,
|
|
104
|
+
queriesExecuted: 0,
|
|
105
|
+
queriesFound: 0,
|
|
106
|
+
avgTopPosition: null,
|
|
107
|
+
top3Count: 0,
|
|
108
|
+
top10Count: 0,
|
|
109
|
+
topOrganicCompetitors: [],
|
|
110
|
+
topPaidCompetitors: [],
|
|
111
|
+
competitorCoverage: {
|
|
112
|
+
organicUniqueDomains: 0,
|
|
113
|
+
paidUniqueDomains: 0,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
pageComparison: [],
|
|
117
|
+
campaign: {
|
|
118
|
+
active: false,
|
|
119
|
+
confidence: 'low',
|
|
120
|
+
evidence: [],
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function dedupeDomainResults(results, targetDomain) {
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
const domainResults = [];
|
|
127
|
+
for (const result of results) {
|
|
128
|
+
if (!result.domain || result.domain === targetDomain)
|
|
129
|
+
continue;
|
|
130
|
+
if (seen.has(result.domain))
|
|
131
|
+
continue;
|
|
132
|
+
seen.add(result.domain);
|
|
133
|
+
domainResults.push({
|
|
134
|
+
domain: result.domain,
|
|
135
|
+
rank: result.rank,
|
|
136
|
+
placement: result.placement,
|
|
137
|
+
placementHint: result.placementHint,
|
|
138
|
+
url: result.url,
|
|
139
|
+
title: result.title,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return domainResults;
|
|
143
|
+
}
|
|
144
|
+
function trackCompetitors(candidates, keyword, targetDomain, trackers) {
|
|
145
|
+
for (const competitor of candidates) {
|
|
146
|
+
if (competitor.domain === targetDomain)
|
|
147
|
+
continue;
|
|
148
|
+
const current = trackers.get(competitor.domain) ?? {
|
|
149
|
+
organicQueries: 0,
|
|
150
|
+
paidQueries: 0,
|
|
151
|
+
bestOrganicRank: null,
|
|
152
|
+
bestPaidRank: null,
|
|
153
|
+
keywords: new Set(),
|
|
154
|
+
};
|
|
155
|
+
if (competitor.placement === 'ad') {
|
|
156
|
+
current.paidQueries += 1;
|
|
157
|
+
if (current.bestPaidRank === null || competitor.rank < current.bestPaidRank) {
|
|
158
|
+
current.bestPaidRank = competitor.rank;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
current.organicQueries += 1;
|
|
163
|
+
if (current.bestOrganicRank === null || competitor.rank < current.bestOrganicRank) {
|
|
164
|
+
current.bestOrganicRank = competitor.rank;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
current.keywords.add(keyword);
|
|
168
|
+
trackers.set(competitor.domain, current);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function finalizeCompetitorSummary(trackers) {
|
|
172
|
+
const ranked = [...trackers.entries()].map(([domain, tracker]) => ({
|
|
173
|
+
domain,
|
|
174
|
+
organicQueries: tracker.organicQueries,
|
|
175
|
+
paidQueries: tracker.paidQueries,
|
|
176
|
+
bestOrganicRank: tracker.bestOrganicRank,
|
|
177
|
+
bestPaidRank: tracker.bestPaidRank,
|
|
178
|
+
matchedKeywords: tracker.keywords.size,
|
|
179
|
+
score: (tracker.organicQueries * 2) + (tracker.paidQueries * 3),
|
|
180
|
+
}));
|
|
181
|
+
const scoreSort = (a, b) => b.score - a.score || a.domain.localeCompare(b.domain);
|
|
182
|
+
const topOrganic = ranked
|
|
183
|
+
.filter((entry) => entry.organicQueries > 0)
|
|
184
|
+
.sort(scoreSort)
|
|
185
|
+
.map(({ score: _ignored, ...entry }) => entry)
|
|
186
|
+
.slice(0, 10);
|
|
187
|
+
const topPaid = ranked
|
|
188
|
+
.filter((entry) => entry.paidQueries > 0)
|
|
189
|
+
.sort(scoreSort)
|
|
190
|
+
.map(({ score: _ignored, ...entry }) => entry)
|
|
191
|
+
.slice(0, 10);
|
|
192
|
+
return {
|
|
193
|
+
topOrganic,
|
|
194
|
+
topPaid,
|
|
195
|
+
coverage: {
|
|
196
|
+
organicUniqueDomains: ranked.filter((entry) => entry.organicQueries > 0).length,
|
|
197
|
+
paidUniqueDomains: ranked.filter((entry) => entry.paidQueries > 0).length,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export async function analyzeKeywordCampaign(options) {
|
|
202
|
+
const targetUrl = options.targetUrl.trim();
|
|
203
|
+
const targetDomain = normalizeHost(targetUrl);
|
|
204
|
+
const queryLimit = clampNumber(options.maxQueries, 20);
|
|
205
|
+
const defaultCampaignResultLimit = 25;
|
|
206
|
+
const searchResultLimit = Math.max(clampNumber(options.maxResultsPerQuery, defaultCampaignResultLimit), 20);
|
|
207
|
+
const minKeywordLength = options.minKeywordLength ?? 2;
|
|
208
|
+
const minWeight = Math.max(1, options.minWeight ?? 1);
|
|
209
|
+
const discovered = toSeedArray(options.discoveredKeywords ?? []);
|
|
210
|
+
const preset = (options.presetKeywords ?? []).map((keyword) => ({
|
|
211
|
+
keyword,
|
|
212
|
+
normalizedKeyword: normalizeKeyword(keyword),
|
|
213
|
+
source: 'preset',
|
|
214
|
+
weight: 1,
|
|
215
|
+
}));
|
|
216
|
+
const seeds = mergeKeywordSeeds(discovered, preset, minKeywordLength, minWeight).slice(0, queryLimit);
|
|
217
|
+
const report = buildEmptyReport(targetUrl, targetDomain);
|
|
218
|
+
if (seeds.length === 0 || !targetDomain) {
|
|
219
|
+
report.summary.queriesRequested = 0;
|
|
220
|
+
return report;
|
|
221
|
+
}
|
|
222
|
+
const pageBuckets = new Map();
|
|
223
|
+
const competitorTrackers = new Map();
|
|
224
|
+
const campaignEvidence = [];
|
|
225
|
+
report.summary.queriesRequested = seeds.length;
|
|
226
|
+
for (const seed of seeds) {
|
|
227
|
+
const query = seed.keyword;
|
|
228
|
+
try {
|
|
229
|
+
const response = await searchGoogleAdvanced(query, {
|
|
230
|
+
num: searchResultLimit,
|
|
231
|
+
transport: options.transport,
|
|
232
|
+
timeout: options.timeout,
|
|
233
|
+
country: options.country,
|
|
234
|
+
gl: options.gl,
|
|
235
|
+
hl: options.hl,
|
|
236
|
+
});
|
|
237
|
+
report.summary.queriesExecuted += 1;
|
|
238
|
+
const parsedResults = response.results.map((result) => ({
|
|
239
|
+
...result,
|
|
240
|
+
domain: normalizeHost(result.url),
|
|
241
|
+
placement: normalizePlacement(result.placement),
|
|
242
|
+
}));
|
|
243
|
+
const matched = parsedResults.find((result) => {
|
|
244
|
+
return result.domain && result.domain === targetDomain;
|
|
245
|
+
});
|
|
246
|
+
const competitorCandidates = dedupeDomainResults(parsedResults.map((result) => ({
|
|
247
|
+
domain: result.domain,
|
|
248
|
+
rank: result.rank,
|
|
249
|
+
placement: result.placement,
|
|
250
|
+
placementHint: result.placementHint,
|
|
251
|
+
url: result.url,
|
|
252
|
+
title: result.title,
|
|
253
|
+
})), targetDomain);
|
|
254
|
+
trackCompetitors(competitorCandidates, seed.normalizedKeyword, targetDomain, competitorTrackers);
|
|
255
|
+
const searchResult = {
|
|
256
|
+
keyword: query,
|
|
257
|
+
source: seed.source,
|
|
258
|
+
sourcePage: seed.sourcePage,
|
|
259
|
+
sourceWeight: seed.weight,
|
|
260
|
+
found: Boolean(matched),
|
|
261
|
+
bestPosition: matched?.rank ?? null,
|
|
262
|
+
totalChecked: parsedResults.length,
|
|
263
|
+
matchedUrl: matched?.url,
|
|
264
|
+
matchedTitle: matched?.title,
|
|
265
|
+
matchedDisplayUrl: matched?.displayedUrl,
|
|
266
|
+
placement: matched ? normalizePlacement(matched.placement) : 'unknown',
|
|
267
|
+
placementHint: matched?.placementHint,
|
|
268
|
+
searchUrl: response.searchUrl,
|
|
269
|
+
searchTransport: response.transport.used,
|
|
270
|
+
competitors: competitorCandidates,
|
|
271
|
+
};
|
|
272
|
+
if (searchResult.found && searchResult.bestPosition !== null) {
|
|
273
|
+
const position = searchResult.bestPosition;
|
|
274
|
+
report.summary.queriesFound += 1;
|
|
275
|
+
if (position <= 3)
|
|
276
|
+
report.summary.top3Count += 1;
|
|
277
|
+
if (position <= 10)
|
|
278
|
+
report.summary.top10Count += 1;
|
|
279
|
+
if (searchResult.placement === 'ad') {
|
|
280
|
+
campaignEvidence.push(`${query} aparece como anúncio em posição #${position}`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
campaignEvidence.push(`${query} aparece orgânico em posição #${position}`);
|
|
284
|
+
}
|
|
285
|
+
const pageKey = seed.sourcePage ?? (seed.source === 'preset' ? 'preset-queries' : 'unknown');
|
|
286
|
+
const bucket = pageBuckets.get(pageKey) ?? {
|
|
287
|
+
tracked: 0,
|
|
288
|
+
found: 0,
|
|
289
|
+
totalPosition: 0,
|
|
290
|
+
positions: [],
|
|
291
|
+
top3: 0,
|
|
292
|
+
top10: 0,
|
|
293
|
+
};
|
|
294
|
+
bucket.tracked += 1;
|
|
295
|
+
bucket.found += 1;
|
|
296
|
+
bucket.totalPosition += position;
|
|
297
|
+
bucket.positions.push(position);
|
|
298
|
+
if (position <= 3)
|
|
299
|
+
bucket.top3 += 1;
|
|
300
|
+
if (position <= 10)
|
|
301
|
+
bucket.top10 += 1;
|
|
302
|
+
pageBuckets.set(pageKey, bucket);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const pageKey = seed.sourcePage ?? (seed.source === 'preset' ? 'preset-queries' : 'unknown');
|
|
306
|
+
const bucket = pageBuckets.get(pageKey) ?? {
|
|
307
|
+
tracked: 0,
|
|
308
|
+
found: 0,
|
|
309
|
+
totalPosition: 0,
|
|
310
|
+
positions: [],
|
|
311
|
+
top3: 0,
|
|
312
|
+
top10: 0,
|
|
313
|
+
};
|
|
314
|
+
bucket.tracked += 1;
|
|
315
|
+
pageBuckets.set(pageKey, bucket);
|
|
316
|
+
}
|
|
317
|
+
report.results.push(searchResult);
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
const pageKey = seed.sourcePage ?? (seed.source === 'preset' ? 'preset-queries' : 'unknown');
|
|
321
|
+
const bucket = pageBuckets.get(pageKey) ?? {
|
|
322
|
+
tracked: 0,
|
|
323
|
+
found: 0,
|
|
324
|
+
totalPosition: 0,
|
|
325
|
+
positions: [],
|
|
326
|
+
top3: 0,
|
|
327
|
+
top10: 0,
|
|
328
|
+
};
|
|
329
|
+
bucket.tracked += 1;
|
|
330
|
+
pageBuckets.set(pageKey, bucket);
|
|
331
|
+
report.summary.queriesExecuted += 1;
|
|
332
|
+
report.results.push({
|
|
333
|
+
keyword: seed.keyword,
|
|
334
|
+
source: seed.source,
|
|
335
|
+
sourcePage: seed.sourcePage,
|
|
336
|
+
sourceWeight: seed.weight,
|
|
337
|
+
found: false,
|
|
338
|
+
bestPosition: null,
|
|
339
|
+
totalChecked: searchResultLimit,
|
|
340
|
+
placement: 'unknown',
|
|
341
|
+
searchUrl: '',
|
|
342
|
+
searchTransport: options.transport ?? 'undici',
|
|
343
|
+
competitors: [],
|
|
344
|
+
});
|
|
345
|
+
campaignEvidence.push(`Falha ao buscar keyword "${seed.keyword}"`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const foundPositions = report.results
|
|
349
|
+
.filter((entry) => entry.found && entry.bestPosition !== null)
|
|
350
|
+
.map((entry) => entry.bestPosition);
|
|
351
|
+
if (foundPositions.length > 0) {
|
|
352
|
+
report.summary.avgTopPosition = Math.round(foundPositions.reduce((acc, pos) => acc + pos, 0) / foundPositions.length);
|
|
353
|
+
}
|
|
354
|
+
const hasTop3 = foundPositions.some((position) => position <= 3);
|
|
355
|
+
const hasTop10 = foundPositions.some((position) => position <= 10);
|
|
356
|
+
report.campaign = {
|
|
357
|
+
active: foundPositions.some((position) => position <= 10),
|
|
358
|
+
confidence: hasTop3 ? 'high' : hasTop10 ? 'medium' : foundPositions.length > 0 ? 'low' : 'low',
|
|
359
|
+
evidence: campaignEvidence,
|
|
360
|
+
};
|
|
361
|
+
const competitorSummary = finalizeCompetitorSummary(competitorTrackers);
|
|
362
|
+
report.summary.topOrganicCompetitors = competitorSummary.topOrganic;
|
|
363
|
+
report.summary.topPaidCompetitors = competitorSummary.topPaid;
|
|
364
|
+
report.summary.competitorCoverage = competitorSummary.coverage;
|
|
365
|
+
report.pageComparison = [...pageBuckets.entries()].map(([pageUrl, bucket]) => ({
|
|
366
|
+
pageUrl,
|
|
367
|
+
tracked: bucket.tracked,
|
|
368
|
+
found: bucket.found,
|
|
369
|
+
avgPosition: bucket.found > 0 ? Math.round(bucket.totalPosition / bucket.found) : null,
|
|
370
|
+
top3: bucket.top3,
|
|
371
|
+
top10: bucket.top10,
|
|
372
|
+
})).sort((a, b) => {
|
|
373
|
+
if (a.avgPosition === null)
|
|
374
|
+
return 1;
|
|
375
|
+
if (b.avgPosition === null)
|
|
376
|
+
return -1;
|
|
377
|
+
return a.avgPosition - b.avgPosition;
|
|
378
|
+
});
|
|
379
|
+
return report;
|
|
380
|
+
}
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.76-next.c8fd7f9",
|
|
4
4
|
"description": "Multi-Protocol SDK for the AI Era - HTTP, WebSocket, DNS, FTP, SFTP, Telnet, HLS unified with AI providers and MCP tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|