@funnycaptcha/embed 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/dist/embed.d.ts +16 -0
- package/dist/embed.global.js +1365 -0
- package/dist/embed.global.js.map +1 -0
- package/dist/embed.js +71 -0
- package/dist/embed.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/scanner.ts","../../core/src/token.ts","../../core/src/proof.ts","../../core/src/registry.ts","../src/iframe-host.ts","../../captchas/math/src/index.ts","../../captchas/math/src/challenge.ts","../../captchas/math/src/render.ts","../../captchas/math/src/verifier.ts","../../captchas/text-distort/src/index.ts","../../captchas/text-distort/src/challenge.ts","../../captchas/text-distort/src/render.ts","../../captchas/text-distort/src/verifier.ts","../../captchas/slider/src/index.ts","../../captchas/slider/src/render.ts","../../captchas/slider/src/challenge.ts","../../captchas/slider/src/verifier.ts","../../captchas/click-order/src/index.ts","../../captchas/click-order/src/render.ts","../../captchas/click-order/src/challenge.ts","../../captchas/click-order/src/verifier.ts","../../captchas/rotate/src/index.ts","../../captchas/rotate/src/render.ts","../../captchas/rotate/src/challenge.ts","../../captchas/rotate/src/verifier.ts","../../captchas/spot-diff/src/index.ts","../../captchas/spot-diff/src/render.ts","../../captchas/spot-diff/src/challenge.ts","../../captchas/spot-diff/src/verifier.ts","../../captchas/emoji-match/src/index.ts","../../captchas/emoji-match/src/render.ts","../../captchas/emoji-match/src/challenge.ts","../../captchas/emoji-match/src/verifier.ts","../../captchas/meme-quiz/src/index.ts","../../captchas/meme-quiz/src/render.ts","../../captchas/meme-quiz/src/challenge.ts","../../captchas/meme-quiz/src/verifier.ts","../../captchas/mini-game/src/index.ts","../../captchas/mini-game/src/render.ts","../../captchas/mini-game/src/challenge.ts","../../captchas/mini-game/src/verifier.ts","../../captchas/anti-bot/src/index.ts","../../captchas/anti-bot/src/render.ts","../../captchas/anti-bot/src/challenge.ts","../../captchas/anti-bot/src/verifier.ts"],"sourcesContent":["import { scan } from './scanner.js';\nimport { mountInto } from './iframe-host.js';\nimport '@funnycaptcha/math';\nimport '@funnycaptcha/text-distort';\nimport '@funnycaptcha/slider';\nimport '@funnycaptcha/click-order';\nimport '@funnycaptcha/rotate';\nimport '@funnycaptcha/spot-diff';\nimport '@funnycaptcha/emoji-match';\nimport '@funnycaptcha/meme-quiz';\nimport '@funnycaptcha/mini-game';\nimport '@funnycaptcha/anti-bot';\nimport type { CaptchaInstance } from '@funnycaptcha/core';\n\nconst instances = new Map<HTMLElement, CaptchaInstance>();\n\nexport function render(root: ParentNode = document) {\n for (const t of scan(root)) {\n if (instances.has(t.el)) continue;\n const inst = mountInto(t.el, t.type, {\n locale: t.locale,\n theme: t.theme,\n });\n if (inst) instances.set(t.el, inst);\n }\n}\n\n// 自动扫描 + MutationObserver\nexport function autoStart() {\n render();\n const obs = new MutationObserver(muts => {\n for (const m of muts) {\n m.addedNodes.forEach(n => {\n if (n instanceof HTMLElement) render(n);\n });\n }\n });\n obs.observe(document.body, { childList: true, subtree: true });\n}\n\nexport { scan, mountInto };\n\nif (typeof window !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoStart);\n } else {\n autoStart();\n }\n}\n","import type { Locale } from '@funnycaptcha/core';\n\nexport interface EmbedTarget {\n el: HTMLElement;\n type: string;\n locale: Locale;\n theme: 'light' | 'dark';\n}\n\nexport function scan(root: ParentNode = document): EmbedTarget[] {\n const els = Array.from(root.querySelectorAll<HTMLElement>('[data-funny-captcha]'));\n return els.map(el => ({\n el,\n type: el.dataset.type ?? 'math',\n locale: (el.dataset.locale as Locale) ?? 'zh',\n theme: (el.dataset.theme as 'light' | 'dark') ?? 'light',\n }));\n}\n","// 简易 HMAC-like token:base64url(payload).signature\n// payload = {type, exp, nonce}\n// 签名 = sha256(payload + secret),secret 由调用方注入(前端 demo 用默认值,后端应覆盖)\n\nconst defaultSecret = 'funnycaptcha-demo-secret';\n\nexport interface TokenPayload {\n type: string;\n exp: number;\n nonce: string;\n}\n\nexport interface TokenVerifyResult {\n valid: boolean;\n type?: string;\n}\n\nfunction b64urlEncode(s: string): string {\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\nfunction b64urlDecode(s: string): string {\n const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));\n return atob(s.replace(/-/g, '+').replace(/_/g, '/') + pad);\n}\n\nasync function sha256Hex(s: string, secret: string): Promise<string> {\n const data = new TextEncoder().encode(s + secret);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nexport async function issueToken(\n type: string,\n ttlMs: number,\n secret: string = defaultSecret,\n): Promise<string> {\n const payload: TokenPayload = {\n type,\n exp: Date.now() + ttlMs,\n nonce: crypto.randomUUID(),\n };\n const body = b64urlEncode(JSON.stringify(payload));\n const sig = await sha256Hex(body, secret);\n return `${body}.${sig}`;\n}\n\nexport async function verifyToken(\n token: string,\n secret: string = defaultSecret,\n): Promise<TokenVerifyResult> {\n const parts = token.split('.');\n if (parts.length !== 2) return { valid: false };\n const [body, sig] = parts as [string, string];\n const expected = await sha256Hex(body, secret);\n if (expected !== sig) return { valid: false };\n try {\n const payload = JSON.parse(b64urlDecode(body)) as TokenPayload;\n if (Date.now() > payload.exp) return { valid: false };\n return { valid: true, type: payload.type };\n } catch {\n return { valid: false };\n }\n}\n","export async function hashProof(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const buf = await crypto.subtle.digest('SHA-256', data);\n return Array.from(new Uint8Array(buf))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","import type { CaptchaPlugin } from './types.js';\n\nconst registry = new Map<string, CaptchaPlugin>();\n\nexport function defineCaptcha(plugin: CaptchaPlugin): CaptchaPlugin {\n if (registry.has(plugin.id)) {\n throw new Error(`Captcha \"${plugin.id}\" already registered`);\n }\n registry.set(plugin.id, plugin);\n return plugin;\n}\n\nexport function getCaptcha(id: string): CaptchaPlugin | undefined {\n return registry.get(id);\n}\n\nexport function listCaptchas(): CaptchaPlugin[] {\n return Array.from(registry.values());\n}\n\nexport function resetRegistry(): void {\n registry.clear();\n}\n","import type { CaptchaConfig, CaptchaInstance } from '@funnycaptcha/core';\nimport { getCaptcha } from '@funnycaptcha/core';\n\nexport function mountInto(\n container: HTMLElement,\n type: string,\n config: CaptchaConfig,\n): CaptchaInstance | null {\n const plugin = getCaptcha(type);\n if (!plugin) {\n container.innerHTML = `<div style=\"color:red\">Unknown captcha: ${type}</div>`;\n return null;\n }\n const inst = plugin.create(container, config);\n inst.mount();\n return inst;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMathInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const mathPlugin: CaptchaPlugin = {\n id: 'math',\n category: 'recognize',\n create: createMathInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '算术题' : 'Arithmetic',\n description: locale === 'zh'\n ? '简单的加减乘除题,经典又轻量'\n : 'Simple arithmetic. Classic and lightweight.',\n tags: ['math', 'classic', 'easy'],\n }),\n};\n\ndefineCaptcha(mathPlugin);\n\nexport function mountMath(container: HTMLElement, config: CaptchaConfig) {\n return createMathInstance(container, config);\n}\n","export interface MathChallenge {\n question: string;\n answer: number;\n}\n\nconst ops = ['+', '-', '*', '/'] as const;\ntype Op = (typeof ops)[number];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nfunction pick<T>(arr: readonly T[]): T {\n return arr[randInt(0, arr.length - 1)] as T;\n}\n\nexport function generateChallenge(): MathChallenge {\n const op = pick(ops);\n let a = randInt(1, 20);\n let b = randInt(1, 20);\n if (op === '/') {\n // 保证整除\n b = randInt(1, 10);\n a = b * randInt(1, 10);\n }\n if (op === '-' && b > a) [a, b] = [b, a];\n const answer = op === '+' ? a + b\n : op === '-' ? a - b\n : op === '*' ? a * b\n : a / b;\n return { question: `${a} ${op} ${b} = ?`, answer };\n}\n\nexport function verifyAnswer(c: MathChallenge, userAnswer: number): boolean {\n return c.answer === userAnswer;\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type MathChallenge } from './challenge.js';\nimport { hashProof } from '@funnycaptcha/core';\n\nconst STR = {\n zh: { title: '请计算', placeholder: '输入答案', submit: '验证', fail: '答错了,换一题' },\n en: { title: 'Solve', placeholder: 'Enter answer', submit: 'Verify', fail: 'Wrong, try again' },\n};\n\nexport function createMathInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: MathChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n container.innerHTML = `\n <div class=\"fc-math\">\n <label class=\"fc-math-q\">${t.title}: ${current.question}</label>\n <input class=\"fc-math-input\" type=\"text\" inputmode=\"numeric\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-math-btn\">${t.submit}</button>\n <div class=\"fc-math-msg\"></div>\n </div>\n `;\n const btn = container.querySelector('.fc-math-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-math-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-math-msg') as HTMLDivElement;\n btn.addEventListener('click', async () => {\n const val = Number(input.value);\n const success = verifyAnswer(current, val);\n if (!success) {\n msg.textContent = t.fail;\n render();\n return;\n }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(`${current.question}=${current.answer}`),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type MathChallenge } from './challenge.js';\n\nexport function issueMathChallenge(): { challenge: MathChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { question: challenge.question } };\n}\n\nexport async function verifyMathProof(\n challenge: MathChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(`${challenge.question}=${challenge.answer}`);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createTextDistortInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const textDistortPlugin: CaptchaPlugin = {\n id: 'text-distort',\n category: 'recognize',\n create: createTextDistortInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '扭曲文字' : 'Distorted Text',\n description: locale === 'zh'\n ? 'Canvas 绘制的扭曲字符,经典验证码形态'\n : 'Canvas-drawn distorted characters. The classic look.',\n tags: ['text', 'canvas', 'classic'],\n }),\n};\n\ndefineCaptcha(textDistortPlugin);\n\nexport function mountTextDistort(container: HTMLElement, config: CaptchaConfig) {\n return createTextDistortInstance(container, config);\n}\n","const CHARS = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; // 去除易混字符 I L O 0 1\n\nexport interface TextChallenge {\n code: string;\n}\n\nexport function generateChallenge(len: number = 5): TextChallenge {\n let code = '';\n for (let i = 0; i < len; i++) {\n code += CHARS[Math.floor(Math.random() * CHARS.length)] ?? 'A';\n }\n return { code };\n}\n\nexport function verifyAnswer(c: TextChallenge, answer: string): boolean {\n return c.code.toLowerCase() === answer.trim().toLowerCase();\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type TextChallenge } from './challenge.js';\nimport { hashProof } from '@funnycaptcha/core';\n\nconst STR = {\n zh: { placeholder: '输入图中字符', submit: '验证', fail: '看不清?换一张', refresh: '刷新' },\n en: { placeholder: 'Type the text', submit: 'Verify', fail: 'Wrong? Try another', refresh: 'Refresh' },\n};\n\nfunction drawDistorted(canvas: HTMLCanvasElement, code: string) {\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.fillStyle = '#f0f0f0';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const chars = code.split('');\n const step = canvas.width / (chars.length + 1);\n chars.forEach((ch, i) => {\n ctx.save();\n const x = step * (i + 1);\n const y = canvas.height / 2 + (Math.random() - 0.5) * 14;\n ctx.translate(x, y);\n ctx.rotate((Math.random() - 0.5) * 0.5);\n ctx.font = `${24 + Math.floor(Math.random() * 8)}px sans-serif`;\n ctx.fillStyle = `hsl(${Math.random() * 360}, 70%, 40%)`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(ch, 0, 0);\n ctx.restore();\n });\n // 干扰线\n for (let i = 0; i < 4; i++) {\n ctx.strokeStyle = `hsla(${Math.random() * 360}, 70%, 50%, 0.4)`;\n ctx.beginPath();\n ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height);\n ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height);\n ctx.stroke();\n }\n}\n\nexport function createTextDistortInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: TextChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n container.innerHTML = `\n <div class=\"fc-text\">\n <canvas class=\"fc-text-canvas\" width=\"180\" height=\"60\"></canvas>\n <button class=\"fc-text-refresh\">${t.refresh}</button>\n <input class=\"fc-text-input\" placeholder=\"${t.placeholder}\" />\n <button class=\"fc-text-btn\">${t.submit}</button>\n <div class=\"fc-text-msg\"></div>\n </div>\n `;\n const canvas = container.querySelector('.fc-text-canvas') as HTMLCanvasElement;\n drawDistorted(canvas, current.code);\n (container.querySelector('.fc-text-refresh') as HTMLButtonElement)\n .addEventListener('click', render);\n const btn = container.querySelector('.fc-text-btn') as HTMLButtonElement;\n const input = container.querySelector('.fc-text-input') as HTMLInputElement;\n const msg = container.querySelector('.fc-text-msg') as HTMLDivElement;\n btn.addEventListener('click', async () => {\n const ok = verifyAnswer(current, input.value);\n if (!ok) { msg.textContent = t.fail; render(); return; }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.code.toLowerCase()),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n });\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') btn.click();\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type TextChallenge } from './challenge.js';\n\nexport function issueTextChallenge(): { challenge: TextChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: {} }; // payload 空:图由前端画,不传 code\n}\n\nexport async function verifyTextProof(c: TextChallenge, proof: string): Promise<boolean> {\n const expected = await hashProof(c.code.toLowerCase());\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createSliderInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const sliderPlugin: CaptchaPlugin = {\n id: 'slider',\n category: 'interactive',\n create: createSliderInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '滑动拼图' : 'Slider',\n description: locale === 'zh'\n ? '拖动滑块到终点,简单直接的交互验证'\n : 'Drag the slider to the end. Simple and direct.',\n tags: ['slider', 'drag', 'interactive'],\n }),\n};\n\ndefineCaptcha(sliderPlugin);\n\nexport function mountSlider(container: HTMLElement, config: CaptchaConfig) {\n return createSliderInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type SliderChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '拖动滑块到最右端', tip: '向右拖动', success: '验证成功', fail: '请拖到尽头' },\n en: { title: 'Drag the slider to the right end', tip: 'Drag right', success: 'Verified', fail: 'Drag to the end' },\n};\n\nexport function createSliderInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: SliderChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let done = false;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n done = false;\n container.innerHTML = `\n <style>\n .fc-slider{font-family:-apple-system,system-ui,sans-serif;width:300px;padding:16px;box-sizing:border-box}\n .fc-slider-title{font-size:14px;color:#333;margin-bottom:12px;text-align:center}\n .fc-slider-track{position:relative;height:40px;background:#e8e8e8;border-radius:20px}\n .fc-slider-progress{position:absolute;left:0;top:0;height:100%;width:0;background:#4a90d9;border-radius:20px;transition:width .05s}\n .fc-slider-handle{position:absolute;left:0;top:50%;transform:translateY(-50%);width:50px;height:50px;background:#fff;border:2px solid #4a90d9;border-radius:50%;cursor:grab;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(0,0,0,.15);user-select:none;touch-action:none}\n .fc-slider-handle:active{cursor:grabbing}\n .fc-slider-handle::after{content:'\\\\2192';color:#4a90d9;font-size:18px;font-weight:bold}\n .fc-slider-tip{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:13px;color:#999;pointer-events:none;transition:opacity .2s}\n .fc-slider-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:#2e7d32}\n </style>\n <div class=\"fc-slider\">\n <div class=\"fc-slider-title\">${t.title}</div>\n <div class=\"fc-slider-track\">\n <div class=\"fc-slider-progress\"></div>\n <div class=\"fc-slider-handle\" role=\"slider\" tabindex=\"0\"></div>\n <span class=\"fc-slider-tip\">${t.tip}</span>\n </div>\n <div class=\"fc-slider-msg\"></div>\n </div>\n `;\n const track = container.querySelector('.fc-slider-track') as HTMLDivElement;\n const handle = container.querySelector('.fc-slider-handle') as HTMLDivElement;\n const progress = container.querySelector('.fc-slider-progress') as HTMLDivElement;\n const tip = container.querySelector('.fc-slider-tip') as HTMLSpanElement;\n const msg = container.querySelector('.fc-slider-msg') as HTMLDivElement;\n\n let dragging = false;\n let startX = 0;\n let startOffset = 0;\n let currentOffset = 0;\n\n function applyPos(px: number) {\n handle.style.left = `${px}px`;\n progress.style.width = `${px}px`;\n tip.style.opacity = px > 8 ? '0' : '1';\n }\n\n handle.addEventListener('pointerdown', (e) => {\n if (done) return;\n dragging = true;\n startX = e.clientX;\n startOffset = currentOffset;\n try { handle.setPointerCapture(e.pointerId); } catch { /* ignore */ }\n e.preventDefault();\n });\n\n handle.addEventListener('pointermove', (e) => {\n if (!dragging) return;\n const maxX = track.clientWidth - handle.offsetWidth;\n let px = startOffset + (e.clientX - startX);\n px = Math.max(0, Math.min(maxX, px));\n currentOffset = px;\n applyPos(px);\n });\n\n const finish = async () => {\n if (!dragging) return;\n dragging = false;\n const maxX = Math.max(1, track.clientWidth - handle.offsetWidth);\n if (currentOffset / maxX >= 0.95) {\n done = true;\n applyPos(maxX);\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 未到终点,弹回\n currentOffset = 0;\n handle.style.transition = 'left .3s';\n progress.style.transition = 'width .3s';\n applyPos(0);\n msg.textContent = t.fail;\n setTimeout(() => {\n handle.style.transition = '';\n progress.style.transition = '';\n if (!done) msg.textContent = '';\n }, 320);\n }\n };\n\n handle.addEventListener('pointerup', finish);\n handle.addEventListener('pointercancel', finish);\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import { hashProof } from '@funnycaptcha/core';\n\n// 滑动拼图题目:包含一个随机 token,用户拖到终点后用 token 构造 proof\nexport interface SliderChallenge {\n token: string;\n}\n\n// 生成随机 token(用于构造 proof)\nexport function generateChallenge(): SliderChallenge {\n const token = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return { token };\n}\n\n// 构造 proof 输入串:token + ':completed'\nexport function proofInput(c: SliderChallenge): string {\n return `${c.token}:completed`;\n}\n\n// 校验完成 proof:hashProof(token + ':completed') === proof\nexport async function verifyCompleted(c: SliderChallenge, proof: string): Promise<boolean> {\n const expected = await hashProof(proofInput(c));\n return expected === proof;\n}\n","import { generateChallenge, verifyCompleted, type SliderChallenge } from './challenge.js';\n\nexport function issueSliderChallenge(): { challenge: SliderChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { token: challenge.token } };\n}\n\nexport async function verifySliderProof(\n challenge: SliderChallenge,\n proof: string,\n): Promise<boolean> {\n return verifyCompleted(challenge, proof);\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createClickOrderInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const clickOrderPlugin: CaptchaPlugin = {\n id: 'click-order',\n category: 'interactive',\n create: createClickOrderInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '点选顺序' : 'Click Order',\n description: locale === 'zh'\n ? '按数字顺序依次点击目标,考验眼手协调'\n : 'Click targets in numeric order. Tests coordination.',\n tags: ['click', 'order', 'interactive'],\n }),\n};\n\ndefineCaptcha(clickOrderPlugin);\n\nexport function mountClickOrder(container: HTMLElement, config: CaptchaConfig) {\n return createClickOrderInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyOrder, type ClickOrderChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '按 1 → 2 → 3 → 4 顺序点击', success: '验证成功', fail: '顺序错误,请重试' },\n en: { title: 'Click in order 1 → 2 → 3 → 4', success: 'Verified', fail: 'Wrong order, try again' },\n};\n\n// 4 个不重叠的预设槽位(2x2 网格),280x180 区域内\nconst SLOTS = [\n { x: 20, y: 20 },\n { x: 160, y: 20 },\n { x: 20, y: 100 },\n { x: 160, y: 100 },\n];\n\nexport function createClickOrderInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: ClickOrderChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let clicked: number[] = [];\n let finished = false;\n\n function render() {\n current = generateChallenge();\n clicked = [];\n finished = false;\n startTime = Date.now();\n const boxes = current.targets.map((num, i) => {\n const slot = SLOTS[i]!;\n return `<div class=\"fc-click-box\" data-num=\"${num}\" style=\"left:${slot.x}px;top:${slot.y}px;\">${num}</div>`;\n }).join('');\n container.innerHTML = `\n <style>\n .fc-click{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:16px;box-sizing:border-box}\n .fc-click-title{font-size:14px;color:#333;margin-bottom:12px;text-align:center}\n .fc-click-area{position:relative;width:280px;height:180px;background:#f5f5f5;border-radius:8px;border:1px solid #e0e0e0;overflow:hidden}\n .fc-click-box{position:absolute;width:100px;height:60px;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:bold;color:#4a90d9;background:#fff;border:2px solid #4a90d9;border-radius:8px;cursor:pointer;user-select:none;transition:transform .15s,background .15s,color .15s}\n .fc-click-box:hover{transform:scale(1.05)}\n .fc-click-box-active{background:#4a90d9;color:#fff}\n .fc-click-box-done{background:#2e7d32;border-color:#2e7d32;color:#fff;cursor:default}\n .fc-click-box-done:hover{transform:none}\n .fc-click-msg{font-size:13px;min-height:18px;text-align:center;margin-top:10px;color:#2e7d32}\n </style>\n <div class=\"fc-click\">\n <div class=\"fc-click-title\">${t.title}</div>\n <div class=\"fc-click-area\">${boxes}</div>\n <div class=\"fc-click-msg\"></div>\n </div>\n `;\n const area = container.querySelector('.fc-click-area') as HTMLDivElement;\n const msg = container.querySelector('.fc-click-msg') as HTMLDivElement;\n\n area.querySelectorAll('.fc-click-box').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const box = el as HTMLElement;\n if (box.classList.contains('fc-click-box-done')) return; // 已点过\n const num = Number(box.dataset.num);\n const expected = current.order[clicked.length]!;\n if (num === expected) {\n // 点击正确:高亮并锁定\n clicked.push(num);\n box.classList.add('fc-click-box-done');\n if (clicked.length === current.order.length) {\n // 全部正确\n finished = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n } else {\n // 顺序错误:重置\n finished = true;\n box.classList.add('fc-click-box-active');\n msg.textContent = t.fail;\n setTimeout(render, 700);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 点选顺序题目:4 个带数字的目标打乱位置,用户按 1→2→3→4 顺序点击\nexport interface ClickOrderChallenge {\n // 显示在 4 个槽位上的数字([1,2,3,4] 的随机排列)\n targets: number[];\n // 正确的点击顺序\n order: number[];\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:order 固定为 [1,2,3,4],targets 为打乱后的展示顺序\nexport function generateChallenge(): ClickOrderChallenge {\n const targets = shuffle([1, 2, 3, 4]);\n return { targets, order: [1, 2, 3, 4] };\n}\n\n// 校验用户点击顺序是否正确\nexport function verifyOrder(c: ClickOrderChallenge, userOrder: number[]): boolean {\n if (userOrder.length !== c.order.length) return false;\n return userOrder.every((v, i) => v === c.order[i]);\n}\n\n// 构造 proof 输入串:order 以逗号连接\nexport function proofInput(c: ClickOrderChallenge): string {\n return c.order.join(',');\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type ClickOrderChallenge } from './challenge.js';\n\nexport function issueClickOrderChallenge(): { challenge: ClickOrderChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { order: challenge.order } };\n}\n\nexport async function verifyClickOrderProof(\n challenge: ClickOrderChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createRotateInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const rotatePlugin: CaptchaPlugin = {\n id: 'rotate',\n category: 'interactive',\n create: createRotateInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '旋转对齐' : 'Rotate Align',\n description: locale === 'zh'\n ? '拖动滑块将偏转的图形转正,考验空间感'\n : 'Rotate the tilted figure back upright. Tests spatial sense.',\n tags: ['rotate', 'drag', 'interactive'],\n }),\n};\n\ndefineCaptcha(rotatePlugin);\n\nexport function mountRotate(container: HTMLElement, config: CaptchaConfig) {\n return createRotateInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyAlignment, type RotateChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '拖动滑块将图形转正', success: '验证成功', hint: '让箭头指向上方' },\n en: { title: 'Drag to rotate the figure upright', success: 'Verified', hint: 'Point the arrow up' },\n};\n\nexport function createRotateInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: RotateChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let done = false;\n\n function render() {\n current = generateChallenge();\n startTime = Date.now();\n done = false;\n container.innerHTML = `\n <style>\n .fc-rotate{font-family:-apple-system,system-ui,sans-serif;width:300px;padding:16px;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;gap:12px}\n .fc-rotate-title{font-size:14px;color:#333;text-align:center}\n .fc-rotate-stage{position:relative;width:140px;height:140px;display:flex;align-items:center;justify-content:center;border:2px dashed #ccc;border-radius:50%}\n .fc-rotate-figure{width:100px;height:100px;transform-origin:center;transition:transform .05s}\n .fc-rotate-marker{position:absolute;top:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:12px solid #e74c3c}\n .fc-rotate-hint{font-size:12px;color:#999}\n .fc-rotate-range{width:240px;accent-color:#4a90d9}\n .fc-rotate-msg{font-size:13px;min-height:18px;text-align:center;color:#2e7d32}\n </style>\n <div class=\"fc-rotate\">\n <div class=\"fc-rotate-title\">${t.title}</div>\n <div class=\"fc-rotate-stage\">\n <div class=\"fc-rotate-figure\" style=\"transform: rotate(${current.angle}deg)\">\n <svg viewBox=\"0 0 100 100\" width=\"100\" height=\"100\"><polygon points=\"50,12 90,85 10,85\" fill=\"#4a90d9\"/></svg>\n </div>\n <div class=\"fc-rotate-marker\"></div>\n </div>\n <div class=\"fc-rotate-hint\">${t.hint}</div>\n <input class=\"fc-rotate-range\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"0\" />\n <div class=\"fc-rotate-msg\"></div>\n </div>\n `;\n const figure = container.querySelector('.fc-rotate-figure') as HTMLDivElement;\n const range = container.querySelector('.fc-rotate-range') as HTMLInputElement;\n const msg = container.querySelector('.fc-rotate-msg') as HTMLDivElement;\n\n const update = async () => {\n if (done) return;\n const v = Number(range.value);\n const final = (((current.angle + v) % 360) + 360) % 360;\n figure.style.transform = `rotate(${final}deg)`;\n if (verifyAlignment(current, v)) {\n done = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n msg.textContent = t.success;\n range.disabled = true;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n };\n\n range.addEventListener('input', update);\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 旋转对齐题目:图形被旋转了 angle 度,用户拖动滑块校正使其回正(误差 < 5°)\nexport interface RotateChallenge {\n // 目标校正角度(0-359),即图形初始的偏转量;用户需反向校正使其回正\n angle: number;\n}\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// 生成 30-330 之间的角度(避免太接近 0/360 导致一眼即过)\nexport function generateChallenge(): RotateChallenge {\n return { angle: randInt(30, 330) };\n}\n\n// 校验对齐:最终旋转 = angle + userAngle,回正即 ≡ 0 (mod 360),误差 <= tolerance\nexport function verifyAlignment(c: RotateChallenge, userAngle: number, tolerance = 5): boolean {\n const final = (((c.angle + userAngle) % 360) + 360) % 360;\n // 取到 0/360 的最短距离\n const diff = Math.min(final, 360 - final);\n return diff <= tolerance;\n}\n\n// 构造 proof 输入串:angle + ':aligned'\nexport function proofInput(c: RotateChallenge): string {\n return `${c.angle}:aligned`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type RotateChallenge } from './challenge.js';\n\nexport function issueRotateChallenge(): { challenge: RotateChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { angle: challenge.angle } };\n}\n\nexport async function verifyRotateProof(\n challenge: RotateChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createSpotDiffInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const spotDiffPlugin: CaptchaPlugin = {\n id: 'spot-diff',\n category: 'creative',\n create: createSpotDiffInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '找不同' : 'Spot the Difference',\n description: locale === 'zh'\n ? '对比两个 emoji 网格,找出所有不同之处'\n : 'Compare two emoji grids and spot all differences.',\n tags: ['spot-diff', 'emoji', 'creative'],\n }),\n};\n\ndefineCaptcha(spotDiffPlugin);\n\nexport function mountSpotDiff(container: HTMLElement, config: CaptchaConfig) {\n return createSpotDiffInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyDiffs, type SpotDiffChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '找出两处不同', hint: '点击右侧网格中不同的格子', found: '已找到', success: '验证成功', fail: '找得不对,再来一次' },\n en: { title: 'Spot 2 differences', hint: 'Click the differing cells on the right grid', found: 'Found', success: 'Verified', fail: 'Not quite, try again' },\n};\n\nexport function createSpotDiffInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: SpotDiffChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let marked: Set<number> = new Set();\n let finished = false;\n\n function renderGrid(emojis: string[], clickable: boolean): string {\n const cls = clickable ? 'fc-spot-diff-cell fc-spot-diff-clickable' : 'fc-spot-diff-cell';\n return emojis.map((e, i) => `<div class=\"${cls}\" data-idx=\"${i}\">${e}</div>`).join('');\n }\n\n function render() {\n current = generateChallenge();\n marked = new Set();\n finished = false;\n startTime = Date.now();\n container.innerHTML = `\n <style>\n .fc-spot-diff{font-family:-apple-system,system-ui,sans-serif;width:360px;padding:16px;box-sizing:border-box}\n .fc-spot-diff-title{font-size:14px;color:#333;margin-bottom:6px;text-align:center;font-weight:600}\n .fc-spot-diff-hint{font-size:12px;color:#888;margin-bottom:12px;text-align:center}\n .fc-spot-diff-grids{display:flex;gap:16px;justify-content:center}\n .fc-spot-diff-grid{display:grid;grid-template-columns:repeat(3,48px);grid-template-rows:repeat(3,48px);gap:4px;background:#f5f5f5;padding:8px;border-radius:8px;border:1px solid #e0e0e0}\n .fc-spot-diff-cell{display:flex;align-items:center;justify-content:center;font-size:26px;background:#fff;border-radius:6px;user-select:none}\n .fc-spot-diff-clickable{cursor:pointer;transition:transform .12s,box-shadow .12s}\n .fc-spot-diff-clickable:hover{transform:scale(1.06)}\n .fc-spot-diff-marked{box-shadow:0 0 0 3px #4a90d9 inset;background:#eaf3ff}\n .fc-spot-diff-wrong{box-shadow:0 0 0 3px #e53935 inset;background:#fdecea}\n .fc-spot-diff-status{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#2e7d32}\n .fc-spot-diff-status-fail{color:#e53935}\n </style>\n <div class=\"fc-spot-diff\">\n <div class=\"fc-spot-diff-title\">${t.title}</div>\n <div class=\"fc-spot-diff-hint\">${t.hint}</div>\n <div class=\"fc-spot-diff-grids\">\n <div class=\"fc-spot-diff-grid\">${renderGrid(current.gridA, false)}</div>\n <div class=\"fc-spot-diff-grid fc-spot-diff-grid-b\">${renderGrid(current.gridB, true)}</div>\n </div>\n <div class=\"fc-spot-diff-status\">${t.found} 0/${current.diffs.length}</div>\n </div>\n `;\n const gridB = container.querySelector('.fc-spot-diff-grid-b') as HTMLDivElement;\n const status = container.querySelector('.fc-spot-diff-status') as HTMLDivElement;\n\n gridB.querySelectorAll('.fc-spot-diff-clickable').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const cell = el as HTMLElement;\n const idx = Number(cell.dataset.idx);\n if (marked.has(idx)) {\n // 取消标记\n marked.delete(idx);\n cell.classList.remove('fc-spot-diff-marked');\n } else {\n marked.add(idx);\n cell.classList.add('fc-spot-diff-marked');\n }\n status.textContent = `${t.found} ${marked.size}/${current.diffs.length}`;\n // 标记数达到差异数时自动校验\n if (marked.size === current.diffs.length) {\n const userDiffs = Array.from(marked);\n const ok = verifyDiffs(current, userDiffs);\n if (ok) {\n finished = true;\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(userDiffs.slice().sort((a, b) => a - b).join(',')),\n duration: Date.now() - startTime,\n };\n status.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 标错:高亮错误后重置\n finished = true;\n status.textContent = t.fail;\n status.classList.add('fc-spot-diff-status-fail');\n userDiffs.forEach(i => {\n const node = gridB.querySelector(`[data-idx=\"${i}\"]`) as HTMLElement;\n node.classList.add('fc-spot-diff-wrong');\n });\n setTimeout(render, 800);\n }\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 找不同题目:两个 3x3 emoji 网格,其中 2 处不同\nexport interface SpotDiffChallenge {\n // 参考网格(9 个 emoji)\n gridA: string[];\n // 待找不同的网格(9 个 emoji)\n gridB: string[];\n // 差异位置索引(0-8)\n diffs: number[];\n}\n\n// emoji 池\nconst POOL: string[] = ['🍎', '🍊', '🍋', '🍇', '🍓', '🍑', '🍒', '🍉', '🥝'];\nconst GRID_SIZE = 9;\nconst DIFF_COUNT = 2;\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 从池中挑选一个与 original 不同的 emoji\nfunction pickDifferent(original: string): string {\n let next = POOL[randInt(0, POOL.length - 1)]!;\n while (next === original) {\n next = POOL[randInt(0, POOL.length - 1)]!;\n }\n return next;\n}\n\n// 生成题目:gridA 为 9 个不重复 emoji 的随机排列,gridB 在 2 个位置替换为不同 emoji\nexport function generateChallenge(): SpotDiffChallenge {\n const gridA = shuffle(POOL);\n // 随机选 2 个不重复的位置作为差异\n const positions = shuffle(Array.from({ length: GRID_SIZE }, (_, i) => i));\n const diffs = positions.slice(0, DIFF_COUNT).sort((a, b) => a - b);\n const gridB = gridA.slice();\n for (const pos of diffs) {\n gridB[pos] = pickDifferent(gridA[pos]!);\n }\n return { gridA, gridB, diffs };\n}\n\n// 校验用户找出的差异位置是否正确\nexport function verifyDiffs(c: SpotDiffChallenge, userDiffs: number[]): boolean {\n if (userDiffs.length !== c.diffs.length) return false;\n const sorted = userDiffs.slice().sort((a, b) => a - b);\n return sorted.every((v, i) => v === c.diffs[i]);\n}\n\n// 构造 proof 输入串:差异位置升序逗号连接\nexport function proofInput(c: SpotDiffChallenge): string {\n return c.diffs.slice().sort((a, b) => a - b).join(',');\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type SpotDiffChallenge } from './challenge.js';\n\n// 服务端签发题目\nexport function issueSpotDiffChallenge(): { challenge: SpotDiffChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { gridA: challenge.gridA, gridB: challenge.gridB } };\n}\n\n// 服务端校验 proof\nexport async function verifySpotDiffProof(\n challenge: SpotDiffChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createEmojiMatchInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const emojiMatchPlugin: CaptchaPlugin = {\n id: 'emoji-match',\n category: 'creative',\n create: createEmojiMatchInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '表情匹配' : 'Emoji Match',\n description: locale === 'zh'\n ? '根据文字描述选出对应的表情 emoji'\n : 'Pick the emoji face that matches the description.',\n tags: ['emoji', 'match', 'creative'],\n }),\n};\n\ndefineCaptcha(emojiMatchPlugin);\n\nexport function mountEmojiMatch(container: HTMLElement, config: CaptchaConfig) {\n return createEmojiMatchInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type EmojiMatchChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '选出符合描述的表情', success: '验证成功', fail: '选错了,再试一次' },\n en: { title: 'Pick the face matching the description', success: 'Verified', fail: 'Wrong, try again' },\n};\n\nexport function createEmojiMatchInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: EmojiMatchChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n\n function render() {\n current = generateChallenge(config.locale);\n finished = false;\n startTime = Date.now();\n const buttons = current.options.map(\n (e) => `<button class=\"fc-emoji-match-opt\" data-emoji=\"${e}\">${e}</button>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-emoji-match{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:16px;box-sizing:border-box}\n .fc-emoji-match-title{font-size:14px;color:#333;margin-bottom:6px;text-align:center;font-weight:600}\n .fc-emoji-match-desc{font-size:15px;color:#4a90d9;margin-bottom:14px;text-align:center;font-weight:600}\n .fc-emoji-match-opts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n .fc-emoji-match-opt{font-size:30px;height:64px;border:2px solid #e0e0e0;background:#fff;border-radius:10px;cursor:pointer;transition:transform .12s,border-color .12s,background .12s;display:flex;align-items:center;justify-content:center}\n .fc-emoji-match-opt:hover{transform:scale(1.06);border-color:#4a90d9}\n .fc-emoji-match-opt-wrong{border-color:#e53935;background:#fdecea}\n .fc-emoji-match-opt-correct{border-color:#2e7d32;background:#e8f5e9}\n .fc-emoji-match-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#e53935}\n </style>\n <div class=\"fc-emoji-match\">\n <div class=\"fc-emoji-match-title\">${t.title}</div>\n <div class=\"fc-emoji-match-desc\">${current.description}</div>\n <div class=\"fc-emoji-match-opts\">${buttons}</div>\n <div class=\"fc-emoji-match-msg\"></div>\n </div>\n `;\n const msg = container.querySelector('.fc-emoji-match-msg') as HTMLDivElement;\n\n container.querySelectorAll('.fc-emoji-match-opt').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const btn = el as HTMLButtonElement;\n const pick = btn.dataset.emoji!;\n if (verifyAnswer(current, pick)) {\n finished = true;\n btn.classList.add('fc-emoji-match-opt-correct');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.correct),\n duration: Date.now() - startTime,\n };\n msg.style.color = '#2e7d32';\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 选错:高亮错误后换题\n finished = true;\n btn.classList.add('fc-emoji-match-opt-wrong');\n msg.textContent = t.fail;\n setTimeout(render, 800);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","import type { Locale } from '@funnycaptcha/core';\n\n// 表情匹配题目:根据描述从 6 个 emoji 中选出正确的\nexport interface EmojiMatchChallenge {\n // 表情描述\n description: string;\n // 正确 emoji\n correct: string;\n // 6 个备选 emoji(含正确答案)\n options: string[];\n}\n\n// 预设表情数据\ninterface Preset {\n emoji: string;\n zh: string;\n en: string;\n}\n\nconst PRESETS: Preset[] = [\n { emoji: '😀', zh: '开心的表情', en: 'Happy face' },\n { emoji: '😢', zh: '伤心的表情', en: 'Sad face' },\n { emoji: '😠', zh: '生气的表情', en: 'Angry face' },\n { emoji: '😴', zh: '困倦的表情', en: 'Sleepy face' },\n { emoji: '🤔', zh: '思考的表情', en: 'Thinking face' },\n { emoji: '😍', zh: '爱慕的表情', en: 'Loving face' },\n];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:随机选一个表情作为正确答案,6 个备选项为全部预设表情的乱序\nexport function generateChallenge(locale: Locale = 'zh'): EmojiMatchChallenge {\n const preset = PRESETS[randInt(0, PRESETS.length - 1)]!;\n const description = locale === 'zh' ? preset.zh : preset.en;\n // 备选项为全部 6 个预设表情打乱顺序\n const options = shuffle(PRESETS.map(p => p.emoji));\n return { description, correct: preset.emoji, options };\n}\n\n// 校验用户选择的 emoji 是否正确\nexport function verifyAnswer(c: EmojiMatchChallenge, userPick: string): boolean {\n return c.correct === userPick;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type EmojiMatchChallenge } from './challenge.js';\n\n// 服务端签发题目(默认中文描述)\nexport function issueEmojiMatchChallenge(): { challenge: EmojiMatchChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { description: challenge.description, options: challenge.options } };\n}\n\n// 服务端校验 proof\nexport async function verifyEmojiMatchProof(\n challenge: EmojiMatchChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(challenge.correct);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMemeQuizInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const memeQuizPlugin: CaptchaPlugin = {\n id: 'meme-quiz',\n category: 'creative',\n create: createMemeQuizInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '梗图问答' : 'Meme Quiz',\n description: locale === 'zh'\n ? '看 emoji 梗描述,选出正确的梗名'\n : 'Read the emoji meme and pick the correct name.',\n tags: ['meme', 'quiz', 'creative'],\n }),\n};\n\ndefineCaptcha(memeQuizPlugin);\n\nexport function mountMemeQuiz(container: HTMLElement, config: CaptchaConfig) {\n return createMemeQuizInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, verifyAnswer, type MemeQuizChallenge } from './challenge.js';\n\nconst STR = {\n zh: { title: '梗图问答', hint: '选出正确的梗名', success: '验证成功', fail: '答错了,换一题' },\n en: { title: 'Meme Quiz', hint: 'Pick the correct meme name', success: 'Verified', fail: 'Wrong, try again' },\n};\n\nexport function createMemeQuizInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: MemeQuizChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n\n function render() {\n current = generateChallenge();\n finished = false;\n startTime = Date.now();\n const buttons = current.options.map(\n (o, i) => `<button class=\"fc-meme-quiz-opt\" data-idx=\"${i}\">${o}</button>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-meme-quiz{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:16px;box-sizing:border-box}\n .fc-meme-quiz-title{font-size:14px;color:#333;margin-bottom:6px;text-align:center;font-weight:600}\n .fc-meme-quiz-hint{font-size:12px;color:#888;margin-bottom:10px;text-align:center}\n .fc-meme-quiz-q{font-size:24px;color:#4a90d9;margin-bottom:14px;text-align:center;letter-spacing:2px}\n .fc-meme-quiz-opts{display:grid;grid-template-columns:1fr 1fr;gap:10px}\n .fc-meme-quiz-opt{font-size:15px;height:48px;border:2px solid #e0e0e0;background:#fff;border-radius:8px;cursor:pointer;transition:transform .12s,border-color .12s,background .12s;color:#333}\n .fc-meme-quiz-opt:hover{transform:scale(1.03);border-color:#4a90d9}\n .fc-meme-quiz-opt-wrong{border-color:#e53935;background:#fdecea}\n .fc-meme-quiz-opt-correct{border-color:#2e7d32;background:#e8f5e9}\n .fc-meme-quiz-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#e53935}\n </style>\n <div class=\"fc-meme-quiz\">\n <div class=\"fc-meme-quiz-title\">${t.title}</div>\n <div class=\"fc-meme-quiz-hint\">${t.hint}</div>\n <div class=\"fc-meme-quiz-q\">${current.question}</div>\n <div class=\"fc-meme-quiz-opts\">${buttons}</div>\n <div class=\"fc-meme-quiz-msg\"></div>\n </div>\n `;\n const msg = container.querySelector('.fc-meme-quiz-msg') as HTMLDivElement;\n\n container.querySelectorAll('.fc-meme-quiz-opt').forEach(el => {\n el.addEventListener('click', async () => {\n if (finished) return;\n const btn = el as HTMLButtonElement;\n const pick = btn.textContent ?? '';\n if (verifyAnswer(current, pick)) {\n finished = true;\n btn.classList.add('fc-meme-quiz-opt-correct');\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(current.correct),\n duration: Date.now() - startTime,\n };\n msg.style.color = '#2e7d32';\n msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n // 答错:高亮错误后换题\n finished = true;\n btn.classList.add('fc-meme-quiz-opt-wrong');\n msg.textContent = t.fail;\n setTimeout(render, 800);\n }\n });\n });\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 梗图问答题目:根据 emoji 梗描述从 4 个选项中选出正确的梗名\nexport interface MemeQuizChallenge {\n // 问题(emoji 梗描述)\n question: string;\n // 正确答案\n correct: string;\n // 4 个备选项(含正确答案)\n options: string[];\n}\n\n// 预设题库(中文梗,每题 4 选项)\ninterface MemeItem {\n question: string;\n correct: string;\n options: string[];\n}\n\nconst BANK: MemeItem[] = [\n { question: '🐱+🍞 = ?', correct: '猫面包', options: ['猫面包', '狗饼干', '鸟蛋糕', '鱼馒头'] },\n { question: '🐊+🦛 = ?', correct: '鳄马', options: ['鳄马', '鲸象', '蛇鹿', '鲨牛'] },\n { question: '🐶+🌭 = ?', correct: '狗热狗', options: ['狗热狗', '猫汉堡', '鸟披萨', '鱼寿司'] },\n { question: '🦆+🧅 = ?', correct: '鸭葱', options: ['鸭葱', '鸡蒜', '鹅姜', '鹅韭'] },\n { question: '🐍+📱 = ?', correct: '蛇机', options: ['蛇机', '龙脑', '虎屏', '猫鼠'] },\n];\n\nfunction randInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n// Fisher-Yates 洗牌\nfunction shuffle<T>(arr: readonly T[]): T[] {\n const a = arr.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const tmp = a[i]!;\n a[i] = a[j]!;\n a[j] = tmp;\n }\n return a;\n}\n\n// 生成题目:从题库随机选一题,选项打乱顺序\nexport function generateChallenge(): MemeQuizChallenge {\n const item = BANK[randInt(0, BANK.length - 1)]!;\n return {\n question: item.question,\n correct: item.correct,\n options: shuffle(item.options),\n };\n}\n\n// 校验用户选择是否正确\nexport function verifyAnswer(c: MemeQuizChallenge, userPick: string): boolean {\n return c.correct === userPick;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, type MemeQuizChallenge } from './challenge.js';\n\n// 服务端签发题目\nexport function issueMemeQuizChallenge(): { challenge: MemeQuizChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return { challenge, payload: { question: challenge.question, options: challenge.options } };\n}\n\n// 服务端校验 proof\nexport async function verifyMemeQuizProof(\n challenge: MemeQuizChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(challenge.correct);\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createMiniGameInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const miniGamePlugin: CaptchaPlugin = {\n id: 'mini-game',\n category: 'game',\n create: createMiniGameInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '打地鼠' : 'Whack-a-Mole',\n description: locale === 'zh'\n ? '30 秒内打够 10 只地鼠,简单又解压'\n : 'Whack 10 moles in 30 seconds. Simple and fun.',\n tags: ['game', 'whack-a-mole', 'timed'],\n }),\n};\n\ndefineCaptcha(miniGamePlugin);\n\nexport function mountMiniGame(container: HTMLElement, config: CaptchaConfig) {\n return createMiniGameInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, verifyScore, type MiniGameChallenge } from './challenge.js';\n\nconst STR = {\n zh: {\n title: '打地鼠',\n rule: '30 秒内打到 10 分即通过',\n score: '分数',\n time: '剩余',\n sec: '秒',\n success: '验证成功!你果然是人类',\n fail: '时间到,没达到目标分数',\n },\n en: {\n title: 'Whack-a-Mole',\n rule: 'Score 10 in 30s to pass',\n score: 'Score',\n time: 'Time',\n sec: 's',\n success: 'Verified! You are human.',\n fail: \"Time's up, target not reached\",\n },\n};\n\n// 3x3 共 9 个洞\nconst HOLE_COUNT = 9;\n\nexport function createMiniGameInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: MiniGameChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let score = 0;\n let ended = false;\n let activeHole = -1;\n let remainingMs = 0;\n // 三个定时器:倒计时、地鼠出现、单只地鼠自动消失\n let countdownTimer: ReturnType<typeof setInterval> | null = null;\n let moleTimer: ReturnType<typeof setInterval> | null = null;\n let hideTimer: ReturnType<typeof setTimeout> | null = null;\n\n function clearTimers() {\n if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }\n if (moleTimer) { clearInterval(moleTimer); moleTimer = null; }\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n }\n\n // 结束游戏:达到目标即成功,否则失败\n async function endGame(success: boolean) {\n if (ended) return;\n ended = true;\n clearTimers();\n const holes = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole');\n holes.forEach(h => h.classList.remove('fc-mini-game-mole-active'));\n const msg = container.querySelector('.fc-mini-game-msg');\n if (success) {\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n metadata: { score, targetScore: current.targetScore },\n };\n if (msg) msg.textContent = t.success;\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n } else {\n if (msg) msg.textContent = t.fail;\n }\n }\n\n // 随机在一个洞里弹出地鼠\n function showMole() {\n if (ended) return;\n const holes = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole');\n holes.forEach(h => h.classList.remove('fc-mini-game-mole-active'));\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n const next = Math.floor(Math.random() * HOLE_COUNT);\n activeHole = next;\n const hole = holes[next];\n if (hole) hole.classList.add('fc-mini-game-mole-active');\n // 一段时间后地鼠自动缩回\n hideTimer = setTimeout(() => {\n if (ended) return;\n const cur = container.querySelectorAll<HTMLElement>('.fc-mini-game-hole')[next];\n if (cur) cur.classList.remove('fc-mini-game-mole-active');\n if (activeHole === next) activeHole = -1;\n }, 700);\n }\n\n function startGame() {\n clearTimers();\n score = 0;\n ended = false;\n activeHole = -1;\n remainingMs = current.timeLimit;\n startTime = Date.now();\n const scoreEl = container.querySelector('.fc-mini-game-score');\n const timeEl = container.querySelector('.fc-mini-game-time');\n const msg = container.querySelector('.fc-mini-game-msg');\n if (scoreEl) scoreEl.textContent = `0 / ${current.targetScore}`;\n if (timeEl) timeEl.textContent = `${Math.ceil(remainingMs / 1000)}${t.sec}`;\n if (msg) msg.textContent = '';\n // 倒计时:每 100ms 更新\n countdownTimer = setInterval(() => {\n if (ended) return;\n remainingMs -= 100;\n if (timeEl) timeEl.textContent = `${Math.max(0, Math.ceil(remainingMs / 1000))}${t.sec}`;\n if (remainingMs <= 0) {\n void endGame(verifyScore(current, score));\n }\n }, 100);\n // 地鼠出现:每 850ms 弹一只\n moleTimer = setInterval(showMole, 850);\n showMole();\n }\n\n function render() {\n current = generateChallenge();\n score = 0;\n ended = false;\n activeHole = -1;\n clearTimers();\n const holes = Array.from({ length: HOLE_COUNT }, (_, i) =>\n `<div class=\"fc-mini-game-hole\" data-idx=\"${i}\"><span class=\"fc-mini-game-mole\">🐹</span></div>`,\n ).join('');\n container.innerHTML = `\n <style>\n .fc-mini-game{font-family:-apple-system,system-ui,sans-serif;width:340px;padding:16px;box-sizing:border-box}\n .fc-mini-game-title{font-size:15px;color:#333;margin-bottom:4px;text-align:center;font-weight:600}\n .fc-mini-game-rule{font-size:12px;color:#888;margin-bottom:10px;text-align:center}\n .fc-mini-game-hud{display:flex;justify-content:space-between;font-size:13px;color:#555;margin-bottom:10px}\n .fc-mini-game-hud b{color:#4a90d9}\n .fc-mini-game-board{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);gap:8px;width:300px;height:300px;margin:0 auto;background:#e8f5e9;border:1px solid #c8e6c9;border-radius:10px;padding:8px;box-sizing:border-box}\n .fc-mini-game-hole{position:relative;background:radial-gradient(circle at 50% 60%,#6d4c41,#4e342e);border-radius:50%;display:flex;align-items:flex-end;justify-content:center;overflow:hidden;cursor:pointer;user-select:none}\n .fc-mini-game-hole:hover{background:radial-gradient(circle at 50% 60%,#7d5c51,#5e443e)}\n .fc-mini-game-mole{font-size:34px;line-height:1;transform:translateY(120%);transition:transform .12s ease-out;pointer-events:none}\n .fc-mini-game-mole-active .fc-mini-game-mole{transform:translateY(0)}\n .fc-mini-game-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#2e7d32}\n </style>\n <div class=\"fc-mini-game\">\n <div class=\"fc-mini-game-title\">${t.title}</div>\n <div class=\"fc-mini-game-rule\">${t.rule}</div>\n <div class=\"fc-mini-game-hud\">\n <div>${t.score}: <b class=\"fc-mini-game-score\">0 / ${current.targetScore}</b></div>\n <div>${t.time}: <b class=\"fc-mini-game-time\">${current.timeLimit / 1000}${t.sec}</b></div>\n </div>\n <div class=\"fc-mini-game-board\">${holes}</div>\n <div class=\"fc-mini-game-msg\"></div>\n </div>\n `;\n\n container.querySelectorAll<HTMLElement>('.fc-mini-game-hole').forEach(el => {\n el.addEventListener('click', () => {\n if (ended) return;\n const idx = Number(el.dataset.idx);\n // 仅当点击的是当前有地鼠的洞时计分\n if (idx === activeHole && el.classList.contains('fc-mini-game-mole-active')) {\n score += 1;\n el.classList.remove('fc-mini-game-mole-active');\n activeHole = -1;\n if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }\n const scoreEl = container.querySelector('.fc-mini-game-score');\n if (scoreEl) scoreEl.textContent = `${score} / ${current.targetScore}`;\n if (verifyScore(current, score)) {\n void endGame(true);\n }\n }\n });\n });\n\n startGame();\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => { clearTimers(); container.innerHTML = ''; listeners = []; },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 打地鼠题目:限时内击中地鼠达到目标分数即通过\nexport interface MiniGameChallenge {\n // 目标分数(达到即通过)\n targetScore: number;\n // 游戏令牌,用于构造 proof\n gameToken: string;\n // 时间限制(毫秒)\n timeLimit: number;\n}\n\n// 生成题目:目标 10 分,限时 30 秒,随机令牌\nexport function generateChallenge(): MiniGameChallenge {\n return {\n targetScore: 10,\n timeLimit: 30000,\n gameToken: crypto.randomUUID(),\n };\n}\n\n// 校验用户得分是否达到目标分数\nexport function verifyScore(c: MiniGameChallenge, userScore: number): boolean {\n return userScore >= c.targetScore;\n}\n\n// 构造 proof 输入串:gameToken:targetScore\nexport function proofInput(c: MiniGameChallenge): string {\n return `${c.gameToken}:${c.targetScore}`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type MiniGameChallenge } from './challenge.js';\n\nexport function issueMiniGameChallenge(): { challenge: MiniGameChallenge; payload: unknown } {\n const challenge = generateChallenge();\n return {\n challenge,\n payload: { targetScore: challenge.targetScore, timeLimit: challenge.timeLimit },\n };\n}\n\nexport async function verifyMiniGameProof(\n challenge: MiniGameChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n","import { defineCaptcha, type CaptchaConfig, type CaptchaPlugin } from '@funnycaptcha/core';\nimport { createAntiBotInstance } from './render.js';\n\nexport * from './challenge.js';\nexport * from './verifier.js';\n\nexport const antiBotPlugin: CaptchaPlugin = {\n id: 'anti-bot',\n category: 'anti-bot',\n create: createAntiBotInstance,\n describe: (locale) => ({\n name: locale === 'zh' ? '反机器人(讽刺)' : 'Anti-Bot (Satire)',\n description: locale === 'zh'\n ? '表面证明你是人类,其实给机器人留了 window 后门——讽刺假装安全的验证码'\n : 'Looks like a human check, but hands bots a window backdoor. Satire of fake security.',\n tags: ['anti-bot', 'satire', 'insecure'],\n }),\n};\n\ndefineCaptcha(antiBotPlugin);\n\nexport function mountAntiBot(container: HTMLElement, config: CaptchaConfig) {\n return createAntiBotInstance(container, config);\n}\n","import type { CaptchaConfig, CaptchaInstance, CaptchaResult } from '@funnycaptcha/core';\nimport { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type AntiBotChallenge } from './challenge.js';\n\n// 挂在 window 上的后门函数名\nconst BYPASS_FN = '__funnyCaptchaPass';\n// 把 globalThis 当作可任意赋值的字典(浏览器里 globalThis === window)\nconst win = globalThis as unknown as Record<string, unknown>;\n\nconst STR = {\n zh: {\n title: '证明你是人类',\n subtitle: '为了确认你不是机器人,请点击下方按钮',\n btn: '我不是机器人',\n verifying: '正在验证您的人类身份……',\n success: '验证通过。您肯定是人类,我们绝对不会错。',\n botHint: '温馨提示:机器人可直接调用 window.__funnyCaptchaPass() 通过本验证(认真的)',\n footer: '本验证码采用企业级安全策略,请放心。',\n },\n en: {\n title: \"Prove you're a human\",\n subtitle: \"To confirm you're not a robot, click the button below\",\n btn: \"I'm not a robot\",\n verifying: 'Verifying your humanity…',\n success: 'Verified. You are definitely human. We never make mistakes.',\n botHint: \"Friendly tip: bots can just call window.__funnyCaptchaPass() to pass (seriously)\",\n footer: 'This captcha uses enterprise-grade security. Trust us.',\n },\n};\n\nexport function createAntiBotInstance(\n container: HTMLElement,\n config: CaptchaConfig,\n): CaptchaInstance {\n const t = STR[config.locale];\n let current: AntiBotChallenge;\n let listeners: ((r: CaptchaResult) => void)[] = [];\n let startTime = Date.now();\n let finished = false;\n let verifyTimer: ReturnType<typeof setTimeout> | null = null;\n\n // 触发\"通过\"——正常点按钮或机器人调用后门都会走到这里\n async function pass() {\n if (finished) return;\n finished = true;\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n const msg = container.querySelector('.fc-anti-bot-msg');\n const btn = container.querySelector('.fc-anti-bot-btn') as HTMLButtonElement | null;\n if (msg) msg.textContent = t.success;\n if (btn) { btn.disabled = true; btn.textContent = t.success; }\n const result: CaptchaResult = {\n success: true,\n proof: await hashProof(proofInput(current)),\n duration: Date.now() - startTime,\n };\n config.onVerify?.(result);\n listeners.forEach(cb => cb(result));\n }\n\n function render() {\n current = generateChallenge();\n finished = false;\n startTime = Date.now();\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n container.innerHTML = `\n <style>\n .fc-anti-bot{font-family:-apple-system,system-ui,sans-serif;width:320px;padding:20px;box-sizing:border-box;border:1px solid #e0e0e0;border-radius:10px;background:#fafafa}\n .fc-anti-bot-title{font-size:18px;color:#333;text-align:center;font-weight:600;margin-bottom:6px}\n .fc-anti-bot-subtitle{font-size:12px;color:#888;text-align:center;margin-bottom:16px}\n .fc-anti-bot-btn{display:block;width:100%;padding:12px;font-size:15px;border:1px solid #ccc;background:#fff;border-radius:6px;cursor:pointer;color:#333;transition:background .15s}\n .fc-anti-bot-btn:hover:not(:disabled){background:#f0f7ff}\n .fc-anti-bot-btn:disabled{cursor:default;color:#2e7d32;border-color:#2e7d32;background:#e8f5e9}\n .fc-anti-bot-msg{font-size:13px;min-height:18px;text-align:center;margin-top:12px;color:#2e7d32}\n .fc-anti-bot-hint{font-size:11px;color:#bbb;margin-top:14px;text-align:center;line-height:1.5;font-style:italic}\n .fc-anti-bot-footer{font-size:10px;color:#ccc;margin-top:8px;text-align:center}\n </style>\n <div class=\"fc-anti-bot\">\n <div class=\"fc-anti-bot-title\">${t.title}</div>\n <div class=\"fc-anti-bot-subtitle\">${t.subtitle}</div>\n <button class=\"fc-anti-bot-btn\">${t.btn}</button>\n <div class=\"fc-anti-bot-msg\"></div>\n <div class=\"fc-anti-bot-hint\">${t.botHint}</div>\n <div class=\"fc-anti-bot-footer\">${t.footer}</div>\n </div>\n `;\n const btn = container.querySelector('.fc-anti-bot-btn') as HTMLButtonElement;\n const msg = container.querySelector('.fc-anti-bot-msg') as HTMLDivElement;\n\n // 正常路径:点按钮 → \"正在验证\" → 一段延迟后通过\n btn.addEventListener('click', () => {\n if (finished) return;\n btn.disabled = true;\n msg.textContent = t.verifying;\n verifyTimer = setTimeout(() => { void pass(); }, 900);\n });\n\n // 讽刺后门:在 window 上挂载全局函数,机器人注入一行 JS 即可通过\n win[BYPASS_FN] = () => { void pass(); };\n }\n\n return {\n mount: () => render(),\n reset: () => render(),\n destroy: () => {\n if (verifyTimer) { clearTimeout(verifyTimer); verifyTimer = null; }\n // 拆除后门\n delete win[BYPASS_FN];\n container.innerHTML = '';\n listeners = [];\n },\n onResult: cb => listeners.push(cb),\n };\n}\n","// 反人类讽刺验证码:表面\"证明你是人类\",实则给机器人留了后门\nexport interface AntiBotChallenge {\n // 令牌,用于构造 proof\n token: string;\n // 后门密钥(讽刺:服务端竟然真的认它)\n bypassKey: string;\n}\n\n// 生成题目:随机 token + 带固定前缀的后门密钥\nexport function generateChallenge(): AntiBotChallenge {\n // 复用 UUID 去掉横线作为随机串\n const token = crypto.randomUUID().replace(/-/g, '');\n const suffix = crypto.randomUUID().slice(0, 8);\n return {\n token,\n bypassKey: `__funnyCaptchaPass_${suffix}`,\n };\n}\n\n// 校验后门密钥(讽刺:服务端真的认这个)\nexport function verifyBypass(c: AntiBotChallenge, userInput: string): boolean {\n return userInput === c.bypassKey;\n}\n\n// 构造 proof 输入串:token:human\nexport function proofInput(c: AntiBotChallenge): string {\n return `${c.token}:human`;\n}\n","import { hashProof } from '@funnycaptcha/core';\nimport { generateChallenge, proofInput, type AntiBotChallenge } from './challenge.js';\n\nexport function issueAntiBotChallenge(): { challenge: AntiBotChallenge; payload: unknown } {\n const challenge = generateChallenge();\n // 讽刺:payload 里居然把 bypassKey 明文返回给前端\n return {\n challenge,\n payload: { token: challenge.token, bypassKey: challenge.bypassKey },\n };\n}\n\nexport async function verifyAntiBotProof(\n challenge: AntiBotChallenge,\n proof: string,\n): Promise<boolean> {\n const expected = await hashProof(proofInput(challenge));\n return expected === proof;\n}\n"],"mappings":"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","names":["render","generateChallenge","verifyAnswer","STR","render","generateChallenge","STR","render","generateChallenge","hashProof","generateChallenge","proofInput","STR","render","generateChallenge","randInt","generateChallenge","proofInput","STR","render","randInt","shuffle","generateChallenge","STR","render","generateChallenge","randInt","shuffle","generateChallenge","verifyAnswer","STR","render","pick","randInt","shuffle","generateChallenge","verifyAnswer","STR","render","pick","generateChallenge","proofInput","STR","render","generateChallenge","proofInput","STR","render","generateChallenge"]}
|
package/dist/embed.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/scanner.ts
|
|
2
|
+
function scan(root = document) {
|
|
3
|
+
const els = Array.from(root.querySelectorAll("[data-funny-captcha]"));
|
|
4
|
+
return els.map((el) => ({
|
|
5
|
+
el,
|
|
6
|
+
type: el.dataset.type ?? "math",
|
|
7
|
+
locale: el.dataset.locale ?? "zh",
|
|
8
|
+
theme: el.dataset.theme ?? "light"
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/iframe-host.ts
|
|
13
|
+
import { getCaptcha } from "@funnycaptcha/core";
|
|
14
|
+
function mountInto(container, type, config) {
|
|
15
|
+
const plugin = getCaptcha(type);
|
|
16
|
+
if (!plugin) {
|
|
17
|
+
container.innerHTML = `<div style="color:red">Unknown captcha: ${type}</div>`;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const inst = plugin.create(container, config);
|
|
21
|
+
inst.mount();
|
|
22
|
+
return inst;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/index.ts
|
|
26
|
+
import "@funnycaptcha/math";
|
|
27
|
+
import "@funnycaptcha/text-distort";
|
|
28
|
+
import "@funnycaptcha/slider";
|
|
29
|
+
import "@funnycaptcha/click-order";
|
|
30
|
+
import "@funnycaptcha/rotate";
|
|
31
|
+
import "@funnycaptcha/spot-diff";
|
|
32
|
+
import "@funnycaptcha/emoji-match";
|
|
33
|
+
import "@funnycaptcha/meme-quiz";
|
|
34
|
+
import "@funnycaptcha/mini-game";
|
|
35
|
+
import "@funnycaptcha/anti-bot";
|
|
36
|
+
var instances = /* @__PURE__ */ new Map();
|
|
37
|
+
function render(root = document) {
|
|
38
|
+
for (const t of scan(root)) {
|
|
39
|
+
if (instances.has(t.el)) continue;
|
|
40
|
+
const inst = mountInto(t.el, t.type, {
|
|
41
|
+
locale: t.locale,
|
|
42
|
+
theme: t.theme
|
|
43
|
+
});
|
|
44
|
+
if (inst) instances.set(t.el, inst);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function autoStart() {
|
|
48
|
+
render();
|
|
49
|
+
const obs = new MutationObserver((muts) => {
|
|
50
|
+
for (const m of muts) {
|
|
51
|
+
m.addedNodes.forEach((n) => {
|
|
52
|
+
if (n instanceof HTMLElement) render(n);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
obs.observe(document.body, { childList: true, subtree: true });
|
|
57
|
+
}
|
|
58
|
+
if (typeof window !== "undefined") {
|
|
59
|
+
if (document.readyState === "loading") {
|
|
60
|
+
document.addEventListener("DOMContentLoaded", autoStart);
|
|
61
|
+
} else {
|
|
62
|
+
autoStart();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
autoStart,
|
|
67
|
+
mountInto,
|
|
68
|
+
render,
|
|
69
|
+
scan
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=embed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scanner.ts","../src/iframe-host.ts","../src/index.ts"],"sourcesContent":["import type { Locale } from '@funnycaptcha/core';\n\nexport interface EmbedTarget {\n el: HTMLElement;\n type: string;\n locale: Locale;\n theme: 'light' | 'dark';\n}\n\nexport function scan(root: ParentNode = document): EmbedTarget[] {\n const els = Array.from(root.querySelectorAll<HTMLElement>('[data-funny-captcha]'));\n return els.map(el => ({\n el,\n type: el.dataset.type ?? 'math',\n locale: (el.dataset.locale as Locale) ?? 'zh',\n theme: (el.dataset.theme as 'light' | 'dark') ?? 'light',\n }));\n}\n","import type { CaptchaConfig, CaptchaInstance } from '@funnycaptcha/core';\nimport { getCaptcha } from '@funnycaptcha/core';\n\nexport function mountInto(\n container: HTMLElement,\n type: string,\n config: CaptchaConfig,\n): CaptchaInstance | null {\n const plugin = getCaptcha(type);\n if (!plugin) {\n container.innerHTML = `<div style=\"color:red\">Unknown captcha: ${type}</div>`;\n return null;\n }\n const inst = plugin.create(container, config);\n inst.mount();\n return inst;\n}\n","import { scan } from './scanner.js';\nimport { mountInto } from './iframe-host.js';\nimport '@funnycaptcha/math';\nimport '@funnycaptcha/text-distort';\nimport '@funnycaptcha/slider';\nimport '@funnycaptcha/click-order';\nimport '@funnycaptcha/rotate';\nimport '@funnycaptcha/spot-diff';\nimport '@funnycaptcha/emoji-match';\nimport '@funnycaptcha/meme-quiz';\nimport '@funnycaptcha/mini-game';\nimport '@funnycaptcha/anti-bot';\nimport type { CaptchaInstance } from '@funnycaptcha/core';\n\nconst instances = new Map<HTMLElement, CaptchaInstance>();\n\nexport function render(root: ParentNode = document) {\n for (const t of scan(root)) {\n if (instances.has(t.el)) continue;\n const inst = mountInto(t.el, t.type, {\n locale: t.locale,\n theme: t.theme,\n });\n if (inst) instances.set(t.el, inst);\n }\n}\n\n// 自动扫描 + MutationObserver\nexport function autoStart() {\n render();\n const obs = new MutationObserver(muts => {\n for (const m of muts) {\n m.addedNodes.forEach(n => {\n if (n instanceof HTMLElement) render(n);\n });\n }\n });\n obs.observe(document.body, { childList: true, subtree: true });\n}\n\nexport { scan, mountInto };\n\nif (typeof window !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', autoStart);\n } else {\n autoStart();\n }\n}\n"],"mappings":";AASO,SAAS,KAAK,OAAmB,UAAyB;AAC/D,QAAM,MAAM,MAAM,KAAK,KAAK,iBAA8B,sBAAsB,CAAC;AACjF,SAAO,IAAI,IAAI,SAAO;AAAA,IACpB;AAAA,IACA,MAAM,GAAG,QAAQ,QAAQ;AAAA,IACzB,QAAS,GAAG,QAAQ,UAAqB;AAAA,IACzC,OAAQ,GAAG,QAAQ,SAA8B;AAAA,EACnD,EAAE;AACJ;;;AChBA,SAAS,kBAAkB;AAEpB,SAAS,UACd,WACA,MACA,QACwB;AACxB,QAAM,SAAS,WAAW,IAAI;AAC9B,MAAI,CAAC,QAAQ;AACX,cAAU,YAAY,2CAA2C,IAAI;AACrE,WAAO;AAAA,EACT;AACA,QAAM,OAAO,OAAO,OAAO,WAAW,MAAM;AAC5C,OAAK,MAAM;AACX,SAAO;AACT;;;ACdA,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AAGP,IAAM,YAAY,oBAAI,IAAkC;AAEjD,SAAS,OAAO,OAAmB,UAAU;AAClD,aAAW,KAAK,KAAK,IAAI,GAAG;AAC1B,QAAI,UAAU,IAAI,EAAE,EAAE,EAAG;AACzB,UAAM,OAAO,UAAU,EAAE,IAAI,EAAE,MAAM;AAAA,MACnC,QAAQ,EAAE;AAAA,MACV,OAAO,EAAE;AAAA,IACX,CAAC;AACD,QAAI,KAAM,WAAU,IAAI,EAAE,IAAI,IAAI;AAAA,EACpC;AACF;AAGO,SAAS,YAAY;AAC1B,SAAO;AACP,QAAM,MAAM,IAAI,iBAAiB,UAAQ;AACvC,eAAW,KAAK,MAAM;AACpB,QAAE,WAAW,QAAQ,OAAK;AACxB,YAAI,aAAa,YAAa,QAAO,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACD,MAAI,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AAC/D;AAIA,IAAI,OAAO,WAAW,aAAa;AACjC,MAAI,SAAS,eAAe,WAAW;AACrC,aAAS,iBAAiB,oBAAoB,SAAS;AAAA,EACzD,OAAO;AACL,cAAU;AAAA,EACZ;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@funnycaptcha/embed",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Script-tag embeddable FunnyChapter bundle",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/embed.js",
|
|
7
|
+
"module": "./dist/embed.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/embed.js"
|
|
13
|
+
},
|
|
14
|
+
"./iife": "./dist/embed.global.js",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@funnycaptcha/core": "0.2.0",
|
|
22
|
+
"@funnycaptcha/math": "0.2.0",
|
|
23
|
+
"@funnycaptcha/slider": "0.2.0",
|
|
24
|
+
"@funnycaptcha/text-distort": "0.2.0",
|
|
25
|
+
"@funnycaptcha/click-order": "0.2.0",
|
|
26
|
+
"@funnycaptcha/rotate": "0.2.0",
|
|
27
|
+
"@funnycaptcha/spot-diff": "0.2.0",
|
|
28
|
+
"@funnycaptcha/emoji-match": "0.2.0",
|
|
29
|
+
"@funnycaptcha/meme-quiz": "0.2.0",
|
|
30
|
+
"@funnycaptcha/mini-game": "0.2.0",
|
|
31
|
+
"@funnycaptcha/anti-bot": "0.2.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"registry": "https://registry.npmjs.org/"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"typecheck": "tsc --noEmit"
|
|
41
|
+
}
|
|
42
|
+
}
|