gscdump 0.17.4 → 0.18.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/dist/contracts.d.mts +17 -4
- package/dist/index.d.mts +62 -11
- package/dist/index.mjs +188 -38
- package/dist/query/index.d.mts +27 -8
- package/dist/query/index.mjs +101 -20
- package/dist/query/plan.d.mts +10 -0
- package/dist/query/plan.mjs +3 -3
- package/package.json +2 -2
package/dist/contracts.d.mts
CHANGED
|
@@ -16,7 +16,14 @@ interface TenantCtx {
|
|
|
16
16
|
userId: string;
|
|
17
17
|
siteId?: string;
|
|
18
18
|
}
|
|
19
|
-
type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'searchAppearance';
|
|
19
|
+
type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'hour' | 'searchAppearance';
|
|
20
|
+
type GscDataState = 'final' | 'all' | 'hourly_all';
|
|
21
|
+
interface GscSearchAnalyticsMetadata {
|
|
22
|
+
/** First date (YYYY-MM-DD, PT) still being collected. Populated when dataState=`all` and grouped by `date`. */
|
|
23
|
+
first_incomplete_date?: string;
|
|
24
|
+
/** First hour (YYYY-MM-DDThh:mm:ss±hh:mm, PT) still being collected. Populated when dataState=`hourly_all` and grouped by `hour`. */
|
|
25
|
+
first_incomplete_hour?: string;
|
|
26
|
+
}
|
|
20
27
|
type GscSearchAnalyticsFilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
|
|
21
28
|
interface GscSearchAnalyticsFilter {
|
|
22
29
|
dimension: GscSearchAnalyticsDimension;
|
|
@@ -28,6 +35,8 @@ interface GscSearchAnalyticsFilterGroup {
|
|
|
28
35
|
filters: GscSearchAnalyticsFilter[];
|
|
29
36
|
}
|
|
30
37
|
type GscSearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
|
|
38
|
+
type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
39
|
+
type GscResponseAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
31
40
|
interface GscSearchAnalyticsRequest {
|
|
32
41
|
startDate: string;
|
|
33
42
|
endDate: string;
|
|
@@ -35,7 +44,10 @@ interface GscSearchAnalyticsRequest {
|
|
|
35
44
|
dimensionFilterGroups?: GscSearchAnalyticsFilterGroup[];
|
|
36
45
|
rowLimit?: number;
|
|
37
46
|
startRow?: number;
|
|
38
|
-
searchType
|
|
47
|
+
/** GSC search corpus. Maps to wire field `type` (the API still accepts the deprecated `searchType` alias). */
|
|
48
|
+
type?: GscSearchType;
|
|
49
|
+
dataState?: GscDataState;
|
|
50
|
+
aggregationType?: GscAggregationType;
|
|
39
51
|
}
|
|
40
52
|
interface GscSearchAnalyticsRow {
|
|
41
53
|
keys: string[];
|
|
@@ -46,6 +58,7 @@ interface GscSearchAnalyticsRow {
|
|
|
46
58
|
}
|
|
47
59
|
interface GscSearchAnalyticsResponse {
|
|
48
60
|
rows?: GscSearchAnalyticsRow[];
|
|
49
|
-
responseAggregationType?:
|
|
61
|
+
responseAggregationType?: GscResponseAggregationType;
|
|
62
|
+
metadata?: GscSearchAnalyticsMetadata;
|
|
50
63
|
}
|
|
51
|
-
export { ColumnDef, ColumnType, GscSearchAnalyticsDimension, GscSearchAnalyticsFilter, GscSearchAnalyticsFilterGroup, GscSearchAnalyticsFilterOperator, GscSearchAnalyticsRequest, GscSearchAnalyticsResponse, GscSearchAnalyticsRow, GscSearchType, Row, TableName, TableSchema, TenantCtx };
|
|
64
|
+
export { ColumnDef, ColumnType, GscAggregationType, GscDataState, GscResponseAggregationType, GscSearchAnalyticsDimension, GscSearchAnalyticsFilter, GscSearchAnalyticsFilterGroup, GscSearchAnalyticsFilterOperator, GscSearchAnalyticsMetadata, GscSearchAnalyticsRequest, GscSearchAnalyticsResponse, GscSearchAnalyticsRow, GscSearchType, Row, TableName, TableSchema, TenantCtx };
|
package/dist/index.d.mts
CHANGED
|
@@ -12,7 +12,14 @@ declare function runSequentialBatch<I, R>(items: I[], operation: (item: I, index
|
|
|
12
12
|
concurrency?: number;
|
|
13
13
|
onProgress?: (result: R, index: number, total: number) => void;
|
|
14
14
|
}): Promise<R[]>;
|
|
15
|
-
type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'searchAppearance';
|
|
15
|
+
type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'hour' | 'searchAppearance';
|
|
16
|
+
type GscDataState = 'final' | 'all' | 'hourly_all';
|
|
17
|
+
interface GscSearchAnalyticsMetadata {
|
|
18
|
+
/** First date (YYYY-MM-DD, PT) still being collected. Populated when dataState=`all` and grouped by `date`. */
|
|
19
|
+
first_incomplete_date?: string;
|
|
20
|
+
/** First hour (YYYY-MM-DDThh:mm:ss±hh:mm, PT) still being collected. Populated when dataState=`hourly_all` and grouped by `hour`. */
|
|
21
|
+
first_incomplete_hour?: string;
|
|
22
|
+
}
|
|
16
23
|
type GscSearchAnalyticsFilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
|
|
17
24
|
interface GscSearchAnalyticsFilter {
|
|
18
25
|
dimension: GscSearchAnalyticsDimension;
|
|
@@ -24,6 +31,8 @@ interface GscSearchAnalyticsFilterGroup {
|
|
|
24
31
|
filters: GscSearchAnalyticsFilter[];
|
|
25
32
|
}
|
|
26
33
|
type GscSearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
|
|
34
|
+
type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
35
|
+
type GscResponseAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
27
36
|
interface GscSearchAnalyticsRequest {
|
|
28
37
|
startDate: string;
|
|
29
38
|
endDate: string;
|
|
@@ -31,7 +40,10 @@ interface GscSearchAnalyticsRequest {
|
|
|
31
40
|
dimensionFilterGroups?: GscSearchAnalyticsFilterGroup[];
|
|
32
41
|
rowLimit?: number;
|
|
33
42
|
startRow?: number;
|
|
34
|
-
searchType
|
|
43
|
+
/** GSC search corpus. Maps to wire field `type` (the API still accepts the deprecated `searchType` alias). */
|
|
44
|
+
type?: GscSearchType;
|
|
45
|
+
dataState?: GscDataState;
|
|
46
|
+
aggregationType?: GscAggregationType;
|
|
35
47
|
}
|
|
36
48
|
declare const _default: {
|
|
37
49
|
name: string;
|
|
@@ -64,6 +76,8 @@ interface DimensionValueMap {
|
|
|
64
76
|
device: Device;
|
|
65
77
|
searchAppearance: string;
|
|
66
78
|
date: string;
|
|
79
|
+
/** Hour bucket — ISO-8601 with PT offset, e.g. `2025-07-14T13:00:00-07:00`. Use with `dataState: 'hourly_all'`. */
|
|
80
|
+
hour: string;
|
|
67
81
|
}
|
|
68
82
|
type Dimension = keyof DimensionValueMap;
|
|
69
83
|
interface QueryParamValueMap {
|
|
@@ -113,6 +127,12 @@ interface BuilderState {
|
|
|
113
127
|
};
|
|
114
128
|
rowLimit?: number;
|
|
115
129
|
startRow?: number;
|
|
130
|
+
/** GSC `dataState`. `'hourly_all'` is required when grouping by `hour`. */
|
|
131
|
+
dataState?: GscDataState;
|
|
132
|
+
/** GSC `aggregationType`. `'byNewsShowcasePanel'` requires `type=discover|googleNews` with NEWS_SHOWCASE searchAppearance. */
|
|
133
|
+
aggregationType?: GscAggregationType;
|
|
134
|
+
/** GSC search corpus. Wins over any `searchType` filter when both are set. */
|
|
135
|
+
searchType?: SearchType;
|
|
116
136
|
}
|
|
117
137
|
type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
|
|
118
138
|
type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
|
|
@@ -127,6 +147,10 @@ interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
|
|
|
127
147
|
orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
|
|
128
148
|
limit: (n: number) => GSCQueryBuilder<D, C>;
|
|
129
149
|
offset: (n: number) => GSCQueryBuilder<D, C>;
|
|
150
|
+
dataState: (state: GscDataState) => GSCQueryBuilder<D, C>;
|
|
151
|
+
aggregationType: (type: GscAggregationType) => GSCQueryBuilder<D, C>;
|
|
152
|
+
/** GSC search corpus (`type` on the wire). Equivalent to filtering by `searchType`. */
|
|
153
|
+
type: (t: GscSearchType) => GSCQueryBuilder<D, C>;
|
|
130
154
|
toBody: () => GscSearchAnalyticsRequest;
|
|
131
155
|
getState: () => BuilderState;
|
|
132
156
|
}
|
|
@@ -239,16 +263,23 @@ declare function createFetch(auth: Auth, options?: FetchOptions): $Fetch;
|
|
|
239
263
|
interface CallOptions {
|
|
240
264
|
signal?: AbortSignal;
|
|
241
265
|
}
|
|
266
|
+
/** Generator return value for `client.query` — exposes API response metadata plus the resolved aggregation type Google actually used (may differ from the requested `aggregationType: 'auto'`). */
|
|
267
|
+
interface QueryReturn {
|
|
268
|
+
metadata?: GscSearchAnalyticsMetadata;
|
|
269
|
+
/** Aggregation type Google actually used. Useful when requesting `auto` to know if rows were aggregated `byPage` vs `byProperty`. */
|
|
270
|
+
responseAggregationType?: GscResponseAggregationType;
|
|
271
|
+
}
|
|
242
272
|
interface GoogleSearchConsoleClient {
|
|
243
273
|
/** Query search analytics with builder, returns async generator yielding typed row batches */
|
|
244
|
-
query: <D extends Dimension[], C>(siteUrl: string, builder: GSCQueryBuilder<D, C>, opts?: CallOptions) => AsyncGenerator<GSCRow<D, C>[]>;
|
|
274
|
+
query: <D extends Dimension[], C>(siteUrl: string, builder: GSCQueryBuilder<D, C>, opts?: CallOptions) => AsyncGenerator<GSCRow<D, C>[], QueryReturn>;
|
|
245
275
|
/**
|
|
246
276
|
* List all sites. Also exposes write ops as `client.sites.add(siteUrl)` and
|
|
247
277
|
* `client.sites.delete(siteUrl)`. Calling `client.sites()` is equivalent to
|
|
248
278
|
* `client.sites.list()`.
|
|
249
279
|
*/
|
|
250
280
|
sites: ((opts?: CallOptions) => Promise<ApiSite[]>) & {
|
|
251
|
-
list: (opts?: CallOptions) => Promise<ApiSite[]>; /**
|
|
281
|
+
list: (opts?: CallOptions) => Promise<ApiSite[]>; /** Retrieve a single property (with permission level). 404 if not in the user's account. */
|
|
282
|
+
get: (siteUrl: string, opts?: CallOptions) => Promise<ApiSite>; /** Add a property in unverified state. Caller must verify ownership separately. */
|
|
252
283
|
add: (siteUrl: string, opts?: CallOptions) => Promise<void>; /** Remove a property from the user's account. */
|
|
253
284
|
delete: (siteUrl: string, opts?: CallOptions) => Promise<void>;
|
|
254
285
|
};
|
|
@@ -266,11 +297,15 @@ interface GoogleSearchConsoleClient {
|
|
|
266
297
|
get: (id: string, opts?: CallOptions) => Promise<VerificationWebResource>;
|
|
267
298
|
delete: (id: string, opts?: CallOptions) => Promise<void>;
|
|
268
299
|
};
|
|
269
|
-
/** Inspect a URL */
|
|
270
|
-
inspect: (siteUrl: string, url: string, opts?: CallOptions
|
|
300
|
+
/** Inspect a URL. `languageCode` is a BCP-47 tag for translating result strings; omit to let Google pick. */
|
|
301
|
+
inspect: (siteUrl: string, url: string, opts?: CallOptions & {
|
|
302
|
+
languageCode?: string;
|
|
303
|
+
}) => Promise<InspectUrlIndexResponse>;
|
|
271
304
|
/** Sitemap operations */
|
|
272
305
|
sitemaps: {
|
|
273
|
-
list: (siteUrl: string, opts?: CallOptions
|
|
306
|
+
/** List sitemaps. Pass `sitemapIndex` to list children of a sitemap-index file. */list: (siteUrl: string, opts?: CallOptions & {
|
|
307
|
+
sitemapIndex?: string;
|
|
308
|
+
}) => Promise<ApiSitemap[]>;
|
|
274
309
|
get: (siteUrl: string, feedpath: string, opts?: CallOptions) => Promise<ApiSitemap>;
|
|
275
310
|
submit: (siteUrl: string, feedpath: string, opts?: CallOptions) => Promise<void>;
|
|
276
311
|
delete: (siteUrl: string, feedpath: string, opts?: CallOptions) => Promise<void>;
|
|
@@ -363,6 +398,14 @@ interface ParsedIndexingResult {
|
|
|
363
398
|
mobileIssues: string | null;
|
|
364
399
|
richResultsVerdict: string | null;
|
|
365
400
|
richResultsItems: string | null;
|
|
401
|
+
ampVerdict: string | null;
|
|
402
|
+
ampUrl: string | null;
|
|
403
|
+
ampIndexingState: string | null;
|
|
404
|
+
ampIndexStatusVerdict: string | null;
|
|
405
|
+
ampRobotsTxtState: string | null;
|
|
406
|
+
ampPageFetchState: string | null;
|
|
407
|
+
ampLastCrawlTime: string | null;
|
|
408
|
+
ampIssues: string | null;
|
|
366
409
|
inspectionResultLink: string | null;
|
|
367
410
|
}
|
|
368
411
|
declare function inspectUrlFlat(client: GoogleSearchConsoleClient, siteUrl: string, inspectionUrl: string): Promise<ParsedIndexingResult>;
|
|
@@ -406,7 +449,7 @@ declare function fetchSites(client: GoogleSearchConsoleClient): Promise<ApiSite[
|
|
|
406
449
|
* Fetches all verified sites with their sitemaps from Google Search Console.
|
|
407
450
|
*/
|
|
408
451
|
declare function fetchSitesWithSitemaps(client: GoogleSearchConsoleClient): Promise<(Site & {
|
|
409
|
-
sitemaps:
|
|
452
|
+
sitemaps: ApiSitemap[];
|
|
410
453
|
})[]>;
|
|
411
454
|
/**
|
|
412
455
|
* Fetches all sitemaps for a site.
|
|
@@ -690,8 +733,8 @@ declare function findBestGscProperty<T extends GscPropertyCandidate>(targetDomai
|
|
|
690
733
|
};
|
|
691
734
|
declare function findExactGscProperty<T extends GscPropertyCandidate>(propertyUrl: string, properties: readonly T[]): T | null;
|
|
692
735
|
declare function formatGscPropertyCandidates(candidates: ReadonlyArray<GscPropertyCandidate | null | undefined>): string;
|
|
693
|
-
declare const
|
|
694
|
-
declare const
|
|
736
|
+
declare const URL_INSPECTION_DAILY_LIMIT = 2000;
|
|
737
|
+
declare const URL_INSPECTION_EFFECTIVE_LIMIT = 1800;
|
|
695
738
|
declare function hasGscReadScope(scopes: string | null | undefined): boolean;
|
|
696
739
|
declare function hasGscWriteScope(scopes: string | null | undefined): boolean;
|
|
697
740
|
declare function hasIndexingScope(scopes: string | null | undefined): boolean;
|
|
@@ -706,6 +749,14 @@ interface ParsedGscSiteUrl {
|
|
|
706
749
|
isDomain: boolean;
|
|
707
750
|
}
|
|
708
751
|
declare function parseGscSiteUrl(siteUrl: string): ParsedGscSiteUrl;
|
|
752
|
+
/**
|
|
753
|
+
* Comparison-canonical form of a GSC property URL. Strips `sc-domain:`,
|
|
754
|
+
* protocol, leading `www.`, trailing slash, and lowercases. Use when matching
|
|
755
|
+
* the same logical site across `https://www.example.com/` vs
|
|
756
|
+
* `sc-domain:example.com` vs bare hostnames — properties Google sometimes
|
|
757
|
+
* returns in different shapes for the same site.
|
|
758
|
+
*/
|
|
759
|
+
declare function normalizeGscSiteUrl(siteUrl: string): string;
|
|
709
760
|
/**
|
|
710
761
|
* Normalize a user-input URL/hostname into a canonical registration target.
|
|
711
762
|
* Returns lowercase hostname stripped of protocol, or null if unparseable.
|
|
@@ -842,4 +893,4 @@ interface FetchSitemapUrlsOptions extends DiscoverSitemapOptions {
|
|
|
842
893
|
* `<loc>https://...</loc>` shape but doesn't validate the schema.
|
|
843
894
|
*/
|
|
844
895
|
declare function fetchSitemapUrls(sitemapUrl: string, options?: FetchSitemapUrlsOptions): Promise<string[]>;
|
|
845
|
-
export { AccountNextAction, AccountStatus, AnalyticsNextAction, AnalyticsStatus, ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, BackfillProgress, CallOptions, DAYS_PER_RANGE, DataRow, DimensionFilter, DimensionFilterGroup, DiscoverSitemapOptions, FetchSitemapUrlsOptions, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, GscApiError, GscApiErrorInfo, GscError, GscErrorKind, GscPropertyCandidate, GscdumpApiOptions,
|
|
896
|
+
export { AccountNextAction, AccountStatus, AnalyticsNextAction, AnalyticsStatus, ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, BackfillProgress, CallOptions, DAYS_PER_RANGE, DataRow, DimensionFilter, DimensionFilterGroup, DiscoverSitemapOptions, FetchSitemapUrlsOptions, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, GscApiError, GscApiErrorInfo, GscError, GscErrorKind, GscPropertyCandidate, GscdumpApiOptions, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, IndexStatusResult, IndexingEligibility, IndexingIneligibleReason, IndexingIssueType, IndexingMetadata, IndexingNextAction, IndexingNotificationType, IndexingResult, IndexingStatus, InspectUrlIndexResponse, InspectUrlResult, InspectionPriority, LifecycleError, LifecycleErrorCode, LifecycleProgress, LifecycleWebhookEnvelope, LifecycleWebhookEvent, MS_PER_DAY, MobileUsabilityResult, OAuthTokens, ParsedGscSiteUrl, ParsedIndexingResult, PartnerLifecycleAccount, PartnerLifecycleResponse, PartnerLifecycleSite, Period, PropertyNextAction, PropertyStatus, PublishUrlNotificationResponse, QueryReturn, QuerySourceMode, RequiredNonNullable, ResolvedAnalyticsRange, RichResultsResult, SearchAnalyticsQuery, SearchAnalyticsResponse, Site, SiteAnalytics, SitemapNextAction, SitemapStatus, URL_INSPECTION_DAILY_LIMIT, URL_INSPECTION_EFFECTIVE_LIMIT, UrlInspectionResult, UrlNotificationMetadata, VerificationMethod, VerificationSite, VerificationSiteType, VerificationToken, VerificationWebResource, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchRequestIndexing, canUseUrlInspection, classifyError, countDays, createAuth, createFetch, daysAgo, deleteSite, deleteSitemap, discoverSitemap, exchangeAuthCode, fetchSitemap, fetchSitemapUrls, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatErrorForCli, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, grantedScopeList, groupIntoRanges, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, verificationMethodsFor, verifySite };
|
package/dist/index.mjs
CHANGED
|
@@ -50,17 +50,26 @@ async function batchRequestIndexing(client, urls, options = {}) {
|
|
|
50
50
|
onProgress
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
+
const WEBMASTERS_READ = "https://www.googleapis.com/auth/webmasters.readonly";
|
|
54
|
+
const WEBMASTERS_WRITE = "https://www.googleapis.com/auth/webmasters";
|
|
55
|
+
const INDEXING = "https://www.googleapis.com/auth/indexing";
|
|
56
|
+
function tokenize(scopes) {
|
|
57
|
+
if (!scopes) return [];
|
|
58
|
+
return scopes.split(/\s+/).filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
function hasScope(scopes, scope) {
|
|
61
|
+
const tokens = tokenize(scopes);
|
|
62
|
+
const suffix = scope.replace("https://www.googleapis.com/auth/", "");
|
|
63
|
+
return tokens.includes(scope) || tokens.includes(suffix);
|
|
64
|
+
}
|
|
53
65
|
function hasGscReadScope(scopes) {
|
|
54
|
-
|
|
55
|
-
return scopes.includes("webmasters.readonly") || scopes.includes("webmasters");
|
|
66
|
+
return hasScope(scopes, WEBMASTERS_READ) || hasScope(scopes, WEBMASTERS_WRITE);
|
|
56
67
|
}
|
|
57
68
|
function hasGscWriteScope(scopes) {
|
|
58
|
-
|
|
59
|
-
return scopes.includes("webmasters") && !scopes.includes("webmasters.readonly");
|
|
69
|
+
return hasScope(scopes, WEBMASTERS_WRITE);
|
|
60
70
|
}
|
|
61
71
|
function hasIndexingScope(scopes) {
|
|
62
|
-
|
|
63
|
-
return scopes.includes("googleapis.com/auth/indexing");
|
|
72
|
+
return hasScope(scopes, INDEXING);
|
|
64
73
|
}
|
|
65
74
|
async function inspectUrl(client, siteUrl, inspectionUrl) {
|
|
66
75
|
const inspection = (await client.inspect(siteUrl, inspectionUrl)).inspectionResult;
|
|
@@ -89,6 +98,7 @@ async function inspectUrlFlat(client, siteUrl, inspectionUrl) {
|
|
|
89
98
|
const index = inspection?.indexStatusResult;
|
|
90
99
|
const mobile = inspection?.mobileUsabilityResult;
|
|
91
100
|
const rich = inspection?.richResultsResult;
|
|
101
|
+
const amp = inspection?.ampResult;
|
|
92
102
|
return {
|
|
93
103
|
url: inspectionUrl,
|
|
94
104
|
verdict: index?.verdict ?? null,
|
|
@@ -106,6 +116,14 @@ async function inspectUrlFlat(client, siteUrl, inspectionUrl) {
|
|
|
106
116
|
mobileIssues: mobile?.issues?.length ? JSON.stringify(mobile.issues) : null,
|
|
107
117
|
richResultsVerdict: rich?.verdict ?? null,
|
|
108
118
|
richResultsItems: rich?.detectedItems?.length ? JSON.stringify(rich.detectedItems) : null,
|
|
119
|
+
ampVerdict: amp?.verdict ?? null,
|
|
120
|
+
ampUrl: amp?.ampUrl ?? null,
|
|
121
|
+
ampIndexingState: amp?.indexingState ?? null,
|
|
122
|
+
ampIndexStatusVerdict: amp?.ampIndexStatusVerdict ?? null,
|
|
123
|
+
ampRobotsTxtState: amp?.robotsTxtState ?? null,
|
|
124
|
+
ampPageFetchState: amp?.pageFetchState ?? null,
|
|
125
|
+
ampLastCrawlTime: amp?.lastCrawlTime ?? null,
|
|
126
|
+
ampIssues: amp?.issues?.length ? JSON.stringify(amp.issues) : null,
|
|
109
127
|
inspectionResultLink: inspection?.inspectionResultLink ?? null
|
|
110
128
|
};
|
|
111
129
|
}
|
|
@@ -123,7 +141,11 @@ function getNextCheckAfter(priority) {
|
|
|
123
141
|
case "low": return now + 30 * 86400;
|
|
124
142
|
}
|
|
125
143
|
}
|
|
126
|
-
const INSPECTION_ALLOWED_PERMISSIONS = new Set([
|
|
144
|
+
const INSPECTION_ALLOWED_PERMISSIONS = new Set([
|
|
145
|
+
"siteOwner",
|
|
146
|
+
"siteFullUser",
|
|
147
|
+
"siteRestrictedUser"
|
|
148
|
+
]);
|
|
127
149
|
function canUseUrlInspection(permissionLevel) {
|
|
128
150
|
return !!permissionLevel && INSPECTION_ALLOWED_PERMISSIONS.has(permissionLevel);
|
|
129
151
|
}
|
|
@@ -216,6 +238,44 @@ function extractRetryAfter(error) {
|
|
|
216
238
|
}
|
|
217
239
|
}
|
|
218
240
|
const QUOTA_MESSAGE_RE = /quota|rate\s*limit/i;
|
|
241
|
+
const QUOTA_REASONS = new Set([
|
|
242
|
+
"dailyLimitExceeded",
|
|
243
|
+
"dailyLimitExceededUnreg",
|
|
244
|
+
"rateLimitExceeded",
|
|
245
|
+
"rateLimitExceededUnreg",
|
|
246
|
+
"userRateLimitExceeded",
|
|
247
|
+
"userRateLimitExceededUnreg",
|
|
248
|
+
"quotaExceeded",
|
|
249
|
+
"concurrentLimitExceeded",
|
|
250
|
+
"variableTermLimitExceeded",
|
|
251
|
+
"variableTermExpiredDailyExceeded",
|
|
252
|
+
"servingLimitExceeded",
|
|
253
|
+
"responseTooLarge",
|
|
254
|
+
"limitExceeded",
|
|
255
|
+
"batchSizeTooLarge",
|
|
256
|
+
"RATE_LIMIT_EXCEEDED",
|
|
257
|
+
"RESOURCE_EXHAUSTED"
|
|
258
|
+
]);
|
|
259
|
+
function extractReason(cause) {
|
|
260
|
+
const data = pickField(cause, [["data"]], (v) => true);
|
|
261
|
+
if (data) {
|
|
262
|
+
const errorInfo = pickField(data, [["error", "details"]], (v) => Array.isArray(v));
|
|
263
|
+
if (errorInfo) {
|
|
264
|
+
const reason = errorInfo.find((d) => typeof d["@type"] === "string" && d["@type"].includes("ErrorInfo"))?.reason;
|
|
265
|
+
if (typeof reason === "string") return reason;
|
|
266
|
+
}
|
|
267
|
+
const directErrors = pickField(data, [["error", "errors"]], (v) => Array.isArray(v));
|
|
268
|
+
if (directErrors) {
|
|
269
|
+
const reason = directErrors[0]?.reason;
|
|
270
|
+
if (typeof reason === "string") return reason;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function isQuotaCondition(cause, message) {
|
|
275
|
+
const reason = extractReason(cause);
|
|
276
|
+
if (reason && QUOTA_REASONS.has(reason)) return true;
|
|
277
|
+
return QUOTA_MESSAGE_RE.test(message);
|
|
278
|
+
}
|
|
219
279
|
function classifyError(cause) {
|
|
220
280
|
const status = extractStatus(cause);
|
|
221
281
|
const message = extractMessage(cause);
|
|
@@ -231,7 +291,7 @@ function classifyError(cause) {
|
|
|
231
291
|
cause
|
|
232
292
|
};
|
|
233
293
|
if (status === 403) {
|
|
234
|
-
if (
|
|
294
|
+
if (isQuotaCondition(cause, message)) return {
|
|
235
295
|
kind: "rate-limited",
|
|
236
296
|
message,
|
|
237
297
|
retryAfter: extractRetryAfter(cause),
|
|
@@ -243,12 +303,12 @@ function classifyError(cause) {
|
|
|
243
303
|
cause
|
|
244
304
|
};
|
|
245
305
|
}
|
|
246
|
-
if (status === 404) return {
|
|
306
|
+
if (status === 404 || status === 410) return {
|
|
247
307
|
kind: "not-found",
|
|
248
308
|
message,
|
|
249
309
|
cause
|
|
250
310
|
};
|
|
251
|
-
if (status === 400 || status === 422) return {
|
|
311
|
+
if (status === 400 || status === 402 || status === 409 || status === 413 || status === 422) return {
|
|
252
312
|
kind: "validation",
|
|
253
313
|
message,
|
|
254
314
|
cause
|
|
@@ -412,7 +472,7 @@ async function fetchSites(client) {
|
|
|
412
472
|
async function fetchSitesWithSitemaps(client) {
|
|
413
473
|
const sites = (await client.sites()).filter((s) => !!s.siteUrl && s.permissionLevel !== "siteUnverifiedUser");
|
|
414
474
|
return Promise.all(sites.map(async (site) => {
|
|
415
|
-
const sitemaps =
|
|
475
|
+
const sitemaps = await client.sitemaps.list(site.siteUrl).catch(() => []);
|
|
416
476
|
return {
|
|
417
477
|
...site,
|
|
418
478
|
sitemaps
|
|
@@ -2152,9 +2212,9 @@ function extractSpecialFilters(filter) {
|
|
|
2152
2212
|
else otherFilters.push(f);
|
|
2153
2213
|
if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
|
|
2154
2214
|
const extracted = extractSpecialFilters(nested);
|
|
2155
|
-
if (extracted.startDate) startDate = extracted.startDate;
|
|
2156
|
-
if (extracted.endDate) endDate = extracted.endDate;
|
|
2157
|
-
if (extracted.searchType) searchType = extracted.searchType;
|
|
2215
|
+
if (!startDate && extracted.startDate) startDate = extracted.startDate;
|
|
2216
|
+
if (!endDate && extracted.endDate) endDate = extracted.endDate;
|
|
2217
|
+
if (!searchType && extracted.searchType) searchType = extracted.searchType;
|
|
2158
2218
|
if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
|
|
2159
2219
|
}
|
|
2160
2220
|
const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
|
|
@@ -2177,11 +2237,40 @@ function resolveToBody(state) {
|
|
|
2177
2237
|
startDate,
|
|
2178
2238
|
endDate
|
|
2179
2239
|
};
|
|
2180
|
-
|
|
2181
|
-
if (
|
|
2182
|
-
if (state.
|
|
2240
|
+
const resolvedType = state.searchType ?? searchType;
|
|
2241
|
+
if (resolvedType) body.type = resolvedType;
|
|
2242
|
+
if (state.rowLimit !== void 0) {
|
|
2243
|
+
if (!Number.isInteger(state.rowLimit) || state.rowLimit < 1) throw new Error(`rowLimit must be a positive integer, got ${state.rowLimit}`);
|
|
2244
|
+
body.rowLimit = state.rowLimit;
|
|
2245
|
+
}
|
|
2246
|
+
if (state.startRow !== void 0) {
|
|
2247
|
+
if (!Number.isInteger(state.startRow) || state.startRow < 0) throw new Error(`startRow must be a non-negative integer, got ${state.startRow}`);
|
|
2248
|
+
if (state.startRow > 0) body.startRow = state.startRow;
|
|
2249
|
+
}
|
|
2250
|
+
const hasHour = state.dimensions?.includes("hour");
|
|
2251
|
+
if (hasHour && state.dataState !== "hourly_all") throw new Error("hour dimension requires dataState: \"hourly_all\"");
|
|
2252
|
+
if (state.dataState === "hourly_all" && !hasHour) throw new Error("dataState: \"hourly_all\" requires grouping by hour dimension");
|
|
2253
|
+
if (state.dataState) body.dataState = state.dataState;
|
|
2183
2254
|
const filterGroups = resolveFilter(dimensionFilter);
|
|
2184
2255
|
if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
|
|
2256
|
+
if (state.aggregationType) {
|
|
2257
|
+
const groupsByPage = (body.dimensions ?? []).includes("page");
|
|
2258
|
+
const apiLeafFilters = filterGroups.flatMap((g) => g.filters ?? []);
|
|
2259
|
+
const filtersByPage = apiLeafFilters.some((f) => f.dimension === "page");
|
|
2260
|
+
if (state.aggregationType === "byProperty") {
|
|
2261
|
+
if (body.type === "discover" || body.type === "googleNews") throw new Error("aggregationType: \"byProperty\" is not supported for type \"discover\" or \"googleNews\"");
|
|
2262
|
+
if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byProperty\" is not allowed when grouping or filtering by page");
|
|
2263
|
+
}
|
|
2264
|
+
if (state.aggregationType === "byNewsShowcasePanel") {
|
|
2265
|
+
if (body.type !== "discover" && body.type !== "googleNews") throw new Error("aggregationType: \"byNewsShowcasePanel\" requires type \"discover\" or \"googleNews\"");
|
|
2266
|
+
if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byNewsShowcasePanel\" is not allowed when grouping or filtering by page");
|
|
2267
|
+
const saFilters = apiLeafFilters.filter((f) => f.dimension === "searchAppearance");
|
|
2268
|
+
const hasNewsShowcase = saFilters.some((f) => f.operator === "equals" && f.expression === "NEWS_SHOWCASE");
|
|
2269
|
+
const hasOther = saFilters.some((f) => !(f.operator === "equals" && f.expression === "NEWS_SHOWCASE"));
|
|
2270
|
+
if (!hasNewsShowcase || hasOther) throw new Error("aggregationType: \"byNewsShowcasePanel\" requires a searchAppearance equals \"NEWS_SHOWCASE\" filter and no other searchAppearance filter");
|
|
2271
|
+
}
|
|
2272
|
+
body.aggregationType = state.aggregationType;
|
|
2273
|
+
}
|
|
2185
2274
|
return body;
|
|
2186
2275
|
}
|
|
2187
2276
|
function isApiFilter(f) {
|
|
@@ -2232,6 +2321,8 @@ function gscdumpApi(options) {
|
|
|
2232
2321
|
retryDelay: 1e3,
|
|
2233
2322
|
retryStatusCodes: [
|
|
2234
2323
|
408,
|
|
2324
|
+
409,
|
|
2325
|
+
425,
|
|
2235
2326
|
429,
|
|
2236
2327
|
500,
|
|
2237
2328
|
502,
|
|
@@ -2263,10 +2354,16 @@ function gscdumpApi(options) {
|
|
|
2263
2354
|
async *query(siteId, builder, opts) {
|
|
2264
2355
|
const state = builder.getState();
|
|
2265
2356
|
const body = resolveToBody(state);
|
|
2266
|
-
const
|
|
2357
|
+
const totalCap = body.rowLimit;
|
|
2358
|
+
const pageSize = Math.min(totalCap ?? 25e3, 25e3);
|
|
2267
2359
|
let startRow = body.startRow || 0;
|
|
2360
|
+
let yielded = 0;
|
|
2361
|
+
let metadata;
|
|
2268
2362
|
while (true) {
|
|
2269
2363
|
opts?.signal?.throwIfAborted();
|
|
2364
|
+
const remaining = totalCap ? totalCap - yielded : pageSize;
|
|
2365
|
+
if (remaining <= 0) break;
|
|
2366
|
+
const rowLimit = Math.min(pageSize, remaining);
|
|
2270
2367
|
const response = await fetch(`/api/sites/${encodeURIComponent(siteId)}/query`, {
|
|
2271
2368
|
method: "POST",
|
|
2272
2369
|
body: {
|
|
@@ -2276,6 +2373,7 @@ function gscdumpApi(options) {
|
|
|
2276
2373
|
},
|
|
2277
2374
|
signal: opts?.signal
|
|
2278
2375
|
});
|
|
2376
|
+
if (response.metadata) metadata = response.metadata;
|
|
2279
2377
|
const rows = response.rows.map((row) => {
|
|
2280
2378
|
const result = rowWithMetricDefaults(row);
|
|
2281
2379
|
state.dimensions.forEach((dim) => {
|
|
@@ -2284,9 +2382,11 @@ function gscdumpApi(options) {
|
|
|
2284
2382
|
return result;
|
|
2285
2383
|
});
|
|
2286
2384
|
yield rows;
|
|
2385
|
+
yielded += rows.length;
|
|
2287
2386
|
if (!response.meta.hasMore || rows.length < rowLimit) break;
|
|
2288
2387
|
startRow += rows.length;
|
|
2289
2388
|
}
|
|
2389
|
+
return { metadata };
|
|
2290
2390
|
},
|
|
2291
2391
|
sites: (() => {
|
|
2292
2392
|
const list = async (opts) => {
|
|
@@ -2300,6 +2400,7 @@ function gscdumpApi(options) {
|
|
|
2300
2400
|
};
|
|
2301
2401
|
return Object.assign(list, {
|
|
2302
2402
|
list,
|
|
2403
|
+
get: unsupported("get"),
|
|
2303
2404
|
add: unsupported("add"),
|
|
2304
2405
|
delete: unsupported("delete")
|
|
2305
2406
|
});
|
|
@@ -2352,6 +2453,15 @@ function gscdumpApi(options) {
|
|
|
2352
2453
|
const GSC_API = "https://searchconsole.googleapis.com";
|
|
2353
2454
|
const INDEXING_API = "https://indexing.googleapis.com";
|
|
2354
2455
|
const SITE_VERIFICATION_API = "https://www.googleapis.com/siteVerification/v1";
|
|
2456
|
+
function encodeSiteUrl(siteUrl) {
|
|
2457
|
+
if (siteUrl.startsWith("sc-domain:")) return `sc-domain:${encodeURIComponent(siteUrl.slice(10))}`;
|
|
2458
|
+
return encodeURIComponent(siteUrl);
|
|
2459
|
+
}
|
|
2460
|
+
function assertValidSiteUrl(siteUrl) {
|
|
2461
|
+
if (siteUrl.startsWith("sc-domain:") && siteUrl.length > 10) return;
|
|
2462
|
+
if (/^https?:\/\/.+/.test(siteUrl)) return;
|
|
2463
|
+
throw new Error(`Invalid siteUrl: expected "https?://…" or "sc-domain:…", got "${siteUrl}"`);
|
|
2464
|
+
}
|
|
2355
2465
|
function createAuth(options) {
|
|
2356
2466
|
let credentials = { refresh_token: options.refreshToken };
|
|
2357
2467
|
return {
|
|
@@ -2393,10 +2503,21 @@ function createFetch(auth, options) {
|
|
|
2393
2503
|
return ofetch.create({
|
|
2394
2504
|
...options,
|
|
2395
2505
|
retry: 3,
|
|
2396
|
-
retryDelay:
|
|
2506
|
+
retryDelay: (ctx) => {
|
|
2507
|
+
const status = ctx.response?.status;
|
|
2508
|
+
if (status === 429 || status === 503) {
|
|
2509
|
+
const header = ctx.response?.headers.get("retry-after");
|
|
2510
|
+
if (header) {
|
|
2511
|
+
const secs = Number.parseInt(header, 10);
|
|
2512
|
+
if (Number.isFinite(secs)) return secs * 1e3;
|
|
2513
|
+
const when = Date.parse(header);
|
|
2514
|
+
if (Number.isFinite(when)) return Math.max(0, when - Date.now());
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
return 1e3;
|
|
2518
|
+
},
|
|
2397
2519
|
retryStatusCodes: [
|
|
2398
2520
|
408,
|
|
2399
|
-
409,
|
|
2400
2521
|
425,
|
|
2401
2522
|
429,
|
|
2402
2523
|
500,
|
|
@@ -2439,7 +2560,7 @@ function googleSearchConsole(auth, options = {}) {
|
|
|
2439
2560
|
}
|
|
2440
2561
|
fetch = createFetch(authState, fetchOptions);
|
|
2441
2562
|
}
|
|
2442
|
-
const rawQuery = (siteUrl, body, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${
|
|
2563
|
+
const rawQuery = (siteUrl, body, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/searchAnalytics/query`, {
|
|
2443
2564
|
method: "POST",
|
|
2444
2565
|
body,
|
|
2445
2566
|
signal: opts?.signal
|
|
@@ -2448,25 +2569,40 @@ function googleSearchConsole(auth, options = {}) {
|
|
|
2448
2569
|
async *query(siteUrl, builder, opts) {
|
|
2449
2570
|
const state = builder.getState();
|
|
2450
2571
|
const body = resolveToBody(state);
|
|
2451
|
-
const
|
|
2572
|
+
const totalCap = body.rowLimit;
|
|
2573
|
+
const pageSize = Math.min(totalCap ?? 25e3, 25e3);
|
|
2452
2574
|
let startRow = body.startRow || 0;
|
|
2575
|
+
let yielded = 0;
|
|
2576
|
+
let metadata;
|
|
2577
|
+
let responseAggregationType;
|
|
2453
2578
|
while (true) {
|
|
2454
2579
|
opts?.signal?.throwIfAborted();
|
|
2455
|
-
const
|
|
2580
|
+
const remaining = totalCap ? totalCap - yielded : pageSize;
|
|
2581
|
+
if (remaining <= 0) break;
|
|
2582
|
+
const rowLimit = Math.min(pageSize, remaining);
|
|
2583
|
+
const response = await rawQuery(siteUrl, {
|
|
2456
2584
|
...body,
|
|
2457
2585
|
startRow,
|
|
2458
2586
|
rowLimit
|
|
2459
|
-
}, opts)
|
|
2587
|
+
}, opts);
|
|
2588
|
+
if (response.metadata) metadata = response.metadata;
|
|
2589
|
+
if (response.responseAggregationType) responseAggregationType = response.responseAggregationType;
|
|
2590
|
+
const rows = (response.rows || []).map((row) => {
|
|
2460
2591
|
const result = rowWithMetricDefaults(row);
|
|
2461
2592
|
state.dimensions.forEach((dim, i) => {
|
|
2462
2593
|
result[dim] = row.keys?.[i];
|
|
2463
2594
|
});
|
|
2464
2595
|
return result;
|
|
2465
2596
|
});
|
|
2597
|
+
if (rows.length === 0) break;
|
|
2466
2598
|
yield rows;
|
|
2467
|
-
|
|
2599
|
+
yielded += rows.length;
|
|
2468
2600
|
startRow += rows.length;
|
|
2469
2601
|
}
|
|
2602
|
+
return {
|
|
2603
|
+
metadata,
|
|
2604
|
+
responseAggregationType
|
|
2605
|
+
};
|
|
2470
2606
|
},
|
|
2471
2607
|
sites: (() => {
|
|
2472
2608
|
const list = async (opts) => {
|
|
@@ -2474,11 +2610,15 @@ function googleSearchConsole(auth, options = {}) {
|
|
|
2474
2610
|
};
|
|
2475
2611
|
return Object.assign(list, {
|
|
2476
2612
|
list,
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2613
|
+
get: (siteUrl, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}`, { signal: opts?.signal }),
|
|
2614
|
+
add: (siteUrl, opts) => {
|
|
2615
|
+
assertValidSiteUrl(siteUrl);
|
|
2616
|
+
return fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}`, {
|
|
2617
|
+
method: "PUT",
|
|
2618
|
+
signal: opts?.signal
|
|
2619
|
+
});
|
|
2620
|
+
},
|
|
2621
|
+
delete: (siteUrl, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}`, {
|
|
2482
2622
|
method: "DELETE",
|
|
2483
2623
|
signal: opts?.signal
|
|
2484
2624
|
})
|
|
@@ -2507,7 +2647,11 @@ function googleSearchConsole(auth, options = {}) {
|
|
|
2507
2647
|
},
|
|
2508
2648
|
inspect: (siteUrl, url, opts) => fetch(`${GSC_API}/v1/urlInspection/index:inspect`, {
|
|
2509
2649
|
method: "POST",
|
|
2510
|
-
body: {
|
|
2650
|
+
body: opts?.languageCode ? {
|
|
2651
|
+
inspectionUrl: url,
|
|
2652
|
+
siteUrl,
|
|
2653
|
+
languageCode: opts.languageCode
|
|
2654
|
+
} : {
|
|
2511
2655
|
inspectionUrl: url,
|
|
2512
2656
|
siteUrl
|
|
2513
2657
|
},
|
|
@@ -2515,14 +2659,17 @@ function googleSearchConsole(auth, options = {}) {
|
|
|
2515
2659
|
}),
|
|
2516
2660
|
sitemaps: {
|
|
2517
2661
|
list: async (siteUrl, opts) => {
|
|
2518
|
-
return (await fetch(`${GSC_API}/webmasters/v3/sites/${
|
|
2662
|
+
return (await fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps`, {
|
|
2663
|
+
signal: opts?.signal,
|
|
2664
|
+
query: opts?.sitemapIndex ? { sitemapIndex: opts.sitemapIndex } : void 0
|
|
2665
|
+
})).sitemap || [];
|
|
2519
2666
|
},
|
|
2520
|
-
get: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${
|
|
2521
|
-
submit: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${
|
|
2667
|
+
get: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, { signal: opts?.signal }),
|
|
2668
|
+
submit: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, {
|
|
2522
2669
|
method: "PUT",
|
|
2523
2670
|
signal: opts?.signal
|
|
2524
2671
|
}),
|
|
2525
|
-
delete: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${
|
|
2672
|
+
delete: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, {
|
|
2526
2673
|
method: "DELETE",
|
|
2527
2674
|
signal: opts?.signal
|
|
2528
2675
|
})
|
|
@@ -2623,6 +2770,9 @@ function parseGscSiteUrl(siteUrl) {
|
|
|
2623
2770
|
isDomain
|
|
2624
2771
|
};
|
|
2625
2772
|
}
|
|
2773
|
+
function normalizeGscSiteUrl(siteUrl) {
|
|
2774
|
+
return siteUrl.replace(/^sc-domain:/, "").replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "").toLowerCase();
|
|
2775
|
+
}
|
|
2626
2776
|
function normalizeRegistrationTarget(inputUrl) {
|
|
2627
2777
|
const trimmed = inputUrl.trim();
|
|
2628
2778
|
if (!trimmed) return null;
|
|
@@ -2695,8 +2845,8 @@ function findExactGscProperty(propertyUrl, properties) {
|
|
|
2695
2845
|
function formatGscPropertyCandidates(candidates) {
|
|
2696
2846
|
return candidates.filter((c) => !!c).map((property) => `${property.siteUrl} (${property.permissionLevel})`).join(", ");
|
|
2697
2847
|
}
|
|
2698
|
-
const
|
|
2699
|
-
const
|
|
2848
|
+
const URL_INSPECTION_DAILY_LIMIT = 2e3;
|
|
2849
|
+
const URL_INSPECTION_EFFECTIVE_LIMIT = 1800;
|
|
2700
2850
|
const GSCDUMP_ONBOARDING_CONTRACT_VERSION = "2026-05-11";
|
|
2701
2851
|
const GSCDUMP_REQUIRED_ANALYTICS_SCOPE = "https://www.googleapis.com/auth/webmasters.readonly";
|
|
2702
2852
|
const GSCDUMP_OPTIONAL_INDEXING_SCOPE = "https://www.googleapis.com/auth/indexing";
|
|
@@ -2885,4 +3035,4 @@ async function fetchSitemapUrls(sitemapUrl, options = {}) {
|
|
|
2885
3035
|
await visit(sitemapUrl, 0);
|
|
2886
3036
|
return out;
|
|
2887
3037
|
}
|
|
2888
|
-
export { DAYS_PER_RANGE, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GscApiError,
|
|
3038
|
+
export { DAYS_PER_RANGE, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GscApiError, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, MS_PER_DAY, URL_INSPECTION_DAILY_LIMIT, URL_INSPECTION_EFFECTIVE_LIMIT, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchRequestIndexing, canUseUrlInspection, classifyError, countDays, createAuth, createFetch, daysAgo, deleteSite, deleteSitemap, discoverSitemap, exchangeAuthCode, fetchSitemap, fetchSitemapUrls, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatErrorForCli, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, grantedScopeList, groupIntoRanges, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, verificationMethodsFor, verifySite };
|
package/dist/query/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _dayjs, { Dayjs } from "dayjs";
|
|
2
2
|
type TableName = 'pages' | 'keywords' | 'countries' | 'devices' | 'page_keywords' | 'search_appearance';
|
|
3
|
-
type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'searchAppearance';
|
|
3
|
+
type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'hour' | 'searchAppearance';
|
|
4
|
+
type GscDataState = 'final' | 'all' | 'hourly_all';
|
|
4
5
|
type GscSearchAnalyticsFilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
|
|
5
6
|
interface GscSearchAnalyticsFilter {
|
|
6
7
|
dimension: GscSearchAnalyticsDimension;
|
|
@@ -12,6 +13,7 @@ interface GscSearchAnalyticsFilterGroup {
|
|
|
12
13
|
filters: GscSearchAnalyticsFilter[];
|
|
13
14
|
}
|
|
14
15
|
type GscSearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
|
|
16
|
+
type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
15
17
|
interface GscSearchAnalyticsRequest {
|
|
16
18
|
startDate: string;
|
|
17
19
|
endDate: string;
|
|
@@ -19,7 +21,10 @@ interface GscSearchAnalyticsRequest {
|
|
|
19
21
|
dimensionFilterGroups?: GscSearchAnalyticsFilterGroup[];
|
|
20
22
|
rowLimit?: number;
|
|
21
23
|
startRow?: number;
|
|
22
|
-
searchType
|
|
24
|
+
/** GSC search corpus. Maps to wire field `type` (the API still accepts the deprecated `searchType` alias). */
|
|
25
|
+
type?: GscSearchType;
|
|
26
|
+
dataState?: GscDataState;
|
|
27
|
+
aggregationType?: GscAggregationType;
|
|
23
28
|
}
|
|
24
29
|
declare const _default: {
|
|
25
30
|
name: string;
|
|
@@ -52,6 +57,8 @@ interface DimensionValueMap {
|
|
|
52
57
|
device: Device;
|
|
53
58
|
searchAppearance: string;
|
|
54
59
|
date: string;
|
|
60
|
+
/** Hour bucket — ISO-8601 with PT offset, e.g. `2025-07-14T13:00:00-07:00`. Use with `dataState: 'hourly_all'`. */
|
|
61
|
+
hour: string;
|
|
55
62
|
}
|
|
56
63
|
type Dimension = keyof DimensionValueMap;
|
|
57
64
|
interface QueryParamValueMap {
|
|
@@ -110,6 +117,12 @@ interface BuilderState {
|
|
|
110
117
|
};
|
|
111
118
|
rowLimit?: number;
|
|
112
119
|
startRow?: number;
|
|
120
|
+
/** GSC `dataState`. `'hourly_all'` is required when grouping by `hour`. */
|
|
121
|
+
dataState?: GscDataState;
|
|
122
|
+
/** GSC `aggregationType`. `'byNewsShowcasePanel'` requires `type=discover|googleNews` with NEWS_SHOWCASE searchAppearance. */
|
|
123
|
+
aggregationType?: GscAggregationType;
|
|
124
|
+
/** GSC search corpus. Wins over any `searchType` filter when both are set. */
|
|
125
|
+
searchType?: SearchType;
|
|
113
126
|
}
|
|
114
127
|
interface JsonInternalFilter {
|
|
115
128
|
dimension: string;
|
|
@@ -136,6 +149,10 @@ interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
|
|
|
136
149
|
orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
|
|
137
150
|
limit: (n: number) => GSCQueryBuilder<D, C>;
|
|
138
151
|
offset: (n: number) => GSCQueryBuilder<D, C>;
|
|
152
|
+
dataState: (state: GscDataState) => GSCQueryBuilder<D, C>;
|
|
153
|
+
aggregationType: (type: GscAggregationType) => GSCQueryBuilder<D, C>;
|
|
154
|
+
/** GSC search corpus (`type` on the wire). Equivalent to filtering by `searchType`. */
|
|
155
|
+
type: (t: GscSearchType) => GSCQueryBuilder<D, C>;
|
|
139
156
|
toBody: () => GscSearchAnalyticsRequest;
|
|
140
157
|
getState: () => BuilderState;
|
|
141
158
|
}
|
|
@@ -147,6 +164,7 @@ declare const device: Column<"device">;
|
|
|
147
164
|
declare const country: Column<"country">;
|
|
148
165
|
declare const searchAppearance: Column<"searchAppearance">;
|
|
149
166
|
declare const date: Column<"date">;
|
|
167
|
+
declare const hour: Column<"hour">;
|
|
150
168
|
declare const clicks: MetricColumn<"clicks">;
|
|
151
169
|
declare const impressions: MetricColumn<"impressions">;
|
|
152
170
|
declare const ctr: MetricColumn<"ctr">;
|
|
@@ -164,15 +182,15 @@ declare function and<F extends Filter<any>[]>(...filters: F): Filter<MergeConstr
|
|
|
164
182
|
declare function or<F extends Filter<any>[]>(...filters: F): Filter<object>;
|
|
165
183
|
declare function not<F extends Filter<any>>(filter: F): Filter<object>;
|
|
166
184
|
declare function gte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
167
|
-
declare function gte
|
|
185
|
+
declare function gte(column: Column<'date'>, value: string): Filter<object>;
|
|
168
186
|
declare function gt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
169
|
-
declare function gt
|
|
187
|
+
declare function gt(column: Column<'date'>, value: string): Filter<object>;
|
|
170
188
|
declare function lte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
171
|
-
declare function lte
|
|
189
|
+
declare function lte(column: Column<'date'>, value: string): Filter<object>;
|
|
172
190
|
declare function lt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
173
|
-
declare function lt
|
|
191
|
+
declare function lt(column: Column<'date'>, value: string): Filter<object>;
|
|
174
192
|
declare function between<M extends Metric>(column: MetricColumn<M>, start: number, end: number): Filter<object>;
|
|
175
|
-
declare function between
|
|
193
|
+
declare function between(column: Column<'date'>, start: string, end: string): Filter<object>;
|
|
176
194
|
declare function topLevel(column: Column<'page'>): Filter<object>;
|
|
177
195
|
type LogicalDataset = TableName;
|
|
178
196
|
type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
|
|
@@ -239,6 +257,7 @@ declare function buildLogicalComparisonPlan(current: BuilderState, previous: Bui
|
|
|
239
257
|
declare function isJsonFilter(value: unknown): value is JsonFilter;
|
|
240
258
|
declare function parseJsonFilter(json: JsonFilter): Filter<any>;
|
|
241
259
|
declare function normalizeFilter(input?: FilterInput): Filter<any> | undefined;
|
|
260
|
+
declare function normalizeBuilderState(state: unknown): BuilderState;
|
|
242
261
|
declare function extractDateRange(input?: FilterInput): {
|
|
243
262
|
startDate?: string;
|
|
244
263
|
endDate?: string;
|
|
@@ -257,4 +276,4 @@ declare function currentPstDate(): string;
|
|
|
257
276
|
declare function dayjsPst(): Dayjs;
|
|
258
277
|
declare function today(): string;
|
|
259
278
|
declare function daysAgo(n: number): string;
|
|
260
|
-
export { type BuilderState, type Column, type ComparisonFilter, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type FilterInput, type GSCQueryBuilder, type GSCResult, type GSCRow, type InternalFilter, type JsonFilter, type JsonInternalFilter, type LogicalComparisonPlan, type LogicalDataset, type LogicalDimensionFilter, type LogicalMetricFilter, type LogicalQueryPlan, type Metric, type MetricColumn, type PlannerCapabilities, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
|
|
279
|
+
export { type BuilderState, type Column, type ComparisonFilter, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type FilterInput, type GSCQueryBuilder, type GSCResult, type GSCRow, type InternalFilter, type JsonFilter, type JsonInternalFilter, type LogicalComparisonPlan, type LogicalDataset, type LogicalDimensionFilter, type LogicalMetricFilter, type LogicalQueryPlan, type Metric, type MetricColumn, type PlannerCapabilities, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, hour, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeBuilderState, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
|
package/dist/query/index.mjs
CHANGED
|
@@ -1631,6 +1631,20 @@ function normalizeFilter(input) {
|
|
|
1631
1631
|
if (isWireFilter(input)) return convertWireGroup(input) ?? void 0;
|
|
1632
1632
|
return input;
|
|
1633
1633
|
}
|
|
1634
|
+
function normalizeBuilderState(state) {
|
|
1635
|
+
if (!state || typeof state !== "object") throw new Error("Invalid state");
|
|
1636
|
+
const s = state;
|
|
1637
|
+
return {
|
|
1638
|
+
dimensions: s.dimensions,
|
|
1639
|
+
metrics: s.metrics,
|
|
1640
|
+
filter: normalizeFilter(s.filter),
|
|
1641
|
+
orderBy: s.orderBy,
|
|
1642
|
+
rowLimit: s.rowLimit,
|
|
1643
|
+
startRow: s.startRow,
|
|
1644
|
+
dataState: s.dataState,
|
|
1645
|
+
aggregationType: s.aggregationType
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1634
1648
|
function extractSpecialFilters(filter) {
|
|
1635
1649
|
if (!filter) return {};
|
|
1636
1650
|
let startDate;
|
|
@@ -1662,9 +1676,9 @@ function extractSpecialFilters(filter) {
|
|
|
1662
1676
|
else otherFilters.push(f);
|
|
1663
1677
|
if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
|
|
1664
1678
|
const extracted = extractSpecialFilters(nested);
|
|
1665
|
-
if (extracted.startDate) startDate = extracted.startDate;
|
|
1666
|
-
if (extracted.endDate) endDate = extracted.endDate;
|
|
1667
|
-
if (extracted.searchType) searchType = extracted.searchType;
|
|
1679
|
+
if (!startDate && extracted.startDate) startDate = extracted.startDate;
|
|
1680
|
+
if (!endDate && extracted.endDate) endDate = extracted.endDate;
|
|
1681
|
+
if (!searchType && extracted.searchType) searchType = extracted.searchType;
|
|
1668
1682
|
if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
|
|
1669
1683
|
}
|
|
1670
1684
|
const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
|
|
@@ -1702,6 +1716,7 @@ function extractSpecialOperatorFilters(input) {
|
|
|
1702
1716
|
}
|
|
1703
1717
|
function extractSearchType(state) {
|
|
1704
1718
|
if (!state) return void 0;
|
|
1719
|
+
if (state.searchType && KNOWN_SEARCH_TYPES.has(state.searchType)) return state.searchType;
|
|
1705
1720
|
const filter = state.filter;
|
|
1706
1721
|
if (!filter || typeof filter !== "object") return void 0;
|
|
1707
1722
|
const raw = filter.searchType;
|
|
@@ -1716,11 +1731,40 @@ function resolveToBody(state) {
|
|
|
1716
1731
|
startDate,
|
|
1717
1732
|
endDate
|
|
1718
1733
|
};
|
|
1719
|
-
|
|
1720
|
-
if (
|
|
1721
|
-
if (state.
|
|
1734
|
+
const resolvedType = state.searchType ?? searchType;
|
|
1735
|
+
if (resolvedType) body.type = resolvedType;
|
|
1736
|
+
if (state.rowLimit !== void 0) {
|
|
1737
|
+
if (!Number.isInteger(state.rowLimit) || state.rowLimit < 1) throw new Error(`rowLimit must be a positive integer, got ${state.rowLimit}`);
|
|
1738
|
+
body.rowLimit = state.rowLimit;
|
|
1739
|
+
}
|
|
1740
|
+
if (state.startRow !== void 0) {
|
|
1741
|
+
if (!Number.isInteger(state.startRow) || state.startRow < 0) throw new Error(`startRow must be a non-negative integer, got ${state.startRow}`);
|
|
1742
|
+
if (state.startRow > 0) body.startRow = state.startRow;
|
|
1743
|
+
}
|
|
1744
|
+
const hasHour = state.dimensions?.includes("hour");
|
|
1745
|
+
if (hasHour && state.dataState !== "hourly_all") throw new Error("hour dimension requires dataState: \"hourly_all\"");
|
|
1746
|
+
if (state.dataState === "hourly_all" && !hasHour) throw new Error("dataState: \"hourly_all\" requires grouping by hour dimension");
|
|
1747
|
+
if (state.dataState) body.dataState = state.dataState;
|
|
1722
1748
|
const filterGroups = resolveFilter(dimensionFilter);
|
|
1723
1749
|
if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
|
|
1750
|
+
if (state.aggregationType) {
|
|
1751
|
+
const groupsByPage = (body.dimensions ?? []).includes("page");
|
|
1752
|
+
const apiLeafFilters = filterGroups.flatMap((g) => g.filters ?? []);
|
|
1753
|
+
const filtersByPage = apiLeafFilters.some((f) => f.dimension === "page");
|
|
1754
|
+
if (state.aggregationType === "byProperty") {
|
|
1755
|
+
if (body.type === "discover" || body.type === "googleNews") throw new Error("aggregationType: \"byProperty\" is not supported for type \"discover\" or \"googleNews\"");
|
|
1756
|
+
if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byProperty\" is not allowed when grouping or filtering by page");
|
|
1757
|
+
}
|
|
1758
|
+
if (state.aggregationType === "byNewsShowcasePanel") {
|
|
1759
|
+
if (body.type !== "discover" && body.type !== "googleNews") throw new Error("aggregationType: \"byNewsShowcasePanel\" requires type \"discover\" or \"googleNews\"");
|
|
1760
|
+
if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byNewsShowcasePanel\" is not allowed when grouping or filtering by page");
|
|
1761
|
+
const saFilters = apiLeafFilters.filter((f) => f.dimension === "searchAppearance");
|
|
1762
|
+
const hasNewsShowcase = saFilters.some((f) => f.operator === "equals" && f.expression === "NEWS_SHOWCASE");
|
|
1763
|
+
const hasOther = saFilters.some((f) => !(f.operator === "equals" && f.expression === "NEWS_SHOWCASE"));
|
|
1764
|
+
if (!hasNewsShowcase || hasOther) throw new Error("aggregationType: \"byNewsShowcasePanel\" requires a searchAppearance equals \"NEWS_SHOWCASE\" filter and no other searchAppearance filter");
|
|
1765
|
+
}
|
|
1766
|
+
body.aggregationType = state.aggregationType;
|
|
1767
|
+
}
|
|
1724
1768
|
return body;
|
|
1725
1769
|
}
|
|
1726
1770
|
function isApiFilter(f) {
|
|
@@ -1805,6 +1849,24 @@ function createBuilder(state) {
|
|
|
1805
1849
|
startRow: n
|
|
1806
1850
|
});
|
|
1807
1851
|
},
|
|
1852
|
+
dataState(s) {
|
|
1853
|
+
return createBuilder({
|
|
1854
|
+
...state,
|
|
1855
|
+
dataState: s
|
|
1856
|
+
});
|
|
1857
|
+
},
|
|
1858
|
+
aggregationType(t) {
|
|
1859
|
+
return createBuilder({
|
|
1860
|
+
...state,
|
|
1861
|
+
aggregationType: t
|
|
1862
|
+
});
|
|
1863
|
+
},
|
|
1864
|
+
type(t) {
|
|
1865
|
+
return createBuilder({
|
|
1866
|
+
...state,
|
|
1867
|
+
searchType: t
|
|
1868
|
+
});
|
|
1869
|
+
},
|
|
1808
1870
|
toBody() {
|
|
1809
1871
|
return resolveToBody(state);
|
|
1810
1872
|
},
|
|
@@ -1830,6 +1892,7 @@ const device = createColumn("device");
|
|
|
1830
1892
|
const country = createColumn("country");
|
|
1831
1893
|
const searchAppearance = createColumn("searchAppearance");
|
|
1832
1894
|
const date = createColumn("date");
|
|
1895
|
+
const hour = createColumn("hour");
|
|
1833
1896
|
const clicks = createMetricColumn("clicks");
|
|
1834
1897
|
const impressions = createMetricColumn("impressions");
|
|
1835
1898
|
const ctr = createMetricColumn("ctr");
|
|
@@ -1857,6 +1920,7 @@ function ne(column, value) {
|
|
|
1857
1920
|
return leafFilter(column.dimension, "notEquals", String(value));
|
|
1858
1921
|
}
|
|
1859
1922
|
function inArray(column, values) {
|
|
1923
|
+
if (values.length === 0) throw new Error(`inArray(${column.dimension}, []) requires at least one value`);
|
|
1860
1924
|
return {
|
|
1861
1925
|
_constraints: {},
|
|
1862
1926
|
_filters: values.map((v) => ({
|
|
@@ -1871,7 +1935,9 @@ function contains(column, pattern) {
|
|
|
1871
1935
|
return leafFilter(column.dimension, "contains", pattern);
|
|
1872
1936
|
}
|
|
1873
1937
|
function like(column, pattern) {
|
|
1874
|
-
return leafFilter(column.dimension, "contains", pattern
|
|
1938
|
+
if (!/[%_]/.test(pattern)) return leafFilter(column.dimension, "contains", pattern);
|
|
1939
|
+
const regex = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".");
|
|
1940
|
+
return leafFilter(column.dimension, "includingRegex", regex);
|
|
1875
1941
|
}
|
|
1876
1942
|
function regex(column, pattern) {
|
|
1877
1943
|
return leafFilter(column.dimension, "includingRegex", typeof pattern === "string" ? pattern : pattern.source);
|
|
@@ -1895,6 +1961,14 @@ function and(...filters) {
|
|
|
1895
1961
|
};
|
|
1896
1962
|
}
|
|
1897
1963
|
function or(...filters) {
|
|
1964
|
+
for (const f of filters) {
|
|
1965
|
+
if (f._groupType === "and" && f._filters.length > 1) throw new Error("or() cannot contain a multi-leaf AND group: GSC filter groups do not nest. Restructure as flat OR or split into multiple queries.");
|
|
1966
|
+
if (f._nestedGroups && f._nestedGroups.length > 0) throw new Error("or() cannot contain nested filter groups: GSC filter groups do not nest.");
|
|
1967
|
+
for (const leaf of f._filters) {
|
|
1968
|
+
if (leaf.dimension === "date") throw new Error("or() cannot contain a date filter: GSC date range is a top-level AND-applied request field, not a filter. Apply the date range outside the or() group.");
|
|
1969
|
+
if (leaf.dimension === "searchType") throw new Error("or() cannot contain a searchType filter: GSC search type is a top-level AND-applied request field. Use .type() outside the or() group.");
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1898
1972
|
return {
|
|
1899
1973
|
_constraints: {},
|
|
1900
1974
|
_filters: filters.flatMap((f) => f._filters),
|
|
@@ -1902,23 +1976,30 @@ function or(...filters) {
|
|
|
1902
1976
|
};
|
|
1903
1977
|
}
|
|
1904
1978
|
function not(filter) {
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1979
|
+
const inverted = [];
|
|
1980
|
+
for (const f of filter._filters) {
|
|
1981
|
+
if (DATE_OPERATORS.includes(f.operator)) throw new Error(`not() cannot invert date operator "${f.operator}": GSC has no negated date filter.`);
|
|
1982
|
+
if (!(f.operator in INVERSIONS)) throw new Error(`not() cannot invert operator "${f.operator}".`);
|
|
1983
|
+
inverted.push({
|
|
1908
1984
|
...f,
|
|
1909
1985
|
operator: invertOperator(f.operator)
|
|
1910
|
-
})
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
return {
|
|
1989
|
+
_constraints: {},
|
|
1990
|
+
_filters: inverted
|
|
1911
1991
|
};
|
|
1912
1992
|
}
|
|
1993
|
+
const INVERSIONS = {
|
|
1994
|
+
equals: "notEquals",
|
|
1995
|
+
notEquals: "equals",
|
|
1996
|
+
contains: "notContains",
|
|
1997
|
+
notContains: "contains",
|
|
1998
|
+
includingRegex: "excludingRegex",
|
|
1999
|
+
excludingRegex: "includingRegex"
|
|
2000
|
+
};
|
|
1913
2001
|
function invertOperator(op) {
|
|
1914
|
-
return
|
|
1915
|
-
equals: "notEquals",
|
|
1916
|
-
notEquals: "equals",
|
|
1917
|
-
contains: "notContains",
|
|
1918
|
-
notContains: "contains",
|
|
1919
|
-
includingRegex: "excludingRegex",
|
|
1920
|
-
excludingRegex: "includingRegex"
|
|
1921
|
-
}[op];
|
|
2002
|
+
return INVERSIONS[op];
|
|
1922
2003
|
}
|
|
1923
2004
|
function gte(column, value) {
|
|
1924
2005
|
return metricOrDimFilter(column, "metricGte", "gte", String(value));
|
|
@@ -2085,4 +2166,4 @@ function today() {
|
|
|
2085
2166
|
function daysAgo(n) {
|
|
2086
2167
|
return dayjsPst().subtract(n, "day").format("YYYY-MM-DD");
|
|
2087
2168
|
}
|
|
2088
|
-
export { Countries, Devices, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
|
|
2169
|
+
export { Countries, Devices, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, hour, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeBuilderState, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
|
package/dist/query/plan.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
type TableName = 'pages' | 'keywords' | 'countries' | 'devices' | 'page_keywords' | 'search_appearance';
|
|
2
|
+
type GscDataState = 'final' | 'all' | 'hourly_all';
|
|
3
|
+
type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
2
4
|
declare const _default: {
|
|
3
5
|
name: string;
|
|
4
6
|
'alpha-2': string;
|
|
@@ -30,6 +32,8 @@ interface DimensionValueMap {
|
|
|
30
32
|
device: Device;
|
|
31
33
|
searchAppearance: string;
|
|
32
34
|
date: string;
|
|
35
|
+
/** Hour bucket — ISO-8601 with PT offset, e.g. `2025-07-14T13:00:00-07:00`. Use with `dataState: 'hourly_all'`. */
|
|
36
|
+
hour: string;
|
|
33
37
|
}
|
|
34
38
|
type Dimension = keyof DimensionValueMap;
|
|
35
39
|
interface QueryParamValueMap {
|
|
@@ -65,6 +69,12 @@ interface BuilderState {
|
|
|
65
69
|
};
|
|
66
70
|
rowLimit?: number;
|
|
67
71
|
startRow?: number;
|
|
72
|
+
/** GSC `dataState`. `'hourly_all'` is required when grouping by `hour`. */
|
|
73
|
+
dataState?: GscDataState;
|
|
74
|
+
/** GSC `aggregationType`. `'byNewsShowcasePanel'` requires `type=discover|googleNews` with NEWS_SHOWCASE searchAppearance. */
|
|
75
|
+
aggregationType?: GscAggregationType;
|
|
76
|
+
/** GSC search corpus. Wins over any `searchType` filter when both are set. */
|
|
77
|
+
searchType?: SearchType;
|
|
68
78
|
}
|
|
69
79
|
type LogicalDataset = TableName;
|
|
70
80
|
type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
|
package/dist/query/plan.mjs
CHANGED
|
@@ -1627,9 +1627,9 @@ function extractSpecialFilters(filter) {
|
|
|
1627
1627
|
else otherFilters.push(f);
|
|
1628
1628
|
if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
|
|
1629
1629
|
const extracted = extractSpecialFilters(nested);
|
|
1630
|
-
if (extracted.startDate) startDate = extracted.startDate;
|
|
1631
|
-
if (extracted.endDate) endDate = extracted.endDate;
|
|
1632
|
-
if (extracted.searchType) searchType = extracted.searchType;
|
|
1630
|
+
if (!startDate && extracted.startDate) startDate = extracted.startDate;
|
|
1631
|
+
if (!endDate && extracted.endDate) endDate = extracted.endDate;
|
|
1632
|
+
if (!searchType && extracted.searchType) searchType = extracted.searchType;
|
|
1633
1633
|
if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
|
|
1634
1634
|
}
|
|
1635
1635
|
const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscdump",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"description": "Google Search Console API wrapper with typed query builder, streaming pagination, and SEO analysis functions",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"defu": "^6.1.7",
|
|
109
109
|
"ofetch": "^1.5.1",
|
|
110
110
|
"ufo": "^1.6.4",
|
|
111
|
-
"@gscdump/contracts": "0.
|
|
111
|
+
"@gscdump/contracts": "0.18.0"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@googleapis/indexing": "^6.0.1",
|