playwright / helpers /captcha /audio.js
cv3inx's picture
Add local CAPTCHA solvers + integrate self-hosted turnstile-solver
a920dd4
Raw
History Blame Contribute Delete
1.71 kB
// Audio CAPTCHA solver — Whisper running locally via @huggingface/transformers.
// No API key, no external service. Model files are pre-downloaded at build time.
//
// Best fit: reCAPTCHA v2 audio fallback (English).
let _pipePromise = null;
async function getPipeline() {
if (_pipePromise) return _pipePromise;
_pipePromise = (async () => {
const { pipeline, env } = await import('@huggingface/transformers');
// Cache + offline-friendly defaults
env.allowLocalModels = true;
env.cacheDir = process.env.TRANSFORMERS_CACHE || '/app/.cache/transformers';
// whisper-tiny.en is fast, English-only, ~40MB. Good for reCAPTCHA.
const model = process.env.WHISPER_MODEL || 'Xenova/whisper-tiny.en';
return pipeline('automatic-speech-recognition', model, {
quantized: true,
});
})();
return _pipePromise;
}
// Transcribe an audio buffer (Uint8Array or Buffer) → text.
async function transcribe(audioBuffer) {
const pipe = await getPipeline();
// The pipeline accepts a URL, a Buffer, or a Float32Array. Buffer is simplest.
const result = await pipe(new Uint8Array(audioBuffer), {
chunk_length_s: 30,
stride_length_s: 5,
});
return (result?.text || '').trim();
}
// Convenience: download the URL with the page's auth cookies & transcribe.
async function transcribeUrl(page, url) {
const buf = await page.evaluate(async (u) => {
const r = await fetch(u, { credentials: 'include' });
if (!r.ok) throw new Error('audio fetch failed: ' + r.status);
const ab = await r.arrayBuffer();
return Array.from(new Uint8Array(ab));
}, url);
return transcribe(Buffer.from(buf));
}
module.exports = { transcribe, transcribeUrl, getPipeline };