···1212 */
13131414// ── LIMIT CONSTANTS ────────────────────────────────────────────────────────
1515-const MAX_FILE_BYTES = 100 * 1024 * 1024; // 100 MB per file
1616-const MAX_DAY_BYTES = 1024 * 1024 * 1024; // 1 GB per IP per day
1717-const MAX_DAY_FILES = 30; // 30 files per IP per day
1515+const MAX_FILE_BYTES = 70 * 1024 * 1024; // 70 MB per file
1616+const MAX_DAY_BYTES = 300 * 1024 * 1024; // 300 MB per IP per day
1717+const MAX_DAY_FILES = 20; // 20 files per IP per day
1818+1919+const CDN_BASE_URL = "https://cdn.madebydanny.uk";
18201921export default {
2022 async fetch(request, env, ctx) {
···4547 return new Response(null, { headers: cors });
4648 }
47495050+ // ── GET /user-content/… — Serve file from R2 ───
5151+ if (request.method === "GET" && url.pathname.startsWith("/user-content/")) {
5252+ const key = url.pathname.slice(1); // strip leading /
5353+ try {
5454+ const object = await env.MY_BUCKET.get(key);
5555+ if (!object) {
5656+ return new Response("Not found", { status: 404, headers: cors });
5757+ }
5858+ const headers = new Headers(cors);
5959+ object.writeHttpMetadata(headers);
6060+ headers.set("Cache-Control", "public, max-age=31536000, immutable");
6161+ headers.set("ETag", object.httpEtag);
6262+ return new Response(object.body, { headers });
6363+ } catch (e) {
6464+ return new Response("Failed to fetch file", { status: 500, headers: cors });
6565+ }
6666+ }
6767+4868 // ── GET /stats ──────────────────────────────────
4969 if (request.method === "GET" && url.pathname === "/stats") {
5070 try {
···110130 const path = `user-content/${dateStr}/${randomId}${fileInfo.extension}`;
111131112132 // ── 2. Race: DB limit check vs R2 upload ────
113113- // request.body is a FixedLengthStream so R2 knows the content length.
114114- // obj.size gives us the real byte count after the write completes.
115133 const [usageResult, uploadResult] = await Promise.allSettled([
116134 env.DB.prepare(
117135 `SELECT file_count, total_size FROM upload_limits WHERE ip = ? AND date = ?`
···174192 ).bind(ip, today, actualSize),
175193 ])
176194 );
195195+196196+ const fileUrl = `${CDN_BASE_URL}/${path}`;
177197178198 return json({
179199 success: true,
180180- url: `https://public-cdn.madebydanny.uk/${path}`,
200200+ url: fileUrl,
181201 path,
182202 contentType,
183203 fileType: fileInfo.type,
···204224 env.DB.prepare(`SELECT COUNT(*) AS c FROM uploads WHERE file_type = 'image'`).first(),
205225 env.DB.prepare(`SELECT COUNT(*) AS c FROM uploads WHERE file_type = 'video'`).first(),
206226 env.DB.prepare(`SELECT COUNT(*) AS c FROM uploads WHERE file_type = 'gif'`).first(),
207207- // COALESCE avoids NULL when the table is empty, which D1 handles more reliably
208227 env.DB.prepare(`SELECT COALESCE(SUM(size), 0) AS s FROM uploads`).first(),
209228 ]);
210229···262281 if (t.includes("audio/wav")) return { type: "audio", extension: ".wav" };
263282 if (t.includes("audio/")) return { type: "audio", extension: "" };
264283265265- // Generic image catch-all comes last so it never swallows video/* types
266284 if (t.includes("image/")) return { type: "image", extension: "" };
267285268286 return { type: "other", extension: "" };
test.html
This is a binary file and will not be displayed.
+22
test.txt
···11+Your Bluesky Account has been deactivated
22+33+Dear {{handle}},
44+55+Your Bluesky account, {{handle}}, has been deactivated/taken down. This can happen for multiple reasons:
66+77+1. You deactivated your account via the Bluesky App (eg, bsky.app, blacksky.community)
88+2. Your account was deactivated by the Bluesky Moderation Team
99+3. You have migrated your to a different PDS
1010+4. You have violated our Terms of Service (https://mbdio.uk/tos)
1111+1212+If you did not deactivate your account or migrate and wish to continue using your account, please contact us at contact@mbdio.uk
1313+1414+We will never delete your account, even if you violated our TOS
1515+1616+Cheers,
1717+MBDIO.uk
1818+1919+---
2020+2121+Please do not reply to this message.
2222+If you need support, please contact us at contact@mbdio.uk