@waniwani/relay 0.4.3 → 0.6.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 CHANGED
@@ -68,6 +68,7 @@ For each matched request, `recordAgentVisit` posts one JSON record of type `Agen
68
68
  | `sourceEventId` | A random UUID for this request |
69
69
  | `relayVersion` | The version of this library |
70
70
  | `platform` | `vercel`, `cloudflare` or `cloudfront`, when the library knows the platform it runs on |
71
+ | `uses` | The relay functions this process called so far: `recordAgentVisit`, `serveAgentPage` and `handleViewerRequest`. Waniwani reads it to tell whether the code has the calls that agent pages and blocking need |
71
72
 
72
73
  The `headers` field can hold only these request headers:
73
74
 
@@ -103,18 +104,20 @@ function serveAgentPage(
103
104
 
104
105
  `ServeAgentPageOptions` takes `region`, `apiUrl` and `key` from `RelayOptions`.
105
106
 
106
- Serves the agent page of a request: a text-first copy of the page that Waniwani renders and stores. It returns that page when all of these hold, and `null` otherwise, so your middleware passes the request on to your site:
107
+ Serves the agent page of a request: a text-first copy of the page that Waniwani renders and stores. Call it for every request, not only for AI agents. A request that `isAgent` does not match and that does not prefer Markdown returns `null` at once, with no network call, so people never wait on it. This check uses the AI bot list that `isAgent` loaded, and does not start a load of its own. `handleViewerRequest` makes the same check on CloudFront. It returns the page when all of these hold, and `null` otherwise, so your middleware passes the request on to your site:
107
108
 
108
109
  - A key is set.
109
110
  - The method is `GET` or `HEAD`.
110
111
  - The request has no `x-waniwani-render` header. Waniwani sends that header when it renders your page.
111
112
  - Agent pages are on in Waniwani for the hostname of the request, and its path is in the list of agent page paths. An entry is an exact path such as `/pricing`, or a folder such as `/products/*`, which covers `/products` and every path under it. `*` covers every path.
112
- - The `User-Agent` belongs to an AI agent that acts for a user, for example `ChatGPT-User` or `Claude-User`. Crawlers that collect training data get your site as usual.
113
+ - The `User-Agent` belongs to an AI agent that acts for a user, for example `ChatGPT-User` or `Claude-User`, or the client prefers Markdown (see below). Crawlers that collect training data and ask for HTML get your site as usual.
113
114
  - Waniwani sends the stored page in time. The rules and the deadlines come from your Waniwani settings.
114
115
 
115
- You can block AI agents per site in Waniwani. When the `User-Agent` of a `GET` or `HEAD` request contains a blocked token, the function answers `403` with the text body `Forbidden`. That check runs before the agent page checks and before the `x-waniwani-render` check, so it works with agent pages off. The response carries `x-waniwani-blocked: 1`, `cache-control: private, no-store` and `vary: user-agent`.
116
+ You can block AI agents per site in Waniwani. When the `User-Agent` of a `GET` or `HEAD` request from an AI agent, or from a client that prefers Markdown, contains a blocked token, the function answers `403` with the text body `Forbidden`. That check runs before the agent page checks and before the `x-waniwani-render` check, so it works with agent pages off. The response carries `x-waniwani-blocked: 1`, `cache-control: private, no-store` and `vary: user-agent`.
116
117
 
117
- The agent gets the page as Markdown when its `Accept` header lists `text/markdown` with a `q` above 0. Every other agent gets the page as HTML, including an agent that sends `*/*` or no `Accept` header.
118
+ An agent that acts for a user gets the page as Markdown when its `Accept` header lists `text/markdown` with a `q` above 0. It gets the page as HTML in all other cases, including when it sends `*/*` or no `Accept` header.
119
+
120
+ Any other client, a person's browser or a script, gets the Markdown page only when it prefers Markdown: its `Accept` header lists `text/markdown` with a `q` above 0, and that `q` is not lower than the `q` of the most specific range that covers `text/html` (`text/html`, then `text/*`, then `*/*`). So `Accept: text/markdown` and `Accept: text/markdown, text/html;q=0.9` get Markdown, and `Accept: text/html, text/markdown;q=0.5` does not. A browser never lists `text/markdown`, so it always gets your site. Such a client never gets the HTML agent page.
118
121
 
119
122
  The page response carries the stored content type, `x-waniwani-agent-page: hit`, `cache-control: private, no-store` and `vary: user-agent, accept`. A `HEAD` request gets the same headers with no body. The settings load on the first call, with a 300 ms limit, and stay in memory for 60 seconds. After that the relay keeps serving the cached settings while it loads new ones in the background. The function never throws, and the promise never rejects.
120
123
 
@@ -127,12 +130,13 @@ import { NextResponse, type NextRequest } from "next/server";
127
130
  export async function middleware(request: NextRequest) {
128
131
  if (isAgent(request)) {
129
132
  recordAgentVisit(request);
130
- return (await serveAgentPage(request)) ?? NextResponse.next();
131
133
  }
132
- return NextResponse.next();
134
+ return (await serveAgentPage(request)) ?? NextResponse.next();
133
135
  }
134
136
  ```
135
137
 
138
+ `serveAgentPage` is outside the `isAgent` check, so a client that is not on the AI bot list and asks for Markdown gets the Markdown page. `recordAgentVisit` stays inside it, so a person's visit sends nothing.
139
+
136
140
  In a Cloudflare Worker, pass the key and fall back to your origin:
137
141
 
138
142
  ```ts
@@ -163,7 +167,7 @@ The two events do these jobs:
163
167
 
164
168
  - **Viewer request** runs on every request, before the CloudFront cache. `handleViewerRequest` decides what to do with the request:
165
169
  - When the site blocks this `User-Agent`, it answers `403`. CloudFront does not cache a response from this event, so a change to your block list applies to the next request.
166
- - When an AI agent that acts for a user asks for a path that has an agent page, it marks the request. To mark it, it adds `/__waniwani-agent/<format>/<host>` in front of the path in `request.uri`. The format is `markdown` or `html`, from the agent's `Accept` header. On CloudFront, `request.uri` is the path only, with no host and no query string. For `https://example.com/pricing?a=1` from an agent that asks for HTML, `request.uri` changes from `/pricing` to `/__waniwani-agent/html/example.com/pricing`, and the query string stays as it is.
170
+ - When an AI agent that acts for a user, or any client that prefers Markdown, asks for a path that has an agent page, it marks the request. To mark it, it adds `/__waniwani-agent/<format>/<host>` in front of the path in `request.uri`. The format is `markdown` or `html`, from the `Accept` header, with the rules of `serveAgentPage`. On CloudFront, `request.uri` is the path only, with no host and no query string. For `https://example.com/pricing?a=1` from an agent that asks for HTML, `request.uri` changes from `/pricing` to `/__waniwani-agent/html/example.com/pricing`, and the query string stays as it is.
167
171
  - In all other cases it returns `null`, and the request continues with no change.
168
172
  - **Origin request** runs only on a cache miss, which is when CloudFront has no stored response for the request. The agent page has `cache-control: private, no-store`, so CloudFront never stores it, and a marked request is always a cache miss. `serveAgentPage` sends the agent page for a marked request. It removes the mark first. So when it cannot send the page, `?? request` sends the original path to your origin.
169
173
 
@@ -173,10 +177,14 @@ Viewer-request handler:
173
177
 
174
178
  ```ts
175
179
  const request = event.Records[0].cf.request;
176
- if (isAgent(request)) await recordAgentVisit(request, { key });
177
- return (await handleViewerRequest(request, { key })) ?? request;
180
+ const visit = isAgent(request) ? recordAgentVisit(request, { key }) : null;
181
+ const answer = await handleViewerRequest(request, { key });
182
+ await visit;
183
+ return answer ?? request;
178
184
  ```
179
185
 
186
+ `recordAgentVisit` reads the path when you call it, so the record keeps the original path although `handleViewerRequest` can mark it. Start the record before `handleViewerRequest` and await it after: the record then reports both calls in `uses`.
187
+
180
188
  Origin-request handler:
181
189
 
182
190
  ```ts
@@ -184,7 +192,7 @@ const request = event.Records[0].cf.request;
184
192
  return (await serveAgentPage(request, { key })) ?? request;
185
193
  ```
186
194
 
187
- Only requests that will get an agent page are marked, so crawlers and people share the normal cache entry, and each listed path gets one extra entry. Lambda@Edge has no environment variables, so pass `key`. People never wait on the site rules: the viewer handler loads them only for an AI agent. A page too large for the 1 MB limit of Lambda@Edge falls through to your origin, and the body goes out as UTF-8 text.
195
+ Only requests that will get an agent page are marked, so crawlers and people share the normal cache entry, and each listed path gets one extra entry. Lambda@Edge has no environment variables, so pass `key`. People never wait on the site rules: the viewer handler loads them only for an AI agent or a request that prefers Markdown. A page too large for the 1 MB limit of Lambda@Edge falls through to your origin, and the body goes out as UTF-8 text.
188
196
 
189
197
  ## Dependencies
190
198
 
package/dist/index.d.ts CHANGED
@@ -68,7 +68,10 @@ type AgentVisitRecord = {
68
68
  sourceEventId: string;
69
69
  relayVersion: string;
70
70
  platform?: Platform;
71
+ /** The relay functions this process called, so WaniWani can show which setup steps the code has. */
72
+ uses: RelayFunction[];
71
73
  };
74
+ type RelayFunction = "recordAgentVisit" | "serveAgentPage" | "handleViewerRequest";
72
75
  /**
73
76
  * Tells whether a request, or the `cf.request` of a Lambda@Edge event, comes from an AI agent. Reads headers only and never waits on the network.
74
77
  * Every 12 hours it starts one background fetch of the newest AI bot list, unless `rules` is `"bundled"`.
@@ -96,4 +99,4 @@ declare function serveAgentPage(request: CloudFrontRequest, options?: ServeAgent
96
99
  declare function handleViewerRequest(request: CloudFrontRequest, options?: ServeAgentPageOptions): Promise<CloudFrontResultResponse | null>;
97
100
 
98
101
  export { handleViewerRequest, isAgent, recordAgentVisit, serveAgentPage };
99
- export type { AgentVisitRecord, CloudFrontRequest, CloudFrontResultResponse, IsAgentOptions, Region, RelayOptions, ServeAgentPageOptions };
102
+ export type { AgentVisitRecord, CloudFrontRequest, CloudFrontResultResponse, IsAgentOptions, Region, RelayFunction, RelayOptions, ServeAgentPageOptions };
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ var settings = {
17
17
  };
18
18
 
19
19
  // src/version.ts
20
- var RELAY_VERSION = true ? "0.4.3" : "0.0.0-dev";
20
+ var RELAY_VERSION = true ? "0.6.0" : "0.0.0-dev";
21
21
 
22
22
  // src/agent-page.ts
23
23
  var isObject = (value) => typeof value === "object" && value !== null;
@@ -173,15 +173,27 @@ async function readCapped(body, deadline, maxBytes) {
173
173
  }
174
174
  return out;
175
175
  }
176
- function pageFormat(accept) {
177
- return accept.split(",").some((range) => {
176
+ function mediaRanges(accept) {
177
+ return accept.split(",").map((range) => {
178
178
  const [type = "", ...params] = range.split(";").map((part) => part.trim().toLowerCase());
179
- if (type !== "text/markdown") {
180
- return false;
181
- }
182
- const q = params.find((param) => param.startsWith("q="));
183
- return q === void 0 || Number(q.slice(2)) > 0;
184
- }) ? "markdown" : "html";
179
+ const q = params.map((param) => param.split("=").map((part) => part.trim())).find(([name]) => name === "q");
180
+ const value = q === void 0 ? 1 : Number(q[1]);
181
+ return { type, q: Number.isFinite(value) ? value : 0 };
182
+ });
183
+ }
184
+ function qualityOf(ranges, type) {
185
+ const named = ranges.filter((range) => range.type === type);
186
+ return named.length === 0 ? null : Math.max(...named.map((range) => range.q));
187
+ }
188
+ var markdownQuality = (ranges) => qualityOf(ranges, "text/markdown") ?? 0;
189
+ function pageFormat(accept) {
190
+ return markdownQuality(mediaRanges(accept)) > 0 ? "markdown" : "html";
191
+ }
192
+ function prefersMarkdown(accept) {
193
+ const ranges = mediaRanges(accept);
194
+ const markdown = markdownQuality(ranges);
195
+ const html = qualityOf(ranges, "text/html") ?? qualityOf(ranges, "text/*") ?? qualityOf(ranges, "*/*") ?? 0;
196
+ return markdown > 0 && markdown >= html;
185
197
  }
