@growth-labs/seo 0.1.5 → 0.2.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/README.md +142 -54
- package/dist/bindings.d.ts +127 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +11 -0
- package/dist/bindings.js.map +1 -0
- package/dist/cron/prune-aeo-r2.d.ts +36 -0
- package/dist/cron/prune-aeo-r2.d.ts.map +1 -0
- package/dist/cron/prune-aeo-r2.js +94 -0
- package/dist/cron/prune-aeo-r2.js.map +1 -0
- package/dist/durable-objects/aeo-revalidation-coord.d.ts +69 -0
- package/dist/durable-objects/aeo-revalidation-coord.d.ts.map +1 -0
- package/dist/durable-objects/aeo-revalidation-coord.js +177 -0
- package/dist/durable-objects/aeo-revalidation-coord.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -14
- package/dist/index.js.map +1 -1
- package/dist/middleware/seo.d.ts +44 -4
- package/dist/middleware/seo.d.ts.map +1 -1
- package/dist/middleware/seo.js +237 -41
- package/dist/middleware/seo.js.map +1 -1
- package/dist/options.d.ts +1293 -6
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +238 -1
- package/dist/options.js.map +1 -1
- package/dist/routes/apple-news.d.ts +4 -0
- package/dist/routes/apple-news.d.ts.map +1 -0
- package/dist/routes/apple-news.js +28 -0
- package/dist/routes/apple-news.js.map +1 -0
- package/dist/routes/llms-full.d.ts +4 -0
- package/dist/routes/llms-full.d.ts.map +1 -0
- package/dist/routes/llms-full.js +29 -0
- package/dist/routes/llms-full.js.map +1 -0
- package/dist/routes/revalidate.d.ts +16 -0
- package/dist/routes/revalidate.d.ts.map +1 -0
- package/dist/routes/revalidate.js +243 -0
- package/dist/routes/revalidate.js.map +1 -0
- package/dist/routes/rss.d.ts.map +1 -1
- package/dist/routes/rss.js +4 -1
- package/dist/routes/rss.js.map +1 -1
- package/dist/routes/sitemap-markdown.d.ts +4 -0
- package/dist/routes/sitemap-markdown.d.ts.map +1 -0
- package/dist/routes/sitemap-markdown.js +32 -0
- package/dist/routes/sitemap-markdown.js.map +1 -0
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/aeo-summary.d.ts +35 -0
- package/dist/utils/aeo-summary.d.ts.map +1 -0
- package/dist/utils/aeo-summary.js +141 -0
- package/dist/utils/aeo-summary.js.map +1 -0
- package/dist/utils/aeo-twin-emitter.d.ts +79 -0
- package/dist/utils/aeo-twin-emitter.d.ts.map +1 -0
- package/dist/utils/aeo-twin-emitter.js +99 -0
- package/dist/utils/aeo-twin-emitter.js.map +1 -0
- package/dist/utils/aeo.d.ts +62 -12
- package/dist/utils/aeo.d.ts.map +1 -1
- package/dist/utils/aeo.js +187 -26
- package/dist/utils/aeo.js.map +1 -1
- package/dist/utils/apple-news-anf.d.ts +38 -0
- package/dist/utils/apple-news-anf.d.ts.map +1 -0
- package/dist/utils/apple-news-anf.js +120 -0
- package/dist/utils/apple-news-anf.js.map +1 -0
- package/dist/utils/apple-news-rss.d.ts +31 -0
- package/dist/utils/apple-news-rss.d.ts.map +1 -0
- package/dist/utils/apple-news-rss.js +103 -0
- package/dist/utils/apple-news-rss.js.map +1 -0
- package/dist/utils/content-filter.d.ts +52 -0
- package/dist/utils/content-filter.d.ts.map +1 -0
- package/dist/utils/content-filter.js +75 -0
- package/dist/utils/content-filter.js.map +1 -0
- package/dist/utils/crawler-class.d.ts +39 -0
- package/dist/utils/crawler-class.d.ts.map +1 -0
- package/dist/utils/crawler-class.js +127 -0
- package/dist/utils/crawler-class.js.map +1 -0
- package/dist/utils/effective-auth.d.ts +28 -0
- package/dist/utils/effective-auth.d.ts.map +1 -0
- package/dist/utils/effective-auth.js +33 -0
- package/dist/utils/effective-auth.js.map +1 -0
- package/dist/utils/fcrdns.d.ts +73 -0
- package/dist/utils/fcrdns.d.ts.map +1 -0
- package/dist/utils/fcrdns.js +219 -0
- package/dist/utils/fcrdns.js.map +1 -0
- package/dist/utils/fresh-layer.d.ts +53 -0
- package/dist/utils/fresh-layer.d.ts.map +1 -0
- package/dist/utils/fresh-layer.js +147 -0
- package/dist/utils/fresh-layer.js.map +1 -0
- package/dist/utils/index.d.ts +14 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +14 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/json-ld/article.d.ts +13 -1
- package/dist/utils/json-ld/article.d.ts.map +1 -1
- package/dist/utils/json-ld/article.js +37 -8
- package/dist/utils/json-ld/article.js.map +1 -1
- package/dist/utils/llms-full.d.ts +29 -0
- package/dist/utils/llms-full.d.ts.map +1 -0
- package/dist/utils/llms-full.js +67 -0
- package/dist/utils/llms-full.js.map +1 -0
- package/dist/utils/meta.d.ts +4 -1
- package/dist/utils/meta.d.ts.map +1 -1
- package/dist/utils/meta.js +25 -2
- package/dist/utils/meta.js.map +1 -1
- package/dist/utils/sitemap-markdown.d.ts +24 -0
- package/dist/utils/sitemap-markdown.d.ts.map +1 -0
- package/dist/utils/sitemap-markdown.js +57 -0
- package/dist/utils/sitemap-markdown.js.map +1 -0
- package/dist/utils/staleness.d.ts +27 -0
- package/dist/utils/staleness.d.ts.map +1 -0
- package/dist/utils/staleness.js +46 -0
- package/dist/utils/staleness.js.map +1 -0
- package/dist/utils/validation.d.ts +41 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +78 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Apple News Format (ANF) JSON document generator.
|
|
2
|
+
//
|
|
3
|
+
// EXPERIMENTAL in 0.2.0. Ships a conservative minimal-viable document that
|
|
4
|
+
// passes Apple News Publisher's static validation. Full round-trip validation
|
|
5
|
+
// against News Publisher requires an Apple developer account — test your
|
|
6
|
+
// output in your News Publisher dashboard before committing to this format.
|
|
7
|
+
//
|
|
8
|
+
// ANF spec: https://developer.apple.com/documentation/apple_news/apple_news_format
|
|
9
|
+
//
|
|
10
|
+
// Scope of this package: produce a valid ANF document. Submission to the News
|
|
11
|
+
// Publisher API (authentication, HMAC signing, multipart POST) is the
|
|
12
|
+
// consumer's concern — the ANF spec explicitly keeps credentials out of scope.
|
|
13
|
+
const ANF_VERSION = '1.7';
|
|
14
|
+
/**
|
|
15
|
+
* Generate an Apple News Format document for a ContentItem. The returned value
|
|
16
|
+
* is a JSON-serializable object; consumers submit it as the `article.json` part
|
|
17
|
+
* of a News Publisher multipart POST.
|
|
18
|
+
*
|
|
19
|
+
* The generated document uses a conservative default layout (7-column grid),
|
|
20
|
+
* standard component styles, and a body composed of: title, byline, hero image,
|
|
21
|
+
* then one body component per paragraph-separated block of the article's
|
|
22
|
+
* description. Consumers producing real articles should replace the body
|
|
23
|
+
* components with their rendered content before submitting.
|
|
24
|
+
*/
|
|
25
|
+
export function generateAppleNewsAnf(item, options) {
|
|
26
|
+
const identifier = item.appleNewsId ?? deriveIdentifier(item.url);
|
|
27
|
+
const language = options.language ?? item.locale ?? 'en';
|
|
28
|
+
const components = [];
|
|
29
|
+
// Title
|
|
30
|
+
components.push({
|
|
31
|
+
role: 'title',
|
|
32
|
+
layout: 'titleLayout',
|
|
33
|
+
text: item.title,
|
|
34
|
+
textStyle: 'titleStyle',
|
|
35
|
+
});
|
|
36
|
+
// Byline
|
|
37
|
+
const byline = options.byline ??
|
|
38
|
+
(item.authors?.length ? `By ${item.authors.map((a) => a.name).join(', ')}` : undefined);
|
|
39
|
+
if (byline) {
|
|
40
|
+
components.push({
|
|
41
|
+
role: 'byline',
|
|
42
|
+
layout: 'bylineLayout',
|
|
43
|
+
text: byline,
|
|
44
|
+
textStyle: 'bylineStyle',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Hero image (first, or single).
|
|
48
|
+
const heroImage = Array.isArray(item.image) ? item.image[0] : item.image;
|
|
49
|
+
if (heroImage) {
|
|
50
|
+
components.push({
|
|
51
|
+
role: 'photo',
|
|
52
|
+
layout: 'heroLayout',
|
|
53
|
+
URL: heroImage,
|
|
54
|
+
caption: item.description,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Body (from description, split on double-newline).
|
|
58
|
+
if (item.description) {
|
|
59
|
+
const paragraphs = item.description.split(/\n{2,}/).filter((p) => p.trim().length > 0);
|
|
60
|
+
for (const p of paragraphs) {
|
|
61
|
+
components.push({
|
|
62
|
+
role: 'body',
|
|
63
|
+
layout: 'bodyLayout',
|
|
64
|
+
text: p.trim(),
|
|
65
|
+
textStyle: 'bodyStyle',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
version: ANF_VERSION,
|
|
71
|
+
identifier,
|
|
72
|
+
title: item.title,
|
|
73
|
+
language,
|
|
74
|
+
layout: {
|
|
75
|
+
columns: 7,
|
|
76
|
+
width: 1024,
|
|
77
|
+
margin: 75,
|
|
78
|
+
gutter: 20,
|
|
79
|
+
},
|
|
80
|
+
documentStyle: {
|
|
81
|
+
backgroundColor: '#FFFFFF',
|
|
82
|
+
},
|
|
83
|
+
metadata: {
|
|
84
|
+
canonicalURL: item.url,
|
|
85
|
+
datePublished: item.datePublished,
|
|
86
|
+
dateModified: item.dateModified,
|
|
87
|
+
thumbnailURL: heroImage,
|
|
88
|
+
excerpt: item.description,
|
|
89
|
+
authors: item.authors?.map((a) => a.name),
|
|
90
|
+
},
|
|
91
|
+
components,
|
|
92
|
+
componentLayouts: {
|
|
93
|
+
titleLayout: { columnStart: 0, columnSpan: 7, margin: { bottom: 18 } },
|
|
94
|
+
bylineLayout: { columnStart: 0, columnSpan: 7, margin: { bottom: 30 } },
|
|
95
|
+
heroLayout: { ignoreDocumentMargin: true, minimumHeight: 500 },
|
|
96
|
+
bodyLayout: { columnStart: 0, columnSpan: 7, margin: { top: 15, bottom: 15 } },
|
|
97
|
+
},
|
|
98
|
+
componentStyles: {
|
|
99
|
+
titleStyle: { textAlignment: 'left', fontName: 'HelveticaNeue-Bold', fontSize: 36 },
|
|
100
|
+
bylineStyle: { textAlignment: 'left', fontName: 'HelveticaNeue-Medium', fontSize: 13 },
|
|
101
|
+
bodyStyle: { textAlignment: 'left', fontName: 'Georgia', fontSize: 18, lineHeight: 26 },
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Derive a stable identifier from the article URL when the consumer hasn't
|
|
107
|
+
* assigned one explicitly. Must be stable across republishes so Apple News
|
|
108
|
+
* de-dupes correctly.
|
|
109
|
+
*/
|
|
110
|
+
function deriveIdentifier(url) {
|
|
111
|
+
// Strip scheme + query/hash; replace non-alphanum with '-' to stay within
|
|
112
|
+
// Apple's identifier constraints.
|
|
113
|
+
return url
|
|
114
|
+
.replace(/^https?:\/\//, '')
|
|
115
|
+
.replace(/[#?].*$/, '')
|
|
116
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
117
|
+
.replace(/-+/g, '-')
|
|
118
|
+
.replace(/^-|-$/g, '');
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=apple-news-anf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apple-news-anf.js","sourceRoot":"","sources":["../../src/utils/apple-news-anf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ResolvedSeoOptions } from '../options.js';
|
|
2
|
+
import type { ContentItem } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Map from a ContentItem to a self-contained HTML fragment suitable for
|
|
5
|
+
* Apple News `content:encoded`. Apple requires absolute URLs and no page chrome
|
|
6
|
+
* (headers, sidebars, footers). Consumers typically produce this from their
|
|
7
|
+
* rendered article's body element.
|
|
8
|
+
*/
|
|
9
|
+
export type ContentHtmlResolver = (item: ContentItem) => string | undefined;
|
|
10
|
+
export interface GenerateAppleNewsRssOptions {
|
|
11
|
+
items: ContentItem[];
|
|
12
|
+
options: ResolvedSeoOptions;
|
|
13
|
+
contentHtml?: ContentHtmlResolver;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate Apple News Publisher RSS (`/apple-news.xml`) per Apple's ingestion
|
|
17
|
+
* requirements. Apple's ingester is stricter than a generic reader — missing
|
|
18
|
+
* required fields cause silent rejection.
|
|
19
|
+
*
|
|
20
|
+
* Hard requirements implemented:
|
|
21
|
+
* - access: 'members' excluded unconditionally (via forAppleNews).
|
|
22
|
+
* - appleNewsPublishable: 'no' excluded (resolved against channel default).
|
|
23
|
+
* - content:encoded CDATA body when fullContent: true.
|
|
24
|
+
* - Hero image via <media:content> (required for Apple News).
|
|
25
|
+
* - dc:creator populated from authors[0].
|
|
26
|
+
* - Category from item.appleNewsSection or channel defaultSection.
|
|
27
|
+
* - guid in permalink form, stable across republishes.
|
|
28
|
+
* - Feed cadence: newest-first, max 100 items, no pagination.
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateAppleNewsRss({ items, options, contentHtml, }: GenerateAppleNewsRssOptions): string;
|
|
31
|
+
//# sourceMappingURL=apple-news-rss.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apple-news-rss.d.ts","sourceRoot":"","sources":["../../src/utils/apple-news-rss.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAgB9C;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,GAAG,SAAS,CAAA;AAE3E,MAAM,WAAW,2BAA2B;IAC3C,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,OAAO,EAAE,kBAAkB,CAAA;IAI3B,WAAW,CAAC,EAAE,mBAAmB,CAAA;CACjC;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,EACpC,KAAK,EACL,OAAO,EACP,WAAW,GACX,EAAE,2BAA2B,GAAG,MAAM,CAgFtC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { forAppleNews } from './content-filter.js';
|
|
2
|
+
function escapeXml(str) {
|
|
3
|
+
return str
|
|
4
|
+
.replace(/&/g, '&')
|
|
5
|
+
.replace(/</g, '<')
|
|
6
|
+
.replace(/>/g, '>')
|
|
7
|
+
.replace(/"/g, '"')
|
|
8
|
+
.replace(/'/g, ''');
|
|
9
|
+
}
|
|
10
|
+
function toRfc822(dateStr) {
|
|
11
|
+
return new Date(dateStr).toUTCString();
|
|
12
|
+
}
|
|
13
|
+
const MAX_ITEMS = 100;
|
|
14
|
+
/**
|
|
15
|
+
* Generate Apple News Publisher RSS (`/apple-news.xml`) per Apple's ingestion
|
|
16
|
+
* requirements. Apple's ingester is stricter than a generic reader — missing
|
|
17
|
+
* required fields cause silent rejection.
|
|
18
|
+
*
|
|
19
|
+
* Hard requirements implemented:
|
|
20
|
+
* - access: 'members' excluded unconditionally (via forAppleNews).
|
|
21
|
+
* - appleNewsPublishable: 'no' excluded (resolved against channel default).
|
|
22
|
+
* - content:encoded CDATA body when fullContent: true.
|
|
23
|
+
* - Hero image via <media:content> (required for Apple News).
|
|
24
|
+
* - dc:creator populated from authors[0].
|
|
25
|
+
* - Category from item.appleNewsSection or channel defaultSection.
|
|
26
|
+
* - guid in permalink form, stable across republishes.
|
|
27
|
+
* - Feed cadence: newest-first, max 100 items, no pagination.
|
|
28
|
+
*/
|
|
29
|
+
export function generateAppleNewsRss({ items, options, contentHtml, }) {
|
|
30
|
+
const appleNews = options.appleNews;
|
|
31
|
+
if (!appleNews?.enabled) {
|
|
32
|
+
throw new Error('generateAppleNewsRss called without appleNews.enabled');
|
|
33
|
+
}
|
|
34
|
+
const filtered = forAppleNews(items, { publishable: appleNews.defaultPublishable });
|
|
35
|
+
// Newest-first ordering.
|
|
36
|
+
const sorted = filtered
|
|
37
|
+
.slice()
|
|
38
|
+
.sort((a, b) => {
|
|
39
|
+
const at = a.datePublished ? new Date(a.datePublished).getTime() : 0;
|
|
40
|
+
const bt = b.datePublished ? new Date(b.datePublished).getTime() : 0;
|
|
41
|
+
return bt - at;
|
|
42
|
+
})
|
|
43
|
+
.slice(0, MAX_ITEMS);
|
|
44
|
+
const selfLink = `${options.site.replace(/\/$/, '')}${appleNews.feedPath}`;
|
|
45
|
+
const channelImage = options.organization.logo;
|
|
46
|
+
const language = options.defaults.locale.replace('_', '-');
|
|
47
|
+
const lastBuildDate = new Date().toUTCString();
|
|
48
|
+
const itemsXml = sorted
|
|
49
|
+
.map((item) => {
|
|
50
|
+
const pubDate = item.datePublished ? toRfc822(item.datePublished) : '';
|
|
51
|
+
const pubDateTag = pubDate ? `\n <pubDate>${escapeXml(pubDate)}</pubDate>` : '';
|
|
52
|
+
const creator = item.authors?.[0]?.name;
|
|
53
|
+
const creatorTag = creator ? `\n <dc:creator>${escapeXml(creator)}</dc:creator>` : '';
|
|
54
|
+
const section = item.appleNewsSection ?? appleNews.defaultSection;
|
|
55
|
+
const categoryTag = section ? `\n <category>${escapeXml(section)}</category>` : '';
|
|
56
|
+
const descTag = item.description
|
|
57
|
+
? `\n <description>${escapeXml(item.description)}</description>`
|
|
58
|
+
: '';
|
|
59
|
+
let contentTag = '';
|
|
60
|
+
if (appleNews.fullContent) {
|
|
61
|
+
const html = contentHtml?.(item);
|
|
62
|
+
if (html) {
|
|
63
|
+
// CDATA-wrap; guard against CDATA injection by splitting ']]>' sequences.
|
|
64
|
+
const safe = html.replace(/]]>/g, ']]]]><![CDATA[>');
|
|
65
|
+
contentTag = `\n <content:encoded><![CDATA[${safe}]]></content:encoded>`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Hero image is required by Apple. We emit media:content on the first image URL;
|
|
69
|
+
// consumers should ensure this is >= 1024x768 per Apple's minimums.
|
|
70
|
+
const heroImage = Array.isArray(item.image) ? item.image[0] : item.image;
|
|
71
|
+
const mediaContentTag = heroImage
|
|
72
|
+
? `\n <media:content url="${escapeXml(heroImage)}" medium="image"/>`
|
|
73
|
+
: '';
|
|
74
|
+
return ` <item>
|
|
75
|
+
<title>${escapeXml(item.title)}</title>
|
|
76
|
+
<link>${escapeXml(item.url)}</link>
|
|
77
|
+
<guid isPermaLink="true">${escapeXml(item.url)}</guid>${pubDateTag}${creatorTag}${categoryTag}${descTag}${contentTag}${mediaContentTag}
|
|
78
|
+
</item>`;
|
|
79
|
+
})
|
|
80
|
+
.join('\n');
|
|
81
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
82
|
+
<rss version="2.0"
|
|
83
|
+
xmlns:atom="http://www.w3.org/2005/Atom"
|
|
84
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
85
|
+
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
|
86
|
+
xmlns:media="http://search.yahoo.com/mrss/">
|
|
87
|
+
<channel>
|
|
88
|
+
<title>${escapeXml(appleNews.channelName)}</title>
|
|
89
|
+
<link>${escapeXml(options.site)}</link>
|
|
90
|
+
<description>${escapeXml(options.organization.name)}</description>
|
|
91
|
+
<language>${escapeXml(language)}</language>
|
|
92
|
+
<atom:link href="${escapeXml(selfLink)}" rel="self" type="application/rss+xml"/>
|
|
93
|
+
<image>
|
|
94
|
+
<url>${escapeXml(channelImage)}</url>
|
|
95
|
+
<title>${escapeXml(appleNews.channelName)}</title>
|
|
96
|
+
<link>${escapeXml(options.site)}</link>
|
|
97
|
+
</image>
|
|
98
|
+
<lastBuildDate>${lastBuildDate}</lastBuildDate>
|
|
99
|
+
${itemsXml}
|
|
100
|
+
</channel>
|
|
101
|
+
</rss>`;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=apple-news-rss.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apple-news-rss.js","sourceRoot":"","sources":["../../src/utils/apple-news-rss.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ContentItem } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Items eligible for the primary sitemap (sitemap-articles, sitemap-pages, etc.).
|
|
4
|
+
* Includes members by default — paywalled URLs should be discoverable so search
|
|
5
|
+
* engines can route users to the paywall landing. Consumers can opt out per item
|
|
6
|
+
* with `includeInSitemap: false`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function forSitemap<T extends ContentItem>(items: T[]): T[];
|
|
9
|
+
/**
|
|
10
|
+
* Items eligible for the markdown-specific sitemap (/sitemap-markdown.xml).
|
|
11
|
+
* Excludes members UNCONDITIONALLY — the markdown sitemap advertises AEO twin
|
|
12
|
+
* URLs that don't exist for gated content. `includeInSitemap: false` on a public
|
|
13
|
+
* item still hides it from here.
|
|
14
|
+
*/
|
|
15
|
+
export declare function forMarkdownSitemap<T extends ContentItem>(items: T[]): T[];
|
|
16
|
+
/**
|
|
17
|
+
* Items eligible for the RSS feed (/feed.xml). Excludes members UNCONDITIONALLY;
|
|
18
|
+
* `includeInFeed: true` on a members item is a no-op. Public items can opt out
|
|
19
|
+
* with `includeInFeed: false`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function forRss<T extends ContentItem>(items: T[]): T[];
|
|
22
|
+
/**
|
|
23
|
+
* Items eligible for Apple News Publisher RSS (/apple-news.xml). Excludes members
|
|
24
|
+
* UNCONDITIONALLY. Additionally filters by `appleNewsPublishable` — if set to 'no'
|
|
25
|
+
* at the item level, excluded regardless of channel default.
|
|
26
|
+
*/
|
|
27
|
+
export declare function forAppleNews<T extends ContentItem>(items: T[], defaults: {
|
|
28
|
+
publishable: 'yes' | 'no';
|
|
29
|
+
}): T[];
|
|
30
|
+
/**
|
|
31
|
+
* Items eligible for the narrated-articles podcast feed (/listen.xml).
|
|
32
|
+
* Requires `audio` property, excludes members UNCONDITIONALLY.
|
|
33
|
+
*/
|
|
34
|
+
export declare function forListen<T extends ContentItem>(items: T[]): T[];
|
|
35
|
+
/**
|
|
36
|
+
* Items eligible for llms.txt (the link index). Excludes members UNCONDITIONALLY.
|
|
37
|
+
* Public items with `includeInFeed: false` still appear — llms.txt is a discovery
|
|
38
|
+
* file, not a feed, so the opt-out only applies to RSS.
|
|
39
|
+
*/
|
|
40
|
+
export declare function forLlms<T extends ContentItem>(items: T[]): T[];
|
|
41
|
+
/**
|
|
42
|
+
* Items eligible for the bulk corpus dump (/llms-full.txt). Same rule as llms.txt:
|
|
43
|
+
* public items only, members unconditionally excluded.
|
|
44
|
+
*/
|
|
45
|
+
export declare function forLlmsFull<T extends ContentItem>(items: T[]): T[];
|
|
46
|
+
/**
|
|
47
|
+
* Items eligible for AEO markdown-twin emission (static mode) or middleware
|
|
48
|
+
* serving (middleware mode). Excludes members UNCONDITIONALLY — gated content
|
|
49
|
+
* is never exposed as markdown at rest or via middleware.
|
|
50
|
+
*/
|
|
51
|
+
export declare function forMarkdownTwin<T extends ContentItem>(items: T[]): T[];
|
|
52
|
+
//# sourceMappingURL=content-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-filter.d.ts","sourceRoot":"","sources":["../../src/utils/content-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAM9C;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAEjE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EACjD,KAAK,EAAE,CAAC,EAAE,EACV,QAAQ,EAAE;IAAE,WAAW,EAAE,KAAK,GAAG,IAAI,CAAA;CAAE,GACrC,CAAC,EAAE,CAOL;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAIhE;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAE9D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAElE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAEtE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Centralizes the access-rule invariants so we don't scatter "members excluded
|
|
2
|
+
// from X" across eight different routes. If the rule ever changes, it changes
|
|
3
|
+
// here, once.
|
|
4
|
+
/**
|
|
5
|
+
* Items eligible for the primary sitemap (sitemap-articles, sitemap-pages, etc.).
|
|
6
|
+
* Includes members by default — paywalled URLs should be discoverable so search
|
|
7
|
+
* engines can route users to the paywall landing. Consumers can opt out per item
|
|
8
|
+
* with `includeInSitemap: false`.
|
|
9
|
+
*/
|
|
10
|
+
export function forSitemap(items) {
|
|
11
|
+
return items.filter((item) => item.includeInSitemap !== false);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Items eligible for the markdown-specific sitemap (/sitemap-markdown.xml).
|
|
15
|
+
* Excludes members UNCONDITIONALLY — the markdown sitemap advertises AEO twin
|
|
16
|
+
* URLs that don't exist for gated content. `includeInSitemap: false` on a public
|
|
17
|
+
* item still hides it from here.
|
|
18
|
+
*/
|
|
19
|
+
export function forMarkdownSitemap(items) {
|
|
20
|
+
return items.filter((item) => item.access !== 'members' && item.includeInSitemap !== false);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Items eligible for the RSS feed (/feed.xml). Excludes members UNCONDITIONALLY;
|
|
24
|
+
* `includeInFeed: true` on a members item is a no-op. Public items can opt out
|
|
25
|
+
* with `includeInFeed: false`.
|
|
26
|
+
*/
|
|
27
|
+
export function forRss(items) {
|
|
28
|
+
return items.filter((item) => item.access !== 'members' && item.includeInFeed !== false);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Items eligible for Apple News Publisher RSS (/apple-news.xml). Excludes members
|
|
32
|
+
* UNCONDITIONALLY. Additionally filters by `appleNewsPublishable` — if set to 'no'
|
|
33
|
+
* at the item level, excluded regardless of channel default.
|
|
34
|
+
*/
|
|
35
|
+
export function forAppleNews(items, defaults) {
|
|
36
|
+
return items.filter((item) => {
|
|
37
|
+
if (item.access === 'members')
|
|
38
|
+
return false;
|
|
39
|
+
if (item.includeInFeed === false)
|
|
40
|
+
return false;
|
|
41
|
+
const effective = item.appleNewsPublishable ?? defaults.publishable;
|
|
42
|
+
return effective === 'yes';
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Items eligible for the narrated-articles podcast feed (/listen.xml).
|
|
47
|
+
* Requires `audio` property, excludes members UNCONDITIONALLY.
|
|
48
|
+
*/
|
|
49
|
+
export function forListen(items) {
|
|
50
|
+
return items.filter((item) => item.audio !== undefined && item.access !== 'members' && item.includeInFeed !== false);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Items eligible for llms.txt (the link index). Excludes members UNCONDITIONALLY.
|
|
54
|
+
* Public items with `includeInFeed: false` still appear — llms.txt is a discovery
|
|
55
|
+
* file, not a feed, so the opt-out only applies to RSS.
|
|
56
|
+
*/
|
|
57
|
+
export function forLlms(items) {
|
|
58
|
+
return items.filter((item) => item.access !== 'members');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Items eligible for the bulk corpus dump (/llms-full.txt). Same rule as llms.txt:
|
|
62
|
+
* public items only, members unconditionally excluded.
|
|
63
|
+
*/
|
|
64
|
+
export function forLlmsFull(items) {
|
|
65
|
+
return items.filter((item) => item.access !== 'members');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Items eligible for AEO markdown-twin emission (static mode) or middleware
|
|
69
|
+
* serving (middleware mode). Excludes members UNCONDITIONALLY — gated content
|
|
70
|
+
* is never exposed as markdown at rest or via middleware.
|
|
71
|
+
*/
|
|
72
|
+
export function forMarkdownTwin(items) {
|
|
73
|
+
return items.filter((item) => item.access !== 'members');
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=content-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-filter.js","sourceRoot":"","sources":["../../src/utils/content-filter.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,8EAA8E;AAC9E,cAAc;AAEd;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAwB,KAAU;IAC3D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC,CAAA;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAwB,KAAU;IACnE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC,CAAA;AAC5F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAwB,KAAU;IACvD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC,CAAA;AACzF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC3B,KAAU,EACV,QAAuC;IAEvC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QAC3C,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK;YAAE,OAAO,KAAK,CAAA;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,IAAI,QAAQ,CAAC,WAAW,CAAA;QACnE,OAAO,SAAS,KAAK,KAAK,CAAA;IAC3B,CAAC,CAAC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAwB,KAAU;IAC1D,OAAO,KAAK,CAAC,MAAM,CAClB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,CAC/F,CAAA;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAwB,KAAU;IACxD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAA;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAwB,KAAU;IAC5D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAA;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAwB,KAAU;IAChE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAA;AACzD,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CrawlerClass } from '../types.js';
|
|
2
|
+
import type { FcrdnsVerifier } from './fcrdns.js';
|
|
3
|
+
export declare const VERIFIED_SEARCH_SUFFIXES: readonly ["googlebot.com", "google.com", "search.msn.com", "applebot.apple.com", "duckduckbot.com"];
|
|
4
|
+
export interface ClassifyRequestInput {
|
|
5
|
+
request: Request;
|
|
6
|
+
fcrdnsVerify?: FcrdnsVerifier;
|
|
7
|
+
skipBotManagementFastPath?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Classify a request into one of four crawler classes.
|
|
11
|
+
*
|
|
12
|
+
* Order of checks:
|
|
13
|
+
* 1. Cloudflare Bot Management fast path (zero subrequests) when available.
|
|
14
|
+
* 2. LLM-training UA match.
|
|
15
|
+
* 3. User-directed LLM agent UA match.
|
|
16
|
+
* 4. FCrDNS against verified-search-crawler suffixes (DoH subrequests if uncached).
|
|
17
|
+
* 5. Fallthrough → anonymous.
|
|
18
|
+
*
|
|
19
|
+
* The BM fast path and UA checks always run; FCrDNS only runs when no UA matches
|
|
20
|
+
* (spoofed UAs that claim to be Googlebot without FCrDNS confirmation fail here).
|
|
21
|
+
*
|
|
22
|
+
* @returns the crawler class for cache-key segmentation, body-variant selection,
|
|
23
|
+
* and JSON-LD redaction decisions downstream.
|
|
24
|
+
*/
|
|
25
|
+
export declare function classifyRequest(input: ClassifyRequestInput): Promise<CrawlerClass>;
|
|
26
|
+
declare function matchesAnyToken(ua: string, tokens: readonly string[]): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Extract the effective client IP from a request. Prefers CF-Connecting-IP
|
|
29
|
+
* (Cloudflare-verified); falls back to X-Forwarded-For's first entry.
|
|
30
|
+
*/
|
|
31
|
+
declare function getClientIp(request: Request): string | null;
|
|
32
|
+
export declare const _internals: {
|
|
33
|
+
LLM_TRAINING_UAS: readonly ["GPTBot", "ClaudeBot", "CCBot", "Google-Extended", "PerplexityBot", "Applebot-Extended", "Bytespider", "FacebookBot", "OAI-SearchBot", "anthropic-ai", "cohere-ai", "AI2Bot", "Diffbot", "ImagesiftBot", "Omgilibot", "Omgili", "Timpibot"];
|
|
34
|
+
USER_DIRECTED_UAS: readonly ["ChatGPT-User", "Claude-User", "PerplexityBot-User", "Google-NotebookLM"];
|
|
35
|
+
matchesAnyToken: typeof matchesAnyToken;
|
|
36
|
+
getClientIp: typeof getClientIp;
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=crawler-class.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawler-class.d.ts","sourceRoot":"","sources":["../../src/utils/crawler-class.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAuCjD,eAAO,MAAM,wBAAwB,qGAM3B,CAAA;AAIV,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAA;IAGhB,YAAY,CAAC,EAAE,cAAc,CAAA;IAE7B,yBAAyB,CAAC,EAAE,OAAO,CAAA;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CA+CxF;AAID,iBAAS,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAIvE;AAED;;;GAGG;AACH,iBAAS,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CASpD;AAID,eAAO,MAAM,UAAU;;;;;CAKtB,CAAA"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ─── User-Agent patterns ───
|
|
2
|
+
// Known LLM-training crawlers. Blocked at robots.txt AND 403'd in middleware on
|
|
3
|
+
// access: 'members' items. Matched case-insensitively against the UA string.
|
|
4
|
+
const LLM_TRAINING_UAS = [
|
|
5
|
+
'GPTBot',
|
|
6
|
+
'ClaudeBot',
|
|
7
|
+
'CCBot',
|
|
8
|
+
'Google-Extended',
|
|
9
|
+
'PerplexityBot',
|
|
10
|
+
'Applebot-Extended',
|
|
11
|
+
'Bytespider',
|
|
12
|
+
'FacebookBot',
|
|
13
|
+
'OAI-SearchBot',
|
|
14
|
+
'anthropic-ai',
|
|
15
|
+
'cohere-ai',
|
|
16
|
+
'AI2Bot',
|
|
17
|
+
'Diffbot',
|
|
18
|
+
'ImagesiftBot',
|
|
19
|
+
'Omgilibot',
|
|
20
|
+
'Omgili',
|
|
21
|
+
'Timpibot',
|
|
22
|
+
];
|
|
23
|
+
// User-directed LLM agents — fetch on behalf of a logged-in user. Treated as anonymous:
|
|
24
|
+
// these forward response to a third-party model that may retain them, so never serve
|
|
25
|
+
// gated content even if member cookies are present.
|
|
26
|
+
const USER_DIRECTED_UAS = [
|
|
27
|
+
'ChatGPT-User',
|
|
28
|
+
'Claude-User',
|
|
29
|
+
'PerplexityBot-User',
|
|
30
|
+
'Google-NotebookLM',
|
|
31
|
+
];
|
|
32
|
+
// FCrDNS suffixes for verified search crawlers. Case-insensitive, dot-boundary match.
|
|
33
|
+
// Deliberately excludes googleusercontent.com (Google Cloud VMs, Apps Script, proxy
|
|
34
|
+
// infrastructure that would let any GCP user impersonate Googlebot).
|
|
35
|
+
export const VERIFIED_SEARCH_SUFFIXES = [
|
|
36
|
+
'googlebot.com',
|
|
37
|
+
'google.com',
|
|
38
|
+
'search.msn.com',
|
|
39
|
+
'applebot.apple.com',
|
|
40
|
+
'duckduckbot.com',
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Classify a request into one of four crawler classes.
|
|
44
|
+
*
|
|
45
|
+
* Order of checks:
|
|
46
|
+
* 1. Cloudflare Bot Management fast path (zero subrequests) when available.
|
|
47
|
+
* 2. LLM-training UA match.
|
|
48
|
+
* 3. User-directed LLM agent UA match.
|
|
49
|
+
* 4. FCrDNS against verified-search-crawler suffixes (DoH subrequests if uncached).
|
|
50
|
+
* 5. Fallthrough → anonymous.
|
|
51
|
+
*
|
|
52
|
+
* The BM fast path and UA checks always run; FCrDNS only runs when no UA matches
|
|
53
|
+
* (spoofed UAs that claim to be Googlebot without FCrDNS confirmation fail here).
|
|
54
|
+
*
|
|
55
|
+
* @returns the crawler class for cache-key segmentation, body-variant selection,
|
|
56
|
+
* and JSON-LD redaction decisions downstream.
|
|
57
|
+
*/
|
|
58
|
+
export async function classifyRequest(input) {
|
|
59
|
+
const { request, fcrdnsVerify, skipBotManagementFastPath = false } = input;
|
|
60
|
+
// 1. Cloudflare Bot Management fast path — zero subrequests, Cloudflare-stood-behind.
|
|
61
|
+
// Only present on Enterprise zones with BM enabled. Absent on Free/Pro/Business.
|
|
62
|
+
if (!skipBotManagementFastPath) {
|
|
63
|
+
const cf = request.cf;
|
|
64
|
+
if (cf?.botManagement?.verifiedBot && cf.verifiedBotCategory === 'Search Engine Crawler') {
|
|
65
|
+
return 'verifiedSearchCrawler';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const ua = request.headers.get('user-agent') ?? '';
|
|
69
|
+
// 2. User-directed LLM agent — checked BEFORE training-bot UAs, because many
|
|
70
|
+
// user-directed UAs contain training-bot substrings (e.g. `PerplexityBot-User`
|
|
71
|
+
// contains `PerplexityBot`; `ChatGPT-User` contains `ChatGPT`). The more
|
|
72
|
+
// specific rule must match first.
|
|
73
|
+
if (matchesAnyToken(ua, USER_DIRECTED_UAS)) {
|
|
74
|
+
return 'userDirectedLlmAgent';
|
|
75
|
+
}
|
|
76
|
+
// 3. LLM-training crawler — matches a training-bot UA substring.
|
|
77
|
+
if (matchesAnyToken(ua, LLM_TRAINING_UAS)) {
|
|
78
|
+
return 'llmTrainingCrawler';
|
|
79
|
+
}
|
|
80
|
+
// 4. FCrDNS for verified search crawlers. Only attempted if a verifier is wired
|
|
81
|
+
// (the classifier is pure when FCrDNS isn't available — production middleware
|
|
82
|
+
// always wires it; pure-function tests can skip it).
|
|
83
|
+
if (fcrdnsVerify) {
|
|
84
|
+
const clientIp = getClientIp(request);
|
|
85
|
+
if (clientIp) {
|
|
86
|
+
const verified = await fcrdnsVerify({
|
|
87
|
+
clientIp,
|
|
88
|
+
trustedSuffixes: VERIFIED_SEARCH_SUFFIXES,
|
|
89
|
+
});
|
|
90
|
+
if (verified)
|
|
91
|
+
return 'verifiedSearchCrawler';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 5. Fallthrough.
|
|
95
|
+
return 'anonymous';
|
|
96
|
+
}
|
|
97
|
+
// ─── Internals ───
|
|
98
|
+
function matchesAnyToken(ua, tokens) {
|
|
99
|
+
if (!ua)
|
|
100
|
+
return false;
|
|
101
|
+
const lowered = ua.toLowerCase();
|
|
102
|
+
return tokens.some((token) => lowered.includes(token.toLowerCase()));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Extract the effective client IP from a request. Prefers CF-Connecting-IP
|
|
106
|
+
* (Cloudflare-verified); falls back to X-Forwarded-For's first entry.
|
|
107
|
+
*/
|
|
108
|
+
function getClientIp(request) {
|
|
109
|
+
const cf = request.headers.get('cf-connecting-ip');
|
|
110
|
+
if (cf)
|
|
111
|
+
return cf;
|
|
112
|
+
const xff = request.headers.get('x-forwarded-for');
|
|
113
|
+
if (xff) {
|
|
114
|
+
const first = xff.split(',')[0]?.trim();
|
|
115
|
+
if (first)
|
|
116
|
+
return first;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
// ─── Exports for testing ───
|
|
121
|
+
export const _internals = {
|
|
122
|
+
LLM_TRAINING_UAS,
|
|
123
|
+
USER_DIRECTED_UAS,
|
|
124
|
+
matchesAnyToken,
|
|
125
|
+
getClientIp,
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=crawler-class.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawler-class.js","sourceRoot":"","sources":["../../src/utils/crawler-class.ts"],"names":[],"mappings":"AAGA,8BAA8B;AAE9B,gFAAgF;AAChF,6EAA6E;AAC7E,MAAM,gBAAgB,GAAG;IACxB,QAAQ;IACR,WAAW;IACX,OAAO;IACP,iBAAiB;IACjB,eAAe;IACf,mBAAmB;IACnB,YAAY;IACZ,aAAa;IACb,eAAe;IACf,cAAc;IACd,WAAW;IACX,QAAQ;IACR,SAAS;IACT,cAAc;IACd,WAAW;IACX,QAAQ;IACR,UAAU;CACD,CAAA;AAEV,wFAAwF;AACxF,qFAAqF;AACrF,oDAAoD;AACpD,MAAM,iBAAiB,GAAG;IACzB,cAAc;IACd,aAAa;IACb,oBAAoB;IACpB,mBAAmB;CACV,CAAA;AAEV,sFAAsF;AACtF,oFAAoF;AACpF,qEAAqE;AACrE,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACvC,eAAe;IACf,YAAY;IACZ,gBAAgB;IAChB,oBAAoB;IACpB,iBAAiB;CACR,CAAA;AAaV;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAA2B;IAChE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,yBAAyB,GAAG,KAAK,EAAE,GAAG,KAAK,CAAA;IAE1E,sFAAsF;IACtF,oFAAoF;IACpF,IAAI,CAAC,yBAAyB,EAAE,CAAC;QAChC,MAAM,EAAE,GACP,OAGA,CAAC,EAAE,CAAA;QACJ,IAAI,EAAE,EAAE,aAAa,EAAE,WAAW,IAAI,EAAE,CAAC,mBAAmB,KAAK,uBAAuB,EAAE,CAAC;YAC1F,OAAO,uBAAuB,CAAA;QAC/B,CAAC;IACF,CAAC;IAED,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;IAElD,6EAA6E;IAC7E,kFAAkF;IAClF,4EAA4E;IAC5E,qCAAqC;IACrC,IAAI,eAAe,CAAC,EAAE,EAAE,iBAAiB,CAAC,EAAE,CAAC;QAC5C,OAAO,sBAAsB,CAAA;IAC9B,CAAC;IAED,iEAAiE;IACjE,IAAI,eAAe,CAAC,EAAE,EAAE,gBAAgB,CAAC,EAAE,CAAC;QAC3C,OAAO,oBAAoB,CAAA;IAC5B,CAAC;IAED,gFAAgF;IAChF,iFAAiF;IACjF,wDAAwD;IACxD,IAAI,YAAY,EAAE,CAAC;QAClB,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;QACrC,IAAI,QAAQ,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC;gBACnC,QAAQ;gBACR,eAAe,EAAE,wBAAwB;aACzC,CAAC,CAAA;YACF,IAAI,QAAQ;gBAAE,OAAO,uBAAuB,CAAA;QAC7C,CAAC;IACF,CAAC;IAED,kBAAkB;IAClB,OAAO,WAAW,CAAA;AACnB,CAAC;AAED,oBAAoB;AAEpB,SAAS,eAAe,CAAC,EAAU,EAAE,MAAyB;IAC7D,IAAI,CAAC,EAAE;QAAE,OAAO,KAAK,CAAA;IACrB,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,EAAE,CAAA;IAChC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;AACrE,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,OAAgB;IACpC,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAClD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAA;IACjB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAClD,IAAI,GAAG,EAAE,CAAC;QACT,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAA;QACvC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAA;IACxB,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,8BAA8B;AAE9B,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,gBAAgB;IAChB,iBAAiB;IACjB,eAAe;IACf,WAAW;CACX,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CrawlerClass, EffectiveAuthSegment } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Raw auth segment from the consumer's auth layer. Consumers produce this (typically
|
|
4
|
+
* 'anon' for unauthenticated, 'member' for a logged-in subscriber) and feed it into
|
|
5
|
+
* the package to derive the effective segment.
|
|
6
|
+
*/
|
|
7
|
+
export type RawAuthSegment = 'anon' | 'member';
|
|
8
|
+
/**
|
|
9
|
+
* Compute the effective auth segment given a crawler class and a raw consumer auth segment.
|
|
10
|
+
*
|
|
11
|
+
* This function encodes the policy from spec "Effective auth segment" (lines
|
|
12
|
+
* "crawler class overrides member cookies"):
|
|
13
|
+
*
|
|
14
|
+
* - Verified search crawler → 'search-full' (regardless of cookies). Gets the
|
|
15
|
+
* sanctioned paywall-marked full body under Flexible Sampling.
|
|
16
|
+
* - LLM training crawler → caller should 403 before calling this; if it does reach
|
|
17
|
+
* here, we surface 'anon' defensively.
|
|
18
|
+
* - User-directed LLM agent → 'anon', ALWAYS. Even if member cookies are present.
|
|
19
|
+
* Load-bearing: ChatGPT-User / Claude-User forward responses to a third-party
|
|
20
|
+
* model that may retain them; we cannot verify the cookies belong to a paid
|
|
21
|
+
* subscriber, and leaking gated content to a third-party LLM defeats gating.
|
|
22
|
+
* - Anonymous → raw as-is.
|
|
23
|
+
*
|
|
24
|
+
* Consumers use `effectiveAuthSegment` (NOT raw `authSegment`) in cache keys so
|
|
25
|
+
* that cache segmentation reflects the crawler-class override.
|
|
26
|
+
*/
|
|
27
|
+
export declare function computeEffectiveAuthSegment(crawlerClass: CrawlerClass, rawAuthSegment: RawAuthSegment): EffectiveAuthSegment;
|
|
28
|
+
//# sourceMappingURL=effective-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-auth.d.ts","sourceRoot":"","sources":["../../src/utils/effective-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAErE;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CAC1C,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,cAAc,GAC5B,oBAAoB,CAYtB"}
|