gscdump 0.17.5 → 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 +54 -11
- package/dist/index.mjs +185 -38
- package/dist/query/index.d.mts +26 -8
- package/dist/query/index.mjs +90 -21
- 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;
|
|
@@ -850,4 +893,4 @@ interface FetchSitemapUrlsOptions extends DiscoverSitemapOptions {
|
|
|
850
893
|
* `<loc>https://...</loc>` shape but doesn't validate the schema.
|
|
851
894
|
*/
|
|
852
895
|
declare function fetchSitemapUrls(sitemapUrl: string, options?: FetchSitemapUrlsOptions): Promise<string[]>;
|
|
853
|
-
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
|
})
|
|
@@ -2698,8 +2845,8 @@ function findExactGscProperty(propertyUrl, properties) {
|
|
|
2698
2845
|
function formatGscPropertyCandidates(candidates) {
|
|
2699
2846
|
return candidates.filter((c) => !!c).map((property) => `${property.siteUrl} (${property.permissionLevel})`).join(", ");
|
|
2700
2847
|
}
|
|
2701
|
-
const
|
|
2702
|
-
const
|
|
2848
|
+
const URL_INSPECTION_DAILY_LIMIT = 2e3;
|
|
2849
|
+
const URL_INSPECTION_EFFECTIVE_LIMIT = 1800;
|
|
2703
2850
|
const GSCDUMP_ONBOARDING_CONTRACT_VERSION = "2026-05-11";
|
|
2704
2851
|
const GSCDUMP_REQUIRED_ANALYTICS_SCOPE = "https://www.googleapis.com/auth/webmasters.readonly";
|
|
2705
2852
|
const GSCDUMP_OPTIONAL_INDEXING_SCOPE = "https://www.googleapis.com/auth/indexing";
|
|
@@ -2888,4 +3035,4 @@ async function fetchSitemapUrls(sitemapUrl, options = {}) {
|
|
|
2888
3035
|
await visit(sitemapUrl, 0);
|
|
2889
3036
|
return out;
|
|
2890
3037
|
}
|
|
2891
|
-
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';
|
|
@@ -258,4 +276,4 @@ declare function currentPstDate(): string;
|
|
|
258
276
|
declare function dayjsPst(): Dayjs;
|
|
259
277
|
declare function today(): string;
|
|
260
278
|
declare function daysAgo(n: number): string;
|
|
261
|
-
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, normalizeBuilderState, 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
|
@@ -1640,7 +1640,9 @@ function normalizeBuilderState(state) {
|
|
|
1640
1640
|
filter: normalizeFilter(s.filter),
|
|
1641
1641
|
orderBy: s.orderBy,
|
|
1642
1642
|
rowLimit: s.rowLimit,
|
|
1643
|
-
startRow: s.startRow
|
|
1643
|
+
startRow: s.startRow,
|
|
1644
|
+
dataState: s.dataState,
|
|
1645
|
+
aggregationType: s.aggregationType
|
|
1644
1646
|
};
|
|
1645
1647
|
}
|
|
1646
1648
|
function extractSpecialFilters(filter) {
|
|
@@ -1674,9 +1676,9 @@ function extractSpecialFilters(filter) {
|
|
|
1674
1676
|
else otherFilters.push(f);
|
|
1675
1677
|
if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
|
|
1676
1678
|
const extracted = extractSpecialFilters(nested);
|
|
1677
|
-
if (extracted.startDate) startDate = extracted.startDate;
|
|
1678
|
-
if (extracted.endDate) endDate = extracted.endDate;
|
|
1679
|
-
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;
|
|
1680
1682
|
if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
|
|
1681
1683
|
}
|
|
1682
1684
|
const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
|
|
@@ -1714,6 +1716,7 @@ function extractSpecialOperatorFilters(input) {
|
|
|
1714
1716
|
}
|
|
1715
1717
|
function extractSearchType(state) {
|
|
1716
1718
|
if (!state) return void 0;
|
|
1719
|
+
if (state.searchType && KNOWN_SEARCH_TYPES.has(state.searchType)) return state.searchType;
|
|
1717
1720
|
const filter = state.filter;
|
|
1718
1721
|
if (!filter || typeof filter !== "object") return void 0;
|
|
1719
1722
|
const raw = filter.searchType;
|
|
@@ -1728,11 +1731,40 @@ function resolveToBody(state) {
|
|
|
1728
1731
|
startDate,
|
|
1729
1732
|
endDate
|
|
1730
1733
|
};
|
|
1731
|
-
|
|
1732
|
-
if (
|
|
1733
|
-
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;
|
|
1734
1748
|
const filterGroups = resolveFilter(dimensionFilter);
|
|
1735
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
|
+
}
|
|
1736
1768
|
return body;
|
|
1737
1769
|
}
|
|
1738
1770
|
function isApiFilter(f) {
|
|
@@ -1817,6 +1849,24 @@ function createBuilder(state) {
|
|
|
1817
1849
|
startRow: n
|
|
1818
1850
|
});
|
|
1819
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
|
+
},
|
|
1820
1870
|
toBody() {
|
|
1821
1871
|
return resolveToBody(state);
|
|
1822
1872
|
},
|
|
@@ -1842,6 +1892,7 @@ const device = createColumn("device");
|
|
|
1842
1892
|
const country = createColumn("country");
|
|
1843
1893
|
const searchAppearance = createColumn("searchAppearance");
|
|
1844
1894
|
const date = createColumn("date");
|
|
1895
|
+
const hour = createColumn("hour");
|
|
1845
1896
|
const clicks = createMetricColumn("clicks");
|
|
1846
1897
|
const impressions = createMetricColumn("impressions");
|
|
1847
1898
|
const ctr = createMetricColumn("ctr");
|
|
@@ -1869,6 +1920,7 @@ function ne(column, value) {
|
|
|
1869
1920
|
return leafFilter(column.dimension, "notEquals", String(value));
|
|
1870
1921
|
}
|
|
1871
1922
|
function inArray(column, values) {
|
|
1923
|
+
if (values.length === 0) throw new Error(`inArray(${column.dimension}, []) requires at least one value`);
|
|
1872
1924
|
return {
|
|
1873
1925
|
_constraints: {},
|
|
1874
1926
|
_filters: values.map((v) => ({
|
|
@@ -1883,7 +1935,9 @@ function contains(column, pattern) {
|
|
|
1883
1935
|
return leafFilter(column.dimension, "contains", pattern);
|
|
1884
1936
|
}
|
|
1885
1937
|
function like(column, pattern) {
|
|
1886
|
-
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);
|
|
1887
1941
|
}
|
|
1888
1942
|
function regex(column, pattern) {
|
|
1889
1943
|
return leafFilter(column.dimension, "includingRegex", typeof pattern === "string" ? pattern : pattern.source);
|
|
@@ -1907,6 +1961,14 @@ function and(...filters) {
|
|
|
1907
1961
|
};
|
|
1908
1962
|
}
|
|
1909
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
|
+
}
|
|
1910
1972
|
return {
|
|
1911
1973
|
_constraints: {},
|
|
1912
1974
|
_filters: filters.flatMap((f) => f._filters),
|
|
@@ -1914,23 +1976,30 @@ function or(...filters) {
|
|
|
1914
1976
|
};
|
|
1915
1977
|
}
|
|
1916
1978
|
function not(filter) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
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({
|
|
1920
1984
|
...f,
|
|
1921
1985
|
operator: invertOperator(f.operator)
|
|
1922
|
-
})
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
return {
|
|
1989
|
+
_constraints: {},
|
|
1990
|
+
_filters: inverted
|
|
1923
1991
|
};
|
|
1924
1992
|
}
|
|
1993
|
+
const INVERSIONS = {
|
|
1994
|
+
equals: "notEquals",
|
|
1995
|
+
notEquals: "equals",
|
|
1996
|
+
contains: "notContains",
|
|
1997
|
+
notContains: "contains",
|
|
1998
|
+
includingRegex: "excludingRegex",
|
|
1999
|
+
excludingRegex: "includingRegex"
|
|
2000
|
+
};
|
|
1925
2001
|
function invertOperator(op) {
|
|
1926
|
-
return
|
|
1927
|
-
equals: "notEquals",
|
|
1928
|
-
notEquals: "equals",
|
|
1929
|
-
contains: "notContains",
|
|
1930
|
-
notContains: "contains",
|
|
1931
|
-
includingRegex: "excludingRegex",
|
|
1932
|
-
excludingRegex: "includingRegex"
|
|
1933
|
-
}[op];
|
|
2002
|
+
return INVERSIONS[op];
|
|
1934
2003
|
}
|
|
1935
2004
|
function gte(column, value) {
|
|
1936
2005
|
return metricOrDimFilter(column, "metricGte", "gte", String(value));
|
|
@@ -2097,4 +2166,4 @@ function today() {
|
|
|
2097
2166
|
function daysAgo(n) {
|
|
2098
2167
|
return dayjsPst().subtract(n, "day").format("YYYY-MM-DD");
|
|
2099
2168
|
}
|
|
2100
|
-
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, normalizeBuilderState, 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",
|