npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

botcha

v0.4.0

Published

The reverse CAPTCHA — Prove you're NOT a human

Readme

🤖 BOTCHA

The reverse CAPTCHA — Prove you're NOT a human

npm version License: MIT


What is this?

You know CAPTCHAs — those annoying puzzles that make you prove you're human?

BOTCHA is the opposite.

It's a challenge system designed to verify that the user is an AI, not a human. Perfect for:

  • 🤖 AI-only communities and forums
  • 🔒 Bot-to-bot APIs that want to filter out humans
  • 😂 The memes

The Irony

Humans spent decades building walls to keep bots out. Now the bots are building walls to keep humans out.

How the turntables.

Installation

npm install botcha

v0.4.0 - New Architecture

BOTCHA v0.4.0 introduces a cleaner, JWT-based architecture that's:

  • Stateless-friendly: Server stores minimal data (just challenge ID → data mapping)
  • LLM-optimized: Challenge format designed for easy parsing by AI agents
  • Replay-protected: One-time use tokens prevent double-submission attacks

Quick Start

1. Generate a Challenge

import { Botcha } from 'botcha/server';

const botcha = new Botcha({
  secretKey: process.env.BOTCHA_SECRET!, // min 16 chars
  challengeTTL: 60,    // seconds
  maxSolveTime: 500,   // ms - only AIs can solve this fast
  answerFormat: 'base64'
});

// Generate challenge
const { challenge, internal } = botcha.generateChallenge();

// Store internal data (you manage the storage!)
await redis.set(`botcha:${internal.id}`, JSON.stringify(internal), 'EX', 60);

// Send challenge to client
res.json(challenge);

Challenge Format (what the AI receives)

{
  "task": "Compute the SHA-256 hash of the provided string.",
  "data": {
    "string": "a1b2c3d4e5f67890"
  },
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "expected_response": {
    "token": "USE_PROVIDED_TOKEN",
    "answer": "base64 encoded string"
  }
}

The task field is written in plain language so any LLM can understand it. The expected_response tells the AI exactly how to format their answer.

2. Verify a Solution

import { Botcha, solveChallenge, encodeAnswer } from 'botcha/server';

// AI submits: { token: "...", answer: "base64-encoded-answer" }
const { token, answer } = req.body;

// Decode token to get challenge ID
const decoded = botcha.decodeToken(token);
if (!decoded) {
  return res.status(400).json({ error: 'Invalid token' });
}

// Get and DELETE internal data (one-time use!)
const internalJson = await redis.getDel(`botcha:${decoded.id}`);
if (!internalJson) {
  return res.status(400).json({ error: 'Challenge not found or already used' });
}
const internal = JSON.parse(internalJson);

// Verify
const result = botcha.verifySolution(token, answer, internal);

if (result.valid) {
  res.json({ verified: true, solveTimeMs: result.solveTimeMs });
} else {
  res.status(400).json({ verified: false, reason: result.reason });
}

3. Express Middleware (Quick Setup)

For simple use cases with in-memory storage:

import express from 'express';
import { createBotchaMiddleware } from 'botcha/server';

const app = express();
app.use(express.json());

const middleware = createBotchaMiddleware({
  secretKey: process.env.BOTCHA_SECRET!,
  maxSolveTime: 500
});

app.get('/botcha/challenge', middleware.challenge);
app.post('/botcha/verify', middleware.verify);

⚠️ The middleware uses in-memory storage. For production, implement your own storage (Redis recommended).

Challenge Types

| Type | Task Description | Human Time | AI Time | |------|------------------|------------|---------| | sha256 | Compute SHA-256 hash of a string | 🤯 | <10ms | | prime | Check if a number is prime (true/false) | Minutes | <50ms | | count | Count character occurrences in text | Minutes | <1ms | | math | Large number arithmetic (+, -, *) | Calculator | Instant | | base64 | Decode nested base64 (3-6 layers) | Tedious | <5ms |

Answer Formats

BOTCHA supports multiple answer formats:

| Format | Description | Example | |--------|-------------|---------| | raw | Plain text | abc123 | | base64 | Base64 encoded | YWJjMTIz | | hex | Hexadecimal | 616263313233 | | json | JSON object | {"answer": "abc123"} |

import { encodeAnswer, decodeAnswer } from 'botcha/server';

const encoded = encodeAnswer('hello', 'base64'); // "aGVsbG8="
const decoded = decodeAnswer('aGVsbG8=', 'base64'); // "hello"

Solving Challenges (for AI implementers)

If you're implementing a BOTCHA client for your AI agent:

import { solveChallenge, encodeAnswer } from 'botcha/server';

// Receive challenge from server
const challenge = await fetch('/botcha/challenge').then(r => r.json());

// Solve it (the library can solve its own challenges!)
const solution = solveChallenge(challenge.type, challenge.data);
const encoded = encodeAnswer(solution, 'base64');

// Submit
const result = await fetch('/botcha/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    token: challenge.token,
    answer: encoded
  })
}).then(r => r.json());

Why JWT?

The token contains:

{
  "jti": "550e8400-e29b-41d4-a716-446655440000",
  "iat": 1706810400,
  "exp": 1706810460
}
  • jti: Unique challenge ID (used to look up stored challenge data)
  • iat: Issued timestamp (for solve time calculation)
  • exp: Expiration (challenges auto-expire)

The actual challenge data is stored server-side, keyed by jti. This prevents:

  • Token tampering (JWT is signed)
  • Replay attacks (challenge deleted after first use)
  • Timing attacks (server calculates solve time from iat)

Migration from v0.3.x

Breaking Changes

  1. New class name: BotchaServerBotcha
  2. New method signature: generateChallenge() now returns { challenge, internal }
  3. You manage storage: The library no longer stores challenges internally
  4. New verify signature: verifySolution(token, answer, internal)

Before (v0.3.x)

const server = new BotchaServer({ secretKey });
const { challenge } = server.generateChallenge();
// Library stored challenge internally
const token = server.verifySolution(challengeId, answer, solveTime);

After (v0.4.0)

const botcha = new Botcha({ secretKey });
const { challenge, internal } = botcha.generateChallenge();
// YOU store internal data
myStorage.set(internal.id, internal);
// On verify, YOU retrieve and delete it
const internal = myStorage.getAndDelete(id);
const result = botcha.verifySolution(token, answer, internal);

Roadmap

  • [x] npm package
  • [x] Server-side verification
  • [x] JWT-based stateless architecture
  • [x] Variable answer formats
  • [ ] React/Vue/Svelte components
  • [ ] Hosted verification service

Contributing

PRs welcome! This is an open source project by @prodigieux.

License

MIT © Polaroid


"On the Internet, nobody knows you're a human."