+4
-4
manifest.yaml
+4
-4
manifest.yaml
···
1
1
display_information:
2
-
name: Tacy
2
+
name: tacy
3
3
description: Automatically uploads files to R2 and provides public URLs
4
-
background_color: "#16302b"
4
+
background_color: "#0e2d52"
5
5
features:
6
6
bot_user:
7
7
display_name: Tacy
···
12
12
- files:read
13
13
- reactions:write
14
14
- chat:write
15
+
- im:history
16
+
- channels:history
15
17
settings:
16
18
event_subscriptions:
17
19
request_url: https://l4.dunkirk.sh/slack/events
18
20
bot_events:
19
21
- message.channels
20
-
- message.groups
21
22
- message.im
22
-
- message.mpim
23
23
org_deploy_enabled: false
24
24
socket_mode_enabled: false
25
25
token_rotation_enabled: false
+6
-86
src/index.ts
+6
-86
src/index.ts
···
153
153
return new Response("Not found", { status: 404 });
154
154
}
155
155
156
-
// Prevent infinite loops
157
-
if (/image-resizing/.test(request.headers.get("via") || "")) {
158
-
const object = await env.IMAGES.get(imageKey);
159
-
if (!object) {
160
-
return new Response("Not found", { status: 404 });
161
-
}
162
-
return new Response(object.body, {
163
-
headers: {
164
-
"Content-Type":
165
-
object.httpMetadata?.contentType || "application/octet-stream",
166
-
"Cache-Control": "public, max-age=31536000",
167
-
},
168
-
});
169
-
}
170
-
171
-
// Parse transformation params
172
-
const width = url.searchParams.get("w");
173
-
const height = url.searchParams.get("h");
174
-
const quality = url.searchParams.get("q") || "85";
175
-
const format = url.searchParams.get("f") || "auto";
176
-
const fit = url.searchParams.get("fit") || "scale-down";
177
-
178
-
// Check cache first
179
-
const cacheKey = new Request(url.toString(), request);
180
-
const cache = caches.default;
181
-
let response = await cache.match(cacheKey);
182
-
if (response) {
183
-
return response;
184
-
}
185
-
186
-
// Fetch from R2
187
-
const object = await env.IMAGES.get(imageKey);
156
+
// Verify object exists before redirecting
157
+
const object = await env.IMAGES.head(imageKey);
188
158
if (!object) {
189
159
return new Response("Not found", { status: 404 });
190
160
}
191
161
192
-
// In local dev, Cloudflare image transformations don't work
193
-
// So we just serve the original image
194
-
const isLocalDev =
195
-
url.hostname === "localhost" || url.hostname.includes("127.0.0.1");
196
-
197
-
if (isLocalDev) {
198
-
const headers = new Headers();
199
-
headers.set(
200
-
"Content-Type",
201
-
object.httpMetadata?.contentType || "image/png",
202
-
);
203
-
headers.set("Cache-Control", "public, max-age=31536000");
204
-
return new Response(object.body, { headers });
205
-
}
206
-
207
-
// Build image transformation options
208
-
const imageOptions: any = {
209
-
quality: parseInt(quality),
210
-
format,
211
-
fit,
212
-
};
213
-
214
-
if (width) imageOptions.width = parseInt(width);
215
-
if (height) imageOptions.height = parseInt(height);
216
-
217
-
// Determine format based on Accept header if auto
218
-
if (format === "auto") {
219
-
const accept = request.headers.get("accept") || "";
220
-
if (/image\/avif/.test(accept)) {
221
-
imageOptions.format = "avif";
222
-
} else if (/image\/webp/.test(accept)) {
223
-
imageOptions.format = "webp";
224
-
}
225
-
}
226
-
227
-
// Fetch and transform
228
-
const imageResponse = await fetch(request.url, {
229
-
cf: {
230
-
image: imageOptions,
231
-
},
232
-
});
233
-
234
-
// Clone response with cache headers
235
-
response = new Response(imageResponse.body, imageResponse);
236
-
response.headers.set(
237
-
"Cache-Control",
238
-
"public, max-age=31536000, s-maxage=86400",
239
-
);
240
-
response.headers.set("Vary", "Accept");
241
-
242
-
// Cache asynchronously
243
-
ctx.waitUntil(cache.put(cacheKey, response.clone()));
244
-
245
-
return response;
162
+
// Redirect to R2 public URL - much more efficient than proxying
163
+
const r2PublicUrl = `${env.R2_PUBLIC_URL}/${imageKey}`;
164
+
return Response.redirect(r2PublicUrl, 307);
246
165
}
247
166
248
167
async function handleImageUpload(
···
308
227
IMAGES: R2Bucket;
309
228
AUTH_TOKEN: string;
310
229
PUBLIC_URL: string;
230
+
R2_PUBLIC_URL: string;
311
231
ALLOWED_CHANNELS?: string;
312
232
}