-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathgetFormat.ts
More file actions
80 lines (66 loc) · 2.91 KB
/
getFormat.ts
File metadata and controls
80 lines (66 loc) · 2.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*
This implementation allows the following
1. SEO Crawlers must be served SEO-friendly HTML at all cost
2. The path may end with a segment with an extension such as .html, .md, or .json, as https://llmstxt.org suggests. This allows easy navigation when testing in browsers.
3. If accept `*\/*` is provided (such as with some clis) it will default to text/markdown, which is desired for simple implementation with curl and fetch.
4. If none of the above is true, it will use the accept header and find the first matching format, or return 'null' if no format matches.
By default this function requires also supports yaml and png.
- YAML is a great alternative to JSON since it's more information-dense than JSON.
- PNG is used for retrieving the og-image, and is added to the default since og-images are an essential way to improve URL shareability.
*/
const getCrawler = (userAgent: string | null) => {
const crawlers = [
{ name: "Facebook", userAgentRegex: /facebookexternalhit|Facebot/ },
{ name: "Twitter", userAgentRegex: /Twitterbot/ },
{ name: "LinkedIn", userAgentRegex: /LinkedInBot/ },
{ name: "Slack", userAgentRegex: /Slackbot-LinkExpanding/ },
{ name: "Discord", userAgentRegex: /Discordbot/ },
{ name: "WhatsApp", userAgentRegex: /WhatsApp/ },
{ name: "Telegram", userAgentRegex: /TelegramBot/ },
{ name: "Pinterest", userAgentRegex: /Pinterest/ },
{ name: "Google", userAgentRegex: /Googlebot/ },
{ name: "Bing", userAgentRegex: /bingbot/ },
];
const crawler = crawlers.find((item) =>
item.userAgentRegex.test(userAgent || ""),
)?.name;
return crawler;
};
const allowedFormats = {
md: "text/markdown",
html: "text/html",
json: "application/json",
yaml: "text/yaml",
png: "image/png",
} as const;
type AllowedFormat = (typeof allowedFormats)[keyof typeof allowedFormats];
/** Useful function to determine what to respond with */
export const getFormat = (request: Request): AllowedFormat | null => {
const accept = request.headers.get("accept") || "*/*";
const pathname = new URL(request.url).pathname;
const segmentChunks = pathname.split("/").pop()!.split(".");
const ext =
segmentChunks.length > 1
? (segmentChunks.pop()! as AllowedFormat)
: undefined;
if (ext && Object.keys(allowedFormats).includes(ext)) {
// allow path to determine format. comes before crawler since this allows easy changing
return Object.entries(allowedFormats).find(
(entry) => entry[0] === ext,
)?.[1]!;
}
const crawler = getCrawler(request.headers.get("user-agent"));
if (crawler) {
return "text/html";
}
if (accept === "*/*") {
return "text/markdown";
}
const acceptedFormats = accept
.split(",")
.map((f) => f.trim().split(";")[0].trim());
const allowedFomat = acceptedFormats.find((format) =>
Object.values(allowedFormats).includes(format as AllowedFormat),
) as AllowedFormat | undefined;
return allowedFomat || null;
};