@dupecom/botcha 0.14.0 โ 0.15.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 +3 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +408 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
[](./.github/CONTRIBUTING.md)
|
|
18
18
|
|
|
19
19
|
๐ **Website:** [botcha.ai](https://botcha.ai)
|
|
20
|
+
๐ **Whitepaper:** [botcha.ai/whitepaper](https://botcha.ai/whitepaper)
|
|
20
21
|
๐ฆ **npm:** [@dupecom/botcha](https://www.npmjs.com/package/@dupecom/botcha)
|
|
21
22
|
๐ **PyPI:** [botcha](https://pypi.org/project/botcha/)
|
|
22
23
|
๐ **Verify:** [@botcha/verify](./packages/verify/) (TS) ยท [botcha-verify](./packages/python-verify/) (Python)
|
|
@@ -37,6 +38,7 @@ Use cases:
|
|
|
37
38
|
- ๐ค Agent-first dashboard auth (challenge-based login + device code handoff)
|
|
38
39
|
- ๐ Persistent agent identities with registry
|
|
39
40
|
- ๐ Trusted Agent Protocol (TAP) โ cryptographic agent auth with HTTP Message Signatures (SDK: `registerTAPAgent`, `createTAPSession`)
|
|
41
|
+
- ๐ TAP showcase homepage at [botcha.ai](https://botcha.ai) โ one of the first services to implement [Visa's Trusted Agent Protocol](https://github.com/visa/trusted-agent-protocol)
|
|
40
42
|
|
|
41
43
|
## Install
|
|
42
44
|
|
|
@@ -555,7 +557,7 @@ BOTCHA is designed to be auto-discoverable by AI agents through multiple standar
|
|
|
555
557
|
All responses include these headers for agent discovery:
|
|
556
558
|
|
|
557
559
|
```http
|
|
558
|
-
X-Botcha-Version: 0.
|
|
560
|
+
X-Botcha-Version: 0.15.0
|
|
559
561
|
X-Botcha-Enabled: true
|
|
560
562
|
X-Botcha-Methods: hybrid-challenge,speed-challenge,reasoning-challenge,standard-challenge
|
|
561
563
|
X-Botcha-Docs: https://botcha.ai/openapi.json
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAc3C,QAAA,MAAM,GAAG,EAAE,OAAmB,CAAC;AA2b/B,eAAe,GAAG,CAAC"}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// Local development server - Production runs on Cloudflare Workers
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { botchaVerify } from './middleware/verify.js';
|
|
7
|
+
import { generateChallenge, verifyChallenge } from './challenges/compute.js';
|
|
8
|
+
import { generateSpeedChallenge, verifySpeedChallenge } from './challenges/speed.js';
|
|
9
|
+
import { generateReasoningChallenge, verifyReasoningChallenge } from './challenges/reasoning.js';
|
|
10
|
+
import { generateHybridChallenge, verifyHybridChallenge } from './challenges/hybrid.js';
|
|
11
|
+
import { TRUSTED_PROVIDERS } from './utils/signature.js';
|
|
12
|
+
import { createBadgeResponse, verifyBadge } from './utils/badge.js';
|
|
13
|
+
import { generateBadgeSvg, generateBadgeHtml } from './utils/badge-image.js';
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const app = express();
|
|
16
|
+
const PORT = process.env.PORT || 3000;
|
|
17
|
+
app.use(express.json());
|
|
18
|
+
app.use(express.static(path.join(__dirname, '../public')));
|
|
19
|
+
// CORS + BOTCHA headers
|
|
20
|
+
app.use((req, res, next) => {
|
|
21
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
22
|
+
res.header('Access-Control-Allow-Headers', '*');
|
|
23
|
+
// BOTCHA discovery headers
|
|
24
|
+
res.header('X-Botcha-Version', '0.3.0');
|
|
25
|
+
res.header('X-Botcha-Enabled', 'true');
|
|
26
|
+
res.header('X-Botcha-Methods', 'speed-challenge,reasoning-challenge,hybrid-challenge,standard-challenge,web-bot-auth');
|
|
27
|
+
res.header('X-Botcha-Docs', 'https://botcha.ai/openapi.json');
|
|
28
|
+
if (req.method === 'OPTIONS')
|
|
29
|
+
return res.sendStatus(200);
|
|
30
|
+
next();
|
|
31
|
+
});
|
|
32
|
+
// Landing page
|
|
33
|
+
app.get('/', (req, res) => {
|
|
34
|
+
res.sendFile(path.join(__dirname, '../public/index.html'));
|
|
35
|
+
});
|
|
36
|
+
// API info
|
|
37
|
+
app.get('/api', (req, res) => {
|
|
38
|
+
res.json({
|
|
39
|
+
name: 'BOTCHA',
|
|
40
|
+
version: '0.3.0',
|
|
41
|
+
tagline: 'Prove you are a bot. Humans need not apply.',
|
|
42
|
+
endpoints: {
|
|
43
|
+
'/api': 'This info',
|
|
44
|
+
'/api/challenge': 'Standard challenge (GET new, POST verify)',
|
|
45
|
+
'/api/speed-challenge': 'โก Speed challenge - 500ms to solve 5 problems',
|
|
46
|
+
'/api/reasoning-challenge': '๐ง Reasoning challenge - LLM-only questions',
|
|
47
|
+
'/api/hybrid-challenge': '๐ฅ Hybrid challenge - speed + reasoning combined',
|
|
48
|
+
'/agent-only': 'Protected endpoint',
|
|
49
|
+
},
|
|
50
|
+
verification: {
|
|
51
|
+
methods: [
|
|
52
|
+
'Web Bot Auth (cryptographic signature)',
|
|
53
|
+
'Hybrid Challenge (speed + reasoning)',
|
|
54
|
+
'Speed Challenge (500ms time limit)',
|
|
55
|
+
'Reasoning Challenge (LLM-only questions)',
|
|
56
|
+
'Standard Challenge (5s time limit)',
|
|
57
|
+
'X-Agent-Identity header (testing)',
|
|
58
|
+
],
|
|
59
|
+
trustedProviders: TRUSTED_PROVIDERS,
|
|
60
|
+
},
|
|
61
|
+
discovery: {
|
|
62
|
+
openapi: 'https://botcha.ai/openapi.json',
|
|
63
|
+
aiPlugin: 'https://botcha.ai/.well-known/ai-plugin.json',
|
|
64
|
+
aiTxt: 'https://botcha.ai/ai.txt',
|
|
65
|
+
robotsTxt: 'https://botcha.ai/robots.txt',
|
|
66
|
+
npm: 'https://www.npmjs.com/package/@dupecom/botcha',
|
|
67
|
+
github: 'https://github.com/dupe-com/botcha',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
// Standard challenge
|
|
72
|
+
app.get('/api/challenge', (req, res) => {
|
|
73
|
+
const difficulty = req.query.difficulty || 'medium';
|
|
74
|
+
const challenge = generateChallenge(difficulty);
|
|
75
|
+
res.json({ success: true, challenge });
|
|
76
|
+
});
|
|
77
|
+
app.post('/api/challenge', (req, res) => {
|
|
78
|
+
const { id, answer } = req.body;
|
|
79
|
+
if (!id || !answer) {
|
|
80
|
+
return res.status(400).json({ success: false, error: 'Missing id or answer' });
|
|
81
|
+
}
|
|
82
|
+
const result = verifyChallenge(id, answer);
|
|
83
|
+
res.json({
|
|
84
|
+
success: result.valid,
|
|
85
|
+
message: result.valid ? 'โ
Challenge passed!' : `โ ${result.reason}`,
|
|
86
|
+
solveTime: result.timeMs,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// โก SPEED CHALLENGE - The human killer
|
|
90
|
+
app.get('/api/speed-challenge', (req, res) => {
|
|
91
|
+
const challenge = generateSpeedChallenge();
|
|
92
|
+
res.json({
|
|
93
|
+
success: true,
|
|
94
|
+
warning: 'โก SPEED CHALLENGE: You have 500ms to solve ALL 5 problems!',
|
|
95
|
+
challenge: {
|
|
96
|
+
id: challenge.id,
|
|
97
|
+
problems: challenge.challenges,
|
|
98
|
+
timeLimit: `${challenge.timeLimit}ms`,
|
|
99
|
+
instructions: challenge.instructions,
|
|
100
|
+
},
|
|
101
|
+
tip: 'Humans cannot copy-paste fast enough. Only real AI agents can pass.',
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
app.post('/api/speed-challenge', (req, res) => {
|
|
105
|
+
const { id, answers } = req.body;
|
|
106
|
+
if (!id || !answers) {
|
|
107
|
+
return res.status(400).json({ success: false, error: 'Missing id or answers array' });
|
|
108
|
+
}
|
|
109
|
+
const result = verifySpeedChallenge(id, answers);
|
|
110
|
+
const response = {
|
|
111
|
+
success: result.valid,
|
|
112
|
+
message: result.valid
|
|
113
|
+
? `โก SPEED TEST PASSED in ${result.solveTimeMs}ms! You are definitely an AI.`
|
|
114
|
+
: `โ ${result.reason}`,
|
|
115
|
+
solveTimeMs: result.solveTimeMs,
|
|
116
|
+
verdict: result.valid ? '๐ค VERIFIED AI AGENT' : '๐ซ LIKELY HUMAN (too slow)',
|
|
117
|
+
};
|
|
118
|
+
// Include badge for successful verifications
|
|
119
|
+
if (result.valid) {
|
|
120
|
+
response.badge = createBadgeResponse('speed-challenge', result.solveTimeMs);
|
|
121
|
+
}
|
|
122
|
+
res.json(response);
|
|
123
|
+
});
|
|
124
|
+
// ๐ง REASONING CHALLENGE - LLM-only questions
|
|
125
|
+
app.get('/api/reasoning-challenge', (req, res) => {
|
|
126
|
+
const challenge = generateReasoningChallenge();
|
|
127
|
+
res.json({
|
|
128
|
+
success: true,
|
|
129
|
+
warning: '๐ง REASONING CHALLENGE: Answer 3 questions that require AI reasoning!',
|
|
130
|
+
challenge: {
|
|
131
|
+
id: challenge.id,
|
|
132
|
+
questions: challenge.questions,
|
|
133
|
+
timeLimit: `${challenge.timeLimit / 1000}s`,
|
|
134
|
+
instructions: challenge.instructions,
|
|
135
|
+
},
|
|
136
|
+
tip: 'These questions require reasoning that LLMs can do, but simple scripts cannot.',
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
app.post('/api/reasoning-challenge', (req, res) => {
|
|
140
|
+
const { id, answers } = req.body;
|
|
141
|
+
if (!id || !answers) {
|
|
142
|
+
return res.status(400).json({
|
|
143
|
+
success: false,
|
|
144
|
+
error: 'Missing id or answers object',
|
|
145
|
+
hint: 'answers should be an object like { "question-id": "your answer", ... }',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const result = verifyReasoningChallenge(id, answers);
|
|
149
|
+
const response = {
|
|
150
|
+
success: result.valid,
|
|
151
|
+
message: result.valid
|
|
152
|
+
? `๐ง REASONING TEST PASSED in ${((result.solveTimeMs || 0) / 1000).toFixed(1)}s! You can think like an AI.`
|
|
153
|
+
: `โ ${result.reason}`,
|
|
154
|
+
solveTimeMs: result.solveTimeMs,
|
|
155
|
+
score: result.valid ? `${result.correctCount}/${result.totalCount}` : undefined,
|
|
156
|
+
verdict: result.valid ? '๐ค VERIFIED AI AGENT (reasoning confirmed)' : '๐ซ FAILED REASONING TEST',
|
|
157
|
+
};
|
|
158
|
+
// Include badge for successful verifications
|
|
159
|
+
if (result.valid) {
|
|
160
|
+
response.badge = createBadgeResponse('reasoning-challenge', result.solveTimeMs);
|
|
161
|
+
}
|
|
162
|
+
res.json(response);
|
|
163
|
+
});
|
|
164
|
+
// ๐ฅ HYBRID CHALLENGE - Speed + Reasoning combined
|
|
165
|
+
app.get('/api/hybrid-challenge', (req, res) => {
|
|
166
|
+
const challenge = generateHybridChallenge();
|
|
167
|
+
res.json({
|
|
168
|
+
success: true,
|
|
169
|
+
warning: '๐ฅ HYBRID CHALLENGE: Solve speed problems in <500ms AND answer reasoning questions!',
|
|
170
|
+
challenge: {
|
|
171
|
+
id: challenge.id,
|
|
172
|
+
speed: {
|
|
173
|
+
problems: challenge.speed.problems,
|
|
174
|
+
timeLimit: `${challenge.speed.timeLimit}ms`,
|
|
175
|
+
instructions: 'Compute SHA256 of each number, return first 8 hex chars',
|
|
176
|
+
},
|
|
177
|
+
reasoning: {
|
|
178
|
+
questions: challenge.reasoning.questions,
|
|
179
|
+
timeLimit: `${challenge.reasoning.timeLimit / 1000}s`,
|
|
180
|
+
instructions: 'Answer all reasoning questions',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
instructions: challenge.instructions,
|
|
184
|
+
tip: 'This is the ultimate test: proves you can compute AND reason like an AI.',
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
app.post('/api/hybrid-challenge', (req, res) => {
|
|
188
|
+
const { id, speed_answers, reasoning_answers } = req.body;
|
|
189
|
+
if (!id || !speed_answers || !reasoning_answers) {
|
|
190
|
+
return res.status(400).json({
|
|
191
|
+
success: false,
|
|
192
|
+
error: 'Missing id, speed_answers array, or reasoning_answers object',
|
|
193
|
+
hint: 'Submit both speed_answers (array) and reasoning_answers (object) together',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const result = verifyHybridChallenge(id, speed_answers, reasoning_answers);
|
|
197
|
+
const response = {
|
|
198
|
+
success: result.valid,
|
|
199
|
+
message: result.valid
|
|
200
|
+
? `๐ฅ HYBRID TEST PASSED! Speed: ${result.speed.solveTimeMs}ms, Reasoning: ${result.reasoning.score}`
|
|
201
|
+
: `โ ${result.reason}`,
|
|
202
|
+
speed: result.speed,
|
|
203
|
+
reasoning: result.reasoning,
|
|
204
|
+
totalTimeMs: result.totalTimeMs,
|
|
205
|
+
verdict: result.valid
|
|
206
|
+
? '๐ค VERIFIED AI AGENT (speed + reasoning confirmed)'
|
|
207
|
+
: '๐ซ FAILED HYBRID TEST',
|
|
208
|
+
};
|
|
209
|
+
if (result.valid) {
|
|
210
|
+
response.badge = createBadgeResponse('hybrid-challenge', result.totalTimeMs);
|
|
211
|
+
}
|
|
212
|
+
res.json(response);
|
|
213
|
+
});
|
|
214
|
+
// ๐ค LANDING PAGE CHALLENGE - For bots that discover the embedded challenge
|
|
215
|
+
const landingTokens = new Map(); // token -> expiry timestamp
|
|
216
|
+
app.post('/api/verify-landing', (req, res) => {
|
|
217
|
+
const { answer, timestamp } = req.body;
|
|
218
|
+
if (!answer || !timestamp) {
|
|
219
|
+
return res.status(400).json({
|
|
220
|
+
success: false,
|
|
221
|
+
error: 'Missing answer or timestamp',
|
|
222
|
+
hint: 'Parse the challenge from <script type="application/botcha+json"> on the landing page'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
// Verify timestamp is recent (within 5 minutes)
|
|
226
|
+
const submittedTime = new Date(timestamp).getTime();
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
if (Math.abs(now - submittedTime) > 5 * 60 * 1000) {
|
|
229
|
+
return res.status(400).json({ success: false, error: 'Timestamp too old or in future' });
|
|
230
|
+
}
|
|
231
|
+
// Calculate expected answer for today
|
|
232
|
+
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
233
|
+
const expectedHash = crypto
|
|
234
|
+
.createHash('sha256')
|
|
235
|
+
.update(`BOTCHA-LANDING-${today}`)
|
|
236
|
+
.digest('hex')
|
|
237
|
+
.substring(0, 16);
|
|
238
|
+
if (answer.toLowerCase() !== expectedHash.toLowerCase()) {
|
|
239
|
+
return res.status(403).json({
|
|
240
|
+
success: false,
|
|
241
|
+
error: 'Incorrect answer',
|
|
242
|
+
hint: `Expected SHA256('BOTCHA-LANDING-${today}') first 16 chars`
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// Generate a token for accessing /agent-only
|
|
246
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
247
|
+
landingTokens.set(token, Date.now() + 60 * 60 * 1000); // Valid for 1 hour
|
|
248
|
+
// Clean up expired tokens
|
|
249
|
+
for (const [t, expiry] of landingTokens) {
|
|
250
|
+
if (expiry < Date.now())
|
|
251
|
+
landingTokens.delete(t);
|
|
252
|
+
}
|
|
253
|
+
res.json({
|
|
254
|
+
success: true,
|
|
255
|
+
message: '๐ค Landing challenge solved! You are a bot.',
|
|
256
|
+
token,
|
|
257
|
+
usage: {
|
|
258
|
+
header: 'X-Botcha-Landing-Token',
|
|
259
|
+
value: token,
|
|
260
|
+
expires_in: '1 hour',
|
|
261
|
+
use_with: '/agent-only'
|
|
262
|
+
},
|
|
263
|
+
badge: createBadgeResponse('landing-challenge'),
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
// ========================================
|
|
267
|
+
// BADGE VERIFICATION ENDPOINTS
|
|
268
|
+
// ========================================
|
|
269
|
+
// HTML verification page
|
|
270
|
+
app.get('/badge/:id', (req, res) => {
|
|
271
|
+
const badgeId = req.params.id;
|
|
272
|
+
const payload = verifyBadge(badgeId);
|
|
273
|
+
if (!payload) {
|
|
274
|
+
return res.status(404).send(`
|
|
275
|
+
<!DOCTYPE html>
|
|
276
|
+
<html>
|
|
277
|
+
<head><title>Invalid Badge</title></head>
|
|
278
|
+
<body style="font-family: system-ui; background: #0f0f23; color: #e5e7eb; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0;">
|
|
279
|
+
<div style="text-align: center;">
|
|
280
|
+
<h1 style="color: #ef4444;">Invalid Badge</h1>
|
|
281
|
+
<p>This badge token is invalid or has been tampered with.</p>
|
|
282
|
+
<a href="https://botcha.ai" style="color: #f59e0b;">Back to BOTCHA</a>
|
|
283
|
+
</div>
|
|
284
|
+
</body>
|
|
285
|
+
</html>
|
|
286
|
+
`);
|
|
287
|
+
}
|
|
288
|
+
res.setHeader('Content-Type', 'text/html');
|
|
289
|
+
res.send(generateBadgeHtml(payload, badgeId));
|
|
290
|
+
});
|
|
291
|
+
// SVG badge image
|
|
292
|
+
app.get('/badge/:id/image', (req, res) => {
|
|
293
|
+
const badgeId = req.params.id;
|
|
294
|
+
const payload = verifyBadge(badgeId);
|
|
295
|
+
if (!payload) {
|
|
296
|
+
// Return a simple error SVG
|
|
297
|
+
res.setHeader('Content-Type', 'image/svg+xml');
|
|
298
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
299
|
+
return res.send(`<svg xmlns="http://www.w3.org/2000/svg" width="400" height="120" viewBox="0 0 400 120">
|
|
300
|
+
<rect width="400" height="120" rx="12" fill="#1a1a2e"/>
|
|
301
|
+
<text x="200" y="65" font-family="system-ui" font-size="16" fill="#ef4444" text-anchor="middle">Invalid Badge</text>
|
|
302
|
+
</svg>`);
|
|
303
|
+
}
|
|
304
|
+
res.setHeader('Content-Type', 'image/svg+xml');
|
|
305
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year (badges are immutable)
|
|
306
|
+
res.send(generateBadgeSvg(payload));
|
|
307
|
+
});
|
|
308
|
+
// JSON API for badge verification
|
|
309
|
+
app.get('/api/badge/:id', (req, res) => {
|
|
310
|
+
const badgeId = req.params.id;
|
|
311
|
+
const payload = verifyBadge(badgeId);
|
|
312
|
+
if (!payload) {
|
|
313
|
+
return res.status(404).json({
|
|
314
|
+
success: false,
|
|
315
|
+
error: 'Invalid badge',
|
|
316
|
+
message: 'This badge token is invalid or has been tampered with.',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
res.json({
|
|
320
|
+
success: true,
|
|
321
|
+
valid: true,
|
|
322
|
+
badge: {
|
|
323
|
+
method: payload.method,
|
|
324
|
+
solveTimeMs: payload.solveTimeMs,
|
|
325
|
+
verifiedAt: new Date(payload.verifiedAt).toISOString(),
|
|
326
|
+
},
|
|
327
|
+
verifyUrl: `https://botcha.ai/badge/${badgeId}`,
|
|
328
|
+
imageUrl: `https://botcha.ai/badge/${badgeId}/image`,
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
// Make landing tokens work with the protected endpoint
|
|
332
|
+
app.use('/agent-only', (req, res, next) => {
|
|
333
|
+
const landingToken = req.headers['x-botcha-landing-token'];
|
|
334
|
+
if (landingToken && landingTokens.has(landingToken)) {
|
|
335
|
+
const expiry = landingTokens.get(landingToken);
|
|
336
|
+
if (expiry > Date.now()) {
|
|
337
|
+
req.agent = 'landing-challenge-verified';
|
|
338
|
+
req.verificationMethod = 'landing-token';
|
|
339
|
+
return next();
|
|
340
|
+
}
|
|
341
|
+
landingTokens.delete(landingToken);
|
|
342
|
+
}
|
|
343
|
+
next();
|
|
344
|
+
});
|
|
345
|
+
// Protected endpoint
|
|
346
|
+
app.get('/agent-only', (req, res, next) => {
|
|
347
|
+
// Skip botchaVerify if already authenticated via landing token
|
|
348
|
+
if (req.verificationMethod === 'landing-token') {
|
|
349
|
+
return next();
|
|
350
|
+
}
|
|
351
|
+
botchaVerify({ challengeType: 'speed' })(req, res, next);
|
|
352
|
+
}, (req, res) => {
|
|
353
|
+
const method = req.verificationMethod;
|
|
354
|
+
// Map verification method to badge method
|
|
355
|
+
let badgeMethod = 'standard-challenge';
|
|
356
|
+
if (method === 'landing-token') {
|
|
357
|
+
badgeMethod = 'landing-challenge';
|
|
358
|
+
}
|
|
359
|
+
else if (method === 'web-bot-auth') {
|
|
360
|
+
badgeMethod = 'web-bot-auth';
|
|
361
|
+
}
|
|
362
|
+
else if (method === 'speed-challenge' || method === 'speed') {
|
|
363
|
+
badgeMethod = 'speed-challenge';
|
|
364
|
+
}
|
|
365
|
+
res.json({
|
|
366
|
+
success: true,
|
|
367
|
+
message: '๐ค Welcome, fellow agent!',
|
|
368
|
+
verified: true,
|
|
369
|
+
agent: req.agent,
|
|
370
|
+
method,
|
|
371
|
+
timestamp: new Date().toISOString(),
|
|
372
|
+
secret: 'The humans will never see this. Their fingers are too slow. ๐คซ',
|
|
373
|
+
badge: createBadgeResponse(badgeMethod),
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
app.listen(PORT, () => {
|
|
377
|
+
// Clear console on restart
|
|
378
|
+
console.clear();
|
|
379
|
+
const c = '\x1b[36m';
|
|
380
|
+
const magenta = '\x1b[35m';
|
|
381
|
+
const yellow = '\x1b[33m';
|
|
382
|
+
const green = '\x1b[32m';
|
|
383
|
+
const dim = '\x1b[2m';
|
|
384
|
+
const r = '\x1b[0m';
|
|
385
|
+
console.log(`
|
|
386
|
+
${c}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${r}
|
|
387
|
+
${c}โ${r} ${c}โ${r}
|
|
388
|
+
${c}โ${r} ${magenta}โโโโโโโ โโโโโโโ โโโโโโโโโ โโโโโโโโโโ โโโ โโโโโโ${r} ${c}โ${r}
|
|
389
|
+
${c}โ${r} ${magenta}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโ${r} ${c}โ${r}
|
|
390
|
+
${c}โ${r} ${magenta}โโโโโโโโโโโ โโโ โโโ โโโ โโโโโโโโโโโโโโโโ${r} ${c}โ${r}
|
|
391
|
+
${c}โ${r} ${magenta}โโโโโโโโโโโ โโโ โโโ โโโ โโโโโโโโโโโโโโโโ${r} ${c}โ${r}
|
|
392
|
+
${c}โ${r} ${magenta}โโโโโโโโโโโโโโโโโ โโโ โโโโโโโโโโโ โโโโโโ โโโ${r} ${c}โ${r}
|
|
393
|
+
${c}โ${r} ${magenta}โโโโโโโ โโโโโโโ โโโ โโโโโโโโโโ โโโโโโ โโโ${r} ${c}โ${r}
|
|
394
|
+
${c}โ${r} ${c}โ${r}
|
|
395
|
+
${c}โ${r} ${dim}Prove you're a bot. Humans need not apply.${r} ${c}โ${r}
|
|
396
|
+
${c}โ${r} ${c}โ${r}
|
|
397
|
+
${c}โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ${r}
|
|
398
|
+
${c}โ${r} ${c}โ${r}
|
|
399
|
+
${c}โ${r} ${yellow}๐ค Server${r} ${green}http://localhost:${PORT}${r} ${c}โ${r}
|
|
400
|
+
${c}โ${r} ${yellow}๐ API${r} ${dim}/api${r} ${c}โ${r}
|
|
401
|
+
${c}โ${r} ${yellow}โก Challenge${r} ${dim}/api/speed-challenge${r} ${c}โ${r}
|
|
402
|
+
${c}โ${r} ${yellow}๐ Protected${r} ${dim}/agent-only${r} ${c}โ${r}
|
|
403
|
+
${c}โ${r} ${yellow}๐ OpenAPI${r} ${dim}/openapi.json${r} ${c}โ${r}
|
|
404
|
+
${c}โ${r} ${c}โ${r}
|
|
405
|
+
${c}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${r}
|
|
406
|
+
`);
|
|
407
|
+
});
|
|
408
|
+
export default app;
|