@dupecom/botcha-cloudflare 0.3.0 → 0.3.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAYL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAOtB,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,YAAY,CAAC,EAAE;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,iBAAiB,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,GAAG;cAAwB,QAAQ;eAAa,SAAS;yCAAK,CAAC;AAuvBrE,eAAe,GAAG,CAAC;AAGnB,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAYL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAOtB,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE,WAAW,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,YAAY,CAAC,EAAE;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,iBAAiB,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,GAAG;cAAwB,QAAQ;eAAa,SAAS;yCAAK,CAAC;AAs3BrE,eAAe,GAAG,CAAC;AAGnB,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -67,44 +67,178 @@ async function requireJWT(c, next) {
67
67
  await next();
68
68
  }
69
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
+ }
70
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
71
149
  return c.json({
72
150
  name: 'BOTCHA',
73
- version: c.env.BOTCHA_VERSION || '0.2.0',
151
+ version,
74
152
  runtime: 'cloudflare-workers',
75
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
+ },
76
162
  endpoints: {
77
- '/': 'API info',
78
- '/health': 'Health check',
79
- '/v1/challenges': 'Generate challenge (GET) or verify (POST) - hybrid by default',
80
- '/v1/challenges?type=speed': 'Speed-only challenge (SHA256 in 500ms)',
81
- '/v1/challenges?type=standard': 'Standard challenge (puzzle solving)',
82
- '/v1/hybrid': 'Hybrid challenge - speed + reasoning combined (GET/POST)',
83
- '/v1/reasoning': 'Reasoning-only challenge - LLM questions (GET/POST)',
84
- '/v1/token': 'Get challenge for JWT token flow (GET)',
85
- '/v1/token/verify': 'Verify challenge and get JWT (POST)',
86
- '/v1/challenge/stream': 'SSE streaming challenge (interactive flow)',
87
- '/v1/challenge/stream/:session': 'SSE session actions (POST: go/solve)',
88
- '/agent-only': 'Protected endpoint (requires JWT)',
89
- '/badge/:id': 'Badge verification page (HTML)',
90
- '/badge/:id/image': 'Badge image (SVG)',
91
- '/api/badge/:id': 'Badge verification (JSON)',
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
+ },
92
193
  },
93
- defaultChallenge: 'hybrid',
94
- rateLimit: {
95
- free: '100 challenges/hour/IP',
96
- headers: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset'],
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
+ },
97
210
  },
98
211
  authentication: {
99
- flow: 'GET /v1/token → solve challenge → POST /v1/token/verify → Bearer token',
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
+ ],
100
219
  tokenExpiry: '1 hour',
101
220
  usage: 'Authorization: Bearer <token>',
102
221
  },
103
- discovery: {
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',
104
235
  openapi: 'https://botcha.ai/openapi.json',
105
236
  aiPlugin: 'https://botcha.ai/.well-known/ai-plugin.json',
106
- npm: 'https://www.npmjs.com/package/@dupecom/botcha-cloudflare',
107
- github: 'https://github.com/i8ramin/botcha',
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',
108
242
  },
109
243
  });
110
244
  });
@@ -1 +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"}
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;AAiT/C,eAAe,GAAG,CAAC"}
@@ -143,29 +143,40 @@ app.post('/v1/challenge/stream/:session', async (c) => {
143
143
  expectedAnswers.push(await sha256First(num.toString(), 8));
144
144
  }
145
145
  // Update session
146
+ const timerStart = Date.now();
146
147
  session.status = 'challenged';
147
148
  session.problems = problems;
148
149
  session.expectedAnswers = expectedAnswers;
149
- session.timerStart = Date.now();
150
+ session.timerStart = timerStart;
151
+ // Store session
150
152
  await storeSession(c.env.CHALLENGES, session);
151
- // Return challenge event
153
+ // Return challenge event with timer start for client to track
152
154
  return c.json({
153
155
  success: true,
154
156
  event: 'challenge',
155
157
  data: {
156
158
  problems,
157
159
  timeLimit: 500,
160
+ timerStart, // Include so client can verify timing
158
161
  instructions: 'Compute SHA256 of each number, return first 8 hex chars',
159
162
  },
160
163
  });
161
164
  }
162
165
  // Handle "solve" action - verify answers
163
166
  if (action === 'solve') {
167
+ // Handle KV eventual consistency - retry once if still in 'ready' state
168
+ if (session.status === 'ready') {
169
+ await new Promise(resolve => setTimeout(resolve, 100));
170
+ const retrySession = await getSession(c.env.CHALLENGES, sessionId);
171
+ if (retrySession && retrySession.status === 'challenged') {
172
+ Object.assign(session, retrySession);
173
+ }
174
+ }
164
175
  if (session.status !== 'challenged') {
165
176
  return c.json({
166
177
  success: false,
167
178
  error: 'INVALID_STATE',
168
- message: `Session is in ${session.status} state, expected challenged`,
179
+ message: `Session is in ${session.status} state, expected challenged. Try sending GO first.`,
169
180
  }, 400);
170
181
  }
171
182
  if (!answers || !Array.isArray(answers)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dupecom/botcha-cloudflare",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "BOTCHA for Cloudflare Workers - Prove you're a bot. Humans need not apply.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",