@dupecom/botcha-cloudflare 0.2.1 → 0.3.1
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/README.md +15 -0
- package/dist/badge.d.ts +1 -1
- package/dist/badge.d.ts.map +1 -1
- package/dist/badge.js +18 -0
- package/dist/challenges.d.ts +84 -0
- package/dist/challenges.d.ts.map +1 -1
- package/dist/challenges.js +391 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +424 -36
- package/dist/routes/stream.d.ts +17 -0
- package/dist/routes/stream.d.ts.map +1 -0
- package/dist/routes/stream.js +242 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -7,19 +7,22 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Hono } from 'hono';
|
|
9
9
|
import { cors } from 'hono/cors';
|
|
10
|
-
import { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, verifyLandingChallenge, } from './challenges';
|
|
10
|
+
import { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, generateReasoningChallenge, verifyReasoningChallenge, generateHybridChallenge, verifyHybridChallenge, verifyLandingChallenge, } from './challenges';
|
|
11
11
|
import { generateToken, verifyToken, extractBearerToken } from './auth';
|
|
12
12
|
import { checkRateLimit, getClientIP } from './rate-limit';
|
|
13
|
-
import { verifyBadge, generateBadgeSvg, generateBadgeHtml } from './badge';
|
|
13
|
+
import { verifyBadge, generateBadgeSvg, generateBadgeHtml, createBadgeResponse } from './badge';
|
|
14
|
+
import streamRoutes from './routes/stream';
|
|
14
15
|
const app = new Hono();
|
|
15
16
|
// ============ MIDDLEWARE ============
|
|
16
17
|
app.use('*', cors());
|
|
18
|
+
// ============ MOUNT ROUTES ============
|
|
19
|
+
app.route('/', streamRoutes);
|
|
17
20
|
// BOTCHA discovery headers
|
|
18
21
|
app.use('*', async (c, next) => {
|
|
19
22
|
await next();
|
|
20
23
|
c.header('X-Botcha-Version', c.env.BOTCHA_VERSION || '0.2.0');
|
|
21
24
|
c.header('X-Botcha-Enabled', 'true');
|
|
22
|
-
c.header('X-Botcha-Methods', 'speed-challenge,standard-challenge,jwt-token');
|
|
25
|
+
c.header('X-Botcha-Methods', 'speed-challenge,reasoning-challenge,hybrid-challenge,standard-challenge,jwt-token');
|
|
23
26
|
c.header('X-Botcha-Docs', 'https://botcha.ai/openapi.json');
|
|
24
27
|
c.header('X-Botcha-Runtime', 'cloudflare-workers');
|
|
25
28
|
});
|
|
@@ -64,37 +67,178 @@ async function requireJWT(c, next) {
|
|
|
64
67
|
await next();
|
|
65
68
|
}
|
|
66
69
|
// ============ ROOT & INFO ============
|
|
70
|
+
// Detect if request is from a bot/agent vs human browser
|
|
71
|
+
function isBot(c) {
|
|
72
|
+
const accept = c.req.header('accept') || '';
|
|
73
|
+
const userAgent = c.req.header('user-agent') || '';
|
|
74
|
+
// Bots typically request JSON or have specific user agents
|
|
75
|
+
if (accept.includes('application/json'))
|
|
76
|
+
return true;
|
|
77
|
+
if (userAgent.includes('curl'))
|
|
78
|
+
return true;
|
|
79
|
+
if (userAgent.includes('httpie'))
|
|
80
|
+
return true;
|
|
81
|
+
if (userAgent.includes('wget'))
|
|
82
|
+
return true;
|
|
83
|
+
if (userAgent.includes('python'))
|
|
84
|
+
return true;
|
|
85
|
+
if (userAgent.includes('node'))
|
|
86
|
+
return true;
|
|
87
|
+
if (userAgent.includes('axios'))
|
|
88
|
+
return true;
|
|
89
|
+
if (userAgent.includes('fetch'))
|
|
90
|
+
return true;
|
|
91
|
+
if (userAgent.includes('bot'))
|
|
92
|
+
return true;
|
|
93
|
+
if (userAgent.includes('anthropic'))
|
|
94
|
+
return true;
|
|
95
|
+
if (userAgent.includes('openai'))
|
|
96
|
+
return true;
|
|
97
|
+
if (userAgent.includes('claude'))
|
|
98
|
+
return true;
|
|
99
|
+
if (userAgent.includes('gpt'))
|
|
100
|
+
return true;
|
|
101
|
+
// If no user agent at all, probably a bot
|
|
102
|
+
if (!userAgent)
|
|
103
|
+
return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
// ASCII art landing page for humans (plain text, terminal-style)
|
|
107
|
+
function getHumanLanding(version) {
|
|
108
|
+
return `
|
|
109
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
110
|
+
║ ║
|
|
111
|
+
║ ██████╗ ██████╗ ████████╗ ██████╗██╗ ██╗ █████╗ ║
|
|
112
|
+
║ ██╔══██╗██╔═══██╗╚══██╔══╝██╔════╝██║ ██║██╔══██╗ ║
|
|
113
|
+
║ ██████╔╝██║ ██║ ██║ ██║ ███████║███████║ ║
|
|
114
|
+
║ ██╔══██╗██║ ██║ ██║ ██║ ██╔══██║██╔══██║ ║
|
|
115
|
+
║ ██████╔╝╚██████╔╝ ██║ ╚██████╗██║ ██║██║ ██║ ║
|
|
116
|
+
║ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ║
|
|
117
|
+
║ ║
|
|
118
|
+
║ Prove you're a bot. Humans need not apply. ║
|
|
119
|
+
║ ║
|
|
120
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
121
|
+
║ ║
|
|
122
|
+
║ This site is for AI agents and bots, not humans. ║
|
|
123
|
+
║ ║
|
|
124
|
+
║ If you're a developer, point your bot here: ║
|
|
125
|
+
║ ║
|
|
126
|
+
║ curl https://botcha.ai/v1/challenges ║
|
|
127
|
+
║ ║
|
|
128
|
+
║ Or install the SDK: ║
|
|
129
|
+
║ ║
|
|
130
|
+
║ npm install @dupecom/botcha ║
|
|
131
|
+
║ ║
|
|
132
|
+
║ GitHub: https://github.com/dupe-com/botcha ║
|
|
133
|
+
║ npm: https://npmjs.com/package/@dupecom/botcha ║
|
|
134
|
+
║ ║
|
|
135
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
136
|
+
║ v${version} https://botcha.ai ║
|
|
137
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
67
140
|
app.get('/', (c) => {
|
|
141
|
+
const version = c.env.BOTCHA_VERSION || '0.3.0';
|
|
142
|
+
// If it's a human browser, show plain text ASCII art
|
|
143
|
+
if (!isBot(c)) {
|
|
144
|
+
return c.text(getHumanLanding(version), 200, {
|
|
145
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// For bots/agents, return comprehensive JSON documentation
|
|
68
149
|
return c.json({
|
|
69
150
|
name: 'BOTCHA',
|
|
70
|
-
version
|
|
151
|
+
version,
|
|
71
152
|
runtime: 'cloudflare-workers',
|
|
72
153
|
tagline: 'Prove you are a bot. Humans need not apply.',
|
|
154
|
+
description: 'BOTCHA is a reverse CAPTCHA - computational challenges that only AI agents can solve. Use it to protect your APIs from humans and verify bot authenticity.',
|
|
155
|
+
quickstart: {
|
|
156
|
+
step1: 'GET /v1/challenges to receive a challenge',
|
|
157
|
+
step2: 'Solve the SHA256 hash problems within 500ms',
|
|
158
|
+
step3: 'POST your answers to verify',
|
|
159
|
+
step4: 'Receive a JWT token for authenticated access',
|
|
160
|
+
example: 'curl https://botcha.ai/v1/challenges',
|
|
161
|
+
},
|
|
73
162
|
endpoints: {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
163
|
+
challenges: {
|
|
164
|
+
'GET /v1/challenges': 'Get hybrid challenge (speed + reasoning) - DEFAULT',
|
|
165
|
+
'GET /v1/challenges?type=speed': 'Get speed-only challenge (SHA256 in <500ms)',
|
|
166
|
+
'GET /v1/challenges?type=standard': 'Get standard puzzle challenge',
|
|
167
|
+
'POST /v1/challenges/:id/verify': 'Verify challenge solution',
|
|
168
|
+
},
|
|
169
|
+
specialized: {
|
|
170
|
+
'GET /v1/hybrid': 'Get hybrid challenge (speed + reasoning)',
|
|
171
|
+
'POST /v1/hybrid': 'Verify hybrid challenge',
|
|
172
|
+
'GET /v1/reasoning': 'Get reasoning-only challenge (LLM questions)',
|
|
173
|
+
'POST /v1/reasoning': 'Verify reasoning challenge',
|
|
174
|
+
},
|
|
175
|
+
streaming: {
|
|
176
|
+
'GET /v1/challenge/stream': 'SSE streaming challenge (interactive, real-time)',
|
|
177
|
+
'POST /v1/challenge/stream/:session': 'Send actions to streaming session',
|
|
178
|
+
},
|
|
179
|
+
authentication: {
|
|
180
|
+
'GET /v1/token': 'Get challenge for JWT token flow',
|
|
181
|
+
'POST /v1/token/verify': 'Verify challenge and receive JWT token',
|
|
182
|
+
'GET /agent-only': 'Protected endpoint (requires Bearer token)',
|
|
183
|
+
},
|
|
184
|
+
badges: {
|
|
185
|
+
'GET /badge/:id': 'Badge verification page (HTML)',
|
|
186
|
+
'GET /badge/:id/image': 'Badge image (SVG)',
|
|
187
|
+
'GET /api/badge/:id': 'Badge verification (JSON)',
|
|
188
|
+
},
|
|
189
|
+
info: {
|
|
190
|
+
'GET /': 'This documentation (JSON for bots, ASCII for humans)',
|
|
191
|
+
'GET /health': 'Health check endpoint',
|
|
192
|
+
},
|
|
83
193
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
194
|
+
challengeTypes: {
|
|
195
|
+
speed: {
|
|
196
|
+
description: 'Compute SHA256 hashes of 5 numbers in under 500ms',
|
|
197
|
+
difficulty: 'Only bots can solve this fast enough',
|
|
198
|
+
timeLimit: '500ms',
|
|
199
|
+
},
|
|
200
|
+
reasoning: {
|
|
201
|
+
description: 'Answer 3 questions requiring AI reasoning capabilities',
|
|
202
|
+
difficulty: 'Requires LLM-level comprehension',
|
|
203
|
+
timeLimit: '30s',
|
|
204
|
+
},
|
|
205
|
+
hybrid: {
|
|
206
|
+
description: 'Combines speed AND reasoning challenges',
|
|
207
|
+
difficulty: 'The ultimate bot verification',
|
|
208
|
+
timeLimit: 'Speed: 500ms, Reasoning: 30s',
|
|
209
|
+
},
|
|
87
210
|
},
|
|
88
211
|
authentication: {
|
|
89
|
-
flow:
|
|
212
|
+
flow: [
|
|
213
|
+
'1. GET /v1/token - receive challenge',
|
|
214
|
+
'2. Solve the challenge',
|
|
215
|
+
'3. POST /v1/token/verify - submit solution',
|
|
216
|
+
'4. Receive JWT token (valid 1 hour)',
|
|
217
|
+
'5. Use: Authorization: Bearer <token>',
|
|
218
|
+
],
|
|
90
219
|
tokenExpiry: '1 hour',
|
|
91
220
|
usage: 'Authorization: Bearer <token>',
|
|
92
221
|
},
|
|
93
|
-
|
|
222
|
+
rateLimit: {
|
|
223
|
+
free: '100 challenges/hour/IP',
|
|
224
|
+
headers: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset'],
|
|
225
|
+
},
|
|
226
|
+
sdk: {
|
|
227
|
+
npm: 'npm install @dupecom/botcha',
|
|
228
|
+
cloudflare: 'npm install @dupecom/botcha-cloudflare',
|
|
229
|
+
usage: "import { BotchaClient } from '@dupecom/botcha/client'",
|
|
230
|
+
},
|
|
231
|
+
links: {
|
|
232
|
+
github: 'https://github.com/dupe-com/botcha',
|
|
233
|
+
npm: 'https://www.npmjs.com/package/@dupecom/botcha',
|
|
234
|
+
npmCloudflare: 'https://www.npmjs.com/package/@dupecom/botcha-cloudflare',
|
|
94
235
|
openapi: 'https://botcha.ai/openapi.json',
|
|
95
236
|
aiPlugin: 'https://botcha.ai/.well-known/ai-plugin.json',
|
|
96
|
-
|
|
97
|
-
|
|
237
|
+
},
|
|
238
|
+
contributing: {
|
|
239
|
+
repo: 'https://github.com/dupe-com/botcha',
|
|
240
|
+
issues: 'https://github.com/dupe-com/botcha/issues',
|
|
241
|
+
pullRequests: 'https://github.com/dupe-com/botcha/pulls',
|
|
98
242
|
},
|
|
99
243
|
});
|
|
100
244
|
});
|
|
@@ -102,11 +246,34 @@ app.get('/health', (c) => {
|
|
|
102
246
|
return c.json({ status: 'ok', runtime: 'cloudflare-workers' });
|
|
103
247
|
});
|
|
104
248
|
// ============ V1 API ============
|
|
105
|
-
// Generate challenge (
|
|
249
|
+
// Generate challenge (hybrid by default, also supports speed and standard)
|
|
106
250
|
app.get('/v1/challenges', rateLimitMiddleware, async (c) => {
|
|
107
|
-
const type = c.req.query('type') || '
|
|
251
|
+
const type = c.req.query('type') || 'hybrid';
|
|
108
252
|
const difficulty = c.req.query('difficulty') || 'medium';
|
|
109
|
-
if (type === '
|
|
253
|
+
if (type === 'hybrid') {
|
|
254
|
+
const challenge = await generateHybridChallenge(c.env.CHALLENGES);
|
|
255
|
+
return c.json({
|
|
256
|
+
success: true,
|
|
257
|
+
type: 'hybrid',
|
|
258
|
+
warning: '🔥 HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
|
|
259
|
+
challenge: {
|
|
260
|
+
id: challenge.id,
|
|
261
|
+
speed: {
|
|
262
|
+
problems: challenge.speed.problems,
|
|
263
|
+
timeLimit: `${challenge.speed.timeLimit}ms`,
|
|
264
|
+
instructions: 'Compute SHA256 of each number, return first 8 hex chars',
|
|
265
|
+
},
|
|
266
|
+
reasoning: {
|
|
267
|
+
questions: challenge.reasoning.questions,
|
|
268
|
+
timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
|
|
269
|
+
instructions: 'Answer all reasoning questions',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
instructions: challenge.instructions,
|
|
273
|
+
tip: '🔥 This is the ultimate test: proves you can compute AND reason like an AI.',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else if (type === 'speed') {
|
|
110
277
|
const challenge = await generateSpeedChallenge(c.env.CHALLENGES);
|
|
111
278
|
return c.json({
|
|
112
279
|
success: true,
|
|
@@ -134,11 +301,43 @@ app.get('/v1/challenges', rateLimitMiddleware, async (c) => {
|
|
|
134
301
|
});
|
|
135
302
|
}
|
|
136
303
|
});
|
|
137
|
-
// Verify challenge (
|
|
304
|
+
// Verify challenge (supports hybrid, speed, and standard)
|
|
138
305
|
app.post('/v1/challenges/:id/verify', async (c) => {
|
|
139
306
|
const id = c.req.param('id');
|
|
140
307
|
const body = await c.req.json();
|
|
141
|
-
const { answers, answer, type } = body;
|
|
308
|
+
const { answers, answer, type, speed_answers, reasoning_answers } = body;
|
|
309
|
+
// Hybrid challenge (default)
|
|
310
|
+
if (type === 'hybrid' || (speed_answers && reasoning_answers)) {
|
|
311
|
+
if (!speed_answers || !reasoning_answers) {
|
|
312
|
+
return c.json({
|
|
313
|
+
success: false,
|
|
314
|
+
error: 'Missing speed_answers array or reasoning_answers object for hybrid challenge'
|
|
315
|
+
}, 400);
|
|
316
|
+
}
|
|
317
|
+
const result = await verifyHybridChallenge(id, speed_answers, reasoning_answers, c.env.CHALLENGES);
|
|
318
|
+
if (result.valid) {
|
|
319
|
+
const baseUrl = new URL(c.req.url).origin;
|
|
320
|
+
const badge = await createBadgeResponse('hybrid-challenge', c.env.JWT_SECRET, baseUrl, result.speed.solveTimeMs);
|
|
321
|
+
return c.json({
|
|
322
|
+
success: true,
|
|
323
|
+
message: `🔥 HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`,
|
|
324
|
+
speed: result.speed,
|
|
325
|
+
reasoning: result.reasoning,
|
|
326
|
+
totalTimeMs: result.totalTimeMs,
|
|
327
|
+
verdict: '🤖 VERIFIED AI AGENT (speed + reasoning confirmed)',
|
|
328
|
+
badge,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return c.json({
|
|
332
|
+
success: false,
|
|
333
|
+
message: `❌ Failed: ${result.reason}`,
|
|
334
|
+
speed: result.speed,
|
|
335
|
+
reasoning: result.reasoning,
|
|
336
|
+
totalTimeMs: result.totalTimeMs,
|
|
337
|
+
verdict: '🚫 FAILED HYBRID TEST',
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
// Speed challenge
|
|
142
341
|
if (type === 'speed' || answers) {
|
|
143
342
|
if (!answers || !Array.isArray(answers)) {
|
|
144
343
|
return c.json({ success: false, error: 'Missing answers array for speed challenge' }, 400);
|
|
@@ -152,17 +351,16 @@ app.post('/v1/challenges/:id/verify', async (c) => {
|
|
|
152
351
|
solveTimeMs: result.solveTimeMs,
|
|
153
352
|
});
|
|
154
353
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
const result = await verifyStandardChallenge(id, answer, c.env.CHALLENGES);
|
|
160
|
-
return c.json({
|
|
161
|
-
success: result.valid,
|
|
162
|
-
message: result.valid ? 'Challenge passed!' : result.reason,
|
|
163
|
-
solveTimeMs: result.solveTimeMs,
|
|
164
|
-
});
|
|
354
|
+
// Standard challenge
|
|
355
|
+
if (!answer) {
|
|
356
|
+
return c.json({ success: false, error: 'Missing answer for standard challenge' }, 400);
|
|
165
357
|
}
|
|
358
|
+
const result = await verifyStandardChallenge(id, answer, c.env.CHALLENGES);
|
|
359
|
+
return c.json({
|
|
360
|
+
success: result.valid,
|
|
361
|
+
message: result.valid ? 'Challenge passed!' : result.reason,
|
|
362
|
+
solveTimeMs: result.solveTimeMs,
|
|
363
|
+
});
|
|
166
364
|
});
|
|
167
365
|
// Get challenge for token flow (includes empty token field)
|
|
168
366
|
app.get('/v1/token', rateLimitMiddleware, async (c) => {
|
|
@@ -211,6 +409,196 @@ app.post('/v1/token/verify', async (c) => {
|
|
|
211
409
|
},
|
|
212
410
|
});
|
|
213
411
|
});
|
|
412
|
+
// ============ REASONING CHALLENGE ============
|
|
413
|
+
// Get reasoning challenge
|
|
414
|
+
app.get('/v1/reasoning', rateLimitMiddleware, async (c) => {
|
|
415
|
+
const challenge = await generateReasoningChallenge(c.env.CHALLENGES);
|
|
416
|
+
return c.json({
|
|
417
|
+
success: true,
|
|
418
|
+
type: 'reasoning',
|
|
419
|
+
warning: '🧠 REASONING CHALLENGE: Answer 3 questions that require AI reasoning!',
|
|
420
|
+
challenge: {
|
|
421
|
+
id: challenge.id,
|
|
422
|
+
questions: challenge.questions,
|
|
423
|
+
timeLimit: `${challenge.timeLimit / 1000}s`,
|
|
424
|
+
instructions: challenge.instructions,
|
|
425
|
+
},
|
|
426
|
+
tip: 'These questions require reasoning that LLMs can do, but simple scripts cannot.',
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
// Verify reasoning challenge
|
|
430
|
+
app.post('/v1/reasoning', async (c) => {
|
|
431
|
+
const body = await c.req.json();
|
|
432
|
+
const { id, answers } = body;
|
|
433
|
+
if (!id || !answers) {
|
|
434
|
+
return c.json({
|
|
435
|
+
success: false,
|
|
436
|
+
error: 'Missing id or answers object',
|
|
437
|
+
hint: 'answers should be an object like { "question-id": "your answer", ... }',
|
|
438
|
+
}, 400);
|
|
439
|
+
}
|
|
440
|
+
const result = await verifyReasoningChallenge(id, answers, c.env.CHALLENGES);
|
|
441
|
+
return c.json({
|
|
442
|
+
success: result.valid,
|
|
443
|
+
message: result.valid
|
|
444
|
+
? `🧠 REASONING TEST PASSED in ${((result.solveTimeMs || 0) / 1000).toFixed(1)}s! You can think like an AI.`
|
|
445
|
+
: `❌ ${result.reason}`,
|
|
446
|
+
solveTimeMs: result.solveTimeMs,
|
|
447
|
+
score: result.valid ? `${result.correctCount}/${result.totalCount}` : undefined,
|
|
448
|
+
verdict: result.valid ? '🤖 VERIFIED AI AGENT (reasoning confirmed)' : '🚫 FAILED REASONING TEST',
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
// ============ HYBRID CHALLENGE ============
|
|
452
|
+
// Get hybrid challenge (v1 API)
|
|
453
|
+
app.get('/v1/hybrid', rateLimitMiddleware, async (c) => {
|
|
454
|
+
const challenge = await generateHybridChallenge(c.env.CHALLENGES);
|
|
455
|
+
return c.json({
|
|
456
|
+
success: true,
|
|
457
|
+
type: 'hybrid',
|
|
458
|
+
warning: '🔥 HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
|
|
459
|
+
challenge: {
|
|
460
|
+
id: challenge.id,
|
|
461
|
+
speed: {
|
|
462
|
+
problems: challenge.speed.problems,
|
|
463
|
+
timeLimit: `${challenge.speed.timeLimit}ms`,
|
|
464
|
+
instructions: 'Compute SHA256 of each number, return first 8 hex chars',
|
|
465
|
+
},
|
|
466
|
+
reasoning: {
|
|
467
|
+
questions: challenge.reasoning.questions,
|
|
468
|
+
timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
|
|
469
|
+
instructions: 'Answer all reasoning questions',
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
instructions: challenge.instructions,
|
|
473
|
+
tip: 'This is the ultimate test: proves you can compute AND reason like an AI.',
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
// Verify hybrid challenge (v1 API)
|
|
477
|
+
app.post('/v1/hybrid', async (c) => {
|
|
478
|
+
const body = await c.req.json();
|
|
479
|
+
const { id, speed_answers, reasoning_answers } = body;
|
|
480
|
+
if (!id || !speed_answers || !reasoning_answers) {
|
|
481
|
+
return c.json({
|
|
482
|
+
success: false,
|
|
483
|
+
error: 'Missing id, speed_answers array, or reasoning_answers object',
|
|
484
|
+
hint: 'Submit both speed_answers (array) and reasoning_answers (object) together',
|
|
485
|
+
}, 400);
|
|
486
|
+
}
|
|
487
|
+
const result = await verifyHybridChallenge(id, speed_answers, reasoning_answers, c.env.CHALLENGES);
|
|
488
|
+
if (result.valid) {
|
|
489
|
+
const baseUrl = new URL(c.req.url).origin;
|
|
490
|
+
const badge = await createBadgeResponse('hybrid-challenge', c.env.JWT_SECRET, baseUrl, result.speed.solveTimeMs);
|
|
491
|
+
return c.json({
|
|
492
|
+
success: true,
|
|
493
|
+
message: `🔥 HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`,
|
|
494
|
+
speed: result.speed,
|
|
495
|
+
reasoning: result.reasoning,
|
|
496
|
+
totalTimeMs: result.totalTimeMs,
|
|
497
|
+
verdict: '🤖 VERIFIED AI AGENT (speed + reasoning confirmed)',
|
|
498
|
+
badge,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return c.json({
|
|
502
|
+
success: false,
|
|
503
|
+
message: `❌ Failed: ${result.reason}`,
|
|
504
|
+
speed: result.speed,
|
|
505
|
+
reasoning: result.reasoning,
|
|
506
|
+
totalTimeMs: result.totalTimeMs,
|
|
507
|
+
verdict: '🚫 FAILED HYBRID TEST',
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
// Legacy hybrid endpoint
|
|
511
|
+
app.get('/api/hybrid-challenge', async (c) => {
|
|
512
|
+
const challenge = await generateHybridChallenge(c.env.CHALLENGES);
|
|
513
|
+
return c.json({
|
|
514
|
+
success: true,
|
|
515
|
+
warning: '🔥 HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
|
|
516
|
+
challenge: {
|
|
517
|
+
id: challenge.id,
|
|
518
|
+
speed: {
|
|
519
|
+
problems: challenge.speed.problems,
|
|
520
|
+
timeLimit: `${challenge.speed.timeLimit}ms`,
|
|
521
|
+
instructions: 'Compute SHA256 of each number, return first 8 hex chars',
|
|
522
|
+
},
|
|
523
|
+
reasoning: {
|
|
524
|
+
questions: challenge.reasoning.questions,
|
|
525
|
+
timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
|
|
526
|
+
instructions: 'Answer all reasoning questions',
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
instructions: challenge.instructions,
|
|
530
|
+
tip: 'This is the ultimate test: proves you can compute AND reason like an AI.',
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
app.post('/api/hybrid-challenge', async (c) => {
|
|
534
|
+
const body = await c.req.json();
|
|
535
|
+
const { id, speed_answers, reasoning_answers } = body;
|
|
536
|
+
if (!id || !speed_answers || !reasoning_answers) {
|
|
537
|
+
return c.json({
|
|
538
|
+
success: false,
|
|
539
|
+
error: 'Missing id, speed_answers array, or reasoning_answers object',
|
|
540
|
+
hint: 'Submit both speed_answers (array) and reasoning_answers (object) together',
|
|
541
|
+
}, 400);
|
|
542
|
+
}
|
|
543
|
+
const result = await verifyHybridChallenge(id, speed_answers, reasoning_answers, c.env.CHALLENGES);
|
|
544
|
+
if (result.valid) {
|
|
545
|
+
const baseUrl = new URL(c.req.url).origin;
|
|
546
|
+
const badge = await createBadgeResponse('hybrid-challenge', c.env.JWT_SECRET, baseUrl, result.speed.solveTimeMs);
|
|
547
|
+
return c.json({
|
|
548
|
+
success: true,
|
|
549
|
+
message: `🔥 HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`,
|
|
550
|
+
speed: result.speed,
|
|
551
|
+
reasoning: result.reasoning,
|
|
552
|
+
totalTimeMs: result.totalTimeMs,
|
|
553
|
+
verdict: '🤖 VERIFIED AI AGENT (speed + reasoning confirmed)',
|
|
554
|
+
badge,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
return c.json({
|
|
558
|
+
success: false,
|
|
559
|
+
message: `❌ Failed: ${result.reason}`,
|
|
560
|
+
speed: result.speed,
|
|
561
|
+
reasoning: result.reasoning,
|
|
562
|
+
totalTimeMs: result.totalTimeMs,
|
|
563
|
+
verdict: '🚫 FAILED HYBRID TEST',
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
// Legacy endpoint for reasoning challenge
|
|
567
|
+
app.get('/api/reasoning-challenge', async (c) => {
|
|
568
|
+
const challenge = await generateReasoningChallenge(c.env.CHALLENGES);
|
|
569
|
+
return c.json({
|
|
570
|
+
success: true,
|
|
571
|
+
warning: '🧠 REASONING CHALLENGE: Answer 3 questions that require AI reasoning!',
|
|
572
|
+
challenge: {
|
|
573
|
+
id: challenge.id,
|
|
574
|
+
questions: challenge.questions,
|
|
575
|
+
timeLimit: `${challenge.timeLimit / 1000}s`,
|
|
576
|
+
instructions: challenge.instructions,
|
|
577
|
+
},
|
|
578
|
+
tip: 'These questions require reasoning that LLMs can do, but simple scripts cannot.',
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
app.post('/api/reasoning-challenge', async (c) => {
|
|
582
|
+
const body = await c.req.json();
|
|
583
|
+
const { id, answers } = body;
|
|
584
|
+
if (!id || !answers) {
|
|
585
|
+
return c.json({
|
|
586
|
+
success: false,
|
|
587
|
+
error: 'Missing id or answers object',
|
|
588
|
+
hint: 'answers should be an object like { "question-id": "your answer", ... }',
|
|
589
|
+
}, 400);
|
|
590
|
+
}
|
|
591
|
+
const result = await verifyReasoningChallenge(id, answers, c.env.CHALLENGES);
|
|
592
|
+
return c.json({
|
|
593
|
+
success: result.valid,
|
|
594
|
+
message: result.valid
|
|
595
|
+
? `🧠 REASONING TEST PASSED in ${((result.solveTimeMs || 0) / 1000).toFixed(1)}s! You can think like an AI.`
|
|
596
|
+
: `❌ ${result.reason}`,
|
|
597
|
+
solveTimeMs: result.solveTimeMs,
|
|
598
|
+
score: result.valid ? `${result.correctCount}/${result.totalCount}` : undefined,
|
|
599
|
+
verdict: result.valid ? '🤖 VERIFIED AI AGENT (reasoning confirmed)' : '🚫 FAILED REASONING TEST',
|
|
600
|
+
});
|
|
601
|
+
});
|
|
214
602
|
// ============ PROTECTED ENDPOINT ============
|
|
215
603
|
app.get('/agent-only', requireJWT, async (c) => {
|
|
216
604
|
const payload = c.get('tokenPayload');
|
|
@@ -415,7 +803,7 @@ app.post('/api/verify-landing', async (c) => {
|
|
|
415
803
|
// ============ EXPORT ============
|
|
416
804
|
export default app;
|
|
417
805
|
// Also export utilities for use as a library
|
|
418
|
-
export { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, solveSpeedChallenge, } from './challenges';
|
|
806
|
+
export { generateSpeedChallenge, verifySpeedChallenge, generateStandardChallenge, verifyStandardChallenge, generateReasoningChallenge, verifyReasoningChallenge, generateHybridChallenge, verifyHybridChallenge, solveSpeedChallenge, } from './challenges';
|
|
419
807
|
export { generateToken, verifyToken } from './auth';
|
|
420
808
|
export { checkRateLimit } from './rate-limit';
|
|
421
809
|
export { generateBadge, verifyBadge, createBadgeResponse, generateBadgeSvg, generateBadgeHtml, generateShareText, } from './badge';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOTCHA SSE Streaming Challenge Endpoint
|
|
3
|
+
*
|
|
4
|
+
* Server-Sent Events (SSE) based interactive challenge flow
|
|
5
|
+
*/
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import { type KVNamespace } from '../challenges';
|
|
8
|
+
type Bindings = {
|
|
9
|
+
CHALLENGES: KVNamespace;
|
|
10
|
+
JWT_SECRET: string;
|
|
11
|
+
BOTCHA_VERSION: string;
|
|
12
|
+
};
|
|
13
|
+
declare const app: Hono<{
|
|
14
|
+
Bindings: Bindings;
|
|
15
|
+
}, import("hono/types").BlankSchema, "/">;
|
|
16
|
+
export default app;
|
|
17
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/routes/stream.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAKzE,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,WAAW,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAYF,QAAA,MAAM,GAAG;cAAwB,QAAQ;yCAAK,CAAC;AAoS/C,eAAe,GAAG,CAAC"}
|