186
198
  async function fetchAgentPage(rules, hostname, path, format) {
187
199
  const controller = new AbortController();
@@ -370,7 +382,7 @@ import { BlockList } from "node:net";
370
382
 
371
383
  // src/logic/agents.generated.json
372
384
  var agents_generated_default = {
373
- source: "Generated by scripts/build-rules.ts from the WaniWani rules file d9b242ef451056c0, do not edit. Each token is matched case-insensitively inside the User-Agent header.",
385
+ source: "Generated by scripts/build-rules.ts from the WaniWani rules file 7f37359b624880d0, do not edit. Each token is matched case-insensitively inside the User-Agent header.",
374
386
  tokens: [
375
387
  "addsearchbot",
376
388
  "agentdatabot",
@@ -482,6 +494,7 @@ var agents_generated_default = {
482
494
  "linerbot",
483
495
  "linkupbot",
484
496
  "manus-user",
497
+ "mediavinebot/",
485
498
  "meta-externalagent",
486
499
  "meta-externalfetcher",
487
500
  "meta-webindexer",
@@ -507,6 +520,7 @@ var agents_generated_default = {
507
520
  "phindbot",
508
521
  "poggio-citations",
509
522
  "poseidon research crawler",
523
+ "publogic crawler",
510
524
  "qodercli",
511
525
  "qualifiedbot",
512
526
  "querit-searchbot",
@@ -737,6 +751,8 @@ var ip_ranges_generated_default = {
737
751
  "51.8.155.48/28",
738
752
  "51.8.155.112/28",
739
753
  "51.57.0.96/28",
754
+ "51.57.158.96/28",
755
+ "51.57.158.112/28",
740
756
  "51.59.24.64/28",
741
757
  "51.59.24.80/28",
742
758
  "51.59.48.80/28",
@@ -1675,6 +1691,10 @@ function hostnameOf(host) {
1675
1691
  const origin = originOf(host);
1676
1692
  return origin === null ? null : new URL(origin).hostname;
1677
1693
  }
1694
+ function cloudFrontHeaderList(input, name) {
1695
+ const values = (input.headers?.[name] ?? []).map((entry) => entry?.value).filter((value) => typeof value === "string");
1696
+ return values.length === 0 ? null : values.join(", ");
1697
+ }
1678
1698
  function cloudFrontHeader(input, name) {
1679
1699
  const value = input.headers?.[name]?.[0]?.value;
1680
1700
  return typeof value === "string" ? value : null;
@@ -1717,15 +1737,15 @@ function toRelayRequest(input) {
1717
1737
  }
1718
1738
 
1719
1739
  // src/index.ts
1740
+ var used = /* @__PURE__ */ new Set();
1720
1741
  function isAgent(request, options = {}) {
1721
1742
  startRulesRefresh(options);
1743
+ return matchesAgent(request, options.rules === "bundled");
1744
+ }
1745
+ function matchesAgent(request, useBundled = false) {
1722
1746
  try {
1723
1747
  const { headers, ip } = toRelayRequest(request);
1724
- return decide({
1725
- headers: readCappedHeaders(headers),
1726
- ip,
1727
- useBundled: options.rules === "bundled"
1728
- });
1748
+ return decide({ headers: readCappedHeaders(headers), ip, useBundled });
1729
1749
  } catch {
1730
1750
  return false;
1731
1751
  }
@@ -1744,6 +1764,7 @@ function startRulesRefresh(options) {
1744
1764
  }
1745
1765
  }
1746
1766
  function recordAgentVisit(request, options = {}) {
1767
+ used.add("recordAgentVisit");
1747
1768
  try {
1748
1769
  const key = readKey(options.key);
1749
1770
  if (!key) {
@@ -1779,7 +1800,8 @@ function recordAgentVisit(request, options = {}) {
1779
1800
  path: url.pathname + url.search,
1780
1801
  headers,
1781
1802
  sourceEventId: crypto.randomUUID(),
1782
- relayVersion: RELAY_VERSION
1803
+ relayVersion: RELAY_VERSION,
1804
+ uses: []
1783
1805
  };
1784
1806
  if (capped["user-agent"] !== void 0 && !omitted.has("user-agent")) {
1785
1807
  record.userAgent = capped["user-agent"];
@@ -1793,6 +1815,8 @@ function recordAgentVisit(request, options = {}) {
1793
1815
  const policy = platform === "cloudfront" ? { timeoutMs: settings.cloudFrontTimeoutMs, retry: false } : { timeoutMs: settings.requestTimeoutMs, retry: true };
1794
1816
  const ingestUrl = `${apiUrl}/api/v2/agent-traffic/ingest/middleware/${key.ingestToken}`;
1795
1817
  const withMatchKey = async () => {
1818
+ await Promise.resolve();
1819
+ record.uses = [...used];
1796
1820
  if (ip !== null && !omitted.has("matchkey")) {
1797
1821
  record.matchKey = await deriveMatchKey(key.orgKey, ip).catch(
1798
1822
  () => void 0
@@ -1819,6 +1843,7 @@ var BLOCKED = {
1819
1843
  }
1820
1844
  };
1821
1845
  async function serveAgentPage(request, options = {}) {
1846
+ used.add("serveAgentPage");
1822
1847
  try {
1823
1848
  return isCloudFrontRequest(request) ? await serveCloudFront(request, options) : await serveFetch(request, options);
1824
1849
  } catch {
@@ -1834,7 +1859,8 @@ async function serveFetch(request, options) {
1834
1859
  pathname: url.pathname,
1835
1860
  userAgent: request.headers.get("user-agent") ?? "",
1836
1861
  accept: request.headers.get("accept") ?? "",
1837
- rendering: request.headers.has("x-waniwani-render")
1862
+ rendering: request.headers.has("x-waniwani-render"),
1863
+ agent: matchesAgent(request)
1838
1864
  },
1839
1865
  options
1840
1866
  );
@@ -1920,7 +1946,7 @@ function blockedAnswer(site, userAgent, method) {
1920
1946
  }
1921
1947
  async function answerAgentPage(request, options) {
1922
1948
  const { method, hostname } = request;
1923
- if (!isPageMethod(method)) {
1949
+ if (!isPageMethod(method) || !(request.agent || prefersMarkdown(request.accept))) {
1924
1950
  return null;
1925
1951
  }
1926
1952
  const match = await findSite(hostname, options);
@@ -1940,10 +1966,14 @@ async function answerAgentPage(request, options) {
1940
1966
  if (!matchesPath(path, site.agentPages.paths)) {
1941
1967
  return null;
1942
1968
  }
1943
- if (!rules.userAgentTokens.some((token) => userAgent.includes(token))) {
1944
- return null;
1969
+ const format = servedFormat(rules, userAgent, request.accept);
1970
+ return format && pageAnswer(rules, hostname, path, method, format);
1971
+ }
1972
+ function servedFormat(rules, userAgent, accept) {
1973
+ if (rules.userAgentTokens.some((token) => userAgent.includes(token))) {
1974
+ return pageFormat(accept);
1945
1975
  }
1946
- return pageAnswer(rules, hostname, path, method, pageFormat(request.accept));
1976
+ return prefersMarkdown(accept) ? "markdown" : null;
1947
1977
  }
1948
1978
  async function pageAnswer(rules, hostname, path, method, format) {
1949
1979
  const page = await fetchAgentPage(rules, hostname, path, format);
@@ -2003,10 +2033,12 @@ function takeMarkFromUri(request) {
2003
2033
  }
2004
2034
  }
2005
2035
  async function handleViewerRequest(request, options = {}) {
2036
+ used.add("handleViewerRequest");
2006
2037
  try {
2007
2038
  stripForgedMark(request);
2008
2039
  const method = request.method.toUpperCase();
2009
- if (!isPageMethod(method) || !isAgent(request, options)) {
2040
+ const accept = cloudFrontHeaderList(request, "accept") ?? "";
2041
+ if (!isPageMethod(method) || !(prefersMarkdown(accept) || isAgent(request, options))) {
2010
2042
  return null;
2011
2043
  }
2012
2044
  const hostname = hostnameOf(cloudFrontHeader(request, "host") ?? void 0);
@@ -2023,10 +2055,10 @@ async function handleViewerRequest(request, options = {}) {
2023
2055
  if (blocked) {
2024
2056
  return toCloudFrontResponse(blocked);
2025
2057
  }
2026
- if (cloudFrontHeader(request, "x-waniwani-render") !== null || !site.agentPages.enabled || !matchesPath(normalizePath(request.uri), site.agentPages.paths) || !rules.userAgentTokens.some((token) => userAgent.includes(token))) {
2058
+ const format = servedFormat(rules, userAgent, accept);
2059
+ if (cloudFrontHeader(request, "x-waniwani-render") !== null || !site.agentPages.enabled || !matchesPath(normalizePath(request.uri), site.agentPages.paths) || format === null) {
2027
2060
  return null;
2028
2061
  }
2029
- const format = pageFormat(cloudFrontHeader(request, "accept") ?? "");
2030
2062
  const uri = `${AGENT_URI_PREFIX}${format}/${encodeURIComponent(hostname)}${request.uri}`;
2031
2063
  if (new TextEncoder().encode(uri).byteLength <= settings.cloudFrontMaxUriBytes) {
2032
2064
  request.uri = uri;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/settings.ts","../src/version.ts","../src/agent-page.ts","../src/headers.ts","../src/http.ts","../src/ingest.ts","../src/key.ts","../src/logic/decide.ts","../src/logic/agents.generated.json","../src/logic/ip-ranges.generated.json","../src/match-key.ts","../src/refresh.ts","../src/wait-until.ts","../src/platform.ts","../src/request.ts","../src/index.ts"],"sourcesContent":["export const settings = {\n\tapiUrls: {\n\t\tus: \"https://app.waniwani.ai\",\n\t\teu: \"https://eu.app.waniwani.ai\",\n\t},\n\trequestTimeoutMs: 2000,\n\tcloudFrontTimeoutMs: 1000,\n\tcloudFrontMaxUriBytes: 8192,\n\tcloudFrontMaxBodyBytes: 1_000_000 - 16_384,\n\tretryDelayMs: 500,\n\trulesRefreshMs: 12 * 60 * 60 * 1000,\n\trulesRetryMs: 5 * 60 * 1000,\n\tsiteRulesTimeoutMs: 300,\n\tsiteRulesRefreshMs: 60_000,\n\tsiteRulesRetryMs: 5_000,\n};\n","declare const __RELAY_PACKAGE_VERSION__: string | undefined;\n\nexport const RELAY_VERSION: string =\n\ttypeof __RELAY_PACKAGE_VERSION__ === \"string\"\n\t\t? __RELAY_PACKAGE_VERSION__\n\t\t: \"0.0.0-dev\";\n","import { settings } from \"./settings\";\nimport { RELAY_VERSION } from \"./version\";\n\nexport type SiteRules = {\n\tversion: 1;\n\tpagesUrl: string;\n\tuserAgentTokens: string[];\n\tdeadlines: { headersMs: number; bodyMs: number };\n\tmaxBodyBytes: number;\n\tsites: Array<{\n\t\thostname: string;\n\t\tblockedUserAgentTokens: string[];\n\t\tagentPages: { enabled: boolean; paths: string[] };\n\t}>;\n};\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === \"object\" && value !== null;\n\nconst isStringArray = (value: unknown): value is string[] =>\n\tArray.isArray(value) && value.every((item) => typeof item === \"string\");\n\nconst isPositive = (value: unknown): value is number =>\n\ttypeof value === \"number\" && Number.isFinite(value) && value > 0;\n\n/** Lowercase like the user agent they are matched against, with blank tokens dropped since they would match every request. */\nconst cleanTokens = (tokens: string[]): string[] =>\n\ttokens\n\t\t.map((token) => token.trim().toLowerCase())\n\t\t.filter((token) => token !== \"\");\n\n/** The site rules in a serve rules file from the app (the `data` of its answer), or null when it does not have the expected form. */\nexport function parseSiteRules(body: unknown): SiteRules | null {\n\tif (!isObject(body) || body.version !== 1) {\n\t\treturn null;\n\t}\n\tconst { pagesUrl, userAgentTokens, deadlines, maxBodyBytes, sites } = body;\n\tif (\n\t\ttypeof pagesUrl !== \"string\" ||\n\t\t!isStringArray(userAgentTokens) ||\n\t\t!isObject(deadlines) ||\n\t\t!isPositive(deadlines.headersMs) ||\n\t\t!isPositive(deadlines.bodyMs) ||\n\t\t!isPositive(maxBodyBytes) ||\n\t\t!Array.isArray(sites)\n\t) {\n\t\treturn null;\n\t}\n\tconst parsedSites: SiteRules[\"sites\"] = [];\n\tfor (const site of sites) {\n\t\tif (!isObject(site)) {\n\t\t\treturn null;\n\t\t}\n\t\tconst blocked = site.blockedUserAgentTokens ?? [];\n\t\tif (\n\t\t\ttypeof site.hostname !== \"string\" ||\n\t\t\t!isStringArray(blocked) ||\n\t\t\t!isObject(site.agentPages) ||\n\t\t\ttypeof site.agentPages.enabled !== \"boolean\" ||\n\t\t\t!isStringArray(site.agentPages.paths)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tparsedSites.push({\n\t\t\thostname: site.hostname.toLowerCase(),\n\t\t\tblockedUserAgentTokens: cleanTokens(blocked),\n\t\t\tagentPages: {\n\t\t\t\tenabled: site.agentPages.enabled,\n\t\t\t\tpaths: site.agentPages.paths,\n\t\t\t},\n\t\t});\n\t}\n\treturn {\n\t\tversion: 1,\n\t\tpagesUrl: pagesUrl.replace(/\\/+$/, \"\"),\n\t\tuserAgentTokens: cleanTokens(userAgentTokens),\n\t\tdeadlines: { headersMs: deadlines.headersMs, bodyMs: deadlines.bodyMs },\n\t\tmaxBodyBytes,\n\t\tsites: parsedSites,\n\t};\n}\n\ntype CacheEntry = {\n\trules: SiteRules | null;\n\tfetchedAt: number;\n\tlastAttemptAt: number;\n\tlastAttemptOk: boolean;\n\tinFlight: Promise<SiteRules | null> | null;\n};\n\nconst cache = new Map<string, CacheEntry>();\n\nasync function fetchSiteRules(\n\turl: string,\n\ttimeoutMs: number,\n): Promise<SiteRules | null> {\n\tconst response = await fetch(url, {\n\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t});\n\tif (!response.ok) {\n\t\tawait response.body?.cancel().catch(() => {});\n\t\treturn null;\n\t}\n\tconst body: unknown = await response.json();\n\treturn parseSiteRules(isObject(body) ? body.data : null);\n}\n\nfunction startFetch(\n\tentry: CacheEntry,\n\turl: string,\n\ttimeoutMs: number,\n): Promise<SiteRules | null> {\n\tconst now = Date.now();\n\tentry.lastAttemptAt = now;\n\tentry.lastAttemptOk = false;\n\tconst work = fetchSiteRules(url, timeoutMs)\n\t\t.catch(() => null)\n\t\t.then((rules) => {\n\t\t\tif (rules) {\n\t\t\t\tentry.rules = rules;\n\t\t\t\tentry.fetchedAt = Date.now();\n\t\t\t\tentry.lastAttemptOk = true;\n\t\t\t}\n\t\t\tentry.inFlight = null;\n\t\t\treturn rules;\n\t\t});\n\tentry.inFlight = work;\n\treturn work;\n}\n\n/**\n * The rules for this org. Awaits the first fetch, then serves the cache and refreshes it in the background once it is stale.\n * At most one fetch per org is in flight.\n */\nexport async function loadSiteRules(\n\tapiUrl: string,\n\tingestToken: string,\n\tbackground: (work: Promise<unknown>) => void,\n): Promise<SiteRules | null> {\n\tconst url = `${apiUrl}/api/v2/agent-traffic/serve/${ingestToken}/rules`;\n\tlet entry = cache.get(url);\n\tif (!entry) {\n\t\tentry = {\n\t\t\trules: null,\n\t\t\tfetchedAt: 0,\n\t\t\tlastAttemptAt: Number.NEGATIVE_INFINITY,\n\t\t\tlastAttemptOk: false,\n\t\t\tinFlight: null,\n\t\t};\n\t\tcache.set(url, entry);\n\t}\n\tconst now = Date.now();\n\tif (entry.rules) {\n\t\tconst stale = now - entry.fetchedAt >= settings.siteRulesRefreshMs;\n\t\tconst retryDue =\n\t\t\tentry.lastAttemptOk ||\n\t\t\tnow - entry.lastAttemptAt >= settings.siteRulesRetryMs;\n\t\tif (stale && retryDue && !entry.inFlight) {\n\t\t\tbackground(startFetch(entry, url, settings.requestTimeoutMs));\n\t\t}\n\t\treturn entry.rules;\n\t}\n\tif (entry.inFlight) {\n\t\treturn entry.inFlight;\n\t}\n\tif (now - entry.lastAttemptAt < settings.siteRulesRetryMs) {\n\t\treturn null;\n\t}\n\treturn startFetch(entry, url, settings.siteRulesTimeoutMs);\n}\n\n/** The pathname without its trailing slashes, except for the root. */\nexport function normalizePath(pathname: string): string {\n\tconst trimmed = pathname.replace(/\\/+$/, \"\");\n\treturn trimmed === \"\" ? \"/\" : trimmed;\n}\n\n/** Whether a path matches an exact entry like `/pricing`, or a folder entry like `/products/*` that also covers `/products`. */\nexport function matchesPath(path: string, patterns: string[]): boolean {\n\treturn patterns.some((pattern) => {\n\t\tif (pattern === \"*\" || pattern === \"/*\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (pattern.endsWith(\"/*\")) {\n\t\t\tconst folder = normalizePath(pattern.slice(0, -2));\n\t\t\treturn path === folder || path.startsWith(`${folder}/`);\n\t\t}\n\t\treturn path === normalizePath(pattern);\n\t});\n}\n\nconst TIMEOUT = Symbol(\"timeout\");\n\nfunction within<T>(\n\tpromise: Promise<T>,\n\tms: number,\n): Promise<T | typeof TIMEOUT> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst deadline = new Promise<typeof TIMEOUT>((resolve) => {\n\t\ttimer = setTimeout(() => resolve(TIMEOUT), ms);\n\t});\n\treturn Promise.race([promise, deadline]).finally(() => clearTimeout(timer));\n}\n\nasync function readCapped(\n\tbody: ReadableStream<Uint8Array>,\n\tdeadline: Promise<typeof TIMEOUT>,\n\tmaxBytes: number,\n): Promise<Uint8Array<ArrayBuffer> | null> {\n\tconst reader = body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet size = 0;\n\ttry {\n\t\twhile (true) {\n\t\t\tconst result = await Promise.race([reader.read(), deadline]);\n\t\t\tif (result === TIMEOUT) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (result.done) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tsize += result.value.byteLength;\n\t\t\tif (size > maxBytes) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tchunks.push(result.value);\n\t\t}\n\t} finally {\n\t\treader.cancel().catch(() => {});\n\t}\n\tconst out = new Uint8Array(size);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tout.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\treturn out;\n}\n\nexport type PageFormat = \"markdown\" | \"html\";\n\n/** Markdown only when the agent lists `text/markdown` with a nonzero q, so an agent that asks for HTML or for anything gets HTML. */\nexport function pageFormat(accept: string): PageFormat {\n\treturn accept.split(\",\").some((range) => {\n\t\tconst [type = \"\", ...params] = range\n\t\t\t.split(\";\")\n\t\t\t.map((part) => part.trim().toLowerCase());\n\t\tif (type !== \"text/markdown\") {\n\t\t\treturn false;\n\t\t}\n\t\tconst q = params.find((param) => param.startsWith(\"q=\"));\n\t\treturn q === undefined || Number(q.slice(2)) > 0;\n\t})\n\t\t? \"markdown\"\n\t\t: \"html\";\n}\n\n/** Fetches the stored page in the agent's format within both deadlines and the size cap. Returns its body and content type, or null. The home page URL has no trailing slash, which the app would answer with a redirect. */\nexport async function fetchAgentPage(\n\trules: SiteRules,\n\thostname: string,\n\tpath: string,\n\tformat: PageFormat,\n): Promise<{ body: Uint8Array<ArrayBuffer>; contentType: string } | null> {\n\tconst controller = new AbortController();\n\ttry {\n\t\tconst response = await within(\n\t\t\tfetch(`${rules.pagesUrl}/${hostname}${path === \"/\" ? \"\" : path}`, {\n\t\t\t\theaders: {\n\t\t\t\t\taccept: format === \"markdown\" ? \"text/markdown\" : \"text/html\",\n\t\t\t\t\t\"x-waniwani-relay\": RELAY_VERSION,\n\t\t\t\t},\n\t\t\t\tsignal: controller.signal,\n\t\t\t}),\n\t\t\trules.deadlines.headersMs,\n\t\t);\n\t\tif (response === TIMEOUT) {\n\t\t\treturn null;\n\t\t}\n\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\tif (\n\t\t\tresponse.status !== 200 ||\n\t\t\t!contentType.toLowerCase().startsWith(\"text/\") ||\n\t\t\t!response.body\n\t\t) {\n\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\treturn null;\n\t\t}\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tconst deadline = new Promise<typeof TIMEOUT>((resolve) => {\n\t\t\ttimer = setTimeout(() => resolve(TIMEOUT), rules.deadlines.bodyMs);\n\t\t});\n\t\ttry {\n\t\t\tconst body = await readCapped(\n\t\t\t\tresponse.body,\n\t\t\t\tdeadline,\n\t\t\t\trules.maxBodyBytes,\n\t\t\t);\n\t\t\treturn body ? { body, contentType } : null;\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tcontroller.abort();\n\t}\n}\n","export const HEADER_CAP: readonly string[] = [\n\t\"user-agent\",\n\t\"referer\",\n\t\"accept\",\n\t\"accept-language\",\n\t\"from\",\n\t\"signature-agent\",\n\t\"signature-input\",\n\t\"signature\",\n\t\"dpop\",\n\t\"sec-ch-ua\",\n\t\"sec-ch-ua-platform\",\n\t\"sec-ch-ua-mobile\",\n\t\"authorization\",\n];\n\nconst KNOWN_SCHEMES = new Map(\n\t[\n\t\t\"Basic\",\n\t\t\"Bearer\",\n\t\t\"Digest\",\n\t\t\"DPoP\",\n\t\t\"HOBA\",\n\t\t\"Mutual\",\n\t\t\"Negotiate\",\n\t\t\"NTLM\",\n\t\t\"OAuth\",\n\t\t\"PrivateToken\",\n\t\t\"SCRAM-SHA-1\",\n\t\t\"SCRAM-SHA-256\",\n\t\t\"vapid\",\n\t\t\"AWS4-HMAC-SHA256\",\n\t].map((scheme) => [scheme.toLowerCase(), scheme]),\n);\n\n/** The canonical name of a known Authorization scheme, kept only when a credential follows it. */\nexport function authorizationScheme(value: string): string | null {\n\tconst parts = value.trim().split(/\\s+/);\n\tif (parts.length < 2) {\n\t\treturn null;\n\t}\n\treturn KNOWN_SCHEMES.get((parts[0] ?? \"\").toLowerCase()) ?? null;\n}\n\nexport function readCappedHeaders(headers: Headers): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tfor (const name of HEADER_CAP) {\n\t\tconst value = headers.get(name);\n\t\tif (value === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (name === \"authorization\") {\n\t\t\tconst scheme = authorizationScheme(value);\n\t\t\tif (scheme !== null) {\n\t\t\t\tout[name] = scheme;\n\t\t\t}\n\t\t} else {\n\t\t\tout[name] = value;\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Cloudflare appends the real IP to a client-sent `X-Forwarded-For`, so `CF-Connecting-IP` comes first. */\nexport function clientIp(headers: Headers): string | null {\n\tconst forwarded = headers.get(\"x-forwarded-for\");\n\tconst candidate =\n\t\theaders.get(\"cf-connecting-ip\") ??\n\t\t(forwarded !== null ? forwarded.split(\",\")[0] : headers.get(\"x-real-ip\"));\n\tconst ip = candidate?.trim().toLowerCase();\n\treturn ip ? ip : null;\n}\n","import { settings } from \"./settings\";\n\nexport type Outcome = { status: number } | { failure: \"timeout\" | \"network\" };\n\n/** One fetch bounded by the timeout. Never rejects. */\nexport async function fetchWithTimeout(\n\turl: string,\n\tinit: RequestInit,\n\ttimeoutMs: number = settings.requestTimeoutMs,\n): Promise<Outcome> {\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\ttry {\n\t\tconst response = await fetch(url, { ...init, signal: controller.signal });\n\t\tawait response.body?.cancel().catch(() => {});\n\t\treturn { status: response.status };\n\t} catch {\n\t\treturn { failure: controller.signal.aborted ? \"timeout\" : \"network\" };\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\nexport const delay = (ms: number) =>\n\tnew Promise<void>((resolve) => setTimeout(resolve, ms));\n","import { delay, fetchWithTimeout, type Outcome } from \"./http\";\nimport { settings } from \"./settings\";\n\nconst isSuccess = (outcome: Outcome) =>\n\t\"status\" in outcome && outcome.status >= 200 && outcome.status < 300;\n\nconst isRetryable = (outcome: Outcome) =>\n\t\"failure\" in outcome || outcome.status === 429 || outcome.status >= 500;\n\nconst describe = (outcome: Outcome) =>\n\t\"failure\" in outcome ? outcome.failure : `status ${outcome.status}`;\n\nexport type PostPolicy = { timeoutMs: number; retry: boolean };\n\nconst post = (url: string, body: string, timeoutMs: number) =>\n\tfetchWithTimeout(\n\t\turl,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody,\n\t\t},\n\t\ttimeoutMs,\n\t);\n\n/** Posts once, retries once on a timeout, network error, 429 or 5xx when the policy allows it, and never rejects. */\nexport async function postEvent(\n\turl: string,\n\tbody: string,\n\tpolicy: PostPolicy = { timeoutMs: settings.requestTimeoutMs, retry: true },\n): Promise<boolean> {\n\tlet outcome = await post(url, body, policy.timeoutMs);\n\tif (policy.retry && !isSuccess(outcome) && isRetryable(outcome)) {\n\t\tawait delay(settings.retryDelayMs);\n\t\toutcome = await post(url, body, policy.timeoutMs);\n\t}\n\tif (isSuccess(outcome)) {\n\t\treturn true;\n\t}\n\tconsole.warn(\n\t\t`[@waniwani/relay] Could not record an agent visit (${describe(outcome)}). The event was dropped.`,\n\t);\n\treturn false;\n}\n","export type RelayKey = { ingestToken: string; orgKey: string };\n\nconst KEY = /^wwt_([A-Za-z0-9_-]+)\\.([A-Za-z0-9_-]+={0,2})$/;\n\nlet warnedMalformed = false;\n\nexport function readKey(explicit: string | undefined): RelayKey | null {\n\tconst raw = explicit ?? readEnv(\"WANIWANI_AGENT_TRAFFIC_KEY\");\n\tif (raw === undefined || raw.trim() === \"\") {\n\t\treturn null;\n\t}\n\tconst match = KEY.exec(raw.trim());\n\tif (!match?.[1] || !match[2]) {\n\t\tif (!warnedMalformed) {\n\t\t\twarnedMalformed = true;\n\t\t\tconsole.warn(\n\t\t\t\t\"[@waniwani/relay] WANIWANI_AGENT_TRAFFIC_KEY is malformed, so no agent visits are recorded. Copy the key again from the WaniWani setup screen.\",\n\t\t\t);\n\t\t}\n\t\treturn null;\n\t}\n\treturn { ingestToken: match[1], orgKey: match[2] };\n}\n\nfunction readEnv(name: string): string | undefined {\n\ttry {\n\t\treturn typeof process === \"undefined\" ? undefined : process.env[name];\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n","import { BlockList } from \"node:net\";\nimport agents from \"./agents.generated.json\";\nimport ranges from \"./ip-ranges.generated.json\";\n\nexport type DecideInput = {\n\theaders: Record<string, string>;\n\tip: string | null;\n\tuseBundled?: boolean;\n};\n\nexport type MatchRules = {\n\ttokens: string[];\n\tipRanges: Array<{ ipv4: string[]; ipv6: string[] }>;\n};\n\nconst MAX_SCANNED_USER_AGENT = 1024;\nconst PREFIX = 3;\n\ntype CompiledRules = {\n\ttokensByPrefix: Map<string, string[]>;\n\tagentRanges: BlockList;\n};\n\nfunction compile(rules: MatchRules): CompiledRules {\n\tconst tokensByPrefix = new Map<string, string[]>();\n\tfor (const token of rules.tokens) {\n\t\tconst key = token.slice(0, PREFIX);\n\t\tconst list = tokensByPrefix.get(key);\n\t\tif (list) {\n\t\t\tlist.push(token);\n\t\t} else {\n\t\t\ttokensByPrefix.set(key, [token]);\n\t\t}\n\t}\n\n\tconst agentRanges = new BlockList();\n\tconst addRanges = (cidrs: string[], type: \"ipv4\" | \"ipv6\") => {\n\t\tfor (const cidr of cidrs) {\n\t\t\tconst [address = \"\", prefix, extra] = cidr.split(\"/\");\n\t\t\tif (!prefix || extra !== undefined) {\n\t\t\t\tthrow new Error(`malformed range ${cidr}`);\n\t\t\t}\n\t\t\tagentRanges.addSubnet(address, Number(prefix), type);\n\t\t}\n\t};\n\tfor (const source of rules.ipRanges) {\n\t\taddRanges(source.ipv4, \"ipv4\");\n\t\taddRanges(source.ipv6, \"ipv6\");\n\t}\n\n\treturn { tokensByPrefix, agentRanges };\n}\n\nconst bundled = compile({ tokens: agents.tokens, ipRanges: ranges.sources });\nlet remote: CompiledRules | null = null;\n\n/** Sets the remote match rules. Throws, and keeps the current ones, when a range is malformed. */\nexport function applyRules(rules: MatchRules): void {\n\tremote = compile(rules);\n}\n\nconst pick = (useBundled: boolean): CompiledRules =>\n\tuseBundled ? bundled : (remote ?? bundled);\n\nexport const isAgentIp = (ip: string, useBundled = false): boolean =>\n\tpick(useBundled).agentRanges.check(ip, ip.includes(\":\") ? \"ipv6\" : \"ipv4\");\n\nexport function hasAgentToken(userAgent: string, useBundled = false): boolean {\n\tconst { tokensByPrefix } = pick(useBundled);\n\tconst text = userAgent.slice(0, MAX_SCANNED_USER_AGENT).toLowerCase();\n\tfor (let i = 0; i + PREFIX <= text.length; i++) {\n\t\tconst candidates = tokensByPrefix.get(text.slice(i, i + PREFIX));\n\t\tif (candidates) {\n\t\t\tfor (const token of candidates) {\n\t\t\t\tif (text.startsWith(token, i)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nexport function decide({\n\theaders,\n\tip,\n\tuseBundled = false,\n}: DecideInput): boolean {\n\tconst userAgent = headers[\"user-agent\"];\n\treturn (\n\t\t(userAgent !== undefined && hasAgentToken(userAgent, useBundled)) ||\n\t\theaders[\"signature-agent\"] !== undefined ||\n\t\theaders.dpop !== undefined ||\n\t\theaders.authorization?.toLowerCase() === \"dpop\" ||\n\t\t(ip !== null && isAgentIp(ip, useBundled))\n\t);\n}\n","{\n\t\"source\": \"Generated by scripts/build-rules.ts from the WaniWani rules file d9b242ef451056c0, do not edit. Each token is matched case-insensitively inside the User-Agent header.\",\n\t\"tokens\": [\n\t\t\"addsearchbot\",\n\t\t\"agentdatabot\",\n\t\t\"agenttimes\",\n\t\t\"ai2bot\",\n\t\t\"ai2bot-deepresearcheval\",\n\t\t\"ai2bot-dolma\",\n\t\t\"aihitbot\",\n\t\t\"aiwebindex\",\n\t\t\"allenai.org\",\n\t\t\"amazon-kendra\",\n\t\t\"amazon-qbusiness\",\n\t\t\"amazonbot\",\n\t\t\"amazonbuyforme\",\n\t\t\"amzn-searchbot\",\n\t\t\"amzn-user\",\n\t\t\"andibot\",\n\t\t\"anthropic-ai\",\n\t\t\"apifybot\",\n\t\t\"apifywebsitecontentcrawler\",\n\t\t\"applebot\",\n\t\t\"aranet-searchbot\",\n\t\t\"atlassian-bot\",\n\t\t\"awario\",\n\t\t\"azureai-searchbot\",\n\t\t\"bedrockbot\",\n\t\t\"bigsur.ai\",\n\t\t\"bixelbot\",\n\t\t\"bravebot\",\n\t\t\"brightbot\",\n\t\t\"buddybot\",\n\t\t\"bytespider\",\n\t\t\"ccbot\",\n\t\t\"channel3bot\",\n\t\t\"chatglm-spider\",\n\t\t\"chatgpt agent\",\n\t\t\"chatgpt-user\",\n\t\t\"claude-code\",\n\t\t\"claude-searchbot\",\n\t\t\"claude-user\",\n\t\t\"claude-web\",\n\t\t\"claudebot\",\n\t\t\"cloudflare-autorag\",\n\t\t\"cloudflarebrowserrenderingcrawler\",\n\t\t\"cloudvertexbot\",\n\t\t\"cohere-ai\",\n\t\t\"cohere-training-data-crawler\",\n\t\t\"cotoyogi\",\n\t\t\"coveobot\",\n\t\t\"cragcrawler\",\n\t\t\"crawl4ai\",\n\t\t\"crawlspace\",\n\t\t\"cursor/1.0.0\",\n\t\t\"datenbank crawler\",\n\t\t\"deepseekbot\",\n\t\t\"diffbot\",\n\t\t\"diffbot-user\",\n\t\t\"doubaobot\",\n\t\t\"duckassistbot\",\n\t\t\"echobot bot\",\n\t\t\"echoboxbot\",\n\t\t\"erniebot\",\n\t\t\"exabot\",\n\t\t\"exasearchbot\",\n\t\t\"exauser\",\n\t\t\"facebookbot\",\n\t\t\"factset_spyderbot\",\n\t\t\"firecrawlagent\",\n\t\t\"friendlycrawler\",\n\t\t\"geisthaus-pagefetcher\",\n\t\t\"gemini-deep-research\",\n\t\t\"google-ads-creatives-assistant\",\n\t\t\"google-agent\",\n\t\t\"google-cloudvertexbot\",\n\t\t\"google-firebase\",\n\t\t\"google-gemini-cli\",\n\t\t\"google-gemininotebook\",\n\t\t\"google-lens\",\n\t\t\"google-notebooklm\",\n\t\t\"googleagent-mariner\",\n\t\t\"googleagent-urlcontext\",\n\t\t\"googleother\",\n\t\t\"googleother-image\",\n\t\t\"googleother-video\",\n\t\t\"gptbot\",\n\t\t\"grove/1.0 rss feed service\",\n\t\t\"henkbot\",\n\t\t\"iaskbot\",\n\t\t\"iaskspider\",\n\t\t\"icc-crawler\",\n\t\t\"imagesiftbot\",\n\t\t\"imagespider\",\n\t\t\"img2dataset\",\n\t\t\"isscyberriskcrawler\",\n\t\t\"jackjillcareersbot\",\n\t\t\"kagi-fetcher\",\n\t\t\"kangaroo bot\",\n\t\t\"keenablebot\",\n\t\t\"kernel searchbot\",\n\t\t\"kimi-agent\",\n\t\t\"kimi-searchbot\",\n\t\t\"kimi-user\",\n\t\t\"kimibot\",\n\t\t\"klaviyoaibot\",\n\t\t\"kunatocrawler\",\n\t\t\"laion-huggingface-processor\",\n\t\t\"laiondownloader\",\n\t\t\"lightpanda\",\n\t\t\"linerbot\",\n\t\t\"linkupbot\",\n\t\t\"manus-user\",\n\t\t\"meta-externalagent\",\n\t\t\"meta-externalfetcher\",\n\t\t\"meta-webindexer\",\n\t\t\"mistralai-index\",\n\t\t\"mistralai-training\",\n\t\t\"mistralai-user\",\n\t\t\"momentic\",\n\t\t\"mozilla-tabstack\",\n\t\t\"mycentralaiscraperbot\",\n\t\t\"nagetbot\",\n\t\t\"netestate imprint crawler\",\n\t\t\"notebooklm\",\n\t\t\"novaact\",\n\t\t\"oai-adsbot\",\n\t\t\"oai-searchbot\",\n\t\t\"omgili\",\n\t\t\"omgilibot\",\n\t\t\"pangubot\",\n\t\t\"panscient\",\n\t\t\"perplexity-user\",\n\t\t\"perplexitybot\",\n\t\t\"petalbot\",\n\t\t\"phindbot\",\n\t\t\"poggio-citations\",\n\t\t\"poseidon research crawler\",\n\t\t\"qodercli\",\n\t\t\"qualifiedbot\",\n\t\t\"querit-searchbot\",\n\t\t\"queritbot\",\n\t\t\"quillbot\",\n\t\t\"qwenbot\",\n\t\t\"reflectionbot\",\n\t\t\"ryebot\",\n\t\t\"sbintuitionsbot\",\n\t\t\"seltzsitemapfetcher\",\n\t\t\"semanticscholarbot\",\n\t\t\"semrushbot-ocob\",\n\t\t\"semrushbot-swa\",\n\t\t\"shap-user\",\n\t\t\"shapbot\",\n\t\t\"sidetrade indexer bot\",\n\t\t\"tavilybot\",\n\t\t\"terra cotta\",\n\t\t\"terracotta\",\n\t\t\"thinkbot\",\n\t\t\"tiktokspider\",\n\t\t\"timpibot\",\n\t\t\"tongyibot\",\n\t\t\"twilioknowledge\",\n\t\t\"twinagent\",\n\t\t\"v0bot\",\n\t\t\"velenpublicwebcrawler\",\n\t\t\"wardbot\",\n\t\t\"wrtnbot\",\n\t\t\"yandexadditional\",\n\t\t\"yandexadditionalbot\",\n\t\t\"yiyanbot\",\n\t\t\"youbot\",\n\t\t\"zanistabot\"\n\t]\n}\n","{\n\t\"sources\": [\n\t\t{\n\t\t\t\"bot\": \"GPTBot\",\n\t\t\t\"url\": \"https://openai.com/gptbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.227.36.0/25\",\n\t\t\t\t\"20.125.66.80/28\",\n\t\t\t\t\"20.171.206.0/24\",\n\t\t\t\t\"20.171.207.0/24\",\n\t\t\t\t\"52.230.152.0/24\",\n\t\t\t\t\"74.7.175.128/25\",\n\t\t\t\t\"74.7.227.0/25\",\n\t\t\t\t\"74.7.227.128/25\",\n\t\t\t\t\"74.7.228.0/25\",\n\t\t\t\t\"74.7.230.0/25\",\n\t\t\t\t\"74.7.241.0/25\",\n\t\t\t\t\"74.7.241.128/25\",\n\t\t\t\t\"74.7.242.0/25\",\n\t\t\t\t\"74.7.243.128/25\",\n\t\t\t\t\"74.7.244.0/25\",\n\t\t\t\t\"132.196.86.0/24\",\n\t\t\t\t\"172.182.202.0/25\",\n\t\t\t\t\"172.182.207.0/25\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"OAI-SearchBot\",\n\t\t\t\"url\": \"https://openai.com/searchbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.227.36.0/25\",\n\t\t\t\t\"13.66.216.176/28\",\n\t\t\t\t\"20.14.99.96/28\",\n\t\t\t\t\"20.25.151.224/28\",\n\t\t\t\t\"20.42.10.176/28\",\n\t\t\t\t\"20.168.18.32/28\",\n\t\t\t\t\"20.169.6.224/28\",\n\t\t\t\t\"20.169.7.48/28\",\n\t\t\t\t\"20.169.77.0/25\",\n\t\t\t\t\"20.171.53.224/28\",\n\t\t\t\t\"20.171.123.64/28\",\n\t\t\t\t\"23.102.145.48/28\",\n\t\t\t\t\"40.67.175.0/25\",\n\t\t\t\t\"40.90.214.16/28\",\n\t\t\t\t\"51.8.102.0/24\",\n\t\t\t\t\"74.7.175.128/25\",\n\t\t\t\t\"74.7.228.0/25\",\n\t\t\t\t\"74.7.228.128/25\",\n\t\t\t\t\"74.7.229.0/25\",\n\t\t\t\t\"74.7.229.128/25\",\n\t\t\t\t\"74.7.230.0/25\",\n\t\t\t\t\"74.7.241.128/25\",\n\t\t\t\t\"74.7.242.128/25\",\n\t\t\t\t\"74.7.243.0/25\",\n\t\t\t\t\"74.7.244.0/25\",\n\t\t\t\t\"104.210.140.128/28\",\n\t\t\t\t\"135.234.64.0/24\",\n\t\t\t\t\"172.182.193.80/28\",\n\t\t\t\t\"172.182.193.224/28\",\n\t\t\t\t\"172.182.194.32/28\",\n\t\t\t\t\"172.182.194.144/28\",\n\t\t\t\t\"172.182.195.48/28\",\n\t\t\t\t\"172.182.209.208/28\",\n\t\t\t\t\"172.182.211.192/28\",\n\t\t\t\t\"172.182.213.192/28\",\n\t\t\t\t\"172.182.224.0/28\",\n\t\t\t\t\"172.203.190.80/28\",\n\t\t\t\t\"172.203.190.112/28\",\n\t\t\t\t\"172.203.190.128/28\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"ChatGPT-User\",\n\t\t\t\"url\": \"https://openai.com/chatgpt-user.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.151.71.176/28\",\n\t\t\t\t\"4.151.119.48/28\",\n\t\t\t\t\"4.189.118.208/28\",\n\t\t\t\t\"4.189.119.48/28\",\n\t\t\t\t\"4.197.22.112/28\",\n\t\t\t\t\"4.197.64.0/28\",\n\t\t\t\t\"4.197.64.16/28\",\n\t\t\t\t\"4.197.64.48/28\",\n\t\t\t\t\"4.197.64.64/28\",\n\t\t\t\t\"4.197.115.112/28\",\n\t\t\t\t\"4.201.232.64/28\",\n\t\t\t\t\"4.205.128.176/28\",\n\t\t\t\t\"4.218.24.64/28\",\n\t\t\t\t\"4.226.200.16/28\",\n\t\t\t\t\"4.226.226.32/28\",\n\t\t\t\t\"9.129.0.0/16\",\n\t\t\t\t\"9.160.34.144/28\",\n\t\t\t\t\"9.160.36.16/28\",\n\t\t\t\t\"9.160.96.16/28\",\n\t\t\t\t\"9.160.128.16/28\",\n\t\t\t\t\"9.160.128.64/28\",\n\t\t\t\t\"9.160.163.128/28\",\n\t\t\t\t\"9.163.101.48/28\",\n\t\t\t\t\"9.205.8.48/28\",\n\t\t\t\t\"9.205.8.64/28\",\n\t\t\t\t\"9.205.25.128/28\",\n\t\t\t\t\"9.205.30.128/28\",\n\t\t\t\t\"9.205.30.144/28\",\n\t\t\t\t\"9.205.30.176/28\",\n\t\t\t\t\"9.205.128.32/28\",\n\t\t\t\t\"9.205.128.48/28\",\n\t\t\t\t\"9.223.181.208/28\",\n\t\t\t\t\"9.234.96.192/28\",\n\t\t\t\t\"9.234.97.96/28\",\n\t\t\t\t\"13.65.138.112/28\",\n\t\t\t\t\"13.67.72.16/28\",\n\t\t\t\t\"13.71.2.208/28\",\n\t\t\t\t\"13.76.32.208/28\",\n\t\t\t\t\"13.76.116.80/28\",\n\t\t\t\t\"13.83.167.128/28\",\n\t\t\t\t\"13.83.237.176/28\",\n\t\t\t\t\"20.45.178.144/28\",\n\t\t\t\t\"20.55.229.144/28\",\n\t\t\t\t\"20.57.199.192/28\",\n\t\t\t\t\"20.63.180.96/28\",\n\t\t\t\t\"20.63.221.64/28\",\n\t\t\t\t\"20.78.130.48/28\",\n\t\t\t\t\"20.83.243.176/28\",\n\t\t\t\t\"20.98.18.80/28\",\n\t\t\t\t\"20.102.212.144/28\",\n\t\t\t\t\"20.113.211.112/28\",\n\t\t\t\t\"20.113.225.112/28\",\n\t\t\t\t\"20.125.112.224/28\",\n\t\t\t\t\"20.125.144.144/28\",\n\t\t\t\t\"20.161.75.208/28\",\n\t\t\t\t\"20.168.7.192/28\",\n\t\t\t\t\"20.169.73.32/28\",\n\t\t\t\t\"20.169.73.64/28\",\n\t\t\t\t\"20.169.78.48/28\",\n\t\t\t\t\"20.169.78.64/28\",\n\t\t\t\t\"20.169.78.128/28\",\n\t\t\t\t\"20.169.78.160/28\",\n\t\t\t\t\"20.169.78.192/28\",\n\t\t\t\t\"20.169.86.224/28\",\n\t\t\t\t\"20.169.86.240/28\",\n\t\t\t\t\"20.170.184.16/28\",\n\t\t\t\t\"20.170.184.32/28\",\n\t\t\t\t\"20.170.184.48/28\",\n\t\t\t\t\"20.170.184.64/28\",\n\t\t\t\t\"20.170.184.80/28\",\n\t\t\t\t\"20.172.29.32/28\",\n\t\t\t\t\"20.199.211.160/28\",\n\t\t\t\t\"20.199.242.0/28\",\n\t\t\t\t\"20.200.212.240/28\",\n\t\t\t\t\"20.206.101.192/28\",\n\t\t\t\t\"20.210.211.192/28\",\n\t\t\t\t\"20.212.62.208/28\",\n\t\t\t\t\"20.215.187.208/28\",\n\t\t\t\t\"20.215.219.128/28\",\n\t\t\t\t\"20.215.219.160/28\",\n\t\t\t\t\"20.215.219.208/28\",\n\t\t\t\t\"20.218.30.240/28\",\n\t\t\t\t\"20.219.71.192/28\",\n\t\t\t\t\"20.219.161.192/28\",\n\t\t\t\t\"20.219.184.96/28\",\n\t\t\t\t\"20.222.36.192/28\",\n\t\t\t\t\"20.227.140.32/28\",\n\t\t\t\t\"20.228.106.176/28\",\n\t\t\t\t\"20.235.87.224/28\",\n\t\t\t\t\"20.249.63.208/28\",\n\t\t\t\t\"20.250.6.128/28\",\n\t\t\t\t\"20.250.136.64/28\",\n\t\t\t\t\"20.254.201.208/28\",\n\t\t\t\t\"23.98.142.176/28\",\n\t\t\t\t\"23.98.186.64/28\",\n\t\t\t\t\"23.98.186.96/28\",\n\t\t\t\t\"23.98.186.176/28\",\n\t\t\t\t\"23.98.186.192/28\",\n\t\t\t\t\"23.101.217.176/28\",\n\t\t\t\t\"23.102.140.144/28\",\n\t\t\t\t\"23.102.141.32/28\",\n\t\t\t\t\"40.74.200.208/28\",\n\t\t\t\t\"40.81.67.96/28\",\n\t\t\t\t\"40.81.234.144/28\",\n\t\t\t\t\"40.84.221.208/28\",\n\t\t\t\t\"40.84.221.224/28\",\n\t\t\t\t\"40.116.73.208/28\",\n\t\t\t\t\"40.119.36.240/28\",\n\t\t\t\t\"40.124.161.0/28\",\n\t\t\t\t\"48.221.40.176/28\",\n\t\t\t\t\"48.221.184.80/28\",\n\t\t\t\t\"48.221.184.96/28\",\n\t\t\t\t\"51.8.155.48/28\",\n\t\t\t\t\"51.8.155.112/28\",\n\t\t\t\t\"51.57.0.96/28\",\n\t\t\t\t\"51.59.24.64/28\",\n\t\t\t\t\"51.59.24.80/28\",\n\t\t\t\t\"51.59.48.80/28\",\n\t\t\t\t\"51.116.2.80/28\",\n\t\t\t\t\"51.116.221.96/28\",\n\t\t\t\t\"52.148.129.32/28\",\n\t\t\t\t\"52.156.77.144/28\",\n\t\t\t\t\"52.159.227.32/28\",\n\t\t\t\t\"52.159.249.96/28\",\n\t\t\t\t\"52.161.49.96/28\",\n\t\t\t\t\"52.165.212.48/28\",\n\t\t\t\t\"52.172.129.160/28\",\n\t\t\t\t\"52.173.219.96/28\",\n\t\t\t\t\"52.173.219.112/28\",\n\t\t\t\t\"52.173.221.16/28\",\n\t\t\t\t\"52.173.221.176/28\",\n\t\t\t\t\"52.173.221.208/28\",\n\t\t\t\t\"52.173.234.16/28\",\n\t\t\t\t\"52.173.234.80/28\",\n\t\t\t\t\"52.183.217.240/28\",\n\t\t\t\t\"52.190.137.16/28\",\n\t\t\t\t\"52.190.137.144/28\",\n\t\t\t\t\"52.190.139.48/28\",\n\t\t\t\t\"52.190.142.64/28\",\n\t\t\t\t\"52.190.190.16/28\",\n\t\t\t\t\"52.190.251.112/28\",\n\t\t\t\t\"52.225.75.208/28\",\n\t\t\t\t\"52.231.30.48/28\",\n\t\t\t\t\"52.231.34.176/28\",\n\t\t\t\t\"52.231.39.144/28\",\n\t\t\t\t\"52.231.50.64/28\",\n\t\t\t\t\"52.236.94.144/28\",\n\t\t\t\t\"52.241.146.208/28\",\n\t\t\t\t\"52.242.132.224/28\",\n\t\t\t\t\"52.242.132.240/28\",\n\t\t\t\t\"52.255.109.80/28\",\n\t\t\t\t\"52.255.109.96/28\",\n\t\t\t\t\"52.255.109.144/28\",\n\t\t\t\t\"52.255.111.0/28\",\n\t\t\t\t\"52.255.111.80/28\",\n\t\t\t\t\"57.154.174.112/28\",\n\t\t\t\t\"57.154.175.0/28\",\n\t\t\t\t\"57.154.187.32/28\",\n\t\t\t\t\"68.154.28.96/28\",\n\t\t\t\t\"68.220.57.64/28\",\n\t\t\t\t\"70.153.32.16/28\",\n\t\t\t\t\"70.153.32.32/28\",\n\t\t\t\t\"70.153.87.224/28\",\n\t\t\t\t\"70.153.189.192/28\",\n\t\t\t\t\"70.156.152.96/28\",\n\t\t\t\t\"74.7.35.48/28\",\n\t\t\t\t\"74.7.35.112/28\",\n\t\t\t\t\"74.7.36.64/28\",\n\t\t\t\t\"74.7.36.80/28\",\n\t\t\t\t\"74.7.36.96/28\",\n\t\t\t\t\"74.161.200.96/28\",\n\t\t\t\t\"74.224.217.64/28\",\n\t\t\t\t\"74.226.253.160/28\",\n\t\t\t\t\"74.248.37.160/28\",\n\t\t\t\t\"85.211.128.16/28\",\n\t\t\t\t\"85.211.128.32/28\",\n\t\t\t\t\"104.208.184.192/28\",\n\t\t\t\t\"104.210.139.192/28\",\n\t\t\t\t\"104.210.139.224/28\",\n\t\t\t\t\"128.85.198.32/28\",\n\t\t\t\t\"132.196.82.48/28\",\n\t\t\t\t\"134.33.102.192/28\",\n\t\t\t\t\"134.138.52.16/28\",\n\t\t\t\t\"134.138.52.64/28\",\n\t\t\t\t\"134.138.52.96/28\",\n\t\t\t\t\"134.138.52.128/28\",\n\t\t\t\t\"134.138.57.64/28\",\n\t\t\t\t\"134.138.57.80/28\",\n\t\t\t\t\"134.138.57.176/28\",\n\t\t\t\t\"134.138.58.32/28\",\n\t\t\t\t\"134.149.233.80/28\",\n\t\t\t\t\"135.13.64.240/28\",\n\t\t\t\t\"135.116.136.160/28\",\n\t\t\t\t\"135.220.73.208/28\",\n\t\t\t\t\"135.220.73.240/28\",\n\t\t\t\t\"135.222.99.32/28\",\n\t\t\t\t\"135.237.131.208/28\",\n\t\t\t\t\"135.237.133.48/28\",\n\t\t\t\t\"137.135.191.176/28\",\n\t\t\t\t\"138.91.46.96/28\",\n\t\t\t\t\"145.132.136.96/28\",\n\t\t\t\t\"158.158.5.32/28\",\n\t\t\t\t\"168.63.252.240/28\",\n\t\t\t\t\"172.162.248.64/28\",\n\t\t\t\t\"172.170.1.80/28\",\n\t\t\t\t\"172.170.225.0/28\",\n\t\t\t\t\"172.170.241.80/28\",\n\t\t\t\t\"172.172.206.48/28\",\n\t\t\t\t\"172.175.152.224/28\",\n\t\t\t\t\"172.178.140.144/28\",\n\t\t\t\t\"172.178.141.112/28\",\n\t\t\t\t\"172.178.141.128/28\",\n\t\t\t\t\"172.183.143.224/28\",\n\t\t\t\t\"172.183.222.128/28\",\n\t\t\t\t\"172.192.112.208/28\",\n\t\t\t\t\"172.197.160.192/28\",\n\t\t\t\t\"172.197.203.16/28\",\n\t\t\t\t\"172.198.58.176/28\",\n\t\t\t\t\"172.198.78.192/28\",\n\t\t\t\t\"172.198.79.112/28\",\n\t\t\t\t\"172.198.161.128/28\",\n\t\t\t\t\"172.199.137.80/28\",\n\t\t\t\t\"172.204.28.224/28\",\n\t\t\t\t\"172.204.96.80/28\",\n\t\t\t\t\"172.205.189.192/28\",\n\t\t\t\t\"172.206.38.240/28\",\n\t\t\t\t\"172.207.1.32/28\",\n\t\t\t\t\"172.212.172.160/28\",\n\t\t\t\t\"172.215.215.32/28\",\n\t\t\t\t\"191.232.238.96/28\",\n\t\t\t\t\"191.233.197.0/28\",\n\t\t\t\t\"191.233.199.160/28\",\n\t\t\t\t\"191.234.167.144/28\",\n\t\t\t\t\"191.235.99.160/28\",\n\t\t\t\t\"191.237.249.64/28\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"ClaudeBot\",\n\t\t\t\"url\": \"https://claude.com/crawling/bots.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"34.152.124.140/32\",\n\t\t\t\t\"34.153.21.190/32\",\n\t\t\t\t\"34.162.27.34/32\",\n\t\t\t\t\"34.162.86.102/32\",\n\t\t\t\t\"34.162.90.102/32\",\n\t\t\t\t\"34.162.111.78/32\",\n\t\t\t\t\"34.162.153.71/32\",\n\t\t\t\t\"34.162.191.81/32\",\n\t\t\t\t\"34.162.230.222/32\",\n\t\t\t\t\"34.162.244.71/32\",\n\t\t\t\t\"34.186.252.4/32\",\n\t\t\t\t\"136.79.143.251/32\",\n\t\t\t\t\"136.83.20.215/32\",\n\t\t\t\t\"216.73.216.0/22\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"PerplexityBot\",\n\t\t\t\"url\": \"https://www.perplexity.com/perplexitybot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"3.222.232.239/32\",\n\t\t\t\t\"3.224.62.45/32\",\n\t\t\t\t\"18.97.1.228/32\",\n\t\t\t\t\"18.97.9.96/32\",\n\t\t\t\t\"18.97.9.97/32\",\n\t\t\t\t\"18.97.9.98/32\",\n\t\t\t\t\"18.97.9.99/32\",\n\t\t\t\t\"18.97.9.100/32\",\n\t\t\t\t\"18.97.9.101/32\",\n\t\t\t\t\"18.97.9.102/32\",\n\t\t\t\t\"18.97.9.103/32\",\n\t\t\t\t\"18.210.92.235/32\",\n\t\t\t\t\"107.20.236.150/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"Perplexity-User\",\n\t\t\t\"url\": \"https://www.perplexity.com/perplexity-user.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"18.97.43.80/32\",\n\t\t\t\t\"18.97.43.81/32\",\n\t\t\t\t\"18.97.43.82/32\",\n\t\t\t\t\"18.97.43.83/32\",\n\t\t\t\t\"18.97.43.84/32\",\n\t\t\t\t\"18.97.43.85/32\",\n\t\t\t\t\"18.97.43.86/32\",\n\t\t\t\t\"18.97.43.87/32\",\n\t\t\t\t\"34.193.163.52/32\",\n\t\t\t\t\"44.208.221.197/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"MistralAI-User\",\n\t\t\t\"url\": \"https://mistral.ai/mistralai-user-ips.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"20.240.160.1/32\",\n\t\t\t\t\"20.240.160.161/32\",\n\t\t\t\t\"20.240.194.83/32\",\n\t\t\t\t\"51.12.243.114/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"CCBot\",\n\t\t\t\"url\": \"https://index.commoncrawl.org/ccbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"3.41.188.32/29\",\n\t\t\t\t\"18.97.9.168/29\",\n\t\t\t\t\"18.97.14.80/29\",\n\t\t\t\t\"18.97.14.88/30\"\n\t\t\t],\n\t\t\t\"ipv6\": [\n\t\t\t\t\"2600:1f28:365:8000::/56\"\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"DuckAssistBot\",\n\t\t\t\"url\": \"https://duckduckgo.com/duckassistbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.144.148.23/32\",\n\t\t\t\t\"4.144.182.50/32\",\n\t\t\t\t\"4.144.227.142/32\",\n\t\t\t\t\"4.144.251.39/32\",\n\t\t\t\t\"4.149.142.7/32\",\n\t\t\t\t\"4.149.159.71/32\",\n\t\t\t\t\"4.149.159.102/32\",\n\t\t\t\t\"4.149.216.220/32\",\n\t\t\t\t\"4.150.142.218/32\",\n\t\t\t\t\"4.154.193.167/32\",\n\t\t\t\t\"4.156.30.123/32\",\n\t\t\t\t\"4.156.136.79/32\",\n\t\t\t\t\"4.156.154.107/32\",\n\t\t\t\t\"4.157.77.76/32\",\n\t\t\t\t\"4.172.49.103/32\",\n\t\t\t\t\"4.177.16.196/32\",\n\t\t\t\t\"4.182.10.198/32\",\n\t\t\t\t\"4.182.131.108/32\",\n\t\t\t\t\"4.195.133.120/32\",\n\t\t\t\t\"4.201.125.59/32\",\n\t\t\t\t\"4.201.141.71/32\",\n\t\t\t\t\"4.201.197.203/32\",\n\t\t\t\t\"4.201.206.133/32\",\n\t\t\t\t\"4.201.220.8/32\",\n\t\t\t\t\"4.207.220.92/32\",\n\t\t\t\t\"4.209.224.56/32\",\n\t\t\t\t\"4.213.46.14/32\",\n\t\t\t\t\"4.226.40.135/32\",\n\t\t\t\t\"4.228.76.163/32\",\n\t\t\t\t\"4.237.155.54/32\",\n\t\t\t\t\"4.237.244.80/32\",\n\t\t\t\t\"4.242.119.13/32\",\n\t\t\t\t\"4.246.10.252/32\",\n\t\t\t\t\"4.248.234.26/32\",\n\t\t\t\t\"4.249.216.104/32\",\n\t\t\t\t\"4.249.223.51/32\",\n\t\t\t\t\"4.249.238.11/32\",\n\t\t\t\t\"4.254.123.56/32\",\n\t\t\t\t\"4.255.35.121/32\",\n\t\t\t\t\"4.255.39.80/32\",\n\t\t\t\t\"13.86.35.212/32\",\n\t\t\t\t\"13.89.106.77/32\",\n\t\t\t\t\"13.90.178.139/32\",\n\t\t\t\t\"20.3.1.178/32\",\n\t\t\t\t\"20.3.60.212/32\",\n\t\t\t\t\"20.8.252.26/32\",\n\t\t\t\t\"20.9.114.242/32\",\n\t\t\t\t\"20.12.141.99/32\",\n\t\t\t\t\"20.12.249.123/32\",\n\t\t\t\t\"20.12.252.21/32\",\n\t\t\t\t\"20.13.44.19/32\",\n\t\t\t\t\"20.15.142.178/32\",\n\t\t\t\t\"20.23.88.231/32\",\n\t\t\t\t\"20.29.149.44/32\",\n\t\t\t\t\"20.29.154.56/32\",\n\t\t\t\t\"20.40.133.240/32\",\n\t\t\t\t\"20.40.147.172/32\",\n\t\t\t\t\"20.43.150.85/32\",\n\t\t\t\t\"20.43.150.93/32\",\n\t\t\t\t\"20.43.152.28/32\",\n\t\t\t\t\"20.43.172.120/32\",\n\t\t\t\t\"20.44.222.1/32\",\n\t\t\t\t\"20.49.129.236/32\",\n\t\t\t\t\"20.49.136.28/32\",\n\t\t\t\t\"20.50.48.159/32\",\n\t\t\t\t\"20.50.48.192/32\",\n\t\t\t\t\"20.50.49.0/32\",\n\t\t\t\t\"20.50.49.25/32\",\n\t\t\t\t\"20.50.49.40/32\",\n\t\t\t\t\"20.50.49.55/32\",\n\t\t\t\t\"20.50.49.237/32\",\n\t\t\t\t\"20.50.50.46/32\",\n\t\t\t\t\"20.50.50.118/32\",\n\t\t\t\t\"20.50.50.121/32\",\n\t\t\t\t\"20.50.50.123/32\",\n\t\t\t\t\"20.50.50.130/32\",\n\t\t\t\t\"20.50.50.134/32\",\n\t\t\t\t\"20.50.50.145/32\",\n\t\t\t\t\"20.50.50.146/32\",\n\t\t\t\t\"20.50.50.163/32\",\n\t\t\t\t\"20.50.168.91/32\",\n\t\t\t\t\"20.53.78.106/32\",\n\t\t\t\t\"20.53.78.123/32\",\n\t\t\t\t\"20.53.78.138/32\",\n\t\t\t\t\"20.53.78.144/32\",\n\t\t\t\t\"20.53.78.236/32\",\n\t\t\t\t\"20.53.91.2/32\",\n\t\t\t\t\"20.53.92.211/32\",\n\t\t\t\t\"20.53.134.160/32\",\n\t\t\t\t\"20.54.224.39/32\",\n\t\t\t\t\"20.56.197.58/32\",\n\t\t\t\t\"20.56.197.63/32\",\n\t\t\t\t\"20.59.0.65/32\",\n\t\t\t\t\"20.61.34.40/32\",\n\t\t\t\t\"20.61.142.192/32\",\n\t\t\t\t\"20.62.224.44/32\",\n\t\t\t\t\"20.64.185.45/32\",\n\t\t\t\t\"20.69.74.250/32\",\n\t\t\t\t\"20.69.129.205/32\",\n\t\t\t\t\"20.69.131.45/32\",\n\t\t\t\t\"20.69.138.149/32\",\n\t\t\t\t\"20.69.138.189/32\",\n\t\t\t\t\"20.69.144.137/32\",\n\t\t\t\t\"20.69.146.171/32\",\n\t\t\t\t\"20.69.147.21/32\",\n\t\t\t\t\"20.69.171.204/32\",\n\t\t\t\t\"20.71.12.143/32\",\n\t\t\t\t\"20.71.69.210/32\",\n\t\t\t\t\"20.72.197.110/32\",\n\t\t\t\t\"20.72.203.250/32\",\n\t\t\t\t\"20.72.218.29/32\",\n\t\t\t\t\"20.72.221.33/32\",\n\t\t\t\t\"20.72.221.45/32\",\n\t\t\t\t\"20.72.225.13/32\",\n\t\t\t\t\"20.72.242.93/32\",\n\t\t\t\t\"20.72.242.209/32\",\n\t\t\t\t\"20.73.132.240/32\",\n\t\t\t\t\"20.73.202.147/32\",\n\t\t\t\t\"20.75.144.152/32\",\n\t\t\t\t\"20.75.254.61/32\",\n\t\t\t\t\"20.77.146.108/32\",\n\t\t\t\t\"20.79.226.26/32\",\n\t\t\t\t\"20.79.238.198/32\",\n\t\t\t\t\"20.79.239.66/32\",\n\t\t\t\t\"20.80.114.203/32\",\n\t\t\t\t\"20.80.120.41/32\",\n\t\t\t\t\"20.80.129.80/32\",\n\t\t\t\t\"20.80.135.7/32\",\n\t\t\t\t\"20.80.156.88/32\",\n\t\t\t\t\"20.81.115.13/32\",\n\t\t\t\t\"20.82.24.207/32\",\n\t\t\t\t\"20.84.138.61/32\",\n\t\t\t\t\"20.84.219.45/32\",\n\t\t\t\t\"20.84.251.223/32\",\n\t\t\t\t\"20.85.138.75/32\",\n\t\t\t\t\"20.85.167.56/32\",\n\t\t\t\t\"20.85.187.146/32\",\n\t\t\t\t\"20.88.168.65/32\",\n\t\t\t\t\"20.93.28.24/32\",\n\t\t\t\t\"20.93.49.96/32\",\n\t\t\t\t\"20.99.154.113/32\",\n\t\t\t\t\"20.99.255.235/32\",\n\t\t\t\t\"20.100.136.36/32\",\n\t\t\t\t\"20.100.140.155/32\",\n\t\t\t\t\"20.101.17.173/32\",\n\t\t\t\t\"20.102.34.205/32\",\n\t\t\t\t\"20.109.129.162/32\",\n\t\t\t\t\"20.109.130.190/32\",\n\t\t\t\t\"20.109.241.18/32\",\n\t\t\t\t\"20.112.58.44/32\",\n\t\t\t\t\"20.113.3.121/32\",\n\t\t\t\t\"20.113.14.159/32\",\n\t\t\t\t\"20.115.181.187/32\",\n\t\t\t\t\"20.118.11.251/32\",\n\t\t\t\t\"20.119.127.146/32\",\n\t\t\t\t\"20.121.75.32/32\",\n\t\t\t\t\"20.166.171.150/32\",\n\t\t\t\t\"20.170.75.54/32\",\n\t\t\t\t\"20.175.232.228/32\",\n\t\t\t\t\"20.185.79.15/32\",\n\t\t\t\t\"20.185.79.47/32\",\n\t\t\t\t\"20.190.21.172/32\",\n\t\t\t\t\"20.191.44.16/32\",\n\t\t\t\t\"20.191.44.22/32\",\n\t\t\t\t\"20.191.44.119/32\",\n\t\t\t\t\"20.191.44.234/32\",\n\t\t\t\t\"20.191.45.212/32\",\n\t\t\t\t\"20.193.12.126/32\",\n\t\t\t\t\"20.193.24.10/32\",\n\t\t\t\t\"20.193.24.251/32\",\n\t\t\t\t\"20.193.25.197/32\",\n\t\t\t\t\"20.193.27.215/32\",\n\t\t\t\t\"20.193.45.113/32\",\n\t\t\t\t\"20.195.58.189/32\",\n\t\t\t\t\"20.195.108.47/32\",\n\t\t\t\t\"20.197.209.11/32\",\n\t\t\t\t\"20.197.209.27/32\",\n\t\t\t\t\"20.198.132.126/32\",\n\t\t\t\t\"20.198.152.208/32\",\n\t\t\t\t\"20.201.15.208/32\",\n\t\t\t\t\"20.204.228.200/32\",\n\t\t\t\t\"20.204.240.172/32\",\n\t\t\t\t\"20.204.241.148/32\",\n\t\t\t\t\"20.204.242.19/32\",\n\t\t\t\t\"20.204.242.101/32\",\n\t\t\t\t\"20.204.243.55/32\",\n\t\t\t\t\"20.204.246.81/32\",\n\t\t\t\t\"20.204.246.254/32\",\n\t\t\t\t\"20.207.72.11/32\",\n\t\t\t\t\"20.207.72.21/32\",\n\t\t\t\t\"20.207.72.110/32\",\n\t\t\t\t\"20.207.72.113/32\",\n\t\t\t\t\"20.207.97.190/32\",\n\t\t\t\t\"20.207.99.197/32\",\n\t\t\t\t\"20.207.107.181/32\",\n\t\t\t\t\"20.207.117.66/32\",\n\t\t\t\t\"20.212.90.107/32\",\n\t\t\t\t\"20.212.139.34/32\",\n\t\t\t\t\"20.216.200.223/32\",\n\t\t\t\t\"20.219.43.246/32\",\n\t\t\t\t\"20.219.45.67/32\",\n\t\t\t\t\"20.219.45.190/32\",\n\t\t\t\t\"20.221.10.115/32\",\n\t\t\t\t\"20.221.84.185/32\",\n\t\t\t\t\"20.226.133.105/32\",\n\t\t\t\t\"20.231.254.179/32\",\n\t\t\t\t\"20.232.51.46/32\",\n\t\t\t\t\"20.232.249.95/32\",\n\t\t\t\t\"20.236.40.93/32\",\n\t\t\t\t\"20.236.195.196/32\",\n\t\t\t\t\"20.236.198.192/32\",\n\t\t\t\t\"20.236.227.113/32\",\n\t\t\t\t\"20.236.233.23/32\",\n\t\t\t\t\"20.236.247.180/32\",\n\t\t\t\t\"20.237.1.51/32\",\n\t\t\t\t\"20.241.135.19/32\",\n\t\t\t\t\"20.241.147.151/32\",\n\t\t\t\t\"20.241.153.203/32\",\n\t\t\t\t\"20.241.164.5/32\",\n\t\t\t\t\"20.241.241.100/32\",\n\t\t\t\t\"20.242.133.53/32\",\n\t\t\t\t\"20.242.135.165/32\",\n\t\t\t\t\"20.242.211.139/32\",\n\t\t\t\t\"20.242.215.53/32\",\n\t\t\t\t\"20.242.237.240/32\",\n\t\t\t\t\"20.246.132.168/32\",\n\t\t\t\t\"20.246.225.34/32\",\n\t\t\t\t\"20.247.235.11/32\",\n\t\t\t\t\"20.250.51.113/32\",\n\t\t\t\t\"20.252.9.6/32\",\n\t\t\t\t\"20.252.16.193/32\",\n\t\t\t\t\"20.252.41.18/32\",\n\t\t\t\t\"20.252.63.162/32\",\n\t\t\t\t\"20.252.88.10/32\",\n\t\t\t\t\"20.253.59.76/32\",\n\t\t\t\t\"20.253.96.199/32\",\n\t\t\t\t\"20.253.114.51/32\",\n\t\t\t\t\"40.64.67.220/32\",\n\t\t\t\t\"40.64.78.45/32\",\n\t\t\t\t\"40.64.88.145/32\",\n\t\t\t\t\"40.64.105.247/32\",\n\t\t\t\t\"40.64.106.11/32\",\n\t\t\t\t\"40.64.106.152/32\",\n\t\t\t\t\"40.65.78.86/32\",\n\t\t\t\t\"40.76.162.17/32\",\n\t\t\t\t\"40.76.162.191/32\",\n\t\t\t\t\"40.76.162.208/32\",\n\t\t\t\t\"40.76.162.247/32\",\n\t\t\t\t\"40.76.163.7/32\",\n\t\t\t\t\"40.76.163.23/32\",\n\t\t\t\t\"40.76.173.151/32\",\n\t\t\t\t\"40.81.250.205/32\",\n\t\t\t\t\"40.82.218.203/32\",\n\t\t\t\t\"40.88.21.235/32\",\n\t\t\t\t\"40.88.195.62/32\",\n\t\t\t\t\"40.89.243.175/32\",\n\t\t\t\t\"40.114.182.45/32\",\n\t\t\t\t\"40.114.182.153/32\",\n\t\t\t\t\"40.114.182.172/32\",\n\t\t\t\t\"40.114.183.88/32\",\n\t\t\t\t\"40.114.183.196/32\",\n\t\t\t\t\"40.114.183.251/32\",\n\t\t\t\t\"40.119.232.50/32\",\n\t\t\t\t\"40.119.232.146/32\",\n\t\t\t\t\"40.119.232.215/32\",\n\t\t\t\t\"40.119.232.218/32\",\n\t\t\t\t\"40.119.232.251/32\",\n\t\t\t\t\"40.121.121.54/32\",\n\t\t\t\t\"40.127.154.196/32\",\n\t\t\t\t\"48.192.16.86/32\",\n\t\t\t\t\"48.192.74.178/32\",\n\t\t\t\t\"48.200.33.227/32\",\n\t\t\t\t\"48.202.155.200/32\",\n\t\t\t\t\"48.202.171.124/32\",\n\t\t\t\t\"48.204.32.87/32\",\n\t\t\t\t\"48.206.18.152/32\",\n\t\t\t\t\"48.217.23.236/32\",\n\t\t\t\t\"48.217.129.210/32\",\n\t\t\t\t\"48.217.212.89/32\",\n\t\t\t\t\"48.223.209.110/32\",\n\t\t\t\t\"51.8.31.28/32\",\n\t\t\t\t\"51.8.71.117/32\",\n\t\t\t\t\"51.8.253.152/32\",\n\t\t\t\t\"51.104.144.31/32\",\n\t\t\t\t\"51.104.146.225/32\",\n\t\t\t\t\"51.104.146.235/32\",\n\t\t\t\t\"51.104.160.167/32\",\n\t\t\t\t\"51.104.160.177/32\",\n\t\t\t\t\"51.104.161.32/32\",\n\t\t\t\t\"51.104.162.149/32\",\n\t\t\t\t\"51.104.163.250/32\",\n\t\t\t\t\"51.104.164.109/32\",\n\t\t\t\t\"51.104.164.147/32\",\n\t\t\t\t\"51.104.164.189/32\",\n\t\t\t\t\"51.104.164.215/32\",\n\t\t\t\t\"51.104.166.111/32\",\n\t\t\t\t\"51.104.167.19/32\",\n\t\t\t\t\"51.104.167.52/32\",\n\t\t\t\t\"51.104.167.54/32\",\n\t\t\t\t\"51.104.167.61/32\",\n\t\t\t\t\"51.104.167.71/32\",\n\t\t\t\t\"51.104.167.87/32\",\n\t\t\t\t\"51.104.167.88/32\",\n\t\t\t\t\"51.104.167.95/32\",\n\t\t\t\t\"51.104.167.96/32\",\n\t\t\t\t\"51.104.167.104/32\",\n\t\t\t\t\"51.104.167.110/32\",\n\t\t\t\t\"51.104.180.26/32\",\n\t\t\t\t\"51.104.180.47/32\",\n\t\t\t\t\"51.104.180.53/32\",\n\t\t\t\t\"51.107.40.209/32\",\n\t\t\t\t\"51.116.131.221/32\",\n\t\t\t\t\"51.120.48.122/32\",\n\t\t\t\t\"51.138.90.161/32\",\n\t\t\t\t\"51.138.90.206/32\",\n\t\t\t\t\"51.138.90.233/32\",\n\t\t\t\t\"51.143.114.45/32\",\n\t\t\t\t\"51.145.185.16/32\",\n\t\t\t\t\"52.142.24.149/32\",\n\t\t\t\t\"52.142.26.175/32\",\n\t\t\t\t\"52.143.95.162/32\",\n\t\t\t\t\"52.143.95.204/32\",\n\t\t\t\t\"52.143.241.111/32\",\n\t\t\t\t\"52.143.242.6/32\",\n\t\t\t\t\"52.143.243.117/32\",\n\t\t\t\t\"52.143.244.81/32\",\n\t\t\t\t\"52.143.247.235/32\",\n\t\t\t\t\"52.146.57.51/32\",\n\t\t\t\t\"52.146.58.236/32\",\n\t\t\t\t\"52.146.59.12/32\",\n\t\t\t\t\"52.146.59.154/32\",\n\t\t\t\t\"52.146.59.156/32\",\n\t\t\t\t\"52.146.63.80/32\",\n\t\t\t\t\"52.148.161.87/32\",\n\t\t\t\t\"52.148.165.38/32\",\n\t\t\t\t\"52.149.25.43/32\",\n\t\t\t\t\"52.149.28.18/32\",\n\t\t\t\t\"52.149.28.83/32\",\n\t\t\t\t\"52.149.30.45/32\",\n\t\t\t\t\"52.149.56.151/32\",\n\t\t\t\t\"52.149.58.27/32\",\n\t\t\t\t\"52.149.58.69/32\",\n\t\t\t\t\"52.149.58.139/32\",\n\t\t\t\t\"52.149.58.173/32\",\n\t\t\t\t\"52.149.60.38/32\",\n\t\t\t\t\"52.149.61.51/32\",\n\t\t\t\t\"52.154.60.82/32\",\n\t\t\t\t\"52.154.169.50/32\",\n\t\t\t\t\"52.154.169.200/32\",\n\t\t\t\t\"52.154.170.26/32\",\n\t\t\t\t\"52.154.170.28/32\",\n\t\t\t\t\"52.154.170.88/32\",\n\t\t\t\t\"52.154.170.96/32\",\n\t\t\t\t\"52.154.170.113/32\",\n\t\t\t\t\"52.154.170.117/32\",\n\t\t\t\t\"52.154.170.122/32\",\n\t\t\t\t\"52.154.170.209/32\",\n\t\t\t\t\"52.154.170.229/32\",\n\t\t\t\t\"52.154.170.243/32\",\n\t\t\t\t\"52.154.171.0/32\",\n\t\t\t\t\"52.154.171.44/32\",\n\t\t\t\t\"52.154.171.70/32\",\n\t\t\t\t\"52.154.171.87/32\",\n\t\t\t\t\"52.154.171.150/32\",\n\t\t\t\t\"52.154.171.196/32\",\n\t\t\t\t\"52.154.171.205/32\",\n\t\t\t\t\"52.154.171.235/32\",\n\t\t\t\t\"52.154.171.250/32\",\n\t\t\t\t\"52.154.172.2/32\",\n\t\t\t\t\"52.154.204.93/32\",\n\t\t\t\t\"52.185.93.208/32\",\n\t\t\t\t\"52.186.37.211/32\",\n\t\t\t\t\"52.188.89.106/32\",\n\t\t\t\t\"52.189.70.103/32\",\n\t\t\t\t\"52.190.37.160/32\",\n\t\t\t\t\"52.191.82.242/32\",\n\t\t\t\t\"52.191.222.158/32\",\n\t\t\t\t\"52.224.16.221/32\",\n\t\t\t\t\"52.224.16.229/32\",\n\t\t\t\t\"52.224.19.152/32\",\n\t\t\t\t\"52.224.20.174/32\",\n\t\t\t\t\"52.224.20.181/32\",\n\t\t\t\t\"52.224.20.186/32\",\n\t\t\t\t\"52.224.20.190/32\",\n\t\t\t\t\"52.224.20.193/32\",\n\t\t\t\t\"52.224.20.203/32\",\n\t\t\t\t\"52.224.20.204/32\",\n\t\t\t\t\"52.224.20.223/32\",\n\t\t\t\t\"52.224.20.227/32\",\n\t\t\t\t\"52.224.20.249/32\",\n\t\t\t\t\"52.224.21.4/32\",\n\t\t\t\t\"52.224.21.19/32\",\n\t\t\t\t\"52.224.21.20/32\",\n\t\t\t\t\"52.224.21.23/32\",\n\t\t\t\t\"52.224.21.27/32\",\n\t\t\t\t\"52.224.21.49/32\",\n\t\t\t\t\"52.224.21.51/32\",\n\t\t\t\t\"52.224.21.53/32\",\n\t\t\t\t\"52.224.21.55/32\",\n\t\t\t\t\"52.224.21.61/32\",\n\t\t\t\t\"52.224.147.21/32\",\n\t\t\t\t\"52.224.191.245/32\",\n\t\t\t\t\"52.242.224.168/32\",\n\t\t\t\t\"52.250.46.221/32\",\n\t\t\t\t\"57.151.44.140/32\",\n\t\t\t\t\"57.152.17.237/32\",\n\t\t\t\t\"57.152.62.107/32\",\n\t\t\t\t\"57.152.72.128/32\",\n\t\t\t\t\"57.155.18.44/32\",\n\t\t\t\t\"57.165.205.172/32\",\n\t\t\t\t\"57.167.88.38/32\",\n\t\t\t\t\"64.236.15.33/32\",\n\t\t\t\t\"64.236.93.100/32\",\n\t\t\t\t\"64.236.118.43/32\",\n\t\t\t\t\"68.219.152.220/32\",\n\t\t\t\t\"72.144.95.185/32\",\n\t\t\t\t\"74.163.169.106/32\",\n\t\t\t\t\"74.163.208.206/32\",\n\t\t\t\t\"74.179.200.126/32\",\n\t\t\t\t\"74.179.232.116/32\",\n\t\t\t\t\"74.242.194.48/32\",\n\t\t\t\t\"98.70.219.37/32\",\n\t\t\t\t\"104.43.54.127/32\",\n\t\t\t\t\"104.43.55.116/32\",\n\t\t\t\t\"104.43.55.117/32\",\n\t\t\t\t\"104.43.55.166/32\",\n\t\t\t\t\"104.43.55.167/32\",\n\t\t\t\t\"108.141.83.74/32\",\n\t\t\t\t\"128.203.82.154/32\",\n\t\t\t\t\"128.203.132.152/32\",\n\t\t\t\t\"128.203.181.79/32\",\n\t\t\t\t\"128.203.214.241/32\",\n\t\t\t\t\"128.203.236.110/32\",\n\t\t\t\t\"130.107.228.224/32\",\n\t\t\t\t\"131.145.39.118/32\",\n\t\t\t\t\"131.189.183.241/32\",\n\t\t\t\t\"132.164.209.198/32\",\n\t\t\t\t\"132.196.106.146/32\",\n\t\t\t\t\"132.220.1.71/32\",\n\t\t\t\t\"134.33.207.189/32\",\n\t\t\t\t\"135.171.251.169/32\",\n\t\t\t\t\"135.233.63.237/32\",\n\t\t\t\t\"135.234.221.112/32\",\n\t\t\t\t\"135.235.183.95/32\",\n\t\t\t\t\"172.168.12.93/32\",\n\t\t\t\t\"172.168.43.43/32\",\n\t\t\t\t\"172.168.51.216/32\",\n\t\t\t\t\"172.168.53.53/32\",\n\t\t\t\t\"172.168.81.217/32\",\n\t\t\t\t\"172.168.94.160/32\",\n\t\t\t\t\"172.168.115.250/32\",\n\t\t\t\t\"172.168.133.76/32\",\n\t\t\t\t\"172.168.137.102/32\",\n\t\t\t\t\"172.168.152.88/32\",\n\t\t\t\t\"172.168.173.230/32\",\n\t\t\t\t\"172.168.190.186/32\",\n\t\t\t\t\"172.168.227.120/32\",\n\t\t\t\t\"172.168.254.119/32\",\n\t\t\t\t\"172.169.7.33/32\",\n\t\t\t\t\"172.169.17.165/32\",\n\t\t\t\t\"172.169.28.184/32\",\n\t\t\t\t\"172.169.36.43/32\",\n\t\t\t\t\"172.169.53.208/32\",\n\t\t\t\t\"172.169.60.134/32\",\n\t\t\t\t\"172.169.93.148/32\",\n\t\t\t\t\"172.169.164.109/32\",\n\t\t\t\t\"172.169.177.131/32\",\n\t\t\t\t\"172.169.181.237/32\",\n\t\t\t\t\"172.171.37.221/32\",\n\t\t\t\t\"172.179.105.87/32\",\n\t\t\t\t\"172.179.128.118/32\",\n\t\t\t\t\"172.188.250.220/32\",\n\t\t\t\t\"172.189.40.18/32\",\n\t\t\t\t\"172.193.184.42/32\",\n\t\t\t\t\"172.193.197.146/32\",\n\t\t\t\t\"172.193.245.229/32\",\n\t\t\t\t\"172.194.141.49/32\",\n\t\t\t\t\"172.199.55.212/32\",\n\t\t\t\t\"172.210.111.68/32\",\n\t\t\t\t\"191.233.3.197/32\",\n\t\t\t\t\"191.233.3.202/32\",\n\t\t\t\t\"191.234.216.4/32\",\n\t\t\t\t\"191.234.216.178/32\",\n\t\t\t\t\"191.235.201.214/32\",\n\t\t\t\t\"191.235.202.38/32\",\n\t\t\t\t\"191.235.202.48/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"Applebot\",\n\t\t\t\"url\": \"https://search.developer.apple.com/applebot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"17.166.20.0/24\",\n\t\t\t\t\"17.166.21.0/24\",\n\t\t\t\t\"17.166.22.0/24\",\n\t\t\t\t\"17.166.23.0/24\",\n\t\t\t\t\"17.166.24.0/24\",\n\t\t\t\t\"17.166.25.0/24\",\n\t\t\t\t\"17.166.150.0/24\",\n\t\t\t\t\"17.166.151.0/24\",\n\t\t\t\t\"17.166.152.0/24\",\n\t\t\t\t\"17.166.153.0/24\",\n\t\t\t\t\"17.166.154.0/24\",\n\t\t\t\t\"17.166.155.0/24\",\n\t\t\t\t\"17.166.192.48/28\",\n\t\t\t\t\"17.166.232.0/24\",\n\t\t\t\t\"17.166.233.0/24\",\n\t\t\t\t\"17.166.234.0/24\",\n\t\t\t\t\"17.166.235.0/24\",\n\t\t\t\t\"17.166.236.0/24\",\n\t\t\t\t\"17.166.237.0/24\",\n\t\t\t\t\"17.166.240.48/28\",\n\t\t\t\t\"17.166.248.48/28\",\n\t\t\t\t\"17.241.193.160/27\",\n\t\t\t\t\"17.241.200.160/27\",\n\t\t\t\t\"17.241.208.160/27\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"Claude API\",\n\t\t\t\"url\": \"https://platform.claude.com/docs/en/api/ip-addresses.md\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"153.61.208.0/20\",\n\t\t\t\t\"160.79.104.0/21\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t}\n\t]\n}\n","const encoder = new TextEncoder();\nconst keys = new Map<string, Promise<CryptoKey>>();\n\nfunction importOrgKey(orgKey: string): Promise<CryptoKey> {\n\tlet key = keys.get(orgKey);\n\tif (!key) {\n\t\tkey = crypto.subtle.importKey(\n\t\t\t\"raw\",\n\t\t\tencoder.encode(orgKey),\n\t\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\t\tfalse,\n\t\t\t[\"sign\"],\n\t\t);\n\t\tkey.catch(() => keys.delete(orgKey));\n\t\tkeys.set(orgKey, key);\n\t}\n\treturn key;\n}\n\nconst toHex = (bytes: ArrayBuffer): string =>\n\tArray.from(new Uint8Array(bytes), (byte) =>\n\t\tbyte.toString(16).padStart(2, \"0\"),\n\t).join(\"\");\n\nexport async function deriveMatchKey(\n\torgKey: string,\n\tip: string,\n): Promise<string> {\n\tconst signature = await crypto.subtle.sign(\n\t\t\"HMAC\",\n\t\tawait importOrgKey(orgKey),\n\t\tencoder.encode(ip),\n\t);\n\treturn toHex(signature);\n}\n","import { applyRules, type MatchRules } from \"./logic/decide\";\nimport { settings } from \"./settings\";\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === \"object\" && value !== null;\n\nconst isStringArray = (value: unknown): value is string[] =>\n\tArray.isArray(value) && value.every((item) => typeof item === \"string\");\n\n/** The match rules in a rules file from the app (the `data` of its answer), or null when it does not have the expected form. A malformed range makes `applyRules` throw instead. */\nexport function parseRemoteRules(body: unknown): MatchRules | null {\n\tif (!isObject(body) || body.version !== 1) {\n\t\treturn null;\n\t}\n\tconst { tokens, ipRanges } = body;\n\tif (!isStringArray(tokens) || !Array.isArray(ipRanges)) {\n\t\treturn null;\n\t}\n\tconst ranges: MatchRules[\"ipRanges\"] = [];\n\tfor (const range of ipRanges) {\n\t\tif (\n\t\t\t!isObject(range) ||\n\t\t\t!isStringArray(range.ipv4) ||\n\t\t\t!isStringArray(range.ipv6)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tranges.push({ ipv4: range.ipv4, ipv6: range.ipv6 });\n\t}\n\treturn {\n\t\ttokens: tokens.map((token) => token.toLowerCase()),\n\t\tipRanges: ranges,\n\t};\n}\n\nlet lastAttemptOk = false;\nlet lastAttemptAt = Number.NEGATIVE_INFINITY;\n\nasync function fetchRules(apiUrl: string): Promise<boolean> {\n\tconst response = await fetch(`${apiUrl}/api/v2/agent-traffic/rules`, {\n\t\tsignal: AbortSignal.timeout(settings.requestTimeoutMs),\n\t});\n\tif (!response.ok) {\n\t\treturn false;\n\t}\n\tconst body: unknown = await response.json();\n\tconst rules = parseRemoteRules(isObject(body) ? body.data : null);\n\tif (!rules) {\n\t\treturn false;\n\t}\n\tapplyRules(rules);\n\treturn true;\n}\n\n/**\n * Starts one fetch of the rules file when the last attempt succeeded more than the refresh interval ago,\n * or failed more than the retry interval ago. Returns that work, or null.\n */\nexport function refreshRules(apiUrl: string): Promise<void> | null {\n\tconst now = Date.now();\n\tconst wait = lastAttemptOk ? settings.rulesRefreshMs : settings.rulesRetryMs;\n\tif (now - lastAttemptAt < wait) {\n\t\treturn null;\n\t}\n\tlastAttemptAt = now;\n\tlastAttemptOk = false;\n\treturn fetchRules(apiUrl).then(\n\t\t(ok) => {\n\t\t\tlastAttemptOk = ok;\n\t\t},\n\t\t() => undefined,\n\t);\n}\n","type WaitUntil = (promise: Promise<unknown>) => void;\ntype RequestContextStore = {\n\tget?: () => { waitUntil?: WaitUntil } | undefined;\n};\n\nconst REQUEST_CONTEXT = Symbol.for(\"@vercel/request-context\");\n\nexport function vercelRequestContext(): { waitUntil?: WaitUntil } | undefined {\n\tconst store: RequestContextStore | undefined = Reflect.get(\n\t\tglobalThis,\n\t\tREQUEST_CONTEXT,\n\t);\n\treturn store?.get?.();\n}\n\n/** The `waitUntil` that the Vercel runtime puts in the request context, the same one `@vercel/functions` reads. */\nexport function vercelWaitUntil(): WaitUntil | undefined {\n\tconst context = vercelRequestContext();\n\treturn context?.waitUntil\n\t\t? (promise) => context.waitUntil?.(promise)\n\t\t: undefined;\n}\n","import { vercelRequestContext } from \"./wait-until\";\n\nexport type Platform = \"vercel\" | \"cloudflare\" | \"cloudfront\";\n\n/** The platform this request runs on, or undefined on a runtime the relay does not know. Never throws. */\nexport function detectPlatform(): Platform | undefined {\n\ttry {\n\t\tconst context = vercelRequestContext();\n\t\tif (typeof context === \"object\" && context !== null) {\n\t\t\treturn \"vercel\";\n\t\t}\n\t\tif (globalThis.navigator?.userAgent === \"Cloudflare-Workers\") {\n\t\t\treturn \"cloudflare\";\n\t\t}\n\t} catch {}\n\treturn undefined;\n}\n","import { clientIp, HEADER_CAP } from \"./headers\";\nimport { detectPlatform, type Platform } from \"./platform\";\n\n/** `event.Records[0].cf.request` of a Lambda@Edge viewer-request event. */\nexport type CloudFrontRequest = {\n\tclientIp: string;\n\tmethod: string;\n\turi: string;\n\tquerystring: string;\n\theaders: Record<string, Array<{ key?: string; value: string }>>;\n};\n\nexport type RelayRequest = {\n\turl: string | null;\n\tmethod: string;\n\theaders: Headers;\n\tip: string | null;\n\tplatform: Platform | undefined;\n};\n\nexport function isCloudFrontRequest(\n\tinput: Request | CloudFrontRequest,\n): input is CloudFrontRequest {\n\treturn (\n\t\ttypeof Reflect.get(input, \"clientIp\") === \"string\" &&\n\t\ttypeof Reflect.get(input, \"uri\") === \"string\"\n\t);\n}\n\n/** The origin for a `host` header, or null when the value holds more than a host and an optional port. */\nfunction originOf(host: string | undefined): string | null {\n\tif (!host) {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst url = new URL(`https://${host}`);\n\t\tconst bare =\n\t\t\turl.pathname === \"/\" &&\n\t\t\turl.search === \"\" &&\n\t\t\turl.hash === \"\" &&\n\t\t\turl.username === \"\" &&\n\t\t\turl.password === \"\";\n\t\treturn bare ? url.origin : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** The hostname in a `host` header or a bare hostname, lowercased and without the port, or null when the value holds more. */\nexport function hostnameOf(host: string | undefined): string | null {\n\tconst origin = originOf(host);\n\treturn origin === null ? null : new URL(origin).hostname;\n}\n\nexport function cloudFrontHeader(\n\tinput: CloudFrontRequest,\n\tname: string,\n): string | null {\n\tconst value = input.headers?.[name]?.[0]?.value;\n\treturn typeof value === \"string\" ? value : null;\n}\n\n/** Copies only the headers the relay reads, so a cookie or a value `Headers` refuses never makes the request fail. */\nfunction cloudFrontHeaders(input: CloudFrontRequest[\"headers\"]): Headers {\n\tconst headers = new Headers();\n\tfor (const name of HEADER_CAP) {\n\t\tfor (const entry of input?.[name] ?? []) {\n\t\t\tif (typeof entry?.value !== \"string\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\theaders.append(name, entry.value);\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn headers;\n}\n\n/** The client can set `X-Forwarded-For` at viewer-request, so a CloudFront request takes its IP from `clientIp` alone. `url` is null when the host header is missing or malformed. */\nexport function toRelayRequest(\n\tinput: Request | CloudFrontRequest,\n): RelayRequest {\n\tif (!isCloudFrontRequest(input)) {\n\t\treturn {\n\t\t\turl: input.url,\n\t\t\tmethod: input.method,\n\t\t\theaders: input.headers,\n\t\t\tip: clientIp(input.headers),\n\t\t\tplatform: detectPlatform(),\n\t\t};\n\t}\n\tconst origin = originOf(input.headers?.host?.[0]?.value);\n\tconst query = input.querystring ? `?${input.querystring}` : \"\";\n\tconst ip = input.clientIp.trim().toLowerCase();\n\treturn {\n\t\turl: origin === null ? null : `${origin}${input.uri}${query}`,\n\t\tmethod: input.method,\n\t\theaders: cloudFrontHeaders(input.headers),\n\t\tip: ip ? ip : null,\n\t\tplatform: \"cloudfront\",\n\t};\n}\n","import {\n\tfetchAgentPage,\n\tloadSiteRules,\n\tmatchesPath,\n\tnormalizePath,\n\ttype PageFormat,\n\tpageFormat,\n\ttype SiteRules,\n} from \"./agent-page\";\nimport { readCappedHeaders } from \"./headers\";\nimport { type PostPolicy, postEvent } from \"./ingest\";\nimport { readKey } from \"./key\";\nimport { decide } from \"./logic\";\nimport { deriveMatchKey } from \"./match-key\";\nimport type { Platform } from \"./platform\";\nimport { refreshRules } from \"./refresh\";\nimport {\n\ttype CloudFrontRequest,\n\tcloudFrontHeader,\n\thostnameOf,\n\tisCloudFrontRequest,\n\ttoRelayRequest,\n} from \"./request\";\nimport { settings } from \"./settings\";\nimport { RELAY_VERSION } from \"./version\";\nimport { vercelWaitUntil } from \"./wait-until\";\n\nexport type { CloudFrontRequest };\n\n/** The response a Lambda@Edge handler returns in place of the request. */\nexport type CloudFrontResultResponse = {\n\tstatus: string;\n\tstatusDescription?: string;\n\theaders: Record<string, Array<{ key: string; value: string }>>;\n\tbody?: string;\n};\n\nexport type Region = keyof typeof settings.apiUrls;\n\nexport type RelayOptions = {\n\t/** The region of your WaniWani organization. Defaults to `\"us\"`. */\n\tregion?: Region;\n\t/** Overrides the host that `region` picks. */\n\tapiUrl?: string;\n\t/** Overrides `process.env.WANIWANI_AGENT_TRAFFIC_KEY`. */\n\tkey?: string;\n\t/** Keeps the background post alive after the response. Defaults to the `waitUntil` of the Vercel request context. */\n\twaitUntil?: (promise: Promise<unknown>) => void;\n\t/** Header names, and the fields `matchKey`, `userAgent` or `referer`, to leave out of every event. */\n\tomit?: Array<string>;\n\t/** `\"remote\"` loads the newest AI bot list from WaniWani every 12 hours. `\"bundled\"` keeps the list shipped in this version. Defaults to `\"remote\"`. */\n\trules?: \"remote\" | \"bundled\";\n};\n\nexport type IsAgentOptions = Pick<\n\tRelayOptions,\n\t\"region\" | \"apiUrl\" | \"waitUntil\" | \"rules\"\n>;\n\nexport type ServeAgentPageOptions = Pick<\n\tRelayOptions,\n\t\"region\" | \"apiUrl\" | \"key\"\n>;\n\nexport type AgentVisitRecord = {\n\toccurredAt: string;\n\tmethod: string;\n\thost: string;\n\tpath: string;\n\tuserAgent?: string;\n\treferer?: string;\n\theaders: Record<string, string>;\n\tmatchKey?: string;\n\tsourceEventId: string;\n\trelayVersion: string;\n\tplatform?: Platform;\n};\n\n/**\n * Tells whether a request, or the `cf.request` of a Lambda@Edge event, comes from an AI agent. Reads headers only and never waits on the network.\n * Every 12 hours it starts one background fetch of the newest AI bot list, unless `rules` is `\"bundled\"`.\n */\nexport function isAgent(\n\trequest: Request | CloudFrontRequest,\n\toptions: IsAgentOptions = {},\n): boolean {\n\tstartRulesRefresh(options);\n\ttry {\n\t\tconst { headers, ip } = toRelayRequest(request);\n\t\treturn decide({\n\t\t\theaders: readCappedHeaders(headers),\n\t\t\tip,\n\t\t\tuseBundled: options.rules === \"bundled\",\n\t\t});\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction startRulesRefresh(options: IsAgentOptions): void {\n\ttry {\n\t\tif (options.rules === \"bundled\") {\n\t\t\treturn;\n\t\t}\n\t\tconst apiUrl = resolveApiUrl(options);\n\t\tconst work = apiUrl ? refreshRules(apiUrl) : null;\n\t\tif (work) {\n\t\t\tschedule(work, options.waitUntil);\n\t\t}\n\t} catch {}\n}\n\n/**\n * Posts one event for this request to WaniWani after the response.\n * Sends nothing when no key is set. Never throws, never rejects and never touches the request.\n * The promise settles when the post is done, so Lambda@Edge, which has no `waitUntil`, awaits it.\n */\nexport function recordAgentVisit(\n\trequest: Request | CloudFrontRequest,\n\toptions: RelayOptions = {},\n): Promise<void> {\n\ttry {\n\t\tconst key = readKey(options.key);\n\t\tif (!key) {\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tconst apiUrl = resolveApiUrl(options);\n\t\tif (!apiUrl) {\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tconst omitted = omitSet(options.omit);\n\t\tconst {\n\t\t\turl: requestUrl,\n\t\t\tmethod,\n\t\t\theaders: requestHeaders,\n\t\t\tip,\n\t\t\tplatform,\n\t\t} = toRelayRequest(request);\n\t\tif (requestUrl === null) {\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tconst capped = readCappedHeaders(requestHeaders);\n\n\t\tconst headers: Record<string, string> = {};\n\t\tfor (const [name, value] of Object.entries(capped)) {\n\t\t\tif (!omitted.has(name)) {\n\t\t\t\theaders[name] = value;\n\t\t\t}\n\t\t}\n\t\tconst url = new URL(requestUrl);\n\t\tconst record: AgentVisitRecord = {\n\t\t\toccurredAt: new Date().toISOString(),\n\t\t\tmethod,\n\t\t\thost: url.hostname,\n\t\t\tpath: url.pathname + url.search,\n\t\t\theaders,\n\t\t\tsourceEventId: crypto.randomUUID(),\n\t\t\trelayVersion: RELAY_VERSION,\n\t\t};\n\t\tif (capped[\"user-agent\"] !== undefined && !omitted.has(\"user-agent\")) {\n\t\t\trecord.userAgent = capped[\"user-agent\"];\n\t\t}\n\t\tif (capped.referer !== undefined && !omitted.has(\"referer\")) {\n\t\t\trecord.referer = capped.referer;\n\t\t}\n\t\tif (platform !== undefined) {\n\t\t\trecord.platform = platform;\n\t\t}\n\n\t\tconst policy: PostPolicy =\n\t\t\tplatform === \"cloudfront\"\n\t\t\t\t? { timeoutMs: settings.cloudFrontTimeoutMs, retry: false }\n\t\t\t\t: { timeoutMs: settings.requestTimeoutMs, retry: true };\n\t\tconst ingestUrl = `${apiUrl}/api/v2/agent-traffic/ingest/middleware/${key.ingestToken}`;\n\t\tconst withMatchKey = async () => {\n\t\t\tif (ip !== null && !omitted.has(\"matchkey\")) {\n\t\t\t\trecord.matchKey = await deriveMatchKey(key.orgKey, ip).catch(\n\t\t\t\t\t() => undefined,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait postEvent(\n\t\t\t\tingestUrl,\n\t\t\t\tJSON.stringify({ requests: [record] }),\n\t\t\t\tpolicy,\n\t\t\t);\n\t\t};\n\t\treturn schedule(withMatchKey(), options.waitUntil);\n\t} catch {\n\t\treturn Promise.resolve();\n\t}\n}\n\ntype PageRequest = {\n\tmethod: string;\n\thostname: string;\n\tpathname: string;\n\tuserAgent: string;\n\taccept: string;\n\trendering: boolean;\n};\n\ntype PageAnswer = {\n\tstatus: 200 | 403;\n\theaders: Record<string, string>;\n\tbody: Uint8Array<ArrayBuffer> | string | null;\n};\n\ntype SiteMatch = {\n\trules: SiteRules;\n\tsite: SiteRules[\"sites\"][number];\n};\n\nconst BLOCKED: Omit<PageAnswer, \"body\"> = {\n\tstatus: 403,\n\theaders: {\n\t\t\"content-type\": \"text/plain; charset=utf-8\",\n\t\t\"x-waniwani-blocked\": \"1\",\n\t\t\"cache-control\": \"private, no-store\",\n\t\tvary: \"user-agent\",\n\t},\n};\n\n/**\n * The stored agent page for this request, when its site lists the path and the user agent acts for a user. A 403 when the site blocks the user agent. Otherwise null, and the caller serves the origin.\n * A `CloudFrontRequest` comes from the origin-request handler. Only a URI that `handleViewerRequest` marked gets the page, read for the host in the mark. The function restores `request.uri` to the original path before anything else, and it never returns a 403 on this path.\n * Never throws and never rejects.\n */\nexport function serveAgentPage(\n\trequest: Request,\n\toptions?: ServeAgentPageOptions,\n): Promise<Response | null>;\nexport function serveAgentPage(\n\trequest: CloudFrontRequest,\n\toptions?: ServeAgentPageOptions,\n): Promise<CloudFrontResultResponse | null>;\nexport async function serveAgentPage(\n\trequest: Request | CloudFrontRequest,\n\toptions: ServeAgentPageOptions = {},\n): Promise<Response | CloudFrontResultResponse | null> {\n\ttry {\n\t\treturn isCloudFrontRequest(request)\n\t\t\t? await serveCloudFront(request, options)\n\t\t\t: await serveFetch(request, options);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function serveFetch(\n\trequest: Request,\n\toptions: ServeAgentPageOptions,\n): Promise<Response | null> {\n\tconst url = new URL(request.url);\n\tconst answer = await answerAgentPage(\n\t\t{\n\t\t\tmethod: request.method.toUpperCase(),\n\t\t\thostname: url.hostname,\n\t\t\tpathname: url.pathname,\n\t\t\tuserAgent: request.headers.get(\"user-agent\") ?? \"\",\n\t\t\taccept: request.headers.get(\"accept\") ?? \"\",\n\t\t\trendering: request.headers.has(\"x-waniwani-render\"),\n\t\t},\n\t\toptions,\n\t);\n\tif (!answer) {\n\t\treturn null;\n\t}\n\treturn new Response(answer.body, {\n\t\tstatus: answer.status,\n\t\theaders: answer.headers,\n\t});\n}\n\nasync function serveCloudFront(\n\trequest: CloudFrontRequest,\n\toptions: ServeAgentPageOptions,\n): Promise<CloudFrontResultResponse | null> {\n\tconst mark = takeMarkFromUri(request);\n\tif (mark === null) {\n\t\treturn null;\n\t}\n\tconst { hostname, format } = mark;\n\tconst method = request.method.toUpperCase();\n\tif (!isPageMethod(method)) {\n\t\treturn null;\n\t}\n\tconst match = await findSite(hostname, options);\n\tif (!match) {\n\t\treturn null;\n\t}\n\tconst answer = await pageAnswer(\n\t\tmatch.rules,\n\t\thostname,\n\t\tnormalizePath(request.uri),\n\t\tmethod,\n\t\tformat,\n\t);\n\tif (!answer) {\n\t\treturn null;\n\t}\n\tconst result = toCloudFrontResponse(answer);\n\tif (\n\t\tresult.body !== undefined &&\n\t\tnew TextEncoder().encode(result.body).byteLength >\n\t\t\tsettings.cloudFrontMaxBodyBytes\n\t) {\n\t\treturn null;\n\t}\n\treturn result;\n}\n\nfunction toCloudFrontResponse(answer: PageAnswer): CloudFrontResultResponse {\n\tconst headers: CloudFrontResultResponse[\"headers\"] = {};\n\tfor (const [name, value] of Object.entries(answer.headers)) {\n\t\theaders[name] = [{ key: headerKey(name), value }];\n\t}\n\tconst result: CloudFrontResultResponse = {\n\t\tstatus: String(answer.status),\n\t\tstatusDescription: answer.status === 200 ? \"OK\" : \"Forbidden\",\n\t\theaders,\n\t};\n\tif (answer.body !== null) {\n\t\tresult.body =\n\t\t\ttypeof answer.body === \"string\"\n\t\t\t\t? answer.body\n\t\t\t\t: new TextDecoder().decode(answer.body);\n\t}\n\treturn result;\n}\n\nfunction headerKey(name: string): string {\n\treturn name\n\t\t.split(\"-\")\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"-\");\n}\n\nconst isPageMethod = (method: string): boolean =>\n\tmethod === \"GET\" || method === \"HEAD\";\n\nasync function findSite(\n\thostname: string,\n\toptions: ServeAgentPageOptions,\n): Promise<SiteMatch | null> {\n\tconst key = readKey(options.key);\n\tif (!key) {\n\t\treturn null;\n\t}\n\tconst apiUrl = resolveApiUrl(options);\n\tif (!apiUrl) {\n\t\treturn null;\n\t}\n\tconst rules = await loadSiteRules(apiUrl, key.ingestToken, (work) =>\n\t\tschedule(work, undefined),\n\t);\n\tif (!rules) {\n\t\treturn null;\n\t}\n\tconst site = rules.sites.find((entry) => entry.hostname === hostname);\n\treturn site ? { rules, site } : null;\n}\n\nfunction blockedAnswer(\n\tsite: SiteMatch[\"site\"],\n\tuserAgent: string,\n\tmethod: string,\n): PageAnswer | null {\n\treturn site.blockedUserAgentTokens.some((token) => userAgent.includes(token))\n\t\t? { ...BLOCKED, body: method === \"HEAD\" ? null : \"Forbidden\" }\n\t\t: null;\n}\n\nasync function answerAgentPage(\n\trequest: PageRequest,\n\toptions: ServeAgentPageOptions,\n): Promise<PageAnswer | null> {\n\tconst { method, hostname } = request;\n\tif (!isPageMethod(method)) {\n\t\treturn null;\n\t}\n\tconst match = await findSite(hostname, options);\n\tif (!match) {\n\t\treturn null;\n\t}\n\tconst { rules, site } = match;\n\tconst userAgent = request.userAgent.toLowerCase();\n\tconst blocked = blockedAnswer(site, userAgent, method);\n\tif (blocked) {\n\t\treturn blocked;\n\t}\n\tif (request.rendering || !site.agentPages.enabled) {\n\t\treturn null;\n\t}\n\tconst path = normalizePath(request.pathname);\n\tif (!matchesPath(path, site.agentPages.paths)) {\n\t\treturn null;\n\t}\n\tif (!rules.userAgentTokens.some((token) => userAgent.includes(token))) {\n\t\treturn null;\n\t}\n\treturn pageAnswer(rules, hostname, path, method, pageFormat(request.accept));\n}\n\nasync function pageAnswer(\n\trules: SiteRules,\n\thostname: string,\n\tpath: string,\n\tmethod: string,\n\tformat: PageFormat,\n): Promise<PageAnswer | null> {\n\tconst page = await fetchAgentPage(rules, hostname, path, format);\n\tif (!page) {\n\t\treturn null;\n\t}\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"content-type\": page.contentType,\n\t\t\t\"x-waniwani-agent-page\": \"hit\",\n\t\t\t\"cache-control\": \"private, no-store\",\n\t\t\tvary: \"user-agent, accept\",\n\t\t},\n\t\tbody: method === \"HEAD\" ? null : page.body,\n\t};\n}\n\nconst AGENT_URI_PREFIX = \"/__waniwani-agent/\";\n\nconst PAGE_FORMATS: readonly PageFormat[] = [\"markdown\", \"html\"];\n\n/** Splits `/__waniwani-agent/<format>/<host><original uri>`. The format is null when the mark names none the relay knows. */\nfunction splitAgentUri(uri: string): {\n\tformat: PageFormat | null;\n\tencodedHost: string;\n\toriginalUri: string;\n} | null {\n\tif (!uri.startsWith(AGENT_URI_PREFIX)) {\n\t\treturn null;\n\t}\n\tconst rest = uri.slice(AGENT_URI_PREFIX.length);\n\tconst formatEnd = rest.indexOf(\"/\");\n\tconst formatName = formatEnd === -1 ? rest : rest.slice(0, formatEnd);\n\tconst format = PAGE_FORMATS.find((known) => known === formatName) ?? null;\n\tconst afterFormat = formatEnd === -1 ? \"\" : rest.slice(formatEnd + 1);\n\tconst slash = afterFormat.indexOf(\"/\");\n\treturn slash === -1\n\t\t? { format, encodedHost: afterFormat, originalUri: \"/\" }\n\t\t: {\n\t\t\t\tformat,\n\t\t\t\tencodedHost: afterFormat.slice(0, slash),\n\t\t\t\toriginalUri: afterFormat.slice(slash),\n\t\t\t};\n}\n\nfunction stripForgedMark(request: CloudFrontRequest): void {\n\tlet forged = splitAgentUri(request.uri);\n\twhile (forged) {\n\t\trequest.uri = forged.originalUri;\n\t\tforged = splitAgentUri(request.uri);\n\t}\n}\n\nfunction takeMarkFromUri(\n\trequest: CloudFrontRequest,\n): { hostname: string; format: PageFormat } | null {\n\tconst parts = splitAgentUri(request.uri);\n\tif (!parts) {\n\t\treturn null;\n\t}\n\trequest.uri = parts.originalUri;\n\tif (parts.format === null) {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst hostname = hostnameOf(decodeURIComponent(parts.encodedHost));\n\t\treturn hostname === null ? null : { hostname, format: parts.format };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * For the viewer-request handler on CloudFront. Strips a `/__waniwani-agent/` prefix the client sent, then, for a GET or HEAD from an AI agent, loads the rules of the site of the `host` header.\n * Returns the 403 response when the site blocks the user agent. CloudFront never caches a response made at viewer-request, so a block takes effect at once.\n * When the site serves an agent page for the path and the user agent acts for a user, rewrites `request.uri` to `/__waniwani-agent/<host><original uri>` so the origin-request handler serves the page under its own cache key. The request stays unmarked when the new URI would pass the CloudFront URI limit.\n * Call `recordAgentVisit` before it, so the event holds the original path. Never throws and never rejects.\n */\nexport async function handleViewerRequest(\n\trequest: CloudFrontRequest,\n\toptions: ServeAgentPageOptions = {},\n): Promise<CloudFrontResultResponse | null> {\n\ttry {\n\t\tstripForgedMark(request);\n\t\tconst method = request.method.toUpperCase();\n\t\tif (!isPageMethod(method) || !isAgent(request, options)) {\n\t\t\treturn null;\n\t\t}\n\t\tconst hostname = hostnameOf(cloudFrontHeader(request, \"host\") ?? undefined);\n\t\tif (hostname === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst match = await findSite(hostname, options);\n\t\tif (!match) {\n\t\t\treturn null;\n\t\t}\n\t\tconst { rules, site } = match;\n\t\tconst userAgent = (\n\t\t\tcloudFrontHeader(request, \"user-agent\") ?? \"\"\n\t\t).toLowerCase();\n\t\tconst blocked = blockedAnswer(site, userAgent, method);\n\t\tif (blocked) {\n\t\t\treturn toCloudFrontResponse(blocked);\n\t\t}\n\t\tif (\n\t\t\tcloudFrontHeader(request, \"x-waniwani-render\") !== null ||\n\t\t\t!site.agentPages.enabled ||\n\t\t\t!matchesPath(normalizePath(request.uri), site.agentPages.paths) ||\n\t\t\t!rules.userAgentTokens.some((token) => userAgent.includes(token))\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tconst format = pageFormat(cloudFrontHeader(request, \"accept\") ?? \"\");\n\t\tconst uri = `${AGENT_URI_PREFIX}${format}/${encodeURIComponent(hostname)}${request.uri}`;\n\t\tif (\n\t\t\tnew TextEncoder().encode(uri).byteLength <= settings.cloudFrontMaxUriBytes\n\t\t) {\n\t\t\trequest.uri = uri;\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nlet warnedRegion = false;\n\nfunction resolveApiUrl(options: IsAgentOptions): string | null {\n\tif (options.apiUrl !== undefined) {\n\t\treturn options.apiUrl.replace(/\\/+$/, \"\");\n\t}\n\tconst region = options.region ?? \"us\";\n\tif (Object.hasOwn(settings.apiUrls, region)) {\n\t\treturn settings.apiUrls[region];\n\t}\n\tif (!warnedRegion) {\n\t\twarnedRegion = true;\n\t\tconsole.warn(\n\t\t\t`[@waniwani/relay] Unknown region ${JSON.stringify(region)}, so no agent visits are recorded. Use \"us\" or \"eu\".`,\n\t\t);\n\t}\n\treturn null;\n}\n\nfunction omitSet(omit: Array<string> | undefined): Set<string> {\n\tconst names = new Set((omit ?? []).map((name) => name.toLowerCase()));\n\tif (names.has(\"useragent\")) {\n\t\tnames.add(\"user-agent\");\n\t}\n\treturn names;\n}\n\nfunction schedule(\n\twork: Promise<unknown>,\n\twaitUntil: RelayOptions[\"waitUntil\"],\n): Promise<void> {\n\tconst settled = work.then(\n\t\t() => undefined,\n\t\t() => undefined,\n\t);\n\ttry {\n\t\t(waitUntil ?? vercelWaitUntil())?.(settled);\n\t} catch {}\n\treturn settled;\n}\n"],"mappings":"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AA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;;;AC7KA;AAAA,EACC,SAAW;AAAA,IACV;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ;AAAA,QACP;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA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","names":["isObject","isStringArray"]}
1
+ {"version":3,"sources":["../src/settings.ts","../src/version.ts","../src/agent-page.ts","../src/headers.ts","../src/http.ts","../src/ingest.ts","../src/key.ts","../src/logic/decide.ts","../src/logic/agents.generated.json","../src/logic/ip-ranges.generated.json","../src/match-key.ts","../src/refresh.ts","../src/wait-until.ts","../src/platform.ts","../src/request.ts","../src/index.ts"],"sourcesContent":["export const settings = {\n\tapiUrls: {\n\t\tus: \"https://app.waniwani.ai\",\n\t\teu: \"https://eu.app.waniwani.ai\",\n\t},\n\trequestTimeoutMs: 2000,\n\tcloudFrontTimeoutMs: 1000,\n\tcloudFrontMaxUriBytes: 8192,\n\tcloudFrontMaxBodyBytes: 1_000_000 - 16_384,\n\tretryDelayMs: 500,\n\trulesRefreshMs: 12 * 60 * 60 * 1000,\n\trulesRetryMs: 5 * 60 * 1000,\n\tsiteRulesTimeoutMs: 300,\n\tsiteRulesRefreshMs: 60_000,\n\tsiteRulesRetryMs: 5_000,\n};\n","declare const __RELAY_PACKAGE_VERSION__: string | undefined;\n\nexport const RELAY_VERSION: string =\n\ttypeof __RELAY_PACKAGE_VERSION__ === \"string\"\n\t\t? __RELAY_PACKAGE_VERSION__\n\t\t: \"0.0.0-dev\";\n","import { settings } from \"./settings\";\nimport { RELAY_VERSION } from \"./version\";\n\nexport type SiteRules = {\n\tversion: 1;\n\tpagesUrl: string;\n\tuserAgentTokens: string[];\n\tdeadlines: { headersMs: number; bodyMs: number };\n\tmaxBodyBytes: number;\n\tsites: Array<{\n\t\thostname: string;\n\t\tblockedUserAgentTokens: string[];\n\t\tagentPages: { enabled: boolean; paths: string[] };\n\t}>;\n};\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === \"object\" && value !== null;\n\nconst isStringArray = (value: unknown): value is string[] =>\n\tArray.isArray(value) && value.every((item) => typeof item === \"string\");\n\nconst isPositive = (value: unknown): value is number =>\n\ttypeof value === \"number\" && Number.isFinite(value) && value > 0;\n\n/** Lowercase like the user agent they are matched against, with blank tokens dropped since they would match every request. */\nconst cleanTokens = (tokens: string[]): string[] =>\n\ttokens\n\t\t.map((token) => token.trim().toLowerCase())\n\t\t.filter((token) => token !== \"\");\n\n/** The site rules in a serve rules file from the app (the `data` of its answer), or null when it does not have the expected form. */\nexport function parseSiteRules(body: unknown): SiteRules | null {\n\tif (!isObject(body) || body.version !== 1) {\n\t\treturn null;\n\t}\n\tconst { pagesUrl, userAgentTokens, deadlines, maxBodyBytes, sites } = body;\n\tif (\n\t\ttypeof pagesUrl !== \"string\" ||\n\t\t!isStringArray(userAgentTokens) ||\n\t\t!isObject(deadlines) ||\n\t\t!isPositive(deadlines.headersMs) ||\n\t\t!isPositive(deadlines.bodyMs) ||\n\t\t!isPositive(maxBodyBytes) ||\n\t\t!Array.isArray(sites)\n\t) {\n\t\treturn null;\n\t}\n\tconst parsedSites: SiteRules[\"sites\"] = [];\n\tfor (const site of sites) {\n\t\tif (!isObject(site)) {\n\t\t\treturn null;\n\t\t}\n\t\tconst blocked = site.blockedUserAgentTokens ?? [];\n\t\tif (\n\t\t\ttypeof site.hostname !== \"string\" ||\n\t\t\t!isStringArray(blocked) ||\n\t\t\t!isObject(site.agentPages) ||\n\t\t\ttypeof site.agentPages.enabled !== \"boolean\" ||\n\t\t\t!isStringArray(site.agentPages.paths)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tparsedSites.push({\n\t\t\thostname: site.hostname.toLowerCase(),\n\t\t\tblockedUserAgentTokens: cleanTokens(blocked),\n\t\t\tagentPages: {\n\t\t\t\tenabled: site.agentPages.enabled,\n\t\t\t\tpaths: site.agentPages.paths,\n\t\t\t},\n\t\t});\n\t}\n\treturn {\n\t\tversion: 1,\n\t\tpagesUrl: pagesUrl.replace(/\\/+$/, \"\"),\n\t\tuserAgentTokens: cleanTokens(userAgentTokens),\n\t\tdeadlines: { headersMs: deadlines.headersMs, bodyMs: deadlines.bodyMs },\n\t\tmaxBodyBytes,\n\t\tsites: parsedSites,\n\t};\n}\n\ntype CacheEntry = {\n\trules: SiteRules | null;\n\tfetchedAt: number;\n\tlastAttemptAt: number;\n\tlastAttemptOk: boolean;\n\tinFlight: Promise<SiteRules | null> | null;\n};\n\nconst cache = new Map<string, CacheEntry>();\n\nasync function fetchSiteRules(\n\turl: string,\n\ttimeoutMs: number,\n): Promise<SiteRules | null> {\n\tconst response = await fetch(url, {\n\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t});\n\tif (!response.ok) {\n\t\tawait response.body?.cancel().catch(() => {});\n\t\treturn null;\n\t}\n\tconst body: unknown = await response.json();\n\treturn parseSiteRules(isObject(body) ? body.data : null);\n}\n\nfunction startFetch(\n\tentry: CacheEntry,\n\turl: string,\n\ttimeoutMs: number,\n): Promise<SiteRules | null> {\n\tconst now = Date.now();\n\tentry.lastAttemptAt = now;\n\tentry.lastAttemptOk = false;\n\tconst work = fetchSiteRules(url, timeoutMs)\n\t\t.catch(() => null)\n\t\t.then((rules) => {\n\t\t\tif (rules) {\n\t\t\t\tentry.rules = rules;\n\t\t\t\tentry.fetchedAt = Date.now();\n\t\t\t\tentry.lastAttemptOk = true;\n\t\t\t}\n\t\t\tentry.inFlight = null;\n\t\t\treturn rules;\n\t\t});\n\tentry.inFlight = work;\n\treturn work;\n}\n\n/**\n * The rules for this org. Awaits the first fetch, then serves the cache and refreshes it in the background once it is stale.\n * At most one fetch per org is in flight.\n */\nexport async function loadSiteRules(\n\tapiUrl: string,\n\tingestToken: string,\n\tbackground: (work: Promise<unknown>) => void,\n): Promise<SiteRules | null> {\n\tconst url = `${apiUrl}/api/v2/agent-traffic/serve/${ingestToken}/rules`;\n\tlet entry = cache.get(url);\n\tif (!entry) {\n\t\tentry = {\n\t\t\trules: null,\n\t\t\tfetchedAt: 0,\n\t\t\tlastAttemptAt: Number.NEGATIVE_INFINITY,\n\t\t\tlastAttemptOk: false,\n\t\t\tinFlight: null,\n\t\t};\n\t\tcache.set(url, entry);\n\t}\n\tconst now = Date.now();\n\tif (entry.rules) {\n\t\tconst stale = now - entry.fetchedAt >= settings.siteRulesRefreshMs;\n\t\tconst retryDue =\n\t\t\tentry.lastAttemptOk ||\n\t\t\tnow - entry.lastAttemptAt >= settings.siteRulesRetryMs;\n\t\tif (stale && retryDue && !entry.inFlight) {\n\t\t\tbackground(startFetch(entry, url, settings.requestTimeoutMs));\n\t\t}\n\t\treturn entry.rules;\n\t}\n\tif (entry.inFlight) {\n\t\treturn entry.inFlight;\n\t}\n\tif (now - entry.lastAttemptAt < settings.siteRulesRetryMs) {\n\t\treturn null;\n\t}\n\treturn startFetch(entry, url, settings.siteRulesTimeoutMs);\n}\n\n/** The pathname without its trailing slashes, except for the root. */\nexport function normalizePath(pathname: string): string {\n\tconst trimmed = pathname.replace(/\\/+$/, \"\");\n\treturn trimmed === \"\" ? \"/\" : trimmed;\n}\n\n/** Whether a path matches an exact entry like `/pricing`, or a folder entry like `/products/*` that also covers `/products`. */\nexport function matchesPath(path: string, patterns: string[]): boolean {\n\treturn patterns.some((pattern) => {\n\t\tif (pattern === \"*\" || pattern === \"/*\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (pattern.endsWith(\"/*\")) {\n\t\t\tconst folder = normalizePath(pattern.slice(0, -2));\n\t\t\treturn path === folder || path.startsWith(`${folder}/`);\n\t\t}\n\t\treturn path === normalizePath(pattern);\n\t});\n}\n\nconst TIMEOUT = Symbol(\"timeout\");\n\nfunction within<T>(\n\tpromise: Promise<T>,\n\tms: number,\n): Promise<T | typeof TIMEOUT> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst deadline = new Promise<typeof TIMEOUT>((resolve) => {\n\t\ttimer = setTimeout(() => resolve(TIMEOUT), ms);\n\t});\n\treturn Promise.race([promise, deadline]).finally(() => clearTimeout(timer));\n}\n\nasync function readCapped(\n\tbody: ReadableStream<Uint8Array>,\n\tdeadline: Promise<typeof TIMEOUT>,\n\tmaxBytes: number,\n): Promise<Uint8Array<ArrayBuffer> | null> {\n\tconst reader = body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet size = 0;\n\ttry {\n\t\twhile (true) {\n\t\t\tconst result = await Promise.race([reader.read(), deadline]);\n\t\t\tif (result === TIMEOUT) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (result.done) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tsize += result.value.byteLength;\n\t\t\tif (size > maxBytes) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tchunks.push(result.value);\n\t\t}\n\t} finally {\n\t\treader.cancel().catch(() => {});\n\t}\n\tconst out = new Uint8Array(size);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tout.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\treturn out;\n}\n\nexport type PageFormat = \"markdown\" | \"html\";\n\ntype MediaRange = { type: string; q: number };\n\nfunction mediaRanges(accept: string): MediaRange[] {\n\treturn accept.split(\",\").map((range) => {\n\t\tconst [type = \"\", ...params] = range\n\t\t\t.split(\";\")\n\t\t\t.map((part) => part.trim().toLowerCase());\n\t\tconst q = params\n\t\t\t.map((param) => param.split(\"=\").map((part) => part.trim()))\n\t\t\t.find(([name]) => name === \"q\");\n\t\tconst value = q === undefined ? 1 : Number(q[1]);\n\t\treturn { type, q: Number.isFinite(value) ? value : 0 };\n\t});\n}\n\n/** The highest q among the ranges of exactly this type, or null when none names it. */\nfunction qualityOf(ranges: MediaRange[], type: string): number | null {\n\tconst named = ranges.filter((range) => range.type === type);\n\treturn named.length === 0 ? null : Math.max(...named.map((range) => range.q));\n}\n\nconst markdownQuality = (ranges: MediaRange[]): number =>\n\tqualityOf(ranges, \"text/markdown\") ?? 0;\n\n/** Markdown only when the agent lists `text/markdown` with a nonzero q, so an agent that asks for HTML or for anything gets HTML. */\nexport function pageFormat(accept: string): PageFormat {\n\treturn markdownQuality(mediaRanges(accept)) > 0 ? \"markdown\" : \"html\";\n}\n\n/** Whether a client names `text/markdown` and ranks it at least as high as the most specific range that covers `text/html`. A browser never names `text/markdown`. */\nexport function prefersMarkdown(accept: string): boolean {\n\tconst ranges = mediaRanges(accept);\n\tconst markdown = markdownQuality(ranges);\n\tconst html =\n\t\tqualityOf(ranges, \"text/html\") ??\n\t\tqualityOf(ranges, \"text/*\") ??\n\t\tqualityOf(ranges, \"*/*\") ??\n\t\t0;\n\treturn markdown > 0 && markdown >= html;\n}\n\n/** Fetches the stored page in the agent's format within both deadlines and the size cap. Returns its body and content type, or null. The home page URL has no trailing slash, which the app would answer with a redirect. */\nexport async function fetchAgentPage(\n\trules: SiteRules,\n\thostname: string,\n\tpath: string,\n\tformat: PageFormat,\n): Promise<{ body: Uint8Array<ArrayBuffer>; contentType: string } | null> {\n\tconst controller = new AbortController();\n\ttry {\n\t\tconst response = await within(\n\t\t\tfetch(`${rules.pagesUrl}/${hostname}${path === \"/\" ? \"\" : path}`, {\n\t\t\t\theaders: {\n\t\t\t\t\taccept: format === \"markdown\" ? \"text/markdown\" : \"text/html\",\n\t\t\t\t\t\"x-waniwani-relay\": RELAY_VERSION,\n\t\t\t\t},\n\t\t\t\tsignal: controller.signal,\n\t\t\t}),\n\t\t\trules.deadlines.headersMs,\n\t\t);\n\t\tif (response === TIMEOUT) {\n\t\t\treturn null;\n\t\t}\n\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\tif (\n\t\t\tresponse.status !== 200 ||\n\t\t\t!contentType.toLowerCase().startsWith(\"text/\") ||\n\t\t\t!response.body\n\t\t) {\n\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\treturn null;\n\t\t}\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tconst deadline = new Promise<typeof TIMEOUT>((resolve) => {\n\t\t\ttimer = setTimeout(() => resolve(TIMEOUT), rules.deadlines.bodyMs);\n\t\t});\n\t\ttry {\n\t\t\tconst body = await readCapped(\n\t\t\t\tresponse.body,\n\t\t\t\tdeadline,\n\t\t\t\trules.maxBodyBytes,\n\t\t\t);\n\t\t\treturn body ? { body, contentType } : null;\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\tcontroller.abort();\n\t}\n}\n","export const HEADER_CAP: readonly string[] = [\n\t\"user-agent\",\n\t\"referer\",\n\t\"accept\",\n\t\"accept-language\",\n\t\"from\",\n\t\"signature-agent\",\n\t\"signature-input\",\n\t\"signature\",\n\t\"dpop\",\n\t\"sec-ch-ua\",\n\t\"sec-ch-ua-platform\",\n\t\"sec-ch-ua-mobile\",\n\t\"authorization\",\n];\n\nconst KNOWN_SCHEMES = new Map(\n\t[\n\t\t\"Basic\",\n\t\t\"Bearer\",\n\t\t\"Digest\",\n\t\t\"DPoP\",\n\t\t\"HOBA\",\n\t\t\"Mutual\",\n\t\t\"Negotiate\",\n\t\t\"NTLM\",\n\t\t\"OAuth\",\n\t\t\"PrivateToken\",\n\t\t\"SCRAM-SHA-1\",\n\t\t\"SCRAM-SHA-256\",\n\t\t\"vapid\",\n\t\t\"AWS4-HMAC-SHA256\",\n\t].map((scheme) => [scheme.toLowerCase(), scheme]),\n);\n\n/** The canonical name of a known Authorization scheme, kept only when a credential follows it. */\nexport function authorizationScheme(value: string): string | null {\n\tconst parts = value.trim().split(/\\s+/);\n\tif (parts.length < 2) {\n\t\treturn null;\n\t}\n\treturn KNOWN_SCHEMES.get((parts[0] ?? \"\").toLowerCase()) ?? null;\n}\n\nexport function readCappedHeaders(headers: Headers): Record<string, string> {\n\tconst out: Record<string, string> = {};\n\tfor (const name of HEADER_CAP) {\n\t\tconst value = headers.get(name);\n\t\tif (value === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (name === \"authorization\") {\n\t\t\tconst scheme = authorizationScheme(value);\n\t\t\tif (scheme !== null) {\n\t\t\t\tout[name] = scheme;\n\t\t\t}\n\t\t} else {\n\t\t\tout[name] = value;\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Cloudflare appends the real IP to a client-sent `X-Forwarded-For`, so `CF-Connecting-IP` comes first. */\nexport function clientIp(headers: Headers): string | null {\n\tconst forwarded = headers.get(\"x-forwarded-for\");\n\tconst candidate =\n\t\theaders.get(\"cf-connecting-ip\") ??\n\t\t(forwarded !== null ? forwarded.split(\",\")[0] : headers.get(\"x-real-ip\"));\n\tconst ip = candidate?.trim().toLowerCase();\n\treturn ip ? ip : null;\n}\n","import { settings } from \"./settings\";\n\nexport type Outcome = { status: number } | { failure: \"timeout\" | \"network\" };\n\n/** One fetch bounded by the timeout. Never rejects. */\nexport async function fetchWithTimeout(\n\turl: string,\n\tinit: RequestInit,\n\ttimeoutMs: number = settings.requestTimeoutMs,\n): Promise<Outcome> {\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\ttry {\n\t\tconst response = await fetch(url, { ...init, signal: controller.signal });\n\t\tawait response.body?.cancel().catch(() => {});\n\t\treturn { status: response.status };\n\t} catch {\n\t\treturn { failure: controller.signal.aborted ? \"timeout\" : \"network\" };\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\nexport const delay = (ms: number) =>\n\tnew Promise<void>((resolve) => setTimeout(resolve, ms));\n","import { delay, fetchWithTimeout, type Outcome } from \"./http\";\nimport { settings } from \"./settings\";\n\nconst isSuccess = (outcome: Outcome) =>\n\t\"status\" in outcome && outcome.status >= 200 && outcome.status < 300;\n\nconst isRetryable = (outcome: Outcome) =>\n\t\"failure\" in outcome || outcome.status === 429 || outcome.status >= 500;\n\nconst describe = (outcome: Outcome) =>\n\t\"failure\" in outcome ? outcome.failure : `status ${outcome.status}`;\n\nexport type PostPolicy = { timeoutMs: number; retry: boolean };\n\nconst post = (url: string, body: string, timeoutMs: number) =>\n\tfetchWithTimeout(\n\t\turl,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody,\n\t\t},\n\t\ttimeoutMs,\n\t);\n\n/** Posts once, retries once on a timeout, network error, 429 or 5xx when the policy allows it, and never rejects. */\nexport async function postEvent(\n\turl: string,\n\tbody: string,\n\tpolicy: PostPolicy = { timeoutMs: settings.requestTimeoutMs, retry: true },\n): Promise<boolean> {\n\tlet outcome = await post(url, body, policy.timeoutMs);\n\tif (policy.retry && !isSuccess(outcome) && isRetryable(outcome)) {\n\t\tawait delay(settings.retryDelayMs);\n\t\toutcome = await post(url, body, policy.timeoutMs);\n\t}\n\tif (isSuccess(outcome)) {\n\t\treturn true;\n\t}\n\tconsole.warn(\n\t\t`[@waniwani/relay] Could not record an agent visit (${describe(outcome)}). The event was dropped.`,\n\t);\n\treturn false;\n}\n","export type RelayKey = { ingestToken: string; orgKey: string };\n\nconst KEY = /^wwt_([A-Za-z0-9_-]+)\\.([A-Za-z0-9_-]+={0,2})$/;\n\nlet warnedMalformed = false;\n\nexport function readKey(explicit: string | undefined): RelayKey | null {\n\tconst raw = explicit ?? readEnv(\"WANIWANI_AGENT_TRAFFIC_KEY\");\n\tif (raw === undefined || raw.trim() === \"\") {\n\t\treturn null;\n\t}\n\tconst match = KEY.exec(raw.trim());\n\tif (!match?.[1] || !match[2]) {\n\t\tif (!warnedMalformed) {\n\t\t\twarnedMalformed = true;\n\t\t\tconsole.warn(\n\t\t\t\t\"[@waniwani/relay] WANIWANI_AGENT_TRAFFIC_KEY is malformed, so no agent visits are recorded. Copy the key again from the WaniWani setup screen.\",\n\t\t\t);\n\t\t}\n\t\treturn null;\n\t}\n\treturn { ingestToken: match[1], orgKey: match[2] };\n}\n\nfunction readEnv(name: string): string | undefined {\n\ttry {\n\t\treturn typeof process === \"undefined\" ? undefined : process.env[name];\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n","import { BlockList } from \"node:net\";\nimport agents from \"./agents.generated.json\";\nimport ranges from \"./ip-ranges.generated.json\";\n\nexport type DecideInput = {\n\theaders: Record<string, string>;\n\tip: string | null;\n\tuseBundled?: boolean;\n};\n\nexport type MatchRules = {\n\ttokens: string[];\n\tipRanges: Array<{ ipv4: string[]; ipv6: string[] }>;\n};\n\nconst MAX_SCANNED_USER_AGENT = 1024;\nconst PREFIX = 3;\n\ntype CompiledRules = {\n\ttokensByPrefix: Map<string, string[]>;\n\tagentRanges: BlockList;\n};\n\nfunction compile(rules: MatchRules): CompiledRules {\n\tconst tokensByPrefix = new Map<string, string[]>();\n\tfor (const token of rules.tokens) {\n\t\tconst key = token.slice(0, PREFIX);\n\t\tconst list = tokensByPrefix.get(key);\n\t\tif (list) {\n\t\t\tlist.push(token);\n\t\t} else {\n\t\t\ttokensByPrefix.set(key, [token]);\n\t\t}\n\t}\n\n\tconst agentRanges = new BlockList();\n\tconst addRanges = (cidrs: string[], type: \"ipv4\" | \"ipv6\") => {\n\t\tfor (const cidr of cidrs) {\n\t\t\tconst [address = \"\", prefix, extra] = cidr.split(\"/\");\n\t\t\tif (!prefix || extra !== undefined) {\n\t\t\t\tthrow new Error(`malformed range ${cidr}`);\n\t\t\t}\n\t\t\tagentRanges.addSubnet(address, Number(prefix), type);\n\t\t}\n\t};\n\tfor (const source of rules.ipRanges) {\n\t\taddRanges(source.ipv4, \"ipv4\");\n\t\taddRanges(source.ipv6, \"ipv6\");\n\t}\n\n\treturn { tokensByPrefix, agentRanges };\n}\n\nconst bundled = compile({ tokens: agents.tokens, ipRanges: ranges.sources });\nlet remote: CompiledRules | null = null;\n\n/** Sets the remote match rules. Throws, and keeps the current ones, when a range is malformed. */\nexport function applyRules(rules: MatchRules): void {\n\tremote = compile(rules);\n}\n\nconst pick = (useBundled: boolean): CompiledRules =>\n\tuseBundled ? bundled : (remote ?? bundled);\n\nexport const isAgentIp = (ip: string, useBundled = false): boolean =>\n\tpick(useBundled).agentRanges.check(ip, ip.includes(\":\") ? \"ipv6\" : \"ipv4\");\n\nexport function hasAgentToken(userAgent: string, useBundled = false): boolean {\n\tconst { tokensByPrefix } = pick(useBundled);\n\tconst text = userAgent.slice(0, MAX_SCANNED_USER_AGENT).toLowerCase();\n\tfor (let i = 0; i + PREFIX <= text.length; i++) {\n\t\tconst candidates = tokensByPrefix.get(text.slice(i, i + PREFIX));\n\t\tif (candidates) {\n\t\t\tfor (const token of candidates) {\n\t\t\t\tif (text.startsWith(token, i)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nexport function decide({\n\theaders,\n\tip,\n\tuseBundled = false,\n}: DecideInput): boolean {\n\tconst userAgent = headers[\"user-agent\"];\n\treturn (\n\t\t(userAgent !== undefined && hasAgentToken(userAgent, useBundled)) ||\n\t\theaders[\"signature-agent\"] !== undefined ||\n\t\theaders.dpop !== undefined ||\n\t\theaders.authorization?.toLowerCase() === \"dpop\" ||\n\t\t(ip !== null && isAgentIp(ip, useBundled))\n\t);\n}\n","{\n\t\"source\": \"Generated by scripts/build-rules.ts from the WaniWani rules file 7f37359b624880d0, do not edit. Each token is matched case-insensitively inside the User-Agent header.\",\n\t\"tokens\": [\n\t\t\"addsearchbot\",\n\t\t\"agentdatabot\",\n\t\t\"agenttimes\",\n\t\t\"ai2bot\",\n\t\t\"ai2bot-deepresearcheval\",\n\t\t\"ai2bot-dolma\",\n\t\t\"aihitbot\",\n\t\t\"aiwebindex\",\n\t\t\"allenai.org\",\n\t\t\"amazon-kendra\",\n\t\t\"amazon-qbusiness\",\n\t\t\"amazonbot\",\n\t\t\"amazonbuyforme\",\n\t\t\"amzn-searchbot\",\n\t\t\"amzn-user\",\n\t\t\"andibot\",\n\t\t\"anthropic-ai\",\n\t\t\"apifybot\",\n\t\t\"apifywebsitecontentcrawler\",\n\t\t\"applebot\",\n\t\t\"aranet-searchbot\",\n\t\t\"atlassian-bot\",\n\t\t\"awario\",\n\t\t\"azureai-searchbot\",\n\t\t\"bedrockbot\",\n\t\t\"bigsur.ai\",\n\t\t\"bixelbot\",\n\t\t\"bravebot\",\n\t\t\"brightbot\",\n\t\t\"buddybot\",\n\t\t\"bytespider\",\n\t\t\"ccbot\",\n\t\t\"channel3bot\",\n\t\t\"chatglm-spider\",\n\t\t\"chatgpt agent\",\n\t\t\"chatgpt-user\",\n\t\t\"claude-code\",\n\t\t\"claude-searchbot\",\n\t\t\"claude-user\",\n\t\t\"claude-web\",\n\t\t\"claudebot\",\n\t\t\"cloudflare-autorag\",\n\t\t\"cloudflarebrowserrenderingcrawler\",\n\t\t\"cloudvertexbot\",\n\t\t\"cohere-ai\",\n\t\t\"cohere-training-data-crawler\",\n\t\t\"cotoyogi\",\n\t\t\"coveobot\",\n\t\t\"cragcrawler\",\n\t\t\"crawl4ai\",\n\t\t\"crawlspace\",\n\t\t\"cursor/1.0.0\",\n\t\t\"datenbank crawler\",\n\t\t\"deepseekbot\",\n\t\t\"diffbot\",\n\t\t\"diffbot-user\",\n\t\t\"doubaobot\",\n\t\t\"duckassistbot\",\n\t\t\"echobot bot\",\n\t\t\"echoboxbot\",\n\t\t\"erniebot\",\n\t\t\"exabot\",\n\t\t\"exasearchbot\",\n\t\t\"exauser\",\n\t\t\"facebookbot\",\n\t\t\"factset_spyderbot\",\n\t\t\"firecrawlagent\",\n\t\t\"friendlycrawler\",\n\t\t\"geisthaus-pagefetcher\",\n\t\t\"gemini-deep-research\",\n\t\t\"google-ads-creatives-assistant\",\n\t\t\"google-agent\",\n\t\t\"google-cloudvertexbot\",\n\t\t\"google-firebase\",\n\t\t\"google-gemini-cli\",\n\t\t\"google-gemininotebook\",\n\t\t\"google-lens\",\n\t\t\"google-notebooklm\",\n\t\t\"googleagent-mariner\",\n\t\t\"googleagent-urlcontext\",\n\t\t\"googleother\",\n\t\t\"googleother-image\",\n\t\t\"googleother-video\",\n\t\t\"gptbot\",\n\t\t\"grove/1.0 rss feed service\",\n\t\t\"henkbot\",\n\t\t\"iaskbot\",\n\t\t\"iaskspider\",\n\t\t\"icc-crawler\",\n\t\t\"imagesiftbot\",\n\t\t\"imagespider\",\n\t\t\"img2dataset\",\n\t\t\"isscyberriskcrawler\",\n\t\t\"jackjillcareersbot\",\n\t\t\"kagi-fetcher\",\n\t\t\"kangaroo bot\",\n\t\t\"keenablebot\",\n\t\t\"kernel searchbot\",\n\t\t\"kimi-agent\",\n\t\t\"kimi-searchbot\",\n\t\t\"kimi-user\",\n\t\t\"kimibot\",\n\t\t\"klaviyoaibot\",\n\t\t\"kunatocrawler\",\n\t\t\"laion-huggingface-processor\",\n\t\t\"laiondownloader\",\n\t\t\"lightpanda\",\n\t\t\"linerbot\",\n\t\t\"linkupbot\",\n\t\t\"manus-user\",\n\t\t\"mediavinebot/\",\n\t\t\"meta-externalagent\",\n\t\t\"meta-externalfetcher\",\n\t\t\"meta-webindexer\",\n\t\t\"mistralai-index\",\n\t\t\"mistralai-training\",\n\t\t\"mistralai-user\",\n\t\t\"momentic\",\n\t\t\"mozilla-tabstack\",\n\t\t\"mycentralaiscraperbot\",\n\t\t\"nagetbot\",\n\t\t\"netestate imprint crawler\",\n\t\t\"notebooklm\",\n\t\t\"novaact\",\n\t\t\"oai-adsbot\",\n\t\t\"oai-searchbot\",\n\t\t\"omgili\",\n\t\t\"omgilibot\",\n\t\t\"pangubot\",\n\t\t\"panscient\",\n\t\t\"perplexity-user\",\n\t\t\"perplexitybot\",\n\t\t\"petalbot\",\n\t\t\"phindbot\",\n\t\t\"poggio-citations\",\n\t\t\"poseidon research crawler\",\n\t\t\"publogic crawler\",\n\t\t\"qodercli\",\n\t\t\"qualifiedbot\",\n\t\t\"querit-searchbot\",\n\t\t\"queritbot\",\n\t\t\"quillbot\",\n\t\t\"qwenbot\",\n\t\t\"reflectionbot\",\n\t\t\"ryebot\",\n\t\t\"sbintuitionsbot\",\n\t\t\"seltzsitemapfetcher\",\n\t\t\"semanticscholarbot\",\n\t\t\"semrushbot-ocob\",\n\t\t\"semrushbot-swa\",\n\t\t\"shap-user\",\n\t\t\"shapbot\",\n\t\t\"sidetrade indexer bot\",\n\t\t\"tavilybot\",\n\t\t\"terra cotta\",\n\t\t\"terracotta\",\n\t\t\"thinkbot\",\n\t\t\"tiktokspider\",\n\t\t\"timpibot\",\n\t\t\"tongyibot\",\n\t\t\"twilioknowledge\",\n\t\t\"twinagent\",\n\t\t\"v0bot\",\n\t\t\"velenpublicwebcrawler\",\n\t\t\"wardbot\",\n\t\t\"wrtnbot\",\n\t\t\"yandexadditional\",\n\t\t\"yandexadditionalbot\",\n\t\t\"yiyanbot\",\n\t\t\"youbot\",\n\t\t\"zanistabot\"\n\t]\n}\n","{\n\t\"sources\": [\n\t\t{\n\t\t\t\"bot\": \"GPTBot\",\n\t\t\t\"url\": \"https://openai.com/gptbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.227.36.0/25\",\n\t\t\t\t\"20.125.66.80/28\",\n\t\t\t\t\"20.171.206.0/24\",\n\t\t\t\t\"20.171.207.0/24\",\n\t\t\t\t\"52.230.152.0/24\",\n\t\t\t\t\"74.7.175.128/25\",\n\t\t\t\t\"74.7.227.0/25\",\n\t\t\t\t\"74.7.227.128/25\",\n\t\t\t\t\"74.7.228.0/25\",\n\t\t\t\t\"74.7.230.0/25\",\n\t\t\t\t\"74.7.241.0/25\",\n\t\t\t\t\"74.7.241.128/25\",\n\t\t\t\t\"74.7.242.0/25\",\n\t\t\t\t\"74.7.243.128/25\",\n\t\t\t\t\"74.7.244.0/25\",\n\t\t\t\t\"132.196.86.0/24\",\n\t\t\t\t\"172.182.202.0/25\",\n\t\t\t\t\"172.182.207.0/25\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"OAI-SearchBot\",\n\t\t\t\"url\": \"https://openai.com/searchbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.227.36.0/25\",\n\t\t\t\t\"13.66.216.176/28\",\n\t\t\t\t\"20.14.99.96/28\",\n\t\t\t\t\"20.25.151.224/28\",\n\t\t\t\t\"20.42.10.176/28\",\n\t\t\t\t\"20.168.18.32/28\",\n\t\t\t\t\"20.169.6.224/28\",\n\t\t\t\t\"20.169.7.48/28\",\n\t\t\t\t\"20.169.77.0/25\",\n\t\t\t\t\"20.171.53.224/28\",\n\t\t\t\t\"20.171.123.64/28\",\n\t\t\t\t\"23.102.145.48/28\",\n\t\t\t\t\"40.67.175.0/25\",\n\t\t\t\t\"40.90.214.16/28\",\n\t\t\t\t\"51.8.102.0/24\",\n\t\t\t\t\"74.7.175.128/25\",\n\t\t\t\t\"74.7.228.0/25\",\n\t\t\t\t\"74.7.228.128/25\",\n\t\t\t\t\"74.7.229.0/25\",\n\t\t\t\t\"74.7.229.128/25\",\n\t\t\t\t\"74.7.230.0/25\",\n\t\t\t\t\"74.7.241.128/25\",\n\t\t\t\t\"74.7.242.128/25\",\n\t\t\t\t\"74.7.243.0/25\",\n\t\t\t\t\"74.7.244.0/25\",\n\t\t\t\t\"104.210.140.128/28\",\n\t\t\t\t\"135.234.64.0/24\",\n\t\t\t\t\"172.182.193.80/28\",\n\t\t\t\t\"172.182.193.224/28\",\n\t\t\t\t\"172.182.194.32/28\",\n\t\t\t\t\"172.182.194.144/28\",\n\t\t\t\t\"172.182.195.48/28\",\n\t\t\t\t\"172.182.209.208/28\",\n\t\t\t\t\"172.182.211.192/28\",\n\t\t\t\t\"172.182.213.192/28\",\n\t\t\t\t\"172.182.224.0/28\",\n\t\t\t\t\"172.203.190.80/28\",\n\t\t\t\t\"172.203.190.112/28\",\n\t\t\t\t\"172.203.190.128/28\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"ChatGPT-User\",\n\t\t\t\"url\": \"https://openai.com/chatgpt-user.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.151.71.176/28\",\n\t\t\t\t\"4.151.119.48/28\",\n\t\t\t\t\"4.189.118.208/28\",\n\t\t\t\t\"4.189.119.48/28\",\n\t\t\t\t\"4.197.22.112/28\",\n\t\t\t\t\"4.197.64.0/28\",\n\t\t\t\t\"4.197.64.16/28\",\n\t\t\t\t\"4.197.64.48/28\",\n\t\t\t\t\"4.197.64.64/28\",\n\t\t\t\t\"4.197.115.112/28\",\n\t\t\t\t\"4.201.232.64/28\",\n\t\t\t\t\"4.205.128.176/28\",\n\t\t\t\t\"4.218.24.64/28\",\n\t\t\t\t\"4.226.200.16/28\",\n\t\t\t\t\"4.226.226.32/28\",\n\t\t\t\t\"9.129.0.0/16\",\n\t\t\t\t\"9.160.34.144/28\",\n\t\t\t\t\"9.160.36.16/28\",\n\t\t\t\t\"9.160.96.16/28\",\n\t\t\t\t\"9.160.128.16/28\",\n\t\t\t\t\"9.160.128.64/28\",\n\t\t\t\t\"9.160.163.128/28\",\n\t\t\t\t\"9.163.101.48/28\",\n\t\t\t\t\"9.205.8.48/28\",\n\t\t\t\t\"9.205.8.64/28\",\n\t\t\t\t\"9.205.25.128/28\",\n\t\t\t\t\"9.205.30.128/28\",\n\t\t\t\t\"9.205.30.144/28\",\n\t\t\t\t\"9.205.30.176/28\",\n\t\t\t\t\"9.205.128.32/28\",\n\t\t\t\t\"9.205.128.48/28\",\n\t\t\t\t\"9.223.181.208/28\",\n\t\t\t\t\"9.234.96.192/28\",\n\t\t\t\t\"9.234.97.96/28\",\n\t\t\t\t\"13.65.138.112/28\",\n\t\t\t\t\"13.67.72.16/28\",\n\t\t\t\t\"13.71.2.208/28\",\n\t\t\t\t\"13.76.32.208/28\",\n\t\t\t\t\"13.76.116.80/28\",\n\t\t\t\t\"13.83.167.128/28\",\n\t\t\t\t\"13.83.237.176/28\",\n\t\t\t\t\"20.45.178.144/28\",\n\t\t\t\t\"20.55.229.144/28\",\n\t\t\t\t\"20.57.199.192/28\",\n\t\t\t\t\"20.63.180.96/28\",\n\t\t\t\t\"20.63.221.64/28\",\n\t\t\t\t\"20.78.130.48/28\",\n\t\t\t\t\"20.83.243.176/28\",\n\t\t\t\t\"20.98.18.80/28\",\n\t\t\t\t\"20.102.212.144/28\",\n\t\t\t\t\"20.113.211.112/28\",\n\t\t\t\t\"20.113.225.112/28\",\n\t\t\t\t\"20.125.112.224/28\",\n\t\t\t\t\"20.125.144.144/28\",\n\t\t\t\t\"20.161.75.208/28\",\n\t\t\t\t\"20.168.7.192/28\",\n\t\t\t\t\"20.169.73.32/28\",\n\t\t\t\t\"20.169.73.64/28\",\n\t\t\t\t\"20.169.78.48/28\",\n\t\t\t\t\"20.169.78.64/28\",\n\t\t\t\t\"20.169.78.128/28\",\n\t\t\t\t\"20.169.78.160/28\",\n\t\t\t\t\"20.169.78.192/28\",\n\t\t\t\t\"20.169.86.224/28\",\n\t\t\t\t\"20.169.86.240/28\",\n\t\t\t\t\"20.170.184.16/28\",\n\t\t\t\t\"20.170.184.32/28\",\n\t\t\t\t\"20.170.184.48/28\",\n\t\t\t\t\"20.170.184.64/28\",\n\t\t\t\t\"20.170.184.80/28\",\n\t\t\t\t\"20.172.29.32/28\",\n\t\t\t\t\"20.199.211.160/28\",\n\t\t\t\t\"20.199.242.0/28\",\n\t\t\t\t\"20.200.212.240/28\",\n\t\t\t\t\"20.206.101.192/28\",\n\t\t\t\t\"20.210.211.192/28\",\n\t\t\t\t\"20.212.62.208/28\",\n\t\t\t\t\"20.215.187.208/28\",\n\t\t\t\t\"20.215.219.128/28\",\n\t\t\t\t\"20.215.219.160/28\",\n\t\t\t\t\"20.215.219.208/28\",\n\t\t\t\t\"20.218.30.240/28\",\n\t\t\t\t\"20.219.71.192/28\",\n\t\t\t\t\"20.219.161.192/28\",\n\t\t\t\t\"20.219.184.96/28\",\n\t\t\t\t\"20.222.36.192/28\",\n\t\t\t\t\"20.227.140.32/28\",\n\t\t\t\t\"20.228.106.176/28\",\n\t\t\t\t\"20.235.87.224/28\",\n\t\t\t\t\"20.249.63.208/28\",\n\t\t\t\t\"20.250.6.128/28\",\n\t\t\t\t\"20.250.136.64/28\",\n\t\t\t\t\"20.254.201.208/28\",\n\t\t\t\t\"23.98.142.176/28\",\n\t\t\t\t\"23.98.186.64/28\",\n\t\t\t\t\"23.98.186.96/28\",\n\t\t\t\t\"23.98.186.176/28\",\n\t\t\t\t\"23.98.186.192/28\",\n\t\t\t\t\"23.101.217.176/28\",\n\t\t\t\t\"23.102.140.144/28\",\n\t\t\t\t\"23.102.141.32/28\",\n\t\t\t\t\"40.74.200.208/28\",\n\t\t\t\t\"40.81.67.96/28\",\n\t\t\t\t\"40.81.234.144/28\",\n\t\t\t\t\"40.84.221.208/28\",\n\t\t\t\t\"40.84.221.224/28\",\n\t\t\t\t\"40.116.73.208/28\",\n\t\t\t\t\"40.119.36.240/28\",\n\t\t\t\t\"40.124.161.0/28\",\n\t\t\t\t\"48.221.40.176/28\",\n\t\t\t\t\"48.221.184.80/28\",\n\t\t\t\t\"48.221.184.96/28\",\n\t\t\t\t\"51.8.155.48/28\",\n\t\t\t\t\"51.8.155.112/28\",\n\t\t\t\t\"51.57.0.96/28\",\n\t\t\t\t\"51.57.158.96/28\",\n\t\t\t\t\"51.57.158.112/28\",\n\t\t\t\t\"51.59.24.64/28\",\n\t\t\t\t\"51.59.24.80/28\",\n\t\t\t\t\"51.59.48.80/28\",\n\t\t\t\t\"51.116.2.80/28\",\n\t\t\t\t\"51.116.221.96/28\",\n\t\t\t\t\"52.148.129.32/28\",\n\t\t\t\t\"52.156.77.144/28\",\n\t\t\t\t\"52.159.227.32/28\",\n\t\t\t\t\"52.159.249.96/28\",\n\t\t\t\t\"52.161.49.96/28\",\n\t\t\t\t\"52.165.212.48/28\",\n\t\t\t\t\"52.172.129.160/28\",\n\t\t\t\t\"52.173.219.96/28\",\n\t\t\t\t\"52.173.219.112/28\",\n\t\t\t\t\"52.173.221.16/28\",\n\t\t\t\t\"52.173.221.176/28\",\n\t\t\t\t\"52.173.221.208/28\",\n\t\t\t\t\"52.173.234.16/28\",\n\t\t\t\t\"52.173.234.80/28\",\n\t\t\t\t\"52.183.217.240/28\",\n\t\t\t\t\"52.190.137.16/28\",\n\t\t\t\t\"52.190.137.144/28\",\n\t\t\t\t\"52.190.139.48/28\",\n\t\t\t\t\"52.190.142.64/28\",\n\t\t\t\t\"52.190.190.16/28\",\n\t\t\t\t\"52.190.251.112/28\",\n\t\t\t\t\"52.225.75.208/28\",\n\t\t\t\t\"52.231.30.48/28\",\n\t\t\t\t\"52.231.34.176/28\",\n\t\t\t\t\"52.231.39.144/28\",\n\t\t\t\t\"52.231.50.64/28\",\n\t\t\t\t\"52.236.94.144/28\",\n\t\t\t\t\"52.241.146.208/28\",\n\t\t\t\t\"52.242.132.224/28\",\n\t\t\t\t\"52.242.132.240/28\",\n\t\t\t\t\"52.255.109.80/28\",\n\t\t\t\t\"52.255.109.96/28\",\n\t\t\t\t\"52.255.109.144/28\",\n\t\t\t\t\"52.255.111.0/28\",\n\t\t\t\t\"52.255.111.80/28\",\n\t\t\t\t\"57.154.174.112/28\",\n\t\t\t\t\"57.154.175.0/28\",\n\t\t\t\t\"57.154.187.32/28\",\n\t\t\t\t\"68.154.28.96/28\",\n\t\t\t\t\"68.220.57.64/28\",\n\t\t\t\t\"70.153.32.16/28\",\n\t\t\t\t\"70.153.32.32/28\",\n\t\t\t\t\"70.153.87.224/28\",\n\t\t\t\t\"70.153.189.192/28\",\n\t\t\t\t\"70.156.152.96/28\",\n\t\t\t\t\"74.7.35.48/28\",\n\t\t\t\t\"74.7.35.112/28\",\n\t\t\t\t\"74.7.36.64/28\",\n\t\t\t\t\"74.7.36.80/28\",\n\t\t\t\t\"74.7.36.96/28\",\n\t\t\t\t\"74.161.200.96/28\",\n\t\t\t\t\"74.224.217.64/28\",\n\t\t\t\t\"74.226.253.160/28\",\n\t\t\t\t\"74.248.37.160/28\",\n\t\t\t\t\"85.211.128.16/28\",\n\t\t\t\t\"85.211.128.32/28\",\n\t\t\t\t\"104.208.184.192/28\",\n\t\t\t\t\"104.210.139.192/28\",\n\t\t\t\t\"104.210.139.224/28\",\n\t\t\t\t\"128.85.198.32/28\",\n\t\t\t\t\"132.196.82.48/28\",\n\t\t\t\t\"134.33.102.192/28\",\n\t\t\t\t\"134.138.52.16/28\",\n\t\t\t\t\"134.138.52.64/28\",\n\t\t\t\t\"134.138.52.96/28\",\n\t\t\t\t\"134.138.52.128/28\",\n\t\t\t\t\"134.138.57.64/28\",\n\t\t\t\t\"134.138.57.80/28\",\n\t\t\t\t\"134.138.57.176/28\",\n\t\t\t\t\"134.138.58.32/28\",\n\t\t\t\t\"134.149.233.80/28\",\n\t\t\t\t\"135.13.64.240/28\",\n\t\t\t\t\"135.116.136.160/28\",\n\t\t\t\t\"135.220.73.208/28\",\n\t\t\t\t\"135.220.73.240/28\",\n\t\t\t\t\"135.222.99.32/28\",\n\t\t\t\t\"135.237.131.208/28\",\n\t\t\t\t\"135.237.133.48/28\",\n\t\t\t\t\"137.135.191.176/28\",\n\t\t\t\t\"138.91.46.96/28\",\n\t\t\t\t\"145.132.136.96/28\",\n\t\t\t\t\"158.158.5.32/28\",\n\t\t\t\t\"168.63.252.240/28\",\n\t\t\t\t\"172.162.248.64/28\",\n\t\t\t\t\"172.170.1.80/28\",\n\t\t\t\t\"172.170.225.0/28\",\n\t\t\t\t\"172.170.241.80/28\",\n\t\t\t\t\"172.172.206.48/28\",\n\t\t\t\t\"172.175.152.224/28\",\n\t\t\t\t\"172.178.140.144/28\",\n\t\t\t\t\"172.178.141.112/28\",\n\t\t\t\t\"172.178.141.128/28\",\n\t\t\t\t\"172.183.143.224/28\",\n\t\t\t\t\"172.183.222.128/28\",\n\t\t\t\t\"172.192.112.208/28\",\n\t\t\t\t\"172.197.160.192/28\",\n\t\t\t\t\"172.197.203.16/28\",\n\t\t\t\t\"172.198.58.176/28\",\n\t\t\t\t\"172.198.78.192/28\",\n\t\t\t\t\"172.198.79.112/28\",\n\t\t\t\t\"172.198.161.128/28\",\n\t\t\t\t\"172.199.137.80/28\",\n\t\t\t\t\"172.204.28.224/28\",\n\t\t\t\t\"172.204.96.80/28\",\n\t\t\t\t\"172.205.189.192/28\",\n\t\t\t\t\"172.206.38.240/28\",\n\t\t\t\t\"172.207.1.32/28\",\n\t\t\t\t\"172.212.172.160/28\",\n\t\t\t\t\"172.215.215.32/28\",\n\t\t\t\t\"191.232.238.96/28\",\n\t\t\t\t\"191.233.197.0/28\",\n\t\t\t\t\"191.233.199.160/28\",\n\t\t\t\t\"191.234.167.144/28\",\n\t\t\t\t\"191.235.99.160/28\",\n\t\t\t\t\"191.237.249.64/28\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"ClaudeBot\",\n\t\t\t\"url\": \"https://claude.com/crawling/bots.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"34.152.124.140/32\",\n\t\t\t\t\"34.153.21.190/32\",\n\t\t\t\t\"34.162.27.34/32\",\n\t\t\t\t\"34.162.86.102/32\",\n\t\t\t\t\"34.162.90.102/32\",\n\t\t\t\t\"34.162.111.78/32\",\n\t\t\t\t\"34.162.153.71/32\",\n\t\t\t\t\"34.162.191.81/32\",\n\t\t\t\t\"34.162.230.222/32\",\n\t\t\t\t\"34.162.244.71/32\",\n\t\t\t\t\"34.186.252.4/32\",\n\t\t\t\t\"136.79.143.251/32\",\n\t\t\t\t\"136.83.20.215/32\",\n\t\t\t\t\"216.73.216.0/22\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"PerplexityBot\",\n\t\t\t\"url\": \"https://www.perplexity.com/perplexitybot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"3.222.232.239/32\",\n\t\t\t\t\"3.224.62.45/32\",\n\t\t\t\t\"18.97.1.228/32\",\n\t\t\t\t\"18.97.9.96/32\",\n\t\t\t\t\"18.97.9.97/32\",\n\t\t\t\t\"18.97.9.98/32\",\n\t\t\t\t\"18.97.9.99/32\",\n\t\t\t\t\"18.97.9.100/32\",\n\t\t\t\t\"18.97.9.101/32\",\n\t\t\t\t\"18.97.9.102/32\",\n\t\t\t\t\"18.97.9.103/32\",\n\t\t\t\t\"18.210.92.235/32\",\n\t\t\t\t\"107.20.236.150/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"Perplexity-User\",\n\t\t\t\"url\": \"https://www.perplexity.com/perplexity-user.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"18.97.43.80/32\",\n\t\t\t\t\"18.97.43.81/32\",\n\t\t\t\t\"18.97.43.82/32\",\n\t\t\t\t\"18.97.43.83/32\",\n\t\t\t\t\"18.97.43.84/32\",\n\t\t\t\t\"18.97.43.85/32\",\n\t\t\t\t\"18.97.43.86/32\",\n\t\t\t\t\"18.97.43.87/32\",\n\t\t\t\t\"34.193.163.52/32\",\n\t\t\t\t\"44.208.221.197/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"MistralAI-User\",\n\t\t\t\"url\": \"https://mistral.ai/mistralai-user-ips.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"20.240.160.1/32\",\n\t\t\t\t\"20.240.160.161/32\",\n\t\t\t\t\"20.240.194.83/32\",\n\t\t\t\t\"51.12.243.114/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"CCBot\",\n\t\t\t\"url\": \"https://index.commoncrawl.org/ccbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"3.41.188.32/29\",\n\t\t\t\t\"18.97.9.168/29\",\n\t\t\t\t\"18.97.14.80/29\",\n\t\t\t\t\"18.97.14.88/30\"\n\t\t\t],\n\t\t\t\"ipv6\": [\n\t\t\t\t\"2600:1f28:365:8000::/56\"\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"DuckAssistBot\",\n\t\t\t\"url\": \"https://duckduckgo.com/duckassistbot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"4.144.148.23/32\",\n\t\t\t\t\"4.144.182.50/32\",\n\t\t\t\t\"4.144.227.142/32\",\n\t\t\t\t\"4.144.251.39/32\",\n\t\t\t\t\"4.149.142.7/32\",\n\t\t\t\t\"4.149.159.71/32\",\n\t\t\t\t\"4.149.159.102/32\",\n\t\t\t\t\"4.149.216.220/32\",\n\t\t\t\t\"4.150.142.218/32\",\n\t\t\t\t\"4.154.193.167/32\",\n\t\t\t\t\"4.156.30.123/32\",\n\t\t\t\t\"4.156.136.79/32\",\n\t\t\t\t\"4.156.154.107/32\",\n\t\t\t\t\"4.157.77.76/32\",\n\t\t\t\t\"4.172.49.103/32\",\n\t\t\t\t\"4.177.16.196/32\",\n\t\t\t\t\"4.182.10.198/32\",\n\t\t\t\t\"4.182.131.108/32\",\n\t\t\t\t\"4.195.133.120/32\",\n\t\t\t\t\"4.201.125.59/32\",\n\t\t\t\t\"4.201.141.71/32\",\n\t\t\t\t\"4.201.197.203/32\",\n\t\t\t\t\"4.201.206.133/32\",\n\t\t\t\t\"4.201.220.8/32\",\n\t\t\t\t\"4.207.220.92/32\",\n\t\t\t\t\"4.209.224.56/32\",\n\t\t\t\t\"4.213.46.14/32\",\n\t\t\t\t\"4.226.40.135/32\",\n\t\t\t\t\"4.228.76.163/32\",\n\t\t\t\t\"4.237.155.54/32\",\n\t\t\t\t\"4.237.244.80/32\",\n\t\t\t\t\"4.242.119.13/32\",\n\t\t\t\t\"4.246.10.252/32\",\n\t\t\t\t\"4.248.234.26/32\",\n\t\t\t\t\"4.249.216.104/32\",\n\t\t\t\t\"4.249.223.51/32\",\n\t\t\t\t\"4.249.238.11/32\",\n\t\t\t\t\"4.254.123.56/32\",\n\t\t\t\t\"4.255.35.121/32\",\n\t\t\t\t\"4.255.39.80/32\",\n\t\t\t\t\"13.86.35.212/32\",\n\t\t\t\t\"13.89.106.77/32\",\n\t\t\t\t\"13.90.178.139/32\",\n\t\t\t\t\"20.3.1.178/32\",\n\t\t\t\t\"20.3.60.212/32\",\n\t\t\t\t\"20.8.252.26/32\",\n\t\t\t\t\"20.9.114.242/32\",\n\t\t\t\t\"20.12.141.99/32\",\n\t\t\t\t\"20.12.249.123/32\",\n\t\t\t\t\"20.12.252.21/32\",\n\t\t\t\t\"20.13.44.19/32\",\n\t\t\t\t\"20.15.142.178/32\",\n\t\t\t\t\"20.23.88.231/32\",\n\t\t\t\t\"20.29.149.44/32\",\n\t\t\t\t\"20.29.154.56/32\",\n\t\t\t\t\"20.40.133.240/32\",\n\t\t\t\t\"20.40.147.172/32\",\n\t\t\t\t\"20.43.150.85/32\",\n\t\t\t\t\"20.43.150.93/32\",\n\t\t\t\t\"20.43.152.28/32\",\n\t\t\t\t\"20.43.172.120/32\",\n\t\t\t\t\"20.44.222.1/32\",\n\t\t\t\t\"20.49.129.236/32\",\n\t\t\t\t\"20.49.136.28/32\",\n\t\t\t\t\"20.50.48.159/32\",\n\t\t\t\t\"20.50.48.192/32\",\n\t\t\t\t\"20.50.49.0/32\",\n\t\t\t\t\"20.50.49.25/32\",\n\t\t\t\t\"20.50.49.40/32\",\n\t\t\t\t\"20.50.49.55/32\",\n\t\t\t\t\"20.50.49.237/32\",\n\t\t\t\t\"20.50.50.46/32\",\n\t\t\t\t\"20.50.50.118/32\",\n\t\t\t\t\"20.50.50.121/32\",\n\t\t\t\t\"20.50.50.123/32\",\n\t\t\t\t\"20.50.50.130/32\",\n\t\t\t\t\"20.50.50.134/32\",\n\t\t\t\t\"20.50.50.145/32\",\n\t\t\t\t\"20.50.50.146/32\",\n\t\t\t\t\"20.50.50.163/32\",\n\t\t\t\t\"20.50.168.91/32\",\n\t\t\t\t\"20.53.78.106/32\",\n\t\t\t\t\"20.53.78.123/32\",\n\t\t\t\t\"20.53.78.138/32\",\n\t\t\t\t\"20.53.78.144/32\",\n\t\t\t\t\"20.53.78.236/32\",\n\t\t\t\t\"20.53.91.2/32\",\n\t\t\t\t\"20.53.92.211/32\",\n\t\t\t\t\"20.53.134.160/32\",\n\t\t\t\t\"20.54.224.39/32\",\n\t\t\t\t\"20.56.197.58/32\",\n\t\t\t\t\"20.56.197.63/32\",\n\t\t\t\t\"20.59.0.65/32\",\n\t\t\t\t\"20.61.34.40/32\",\n\t\t\t\t\"20.61.142.192/32\",\n\t\t\t\t\"20.62.224.44/32\",\n\t\t\t\t\"20.64.185.45/32\",\n\t\t\t\t\"20.69.74.250/32\",\n\t\t\t\t\"20.69.129.205/32\",\n\t\t\t\t\"20.69.131.45/32\",\n\t\t\t\t\"20.69.138.149/32\",\n\t\t\t\t\"20.69.138.189/32\",\n\t\t\t\t\"20.69.144.137/32\",\n\t\t\t\t\"20.69.146.171/32\",\n\t\t\t\t\"20.69.147.21/32\",\n\t\t\t\t\"20.69.171.204/32\",\n\t\t\t\t\"20.71.12.143/32\",\n\t\t\t\t\"20.71.69.210/32\",\n\t\t\t\t\"20.72.197.110/32\",\n\t\t\t\t\"20.72.203.250/32\",\n\t\t\t\t\"20.72.218.29/32\",\n\t\t\t\t\"20.72.221.33/32\",\n\t\t\t\t\"20.72.221.45/32\",\n\t\t\t\t\"20.72.225.13/32\",\n\t\t\t\t\"20.72.242.93/32\",\n\t\t\t\t\"20.72.242.209/32\",\n\t\t\t\t\"20.73.132.240/32\",\n\t\t\t\t\"20.73.202.147/32\",\n\t\t\t\t\"20.75.144.152/32\",\n\t\t\t\t\"20.75.254.61/32\",\n\t\t\t\t\"20.77.146.108/32\",\n\t\t\t\t\"20.79.226.26/32\",\n\t\t\t\t\"20.79.238.198/32\",\n\t\t\t\t\"20.79.239.66/32\",\n\t\t\t\t\"20.80.114.203/32\",\n\t\t\t\t\"20.80.120.41/32\",\n\t\t\t\t\"20.80.129.80/32\",\n\t\t\t\t\"20.80.135.7/32\",\n\t\t\t\t\"20.80.156.88/32\",\n\t\t\t\t\"20.81.115.13/32\",\n\t\t\t\t\"20.82.24.207/32\",\n\t\t\t\t\"20.84.138.61/32\",\n\t\t\t\t\"20.84.219.45/32\",\n\t\t\t\t\"20.84.251.223/32\",\n\t\t\t\t\"20.85.138.75/32\",\n\t\t\t\t\"20.85.167.56/32\",\n\t\t\t\t\"20.85.187.146/32\",\n\t\t\t\t\"20.88.168.65/32\",\n\t\t\t\t\"20.93.28.24/32\",\n\t\t\t\t\"20.93.49.96/32\",\n\t\t\t\t\"20.99.154.113/32\",\n\t\t\t\t\"20.99.255.235/32\",\n\t\t\t\t\"20.100.136.36/32\",\n\t\t\t\t\"20.100.140.155/32\",\n\t\t\t\t\"20.101.17.173/32\",\n\t\t\t\t\"20.102.34.205/32\",\n\t\t\t\t\"20.109.129.162/32\",\n\t\t\t\t\"20.109.130.190/32\",\n\t\t\t\t\"20.109.241.18/32\",\n\t\t\t\t\"20.112.58.44/32\",\n\t\t\t\t\"20.113.3.121/32\",\n\t\t\t\t\"20.113.14.159/32\",\n\t\t\t\t\"20.115.181.187/32\",\n\t\t\t\t\"20.118.11.251/32\",\n\t\t\t\t\"20.119.127.146/32\",\n\t\t\t\t\"20.121.75.32/32\",\n\t\t\t\t\"20.166.171.150/32\",\n\t\t\t\t\"20.170.75.54/32\",\n\t\t\t\t\"20.175.232.228/32\",\n\t\t\t\t\"20.185.79.15/32\",\n\t\t\t\t\"20.185.79.47/32\",\n\t\t\t\t\"20.190.21.172/32\",\n\t\t\t\t\"20.191.44.16/32\",\n\t\t\t\t\"20.191.44.22/32\",\n\t\t\t\t\"20.191.44.119/32\",\n\t\t\t\t\"20.191.44.234/32\",\n\t\t\t\t\"20.191.45.212/32\",\n\t\t\t\t\"20.193.12.126/32\",\n\t\t\t\t\"20.193.24.10/32\",\n\t\t\t\t\"20.193.24.251/32\",\n\t\t\t\t\"20.193.25.197/32\",\n\t\t\t\t\"20.193.27.215/32\",\n\t\t\t\t\"20.193.45.113/32\",\n\t\t\t\t\"20.195.58.189/32\",\n\t\t\t\t\"20.195.108.47/32\",\n\t\t\t\t\"20.197.209.11/32\",\n\t\t\t\t\"20.197.209.27/32\",\n\t\t\t\t\"20.198.132.126/32\",\n\t\t\t\t\"20.198.152.208/32\",\n\t\t\t\t\"20.201.15.208/32\",\n\t\t\t\t\"20.204.228.200/32\",\n\t\t\t\t\"20.204.240.172/32\",\n\t\t\t\t\"20.204.241.148/32\",\n\t\t\t\t\"20.204.242.19/32\",\n\t\t\t\t\"20.204.242.101/32\",\n\t\t\t\t\"20.204.243.55/32\",\n\t\t\t\t\"20.204.246.81/32\",\n\t\t\t\t\"20.204.246.254/32\",\n\t\t\t\t\"20.207.72.11/32\",\n\t\t\t\t\"20.207.72.21/32\",\n\t\t\t\t\"20.207.72.110/32\",\n\t\t\t\t\"20.207.72.113/32\",\n\t\t\t\t\"20.207.97.190/32\",\n\t\t\t\t\"20.207.99.197/32\",\n\t\t\t\t\"20.207.107.181/32\",\n\t\t\t\t\"20.207.117.66/32\",\n\t\t\t\t\"20.212.90.107/32\",\n\t\t\t\t\"20.212.139.34/32\",\n\t\t\t\t\"20.216.200.223/32\",\n\t\t\t\t\"20.219.43.246/32\",\n\t\t\t\t\"20.219.45.67/32\",\n\t\t\t\t\"20.219.45.190/32\",\n\t\t\t\t\"20.221.10.115/32\",\n\t\t\t\t\"20.221.84.185/32\",\n\t\t\t\t\"20.226.133.105/32\",\n\t\t\t\t\"20.231.254.179/32\",\n\t\t\t\t\"20.232.51.46/32\",\n\t\t\t\t\"20.232.249.95/32\",\n\t\t\t\t\"20.236.40.93/32\",\n\t\t\t\t\"20.236.195.196/32\",\n\t\t\t\t\"20.236.198.192/32\",\n\t\t\t\t\"20.236.227.113/32\",\n\t\t\t\t\"20.236.233.23/32\",\n\t\t\t\t\"20.236.247.180/32\",\n\t\t\t\t\"20.237.1.51/32\",\n\t\t\t\t\"20.241.135.19/32\",\n\t\t\t\t\"20.241.147.151/32\",\n\t\t\t\t\"20.241.153.203/32\",\n\t\t\t\t\"20.241.164.5/32\",\n\t\t\t\t\"20.241.241.100/32\",\n\t\t\t\t\"20.242.133.53/32\",\n\t\t\t\t\"20.242.135.165/32\",\n\t\t\t\t\"20.242.211.139/32\",\n\t\t\t\t\"20.242.215.53/32\",\n\t\t\t\t\"20.242.237.240/32\",\n\t\t\t\t\"20.246.132.168/32\",\n\t\t\t\t\"20.246.225.34/32\",\n\t\t\t\t\"20.247.235.11/32\",\n\t\t\t\t\"20.250.51.113/32\",\n\t\t\t\t\"20.252.9.6/32\",\n\t\t\t\t\"20.252.16.193/32\",\n\t\t\t\t\"20.252.41.18/32\",\n\t\t\t\t\"20.252.63.162/32\",\n\t\t\t\t\"20.252.88.10/32\",\n\t\t\t\t\"20.253.59.76/32\",\n\t\t\t\t\"20.253.96.199/32\",\n\t\t\t\t\"20.253.114.51/32\",\n\t\t\t\t\"40.64.67.220/32\",\n\t\t\t\t\"40.64.78.45/32\",\n\t\t\t\t\"40.64.88.145/32\",\n\t\t\t\t\"40.64.105.247/32\",\n\t\t\t\t\"40.64.106.11/32\",\n\t\t\t\t\"40.64.106.152/32\",\n\t\t\t\t\"40.65.78.86/32\",\n\t\t\t\t\"40.76.162.17/32\",\n\t\t\t\t\"40.76.162.191/32\",\n\t\t\t\t\"40.76.162.208/32\",\n\t\t\t\t\"40.76.162.247/32\",\n\t\t\t\t\"40.76.163.7/32\",\n\t\t\t\t\"40.76.163.23/32\",\n\t\t\t\t\"40.76.173.151/32\",\n\t\t\t\t\"40.81.250.205/32\",\n\t\t\t\t\"40.82.218.203/32\",\n\t\t\t\t\"40.88.21.235/32\",\n\t\t\t\t\"40.88.195.62/32\",\n\t\t\t\t\"40.89.243.175/32\",\n\t\t\t\t\"40.114.182.45/32\",\n\t\t\t\t\"40.114.182.153/32\",\n\t\t\t\t\"40.114.182.172/32\",\n\t\t\t\t\"40.114.183.88/32\",\n\t\t\t\t\"40.114.183.196/32\",\n\t\t\t\t\"40.114.183.251/32\",\n\t\t\t\t\"40.119.232.50/32\",\n\t\t\t\t\"40.119.232.146/32\",\n\t\t\t\t\"40.119.232.215/32\",\n\t\t\t\t\"40.119.232.218/32\",\n\t\t\t\t\"40.119.232.251/32\",\n\t\t\t\t\"40.121.121.54/32\",\n\t\t\t\t\"40.127.154.196/32\",\n\t\t\t\t\"48.192.16.86/32\",\n\t\t\t\t\"48.192.74.178/32\",\n\t\t\t\t\"48.200.33.227/32\",\n\t\t\t\t\"48.202.155.200/32\",\n\t\t\t\t\"48.202.171.124/32\",\n\t\t\t\t\"48.204.32.87/32\",\n\t\t\t\t\"48.206.18.152/32\",\n\t\t\t\t\"48.217.23.236/32\",\n\t\t\t\t\"48.217.129.210/32\",\n\t\t\t\t\"48.217.212.89/32\",\n\t\t\t\t\"48.223.209.110/32\",\n\t\t\t\t\"51.8.31.28/32\",\n\t\t\t\t\"51.8.71.117/32\",\n\t\t\t\t\"51.8.253.152/32\",\n\t\t\t\t\"51.104.144.31/32\",\n\t\t\t\t\"51.104.146.225/32\",\n\t\t\t\t\"51.104.146.235/32\",\n\t\t\t\t\"51.104.160.167/32\",\n\t\t\t\t\"51.104.160.177/32\",\n\t\t\t\t\"51.104.161.32/32\",\n\t\t\t\t\"51.104.162.149/32\",\n\t\t\t\t\"51.104.163.250/32\",\n\t\t\t\t\"51.104.164.109/32\",\n\t\t\t\t\"51.104.164.147/32\",\n\t\t\t\t\"51.104.164.189/32\",\n\t\t\t\t\"51.104.164.215/32\",\n\t\t\t\t\"51.104.166.111/32\",\n\t\t\t\t\"51.104.167.19/32\",\n\t\t\t\t\"51.104.167.52/32\",\n\t\t\t\t\"51.104.167.54/32\",\n\t\t\t\t\"51.104.167.61/32\",\n\t\t\t\t\"51.104.167.71/32\",\n\t\t\t\t\"51.104.167.87/32\",\n\t\t\t\t\"51.104.167.88/32\",\n\t\t\t\t\"51.104.167.95/32\",\n\t\t\t\t\"51.104.167.96/32\",\n\t\t\t\t\"51.104.167.104/32\",\n\t\t\t\t\"51.104.167.110/32\",\n\t\t\t\t\"51.104.180.26/32\",\n\t\t\t\t\"51.104.180.47/32\",\n\t\t\t\t\"51.104.180.53/32\",\n\t\t\t\t\"51.107.40.209/32\",\n\t\t\t\t\"51.116.131.221/32\",\n\t\t\t\t\"51.120.48.122/32\",\n\t\t\t\t\"51.138.90.161/32\",\n\t\t\t\t\"51.138.90.206/32\",\n\t\t\t\t\"51.138.90.233/32\",\n\t\t\t\t\"51.143.114.45/32\",\n\t\t\t\t\"51.145.185.16/32\",\n\t\t\t\t\"52.142.24.149/32\",\n\t\t\t\t\"52.142.26.175/32\",\n\t\t\t\t\"52.143.95.162/32\",\n\t\t\t\t\"52.143.95.204/32\",\n\t\t\t\t\"52.143.241.111/32\",\n\t\t\t\t\"52.143.242.6/32\",\n\t\t\t\t\"52.143.243.117/32\",\n\t\t\t\t\"52.143.244.81/32\",\n\t\t\t\t\"52.143.247.235/32\",\n\t\t\t\t\"52.146.57.51/32\",\n\t\t\t\t\"52.146.58.236/32\",\n\t\t\t\t\"52.146.59.12/32\",\n\t\t\t\t\"52.146.59.154/32\",\n\t\t\t\t\"52.146.59.156/32\",\n\t\t\t\t\"52.146.63.80/32\",\n\t\t\t\t\"52.148.161.87/32\",\n\t\t\t\t\"52.148.165.38/32\",\n\t\t\t\t\"52.149.25.43/32\",\n\t\t\t\t\"52.149.28.18/32\",\n\t\t\t\t\"52.149.28.83/32\",\n\t\t\t\t\"52.149.30.45/32\",\n\t\t\t\t\"52.149.56.151/32\",\n\t\t\t\t\"52.149.58.27/32\",\n\t\t\t\t\"52.149.58.69/32\",\n\t\t\t\t\"52.149.58.139/32\",\n\t\t\t\t\"52.149.58.173/32\",\n\t\t\t\t\"52.149.60.38/32\",\n\t\t\t\t\"52.149.61.51/32\",\n\t\t\t\t\"52.154.60.82/32\",\n\t\t\t\t\"52.154.169.50/32\",\n\t\t\t\t\"52.154.169.200/32\",\n\t\t\t\t\"52.154.170.26/32\",\n\t\t\t\t\"52.154.170.28/32\",\n\t\t\t\t\"52.154.170.88/32\",\n\t\t\t\t\"52.154.170.96/32\",\n\t\t\t\t\"52.154.170.113/32\",\n\t\t\t\t\"52.154.170.117/32\",\n\t\t\t\t\"52.154.170.122/32\",\n\t\t\t\t\"52.154.170.209/32\",\n\t\t\t\t\"52.154.170.229/32\",\n\t\t\t\t\"52.154.170.243/32\",\n\t\t\t\t\"52.154.171.0/32\",\n\t\t\t\t\"52.154.171.44/32\",\n\t\t\t\t\"52.154.171.70/32\",\n\t\t\t\t\"52.154.171.87/32\",\n\t\t\t\t\"52.154.171.150/32\",\n\t\t\t\t\"52.154.171.196/32\",\n\t\t\t\t\"52.154.171.205/32\",\n\t\t\t\t\"52.154.171.235/32\",\n\t\t\t\t\"52.154.171.250/32\",\n\t\t\t\t\"52.154.172.2/32\",\n\t\t\t\t\"52.154.204.93/32\",\n\t\t\t\t\"52.185.93.208/32\",\n\t\t\t\t\"52.186.37.211/32\",\n\t\t\t\t\"52.188.89.106/32\",\n\t\t\t\t\"52.189.70.103/32\",\n\t\t\t\t\"52.190.37.160/32\",\n\t\t\t\t\"52.191.82.242/32\",\n\t\t\t\t\"52.191.222.158/32\",\n\t\t\t\t\"52.224.16.221/32\",\n\t\t\t\t\"52.224.16.229/32\",\n\t\t\t\t\"52.224.19.152/32\",\n\t\t\t\t\"52.224.20.174/32\",\n\t\t\t\t\"52.224.20.181/32\",\n\t\t\t\t\"52.224.20.186/32\",\n\t\t\t\t\"52.224.20.190/32\",\n\t\t\t\t\"52.224.20.193/32\",\n\t\t\t\t\"52.224.20.203/32\",\n\t\t\t\t\"52.224.20.204/32\",\n\t\t\t\t\"52.224.20.223/32\",\n\t\t\t\t\"52.224.20.227/32\",\n\t\t\t\t\"52.224.20.249/32\",\n\t\t\t\t\"52.224.21.4/32\",\n\t\t\t\t\"52.224.21.19/32\",\n\t\t\t\t\"52.224.21.20/32\",\n\t\t\t\t\"52.224.21.23/32\",\n\t\t\t\t\"52.224.21.27/32\",\n\t\t\t\t\"52.224.21.49/32\",\n\t\t\t\t\"52.224.21.51/32\",\n\t\t\t\t\"52.224.21.53/32\",\n\t\t\t\t\"52.224.21.55/32\",\n\t\t\t\t\"52.224.21.61/32\",\n\t\t\t\t\"52.224.147.21/32\",\n\t\t\t\t\"52.224.191.245/32\",\n\t\t\t\t\"52.242.224.168/32\",\n\t\t\t\t\"52.250.46.221/32\",\n\t\t\t\t\"57.151.44.140/32\",\n\t\t\t\t\"57.152.17.237/32\",\n\t\t\t\t\"57.152.62.107/32\",\n\t\t\t\t\"57.152.72.128/32\",\n\t\t\t\t\"57.155.18.44/32\",\n\t\t\t\t\"57.165.205.172/32\",\n\t\t\t\t\"57.167.88.38/32\",\n\t\t\t\t\"64.236.15.33/32\",\n\t\t\t\t\"64.236.93.100/32\",\n\t\t\t\t\"64.236.118.43/32\",\n\t\t\t\t\"68.219.152.220/32\",\n\t\t\t\t\"72.144.95.185/32\",\n\t\t\t\t\"74.163.169.106/32\",\n\t\t\t\t\"74.163.208.206/32\",\n\t\t\t\t\"74.179.200.126/32\",\n\t\t\t\t\"74.179.232.116/32\",\n\t\t\t\t\"74.242.194.48/32\",\n\t\t\t\t\"98.70.219.37/32\",\n\t\t\t\t\"104.43.54.127/32\",\n\t\t\t\t\"104.43.55.116/32\",\n\t\t\t\t\"104.43.55.117/32\",\n\t\t\t\t\"104.43.55.166/32\",\n\t\t\t\t\"104.43.55.167/32\",\n\t\t\t\t\"108.141.83.74/32\",\n\t\t\t\t\"128.203.82.154/32\",\n\t\t\t\t\"128.203.132.152/32\",\n\t\t\t\t\"128.203.181.79/32\",\n\t\t\t\t\"128.203.214.241/32\",\n\t\t\t\t\"128.203.236.110/32\",\n\t\t\t\t\"130.107.228.224/32\",\n\t\t\t\t\"131.145.39.118/32\",\n\t\t\t\t\"131.189.183.241/32\",\n\t\t\t\t\"132.164.209.198/32\",\n\t\t\t\t\"132.196.106.146/32\",\n\t\t\t\t\"132.220.1.71/32\",\n\t\t\t\t\"134.33.207.189/32\",\n\t\t\t\t\"135.171.251.169/32\",\n\t\t\t\t\"135.233.63.237/32\",\n\t\t\t\t\"135.234.221.112/32\",\n\t\t\t\t\"135.235.183.95/32\",\n\t\t\t\t\"172.168.12.93/32\",\n\t\t\t\t\"172.168.43.43/32\",\n\t\t\t\t\"172.168.51.216/32\",\n\t\t\t\t\"172.168.53.53/32\",\n\t\t\t\t\"172.168.81.217/32\",\n\t\t\t\t\"172.168.94.160/32\",\n\t\t\t\t\"172.168.115.250/32\",\n\t\t\t\t\"172.168.133.76/32\",\n\t\t\t\t\"172.168.137.102/32\",\n\t\t\t\t\"172.168.152.88/32\",\n\t\t\t\t\"172.168.173.230/32\",\n\t\t\t\t\"172.168.190.186/32\",\n\t\t\t\t\"172.168.227.120/32\",\n\t\t\t\t\"172.168.254.119/32\",\n\t\t\t\t\"172.169.7.33/32\",\n\t\t\t\t\"172.169.17.165/32\",\n\t\t\t\t\"172.169.28.184/32\",\n\t\t\t\t\"172.169.36.43/32\",\n\t\t\t\t\"172.169.53.208/32\",\n\t\t\t\t\"172.169.60.134/32\",\n\t\t\t\t\"172.169.93.148/32\",\n\t\t\t\t\"172.169.164.109/32\",\n\t\t\t\t\"172.169.177.131/32\",\n\t\t\t\t\"172.169.181.237/32\",\n\t\t\t\t\"172.171.37.221/32\",\n\t\t\t\t\"172.179.105.87/32\",\n\t\t\t\t\"172.179.128.118/32\",\n\t\t\t\t\"172.188.250.220/32\",\n\t\t\t\t\"172.189.40.18/32\",\n\t\t\t\t\"172.193.184.42/32\",\n\t\t\t\t\"172.193.197.146/32\",\n\t\t\t\t\"172.193.245.229/32\",\n\t\t\t\t\"172.194.141.49/32\",\n\t\t\t\t\"172.199.55.212/32\",\n\t\t\t\t\"172.210.111.68/32\",\n\t\t\t\t\"191.233.3.197/32\",\n\t\t\t\t\"191.233.3.202/32\",\n\t\t\t\t\"191.234.216.4/32\",\n\t\t\t\t\"191.234.216.178/32\",\n\t\t\t\t\"191.235.201.214/32\",\n\t\t\t\t\"191.235.202.38/32\",\n\t\t\t\t\"191.235.202.48/32\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"Applebot\",\n\t\t\t\"url\": \"https://search.developer.apple.com/applebot.json\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"17.166.20.0/24\",\n\t\t\t\t\"17.166.21.0/24\",\n\t\t\t\t\"17.166.22.0/24\",\n\t\t\t\t\"17.166.23.0/24\",\n\t\t\t\t\"17.166.24.0/24\",\n\t\t\t\t\"17.166.25.0/24\",\n\t\t\t\t\"17.166.150.0/24\",\n\t\t\t\t\"17.166.151.0/24\",\n\t\t\t\t\"17.166.152.0/24\",\n\t\t\t\t\"17.166.153.0/24\",\n\t\t\t\t\"17.166.154.0/24\",\n\t\t\t\t\"17.166.155.0/24\",\n\t\t\t\t\"17.166.192.48/28\",\n\t\t\t\t\"17.166.232.0/24\",\n\t\t\t\t\"17.166.233.0/24\",\n\t\t\t\t\"17.166.234.0/24\",\n\t\t\t\t\"17.166.235.0/24\",\n\t\t\t\t\"17.166.236.0/24\",\n\t\t\t\t\"17.166.237.0/24\",\n\t\t\t\t\"17.166.240.48/28\",\n\t\t\t\t\"17.166.248.48/28\",\n\t\t\t\t\"17.241.193.160/27\",\n\t\t\t\t\"17.241.200.160/27\",\n\t\t\t\t\"17.241.208.160/27\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t},\n\t\t{\n\t\t\t\"bot\": \"Claude API\",\n\t\t\t\"url\": \"https://platform.claude.com/docs/en/api/ip-addresses.md\",\n\t\t\t\"ipv4\": [\n\t\t\t\t\"153.61.208.0/20\",\n\t\t\t\t\"160.79.104.0/21\"\n\t\t\t],\n\t\t\t\"ipv6\": []\n\t\t}\n\t]\n}\n","const encoder = new TextEncoder();\nconst keys = new Map<string, Promise<CryptoKey>>();\n\nfunction importOrgKey(orgKey: string): Promise<CryptoKey> {\n\tlet key = keys.get(orgKey);\n\tif (!key) {\n\t\tkey = crypto.subtle.importKey(\n\t\t\t\"raw\",\n\t\t\tencoder.encode(orgKey),\n\t\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\t\tfalse,\n\t\t\t[\"sign\"],\n\t\t);\n\t\tkey.catch(() => keys.delete(orgKey));\n\t\tkeys.set(orgKey, key);\n\t}\n\treturn key;\n}\n\nconst toHex = (bytes: ArrayBuffer): string =>\n\tArray.from(new Uint8Array(bytes), (byte) =>\n\t\tbyte.toString(16).padStart(2, \"0\"),\n\t).join(\"\");\n\nexport async function deriveMatchKey(\n\torgKey: string,\n\tip: string,\n): Promise<string> {\n\tconst signature = await crypto.subtle.sign(\n\t\t\"HMAC\",\n\t\tawait importOrgKey(orgKey),\n\t\tencoder.encode(ip),\n\t);\n\treturn toHex(signature);\n}\n","import { applyRules, type MatchRules } from \"./logic/decide\";\nimport { settings } from \"./settings\";\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === \"object\" && value !== null;\n\nconst isStringArray = (value: unknown): value is string[] =>\n\tArray.isArray(value) && value.every((item) => typeof item === \"string\");\n\n/** The match rules in a rules file from the app (the `data` of its answer), or null when it does not have the expected form. A malformed range makes `applyRules` throw instead. */\nexport function parseRemoteRules(body: unknown): MatchRules | null {\n\tif (!isObject(body) || body.version !== 1) {\n\t\treturn null;\n\t}\n\tconst { tokens, ipRanges } = body;\n\tif (!isStringArray(tokens) || !Array.isArray(ipRanges)) {\n\t\treturn null;\n\t}\n\tconst ranges: MatchRules[\"ipRanges\"] = [];\n\tfor (const range of ipRanges) {\n\t\tif (\n\t\t\t!isObject(range) ||\n\t\t\t!isStringArray(range.ipv4) ||\n\t\t\t!isStringArray(range.ipv6)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tranges.push({ ipv4: range.ipv4, ipv6: range.ipv6 });\n\t}\n\treturn {\n\t\ttokens: tokens.map((token) => token.toLowerCase()),\n\t\tipRanges: ranges,\n\t};\n}\n\nlet lastAttemptOk = false;\nlet lastAttemptAt = Number.NEGATIVE_INFINITY;\n\nasync function fetchRules(apiUrl: string): Promise<boolean> {\n\tconst response = await fetch(`${apiUrl}/api/v2/agent-traffic/rules`, {\n\t\tsignal: AbortSignal.timeout(settings.requestTimeoutMs),\n\t});\n\tif (!response.ok) {\n\t\treturn false;\n\t}\n\tconst body: unknown = await response.json();\n\tconst rules = parseRemoteRules(isObject(body) ? body.data : null);\n\tif (!rules) {\n\t\treturn false;\n\t}\n\tapplyRules(rules);\n\treturn true;\n}\n\n/**\n * Starts one fetch of the rules file when the last attempt succeeded more than the refresh interval ago,\n * or failed more than the retry interval ago. Returns that work, or null.\n */\nexport function refreshRules(apiUrl: string): Promise<void> | null {\n\tconst now = Date.now();\n\tconst wait = lastAttemptOk ? settings.rulesRefreshMs : settings.rulesRetryMs;\n\tif (now - lastAttemptAt < wait) {\n\t\treturn null;\n\t}\n\tlastAttemptAt = now;\n\tlastAttemptOk = false;\n\treturn fetchRules(apiUrl).then(\n\t\t(ok) => {\n\t\t\tlastAttemptOk = ok;\n\t\t},\n\t\t() => undefined,\n\t);\n}\n","type WaitUntil = (promise: Promise<unknown>) => void;\ntype RequestContextStore = {\n\tget?: () => { waitUntil?: WaitUntil } | undefined;\n};\n\nconst REQUEST_CONTEXT = Symbol.for(\"@vercel/request-context\");\n\nexport function vercelRequestContext(): { waitUntil?: WaitUntil } | undefined {\n\tconst store: RequestContextStore | undefined = Reflect.get(\n\t\tglobalThis,\n\t\tREQUEST_CONTEXT,\n\t);\n\treturn store?.get?.();\n}\n\n/** The `waitUntil` that the Vercel runtime puts in the request context, the same one `@vercel/functions` reads. */\nexport function vercelWaitUntil(): WaitUntil | undefined {\n\tconst context = vercelRequestContext();\n\treturn context?.waitUntil\n\t\t? (promise) => context.waitUntil?.(promise)\n\t\t: undefined;\n}\n","import { vercelRequestContext } from \"./wait-until\";\n\nexport type Platform = \"vercel\" | \"cloudflare\" | \"cloudfront\";\n\n/** The platform this request runs on, or undefined on a runtime the relay does not know. Never throws. */\nexport function detectPlatform(): Platform | undefined {\n\ttry {\n\t\tconst context = vercelRequestContext();\n\t\tif (typeof context === \"object\" && context !== null) {\n\t\t\treturn \"vercel\";\n\t\t}\n\t\tif (globalThis.navigator?.userAgent === \"Cloudflare-Workers\") {\n\t\t\treturn \"cloudflare\";\n\t\t}\n\t} catch {}\n\treturn undefined;\n}\n","import { clientIp, HEADER_CAP } from \"./headers\";\nimport { detectPlatform, type Platform } from \"./platform\";\n\n/** `event.Records[0].cf.request` of a Lambda@Edge viewer-request event. */\nexport type CloudFrontRequest = {\n\tclientIp: string;\n\tmethod: string;\n\turi: string;\n\tquerystring: string;\n\theaders: Record<string, Array<{ key?: string; value: string }>>;\n};\n\nexport type RelayRequest = {\n\turl: string | null;\n\tmethod: string;\n\theaders: Headers;\n\tip: string | null;\n\tplatform: Platform | undefined;\n};\n\nexport function isCloudFrontRequest(\n\tinput: Request | CloudFrontRequest,\n): input is CloudFrontRequest {\n\treturn (\n\t\ttypeof Reflect.get(input, \"clientIp\") === \"string\" &&\n\t\ttypeof Reflect.get(input, \"uri\") === \"string\"\n\t);\n}\n\n/** The origin for a `host` header, or null when the value holds more than a host and an optional port. */\nfunction originOf(host: string | undefined): string | null {\n\tif (!host) {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst url = new URL(`https://${host}`);\n\t\tconst bare =\n\t\t\turl.pathname === \"/\" &&\n\t\t\turl.search === \"\" &&\n\t\t\turl.hash === \"\" &&\n\t\t\turl.username === \"\" &&\n\t\t\turl.password === \"\";\n\t\treturn bare ? url.origin : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** The hostname in a `host` header or a bare hostname, lowercased and without the port, or null when the value holds more. */\nexport function hostnameOf(host: string | undefined): string | null {\n\tconst origin = originOf(host);\n\treturn origin === null ? null : new URL(origin).hostname;\n}\n\n/** Every line of a header that CloudFront splits into several entries, joined like a fetch `Headers` joins them. */\nexport function cloudFrontHeaderList(\n\tinput: CloudFrontRequest,\n\tname: string,\n): string | null {\n\tconst values = (input.headers?.[name] ?? [])\n\t\t.map((entry) => entry?.value)\n\t\t.filter((value) => typeof value === \"string\");\n\treturn values.length === 0 ? null : values.join(\", \");\n}\n\nexport function cloudFrontHeader(\n\tinput: CloudFrontRequest,\n\tname: string,\n): string | null {\n\tconst value = input.headers?.[name]?.[0]?.value;\n\treturn typeof value === \"string\" ? value : null;\n}\n\n/** Copies only the headers the relay reads, so a cookie or a value `Headers` refuses never makes the request fail. */\nfunction cloudFrontHeaders(input: CloudFrontRequest[\"headers\"]): Headers {\n\tconst headers = new Headers();\n\tfor (const name of HEADER_CAP) {\n\t\tfor (const entry of input?.[name] ?? []) {\n\t\t\tif (typeof entry?.value !== \"string\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\theaders.append(name, entry.value);\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn headers;\n}\n\n/** The client can set `X-Forwarded-For` at viewer-request, so a CloudFront request takes its IP from `clientIp` alone. `url` is null when the host header is missing or malformed. */\nexport function toRelayRequest(\n\tinput: Request | CloudFrontRequest,\n): RelayRequest {\n\tif (!isCloudFrontRequest(input)) {\n\t\treturn {\n\t\t\turl: input.url,\n\t\t\tmethod: input.method,\n\t\t\theaders: input.headers,\n\t\t\tip: clientIp(input.headers),\n\t\t\tplatform: detectPlatform(),\n\t\t};\n\t}\n\tconst origin = originOf(input.headers?.host?.[0]?.value);\n\tconst query = input.querystring ? `?${input.querystring}` : \"\";\n\tconst ip = input.clientIp.trim().toLowerCase();\n\treturn {\n\t\turl: origin === null ? null : `${origin}${input.uri}${query}`,\n\t\tmethod: input.method,\n\t\theaders: cloudFrontHeaders(input.headers),\n\t\tip: ip ? ip : null,\n\t\tplatform: \"cloudfront\",\n\t};\n}\n","import {\n\tfetchAgentPage,\n\tloadSiteRules,\n\tmatchesPath,\n\tnormalizePath,\n\ttype PageFormat,\n\tpageFormat,\n\tprefersMarkdown,\n\ttype SiteRules,\n} from \"./agent-page\";\nimport { readCappedHeaders } from \"./headers\";\nimport { type PostPolicy, postEvent } from \"./ingest\";\nimport { readKey } from \"./key\";\nimport { decide } from \"./logic\";\nimport { deriveMatchKey } from \"./match-key\";\nimport type { Platform } from \"./platform\";\nimport { refreshRules } from \"./refresh\";\nimport {\n\ttype CloudFrontRequest,\n\tcloudFrontHeader,\n\tcloudFrontHeaderList,\n\thostnameOf,\n\tisCloudFrontRequest,\n\ttoRelayRequest,\n} from \"./request\";\nimport { settings } from \"./settings\";\nimport { RELAY_VERSION } from \"./version\";\nimport { vercelWaitUntil } from \"./wait-until\";\n\nexport type { CloudFrontRequest };\n\n/** The response a Lambda@Edge handler returns in place of the request. */\nexport type CloudFrontResultResponse = {\n\tstatus: string;\n\tstatusDescription?: string;\n\theaders: Record<string, Array<{ key: string; value: string }>>;\n\tbody?: string;\n};\n\nexport type Region = keyof typeof settings.apiUrls;\n\nexport type RelayOptions = {\n\t/** The region of your WaniWani organization. Defaults to `\"us\"`. */\n\tregion?: Region;\n\t/** Overrides the host that `region` picks. */\n\tapiUrl?: string;\n\t/** Overrides `process.env.WANIWANI_AGENT_TRAFFIC_KEY`. */\n\tkey?: string;\n\t/** Keeps the background post alive after the response. Defaults to the `waitUntil` of the Vercel request context. */\n\twaitUntil?: (promise: Promise<unknown>) => void;\n\t/** Header names, and the fields `matchKey`, `userAgent` or `referer`, to leave out of every event. */\n\tomit?: Array<string>;\n\t/** `\"remote\"` loads the newest AI bot list from WaniWani every 12 hours. `\"bundled\"` keeps the list shipped in this version. Defaults to `\"remote\"`. */\n\trules?: \"remote\" | \"bundled\";\n};\n\nexport type IsAgentOptions = Pick<\n\tRelayOptions,\n\t\"region\" | \"apiUrl\" | \"waitUntil\" | \"rules\"\n>;\n\nexport type ServeAgentPageOptions = Pick<\n\tRelayOptions,\n\t\"region\" | \"apiUrl\" | \"key\"\n>;\n\nexport type AgentVisitRecord = {\n\toccurredAt: string;\n\tmethod: string;\n\thost: string;\n\tpath: string;\n\tuserAgent?: string;\n\treferer?: string;\n\theaders: Record<string, string>;\n\tmatchKey?: string;\n\tsourceEventId: string;\n\trelayVersion: string;\n\tplatform?: Platform;\n\t/** The relay functions this process called, so WaniWani can show which setup steps the code has. */\n\tuses: RelayFunction[];\n};\n\nexport type RelayFunction =\n\t| \"recordAgentVisit\"\n\t| \"serveAgentPage\"\n\t| \"handleViewerRequest\";\n\nconst used = new Set<RelayFunction>();\n\n/**\n * Tells whether a request, or the `cf.request` of a Lambda@Edge event, comes from an AI agent. Reads headers only and never waits on the network.\n * Every 12 hours it starts one background fetch of the newest AI bot list, unless `rules` is `\"bundled\"`.\n */\nexport function isAgent(\n\trequest: Request | CloudFrontRequest,\n\toptions: IsAgentOptions = {},\n): boolean {\n\tstartRulesRefresh(options);\n\treturn matchesAgent(request, options.rules === \"bundled\");\n}\n\nfunction matchesAgent(\n\trequest: Request | CloudFrontRequest,\n\tuseBundled = false,\n): boolean {\n\ttry {\n\t\tconst { headers, ip } = toRelayRequest(request);\n\t\treturn decide({ headers: readCappedHeaders(headers), ip, useBundled });\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction startRulesRefresh(options: IsAgentOptions): void {\n\ttry {\n\t\tif (options.rules === \"bundled\") {\n\t\t\treturn;\n\t\t}\n\t\tconst apiUrl = resolveApiUrl(options);\n\t\tconst work = apiUrl ? refreshRules(apiUrl) : null;\n\t\tif (work) {\n\t\t\tschedule(work, options.waitUntil);\n\t\t}\n\t} catch {}\n}\n\n/**\n * Posts one event for this request to WaniWani after the response.\n * Sends nothing when no key is set. Never throws, never rejects and never touches the request.\n * The promise settles when the post is done, so Lambda@Edge, which has no `waitUntil`, awaits it.\n */\nexport function recordAgentVisit(\n\trequest: Request | CloudFrontRequest,\n\toptions: RelayOptions = {},\n): Promise<void> {\n\tused.add(\"recordAgentVisit\");\n\ttry {\n\t\tconst key = readKey(options.key);\n\t\tif (!key) {\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tconst apiUrl = resolveApiUrl(options);\n\t\tif (!apiUrl) {\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tconst omitted = omitSet(options.omit);\n\t\tconst {\n\t\t\turl: requestUrl,\n\t\t\tmethod,\n\t\t\theaders: requestHeaders,\n\t\t\tip,\n\t\t\tplatform,\n\t\t} = toRelayRequest(request);\n\t\tif (requestUrl === null) {\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tconst capped = readCappedHeaders(requestHeaders);\n\n\t\tconst headers: Record<string, string> = {};\n\t\tfor (const [name, value] of Object.entries(capped)) {\n\t\t\tif (!omitted.has(name)) {\n\t\t\t\theaders[name] = value;\n\t\t\t}\n\t\t}\n\t\tconst url = new URL(requestUrl);\n\t\tconst record: AgentVisitRecord = {\n\t\t\toccurredAt: new Date().toISOString(),\n\t\t\tmethod,\n\t\t\thost: url.hostname,\n\t\t\tpath: url.pathname + url.search,\n\t\t\theaders,\n\t\t\tsourceEventId: crypto.randomUUID(),\n\t\t\trelayVersion: RELAY_VERSION,\n\t\t\tuses: [],\n\t\t};\n\t\tif (capped[\"user-agent\"] !== undefined && !omitted.has(\"user-agent\")) {\n\t\t\trecord.userAgent = capped[\"user-agent\"];\n\t\t}\n\t\tif (capped.referer !== undefined && !omitted.has(\"referer\")) {\n\t\t\trecord.referer = capped.referer;\n\t\t}\n\t\tif (platform !== undefined) {\n\t\t\trecord.platform = platform;\n\t\t}\n\n\t\tconst policy: PostPolicy =\n\t\t\tplatform === \"cloudfront\"\n\t\t\t\t? { timeoutMs: settings.cloudFrontTimeoutMs, retry: false }\n\t\t\t\t: { timeoutMs: settings.requestTimeoutMs, retry: true };\n\t\tconst ingestUrl = `${apiUrl}/api/v2/agent-traffic/ingest/middleware/${key.ingestToken}`;\n\t\tconst withMatchKey = async () => {\n\t\t\t// Lets a serveAgentPage or handleViewerRequest call that follows on the same request join `used`.\n\t\t\tawait Promise.resolve();\n\t\t\trecord.uses = [...used];\n\t\t\tif (ip !== null && !omitted.has(\"matchkey\")) {\n\t\t\t\trecord.matchKey = await deriveMatchKey(key.orgKey, ip).catch(\n\t\t\t\t\t() => undefined,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait postEvent(\n\t\t\t\tingestUrl,\n\t\t\t\tJSON.stringify({ requests: [record] }),\n\t\t\t\tpolicy,\n\t\t\t);\n\t\t};\n\t\treturn schedule(withMatchKey(), options.waitUntil);\n\t} catch {\n\t\treturn Promise.resolve();\n\t}\n}\n\ntype PageRequest = {\n\tmethod: string;\n\thostname: string;\n\tpathname: string;\n\tuserAgent: string;\n\taccept: string;\n\trendering: boolean;\n\tagent: boolean;\n};\n\ntype PageAnswer = {\n\tstatus: 200 | 403;\n\theaders: Record<string, string>;\n\tbody: Uint8Array<ArrayBuffer> | string | null;\n};\n\ntype SiteMatch = {\n\trules: SiteRules;\n\tsite: SiteRules[\"sites\"][number];\n};\n\nconst BLOCKED: Omit<PageAnswer, \"body\"> = {\n\tstatus: 403,\n\theaders: {\n\t\t\"content-type\": \"text/plain; charset=utf-8\",\n\t\t\"x-waniwani-blocked\": \"1\",\n\t\t\"cache-control\": \"private, no-store\",\n\t\tvary: \"user-agent\",\n\t},\n};\n\n/**\n * The stored agent page for this request, when its site lists the path and the user agent acts for a user. A 403 when the site blocks the user agent. Otherwise null, and the caller serves the origin.\n * A `CloudFrontRequest` comes from the origin-request handler. Only a URI that `handleViewerRequest` marked gets the page, read for the host in the mark. The function restores `request.uri` to the original path before anything else, and it never returns a 403 on this path.\n * Never throws and never rejects.\n */\nexport function serveAgentPage(\n\trequest: Request,\n\toptions?: ServeAgentPageOptions,\n): Promise<Response | null>;\nexport function serveAgentPage(\n\trequest: CloudFrontRequest,\n\toptions?: ServeAgentPageOptions,\n): Promise<CloudFrontResultResponse | null>;\nexport async function serveAgentPage(\n\trequest: Request | CloudFrontRequest,\n\toptions: ServeAgentPageOptions = {},\n): Promise<Response | CloudFrontResultResponse | null> {\n\tused.add(\"serveAgentPage\");\n\ttry {\n\t\treturn isCloudFrontRequest(request)\n\t\t\t? await serveCloudFront(request, options)\n\t\t\t: await serveFetch(request, options);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function serveFetch(\n\trequest: Request,\n\toptions: ServeAgentPageOptions,\n): Promise<Response | null> {\n\tconst url = new URL(request.url);\n\tconst answer = await answerAgentPage(\n\t\t{\n\t\t\tmethod: request.method.toUpperCase(),\n\t\t\thostname: url.hostname,\n\t\t\tpathname: url.pathname,\n\t\t\tuserAgent: request.headers.get(\"user-agent\") ?? \"\",\n\t\t\taccept: request.headers.get(\"accept\") ?? \"\",\n\t\t\trendering: request.headers.has(\"x-waniwani-render\"),\n\t\t\tagent: matchesAgent(request),\n\t\t},\n\t\toptions,\n\t);\n\tif (!answer) {\n\t\treturn null;\n\t}\n\treturn new Response(answer.body, {\n\t\tstatus: answer.status,\n\t\theaders: answer.headers,\n\t});\n}\n\nasync function serveCloudFront(\n\trequest: CloudFrontRequest,\n\toptions: ServeAgentPageOptions,\n): Promise<CloudFrontResultResponse | null> {\n\tconst mark = takeMarkFromUri(request);\n\tif (mark === null) {\n\t\treturn null;\n\t}\n\tconst { hostname, format } = mark;\n\tconst method = request.method.toUpperCase();\n\tif (!isPageMethod(method)) {\n\t\treturn null;\n\t}\n\tconst match = await findSite(hostname, options);\n\tif (!match) {\n\t\treturn null;\n\t}\n\tconst answer = await pageAnswer(\n\t\tmatch.rules,\n\t\thostname,\n\t\tnormalizePath(request.uri),\n\t\tmethod,\n\t\tformat,\n\t);\n\tif (!answer) {\n\t\treturn null;\n\t}\n\tconst result = toCloudFrontResponse(answer);\n\tif (\n\t\tresult.body !== undefined &&\n\t\tnew TextEncoder().encode(result.body).byteLength >\n\t\t\tsettings.cloudFrontMaxBodyBytes\n\t) {\n\t\treturn null;\n\t}\n\treturn result;\n}\n\nfunction toCloudFrontResponse(answer: PageAnswer): CloudFrontResultResponse {\n\tconst headers: CloudFrontResultResponse[\"headers\"] = {};\n\tfor (const [name, value] of Object.entries(answer.headers)) {\n\t\theaders[name] = [{ key: headerKey(name), value }];\n\t}\n\tconst result: CloudFrontResultResponse = {\n\t\tstatus: String(answer.status),\n\t\tstatusDescription: answer.status === 200 ? \"OK\" : \"Forbidden\",\n\t\theaders,\n\t};\n\tif (answer.body !== null) {\n\t\tresult.body =\n\t\t\ttypeof answer.body === \"string\"\n\t\t\t\t? answer.body\n\t\t\t\t: new TextDecoder().decode(answer.body);\n\t}\n\treturn result;\n}\n\nfunction headerKey(name: string): string {\n\treturn name\n\t\t.split(\"-\")\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"-\");\n}\n\nconst isPageMethod = (method: string): boolean =>\n\tmethod === \"GET\" || method === \"HEAD\";\n\nasync function findSite(\n\thostname: string,\n\toptions: ServeAgentPageOptions,\n): Promise<SiteMatch | null> {\n\tconst key = readKey(options.key);\n\tif (!key) {\n\t\treturn null;\n\t}\n\tconst apiUrl = resolveApiUrl(options);\n\tif (!apiUrl) {\n\t\treturn null;\n\t}\n\tconst rules = await loadSiteRules(apiUrl, key.ingestToken, (work) =>\n\t\tschedule(work, undefined),\n\t);\n\tif (!rules) {\n\t\treturn null;\n\t}\n\tconst site = rules.sites.find((entry) => entry.hostname === hostname);\n\treturn site ? { rules, site } : null;\n}\n\nfunction blockedAnswer(\n\tsite: SiteMatch[\"site\"],\n\tuserAgent: string,\n\tmethod: string,\n): PageAnswer | null {\n\treturn site.blockedUserAgentTokens.some((token) => userAgent.includes(token))\n\t\t? { ...BLOCKED, body: method === \"HEAD\" ? null : \"Forbidden\" }\n\t\t: null;\n}\n\nasync function answerAgentPage(\n\trequest: PageRequest,\n\toptions: ServeAgentPageOptions,\n): Promise<PageAnswer | null> {\n\tconst { method, hostname } = request;\n\tif (\n\t\t!isPageMethod(method) ||\n\t\t!(request.agent || prefersMarkdown(request.accept))\n\t) {\n\t\treturn null;\n\t}\n\tconst match = await findSite(hostname, options);\n\tif (!match) {\n\t\treturn null;\n\t}\n\tconst { rules, site } = match;\n\tconst userAgent = request.userAgent.toLowerCase();\n\tconst blocked = blockedAnswer(site, userAgent, method);\n\tif (blocked) {\n\t\treturn blocked;\n\t}\n\tif (request.rendering || !site.agentPages.enabled) {\n\t\treturn null;\n\t}\n\tconst path = normalizePath(request.pathname);\n\tif (!matchesPath(path, site.agentPages.paths)) {\n\t\treturn null;\n\t}\n\tconst format = servedFormat(rules, userAgent, request.accept);\n\treturn format && pageAnswer(rules, hostname, path, method, format);\n}\n\n/** An agent that acts for a user gets the page in the format it asks for. Any other client gets the Markdown page only when it prefers Markdown. */\nfunction servedFormat(\n\trules: SiteRules,\n\tuserAgent: string,\n\taccept: string,\n): PageFormat | null {\n\tif (rules.userAgentTokens.some((token) => userAgent.includes(token))) {\n\t\treturn pageFormat(accept);\n\t}\n\treturn prefersMarkdown(accept) ? \"markdown\" : null;\n}\n\nasync function pageAnswer(\n\trules: SiteRules,\n\thostname: string,\n\tpath: string,\n\tmethod: string,\n\tformat: PageFormat,\n): Promise<PageAnswer | null> {\n\tconst page = await fetchAgentPage(rules, hostname, path, format);\n\tif (!page) {\n\t\treturn null;\n\t}\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"content-type\": page.contentType,\n\t\t\t\"x-waniwani-agent-page\": \"hit\",\n\t\t\t\"cache-control\": \"private, no-store\",\n\t\t\tvary: \"user-agent, accept\",\n\t\t},\n\t\tbody: method === \"HEAD\" ? null : page.body,\n\t};\n}\n\nconst AGENT_URI_PREFIX = \"/__waniwani-agent/\";\n\nconst PAGE_FORMATS: readonly PageFormat[] = [\"markdown\", \"html\"];\n\n/** Splits `/__waniwani-agent/<format>/<host><original uri>`. The format is null when the mark names none the relay knows. */\nfunction splitAgentUri(uri: string): {\n\tformat: PageFormat | null;\n\tencodedHost: string;\n\toriginalUri: string;\n} | null {\n\tif (!uri.startsWith(AGENT_URI_PREFIX)) {\n\t\treturn null;\n\t}\n\tconst rest = uri.slice(AGENT_URI_PREFIX.length);\n\tconst formatEnd = rest.indexOf(\"/\");\n\tconst formatName = formatEnd === -1 ? rest : rest.slice(0, formatEnd);\n\tconst format = PAGE_FORMATS.find((known) => known === formatName) ?? null;\n\tconst afterFormat = formatEnd === -1 ? \"\" : rest.slice(formatEnd + 1);\n\tconst slash = afterFormat.indexOf(\"/\");\n\treturn slash === -1\n\t\t? { format, encodedHost: afterFormat, originalUri: \"/\" }\n\t\t: {\n\t\t\t\tformat,\n\t\t\t\tencodedHost: afterFormat.slice(0, slash),\n\t\t\t\toriginalUri: afterFormat.slice(slash),\n\t\t\t};\n}\n\nfunction stripForgedMark(request: CloudFrontRequest): void {\n\tlet forged = splitAgentUri(request.uri);\n\twhile (forged) {\n\t\trequest.uri = forged.originalUri;\n\t\tforged = splitAgentUri(request.uri);\n\t}\n}\n\nfunction takeMarkFromUri(\n\trequest: CloudFrontRequest,\n): { hostname: string; format: PageFormat } | null {\n\tconst parts = splitAgentUri(request.uri);\n\tif (!parts) {\n\t\treturn null;\n\t}\n\trequest.uri = parts.originalUri;\n\tif (parts.format === null) {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst hostname = hostnameOf(decodeURIComponent(parts.encodedHost));\n\t\treturn hostname === null ? null : { hostname, format: parts.format };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * For the viewer-request handler on CloudFront. Strips a `/__waniwani-agent/` prefix the client sent, then, for a GET or HEAD from an AI agent, loads the rules of the site of the `host` header.\n * Returns the 403 response when the site blocks the user agent. CloudFront never caches a response made at viewer-request, so a block takes effect at once.\n * When the site serves an agent page for the path and the user agent acts for a user, rewrites `request.uri` to `/__waniwani-agent/<host><original uri>` so the origin-request handler serves the page under its own cache key. The request stays unmarked when the new URI would pass the CloudFront URI limit.\n * Call `recordAgentVisit` before it, so the event holds the original path. Never throws and never rejects.\n */\nexport async function handleViewerRequest(\n\trequest: CloudFrontRequest,\n\toptions: ServeAgentPageOptions = {},\n): Promise<CloudFrontResultResponse | null> {\n\tused.add(\"handleViewerRequest\");\n\ttry {\n\t\tstripForgedMark(request);\n\t\tconst method = request.method.toUpperCase();\n\t\tconst accept = cloudFrontHeaderList(request, \"accept\") ?? \"\";\n\t\tif (\n\t\t\t!isPageMethod(method) ||\n\t\t\t!(prefersMarkdown(accept) || isAgent(request, options))\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tconst hostname = hostnameOf(cloudFrontHeader(request, \"host\") ?? undefined);\n\t\tif (hostname === null) {\n\t\t\treturn null;\n\t\t}\n\t\tconst match = await findSite(hostname, options);\n\t\tif (!match) {\n\t\t\treturn null;\n\t\t}\n\t\tconst { rules, site } = match;\n\t\tconst userAgent = (\n\t\t\tcloudFrontHeader(request, \"user-agent\") ?? \"\"\n\t\t).toLowerCase();\n\t\tconst blocked = blockedAnswer(site, userAgent, method);\n\t\tif (blocked) {\n\t\t\treturn toCloudFrontResponse(blocked);\n\t\t}\n\t\tconst format = servedFormat(rules, userAgent, accept);\n\t\tif (\n\t\t\tcloudFrontHeader(request, \"x-waniwani-render\") !== null ||\n\t\t\t!site.agentPages.enabled ||\n\t\t\t!matchesPath(normalizePath(request.uri), site.agentPages.paths) ||\n\t\t\tformat === null\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\tconst uri = `${AGENT_URI_PREFIX}${format}/${encodeURIComponent(hostname)}${request.uri}`;\n\t\tif (\n\t\t\tnew TextEncoder().encode(uri).byteLength <= settings.cloudFrontMaxUriBytes\n\t\t) {\n\t\t\trequest.uri = uri;\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nlet warnedRegion = false;\n\nfunction resolveApiUrl(options: IsAgentOptions): string | null {\n\tif (options.apiUrl !== undefined) {\n\t\treturn options.apiUrl.replace(/\\/+$/, \"\");\n\t}\n\tconst region = options.region ?? \"us\";\n\tif (Object.hasOwn(settings.apiUrls, region)) {\n\t\treturn settings.apiUrls[region];\n\t}\n\tif (!warnedRegion) {\n\t\twarnedRegion = true;\n\t\tconsole.warn(\n\t\t\t`[@waniwani/relay] Unknown region ${JSON.stringify(region)}, so no agent visits are recorded. Use \"us\" or \"eu\".`,\n\t\t);\n\t}\n\treturn null;\n}\n\nfunction omitSet(omit: Array<string> | undefined): Set<string> {\n\tconst names = new Set((omit ?? []).map((name) => name.toLowerCase()));\n\tif (names.has(\"useragent\")) {\n\t\tnames.add(\"user-agent\");\n\t}\n\treturn names;\n}\n\nfunction schedule(\n\twork: Promise<unknown>,\n\twaitUntil: RelayOptions[\"waitUntil\"],\n): Promise<void> {\n\tconst settled = work.then(\n\t\t() => undefined,\n\t\t() => undefined,\n\t);\n\ttry {\n\t\t(waitUntil ?? vercelWaitUntil())?.(settled);\n\t} catch {}\n\treturn settled;\n}\n"],"mappings":";AAAO,IAAM,WAAW;AAAA,EACvB,SAAS;AAAA,IACR,IAAI;AAAA,IACJ,IAAI;AAAA,EACL;AAAA,EACA,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,uBAAuB;AAAA,EACvB,wBAAwB,MAAY;AAAA,EACpC,cAAc;AAAA,EACd,gBAAgB,KAAK,KAAK,KAAK;AAAA,EAC/B,cAAc,IAAI,KAAK;AAAA,EACvB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,kBAAkB;AACnB;;;ACbO,IAAM,gBACZ,OACG,UACA;;;ACWJ,IAAM,WAAW,CAAC,UACjB,OAAO,UAAU,YAAY,UAAU;AAExC,IAAM,gBAAgB,CAAC,UACtB,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,SAAS,OAAO,SAAS,QAAQ;AAEvE,IAAM,aAAa,CAAC,UACnB,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ;AAGhE,IAAM,cAAc,CAAC,WACpB,OACE,IAAI,CAAC,UAAU,MAAM,KAAK,EAAE,YAAY,CAAC,EACzC,OAAO,CAAC,UAAU,UAAU,EAAE;AAG1B,SAAS,eAAe,MAAiC;AAC/D,MAAI,CAAC,SAAS,IAAI,KAAK,KAAK,YAAY,GAAG;AAC1C,WAAO;AAAA,EACR;AACA,QAAM,EAAE,UAAU,iBAAiB,WAAW,cAAc,MAAM,IAAI;AACtE,MACC,OAAO,aAAa,YACpB,CAAC,cAAc,eAAe,KAC9B,CAAC,SAAS,SAAS,KACnB,CAAC,WAAW,UAAU,SAAS,KAC/B,CAAC,WAAW,UAAU,MAAM,KAC5B,CAAC,WAAW,YAAY,KACxB,CAAC,MAAM,QAAQ,KAAK,GACnB;AACD,WAAO;AAAA,EACR;AACA,QAAM,cAAkC,CAAC;AACzC,aAAW,QAAQ,OAAO;AACzB,QAAI,CAAC,SAAS,IAAI,GAAG;AACpB,aAAO;AAAA,IACR;AACA,UAAM,UAAU,KAAK,0BAA0B,CAAC;AAChD,QACC,OAAO,KAAK,aAAa,YACzB,CAAC,cAAc,OAAO,KACtB,CAAC,SAAS,KAAK,UAAU,KACzB,OAAO,KAAK,WAAW,YAAY,aACnC,CAAC,cAAc,KAAK,WAAW,KAAK,GACnC;AACD,aAAO;AAAA,IACR;AACA,gBAAY,KAAK;AAAA,MAChB,UAAU,KAAK,SAAS,YAAY;AAAA,MACpC,wBAAwB,YAAY,OAAO;AAAA,MAC3C,YAAY;AAAA,QACX,SAAS,KAAK,WAAW;AAAA,QACzB,OAAO,KAAK,WAAW;AAAA,MACxB;AAAA,IACD,CAAC;AAAA,EACF;AACA,SAAO;AAAA,IACN,SAAS;AAAA,IACT,UAAU,SAAS,QAAQ,QAAQ,EAAE;AAAA,IACrC,iBAAiB,YAAY,eAAe;AAAA,IAC5C,WAAW,EAAE,WAAW,UAAU,WAAW,QAAQ,UAAU,OAAO;AAAA,IACtE;AAAA,IACA,OAAO;AAAA,EACR;AACD;AAUA,IAAM,QAAQ,oBAAI,IAAwB;AAE1C,eAAe,eACd,KACA,WAC4B;AAC5B,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IACjC,QAAQ,YAAY,QAAQ,SAAS;AAAA,EACtC,CAAC;AACD,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,SAAS,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC5C,WAAO;AAAA,EACR;AACA,QAAM,OAAgB,MAAM,SAAS,KAAK;AAC1C,SAAO,eAAe,SAAS,IAAI,IAAI,KAAK,OAAO,IAAI;AACxD;AAEA,SAAS,WACR,OACA,KACA,WAC4B;AAC5B,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,gBAAgB;AACtB,QAAM,gBAAgB;AACtB,QAAM,OAAO,eAAe,KAAK,SAAS,EACxC,MAAM,MAAM,IAAI,EAChB,KAAK,CAAC,UAAU;AAChB,QAAI,OAAO;AACV,YAAM,QAAQ;AACd,YAAM,YAAY,KAAK,IAAI;AAC3B,YAAM,gBAAgB;AAAA,IACvB;AACA,UAAM,WAAW;AACjB,WAAO;AAAA,EACR,CAAC;AACF,QAAM,WAAW;AACjB,SAAO;AACR;AAMA,eAAsB,cACrB,QACA,aACA,YAC4B;AAC5B,QAAM,MAAM,GAAG,MAAM,+BAA+B,WAAW;AAC/D,MAAI,QAAQ,MAAM,IAAI,GAAG;AACzB,MAAI,CAAC,OAAO;AACX,YAAQ;AAAA,MACP,OAAO;AAAA,MACP,WAAW;AAAA,MACX,eAAe,OAAO;AAAA,MACtB,eAAe;AAAA,MACf,UAAU;AAAA,IACX;AACA,UAAM,IAAI,KAAK,KAAK;AAAA,EACrB;AACA,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,MAAM,OAAO;AAChB,UAAM,QAAQ,MAAM,MAAM,aAAa,SAAS;AAChD,UAAM,WACL,MAAM,iBACN,MAAM,MAAM,iBAAiB,SAAS;AACvC,QAAI,SAAS,YAAY,CAAC,MAAM,UAAU;AACzC,iBAAW,WAAW,OAAO,KAAK,SAAS,gBAAgB,CAAC;AAAA,IAC7D;AACA,WAAO,MAAM;AAAA,EACd;AACA,MAAI,MAAM,UAAU;AACnB,WAAO,MAAM;AAAA,EACd;AACA,MAAI,MAAM,MAAM,gBAAgB,SAAS,kBAAkB;AAC1D,WAAO;AAAA,EACR;AACA,SAAO,WAAW,OAAO,KAAK,SAAS,kBAAkB;AAC1D;AAGO,SAAS,cAAc,UAA0B;AACvD,QAAM,UAAU,SAAS,QAAQ,QAAQ,EAAE;AAC3C,SAAO,YAAY,KAAK,MAAM;AAC/B;AAGO,SAAS,YAAY,MAAc,UAA6B;AACtE,SAAO,SAAS,KAAK,CAAC,YAAY;AACjC,QAAI,YAAY,OAAO,YAAY,MAAM;AACxC,aAAO;AAAA,IACR;AACA,QAAI,QAAQ,SAAS,IAAI,GAAG;AAC3B,YAAM,SAAS,cAAc,QAAQ,MAAM,GAAG,EAAE,CAAC;AACjD,aAAO,SAAS,UAAU,KAAK,WAAW,GAAG,MAAM,GAAG;AAAA,IACvD;AACA,WAAO,SAAS,cAAc,OAAO;AAAA,EACtC,CAAC;AACF;AAEA,IAAM,UAAU,uBAAO,SAAS;AAEhC,SAAS,OACR,SACA,IAC8B;AAC9B,MAAI;AACJ,QAAM,WAAW,IAAI,QAAwB,CAAC,YAAY;AACzD,YAAQ,WAAW,MAAM,QAAQ,OAAO,GAAG,EAAE;AAAA,EAC9C,CAAC;AACD,SAAO,QAAQ,KAAK,CAAC,SAAS,QAAQ,CAAC,EAAE,QAAQ,MAAM,aAAa,KAAK,CAAC;AAC3E;AAEA,eAAe,WACd,MACA,UACA,UAC0C;AAC1C,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,SAAuB,CAAC;AAC9B,MAAI,OAAO;AACX,MAAI;AACH,WAAO,MAAM;AACZ,YAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,OAAO,KAAK,GAAG,QAAQ,CAAC;AAC3D,UAAI,WAAW,SAAS;AACvB,eAAO;AAAA,MACR;AACA,UAAI,OAAO,MAAM;AAChB;AAAA,MACD;AACA,cAAQ,OAAO,MAAM;AACrB,UAAI,OAAO,UAAU;AACpB,eAAO;AAAA,MACR;AACA,aAAO,KAAK,OAAO,KAAK;AAAA,IACzB;AAAA,EACD,UAAE;AACD,WAAO,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/B;AACA,QAAM,MAAM,IAAI,WAAW,IAAI;AAC/B,MAAI,SAAS;AACb,aAAW,SAAS,QAAQ;AAC3B,QAAI,IAAI,OAAO,MAAM;AACrB,cAAU,MAAM;AAAA,EACjB;AACA,SAAO;AACR;AAMA,SAAS,YAAY,QAA8B;AAClD,SAAO,OAAO,MAAM,GAAG,EAAE,IAAI,CAAC,UAAU;AACvC,UAAM,CAAC,OAAO,IAAI,GAAG,MAAM,IAAI,MAC7B,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,EAAE,YAAY,CAAC;AACzC,UAAM,IAAI,OACR,IAAI,CAAC,UAAU,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,EAC1D,KAAK,CAAC,CAAC,IAAI,MAAM,SAAS,GAAG;AAC/B,UAAM,QAAQ,MAAM,SAAY,IAAI,OAAO,EAAE,CAAC,CAAC;AAC/C,WAAO,EAAE,MAAM,GAAG,OAAO,SAAS,KAAK,IAAI,QAAQ,EAAE;AAAA,EACtD,CAAC;AACF;AAGA,SAAS,UAAU,QAAsB,MAA6B;AACrE,QAAM,QAAQ,OAAO,OAAO,CAAC,UAAU,MAAM,SAAS,IAAI;AAC1D,SAAO,MAAM,WAAW,IAAI,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,MAAM,CAAC,CAAC;AAC7E;AAEA,IAAM,kBAAkB,CAAC,WACxB,UAAU,QAAQ,eAAe,KAAK;AAGhC,SAAS,WAAW,QAA4B;AACtD,SAAO,gBAAgB,YAAY,MAAM,CAAC,IAAI,IAAI,aAAa;AAChE;AAGO,SAAS,gBAAgB,QAAyB;AACxD,QAAM,SAAS,YAAY,MAAM;AACjC,QAAM,WAAW,gBAAgB,MAAM;AACvC,QAAM,OACL,UAAU,QAAQ,WAAW,KAC7B,UAAU,QAAQ,QAAQ,KAC1B,UAAU,QAAQ,KAAK,KACvB;AACD,SAAO,WAAW,KAAK,YAAY;AACpC;AAGA,eAAsB,eACrB,OACA,UACA,MACA,QACyE;AACzE,QAAM,aAAa,IAAI,gBAAgB;AACvC,MAAI;AACH,UAAM,WAAW,MAAM;AAAA,MACtB,MAAM,GAAG,MAAM,QAAQ,IAAI,QAAQ,GAAG,SAAS,MAAM,KAAK,IAAI,IAAI;AAAA,QACjE,SAAS;AAAA,UACR,QAAQ,WAAW,aAAa,kBAAkB;AAAA,UAClD,oBAAoB;AAAA,QACrB;AAAA,QACA,QAAQ,WAAW;AAAA,MACpB,CAAC;AAAA,MACD,MAAM,UAAU;AAAA,IACjB;AACA,QAAI,aAAa,SAAS;AACzB,aAAO;AAAA,IACR;AACA,UAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,QACC,SAAS,WAAW,OACpB,CAAC,YAAY,YAAY,EAAE,WAAW,OAAO,KAC7C,CAAC,SAAS,MACT;AACD,YAAM,SAAS,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC5C,aAAO;AAAA,IACR;AACA,QAAI;AACJ,UAAM,WAAW,IAAI,QAAwB,CAAC,YAAY;AACzD,cAAQ,WAAW,MAAM,QAAQ,OAAO,GAAG,MAAM,UAAU,MAAM;AAAA,IAClE,CAAC;AACD,QAAI;AACH,YAAM,OAAO,MAAM;AAAA,QAClB,SAAS;AAAA,QACT;AAAA,QACA,MAAM;AAAA,MACP;AACA,aAAO,OAAO,EAAE,MAAM,YAAY,IAAI;AAAA,IACvC,UAAE;AACD,mBAAa,KAAK;AAAA,IACnB;AAAA,EACD,QAAQ;AACP,WAAO;AAAA,EACR,UAAE;AACD,eAAW,MAAM;AAAA,EAClB;AACD;;;AC5UO,IAAM,aAAgC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAEA,IAAM,gBAAgB,IAAI;AAAA,EACzB;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,YAAY,GAAG,MAAM,CAAC;AACjD;AAGO,SAAS,oBAAoB,OAA8B;AACjE,QAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,KAAK;AACtC,MAAI,MAAM,SAAS,GAAG;AACrB,WAAO;AAAA,EACR;AACA,SAAO,cAAc,KAAK,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK;AAC7D;AAEO,SAAS,kBAAkB,SAA0C;AAC3E,QAAM,MAA8B,CAAC;AACrC,aAAW,QAAQ,YAAY;AAC9B,UAAM,QAAQ,QAAQ,IAAI,IAAI;AAC9B,QAAI,UAAU,MAAM;AACnB;AAAA,IACD;AACA,QAAI,SAAS,iBAAiB;AAC7B,YAAM,SAAS,oBAAoB,KAAK;AACxC,UAAI,WAAW,MAAM;AACpB,YAAI,IAAI,IAAI;AAAA,MACb;AAAA,IACD,OAAO;AACN,UAAI,IAAI,IAAI;AAAA,IACb;AAAA,EACD;AACA,SAAO;AACR;AAGO,SAAS,SAAS,SAAiC;AACzD,QAAM,YAAY,QAAQ,IAAI,iBAAiB;AAC/C,QAAM,YACL,QAAQ,IAAI,kBAAkB,MAC7B,cAAc,OAAO,UAAU,MAAM,GAAG,EAAE,CAAC,IAAI,QAAQ,IAAI,WAAW;AACxE,QAAM,KAAK,WAAW,KAAK,EAAE,YAAY;AACzC,SAAO,KAAK,KAAK;AAClB;;;AClEA,eAAsB,iBACrB,KACA,MACA,YAAoB,SAAS,kBACV;AACnB,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,MAAI;AACH,UAAM,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,MAAM,QAAQ,WAAW,OAAO,CAAC;AACxE,UAAM,SAAS,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC5C,WAAO,EAAE,QAAQ,SAAS,OAAO;AAAA,EAClC,QAAQ;AACP,WAAO,EAAE,SAAS,WAAW,OAAO,UAAU,YAAY,UAAU;AAAA,EACrE,UAAE;AACD,iBAAa,KAAK;AAAA,EACnB;AACD;AAEO,IAAM,QAAQ,CAAC,OACrB,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;;;ACrBvD,IAAM,YAAY,CAAC,YAClB,YAAY,WAAW,QAAQ,UAAU,OAAO,QAAQ,SAAS;AAElE,IAAM,cAAc,CAAC,YACpB,aAAa,WAAW,QAAQ,WAAW,OAAO,QAAQ,UAAU;AAErE,IAAM,WAAW,CAAC,YACjB,aAAa,UAAU,QAAQ,UAAU,UAAU,QAAQ,MAAM;AAIlE,IAAM,OAAO,CAAC,KAAa,MAAc,cACxC;AAAA,EACC;AAAA,EACA;AAAA,IACC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C;AAAA,EACD;AAAA,EACA;AACD;AAGD,eAAsB,UACrB,KACA,MACA,SAAqB,EAAE,WAAW,SAAS,kBAAkB,OAAO,KAAK,GACtD;AACnB,MAAI,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,SAAS;AACpD,MAAI,OAAO,SAAS,CAAC,UAAU,OAAO,KAAK,YAAY,OAAO,GAAG;AAChE,UAAM,MAAM,SAAS,YAAY;AACjC,cAAU,MAAM,KAAK,KAAK,MAAM,OAAO,SAAS;AAAA,EACjD;AACA,MAAI,UAAU,OAAO,GAAG;AACvB,WAAO;AAAA,EACR;AACA,UAAQ;AAAA,IACP,sDAAsD,SAAS,OAAO,CAAC;AAAA,EACxE;AACA,SAAO;AACR;;;ACzCA,IAAM,MAAM;AAEZ,IAAI,kBAAkB;AAEf,SAAS,QAAQ,UAA+C;AACtE,QAAM,MAAM,YAAY,QAAQ,4BAA4B;AAC5D,MAAI,QAAQ,UAAa,IAAI,KAAK,MAAM,IAAI;AAC3C,WAAO;AAAA,EACR;AACA,QAAM,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC;AACjC,MAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG;AAC7B,QAAI,CAAC,iBAAiB;AACrB,wBAAkB;AAClB,cAAQ;AAAA,QACP;AAAA,MACD;AAAA,IACD;AACA,WAAO;AAAA,EACR;AACA,SAAO,EAAE,aAAa,MAAM,CAAC,GAAG,QAAQ,MAAM,CAAC,EAAE;AAClD;AAEA,SAAS,QAAQ,MAAkC;AAClD,MAAI;AACH,WAAO,OAAO,YAAY,cAAc,SAAY,QAAQ,IAAI,IAAI;AAAA,EACrE,QAAQ;AACP,WAAO;AAAA,EACR;AACD;;;AC9BA,SAAS,iBAAiB;;;ACA1B;AAAA,EACC,QAAU;AAAA,EACV,QAAU;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;;;AC/KA;AAAA,EACC,SAAW;AAAA,IACV;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ;AAAA,QACP;AAAA,MACD;AAAA,IACD;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,IACA;AAAA,MACC,KAAO;AAAA,MACP,KAAO;AAAA,MACP,MAAQ;AAAA,QACP;AAAA,QACA;AAAA,MACD;AAAA,MACA,MAAQ,CAAC;AAAA,IACV;AAAA,EACD;AACD;;;AFt5BA,IAAM,yBAAyB;AAC/B,IAAM,SAAS;AAOf,SAAS,QAAQ,OAAkC;AAClD,QAAM,iBAAiB,oBAAI,IAAsB;AACjD,aAAW,SAAS,MAAM,QAAQ;AACjC,UAAM,MAAM,MAAM,MAAM,GAAG,MAAM;AACjC,UAAM,OAAO,eAAe,IAAI,GAAG;AACnC,QAAI,MAAM;AACT,WAAK,KAAK,KAAK;AAAA,IAChB,OAAO;AACN,qBAAe,IAAI,KAAK,CAAC,KAAK,CAAC;AAAA,IAChC;AAAA,EACD;AAEA,QAAM,cAAc,IAAI,UAAU;AAClC,QAAM,YAAY,CAAC,OAAiB,SAA0B;AAC7D,eAAW,QAAQ,OAAO;AACzB,YAAM,CAAC,UAAU,IAAI,QAAQ,KAAK,IAAI,KAAK,MAAM,GAAG;AACpD,UAAI,CAAC,UAAU,UAAU,QAAW;AACnC,cAAM,IAAI,MAAM,mBAAmB,IAAI,EAAE;AAAA,MAC1C;AACA,kBAAY,UAAU,SAAS,OAAO,MAAM,GAAG,IAAI;AAAA,IACpD;AAAA,EACD;AACA,aAAW,UAAU,MAAM,UAAU;AACpC,cAAU,OAAO,MAAM,MAAM;AAC7B,cAAU,OAAO,MAAM,MAAM;AAAA,EAC9B;AAEA,SAAO,EAAE,gBAAgB,YAAY;AACtC;AAEA,IAAM,UAAU,QAAQ,EAAE,QAAQ,yBAAO,QAAQ,UAAU,4BAAO,QAAQ,CAAC;AAC3E,IAAI,SAA+B;AAG5B,SAAS,WAAW,OAAyB;AACnD,WAAS,QAAQ,KAAK;AACvB;AAEA,IAAM,OAAO,CAAC,eACb,aAAa,UAAW,UAAU;AAE5B,IAAM,YAAY,CAAC,IAAY,aAAa,UAClD,KAAK,UAAU,EAAE,YAAY,MAAM,IAAI,GAAG,SAAS,GAAG,IAAI,SAAS,MAAM;AAEnE,SAAS,cAAc,WAAmB,aAAa,OAAgB;AAC7E,QAAM,EAAE,eAAe,IAAI,KAAK,UAAU;AAC1C,QAAM,OAAO,UAAU,MAAM,GAAG,sBAAsB,EAAE,YAAY;AACpE,WAAS,IAAI,GAAG,IAAI,UAAU,KAAK,QAAQ,KAAK;AAC/C,UAAM,aAAa,eAAe,IAAI,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC;AAC/D,QAAI,YAAY;AACf,iBAAW,SAAS,YAAY;AAC/B,YAAI,KAAK,WAAW,OAAO,CAAC,GAAG;AAC9B,iBAAO;AAAA,QACR;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEO,SAAS,OAAO;AAAA,EACtB;AAAA,EACA;AAAA,EACA,aAAa;AACd,GAAyB;AACxB,QAAM,YAAY,QAAQ,YAAY;AACtC,SACE,cAAc,UAAa,cAAc,WAAW,UAAU,KAC/D,QAAQ,iBAAiB,MAAM,UAC/B,QAAQ,SAAS,UACjB,QAAQ,eAAe,YAAY,MAAM,UACxC,OAAO,QAAQ,UAAU,IAAI,UAAU;AAE1C;;;AGhGA,IAAM,UAAU,IAAI,YAAY;AAChC,IAAM,OAAO,oBAAI,IAAgC;AAEjD,SAAS,aAAa,QAAoC;AACzD,MAAI,MAAM,KAAK,IAAI,MAAM;AACzB,MAAI,CAAC,KAAK;AACT,UAAM,OAAO,OAAO;AAAA,MACnB;AAAA,MACA,QAAQ,OAAO,MAAM;AAAA,MACrB,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,MAChC;AAAA,MACA,CAAC,MAAM;AAAA,IACR;AACA,QAAI,MAAM,MAAM,KAAK,OAAO,MAAM,CAAC;AACnC,SAAK,IAAI,QAAQ,GAAG;AAAA,EACrB;AACA,SAAO;AACR;AAEA,IAAM,QAAQ,CAAC,UACd,MAAM;AAAA,EAAK,IAAI,WAAW,KAAK;AAAA,EAAG,CAAC,SAClC,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClC,EAAE,KAAK,EAAE;AAEV,eAAsB,eACrB,QACA,IACkB;AAClB,QAAM,YAAY,MAAM,OAAO,OAAO;AAAA,IACrC;AAAA,IACA,MAAM,aAAa,MAAM;AAAA,IACzB,QAAQ,OAAO,EAAE;AAAA,EAClB;AACA,SAAO,MAAM,SAAS;AACvB;;;AC/BA,IAAMA,YAAW,CAAC,UACjB,OAAO,UAAU,YAAY,UAAU;AAExC,IAAMC,iBAAgB,CAAC,UACtB,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,SAAS,OAAO,SAAS,QAAQ;AAGhE,SAAS,iBAAiB,MAAkC;AAClE,MAAI,CAACD,UAAS,IAAI,KAAK,KAAK,YAAY,GAAG;AAC1C,WAAO;AAAA,EACR;AACA,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,MAAI,CAACC,eAAc,MAAM,KAAK,CAAC,MAAM,QAAQ,QAAQ,GAAG;AACvD,WAAO;AAAA,EACR;AACA,QAAM,SAAiC,CAAC;AACxC,aAAW,SAAS,UAAU;AAC7B,QACC,CAACD,UAAS,KAAK,KACf,CAACC,eAAc,MAAM,IAAI,KACzB,CAACA,eAAc,MAAM,IAAI,GACxB;AACD,aAAO;AAAA,IACR;AACA,WAAO,KAAK,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,EACnD;AACA,SAAO;AAAA,IACN,QAAQ,OAAO,IAAI,CAAC,UAAU,MAAM,YAAY,CAAC;AAAA,IACjD,UAAU;AAAA,EACX;AACD;AAEA,IAAI,gBAAgB;AACpB,IAAI,gBAAgB,OAAO;AAE3B,eAAe,WAAW,QAAkC;AAC3D,QAAM,WAAW,MAAM,MAAM,GAAG,MAAM,+BAA+B;AAAA,IACpE,QAAQ,YAAY,QAAQ,SAAS,gBAAgB;AAAA,EACtD,CAAC;AACD,MAAI,CAAC,SAAS,IAAI;AACjB,WAAO;AAAA,EACR;AACA,QAAM,OAAgB,MAAM,SAAS,KAAK;AAC1C,QAAM,QAAQ,iBAAiBD,UAAS,IAAI,IAAI,KAAK,OAAO,IAAI;AAChE,MAAI,CAAC,OAAO;AACX,WAAO;AAAA,EACR;AACA,aAAW,KAAK;AAChB,SAAO;AACR;AAMO,SAAS,aAAa,QAAsC;AAClE,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,OAAO,gBAAgB,SAAS,iBAAiB,SAAS;AAChE,MAAI,MAAM,gBAAgB,MAAM;AAC/B,WAAO;AAAA,EACR;AACA,kBAAgB;AAChB,kBAAgB;AAChB,SAAO,WAAW,MAAM,EAAE;AAAA,IACzB,CAAC,OAAO;AACP,sBAAgB;AAAA,IACjB;AAAA,IACA,MAAM;AAAA,EACP;AACD;;;ACnEA,IAAM,kBAAkB,uBAAO,IAAI,yBAAyB;AAErD,SAAS,uBAA8D;AAC7E,QAAM,QAAyC,QAAQ;AAAA,IACtD;AAAA,IACA;AAAA,EACD;AACA,SAAO,OAAO,MAAM;AACrB;AAGO,SAAS,kBAAyC;AACxD,QAAM,UAAU,qBAAqB;AACrC,SAAO,SAAS,YACb,CAAC,YAAY,QAAQ,YAAY,OAAO,IACxC;AACJ;;;AChBO,SAAS,iBAAuC;AACtD,MAAI;AACH,UAAM,UAAU,qBAAqB;AACrC,QAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACpD,aAAO;AAAA,IACR;AACA,QAAI,WAAW,WAAW,cAAc,sBAAsB;AAC7D,aAAO;AAAA,IACR;AAAA,EACD,QAAQ;AAAA,EAAC;AACT,SAAO;AACR;;;ACIO,SAAS,oBACf,OAC6B;AAC7B,SACC,OAAO,QAAQ,IAAI,OAAO,UAAU,MAAM,YAC1C,OAAO,QAAQ,IAAI,OAAO,KAAK,MAAM;AAEvC;AAGA,SAAS,SAAS,MAAyC;AAC1D,MAAI,CAAC,MAAM;AACV,WAAO;AAAA,EACR;AACA,MAAI;AACH,UAAM,MAAM,IAAI,IAAI,WAAW,IAAI,EAAE;AACrC,UAAM,OACL,IAAI,aAAa,OACjB,IAAI,WAAW,MACf,IAAI,SAAS,MACb,IAAI,aAAa,MACjB,IAAI,aAAa;AAClB,WAAO,OAAO,IAAI,SAAS;AAAA,EAC5B,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAGO,SAAS,WAAW,MAAyC;AACnE,QAAM,SAAS,SAAS,IAAI;AAC5B,SAAO,WAAW,OAAO,OAAO,IAAI,IAAI,MAAM,EAAE;AACjD;AAGO,SAAS,qBACf,OACA,MACgB;AAChB,QAAM,UAAU,MAAM,UAAU,IAAI,KAAK,CAAC,GACxC,IAAI,CAAC,UAAU,OAAO,KAAK,EAC3B,OAAO,CAAC,UAAU,OAAO,UAAU,QAAQ;AAC7C,SAAO,OAAO,WAAW,IAAI,OAAO,OAAO,KAAK,IAAI;AACrD;AAEO,SAAS,iBACf,OACA,MACgB;AAChB,QAAM,QAAQ,MAAM,UAAU,IAAI,IAAI,CAAC,GAAG;AAC1C,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC5C;AAGA,SAAS,kBAAkB,OAA8C;AACxE,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,QAAQ,YAAY;AAC9B,eAAW,SAAS,QAAQ,IAAI,KAAK,CAAC,GAAG;AACxC,UAAI,OAAO,OAAO,UAAU,UAAU;AACrC;AAAA,MACD;AACA,UAAI;AACH,gBAAQ,OAAO,MAAM,MAAM,KAAK;AAAA,MACjC,QAAQ;AAAA,MAAC;AAAA,IACV;AAAA,EACD;AACA,SAAO;AACR;AAGO,SAAS,eACf,OACe;AACf,MAAI,CAAC,oBAAoB,KAAK,GAAG;AAChC,WAAO;AAAA,MACN,KAAK,MAAM;AAAA,MACX,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,IAAI,SAAS,MAAM,OAAO;AAAA,MAC1B,UAAU,eAAe;AAAA,IAC1B;AAAA,EACD;AACA,QAAM,SAAS,SAAS,MAAM,SAAS,OAAO,CAAC,GAAG,KAAK;AACvD,QAAM,QAAQ,MAAM,cAAc,IAAI,MAAM,WAAW,KAAK;AAC5D,QAAM,KAAK,MAAM,SAAS,KAAK,EAAE,YAAY;AAC7C,SAAO;AAAA,IACN,KAAK,WAAW,OAAO,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,GAAG,KAAK;AAAA,IAC3D,QAAQ,MAAM;AAAA,IACd,SAAS,kBAAkB,MAAM,OAAO;AAAA,IACxC,IAAI,KAAK,KAAK;AAAA,IACd,UAAU;AAAA,EACX;AACD;;;ACzBA,IAAM,OAAO,oBAAI,IAAmB;AAM7B,SAAS,QACf,SACA,UAA0B,CAAC,GACjB;AACV,oBAAkB,OAAO;AACzB,SAAO,aAAa,SAAS,QAAQ,UAAU,SAAS;AACzD;AAEA,SAAS,aACR,SACA,aAAa,OACH;AACV,MAAI;AACH,UAAM,EAAE,SAAS,GAAG,IAAI,eAAe,OAAO;AAC9C,WAAO,OAAO,EAAE,SAAS,kBAAkB,OAAO,GAAG,IAAI,WAAW,CAAC;AAAA,EACtE,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAS,kBAAkB,SAA+B;AACzD,MAAI;AACH,QAAI,QAAQ,UAAU,WAAW;AAChC;AAAA,IACD;AACA,UAAM,SAAS,cAAc,OAAO;AACpC,UAAM,OAAO,SAAS,aAAa,MAAM,IAAI;AAC7C,QAAI,MAAM;AACT,eAAS,MAAM,QAAQ,SAAS;AAAA,IACjC;AAAA,EACD,QAAQ;AAAA,EAAC;AACV;AAOO,SAAS,iBACf,SACA,UAAwB,CAAC,GACT;AAChB,OAAK,IAAI,kBAAkB;AAC3B,MAAI;AACH,UAAM,MAAM,QAAQ,QAAQ,GAAG;AAC/B,QAAI,CAAC,KAAK;AACT,aAAO,QAAQ,QAAQ;AAAA,IACxB;AACA,UAAM,SAAS,cAAc,OAAO;AACpC,QAAI,CAAC,QAAQ;AACZ,aAAO,QAAQ,QAAQ;AAAA,IACxB;AACA,UAAM,UAAU,QAAQ,QAAQ,IAAI;AACpC,UAAM;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACD,IAAI,eAAe,OAAO;AAC1B,QAAI,eAAe,MAAM;AACxB,aAAO,QAAQ,QAAQ;AAAA,IACxB;AACA,UAAM,SAAS,kBAAkB,cAAc;AAE/C,UAAM,UAAkC,CAAC;AACzC,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACnD,UAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AACvB,gBAAQ,IAAI,IAAI;AAAA,MACjB;AAAA,IACD;AACA,UAAM,MAAM,IAAI,IAAI,UAAU;AAC9B,UAAM,SAA2B;AAAA,MAChC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC;AAAA,MACA,MAAM,IAAI;AAAA,MACV,MAAM,IAAI,WAAW,IAAI;AAAA,MACzB;AAAA,MACA,eAAe,OAAO,WAAW;AAAA,MACjC,cAAc;AAAA,MACd,MAAM,CAAC;AAAA,IACR;AACA,QAAI,OAAO,YAAY,MAAM,UAAa,CAAC,QAAQ,IAAI,YAAY,GAAG;AACrE,aAAO,YAAY,OAAO,YAAY;AAAA,IACvC;AACA,QAAI,OAAO,YAAY,UAAa,CAAC,QAAQ,IAAI,SAAS,GAAG;AAC5D,aAAO,UAAU,OAAO;AAAA,IACzB;AACA,QAAI,aAAa,QAAW;AAC3B,aAAO,WAAW;AAAA,IACnB;AAEA,UAAM,SACL,aAAa,eACV,EAAE,WAAW,SAAS,qBAAqB,OAAO,MAAM,IACxD,EAAE,WAAW,SAAS,kBAAkB,OAAO,KAAK;AACxD,UAAM,YAAY,GAAG,MAAM,2CAA2C,IAAI,WAAW;AACrF,UAAM,eAAe,YAAY;AAEhC,YAAM,QAAQ,QAAQ;AACtB,aAAO,OAAO,CAAC,GAAG,IAAI;AACtB,UAAI,OAAO,QAAQ,CAAC,QAAQ,IAAI,UAAU,GAAG;AAC5C,eAAO,WAAW,MAAM,eAAe,IAAI,QAAQ,EAAE,EAAE;AAAA,UACtD,MAAM;AAAA,QACP;AAAA,MACD;AACA,YAAM;AAAA,QACL;AAAA,QACA,KAAK,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;AAAA,QACrC;AAAA,MACD;AAAA,IACD;AACA,WAAO,SAAS,aAAa,GAAG,QAAQ,SAAS;AAAA,EAClD,QAAQ;AACP,WAAO,QAAQ,QAAQ;AAAA,EACxB;AACD;AAuBA,IAAM,UAAoC;AAAA,EACzC,QAAQ;AAAA,EACR,SAAS;AAAA,IACR,gBAAgB;AAAA,IAChB,sBAAsB;AAAA,IACtB,iBAAiB;AAAA,IACjB,MAAM;AAAA,EACP;AACD;AAeA,eAAsB,eACrB,SACA,UAAiC,CAAC,GACoB;AACtD,OAAK,IAAI,gBAAgB;AACzB,MAAI;AACH,WAAO,oBAAoB,OAAO,IAC/B,MAAM,gBAAgB,SAAS,OAAO,IACtC,MAAM,WAAW,SAAS,OAAO;AAAA,EACrC,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,eAAe,WACd,SACA,SAC2B;AAC3B,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,MACC,QAAQ,QAAQ,OAAO,YAAY;AAAA,MACnC,UAAU,IAAI;AAAA,MACd,UAAU,IAAI;AAAA,MACd,WAAW,QAAQ,QAAQ,IAAI,YAAY,KAAK;AAAA,MAChD,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAAA,MACzC,WAAW,QAAQ,QAAQ,IAAI,mBAAmB;AAAA,MAClD,OAAO,aAAa,OAAO;AAAA,IAC5B;AAAA,IACA;AAAA,EACD;AACA,MAAI,CAAC,QAAQ;AACZ,WAAO;AAAA,EACR;AACA,SAAO,IAAI,SAAS,OAAO,MAAM;AAAA,IAChC,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,EACjB,CAAC;AACF;AAEA,eAAe,gBACd,SACA,SAC2C;AAC3C,QAAM,OAAO,gBAAgB,OAAO;AACpC,MAAI,SAAS,MAAM;AAClB,WAAO;AAAA,EACR;AACA,QAAM,EAAE,UAAU,OAAO,IAAI;AAC7B,QAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,MAAI,CAAC,aAAa,MAAM,GAAG;AAC1B,WAAO;AAAA,EACR;AACA,QAAM,QAAQ,MAAM,SAAS,UAAU,OAAO;AAC9C,MAAI,CAAC,OAAO;AACX,WAAO;AAAA,EACR;AACA,QAAM,SAAS,MAAM;AAAA,IACpB,MAAM;AAAA,IACN;AAAA,IACA,cAAc,QAAQ,GAAG;AAAA,IACzB;AAAA,IACA;AAAA,EACD;AACA,MAAI,CAAC,QAAQ;AACZ,WAAO;AAAA,EACR;AACA,QAAM,SAAS,qBAAqB,MAAM;AAC1C,MACC,OAAO,SAAS,UAChB,IAAI,YAAY,EAAE,OAAO,OAAO,IAAI,EAAE,aACrC,SAAS,wBACT;AACD,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAEA,SAAS,qBAAqB,QAA8C;AAC3E,QAAM,UAA+C,CAAC;AACtD,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AAC3D,YAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,UAAU,IAAI,GAAG,MAAM,CAAC;AAAA,EACjD;AACA,QAAM,SAAmC;AAAA,IACxC,QAAQ,OAAO,OAAO,MAAM;AAAA,IAC5B,mBAAmB,OAAO,WAAW,MAAM,OAAO;AAAA,IAClD;AAAA,EACD;AACA,MAAI,OAAO,SAAS,MAAM;AACzB,WAAO,OACN,OAAO,OAAO,SAAS,WACpB,OAAO,OACP,IAAI,YAAY,EAAE,OAAO,OAAO,IAAI;AAAA,EACzC;AACA,SAAO;AACR;AAEA,SAAS,UAAU,MAAsB;AACxC,SAAO,KACL,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AACX;AAEA,IAAM,eAAe,CAAC,WACrB,WAAW,SAAS,WAAW;AAEhC,eAAe,SACd,UACA,SAC4B;AAC5B,QAAM,MAAM,QAAQ,QAAQ,GAAG;AAC/B,MAAI,CAAC,KAAK;AACT,WAAO;AAAA,EACR;AACA,QAAM,SAAS,cAAc,OAAO;AACpC,MAAI,CAAC,QAAQ;AACZ,WAAO;AAAA,EACR;AACA,QAAM,QAAQ,MAAM;AAAA,IAAc;AAAA,IAAQ,IAAI;AAAA,IAAa,CAAC,SAC3D,SAAS,MAAM,MAAS;AAAA,EACzB;AACA,MAAI,CAAC,OAAO;AACX,WAAO;AAAA,EACR;AACA,QAAM,OAAO,MAAM,MAAM,KAAK,CAAC,UAAU,MAAM,aAAa,QAAQ;AACpE,SAAO,OAAO,EAAE,OAAO,KAAK,IAAI;AACjC;AAEA,SAAS,cACR,MACA,WACA,QACoB;AACpB,SAAO,KAAK,uBAAuB,KAAK,CAAC,UAAU,UAAU,SAAS,KAAK,CAAC,IACzE,EAAE,GAAG,SAAS,MAAM,WAAW,SAAS,OAAO,YAAY,IAC3D;AACJ;AAEA,eAAe,gBACd,SACA,SAC6B;AAC7B,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,MACC,CAAC,aAAa,MAAM,KACpB,EAAE,QAAQ,SAAS,gBAAgB,QAAQ,MAAM,IAChD;AACD,WAAO;AAAA,EACR;AACA,QAAM,QAAQ,MAAM,SAAS,UAAU,OAAO;AAC9C,MAAI,CAAC,OAAO;AACX,WAAO;AAAA,EACR;AACA,QAAM,EAAE,OAAO,KAAK,IAAI;AACxB,QAAM,YAAY,QAAQ,UAAU,YAAY;AAChD,QAAM,UAAU,cAAc,MAAM,WAAW,MAAM;AACrD,MAAI,SAAS;AACZ,WAAO;AAAA,EACR;AACA,MAAI,QAAQ,aAAa,CAAC,KAAK,WAAW,SAAS;AAClD,WAAO;AAAA,EACR;AACA,QAAM,OAAO,cAAc,QAAQ,QAAQ;AAC3C,MAAI,CAAC,YAAY,MAAM,KAAK,WAAW,KAAK,GAAG;AAC9C,WAAO;AAAA,EACR;AACA,QAAM,SAAS,aAAa,OAAO,WAAW,QAAQ,MAAM;AAC5D,SAAO,UAAU,WAAW,OAAO,UAAU,MAAM,QAAQ,MAAM;AAClE;AAGA,SAAS,aACR,OACA,WACA,QACoB;AACpB,MAAI,MAAM,gBAAgB,KAAK,CAAC,UAAU,UAAU,SAAS,KAAK,CAAC,GAAG;AACrE,WAAO,WAAW,MAAM;AAAA,EACzB;AACA,SAAO,gBAAgB,MAAM,IAAI,aAAa;AAC/C;AAEA,eAAe,WACd,OACA,UACA,MACA,QACA,QAC6B;AAC7B,QAAM,OAAO,MAAM,eAAe,OAAO,UAAU,MAAM,MAAM;AAC/D,MAAI,CAAC,MAAM;AACV,WAAO;AAAA,EACR;AACA,SAAO;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,MACR,gBAAgB,KAAK;AAAA,MACrB,yBAAyB;AAAA,MACzB,iBAAiB;AAAA,MACjB,MAAM;AAAA,IACP;AAAA,IACA,MAAM,WAAW,SAAS,OAAO,KAAK;AAAA,EACvC;AACD;AAEA,IAAM,mBAAmB;AAEzB,IAAM,eAAsC,CAAC,YAAY,MAAM;AAG/D,SAAS,cAAc,KAId;AACR,MAAI,CAAC,IAAI,WAAW,gBAAgB,GAAG;AACtC,WAAO;AAAA,EACR;AACA,QAAM,OAAO,IAAI,MAAM,iBAAiB,MAAM;AAC9C,QAAM,YAAY,KAAK,QAAQ,GAAG;AAClC,QAAM,aAAa,cAAc,KAAK,OAAO,KAAK,MAAM,GAAG,SAAS;AACpE,QAAM,SAAS,aAAa,KAAK,CAAC,UAAU,UAAU,UAAU,KAAK;AACrE,QAAM,cAAc,cAAc,KAAK,KAAK,KAAK,MAAM,YAAY,CAAC;AACpE,QAAM,QAAQ,YAAY,QAAQ,GAAG;AACrC,SAAO,UAAU,KACd,EAAE,QAAQ,aAAa,aAAa,aAAa,IAAI,IACrD;AAAA,IACA;AAAA,IACA,aAAa,YAAY,MAAM,GAAG,KAAK;AAAA,IACvC,aAAa,YAAY,MAAM,KAAK;AAAA,EACrC;AACH;AAEA,SAAS,gBAAgB,SAAkC;AAC1D,MAAI,SAAS,cAAc,QAAQ,GAAG;AACtC,SAAO,QAAQ;AACd,YAAQ,MAAM,OAAO;AACrB,aAAS,cAAc,QAAQ,GAAG;AAAA,EACnC;AACD;AAEA,SAAS,gBACR,SACkD;AAClD,QAAM,QAAQ,cAAc,QAAQ,GAAG;AACvC,MAAI,CAAC,OAAO;AACX,WAAO;AAAA,EACR;AACA,UAAQ,MAAM,MAAM;AACpB,MAAI,MAAM,WAAW,MAAM;AAC1B,WAAO;AAAA,EACR;AACA,MAAI;AACH,UAAM,WAAW,WAAW,mBAAmB,MAAM,WAAW,CAAC;AACjE,WAAO,aAAa,OAAO,OAAO,EAAE,UAAU,QAAQ,MAAM,OAAO;AAAA,EACpE,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAQA,eAAsB,oBACrB,SACA,UAAiC,CAAC,GACS;AAC3C,OAAK,IAAI,qBAAqB;AAC9B,MAAI;AACH,oBAAgB,OAAO;AACvB,UAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,UAAM,SAAS,qBAAqB,SAAS,QAAQ,KAAK;AAC1D,QACC,CAAC,aAAa,MAAM,KACpB,EAAE,gBAAgB,MAAM,KAAK,QAAQ,SAAS,OAAO,IACpD;AACD,aAAO;AAAA,IACR;AACA,UAAM,WAAW,WAAW,iBAAiB,SAAS,MAAM,KAAK,MAAS;AAC1E,QAAI,aAAa,MAAM;AACtB,aAAO;AAAA,IACR;AACA,UAAM,QAAQ,MAAM,SAAS,UAAU,OAAO;AAC9C,QAAI,CAAC,OAAO;AACX,aAAO;AAAA,IACR;AACA,UAAM,EAAE,OAAO,KAAK,IAAI;AACxB,UAAM,aACL,iBAAiB,SAAS,YAAY,KAAK,IAC1C,YAAY;AACd,UAAM,UAAU,cAAc,MAAM,WAAW,MAAM;AACrD,QAAI,SAAS;AACZ,aAAO,qBAAqB,OAAO;AAAA,IACpC;AACA,UAAM,SAAS,aAAa,OAAO,WAAW,MAAM;AACpD,QACC,iBAAiB,SAAS,mBAAmB,MAAM,QACnD,CAAC,KAAK,WAAW,WACjB,CAAC,YAAY,cAAc,QAAQ,GAAG,GAAG,KAAK,WAAW,KAAK,KAC9D,WAAW,MACV;AACD,aAAO;AAAA,IACR;AACA,UAAM,MAAM,GAAG,gBAAgB,GAAG,MAAM,IAAI,mBAAmB,QAAQ,CAAC,GAAG,QAAQ,GAAG;AACtF,QACC,IAAI,YAAY,EAAE,OAAO,GAAG,EAAE,cAAc,SAAS,uBACpD;AACD,cAAQ,MAAM;AAAA,IACf;AACA,WAAO;AAAA,EACR,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,IAAI,eAAe;AAEnB,SAAS,cAAc,SAAwC;AAC9D,MAAI,QAAQ,WAAW,QAAW;AACjC,WAAO,QAAQ,OAAO,QAAQ,QAAQ,EAAE;AAAA,EACzC;AACA,QAAM,SAAS,QAAQ,UAAU;AACjC,MAAI,OAAO,OAAO,SAAS,SAAS,MAAM,GAAG;AAC5C,WAAO,SAAS,QAAQ,MAAM;AAAA,EAC/B;AACA,MAAI,CAAC,cAAc;AAClB,mBAAe;AACf,YAAQ;AAAA,MACP,oCAAoC,KAAK,UAAU,MAAM,CAAC;AAAA,IAC3D;AAAA,EACD;AACA,SAAO;AACR;AAEA,SAAS,QAAQ,MAA8C;AAC9D,QAAM,QAAQ,IAAI,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC,CAAC;AACpE,MAAI,MAAM,IAAI,WAAW,GAAG;AAC3B,UAAM,IAAI,YAAY;AAAA,EACvB;AACA,SAAO;AACR;AAEA,SAAS,SACR,MACA,WACgB;AAChB,QAAM,UAAU,KAAK;AAAA,IACpB,MAAM;AAAA,IACN,MAAM;AAAA,EACP;AACA,MAAI;AACH,KAAC,aAAa,gBAAgB,KAAK,OAAO;AAAA,EAC3C,QAAQ;AAAA,EAAC;AACT,SAAO;AACR;","names":["isObject","isStringArray"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waniwani/relay",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "description": "Records AI agent visits to your website from your middleware and posts them to WaniWani. Open source, so your security team can read every line.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",