@yigitahmetsahin/captcha-solver 3.0.0 → 3.1.0
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 +1 -0
- package/dist/index.cjs +752 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -5
- package/dist/index.d.ts +77 -5
- package/dist/index.js +738 -35
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/solver.ts","../src/preprocess.ts"],"sourcesContent":["import type { LanguageModel, LanguageModelUsage } from 'ai';\nimport { generateText } from 'ai';\nimport type { PreprocessOptions } from './preprocess.js';\nimport { preprocessCaptchaToBuffer } from './preprocess.js';\n\nconst PROMPT = `You are an expert OCR assistant reading distorted text from a CAPTCHA image.\nTwo versions of the same captcha are provided. Cross-reference both to determine the correct text.\nThe text may contain uppercase letters (A-Z), lowercase letters (a-z), and/or digits (0-9).\nPay close attention to:\n- Letter case: lowercase \"e\" has a horizontal bar inside, digit \"0\" does not. Lowercase \"r\" has a short descender, uppercase \"T\" has a flat top.\n- Similar shapes: \"5\" has a flat top + curved bottom, \"S\" is fully curved. \"4\" has an angled stroke, \"A\" has a pointed top. \"6\" has a closed bottom loop, \"8\" has two loops. \"2\" has a curved top + flat bottom, \"Z\" has all straight lines.\nOutput ONLY the exact characters you read, preserving case. Nothing else.`;\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport type Provider = 'openai' | 'anthropic' | 'google';\n\nexport interface SolverOptions {\n /** AI provider to use when constructing the model from an API key (default: \"openai\") */\n provider?: Provider;\n /** Model ID passed to the provider (default: \"gpt-4o\") */\n model?: string;\n}\n\nexport interface SolveOptions {\n /** Number of voting attempts (default: 7) */\n numAttempts?: number;\n /** Expected captcha length — results of other lengths are discarded */\n expectedLength?: number;\n /** Max retries per attempt on API failure (default: 2) */\n maxRetries?: number;\n /** Whether to log attempt details (default: true) */\n verbose?: boolean;\n /**\n * Confusion groups for majority voting.\n * Pass a Record<string, string> to merge visually similar characters,\n * or `false` to disable (default: false).\n * Use LEGACY_CONFUSION_GROUPS to restore pre-3.0 behavior.\n */\n confusionGroups?: Record<string, string> | false;\n /** Preprocessing options passed to the image pipeline */\n preprocess?: PreprocessOptions;\n}\n\nexport interface SolveResult {\n /** The solved captcha text (majority-voted) */\n text: string;\n /** Per-attempt raw answers (before voting) */\n attempts: string[];\n /** Aggregated token usage across all parallel attempts */\n usage: LanguageModelUsage;\n /** Per-attempt usage breakdown */\n attemptUsages: LanguageModelUsage[];\n}\n\ninterface AttemptResult {\n text: string;\n usage: LanguageModelUsage;\n}\n\n// ── Provider resolution ──────────────────────────────────────────────\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n openai: 'gpt-4o',\n anthropic: 'claude-sonnet-4-20250514',\n google: 'gemini-2.0-flash',\n};\n\nasync function resolveModel(\n apiKey: string,\n provider: Provider,\n modelId: string\n): Promise<LanguageModel> {\n switch (provider) {\n case 'openai': {\n const { createOpenAI } = await import('@ai-sdk/openai');\n return createOpenAI({ apiKey })(modelId);\n }\n case 'anthropic': {\n // @ts-expect-error — optional peer dependency\n const { createAnthropic } = await import('@ai-sdk/anthropic');\n return createAnthropic({ apiKey })(modelId);\n }\n case 'google': {\n // @ts-expect-error — optional peer dependency\n const { createGoogleGenerativeAI } = await import('@ai-sdk/google');\n return createGoogleGenerativeAI({ apiKey })(modelId);\n }\n default:\n throw new Error(\n `Unknown provider \"${provider}\". Install the matching @ai-sdk/* package and pass the model directly.`\n );\n }\n}\n\n// ── Confusion groups ─────────────────────────────────────────────────\n\n/**\n * Pre-3.0 confusion groups that merge visually similar characters.\n * Opt-in via `{ confusionGroups: LEGACY_CONFUSION_GROUPS }`.\n *\n * Maps: 1/I/L → '1', O/D/0 → 'O', S/5 → 'S', Z/2 → 'Z'\n */\nexport const LEGACY_CONFUSION_GROUPS: Record<string, string> = {\n '1': '1',\n I: '1',\n L: '1',\n O: 'O',\n D: 'O',\n '0': 'O',\n S: 'S',\n '5': 'S',\n Z: 'Z',\n '2': 'Z',\n};\n\n// ── Majority voting ──────────────────────────────────────────────────\n\n/**\n * Character-level majority vote across multiple attempts.\n * When `groups` is provided, visually similar characters are merged\n * during counting (e.g. 1/I/L all count toward '1').\n */\nexport function majorityVote(\n attempts: string[],\n expectedLength?: number,\n groups?: Record<string, string> | false\n): string {\n let filtered = expectedLength ? attempts.filter((a) => a.length === expectedLength) : attempts;\n\n if (filtered.length === 0) {\n filtered = attempts;\n }\n if (filtered.length === 0) return '';\n\n // Find most common length\n const lenCounts = new Map<number, number>();\n for (const a of filtered) {\n lenCounts.set(a.length, (lenCounts.get(a.length) ?? 0) + 1);\n }\n let bestLen = 0;\n let bestCount = 0;\n for (const [len, count] of lenCounts) {\n if (count > bestCount) {\n bestLen = len;\n bestCount = count;\n }\n }\n\n const sameLenAttempts = filtered.filter((a) => a.length === bestLen);\n if (sameLenAttempts.length === 0) return filtered[0];\n\n const useGroups = groups && typeof groups === 'object' ? groups : undefined;\n\n // Vote per character position\n const result: string[] = [];\n for (let pos = 0; pos < bestLen; pos++) {\n const charCounts = new Map<string, number>();\n for (const a of sameLenAttempts) {\n const ch = a[pos];\n charCounts.set(ch, (charCounts.get(ch) ?? 0) + 1);\n }\n\n if (useGroups) {\n // Confusion-aware voting\n const groupCounts = new Map<string, number>();\n for (const [ch, count] of charCounts) {\n const canonical = useGroups[ch] ?? ch;\n groupCounts.set(canonical, (groupCounts.get(canonical) ?? 0) + count);\n }\n\n let bestGroup = '';\n let bestGroupCount = 0;\n for (const [canonical, count] of groupCounts) {\n if (count > bestGroupCount) {\n bestGroup = canonical;\n bestGroupCount = count;\n }\n }\n result.push(bestGroup);\n } else {\n // Simple majority — pick the most frequent raw character\n let bestChar = '';\n let bestCharCount = 0;\n for (const [ch, count] of charCounts) {\n if (count > bestCharCount) {\n bestChar = ch;\n bestCharCount = count;\n }\n }\n result.push(bestChar);\n }\n }\n\n return result.join('');\n}\n\n// ── Usage aggregation ────────────────────────────────────────────────\n\nfunction sumOptional(a: number | undefined, b: number | undefined): number | undefined {\n if (a === undefined && b === undefined) return undefined;\n return (a ?? 0) + (b ?? 0);\n}\n\nfunction aggregateUsage(usages: LanguageModelUsage[]): LanguageModelUsage {\n const zero: LanguageModelUsage = {\n inputTokens: undefined,\n inputTokenDetails: {\n noCacheTokens: undefined,\n cacheReadTokens: undefined,\n cacheWriteTokens: undefined,\n },\n outputTokens: undefined,\n outputTokenDetails: {\n textTokens: undefined,\n reasoningTokens: undefined,\n },\n totalTokens: undefined,\n };\n return usages.reduce<LanguageModelUsage>(\n (acc, u) => ({\n inputTokens: sumOptional(acc.inputTokens, u.inputTokens),\n inputTokenDetails: {\n noCacheTokens: sumOptional(\n acc.inputTokenDetails.noCacheTokens,\n u.inputTokenDetails.noCacheTokens\n ),\n cacheReadTokens: sumOptional(\n acc.inputTokenDetails.cacheReadTokens,\n u.inputTokenDetails.cacheReadTokens\n ),\n cacheWriteTokens: sumOptional(\n acc.inputTokenDetails.cacheWriteTokens,\n u.inputTokenDetails.cacheWriteTokens\n ),\n },\n outputTokens: sumOptional(acc.outputTokens, u.outputTokens),\n outputTokenDetails: {\n textTokens: sumOptional(acc.outputTokenDetails.textTokens, u.outputTokenDetails.textTokens),\n reasoningTokens: sumOptional(\n acc.outputTokenDetails.reasoningTokens,\n u.outputTokenDetails.reasoningTokens\n ),\n },\n totalTokens: sumOptional(acc.totalTokens, u.totalTokens),\n }),\n zero\n );\n}\n\n// ── Solver class ─────────────────────────────────────────────────────\n\nexport class Solver {\n private _model: LanguageModel | null = null;\n private _pendingModel: Promise<LanguageModel> | null = null;\n\n /**\n * Create a captcha solver.\n *\n * @example\n * // Simple — defaults to OpenAI gpt-4o\n * const solver = new Solver('sk-...');\n *\n * @example\n * // Specify provider and model\n * const solver = new Solver('sk-ant-...', { provider: 'anthropic', model: 'claude-sonnet-4-20250514' });\n *\n * @example\n * // Pass an AI SDK model directly\n * import { createOpenAI } from '@ai-sdk/openai';\n * const openai = createOpenAI({ apiKey: 'sk-...' });\n * const solver = new Solver(openai('gpt-4o'));\n */\n constructor(keyOrModel: string | LanguageModel, options?: SolverOptions) {\n if (typeof keyOrModel === 'string') {\n const provider = options?.provider ?? 'openai';\n const modelId = options?.model ?? DEFAULT_MODELS[provider];\n // Lazily resolve the model on first use\n this._pendingModel = resolveModel(keyOrModel, provider, modelId);\n } else {\n this._model = keyOrModel;\n }\n }\n\n private async getModel(): Promise<LanguageModel> {\n if (this._model) return this._model;\n this._model = await this._pendingModel!;\n this._pendingModel = null;\n return this._model;\n }\n\n /**\n * Solve a captcha image.\n *\n * @param input - File path (string) or raw image Buffer\n * @param options - Solve options (attempts, expected length, etc.)\n * @returns Solved text, per-attempt answers, and token usage\n */\n async solve(input: string | Buffer, options: SolveOptions = {}): Promise<SolveResult> {\n const {\n numAttempts = 7,\n expectedLength,\n maxRetries = 2,\n verbose = true,\n confusionGroups = false,\n preprocess,\n } = options;\n\n const model = await this.getModel();\n\n // Two complementary views:\n // 1. Enhanced grayscale (high contrast + auto-crop) — great for clear text\n // 2. Color original (upscaled, no greyscale, no contrast) — preserves subtle features\n const [enhancedBuffer, colorBuffer] = await Promise.all([\n preprocessCaptchaToBuffer(input, preprocess),\n preprocessCaptchaToBuffer(input, {\n blur: 0,\n scale: 4,\n contrast: 1.0,\n sharpen: false,\n crop: 'none',\n padding: 40,\n greyscale: false,\n }),\n ]);\n\n // Fire all attempts in parallel for speed\n const results = await Promise.all(\n Array.from({ length: numAttempts }, () =>\n this.singleAttempt(model, enhancedBuffer, colorBuffer, maxRetries)\n )\n );\n const valid = results.filter((r): r is AttemptResult => r !== null);\n if (verbose) {\n valid.forEach((r, i) => console.log(` Attempt ${i + 1}: ${r.text}`));\n }\n\n const attempts = valid.map((r) => r.text);\n const attemptUsages = valid.map((r) => r.usage);\n const usage = aggregateUsage(attemptUsages);\n\n if (attempts.length === 0) {\n if (verbose) console.log(' All attempts failed!');\n return { text: '', attempts, usage, attemptUsages };\n }\n\n return {\n text: majorityVote(attempts, expectedLength, confusionGroups),\n attempts,\n usage,\n attemptUsages,\n };\n }\n\n /**\n * Make a single API call to read the captcha.\n * Retries up to `maxRetries` times on failure.\n */\n private async singleAttempt(\n model: LanguageModel,\n primaryBuffer: Buffer,\n secondaryBuffer: Buffer,\n maxRetries: number\n ): Promise<AttemptResult | null> {\n for (let retry = 0; retry <= maxRetries; retry++) {\n try {\n const { text, usage } = await generateText({\n model,\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: PROMPT },\n { type: 'image', image: primaryBuffer },\n { type: 'image', image: secondaryBuffer },\n ],\n },\n ],\n temperature: 1,\n maxOutputTokens: 256,\n });\n\n const raw = text.trim();\n\n // Detect refusals\n const lower = raw.toLowerCase();\n if (\n lower.includes('sorry') ||\n lower.includes(\"can't help\") ||\n lower.includes('cannot help') ||\n lower.includes('unable to') ||\n lower.includes(\"i can't\") ||\n raw.length > 20\n ) {\n return null;\n }\n\n // Clean: keep only uppercase letters and digits\n const cleaned = raw.replace(/[^A-Za-z0-9]/g, '');\n return cleaned ? { text: cleaned, usage } : null;\n } catch (_err) {\n if (retry < maxRetries) {\n await new Promise((r) => setTimeout(r, 1000 * (retry + 1)));\n continue;\n }\n return null;\n }\n }\n return null;\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport sharp from 'sharp';\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport interface CropFractions {\n /** Fraction from left edge (0–1, default: 0.1) */\n left: number;\n /** Fraction from top edge (0–1, default: 0.02) */\n top: number;\n /** Fraction from left to keep (0–1, default: 0.9) */\n right: number;\n /** Fraction from top to keep (0–1, default: 0.6) */\n bottom: number;\n}\n\nexport interface PreprocessOptions {\n /** Gaussian blur radius (default: 1.5). Set to 0 to skip. */\n blur?: number;\n /** Upscale factor (default: 4) */\n scale?: number;\n /** Contrast multiplier around image mean (default: 3.0). Set to 1 to skip. */\n contrast?: number;\n /** Enable unsharp-mask sharpening (default: true) */\n sharpen?: boolean;\n /**\n * Crop mode (default: 'auto'):\n * - 'auto' – trim whitespace after contrast enhancement, with margin\n * - 'legacy' – fixed-percentage crop (original behavior)\n * - 'none' – skip cropping\n * - CropFractions – custom crop percentages\n */\n crop?: 'auto' | 'legacy' | 'none' | CropFractions;\n /** Add white padding around the result (default: true). Pass false to skip, or a number for custom px. */\n padding?: boolean | number;\n /** Invert colors (negate) after processing (default: false) */\n negate?: boolean;\n /** Convert to greyscale (default: true) */\n greyscale?: boolean;\n}\n\nconst LEGACY_CROP: CropFractions = { left: 0.1, top: 0.02, right: 0.9, bottom: 0.6 };\n\n// ── Public API ───────────────────────────────────────────────────────\n\n/**\n * Preprocess a captcha image and return a base64-encoded PNG string.\n */\nexport async function preprocessCaptcha(\n input: string | Buffer,\n options?: PreprocessOptions\n): Promise<string> {\n const buf = await preprocessCaptchaToBuffer(input, options);\n return buf.toString('base64');\n}\n\n/**\n * Preprocess a captcha image and return the resulting PNG as a raw Buffer.\n *\n * Pipeline:\n * 1. Gaussian blur in color space (smooths dither pattern)\n * 2. Grayscale conversion\n * 3. Upscale with Lanczos\n * 4. Contrast boost around image mean + sharpen\n * 5. Crop (auto-detect, legacy fixed, none, or custom)\n * 6. Add white padding\n */\nexport async function preprocessCaptchaToBuffer(\n input: string | Buffer,\n options?: PreprocessOptions\n): Promise<Buffer> {\n const {\n blur = 1.5,\n scale = 4,\n contrast = 3.0,\n sharpen = true,\n crop = 'auto',\n padding = true,\n negate = false,\n greyscale = true,\n } = options ?? {};\n\n const source = typeof input === 'string' ? path.resolve(input) : input;\n\n // Read original dimensions\n const metadata = await sharp(source).metadata();\n const origW = metadata.width!;\n const origH = metadata.height!;\n\n // Step 1-2: Blur (optional) + greyscale (optional)\n let pipeline = sharp(source);\n if (blur > 0) pipeline = pipeline.blur(blur);\n if (greyscale) pipeline = pipeline.greyscale();\n const smoothed = await pipeline.toBuffer();\n\n // Step 3: Upscale with Lanczos\n const upscaled = await sharp(smoothed)\n .resize(origW * scale, origH * scale, { kernel: 'lanczos3' })\n .toBuffer();\n\n // Step 4: Contrast boost + sharpen\n let enhanced: Buffer;\n if (contrast !== 1.0) {\n const stats = await sharp(upscaled).stats();\n const mean = stats.channels[0].mean;\n let pipe = sharp(upscaled).linear(contrast, mean * (1 - contrast));\n if (sharpen) pipe = pipe.sharpen({ sigma: 1.0, m1: 2.0, m2: 1.0 });\n enhanced = await pipe.toBuffer();\n } else {\n enhanced = sharpen\n ? await sharp(upscaled).sharpen({ sigma: 1.0, m1: 2.0, m2: 1.0 }).toBuffer()\n : upscaled;\n }\n\n // Step 5: Crop\n let cropped: Buffer;\n if (crop === 'none') {\n cropped = enhanced;\n } else if (crop === 'auto') {\n cropped = await autoCrop(enhanced);\n } else {\n const fractions = crop === 'legacy' ? LEGACY_CROP : crop;\n const scaledW = origW * scale;\n const scaledH = origH * scale;\n const cropLeft = Math.floor(scaledW * fractions.left);\n const cropTop = Math.floor(scaledH * fractions.top);\n const cropRight = Math.floor(scaledW * fractions.right);\n const cropBottom = Math.floor(scaledH * fractions.bottom);\n const cropW = cropRight - cropLeft;\n const cropH = cropBottom - cropTop;\n cropped = await sharp(enhanced)\n .extract({ left: cropLeft, top: cropTop, width: cropW, height: cropH })\n .toBuffer();\n }\n\n // Step 6: Negate (optional)\n const final = negate ? await sharp(cropped).negate().toBuffer() : cropped;\n\n // Step 7: Padding\n if (padding === false) {\n return sharp(final).png().toBuffer();\n }\n const pad = typeof padding === 'number' ? padding : undefined;\n const vPad = pad ?? 20;\n const hPad = pad ?? 30;\n return sharp(final)\n .extend({\n top: vPad,\n bottom: vPad,\n left: hPad,\n right: hPad,\n background: { r: 255, g: 255, b: 255 },\n })\n .png()\n .toBuffer();\n}\n\n/**\n * Auto-crop: use sharp.trim() to detect the content bounding box after\n * contrast enhancement, then add a small margin. Falls back to the\n * untrimmed image if trim removes everything.\n */\nasync function autoCrop(enhanced: Buffer): Promise<Buffer> {\n try {\n const trimmed = sharp(enhanced).trim({ threshold: 30 });\n const trimmedBuf = await trimmed.toBuffer({ resolveWithObject: true });\n\n // If trim left a reasonable image, add a margin\n const { width, height } = trimmedBuf.info;\n if (width > 2 && height > 2) {\n return trimmedBuf.data;\n }\n } catch {\n // trim() can throw if image is uniform — fall through\n }\n\n // Fallback: return untrimmed\n return enhanced;\n}\n\n/**\n * Read an image file and return its base64-encoded content.\n */\nexport function imageToBase64(imagePath: string): string {\n const buffer = fs.readFileSync(imagePath);\n return buffer.toString('base64');\n}\n"],"mappings":";AACA,SAAS,oBAAoB;;;ACD7B,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,WAAW;AAwClB,IAAM,cAA6B,EAAE,MAAM,KAAK,KAAK,MAAM,OAAO,KAAK,QAAQ,IAAI;AAOnF,eAAsB,kBACpB,OACA,SACiB;AACjB,QAAM,MAAM,MAAM,0BAA0B,OAAO,OAAO;AAC1D,SAAO,IAAI,SAAS,QAAQ;AAC9B;AAaA,eAAsB,0BACpB,OACA,SACiB;AACjB,QAAM;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,UAAU;AAAA,IACV,OAAO;AAAA,IACP,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY;AAAA,EACd,IAAI,WAAW,CAAC;AAEhB,QAAM,SAAS,OAAO,UAAU,WAAW,KAAK,QAAQ,KAAK,IAAI;AAGjE,QAAM,WAAW,MAAM,MAAM,MAAM,EAAE,SAAS;AAC9C,QAAM,QAAQ,SAAS;AACvB,QAAM,QAAQ,SAAS;AAGvB,MAAI,WAAW,MAAM,MAAM;AAC3B,MAAI,OAAO,EAAG,YAAW,SAAS,KAAK,IAAI;AAC3C,MAAI,UAAW,YAAW,SAAS,UAAU;AAC7C,QAAM,WAAW,MAAM,SAAS,SAAS;AAGzC,QAAM,WAAW,MAAM,MAAM,QAAQ,EAClC,OAAO,QAAQ,OAAO,QAAQ,OAAO,EAAE,QAAQ,WAAW,CAAC,EAC3D,SAAS;AAGZ,MAAI;AACJ,MAAI,aAAa,GAAK;AACpB,UAAM,QAAQ,MAAM,MAAM,QAAQ,EAAE,MAAM;AAC1C,UAAM,OAAO,MAAM,SAAS,CAAC,EAAE;AAC/B,QAAI,OAAO,MAAM,QAAQ,EAAE,OAAO,UAAU,QAAQ,IAAI,SAAS;AACjE,QAAI,QAAS,QAAO,KAAK,QAAQ,EAAE,OAAO,GAAK,IAAI,GAAK,IAAI,EAAI,CAAC;AACjE,eAAW,MAAM,KAAK,SAAS;AAAA,EACjC,OAAO;AACL,eAAW,UACP,MAAM,MAAM,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAK,IAAI,GAAK,IAAI,EAAI,CAAC,EAAE,SAAS,IACzE;AAAA,EACN;AAGA,MAAI;AACJ,MAAI,SAAS,QAAQ;AACnB,cAAU;AAAA,EACZ,WAAW,SAAS,QAAQ;AAC1B,cAAU,MAAM,SAAS,QAAQ;AAAA,EACnC,OAAO;AACL,UAAM,YAAY,SAAS,WAAW,cAAc;AACpD,UAAM,UAAU,QAAQ;AACxB,UAAM,UAAU,QAAQ;AACxB,UAAM,WAAW,KAAK,MAAM,UAAU,UAAU,IAAI;AACpD,UAAM,UAAU,KAAK,MAAM,UAAU,UAAU,GAAG;AAClD,UAAM,YAAY,KAAK,MAAM,UAAU,UAAU,KAAK;AACtD,UAAM,aAAa,KAAK,MAAM,UAAU,UAAU,MAAM;AACxD,UAAM,QAAQ,YAAY;AAC1B,UAAM,QAAQ,aAAa;AAC3B,cAAU,MAAM,MAAM,QAAQ,EAC3B,QAAQ,EAAE,MAAM,UAAU,KAAK,SAAS,OAAO,OAAO,QAAQ,MAAM,CAAC,EACrE,SAAS;AAAA,EACd;AAGA,QAAM,QAAQ,SAAS,MAAM,MAAM,OAAO,EAAE,OAAO,EAAE,SAAS,IAAI;AAGlE,MAAI,YAAY,OAAO;AACrB,WAAO,MAAM,KAAK,EAAE,IAAI,EAAE,SAAS;AAAA,EACrC;AACA,QAAM,MAAM,OAAO,YAAY,WAAW,UAAU;AACpD,QAAM,OAAO,OAAO;AACpB,QAAM,OAAO,OAAO;AACpB,SAAO,MAAM,KAAK,EACf,OAAO;AAAA,IACN,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA,EACvC,CAAC,EACA,IAAI,EACJ,SAAS;AACd;AAOA,eAAe,SAAS,UAAmC;AACzD,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,EAAE,KAAK,EAAE,WAAW,GAAG,CAAC;AACtD,UAAM,aAAa,MAAM,QAAQ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAGrE,UAAM,EAAE,OAAO,OAAO,IAAI,WAAW;AACrC,QAAI,QAAQ,KAAK,SAAS,GAAG;AAC3B,aAAO,WAAW;AAAA,IACpB;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,SAAO;AACT;AAKO,SAAS,cAAc,WAA2B;AACvD,QAAM,SAAS,GAAG,aAAa,SAAS;AACxC,SAAO,OAAO,SAAS,QAAQ;AACjC;;;ADtLA,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyDf,IAAM,iBAA2C;AAAA,EAC/C,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AACV;AAEA,eAAe,aACb,QACA,UACA,SACwB;AACxB,UAAQ,UAAU;AAAA,IAChB,KAAK,UAAU;AACb,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,gBAAgB;AACtD,aAAO,aAAa,EAAE,OAAO,CAAC,EAAE,OAAO;AAAA,IACzC;AAAA,IACA,KAAK,aAAa;AAEhB,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,mBAAmB;AAC5D,aAAO,gBAAgB,EAAE,OAAO,CAAC,EAAE,OAAO;AAAA,IAC5C;AAAA,IACA,KAAK,UAAU;AAEb,YAAM,EAAE,yBAAyB,IAAI,MAAM,OAAO,gBAAgB;AAClE,aAAO,yBAAyB,EAAE,OAAO,CAAC,EAAE,OAAO;AAAA,IACrD;AAAA,IACA;AACE,YAAM,IAAI;AAAA,QACR,qBAAqB,QAAQ;AAAA,MAC/B;AAAA,EACJ;AACF;AAUO,IAAM,0BAAkD;AAAA,EAC7D,KAAK;AAAA,EACL,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,KAAK;AAAA,EACL,GAAG;AAAA,EACH,KAAK;AAAA,EACL,GAAG;AAAA,EACH,KAAK;AACP;AASO,SAAS,aACd,UACA,gBACA,QACQ;AACR,MAAI,WAAW,iBAAiB,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,cAAc,IAAI;AAEtF,MAAI,SAAS,WAAW,GAAG;AACzB,eAAW;AAAA,EACb;AACA,MAAI,SAAS,WAAW,EAAG,QAAO;AAGlC,QAAM,YAAY,oBAAI,IAAoB;AAC1C,aAAW,KAAK,UAAU;AACxB,cAAU,IAAI,EAAE,SAAS,UAAU,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,EAC5D;AACA,MAAI,UAAU;AACd,MAAI,YAAY;AAChB,aAAW,CAAC,KAAK,KAAK,KAAK,WAAW;AACpC,QAAI,QAAQ,WAAW;AACrB,gBAAU;AACV,kBAAY;AAAA,IACd;AAAA,EACF;AAEA,QAAM,kBAAkB,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO;AACnE,MAAI,gBAAgB,WAAW,EAAG,QAAO,SAAS,CAAC;AAEnD,QAAM,YAAY,UAAU,OAAO,WAAW,WAAW,SAAS;AAGlE,QAAM,SAAmB,CAAC;AAC1B,WAAS,MAAM,GAAG,MAAM,SAAS,OAAO;AACtC,UAAM,aAAa,oBAAI,IAAoB;AAC3C,eAAW,KAAK,iBAAiB;AAC/B,YAAM,KAAK,EAAE,GAAG;AAChB,iBAAW,IAAI,KAAK,WAAW,IAAI,EAAE,KAAK,KAAK,CAAC;AAAA,IAClD;AAEA,QAAI,WAAW;AAEb,YAAM,cAAc,oBAAI,IAAoB;AAC5C,iBAAW,CAAC,IAAI,KAAK,KAAK,YAAY;AACpC,cAAM,YAAY,UAAU,EAAE,KAAK;AACnC,oBAAY,IAAI,YAAY,YAAY,IAAI,SAAS,KAAK,KAAK,KAAK;AAAA,MACtE;AAEA,UAAI,YAAY;AAChB,UAAI,iBAAiB;AACrB,iBAAW,CAAC,WAAW,KAAK,KAAK,aAAa;AAC5C,YAAI,QAAQ,gBAAgB;AAC1B,sBAAY;AACZ,2BAAiB;AAAA,QACnB;AAAA,MACF;AACA,aAAO,KAAK,SAAS;AAAA,IACvB,OAAO;AAEL,UAAI,WAAW;AACf,UAAI,gBAAgB;AACpB,iBAAW,CAAC,IAAI,KAAK,KAAK,YAAY;AACpC,YAAI,QAAQ,eAAe;AACzB,qBAAW;AACX,0BAAgB;AAAA,QAClB;AAAA,MACF;AACA,aAAO,KAAK,QAAQ;AAAA,IACtB;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,EAAE;AACvB;AAIA,SAAS,YAAY,GAAuB,GAA2C;AACrF,MAAI,MAAM,UAAa,MAAM,OAAW,QAAO;AAC/C,UAAQ,KAAK,MAAM,KAAK;AAC1B;AAEA,SAAS,eAAe,QAAkD;AACxE,QAAM,OAA2B;AAAA,IAC/B,aAAa;AAAA,IACb,mBAAmB;AAAA,MACjB,eAAe;AAAA,MACf,iBAAiB;AAAA,MACjB,kBAAkB;AAAA,IACpB;AAAA,IACA,cAAc;AAAA,IACd,oBAAoB;AAAA,MAClB,YAAY;AAAA,MACZ,iBAAiB;AAAA,IACnB;AAAA,IACA,aAAa;AAAA,EACf;AACA,SAAO,OAAO;AAAA,IACZ,CAAC,KAAK,OAAO;AAAA,MACX,aAAa,YAAY,IAAI,aAAa,EAAE,WAAW;AAAA,MACvD,mBAAmB;AAAA,QACjB,eAAe;AAAA,UACb,IAAI,kBAAkB;AAAA,UACtB,EAAE,kBAAkB;AAAA,QACtB;AAAA,QACA,iBAAiB;AAAA,UACf,IAAI,kBAAkB;AAAA,UACtB,EAAE,kBAAkB;AAAA,QACtB;AAAA,QACA,kBAAkB;AAAA,UAChB,IAAI,kBAAkB;AAAA,UACtB,EAAE,kBAAkB;AAAA,QACtB;AAAA,MACF;AAAA,MACA,cAAc,YAAY,IAAI,cAAc,EAAE,YAAY;AAAA,MAC1D,oBAAoB;AAAA,QAClB,YAAY,YAAY,IAAI,mBAAmB,YAAY,EAAE,mBAAmB,UAAU;AAAA,QAC1F,iBAAiB;AAAA,UACf,IAAI,mBAAmB;AAAA,UACvB,EAAE,mBAAmB;AAAA,QACvB;AAAA,MACF;AAAA,MACA,aAAa,YAAY,IAAI,aAAa,EAAE,WAAW;AAAA,IACzD;AAAA,IACA;AAAA,EACF;AACF;AAIO,IAAM,SAAN,MAAa;AAAA,EACV,SAA+B;AAAA,EAC/B,gBAA+C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBvD,YAAY,YAAoC,SAAyB;AACvE,QAAI,OAAO,eAAe,UAAU;AAClC,YAAM,WAAW,SAAS,YAAY;AACtC,YAAM,UAAU,SAAS,SAAS,eAAe,QAAQ;AAEzD,WAAK,gBAAgB,aAAa,YAAY,UAAU,OAAO;AAAA,IACjE,OAAO;AACL,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAc,WAAmC;AAC/C,QAAI,KAAK,OAAQ,QAAO,KAAK;AAC7B,SAAK,SAAS,MAAM,KAAK;AACzB,SAAK,gBAAgB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MAAM,OAAwB,UAAwB,CAAC,GAAyB;AACpF,UAAM;AAAA,MACJ,cAAc;AAAA,MACd;AAAA,MACA,aAAa;AAAA,MACb,UAAU;AAAA,MACV,kBAAkB;AAAA,MAClB;AAAA,IACF,IAAI;AAEJ,UAAM,QAAQ,MAAM,KAAK,SAAS;AAKlC,UAAM,CAAC,gBAAgB,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtD,0BAA0B,OAAO,UAAU;AAAA,MAC3C,0BAA0B,OAAO;AAAA,QAC/B,MAAM;AAAA,QACN,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,QACN,SAAS;AAAA,QACT,WAAW;AAAA,MACb,CAAC;AAAA,IACH,CAAC;AAGD,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC5B,MAAM;AAAA,QAAK,EAAE,QAAQ,YAAY;AAAA,QAAG,MAClC,KAAK,cAAc,OAAO,gBAAgB,aAAa,UAAU;AAAA,MACnE;AAAA,IACF;AACA,UAAM,QAAQ,QAAQ,OAAO,CAAC,MAA0B,MAAM,IAAI;AAClE,QAAI,SAAS;AACX,YAAM,QAAQ,CAAC,GAAG,MAAM,QAAQ,IAAI,aAAa,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;AAAA,IACtE;AAEA,UAAM,WAAW,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AACxC,UAAM,gBAAgB,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK;AAC9C,UAAM,QAAQ,eAAe,aAAa;AAE1C,QAAI,SAAS,WAAW,GAAG;AACzB,UAAI,QAAS,SAAQ,IAAI,wBAAwB;AACjD,aAAO,EAAE,MAAM,IAAI,UAAU,OAAO,cAAc;AAAA,IACpD;AAEA,WAAO;AAAA,MACL,MAAM,aAAa,UAAU,gBAAgB,eAAe;AAAA,MAC5D;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cACZ,OACA,eACA,iBACA,YAC+B;AAC/B,aAAS,QAAQ,GAAG,SAAS,YAAY,SAAS;AAChD,UAAI;AACF,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,aAAa;AAAA,UACzC;AAAA,UACA,UAAU;AAAA,YACR;AAAA,cACE,MAAM;AAAA,cACN,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,OAAO;AAAA,gBAC7B,EAAE,MAAM,SAAS,OAAO,cAAc;AAAA,gBACtC,EAAE,MAAM,SAAS,OAAO,gBAAgB;AAAA,cAC1C;AAAA,YACF;AAAA,UACF;AAAA,UACA,aAAa;AAAA,UACb,iBAAiB;AAAA,QACnB,CAAC;AAED,cAAM,MAAM,KAAK,KAAK;AAGtB,cAAM,QAAQ,IAAI,YAAY;AAC9B,YACE,MAAM,SAAS,OAAO,KACtB,MAAM,SAAS,YAAY,KAC3B,MAAM,SAAS,aAAa,KAC5B,MAAM,SAAS,WAAW,KAC1B,MAAM,SAAS,SAAS,KACxB,IAAI,SAAS,IACb;AACA,iBAAO;AAAA,QACT;AAGA,cAAM,UAAU,IAAI,QAAQ,iBAAiB,EAAE;AAC/C,eAAO,UAAU,EAAE,MAAM,SAAS,MAAM,IAAI;AAAA,MAC9C,SAAS,MAAM;AACb,YAAI,QAAQ,YAAY;AACtB,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,OAAQ,QAAQ,EAAE,CAAC;AAC1D;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/preprocess.ts","../src/tesseract.ts","../src/solver.ts","../src/disambiguate.ts","../src/index.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport sharp from 'sharp';\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport interface CropFractions {\n /** Fraction from left edge (0–1, default: 0.1) */\n left: number;\n /** Fraction from top edge (0–1, default: 0.02) */\n top: number;\n /** Fraction from left to keep (0–1, default: 0.9) */\n right: number;\n /** Fraction from top to keep (0–1, default: 0.6) */\n bottom: number;\n}\n\nexport interface PreprocessOptions {\n /**\n * Fraction of image height to keep from the top, cropping the bottom (default: 1.0, no pre-crop).\n * Useful for removing dark bands at the bottom of dithered captchas.\n */\n preCropHeight?: number;\n /** Median filter size at original resolution before other processing (default: 0, off). Odd number. */\n median?: number;\n /** Gaussian blur radius (default: 1.5). Set to 0 to skip. */\n blur?: number;\n /** Convert to greyscale (default: true) */\n greyscale?: boolean;\n /** Upscale factor (default: 4) */\n scale?: number;\n /** Upscale interpolation kernel (default: 'lanczos3') */\n upscaleKernel?: 'lanczos3' | 'nearest' | 'cubic' | 'mitchell';\n /** Gaussian blur applied AFTER upscaling — use large values (10-20) for dither removal (default: 0, off) */\n postBlur?: number;\n /** Normalise (stretch histogram to full range) before contrast/threshold (default: false) */\n normalise?: boolean;\n /** Contrast multiplier around image mean (default: 3.0). Set to 1 to skip. */\n contrast?: number;\n /** Enable unsharp-mask sharpening (default: true) */\n sharpen?: boolean;\n /** Binary threshold (0-255). Applied after contrast. (default: false, off) */\n threshold?: number | false;\n /** Invert colors (negate) after processing (default: false) */\n negate?: boolean;\n /**\n * Crop mode (default: 'auto'):\n * - 'auto' – trim whitespace after contrast enhancement, with margin\n * - 'legacy' – fixed-percentage crop (original behavior)\n * - 'none' – skip cropping\n * - CropFractions – custom crop percentages\n */\n crop?: 'auto' | 'legacy' | 'none' | CropFractions;\n /** Add white padding around the result (default: true). Pass false to skip, or a number for custom px. */\n padding?: boolean | number;\n /** Resize final image to this width in pixels, maintaining aspect ratio (default: none). Useful for downscaling after high-res processing. */\n targetWidth?: number;\n}\n\nconst LEGACY_CROP: CropFractions = { left: 0.1, top: 0.02, right: 0.9, bottom: 0.6 };\n\n// ── Public API ───────────────────────────────────────────────────────\n\n/**\n * Preprocess a captcha image and return a base64-encoded PNG string.\n */\nexport async function preprocessCaptcha(\n input: string | Buffer,\n options?: PreprocessOptions\n): Promise<string> {\n const buf = await preprocessCaptchaToBuffer(input, options);\n return buf.toString('base64');\n}\n\n/**\n * Preprocess a captcha image and return the resulting PNG as a raw Buffer.\n *\n * Pipeline:\n * 1. Gaussian blur in color space (smooths dither pattern)\n * 2. Grayscale conversion\n * 3. Upscale with Lanczos\n * 4. Contrast boost around image mean + sharpen\n * 5. Crop (auto-detect, legacy fixed, none, or custom)\n * 6. Add white padding\n */\nexport async function preprocessCaptchaToBuffer(\n input: string | Buffer,\n options?: PreprocessOptions\n): Promise<Buffer> {\n const {\n preCropHeight = 1.0,\n median = 0,\n blur = 1.5,\n greyscale = true,\n scale = 4,\n upscaleKernel = 'lanczos3',\n postBlur = 0,\n normalise = false,\n contrast = 3.0,\n sharpen = true,\n threshold = false,\n negate = false,\n crop = 'auto',\n padding = true,\n } = options ?? {};\n\n let source: string | Buffer = typeof input === 'string' ? path.resolve(input) : input;\n\n // Read original dimensions\n const metadata = await sharp(source).metadata();\n const origW = metadata.width!;\n let origH = metadata.height!;\n\n // Step 0: Pre-crop bottom portion (removes dark bands in dithered captchas)\n if (preCropHeight < 1.0 && preCropHeight > 0) {\n const keepH = Math.floor(origH * preCropHeight);\n source = await sharp(source)\n .extract({ left: 0, top: 0, width: origW, height: keepH })\n .toBuffer();\n origH = keepH;\n }\n\n // Step 1: Median filter at original resolution (great for salt-pepper / dither noise)\n let pipeline = sharp(source);\n if (median > 0) pipeline = pipeline.median(median);\n\n // Step 2: Blur (optional) at original resolution\n if (blur > 0) pipeline = pipeline.blur(blur);\n\n // Step 3: Greyscale (optional)\n if (greyscale) pipeline = pipeline.greyscale();\n const smoothed = await pipeline.toBuffer();\n\n // Step 4: Upscale with configurable kernel\n const upscaled = await sharp(smoothed)\n .resize(origW * scale, origH * scale, { kernel: upscaleKernel })\n .toBuffer();\n\n // Step 5: Post-upscale blur (for dither removal on the enlarged image)\n let postProcessed = upscaled;\n if (postBlur > 0) {\n postProcessed = await sharp(upscaled).blur(postBlur).toBuffer();\n }\n\n // Step 6: Normalise (optional — stretch histogram to full range)\n if (normalise) {\n postProcessed = await sharp(postProcessed).normalise().toBuffer();\n }\n\n // Step 7: Contrast boost + sharpen\n let enhanced: Buffer;\n if (contrast !== 1.0) {\n const stats = await sharp(postProcessed).stats();\n const mean = stats.channels[0].mean;\n let pipe = sharp(postProcessed).linear(contrast, mean * (1 - contrast));\n if (sharpen) pipe = pipe.sharpen({ sigma: 1.0, m1: 2.0, m2: 1.0 });\n enhanced = await pipe.toBuffer();\n } else {\n enhanced = sharpen\n ? await sharp(postProcessed).sharpen({ sigma: 1.0, m1: 2.0, m2: 1.0 }).toBuffer()\n : postProcessed;\n }\n\n // Step 8: Threshold (optional — binary B/W)\n if (threshold !== false && typeof threshold === 'number') {\n enhanced = await sharp(enhanced).threshold(threshold).toBuffer();\n }\n\n // Step 9: Target width resize (downscale after high-res processing)\n const targetWidth = options?.targetWidth;\n if (targetWidth && targetWidth > 0) {\n enhanced = await sharp(enhanced).resize(targetWidth, null, { kernel: 'lanczos3' }).toBuffer();\n }\n\n // Step 10: Crop\n let cropped: Buffer;\n if (crop === 'none') {\n cropped = enhanced;\n } else if (crop === 'auto') {\n cropped = await autoCrop(enhanced);\n } else {\n const fractions = crop === 'legacy' ? LEGACY_CROP : crop;\n const scaledW = origW * scale;\n const scaledH = origH * scale;\n const cropLeft = Math.floor(scaledW * fractions.left);\n const cropTop = Math.floor(scaledH * fractions.top);\n const cropRight = Math.floor(scaledW * fractions.right);\n const cropBottom = Math.floor(scaledH * fractions.bottom);\n const cropW = cropRight - cropLeft;\n const cropH = cropBottom - cropTop;\n cropped = await sharp(enhanced)\n .extract({ left: cropLeft, top: cropTop, width: cropW, height: cropH })\n .toBuffer();\n }\n\n // Step 6: Negate (optional)\n const final = negate ? await sharp(cropped).negate().toBuffer() : cropped;\n\n // Step 7: Padding\n if (padding === false) {\n return sharp(final).png().toBuffer();\n }\n const pad = typeof padding === 'number' ? padding : undefined;\n const vPad = pad ?? 20;\n const hPad = pad ?? 30;\n return sharp(final)\n .extend({\n top: vPad,\n bottom: vPad,\n left: hPad,\n right: hPad,\n background: { r: 255, g: 255, b: 255 },\n })\n .png()\n .toBuffer();\n}\n\n/**\n * Auto-crop: use sharp.trim() to detect the content bounding box after\n * contrast enhancement, then add a small margin. Falls back to the\n * untrimmed image if trim removes everything.\n */\nasync function autoCrop(enhanced: Buffer): Promise<Buffer> {\n try {\n const trimmed = sharp(enhanced).trim({ threshold: 30 });\n const trimmedBuf = await trimmed.toBuffer({ resolveWithObject: true });\n\n // If trim left a reasonable image, add a margin\n const { width, height } = trimmedBuf.info;\n if (width > 2 && height > 2) {\n return trimmedBuf.data;\n }\n } catch {\n // trim() can throw if image is uniform — fall through\n }\n\n // Fallback: return untrimmed\n return enhanced;\n}\n\n/**\n * Read an image file and return its base64-encoded content.\n */\nexport function imageToBase64(imagePath: string): string {\n const buffer = fs.readFileSync(imagePath);\n return buffer.toString('base64');\n}\n","import type { PreprocessOptions } from './preprocess.js';\nimport { preprocessCaptchaToBuffer } from './preprocess.js';\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface TesseractWorker {\n setParameters: (params: Record<string, string>) => Promise<void>;\n recognize: (image: Buffer) => Promise<{ data: { text: string } }>;\n terminate: () => Promise<void>;\n}\n\nexport interface TesseractReader {\n recognize: (image: Buffer) => Promise<string>;\n recognizeMulti: (input: string | Buffer, variants: PreprocessOptions[]) => Promise<string[]>;\n dispose: () => Promise<void>;\n}\n\n// ── Public API ───────────────────────────────────────────────────────\n\n/**\n * Create a Tesseract OCR reader. Returns null if tesseract.js is not installed.\n * The reader uses PSM_SINGLE_LINE and an A-Z0-9 whitelist.\n */\nexport async function createTesseractReader(): Promise<TesseractReader | null> {\n let createWorker: (lang: string) => Promise<TesseractWorker>;\n try {\n const tess = await import('tesseract.js');\n createWorker = tess.createWorker as unknown as typeof createWorker;\n } catch {\n return null; // tesseract.js not installed\n }\n\n const worker = await createWorker('eng');\n await worker.setParameters({\n tessedit_char_whitelist: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',\n tessedit_pageseg_mode: '7', // PSM.SINGLE_LINE\n });\n\n return {\n async recognize(image: Buffer): Promise<string> {\n const { data } = await worker.recognize(image);\n return data.text.trim().replace(/[^A-Z0-9]/g, '');\n },\n\n async recognizeMulti(input: string | Buffer, variants: PreprocessOptions[]): Promise<string[]> {\n const results: string[] = [];\n for (const opts of variants) {\n try {\n const buf = await preprocessCaptchaToBuffer(input, opts);\n const { data } = await worker.recognize(buf);\n const clean = data.text.trim().replace(/[^A-Z0-9]/g, '');\n if (clean.length >= 2 && clean.length <= 8) {\n results.push(clean);\n }\n } catch {\n // skip failed variant\n }\n }\n return results;\n },\n\n async dispose(): Promise<void> {\n await worker.terminate();\n },\n };\n}\n\n/**\n * Default preprocessing variants for Tesseract OCR.\n * Different blur/threshold levels produce diverse reads.\n */\nexport const TESSERACT_VARIANTS: PreprocessOptions[] = [\n // Variant 1: standard enhanced\n {\n blur: 1.5,\n greyscale: true,\n scale: 4,\n contrast: 3.0,\n sharpen: true,\n crop: 'auto',\n padding: true,\n },\n // Variant 2: enhanced + negated\n {\n blur: 1.5,\n greyscale: true,\n scale: 4,\n contrast: 3.0,\n sharpen: true,\n negate: true,\n crop: 'auto',\n padding: true,\n },\n];\n","import type { LanguageModel, LanguageModelUsage } from 'ai';\nimport { generateText } from 'ai';\nimport type { PreprocessOptions } from './preprocess.js';\nimport { preprocessCaptchaToBuffer } from './preprocess.js';\nimport { disambiguateResult } from './disambiguate.js';\nimport type { TesseractReader } from './tesseract.js';\n\nconst PROMPT = `Read the 4 distorted characters in these images. Two processed versions shown.\nThe text uses UPPERCASE A-Z and digits 0-9 only. No lowercase.\n\nWARNING: The dithered rendering makes many characters appear as \"2\". Before writing \"2\", check:\n- Could it be \"6\"? (has closed loop at bottom)\n- Could it be \"L\"? (has vertical stem + horizontal foot, 90° corner)\n- Could it be \"1\"? (thin vertical stroke, no curve)\n- Could it be \"Z\"? (all straight lines, sharp angles)\n\nAlso watch for: O/0 have curved sides (not D which has flat left); B has two bumps (not D with one curve); X is two crossing diagonals (not K with vertical bar); G has horizontal bar inside (not C).\n\nOutput ONLY the 4 characters.`;\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport type Provider = 'openai' | 'anthropic' | 'google';\n\nexport interface SolverOptions {\n /** AI provider to use when constructing the model from an API key (default: \"openai\") */\n provider?: Provider;\n /** Model ID passed to the provider (default: \"gpt-4o\") */\n model?: string;\n}\n\nexport interface SolveOptions {\n /** Number of voting attempts (default: 7) */\n numAttempts?: number;\n /** Expected captcha length — results of other lengths are discarded */\n expectedLength?: number;\n /** Max retries per attempt on API failure (default: 2) */\n maxRetries?: number;\n /** Whether to log attempt details (default: true) */\n verbose?: boolean;\n /**\n * Confusion groups for majority voting.\n * Pass a Record<string, string> to merge visually similar characters,\n * or `false` to disable (default: false).\n * Use LEGACY_CONFUSION_GROUPS to restore pre-3.0 behavior.\n */\n confusionGroups?: Record<string, string> | false;\n /** Preprocessing options passed to the image pipeline */\n preprocess?: PreprocessOptions;\n /** Use Tesseract OCR as an additional voter (default: true if tesseract.js is installed) */\n useTesseract?: boolean;\n /** Use programmatic hole-detection to disambiguate 2/6/L/1 (default: true) */\n useDisambiguation?: boolean;\n}\n\nexport interface SolveResult {\n /** The solved captcha text (majority-voted) */\n text: string;\n /** Per-attempt raw answers (before voting) */\n attempts: string[];\n /** Aggregated token usage across all parallel attempts */\n usage: LanguageModelUsage;\n /** Per-attempt usage breakdown */\n attemptUsages: LanguageModelUsage[];\n}\n\ninterface AttemptResult {\n text: string;\n usage: LanguageModelUsage;\n}\n\n// ── Provider resolution ──────────────────────────────────────────────\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n openai: 'gpt-4o',\n anthropic: 'claude-sonnet-4-20250514',\n google: 'gemini-2.0-flash',\n};\n\nasync function resolveModel(\n apiKey: string,\n provider: Provider,\n modelId: string\n): Promise<LanguageModel> {\n switch (provider) {\n case 'openai': {\n const { createOpenAI } = await import('@ai-sdk/openai');\n return createOpenAI({ apiKey })(modelId);\n }\n case 'anthropic': {\n const { createAnthropic } = await import('@ai-sdk/anthropic');\n return createAnthropic({ apiKey })(modelId);\n }\n case 'google': {\n // @ts-expect-error — optional peer dependency\n const { createGoogleGenerativeAI } = await import('@ai-sdk/google');\n return createGoogleGenerativeAI({ apiKey })(modelId);\n }\n default:\n throw new Error(\n `Unknown provider \"${provider}\". Install the matching @ai-sdk/* package and pass the model directly.`\n );\n }\n}\n\n// ── Confusion groups ─────────────────────────────────────────────────\n\n/**\n * Pre-3.0 confusion groups that merge visually similar characters.\n * Opt-in via `{ confusionGroups: LEGACY_CONFUSION_GROUPS }`.\n *\n * Maps: 1/I/L → '1', O/D/0 → 'O', S/5 → 'S', Z/2 → 'Z'\n */\nexport const LEGACY_CONFUSION_GROUPS: Record<string, string> = {\n '1': '1',\n I: '1',\n L: '1',\n O: 'O',\n D: 'O',\n '0': 'O',\n S: 'S',\n '5': 'S',\n Z: 'Z',\n '2': 'Z',\n};\n\n/**\n * Confusion groups optimised for dithered / halftone captchas.\n * Vision models systematically misread certain characters in dithered rendering.\n *\n * Maps: D→'O', I→'1', K/A→'X', C→'G', 9→'8', Y→'X', E→'5'\n */\nexport const DITHER_CONFUSION_GROUPS: Record<string, string> = {\n D: 'O',\n O: 'O',\n I: '1',\n '1': '1',\n K: 'X',\n X: 'X',\n A: 'X',\n C: 'G',\n G: 'G',\n '9': '8',\n '8': '8',\n Y: 'X',\n E: '5',\n '5': '5',\n};\n\n// ── Majority voting ──────────────────────────────────────────────────\n\n/**\n * Character-level majority vote across multiple attempts.\n * When `groups` is provided, visually similar characters are merged\n * during counting (e.g. 1/I/L all count toward '1').\n *\n * After voting, a repetition penalty is applied: if any character appears\n * 3+ times in the result (unlikely in real captchas), positions with that\n * character are reconsidered using the next-best alternative.\n */\nexport function majorityVote(\n attempts: string[],\n expectedLength?: number,\n groups?: Record<string, string> | false\n): string {\n let filtered = expectedLength ? attempts.filter((a) => a.length === expectedLength) : attempts;\n\n if (filtered.length === 0) {\n filtered = attempts;\n }\n if (filtered.length === 0) return '';\n\n // Find most common length\n const lenCounts = new Map<number, number>();\n for (const a of filtered) {\n lenCounts.set(a.length, (lenCounts.get(a.length) ?? 0) + 1);\n }\n let bestLen = 0;\n let bestCount = 0;\n for (const [len, count] of lenCounts) {\n if (count > bestCount) {\n bestLen = len;\n bestCount = count;\n }\n }\n\n const sameLenAttempts = filtered.filter((a) => a.length === bestLen);\n if (sameLenAttempts.length === 0) return filtered[0];\n\n const useGroups = groups && typeof groups === 'object' ? groups : undefined;\n\n // Vote per character position, collecting full ranked results\n const result: string[] = [];\n const rankedByPos: Map<string, number>[] = [];\n\n for (let pos = 0; pos < bestLen; pos++) {\n const charCounts = new Map<string, number>();\n for (const a of sameLenAttempts) {\n const ch = a[pos];\n charCounts.set(ch, (charCounts.get(ch) ?? 0) + 1);\n }\n\n if (useGroups) {\n const groupCounts = new Map<string, number>();\n for (const [ch, count] of charCounts) {\n const canonical = useGroups[ch] ?? ch;\n groupCounts.set(canonical, (groupCounts.get(canonical) ?? 0) + count);\n }\n rankedByPos.push(groupCounts);\n\n let bestGroup = '';\n let bestGroupCount = 0;\n for (const [canonical, count] of groupCounts) {\n if (count > bestGroupCount) {\n bestGroup = canonical;\n bestGroupCount = count;\n }\n }\n result.push(bestGroup);\n } else {\n rankedByPos.push(charCounts);\n\n let bestChar = '';\n let bestCharCount = 0;\n for (const [ch, count] of charCounts) {\n if (count > bestCharCount) {\n bestChar = ch;\n bestCharCount = count;\n }\n }\n result.push(bestChar);\n }\n }\n\n // Repetition penalty: captchas rarely have 3+ identical characters.\n // Keep the position with the strongest vote; substitute excess positions\n // with the best alternative that does NOT already appear in the result.\n if (bestLen >= 4) {\n const charFreq = new Map<string, number>();\n for (const ch of result) {\n charFreq.set(ch, (charFreq.get(ch) ?? 0) + 1);\n }\n for (const [ch, freq] of charFreq) {\n if (freq < 3) continue;\n // Find the position with the STRONGEST vote for this char — keep it\n let strongestPos = -1;\n let strongestCount = 0;\n for (let pos = 0; pos < bestLen; pos++) {\n if (result[pos] !== ch) continue;\n const count = rankedByPos[pos].get(ch) ?? 0;\n if (count > strongestCount) {\n strongestCount = count;\n strongestPos = pos;\n }\n }\n // Substitute excess positions, preferring chars NOT already in the result\n for (let pos = 0; pos < bestLen; pos++) {\n if (result[pos] !== ch || pos === strongestPos) continue;\n const ranked = rankedByPos[pos];\n const usedChars = new Set(result);\n // Prefer unique alternatives (not already in result)\n let bestUnique = '';\n let bestUniqueCount = 0;\n let bestAny = '';\n let bestAnyCount = 0;\n for (const [c, count] of ranked) {\n if (c === ch) continue;\n if (count > bestAnyCount) {\n bestAny = c;\n bestAnyCount = count;\n }\n if (!usedChars.has(c) && count > bestUniqueCount) {\n bestUnique = c;\n bestUniqueCount = count;\n }\n }\n // Prefer unique alternatives; fall back to any alternative\n const sub = bestUniqueCount >= 2 ? bestUnique : bestAnyCount >= 2 ? bestAny : '';\n if (sub) {\n result[pos] = sub;\n }\n }\n }\n }\n\n return result.join('');\n}\n\n/**\n * Raw character-level majority vote WITHOUT repetition penalty.\n * Returns per-position vote maps for disambiguation.\n */\nexport function majorityVoteDetailed(\n attempts: string[],\n expectedLength?: number,\n groups?: Record<string, string> | false\n): { result: string[]; rankedByPos: Map<string, number>[] } {\n let filtered = expectedLength ? attempts.filter((a) => a.length === expectedLength) : attempts;\n if (filtered.length === 0) filtered = attempts;\n if (filtered.length === 0) return { result: [], rankedByPos: [] };\n\n const lenCounts = new Map<number, number>();\n for (const a of filtered) lenCounts.set(a.length, (lenCounts.get(a.length) ?? 0) + 1);\n let bestLen = 0;\n let bestCount = 0;\n for (const [len, count] of lenCounts) {\n if (count > bestCount) {\n bestLen = len;\n bestCount = count;\n }\n }\n const sameLenAttempts = filtered.filter((a) => a.length === bestLen);\n if (sameLenAttempts.length === 0) return { result: [...filtered[0]], rankedByPos: [] };\n\n const useGroups = groups && typeof groups === 'object' ? groups : undefined;\n const result: string[] = [];\n const rankedByPos: Map<string, number>[] = [];\n\n for (let pos = 0; pos < bestLen; pos++) {\n const counts = new Map<string, number>();\n for (const a of sameLenAttempts) {\n const ch = useGroups ? (useGroups[a[pos]] ?? a[pos]) : a[pos];\n counts.set(ch, (counts.get(ch) ?? 0) + 1);\n }\n rankedByPos.push(counts);\n let bestChar = '';\n let bestCharCount = 0;\n for (const [ch, count] of counts) {\n if (count > bestCharCount) {\n bestChar = ch;\n bestCharCount = count;\n }\n }\n result.push(bestChar);\n }\n\n return { result, rankedByPos };\n}\n\n// ── Usage aggregation ────────────────────────────────────────────────\n\nfunction sumOptional(a: number | undefined, b: number | undefined): number | undefined {\n if (a === undefined && b === undefined) return undefined;\n return (a ?? 0) + (b ?? 0);\n}\n\nfunction aggregateUsage(usages: LanguageModelUsage[]): LanguageModelUsage {\n const zero: LanguageModelUsage = {\n inputTokens: undefined,\n inputTokenDetails: {\n noCacheTokens: undefined,\n cacheReadTokens: undefined,\n cacheWriteTokens: undefined,\n },\n outputTokens: undefined,\n outputTokenDetails: {\n textTokens: undefined,\n reasoningTokens: undefined,\n },\n totalTokens: undefined,\n };\n return usages.reduce<LanguageModelUsage>(\n (acc, u) => ({\n inputTokens: sumOptional(acc.inputTokens, u.inputTokens),\n inputTokenDetails: {\n noCacheTokens: sumOptional(\n acc.inputTokenDetails.noCacheTokens,\n u.inputTokenDetails.noCacheTokens\n ),\n cacheReadTokens: sumOptional(\n acc.inputTokenDetails.cacheReadTokens,\n u.inputTokenDetails.cacheReadTokens\n ),\n cacheWriteTokens: sumOptional(\n acc.inputTokenDetails.cacheWriteTokens,\n u.inputTokenDetails.cacheWriteTokens\n ),\n },\n outputTokens: sumOptional(acc.outputTokens, u.outputTokens),\n outputTokenDetails: {\n textTokens: sumOptional(acc.outputTokenDetails.textTokens, u.outputTokenDetails.textTokens),\n reasoningTokens: sumOptional(\n acc.outputTokenDetails.reasoningTokens,\n u.outputTokenDetails.reasoningTokens\n ),\n },\n totalTokens: sumOptional(acc.totalTokens, u.totalTokens),\n }),\n zero\n );\n}\n\n// ── Solver class ─────────────────────────────────────────────────────\n\nexport class Solver {\n private _model: LanguageModel | null = null;\n private _pendingModel: Promise<LanguageModel> | null = null;\n\n /**\n * Create a captcha solver.\n *\n * @example\n * // Simple — defaults to OpenAI gpt-4o\n * const solver = new Solver('sk-...');\n *\n * @example\n * // Specify provider and model\n * const solver = new Solver('sk-ant-...', { provider: 'anthropic', model: 'claude-sonnet-4-20250514' });\n *\n * @example\n * // Pass an AI SDK model directly\n * import { createOpenAI } from '@ai-sdk/openai';\n * const openai = createOpenAI({ apiKey: 'sk-...' });\n * const solver = new Solver(openai('gpt-4o'));\n */\n constructor(keyOrModel: string | LanguageModel, options?: SolverOptions) {\n if (typeof keyOrModel === 'string') {\n const provider = options?.provider ?? 'openai';\n const modelId = options?.model ?? DEFAULT_MODELS[provider];\n // Lazily resolve the model on first use\n this._pendingModel = resolveModel(keyOrModel, provider, modelId);\n } else {\n this._model = keyOrModel;\n }\n }\n\n private async getModel(): Promise<LanguageModel> {\n if (this._model) return this._model;\n this._model = await this._pendingModel!;\n this._pendingModel = null;\n return this._model;\n }\n\n /**\n * Solve a captcha image.\n *\n * @param input - File path (string) or raw image Buffer\n * @param options - Solve options (attempts, expected length, etc.)\n * @returns Solved text, per-attempt answers, and token usage\n */\n async solve(input: string | Buffer, options: SolveOptions = {}): Promise<SolveResult> {\n const {\n numAttempts = 9,\n expectedLength,\n maxRetries = 2,\n verbose = true,\n confusionGroups = false,\n preprocess,\n useTesseract = true,\n useDisambiguation = true,\n } = options;\n\n const model = await this.getModel();\n\n // Three preprocessing variants with different blur levels for diversity:\n // 1. Enhanced grayscale (standard: high contrast + auto-crop)\n // 2. Heavy dither-clean (postBlur=15) — best for round/looped chars (G, 8, B, O)\n // 3. Medium dither-clean (postBlur=8) — preserves thin strokes better (X, 1, L, 5)\n const [enhancedBuffer, heavyCleanBuffer, mediumCleanBuffer] = await Promise.all([\n preprocessCaptchaToBuffer(input, preprocess),\n preprocessCaptchaToBuffer(input, {\n blur: 0,\n greyscale: true,\n scale: 8,\n upscaleKernel: 'nearest',\n postBlur: 15,\n normalise: true,\n contrast: 1.0,\n sharpen: false,\n threshold: 140,\n negate: true,\n crop: 'none',\n targetWidth: 800,\n padding: 20,\n }),\n preprocessCaptchaToBuffer(input, {\n blur: 0,\n greyscale: true,\n scale: 8,\n upscaleKernel: 'nearest',\n postBlur: 8,\n normalise: true,\n contrast: 1.0,\n sharpen: false,\n threshold: 120,\n negate: true,\n crop: 'none',\n targetWidth: 800,\n padding: 20,\n }),\n ]);\n\n // Split vision model attempts across preprocessing variants for diverse reads\n const halfN = Math.ceil(numAttempts / 2);\n const visionResults = await Promise.all([\n ...Array.from({ length: halfN }, () =>\n this.singleAttempt(model, enhancedBuffer, heavyCleanBuffer, maxRetries)\n ),\n ...Array.from({ length: numAttempts - halfN }, () =>\n this.singleAttempt(model, enhancedBuffer, mediumCleanBuffer, maxRetries)\n ),\n ]);\n const valid = visionResults.filter((r): r is AttemptResult => r !== null);\n if (verbose) {\n valid.forEach((r, i) => console.log(` Attempt ${i + 1}: ${r.text}`));\n }\n\n const attempts = valid.map((r) => r.text);\n const attemptUsages = valid.map((r) => r.usage);\n\n // Tesseract OCR as additional voter (algorithmic diversity)\n if (useTesseract) {\n try {\n const reader = await this.getTesseractReader();\n if (reader) {\n const { TESSERACT_VARIANTS } = await import('./tesseract.js');\n const tessReads = await reader.recognizeMulti(input, TESSERACT_VARIANTS);\n for (const read of tessReads) {\n attempts.push(read);\n if (verbose) console.log(` Tesseract: ${read}`);\n }\n }\n } catch {\n // Tesseract not available — silently skip\n }\n }\n\n // Self-correction pass: re-ask the model to verify suspicious reads\n // This helps detect 6/L/1 that were misread as 2/Z in the initial pass\n const correctionAttempts = Math.min(3, Math.floor(numAttempts / 3));\n if (correctionAttempts > 0 && attempts.length > 0) {\n // Take the most common initial read as the basis for correction\n const initialVote = majorityVote(attempts, expectedLength, confusionGroups);\n const suspiciousCount = [...initialVote].filter((c) => c === '2' || c === 'Z').length;\n if (suspiciousCount >= 2 && initialVote.length === (expectedLength ?? initialVote.length)) {\n const corrPrompt = this.buildCorrectionPrompt(initialVote);\n if (corrPrompt) {\n const corrections = await Promise.all(\n Array.from({ length: correctionAttempts }, () =>\n this.selfCorrect(model, enhancedBuffer, heavyCleanBuffer, initialVote, corrPrompt)\n )\n );\n for (const c of corrections) {\n if (c) {\n // Weight corrections heavily — each correction counts as 5 votes\n for (let w = 0; w < 5; w++) attempts.push(c.text);\n if (verbose) console.log(` Corrected: ${c.text}`);\n }\n }\n }\n }\n }\n\n const usage = aggregateUsage(attemptUsages);\n\n if (attempts.length === 0) {\n if (verbose) console.log(' All attempts failed!');\n return { text: '', attempts, usage, attemptUsages };\n }\n\n // Step 1: Raw vote (no repetition penalty yet)\n const { result, rankedByPos } = majorityVoteDetailed(attempts, expectedLength, confusionGroups);\n\n // Step 2: Programmatic disambiguation on raw vote\n // Two passes: heavy-clean for hole detection (→\"6\"), light-clean for shape features (→\"L\")\n if (useDisambiguation && result.length > 0 && rankedByPos.length > 0) {\n try {\n // Pass 1: hole detection on heavy-clean image (detects closed loops → \"6\", \"8\")\n await disambiguateResult(result, rankedByPos, heavyCleanBuffer);\n // Pass 2: shape features on light-clean image (detects L from width ratios)\n const lightCleanBuffer = await preprocessCaptchaToBuffer(input, {\n median: 3,\n blur: 0,\n greyscale: true,\n scale: 4,\n postBlur: 3,\n normalise: true,\n contrast: 1.0,\n sharpen: false,\n threshold: 128,\n crop: 'none',\n padding: 20,\n });\n await disambiguateResult(result, rankedByPos, lightCleanBuffer);\n } catch {\n // disambiguation failed — keep raw vote\n }\n }\n\n // Step 3: Apply repetition penalty + final vote (handles remaining issues)\n const finalText = majorityVote(\n [...attempts, result.join('')], // include disambiguated result as an extra \"vote\"\n expectedLength,\n confusionGroups\n );\n\n return {\n text: finalText,\n attempts,\n usage,\n attemptUsages,\n };\n }\n\n private _tesseractReader: TesseractReader | null | undefined = undefined;\n\n private async getTesseractReader(): Promise<TesseractReader | null> {\n if (this._tesseractReader !== undefined) return this._tesseractReader;\n try {\n const { createTesseractReader } = await import('./tesseract.js');\n this._tesseractReader = await createTesseractReader();\n } catch {\n this._tesseractReader = null;\n }\n return this._tesseractReader;\n }\n\n /** Clean up resources (Tesseract worker). */\n async dispose(): Promise<void> {\n if (this._tesseractReader) {\n await this._tesseractReader.dispose();\n this._tesseractReader = null;\n }\n }\n\n private buildCorrectionPrompt(initial: string): string | null {\n const checks = [...initial]\n .map((c, pos) => {\n if (c !== '2' && c !== 'Z') return null;\n if (pos === 0)\n return `Pos ${pos + 1} (\"${c}\"): thin stroke → \"1\"? closed loop at bottom → \"6\"? vertical+foot → \"L\"?`;\n if (pos < initial.length - 1)\n return `Pos ${pos + 1} (\"${c}\"): vertical + horizontal foot → \"L\"? thin stroke → \"1\"? loop → \"6\"?`;\n return `Pos ${pos + 1} (\"${c}\"): curved top → keep \"2\"; straight angles → \"Z\"`;\n })\n .filter(Boolean);\n if (!checks.length) return null;\n const prefix =\n [...initial].filter((c) => c === '2' || c === 'Z').length >= 3\n ? `\"${initial}\" has many similar chars — unusual for a captcha.\\n`\n : '';\n return `${prefix}Recheck:\\n${checks.join('\\n')}\\nOnly change with clear evidence. Output ONLY the corrected 4 characters.`;\n }\n\n private async selfCorrect(\n model: LanguageModel,\n primaryBuffer: Buffer,\n secondaryBuffer: Buffer,\n initial: string,\n correctionPrompt: string\n ): Promise<{ text: string } | null> {\n try {\n const { text } = await generateText({\n model,\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: PROMPT },\n { type: 'image', image: primaryBuffer },\n { type: 'image', image: secondaryBuffer },\n ],\n },\n { role: 'assistant', content: initial },\n {\n role: 'user',\n content: [\n { type: 'text', text: correctionPrompt },\n { type: 'image', image: primaryBuffer },\n ],\n },\n ],\n temperature: 0.3,\n maxOutputTokens: 32,\n });\n const cleaned = text\n .trim()\n .replace(/[^A-Za-z0-9]/g, '')\n .toUpperCase();\n return cleaned.length >= 2 && cleaned.length <= 8 ? { text: cleaned } : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Make a single API call to read the captcha.\n * Retries up to `maxRetries` times on failure.\n */\n private async singleAttempt(\n model: LanguageModel,\n primaryBuffer: Buffer,\n secondaryBuffer: Buffer,\n maxRetries: number\n ): Promise<AttemptResult | null> {\n for (let retry = 0; retry <= maxRetries; retry++) {\n try {\n const { text, usage } = await generateText({\n model,\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: PROMPT },\n { type: 'image', image: primaryBuffer },\n { type: 'image', image: secondaryBuffer },\n ],\n },\n ],\n temperature: 1,\n maxOutputTokens: 256,\n });\n\n const raw = text.trim();\n\n // Detect refusals\n const lower = raw.toLowerCase();\n if (\n lower.includes('sorry') ||\n lower.includes(\"can't help\") ||\n lower.includes('cannot help') ||\n lower.includes('unable to') ||\n lower.includes(\"i can't\")\n ) {\n return null;\n }\n\n // Extract answer: if short, use directly; if long (reasoning model), find last 2-8 char alphanumeric token\n let answer = '';\n const allAlpha = raw.replace(/[^A-Za-z0-9]/g, '').toUpperCase();\n if (allAlpha.length <= 10) {\n answer = allAlpha;\n } else {\n // Long output — scan lines from end for a short alphanumeric-only token\n const lines = raw.split(/\\n/).reverse();\n for (const line of lines) {\n const tokens = line.trim().split(/\\s+/);\n for (let ti = tokens.length - 1; ti >= 0; ti--) {\n const clean = tokens[ti].replace(/[^A-Za-z0-9]/g, '').toUpperCase();\n if (clean.length >= 2 && clean.length <= 8) {\n answer = clean;\n break;\n }\n }\n if (answer) break;\n }\n if (!answer) answer = allAlpha.slice(-8); // fallback: last 8 chars\n }\n return answer ? { text: answer, usage } : null;\n } catch (_err) {\n if (retry < maxRetries) {\n await new Promise((r) => setTimeout(r, 1000 * (retry + 1)));\n continue;\n }\n return null;\n }\n }\n return null;\n }\n}\n","import sharp from 'sharp';\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface CharRegion {\n left: number;\n right: number;\n top: number;\n bottom: number;\n}\n\ninterface CharFeatures {\n hasHoleBottom: boolean; // closed loop in bottom half → \"6\"\n hasHoleTop: boolean; // closed loop in top half → \"0\", \"8\"\n holeCount: number;\n aspectRatio: number; // height / width — \"1\" is very tall & narrow\n bottomHorizontalExtent: number; // fraction of width with dark pixels at bottom → \"L\" has wide bottom\n topHorizontalExtent: number; // fraction of width with dark pixels at top\n topCurvature: boolean; // curved top → \"2\", \"6\"; flat/absent → \"1\", \"L\", \"Z\"\n}\n\n// ── Public API ───────────────────────────────────────────────────────\n\n/**\n * Disambiguate characters in a voted result using deterministic image features.\n * Only acts on positions voted as \"2\" or \"Z\" where alternatives like 6/L/1 received votes.\n *\n * @param result - The voted character array (mutable, modified in place)\n * @param rankedByPos - Per-position vote counts from majorityVote\n * @param binaryImage - The preprocessed binary image buffer (dark text on white, from threshold+negate)\n */\nexport async function disambiguateResult(\n result: string[],\n rankedByPos: Map<string, number>[],\n binaryImage: Buffer\n): Promise<void> {\n // Only disambiguate positions voted as \"2\" or \"Z\" (the commonly confused characters)\n const ambiguousPositions: number[] = [];\n for (let pos = 0; pos < result.length; pos++) {\n if (result[pos] !== '2' && result[pos] !== 'Z') continue;\n const ranked = rankedByPos[pos];\n const hasAlt =\n (ranked.get('6') ?? 0) >= 1 || (ranked.get('L') ?? 0) >= 1 || (ranked.get('1') ?? 0) >= 1;\n if (hasAlt) {\n ambiguousPositions.push(pos);\n continue;\n }\n // Also trigger when 3+ positions are \"2\"/\"Z\" (suspiciously repetitive)\n const twoZCount = result.filter((c) => c === '2' || c === 'Z').length;\n if (twoZCount >= 3) {\n ambiguousPositions.push(pos);\n }\n }\n\n if (ambiguousPositions.length === 0) return;\n\n // Invert the image so characters are WHITE (>128) on BLACK (<128) background.\n // White blobs are better separated from the dark background for analysis.\n const meta = await sharp(binaryImage).metadata();\n const fullW = meta.width!;\n const fullH = meta.height!;\n const cropTop = Math.floor(fullH * 0.12);\n const cropH = Math.floor(fullH * 0.76);\n const { data, info } = await sharp(binaryImage)\n .extract({ left: 0, top: cropTop, width: fullW, height: cropH })\n .greyscale()\n .negate() // now: chars=WHITE(255), bg=BLACK(0)\n .raw()\n .toBuffer({ resolveWithObject: true });\n\n const w = info.width;\n const h = info.height;\n const pixels = new Uint8Array(data);\n\n // Segment characters by column projection\n const regions = segmentCharacters(pixels, w, h, result.length);\n if (!regions || regions.length !== result.length) return; // segmentation failed\n\n // Analyse and disambiguate each ambiguous position\n for (const pos of ambiguousPositions) {\n const region = regions[pos];\n const features = analyseCharacter(pixels, w, h, region);\n const newChar = classifyFromFeatures(features, result[pos]);\n if (newChar) {\n result[pos] = newChar;\n }\n }\n}\n\n// ── Character segmentation ──────────────────────────────────────────\n\n/**\n * Segment the image into N character regions using valley detection in column projection.\n * Computes dark-pixel density per column, smooths, then finds N-1 deepest valleys.\n */\nfunction segmentCharacters(\n pixels: Uint8Array,\n w: number,\n h: number,\n expectedCount: number\n): CharRegion[] | null {\n // Column projection: count dark pixels per column\n const colDensity = new Float64Array(w);\n for (let x = 0; x < w; x++) {\n let count = 0;\n for (let y = 0; y < h; y++) {\n if (pixels[y * w + x] >= 128) count++;\n }\n colDensity[x] = count / h;\n }\n\n // Find content bounds (skip padding)\n let contentLeft = 0;\n let contentRight = w;\n for (let x = 0; x < w; x++) {\n if (colDensity[x] > 0.05) {\n contentLeft = x;\n break;\n }\n }\n for (let x = w - 1; x >= 0; x--) {\n if (colDensity[x] > 0.05) {\n contentRight = x + 1;\n break;\n }\n }\n\n // Smooth the density with a moving average (window=15)\n const smoothW = 15;\n const smoothed = new Float64Array(w);\n for (let x = contentLeft; x < contentRight; x++) {\n let sum = 0;\n let count = 0;\n for (let dx = -smoothW; dx <= smoothW; dx++) {\n const nx = x + dx;\n if (nx >= contentLeft && nx < contentRight) {\n sum += colDensity[nx];\n count++;\n }\n }\n smoothed[x] = sum / count;\n }\n\n // Find N-1 valleys (local minima) to divide into N regions\n // Enforce generous margins so splits aren't too close to edges\n const charWidth = (contentRight - contentLeft) / expectedCount;\n const margin = Math.floor(charWidth * 0.6); // at least 60% of avg char width from edge\n const searchLeft = contentLeft + margin;\n const searchRight = contentRight - margin;\n\n // Collect all local minima with their depth\n const valleys: { x: number; depth: number }[] = [];\n for (let x = searchLeft + 1; x < searchRight - 1; x++) {\n if (smoothed[x] <= smoothed[x - 1] && smoothed[x] <= smoothed[x + 1]) {\n // Local minimum — compute depth as difference from neighbors\n const leftMax = Math.max(...Array.from(smoothed.slice(Math.max(searchLeft, x - 40), x)));\n const rightMax = Math.max(\n ...Array.from(smoothed.slice(x + 1, Math.min(searchRight, x + 41)))\n );\n const depth = Math.min(leftMax, rightMax) - smoothed[x];\n if (depth > 0.01) {\n valleys.push({ x, depth });\n }\n }\n }\n\n // Sort by depth (deepest first) and pick N-1 non-overlapping valleys\n valleys.sort((a, b) => b.depth - a.depth);\n const splits: number[] = [];\n const minDist = charWidth * 0.6; // splits must be at least 60% of avg char width apart\n for (const v of valleys) {\n if (splits.length >= expectedCount - 1) break;\n if (splits.every((s) => Math.abs(s - v.x) > minDist)) {\n splits.push(v.x);\n }\n }\n\n if (splits.length < expectedCount - 1) {\n // Fallback: evenly divide\n const step = (contentRight - contentLeft) / expectedCount;\n splits.length = 0;\n for (let i = 1; i < expectedCount; i++) {\n splits.push(Math.floor(contentLeft + step * i));\n }\n }\n\n splits.sort((a, b) => a - b);\n const boundaries = [contentLeft, ...splits, contentRight];\n\n // Build regions with vertical bounds\n return boundaries.slice(0, expectedCount).map((start, idx) => {\n const end = boundaries[idx + 1];\n let top = h;\n let bottom = 0;\n for (let y = 0; y < h; y++) {\n for (let x = start; x < end; x++) {\n if (pixels[y * w + x] >= 128) {\n if (y < top) top = y;\n if (y > bottom) bottom = y;\n }\n }\n }\n return { left: start, right: end, top: Math.max(0, top), bottom: Math.min(h, bottom + 1) };\n });\n}\n\n// ── Hole detection ──────────────────────────────────────────────────\n\n/**\n * Detect enclosed holes inside text characters using BFS flood-fill.\n * Image is WHITE-on-BLACK (text=white, bg=black).\n * Holes = BLACK regions enclosed by WHITE text, unreachable from the border.\n * Starts BFS from all border BLACK pixels; unvisited BLACK pixels = holes.\n */\nfunction detectHoles(\n pixels: Uint8Array,\n imgW: number,\n region: CharRegion\n): { count: number; hasBottom: boolean; hasTop: boolean } {\n const rw = region.right - region.left;\n const rh = region.bottom - region.top;\n if (rw < 3 || rh < 3) return { count: 0, hasBottom: false, hasTop: false };\n\n // Extract region: 1 = WHITE (text), 0 = BLACK (background/potential hole)\n const grid = new Uint8Array(rw * rh);\n for (let ly = 0; ly < rh; ly++) {\n for (let lx = 0; lx < rw; lx++) {\n const px = pixels[(region.top + ly) * imgW + (region.left + lx)];\n grid[ly * rw + lx] = px >= 128 ? 1 : 0;\n }\n }\n\n // BFS flood-fill from all border BLACK (0) pixels\n const visited = new Uint8Array(rw * rh);\n const queue: number[] = [];\n\n for (let lx = 0; lx < rw; lx++) {\n if (grid[lx] === 0 && !visited[lx]) {\n visited[lx] = 1;\n queue.push(lx);\n }\n const bottom = (rh - 1) * rw + lx;\n if (grid[bottom] === 0 && !visited[bottom]) {\n visited[bottom] = 1;\n queue.push(bottom);\n }\n }\n for (let ly = 0; ly < rh; ly++) {\n const left = ly * rw;\n if (grid[left] === 0 && !visited[left]) {\n visited[left] = 1;\n queue.push(left);\n }\n const right = ly * rw + rw - 1;\n if (grid[right] === 0 && !visited[right]) {\n visited[right] = 1;\n queue.push(right);\n }\n }\n\n // BFS\n let qi = 0;\n while (qi < queue.length) {\n const idx = queue[qi++];\n const lx = idx % rw;\n const ly = Math.floor(idx / rw);\n for (const [dx, dy] of [\n [0, 1],\n [0, -1],\n [1, 0],\n [-1, 0],\n ] as const) {\n const nx = lx + dx;\n const ny = ly + dy;\n if (nx < 0 || nx >= rw || ny < 0 || ny >= rh) continue;\n const ni = ny * rw + nx;\n if (!visited[ni] && grid[ni] === 0) {\n visited[ni] = 1;\n queue.push(ni);\n }\n }\n }\n\n // Any BLACK (0) pixel not visited = enclosed hole inside white text\n let holeCount = 0;\n let hasBottom = false;\n let hasTop = false;\n const midY = rh / 2;\n\n // Use flood-fill to count distinct holes\n for (let ly = 0; ly < rh; ly++) {\n for (let lx = 0; lx < rw; lx++) {\n const idx = ly * rw + lx;\n if (grid[idx] === 0 && !visited[idx]) {\n // Found an unvisited black region enclosed by white — measure its area\n const holeQueue = [idx];\n visited[idx] = 1;\n let hi = 0;\n let area = 0;\n let sumY = 0;\n while (hi < holeQueue.length) {\n const hidx = holeQueue[hi++];\n area++;\n sumY += Math.floor(hidx / rw);\n const hx = hidx % rw;\n const hy = Math.floor(hidx / rw);\n for (const [dx, dy] of [\n [0, 1],\n [0, -1],\n [1, 0],\n [-1, 0],\n ] as const) {\n const hnx = hx + dx;\n const hny = hy + dy;\n if (hnx < 0 || hnx >= rw || hny < 0 || hny >= rh) continue;\n const hni = hny * rw + hnx;\n if (!visited[hni] && grid[hni] === 0) {\n visited[hni] = 1;\n holeQueue.push(hni);\n }\n }\n }\n\n // Only count holes larger than 0.5% of the character area (filters out tiny blur artifacts)\n const charArea = rw * rh;\n if (area > charArea * 0.005) {\n holeCount++;\n const avgY = sumY / area;\n if (avgY >= midY) hasBottom = true;\n else hasTop = true;\n }\n }\n }\n }\n\n return { count: holeCount, hasBottom, hasTop };\n}\n\n// ── Character analysis ──────────────────────────────────────────────\n\nfunction analyseCharacter(\n pixels: Uint8Array,\n imgW: number,\n _imgH: number,\n region: CharRegion\n): CharFeatures {\n const rw = region.right - region.left;\n const rh = region.bottom - region.top;\n const holes = detectHoles(pixels, imgW, region);\n\n // Aspect ratio (height / width)\n const aspectRatio = rh / Math.max(rw, 1);\n\n // Width of text in top 25% vs bottom 25%\n const quarterH = Math.max(3, Math.floor(rh * 0.25));\n let topMinX = rw,\n topMaxX = 0,\n botMinX = rw,\n botMaxX = 0;\n for (let lx = 0; lx < rw; lx++) {\n for (let ly = 0; ly < quarterH; ly++) {\n if (pixels[(region.top + ly) * imgW + (region.left + lx)] >= 128) {\n if (lx < topMinX) topMinX = lx;\n if (lx > topMaxX) topMaxX = lx;\n }\n }\n for (let ly = rh - quarterH; ly < rh; ly++) {\n if (pixels[(region.top + ly) * imgW + (region.left + lx)] >= 128) {\n if (lx < botMinX) botMinX = lx;\n if (lx > botMaxX) botMaxX = lx;\n }\n }\n }\n const topWidth = topMaxX > topMinX ? (topMaxX - topMinX) / rw : 0;\n const bottomWidth = botMaxX > botMinX ? (botMaxX - botMinX) / rw : 0;\n const bottomHorizontalExtent = bottomWidth;\n const topHorizontalExtent = topWidth;\n\n // Top curvature: is there significant dark area in the top-right quadrant?\n // \"2\" has a curved top-right; \"1\" and \"L\" don't\n const topQuarterH = Math.max(3, Math.floor(rh * 0.25));\n const rightHalf = Math.floor(rw / 2);\n let topRightDark = 0;\n let topRightTotal = 0;\n for (let ly = 0; ly < topQuarterH; ly++) {\n for (let lx = rightHalf; lx < rw; lx++) {\n topRightTotal++;\n if (pixels[(region.top + ly) * imgW + (region.left + lx)] >= 128) {\n topRightDark++;\n }\n }\n }\n const topCurvature = topRightTotal > 0 && topRightDark / topRightTotal > 0.15;\n\n return {\n hasHoleBottom: holes.hasBottom,\n hasHoleTop: holes.hasTop,\n holeCount: holes.count,\n aspectRatio,\n bottomHorizontalExtent,\n topHorizontalExtent,\n topCurvature,\n };\n}\n\n// ── Classification rules ────────────────────────────────────────────\n\nfunction classifyFromFeatures(features: CharFeatures, _votedChar: string): string | null {\n // Rule 1: Closed loop at bottom → \"6\" (not \"2\")\n if (features.hasHoleBottom && !features.hasHoleTop) {\n return '6';\n }\n\n // Rule 2: Two holes → \"8\"\n if (features.holeCount >= 2) {\n return '8';\n }\n\n // Rule 3: One hole at top only → \"0\" or \"9\"\n if (features.hasHoleTop && !features.hasHoleBottom) {\n // Could be 0, 9, P, D — don't change unless we're sure\n return null;\n }\n\n // Rule 4: Very narrow (aspect ratio > 1.8) and no holes → \"1\"\n if (features.holeCount === 0 && features.aspectRatio > 1.8 && !features.topCurvature) {\n return '1';\n }\n\n // Rule 5: Bottom wider than top + no holes → \"L\"\n // L has a wide horizontal foot at bottom but narrow stem at top\n if (\n features.holeCount === 0 &&\n features.bottomHorizontalExtent > 0.5 &&\n features.bottomHorizontalExtent > features.topHorizontalExtent * 1.15 &&\n features.aspectRatio > 0.8\n ) {\n return 'L';\n }\n\n // No confident classification — keep the voted character\n return null;\n}\n","export { Solver } from './solver.js';\nexport {\n majorityVote,\n majorityVoteDetailed,\n LEGACY_CONFUSION_GROUPS,\n DITHER_CONFUSION_GROUPS,\n} from './solver.js';\nexport type { SolverOptions, SolveOptions, SolveResult, Provider } from './solver.js';\nexport type { LanguageModelUsage } from 'ai';\nexport { preprocessCaptcha, preprocessCaptchaToBuffer, imageToBase64 } from './preprocess.js';\nexport type { PreprocessOptions, CropFractions } from './preprocess.js';\nexport { createTesseractReader, TESSERACT_VARIANTS } from './tesseract.js';\nexport type { TesseractReader } from './tesseract.js';\nexport { disambiguateResult } from './disambiguate.js';\n"],"mappings":"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","names":["sharp","TESSERACT_VARIANTS","createTesseractReader"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yigitahmetsahin/captcha-solver",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "AI-powered captcha solver using image preprocessing and multi-provider vision models (Vercel AI SDK)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -54,10 +54,14 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"ai": "^6.0.146",
|
|
56
56
|
"dotenv": "^16.4.7",
|
|
57
|
-
"sharp": "^0.33.5"
|
|
57
|
+
"sharp": "^0.33.5",
|
|
58
|
+
"tesseract.js": "^7.0.0"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
|
-
"@ai-sdk/
|
|
61
|
+
"@ai-sdk/anthropic": "^3.0.67",
|
|
62
|
+
"@ai-sdk/openai": "^3.0.51",
|
|
63
|
+
"@emnapi/core": "^1.9.2",
|
|
64
|
+
"@emnapi/runtime": "^1.9.2",
|
|
61
65
|
"@eslint/js": "^9.39.2",
|
|
62
66
|
"@types/node": "^22.10.0",
|
|
63
67
|
"@vitest/coverage-v8": "^4.0.18",
|
|
@@ -72,7 +76,7 @@
|
|
|
72
76
|
"vitest": "^4.0.17"
|
|
73
77
|
},
|
|
74
78
|
"peerDependencies": {
|
|
75
|
-
"@ai-sdk/anthropic": "
|
|
79
|
+
"@ai-sdk/anthropic": "^3.0.67",
|
|
76
80
|
"@ai-sdk/google": ">=1.0.0",
|
|
77
81
|
"@ai-sdk/openai": ">=1.0.0"
|
|
78
82
|
},
|