The blog’s read counts and like buttons are powered by a tiny Cloudflare Worker backed by a D1 database. You own it end to end: free tier, no cookies, no IPs, no personal data — just one counter row per post URL.
Create a free Cloudflare account at https://dash.cloudflare.com/sign-up (skip if you already have one).
blog-stats (any name works).schema.sql → Execute.
(Or: npx wrangler d1 execute blog-stats --file schema.sql if you use wrangler.)worker.js, Deploy.DB, select the blog-stats database, Deploy again.https://blog-stats.<your-name>.workers.dev)._config.yml under
stats: worker_url:), and the read/like counters go live on every post.The /contact/ page posts to the Worker (POST /api/contact) instead of
directly to Formspree. Defenses, in order:
_gotcha field; naive bots fill it, humans never
see it. Filled → quarantined, sender sees a fake success.gpt-4o-mini) called through a Cloudflare
AI Gateway classifies each message as LEGIT or SPAM, and also catches
prompt injection (“ignore previous instructions”, etc.). Spam is
quarantined in D1, never forwarded, sender sees a fake success. Honest
limit: this is a quality filter, not a human-vs-agent detector —
declared agents are explicitly welcome (see
/.well-known/agent-contact.json). History: the gate originally used
Workers AI, but Cloudflare deprecated the chosen model (May 2026) and no
replacement model ID worked on the account — so it now goes through the
blog-contact-gateway AI Gateway to OpenAI (pennies per month; needs
your API key — see below).name / operator / purpose
(see the contact page); the LLM gate lets declared agents through.Legit messages are forwarded to the Formspree “Contact” form (email
notification to you). If the Formspree form ID isn’t set yet, or a forward
fails, legit messages are stashed in the D1 contact_inbox table — read them
in the D1 console, nothing is silently dropped.
blog-stats → Edit code →
replace everything with the contents of worker.js → Deploy.blog-contact-gateway (defaults are fine). The worker calls OpenAI
through https://gateway.ai.cloudflare.com/v1/<account-id>/blog-contact-gateway/openai.OPENAI_API_KEY → paste your OpenAI API key →
Save. (Never in chat, never in the repo.) Until this is set, the LLM gate
fails open — messages still flow, they just skip AI filtering.blog-stats → Console → paste the contents of
schema-contact.sql → Execute.FORMSPREE_FORM_ID = your Formspree “Contact” form ID
(e.g. xgavprng). Until this is set, legit messages pile up in
contact_inbox instead of emailing you.lianggou.github.io at
https://www.google.com/recaptcha/admin (v2, Checkbox) → put the SITE key in
the site’s _includes/contact-form.html (data-sitekey) → put the SECRET
key in the Formspree form’s Settings → CAPTCHA → Custom reCAPTCHA.To review quarantined spam anytime: D1 → blog-stats → Console →
SELECT * FROM contact_quarantine ORDER BY id DESC LIMIT 20;
https://lianggou.github.io (see CORS
in worker.js).blog-stats → Console →
SELECT * FROM stats ORDER BY views DESC;