-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathproxy.ts
More file actions
120 lines (99 loc) Β· 3.17 KB
/
Copy pathproxy.ts
File metadata and controls
120 lines (99 loc) Β· 3.17 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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
function parseMediaType(mt: string): { type: string; quality: number } {
const parts = mt.trim().split(";");
const type = parts[0].trim().toLowerCase();
let quality = 1;
for (let i = 1; i < parts.length; i++) {
const param = parts[i].trim();
if (param.startsWith("q=")) {
quality = parseFloat(param.slice(2)) || 0;
}
}
return { type, quality };
}
function shouldServeMarkdown(acceptHeader: string | null): boolean {
if (!acceptHeader) return false;
type MediaInfo = { quality: number; position: number };
const found: Record<string, MediaInfo> = {};
const relevantTypes = ["text/html", "text/markdown", "text/plain"];
acceptHeader.split(",").forEach((mt, i) => {
const { type, quality } = parseMediaType(mt);
if (relevantTypes.includes(type) && !found[type]) {
found[type] = { quality, position: i };
}
});
const html = found["text/html"];
const markdown = found["text/markdown"];
const plain = found["text/plain"];
if (!markdown && !plain) return false;
if (!html) return true;
const preferHigherQuality = (a: MediaInfo, b: MediaInfo): MediaInfo => {
if (a.quality !== b.quality) {
return a.quality > b.quality ? a : b;
}
return a.position < b.position ? a : b;
};
let best: MediaInfo;
if (!markdown) {
best = plain;
} else if (!plain) {
best = markdown;
} else {
best = preferHigherQuality(markdown, plain);
}
return preferHigherQuality(best, html) === best;
}
const LLM_USER_AGENTS = [
"GPTBot",
"ChatGPT-User",
"Claude-Web",
"Claudebot",
"Anthropic",
"CCBot",
"PerplexityBot",
"Google-Extended",
"Bytespider",
"cohere-ai",
];
function isLLMAgent(userAgent: string | null): boolean {
if (!userAgent) return false;
return LLM_USER_AGENTS.some((agent) =>
userAgent.toLowerCase().includes(agent.toLowerCase())
);
}
export function proxy(request: NextRequest) {
const userAgent = request.headers.get("user-agent");
const acceptHeader = request.headers.get("accept");
const { pathname } = request.nextUrl;
// Auth check for /visitors/gang routes
const isAuthenticated = request.cookies.get("auth");
if (!isAuthenticated && pathname.startsWith("/visitors/gang")) {
return NextResponse.redirect(new URL("/visitors/login", request.url));
}
// Skip for static files, API routes, and already markdown routes
if (
pathname.startsWith("/_next") ||
pathname.startsWith("/api") ||
pathname.startsWith("/md") ||
pathname.includes(".") // static files like .ico, .png, etc.
) {
return NextResponse.next();
}
// Serve markdown if Accept header prefers it, or if it's an LLM agent
if (shouldServeMarkdown(acceptHeader) || isLLMAgent(userAgent)) {
const url = request.nextUrl.clone();
url.pathname = "/api/md";
url.searchParams.set("path", pathname);
const response = NextResponse.rewrite(url);
response.headers.set("x-original-path", pathname);
return response;
}
return NextResponse.next();
}
export const config = {
matcher: [
"/visitors/gang/:path*",
"/((?!_next/static|_next/image|favicon.ico|.*\\.).*)",
],
};