Feat: Add GoatCounter analytics, Terms & Privacy pages, prompt copy button

- Update AI system prompt example (README + index)
- Add copy button for the prompt on the index page
- Add GoatCounter to homepage; report image hits server-side via
  waitUntil (optional GOATCOUNTER_URL/GOATCOUNTER_TOKEN)
- Add TERMS.md and PRIVACY.md, linked in the index footer
- Remove dead c.planetrenox.com tracker

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 08:51:48 -07:00
co-authored by Claude Opus 5.5
parent 2976236c00
commit 023f4d132c
5 changed files with 133 additions and 6 deletions
+15
View File
@@ -23,6 +23,8 @@ export async function onRequest(context) {
if (!query) return jsonResponse(400, { error: "Empty query" });
if (query.length > 200) return jsonResponse(400, { error: "Query too long (max 200 characters)" });
context.waitUntil(countHit(env, request, query));
const cacheKey = query;
const r2Key = await sha256(query);
@@ -209,6 +211,19 @@ async function notify(env,{ title, message, tags, priority }) {
} catch {}
}
async function countHit(env, request, query) {
if (!env.GOATCOUNTER_URL || !env.GOATCOUNTER_TOKEN) return;
const h = k => request.headers.get(k) || "";
try {
await fetch(`${env.GOATCOUNTER_URL}/api/v0/count`, {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${env.GOATCOUNTER_TOKEN}` },
body: JSON.stringify({ no_sessions: true, hits: [{ path: `/${query.replace(/ /g, "+")}`, title: query, ref: h("referer"), user_agent: h("user-agent"), ip: h("cf-connecting-ip") }] }),
signal: AbortSignal.timeout(5000)
});
} catch {}
}
function normalizeQuery(path) {
try {
return decodeURIComponent(path.replace(/\+/g, " ")).toLowerCase().trim().replace(/[\x00-\x1f]/g, "").replace(/\/+$/, "").replace(/\s+/g, " ");