This project is a CAPTCHA-like authentication system where the user is shown an emoji and must mimic the same facial expression in front of their camera to pass the verification.
Built with Next.js 15, React 19, and Tailwind CSS. Scoring supports two engines:
The “emo” in emoCAPTCHA stands for both “emotion” and “emoji”.
- On-device (MediaPipe Tasks Vision) — private and fast, no network calls
- Cloud (OpenAI Vision) — simple and robust; requires
OPENAI_API_KEY
- Next.js (App Router, Turbopack)
- React 19
- Tailwind CSS
- MediaPipe Tasks Vision (on-device face blendshapes)
- OpenAI Vision API (optional)
This project was pair‑programmed in Cursor/Windsurf with GPT‑5 assistance.
Regular CAPTCHAs are tedious. emoCAPTCHA makes verification playful by asking people to mimic emojis. Beyond fun, the goal is societal benefit: modern AI still struggles with reading human emotions, and ethically sourced examples of real facial expressions can help improve that. If users choose to contribute, their emoji-matching selfies could form a valuable dataset to train models to better understand human emotions.
Ethics and privacy are central: any data contribution must be strictly opt‑in, consented, and protected with clear retention and anonymization policies. This repo ships with data collection disabled by default.
Prereqs: Node.js 20+, a browser with webcam access.
- Install dependencies (pick ONE package manager and stick with it):
# npm (recommended)
npm install
# or Yarn
yarn install- Optional: enable OpenAI scoring by creating
.env.local:
OPENAI_API_KEY=sk-...
# optional overrides
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_VISION_MODEL=gpt-5- Run the dev server (defaults to port 3000):
npm run dev
# or: PORT=3000 npm run dev
# or: yarn devOpen http://localhost:3000.
- UI:
components/EmoCaptcha.tsxrenders the camera, emoji challenge, and verification flow. - Challenges:
constants/emojis.tsdefines all emoji prompts and local thresholds. - Cloud scoring:
app/api/score/route.tscalls the OpenAI Vision model and returns{ matched, confidence, reasons, model }. - On-device scoring: MediaPipe face blendshapes are evaluated locally against thresholds.
By default, the component uses the cloud engine (OpenAI). If the cloud call fails, it automatically falls back to on‑device scoring. There is no visible engine toggle in the UI:
// components/EmoCaptcha.tsx
const [engine] = useState<'mediapipe' | 'openai'>('openai')You can change the default engine in code by editing the initial useState value if desired.
Scores a captured selfie against the active emoji challenge using OpenAI Vision.
Request body:
{
"imageDataUrl": "data:image/jpeg;base64,...",
"challenge": { "id": "sleep", "emoji": "😴", "label": "Sleep (eyes closed)" }
}Response:
{
"matched": true,
"confidence": 0.71,
"reasons": ["eyes mostly closed", "drooped eyelids / drowsy gaze", "relaxed mouth and jaw"],
"model": "gpt-5"
}Notes:
- Env validation: if
OPENAI_API_KEYis not set, the endpoint returns an error. - Rate limiting: best‑effort in‑memory limiter (30 req/min per client). Use Redis/Upstash in production.
- Confidence is rounded to two decimals; pass threshold is ≥ 0.60.
Security headers: next.config.ts sets CSP, Permissions‑Policy (camera allowed only for self), HSTS, and other hardened defaults.
- On-device mode: images never leave the browser. MediaPipe model is loaded from a public CDN.
- Cloud mode: the captured image is sent to the configured OpenAI API endpoint. Do not enable without user consent.
- Start:
npm run dev(oryarn dev). If port 3000 is busy, Next.js selects the next available port. - Build:
npm run build→npm start. - Key files:
components/EmoCaptcha.tsx— main UI and verification logicapp/api/score/route.ts— OpenAI Vision scoringconstants/emojis.ts— emoji challenges and thresholds
Add a new challenge:
- Append to
EMOJI_CHALLENGESinconstants/emojis.ts. - Update the switch in
components/EmoCaptcha.tsxinsideevaluateChallengefor on-device rules (if needed).
- Camera not working: ensure browser permissions are granted for the site.
- Port conflict: stop existing servers on 3000 (
lsof -ti :3000 | xargs kill -9) or runPORT=3000 npm run dev. - MediaPipe model load issues: verify network access to
cdn.jsdelivr.net. - Mixed lockfiles: avoid using both npm and Yarn. Prefer one and delete the other lockfile.
MIT — see LICENSE for details.
