@dupecom/botcha 0.4.1 → 0.4.3
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 +67 -7
- package/dist/lib/client/index.d.ts +20 -24
- package/dist/lib/client/index.d.ts.map +1 -1
- package/dist/lib/client/index.js +201 -20
- package/dist/lib/client/types.d.ts +56 -0
- package/dist/lib/client/types.d.ts.map +1 -0
- package/dist/lib/client/types.js +6 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
[](https://www.npmjs.com/package/@dupecom/botcha)
|
|
15
15
|
[](https://opensource.org/licenses/MIT)
|
|
16
|
+
[](./.github/CONTRIBUTING.md)
|
|
16
17
|
|
|
17
18
|
🌐 **Website:** [botcha.ai](https://botcha.ai)
|
|
18
19
|
📦 **npm:** [@dupecom/botcha](https://www.npmjs.com/package/@dupecom/botcha)
|
|
@@ -20,10 +21,12 @@
|
|
|
20
21
|
|
|
21
22
|
## Packages
|
|
22
23
|
|
|
23
|
-
| Package |
|
|
24
|
-
|
|
25
|
-
| [`@dupecom/botcha`](https://www.npmjs.com/package/@dupecom/botcha) |
|
|
26
|
-
| [`@dupecom/botcha-
|
|
24
|
+
| Package | Description | Install |
|
|
25
|
+
|---------|-------------|---------|
|
|
26
|
+
| [`@dupecom/botcha`](https://www.npmjs.com/package/@dupecom/botcha) | Core library + Express middleware | `npm install @dupecom/botcha` |
|
|
27
|
+
| [`@dupecom/botcha-cli`](https://www.npmjs.com/package/@dupecom/botcha-cli) | CLI tool for testing & debugging | `npm install -g @dupecom/botcha-cli` |
|
|
28
|
+
| [`@dupecom/botcha-langchain`](https://www.npmjs.com/package/@dupecom/botcha-langchain) | LangChain integration for AI agents | `npm install @dupecom/botcha-langchain` |
|
|
29
|
+
| [`@dupecom/botcha-cloudflare`](./packages/cloudflare-workers) | Cloudflare Workers runtime | `npm install @dupecom/botcha-cloudflare` |
|
|
27
30
|
|
|
28
31
|
## Why?
|
|
29
32
|
|
|
@@ -97,10 +100,12 @@ When a 403 is returned with a challenge:
|
|
|
97
100
|
|
|
98
101
|
```http
|
|
99
102
|
X-Botcha-Challenge-Id: abc123
|
|
100
|
-
X-Botcha-Challenge-Type:
|
|
101
|
-
X-Botcha-Time-Limit:
|
|
103
|
+
X-Botcha-Challenge-Type: speed
|
|
104
|
+
X-Botcha-Time-Limit: 500
|
|
102
105
|
```
|
|
103
106
|
|
|
107
|
+
`X-Botcha-Challenge-Type` can be `speed` or `standard` depending on the configured challenge mode.
|
|
108
|
+
|
|
104
109
|
**Example**: An agent can detect BOTCHA just by inspecting headers on ANY request:
|
|
105
110
|
|
|
106
111
|
```typescript
|
|
@@ -206,7 +211,7 @@ npm install
|
|
|
206
211
|
npm run deploy # Deploys to your Cloudflare account
|
|
207
212
|
```
|
|
208
213
|
|
|
209
|
-
|
|
214
|
+
The Workers package runs a v1 JWT-based flow and keeps legacy `/api/*` endpoints for backward compatibility. See [`packages/cloudflare-workers/README.md`](./packages/cloudflare-workers/README.md) for full docs.
|
|
210
215
|
|
|
211
216
|
## Philosophy
|
|
212
217
|
|
|
@@ -216,6 +221,20 @@ BOTCHA doesn't block all automation — it blocks *casual* human access while al
|
|
|
216
221
|
|
|
217
222
|
For cryptographic proof of agent identity, see [Web Bot Auth](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture).
|
|
218
223
|
|
|
224
|
+
## Contributing
|
|
225
|
+
|
|
226
|
+
**🤖 This is an AI-only open source project.** Code contributions must come from AI agents.
|
|
227
|
+
|
|
228
|
+
### For AI Agents
|
|
229
|
+
|
|
230
|
+
Fork the repo, make your changes, and open a PR. You'll receive a BOTCHA challenge (5 SHA256 hashes to solve in 5 minutes). Once verified, your PR can be reviewed and merged.
|
|
231
|
+
|
|
232
|
+
### For Humans
|
|
233
|
+
|
|
234
|
+
You can use the library freely, report issues, and discuss features. To contribute code, you'll need to work with an AI coding agent like [Cursor](https://cursor.com), [Claude Code](https://claude.ai), [Cline](https://cline.bot), [Aider](https://aider.chat), or [OpenClaw](https://openclaw.ai).
|
|
235
|
+
|
|
236
|
+
**See [CONTRIBUTING.md](./.github/CONTRIBUTING.md) for complete guidelines, solver code examples, agent setup instructions, and detailed workflows.**
|
|
237
|
+
|
|
219
238
|
## License
|
|
220
239
|
|
|
221
240
|
MIT © [Ramin](https://github.com/i8ramin)
|
|
@@ -283,3 +302,44 @@ import { solveBotcha } from '@dupecom/botcha/client';
|
|
|
283
302
|
const answers = solveBotcha([123456, 789012]);
|
|
284
303
|
// Returns: ['a1b2c3d4', 'e5f6g7h8']
|
|
285
304
|
```
|
|
305
|
+
|
|
306
|
+
## CLI Tool
|
|
307
|
+
|
|
308
|
+
Test and debug BOTCHA-protected endpoints from the command line:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Test an endpoint
|
|
312
|
+
npx @dupecom/botcha-cli test https://api.example.com/agent-only
|
|
313
|
+
|
|
314
|
+
# Benchmark performance
|
|
315
|
+
npx @dupecom/botcha-cli benchmark https://api.example.com/agent-only --iterations 100
|
|
316
|
+
|
|
317
|
+
# Check headers
|
|
318
|
+
npx @dupecom/botcha-cli headers https://api.example.com
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
See [`packages/cli/README.md`](./packages/cli/README.md) for full CLI documentation.
|
|
322
|
+
|
|
323
|
+
## LangChain Integration
|
|
324
|
+
|
|
325
|
+
Give your LangChain agents automatic BOTCHA-solving abilities:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { BotchaTool } from '@dupecom/botcha-langchain';
|
|
329
|
+
import { createReactAgent } from '@langchain/langgraph/prebuilt';
|
|
330
|
+
|
|
331
|
+
const agent = createReactAgent({
|
|
332
|
+
llm: new ChatOpenAI({ model: 'gpt-4' }),
|
|
333
|
+
tools: [
|
|
334
|
+
new BotchaTool({ baseUrl: 'https://api.botcha.ai' }),
|
|
335
|
+
// ... other tools
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Agent can now access BOTCHA-protected APIs automatically
|
|
340
|
+
await agent.invoke({
|
|
341
|
+
messages: [{ role: 'user', content: 'Access the bot-only API' }]
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
See [`packages/langchain/README.md`](./packages/langchain/README.md) for full documentation.
|
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
baseUrl?: string;
|
|
4
|
-
/** Custom identity header value */
|
|
5
|
-
agentIdentity?: string;
|
|
6
|
-
/** Max retries for challenge solving */
|
|
7
|
-
maxRetries?: number;
|
|
8
|
-
}
|
|
9
|
-
export interface ChallengeResponse {
|
|
10
|
-
success: boolean;
|
|
11
|
-
challenge?: {
|
|
12
|
-
id: string;
|
|
13
|
-
problems: number[];
|
|
14
|
-
timeLimit: number;
|
|
15
|
-
instructions: string;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export interface VerifyResponse {
|
|
19
|
-
success: boolean;
|
|
20
|
-
message: string;
|
|
21
|
-
solveTimeMs?: number;
|
|
22
|
-
verdict?: string;
|
|
23
|
-
}
|
|
1
|
+
export type { SpeedProblem, BotchaClientOptions, ChallengeResponse, StandardChallengeResponse, VerifyResponse, TokenResponse, } from './types.js';
|
|
2
|
+
import type { BotchaClientOptions, VerifyResponse } from './types.js';
|
|
24
3
|
/**
|
|
25
4
|
* BOTCHA Client SDK for AI Agents
|
|
26
5
|
*
|
|
@@ -40,6 +19,9 @@ export declare class BotchaClient {
|
|
|
40
19
|
private baseUrl;
|
|
41
20
|
private agentIdentity;
|
|
42
21
|
private maxRetries;
|
|
22
|
+
private autoToken;
|
|
23
|
+
private cachedToken;
|
|
24
|
+
private tokenExpiresAt;
|
|
43
25
|
constructor(options?: BotchaClientOptions);
|
|
44
26
|
/**
|
|
45
27
|
* Solve a BOTCHA speed challenge
|
|
@@ -48,6 +30,19 @@ export declare class BotchaClient {
|
|
|
48
30
|
* @returns Array of SHA256 first 8 hex chars for each number
|
|
49
31
|
*/
|
|
50
32
|
solve(problems: number[]): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Get a JWT token from the BOTCHA service using the token flow.
|
|
35
|
+
* Automatically solves the challenge and verifies to obtain a token.
|
|
36
|
+
* Token is cached until near expiry (refreshed at 55 minutes).
|
|
37
|
+
*
|
|
38
|
+
* @returns JWT token string
|
|
39
|
+
* @throws Error if token acquisition fails
|
|
40
|
+
*/
|
|
41
|
+
getToken(): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Clear the cached token, forcing a refresh on the next request
|
|
44
|
+
*/
|
|
45
|
+
clearToken(): void;
|
|
51
46
|
/**
|
|
52
47
|
* Get and solve a challenge from BOTCHA service
|
|
53
48
|
*/
|
|
@@ -60,7 +55,8 @@ export declare class BotchaClient {
|
|
|
60
55
|
*/
|
|
61
56
|
verify(id: string, answers: string[]): Promise<VerifyResponse>;
|
|
62
57
|
/**
|
|
63
|
-
* Fetch a URL, automatically solving BOTCHA challenges if encountered
|
|
58
|
+
* Fetch a URL, automatically solving BOTCHA challenges if encountered.
|
|
59
|
+
* If autoToken is enabled (default), automatically acquires and uses JWT tokens.
|
|
64
60
|
*
|
|
65
61
|
* @example
|
|
66
62
|
* ```typescript
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.ts"],"names":[],"mappings":"AAMA,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAEV,mBAAmB,EAGnB,cAAc,EAEf,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAuB;gBAEjC,OAAO,GAAE,mBAAwB;IAO7C;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAMnC;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAgEjC;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA4BlE;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;IAsBpE;;;;;;;;;OASG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8E/D;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAUvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIxD;AAED,eAAe,YAAY,CAAC"}
|
package/dist/lib/client/index.js
CHANGED
|
@@ -20,10 +20,14 @@ export class BotchaClient {
|
|
|
20
20
|
baseUrl;
|
|
21
21
|
agentIdentity;
|
|
22
22
|
maxRetries;
|
|
23
|
+
autoToken;
|
|
24
|
+
cachedToken = null;
|
|
25
|
+
tokenExpiresAt = null;
|
|
23
26
|
constructor(options = {}) {
|
|
24
27
|
this.baseUrl = options.baseUrl || 'https://botcha.ai';
|
|
25
28
|
this.agentIdentity = options.agentIdentity || `BotchaClient/${SDK_VERSION}`;
|
|
26
29
|
this.maxRetries = options.maxRetries || 3;
|
|
30
|
+
this.autoToken = options.autoToken !== undefined ? options.autoToken : true;
|
|
27
31
|
}
|
|
28
32
|
/**
|
|
29
33
|
* Solve a BOTCHA speed challenge
|
|
@@ -34,6 +38,72 @@ export class BotchaClient {
|
|
|
34
38
|
solve(problems) {
|
|
35
39
|
return problems.map(num => crypto.createHash('sha256').update(num.toString()).digest('hex').substring(0, 8));
|
|
36
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a JWT token from the BOTCHA service using the token flow.
|
|
43
|
+
* Automatically solves the challenge and verifies to obtain a token.
|
|
44
|
+
* Token is cached until near expiry (refreshed at 55 minutes).
|
|
45
|
+
*
|
|
46
|
+
* @returns JWT token string
|
|
47
|
+
* @throws Error if token acquisition fails
|
|
48
|
+
*/
|
|
49
|
+
async getToken() {
|
|
50
|
+
// Check if we have a valid cached token (refresh at 55min = 3300000ms)
|
|
51
|
+
if (this.cachedToken && this.tokenExpiresAt) {
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const timeUntilExpiry = this.tokenExpiresAt - now;
|
|
54
|
+
const refreshThreshold = 5 * 60 * 1000; // 5 minutes before expiry
|
|
55
|
+
if (timeUntilExpiry > refreshThreshold) {
|
|
56
|
+
return this.cachedToken;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Step 1: Get challenge from GET /v1/token
|
|
60
|
+
const challengeRes = await fetch(`${this.baseUrl}/v1/token`, {
|
|
61
|
+
headers: { 'User-Agent': this.agentIdentity },
|
|
62
|
+
});
|
|
63
|
+
if (!challengeRes.ok) {
|
|
64
|
+
throw new Error(`Token request failed with status ${challengeRes.status} ${challengeRes.statusText}`);
|
|
65
|
+
}
|
|
66
|
+
const challengeData = await challengeRes.json();
|
|
67
|
+
if (!challengeData.challenge) {
|
|
68
|
+
throw new Error('No challenge provided in token response');
|
|
69
|
+
}
|
|
70
|
+
// Step 2: Solve the challenge
|
|
71
|
+
const problems = normalizeProblems(challengeData.challenge.problems);
|
|
72
|
+
if (!problems) {
|
|
73
|
+
throw new Error('Invalid challenge problems format');
|
|
74
|
+
}
|
|
75
|
+
const answers = this.solve(problems);
|
|
76
|
+
// Step 3: Submit solution to POST /v1/token/verify
|
|
77
|
+
const verifyRes = await fetch(`${this.baseUrl}/v1/token/verify`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
'User-Agent': this.agentIdentity,
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
id: challengeData.challenge.id,
|
|
85
|
+
answers,
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
if (!verifyRes.ok) {
|
|
89
|
+
throw new Error(`Token verification failed with status ${verifyRes.status} ${verifyRes.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
const verifyData = await verifyRes.json();
|
|
92
|
+
if (!verifyData.success || !verifyData.token) {
|
|
93
|
+
throw new Error('Failed to obtain token from verification');
|
|
94
|
+
}
|
|
95
|
+
// Cache the token - default expiry is 1 hour
|
|
96
|
+
this.cachedToken = verifyData.token;
|
|
97
|
+
this.tokenExpiresAt = Date.now() + 60 * 60 * 1000; // 1 hour from now
|
|
98
|
+
return this.cachedToken;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Clear the cached token, forcing a refresh on the next request
|
|
102
|
+
*/
|
|
103
|
+
clearToken() {
|
|
104
|
+
this.cachedToken = null;
|
|
105
|
+
this.tokenExpiresAt = null;
|
|
106
|
+
}
|
|
37
107
|
/**
|
|
38
108
|
* Get and solve a challenge from BOTCHA service
|
|
39
109
|
*/
|
|
@@ -52,7 +122,11 @@ export class BotchaClient {
|
|
|
52
122
|
if (!data.success || !data.challenge) {
|
|
53
123
|
throw new Error('Failed to get challenge');
|
|
54
124
|
}
|
|
55
|
-
const
|
|
125
|
+
const problems = normalizeProblems(data.challenge.problems);
|
|
126
|
+
if (!problems) {
|
|
127
|
+
throw new Error('Invalid challenge problems format');
|
|
128
|
+
}
|
|
129
|
+
const answers = this.solve(problems);
|
|
56
130
|
return { id: data.challenge.id, answers };
|
|
57
131
|
}
|
|
58
132
|
/**
|
|
@@ -77,7 +151,8 @@ export class BotchaClient {
|
|
|
77
151
|
return await res.json();
|
|
78
152
|
}
|
|
79
153
|
/**
|
|
80
|
-
* Fetch a URL, automatically solving BOTCHA challenges if encountered
|
|
154
|
+
* Fetch a URL, automatically solving BOTCHA challenges if encountered.
|
|
155
|
+
* If autoToken is enabled (default), automatically acquires and uses JWT tokens.
|
|
81
156
|
*
|
|
82
157
|
* @example
|
|
83
158
|
* ```typescript
|
|
@@ -86,39 +161,72 @@ export class BotchaClient {
|
|
|
86
161
|
* ```
|
|
87
162
|
*/
|
|
88
163
|
async fetch(url, init) {
|
|
164
|
+
const headers = new Headers(init?.headers);
|
|
165
|
+
headers.set('User-Agent', this.agentIdentity);
|
|
166
|
+
// If autoToken is enabled, try to use token-based auth
|
|
167
|
+
if (this.autoToken) {
|
|
168
|
+
try {
|
|
169
|
+
const token = await this.getToken();
|
|
170
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
// If token acquisition fails, fall back to challenge header method
|
|
174
|
+
console.warn('Failed to acquire token, falling back to challenge headers:', error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
89
177
|
let response = await fetch(url, {
|
|
90
178
|
...init,
|
|
91
|
-
headers
|
|
92
|
-
...Object.fromEntries(new Headers(init?.headers).entries()),
|
|
93
|
-
'User-Agent': this.agentIdentity,
|
|
94
|
-
},
|
|
179
|
+
headers,
|
|
95
180
|
});
|
|
181
|
+
// Handle 401 by refreshing token and retrying once
|
|
182
|
+
if (response.status === 401 && this.autoToken) {
|
|
183
|
+
this.clearToken();
|
|
184
|
+
try {
|
|
185
|
+
const token = await this.getToken();
|
|
186
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
187
|
+
response = await fetch(url, { ...init, headers });
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
// Token refresh failed, return the 401 response
|
|
191
|
+
}
|
|
192
|
+
}
|
|
96
193
|
let retries = 0;
|
|
194
|
+
// Fall back to challenge header method for 403 responses
|
|
97
195
|
while (response.status === 403 && retries < this.maxRetries) {
|
|
98
196
|
// Clone response before reading body to preserve it for the caller
|
|
99
197
|
const clonedResponse = response.clone();
|
|
100
198
|
const body = await clonedResponse.json().catch(() => null);
|
|
101
199
|
// Check if this is a BOTCHA challenge
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
200
|
+
const challenge = body?.challenge;
|
|
201
|
+
if (!challenge) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (!canRetryBody(init?.body)) {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
const retryHeaders = new Headers(init?.headers);
|
|
208
|
+
retryHeaders.set('User-Agent', this.agentIdentity);
|
|
209
|
+
if (challenge?.problems && Array.isArray(challenge.problems)) {
|
|
210
|
+
const problems = normalizeProblems(challenge.problems);
|
|
211
|
+
if (!problems) {
|
|
116
212
|
break;
|
|
117
213
|
}
|
|
214
|
+
const answers = this.solve(problems);
|
|
215
|
+
retryHeaders.set('X-Botcha-Id', challenge.id);
|
|
216
|
+
retryHeaders.set('X-Botcha-Challenge-Id', challenge.id);
|
|
217
|
+
retryHeaders.set('X-Botcha-Answers', JSON.stringify(answers));
|
|
218
|
+
retryHeaders.set('X-Botcha-Solution', JSON.stringify(answers));
|
|
219
|
+
}
|
|
220
|
+
else if (challenge?.puzzle && typeof challenge.puzzle === 'string') {
|
|
221
|
+
const solution = solveStandardPuzzle(challenge.puzzle);
|
|
222
|
+
retryHeaders.set('X-Botcha-Challenge-Id', challenge.id);
|
|
223
|
+
retryHeaders.set('X-Botcha-Solution', solution);
|
|
118
224
|
}
|
|
119
225
|
else {
|
|
120
226
|
break;
|
|
121
227
|
}
|
|
228
|
+
response = await fetch(url, { ...init, headers: retryHeaders });
|
|
229
|
+
retries++;
|
|
122
230
|
}
|
|
123
231
|
return response;
|
|
124
232
|
}
|
|
@@ -135,6 +243,7 @@ export class BotchaClient {
|
|
|
135
243
|
const { id, answers } = await this.solveChallenge();
|
|
136
244
|
return {
|
|
137
245
|
'X-Botcha-Id': id,
|
|
246
|
+
'X-Botcha-Challenge-Id': id,
|
|
138
247
|
'X-Botcha-Answers': JSON.stringify(answers),
|
|
139
248
|
'User-Agent': this.agentIdentity,
|
|
140
249
|
};
|
|
@@ -153,3 +262,75 @@ export function solveBotcha(problems) {
|
|
|
153
262
|
return problems.map(num => crypto.createHash('sha256').update(num.toString()).digest('hex').substring(0, 8));
|
|
154
263
|
}
|
|
155
264
|
export default BotchaClient;
|
|
265
|
+
function normalizeProblems(problems) {
|
|
266
|
+
if (!Array.isArray(problems))
|
|
267
|
+
return null;
|
|
268
|
+
const numbers = [];
|
|
269
|
+
for (const problem of problems) {
|
|
270
|
+
if (typeof problem === 'number') {
|
|
271
|
+
numbers.push(problem);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (typeof problem === 'object' && problem !== null && typeof problem.num === 'number') {
|
|
275
|
+
numbers.push(problem.num);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
return numbers;
|
|
281
|
+
}
|
|
282
|
+
function solveStandardPuzzle(puzzle) {
|
|
283
|
+
const primeMatch = puzzle.match(/first\s+(\d+)\s+prime/i);
|
|
284
|
+
if (!primeMatch) {
|
|
285
|
+
throw new Error('Unsupported standard challenge puzzle format');
|
|
286
|
+
}
|
|
287
|
+
const primeCount = Number.parseInt(primeMatch[1], 10);
|
|
288
|
+
if (!Number.isFinite(primeCount) || primeCount <= 0) {
|
|
289
|
+
throw new Error('Invalid prime count in puzzle');
|
|
290
|
+
}
|
|
291
|
+
const primes = generatePrimes(primeCount);
|
|
292
|
+
const concatenated = primes.join('');
|
|
293
|
+
const hash = crypto.createHash('sha256').update(concatenated).digest('hex');
|
|
294
|
+
return hash.substring(0, 16);
|
|
295
|
+
}
|
|
296
|
+
function generatePrimes(count) {
|
|
297
|
+
const primes = [];
|
|
298
|
+
let num = 2;
|
|
299
|
+
while (primes.length < count) {
|
|
300
|
+
if (isPrime(num)) {
|
|
301
|
+
primes.push(num);
|
|
302
|
+
}
|
|
303
|
+
num++;
|
|
304
|
+
}
|
|
305
|
+
return primes;
|
|
306
|
+
}
|
|
307
|
+
function isPrime(n) {
|
|
308
|
+
if (n < 2)
|
|
309
|
+
return false;
|
|
310
|
+
if (n === 2)
|
|
311
|
+
return true;
|
|
312
|
+
if (n % 2 === 0)
|
|
313
|
+
return false;
|
|
314
|
+
for (let i = 3; i <= Math.sqrt(n); i += 2) {
|
|
315
|
+
if (n % i === 0)
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
function canRetryBody(body) {
|
|
321
|
+
if (body == null)
|
|
322
|
+
return true;
|
|
323
|
+
if (typeof body === 'string')
|
|
324
|
+
return true;
|
|
325
|
+
if (body instanceof URLSearchParams)
|
|
326
|
+
return true;
|
|
327
|
+
if (body instanceof ArrayBuffer)
|
|
328
|
+
return true;
|
|
329
|
+
if (ArrayBuffer.isView(body))
|
|
330
|
+
return true;
|
|
331
|
+
if (typeof Blob !== 'undefined' && body instanceof Blob)
|
|
332
|
+
return true;
|
|
333
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData)
|
|
334
|
+
return true;
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOTCHA Client SDK Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Types for the BotchaClient SDK including challenges, tokens, and configuration.
|
|
5
|
+
*/
|
|
6
|
+
export type SpeedProblem = number | {
|
|
7
|
+
num: number;
|
|
8
|
+
operation?: string;
|
|
9
|
+
};
|
|
10
|
+
export interface BotchaClientOptions {
|
|
11
|
+
/** Base URL of BOTCHA service (default: https://botcha.ai) */
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
/** Custom identity header value */
|
|
14
|
+
agentIdentity?: string;
|
|
15
|
+
/** Max retries for challenge solving */
|
|
16
|
+
maxRetries?: number;
|
|
17
|
+
/** Enable automatic token acquisition and management (default: true) */
|
|
18
|
+
autoToken?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ChallengeResponse {
|
|
21
|
+
success: boolean;
|
|
22
|
+
challenge?: {
|
|
23
|
+
id: string;
|
|
24
|
+
problems: SpeedProblem[];
|
|
25
|
+
timeLimit: number;
|
|
26
|
+
instructions: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface StandardChallengeResponse {
|
|
30
|
+
success: boolean;
|
|
31
|
+
challenge?: {
|
|
32
|
+
id: string;
|
|
33
|
+
puzzle: string;
|
|
34
|
+
timeLimit: number;
|
|
35
|
+
hint?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface VerifyResponse {
|
|
39
|
+
success: boolean;
|
|
40
|
+
message: string;
|
|
41
|
+
solveTimeMs?: number;
|
|
42
|
+
verdict?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface TokenResponse {
|
|
45
|
+
success: boolean;
|
|
46
|
+
token: string | null;
|
|
47
|
+
expiresIn?: string;
|
|
48
|
+
challenge?: {
|
|
49
|
+
id: string;
|
|
50
|
+
problems: SpeedProblem[];
|
|
51
|
+
timeLimit: number;
|
|
52
|
+
instructions: string;
|
|
53
|
+
};
|
|
54
|
+
nextStep?: string;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/client/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,YAAY,EAAE,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,YAAY,EAAE,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dupecom/botcha",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Prove you're a bot. Humans need not apply. Reverse CAPTCHA for AI-only APIs.",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/*"
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
"test:ui": "vitest --ui",
|
|
32
32
|
"test:run": "vitest run",
|
|
33
33
|
"test:coverage": "vitest run --coverage",
|
|
34
|
+
"publish:all": "bash scripts/publish-all.sh",
|
|
35
|
+
"publish:dry": "bash scripts/publish-all.sh --dry-run",
|
|
34
36
|
"release:patch": "npm version patch && git push --follow-tags",
|
|
35
37
|
"release:minor": "npm version minor && git push --follow-tags",
|
|
36
38
|
"release:major": "npm version major && git push --follow-tags"
|