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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
import { load } from "cheerio";
import { AwsClient } from "aws4fetch";
import type { NextRequest } from "next/server";
import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function POST(request: NextRequest) {
const r2 = new AwsClient({
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
});
async function unfurl(url: string) {
const response = await fetch(url);
if (response.status >= 400) {
throw new Error(`Error fetching url: ${response.status}`);
}
const contentType = response.headers.get("content-type");
if (!contentType?.includes("text/html")) {
throw new Error(`Content-type not right: ${contentType}`);
}
const content = await response.text();
const $ = load(content);
const og: { [key: string]: string | undefined } = {};
const twitter: { [key: string]: string | undefined } = {};
$("meta[property^=og:]").each(
// @ts-ignore, it just works so why care of type safety if someone has better way go ahead
(_, el) => (og[$(el).attr("property")!] = $(el).attr("content")),
);
$("meta[name^=twitter:]").each(
// @ts-ignore
(_, el) => (twitter[$(el).attr("name")!] = $(el).attr("content")),
);
const title =
og["og:title"] ??
twitter["twitter:title"] ??
$("title").text() ??
undefined;
const description =
og["og:description"] ??
twitter["twitter:description"] ??
$('meta[name="description"]').attr("content") ??
undefined;
const image =
og["og:image:secure_url"] ??
og["og:image"] ??
twitter["twitter:image"] ??
undefined;
return {
title,
description,
image,
};
}
const d = await ensureAuth(request);
if (!d) {
return new Response("Unauthorized", { status: 401 });
}
if (
!process.env.R2_ACCESS_KEY_ID ||
!process.env.R2_ACCOUNT_ID ||
!process.env.R2_SECRET_ACCESS_KEY ||
!process.env.R2_BUCKET_NAME
) {
return new Response(
"Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.",
{ status: 500 },
);
}
const website = new URL(request.url).searchParams.get("website");
if (!website) {
return new Response("Missing website", { status: 400 });
}
const salt = () => Math.floor(Math.random() * 11);
const encodeWebsite = `${encodeURIComponent(website)}${salt()}`;
try {
// this returns the og image, description and title of website
const response = await unfurl(website);
if (!response.image) {
return new Response(JSON.stringify(response));
}
if (!process.env.DEV_IMAGES) {
return new Response("Missing DEV_IMAGES namespace.", { status: 500 });
}
const imageUrl = await process.env.DEV_IMAGES!.get(encodeWebsite);
if (imageUrl) {
return new Response(
JSON.stringify({
image: imageUrl,
title: response.title,
description: response.description,
}),
);
}
const res = await fetch(`${response.image}`);
const image = await res.blob();
const url = new URL(
`https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
);
url.pathname = encodeWebsite;
url.searchParams.set("X-Amz-Expires", "3600");
const signedPuturl = await r2.sign(
new Request(url, {
method: "PUT",
}),
{
aws: { signQuery: true },
},
);
await fetch(signedPuturl.url, {
method: "PUT",
body: image,
});
await process.env.DEV_IMAGES.put(
encodeWebsite,
`${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
);
return new Response(
JSON.stringify({
image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
title: response.title,
description: response.description,
}),
);
} catch (error) {
console.log(error);
return new Response(
JSON.stringify({
status: 500,
error: error,
}),
);
}
}
|