You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buster/src/solve/main.js

218 lines
5.2 KiB
JavaScript

import browser from 'webextension-polyfill';
import audioBufferToWav from 'audiobuffer-to-wav';
import storage from 'storage/storage';
import {getText, waitForElement, arrayBufferToBase64} from 'utils/common';
import {captchaGoogleSpeechApiLangCodes} from 'utils/data';
let solverRunning = false;
function setButton() {
const infoButton = document.body.querySelector(
'button#recaptcha-help-button'
);
if (infoButton) {
infoButton.remove();
const div = document.createElement('div');
div.classList.add('button-holder');
const button = document.createElement('button');
button.classList.add('rc-button', 'goog-inline-block');
button.setAttribute('tabindex', '0');
button.setAttribute('title', getText('buttonText_solve'));
button.id = 'buster-button';
button.addEventListener('click', start);
button.addEventListener('keydown', e => {
if (['Enter', ' '].includes(e.key)) {
start(e);
}
});
div.appendChild(button);
document.querySelector('.rc-buttons').appendChild(div);
}
}
async function isBlocked({timeout = 0} = {}) {
const selector = '.rc-doscaptcha-body';
if (timeout) {
return Boolean(await waitForElement(selector, {timeout}));
}
return Boolean(document.querySelector(selector));
}
async function prepareAudio(audio) {
const ctx = new AudioContext();
const data = await ctx.decodeAudioData(audio);
await ctx.close();
const offlineCtx = new OfflineAudioContext({
// force mono output
numberOfChannels: 1,
length: 16000 * data.duration,
sampleRate: 16000
});
const source = offlineCtx.createBufferSource();
source.buffer = data;
source.connect(offlineCtx.destination);
// discard 1 second noise from beginning/end
source.start(0, 1, data.duration - 2);
return arrayBufferToBase64(
audioBufferToWav(await offlineCtx.startRendering())
);
}
function dispatchEnter(node) {
const keyEvent = {
code: 'Enter',
key: 'Enter',
keyCode: 13,
which: 13,
view: window,
bubbles: true,
composed: true,
cancelable: true
};
node.focus();
node.dispatchEvent(new KeyboardEvent('keydown', keyEvent));
node.dispatchEvent(new KeyboardEvent('keypress', keyEvent));
node.click();
}
async function solve() {
let audioUrl;
let solution;
if (await isBlocked()) {
return;
}
const audioEl = document.querySelector('#audio-source');
if (audioEl) {
audioUrl = audioEl.src;
} else {
dispatchEnter(document.querySelector('button#recaptcha-audio-button'));
const result = await Promise.race([
new Promise(resolve => {
waitForElement('#audio-source', {timeout: 10000}).then(el =>
resolve({audioUrl: el && el.src})
);
}),
new Promise(resolve => {
isBlocked({timeout: 10000}).then(blocked => resolve({blocked}));
})
]);
if (result.blocked) {
return;
}
audioUrl = result.audioUrl;
}
const audioRsp = await fetch(audioUrl, {referrer: ''});
const audioContent = await prepareAudio(await audioRsp.arrayBuffer());
const {speechService} = await storage.get('speechService', 'sync');
if (['googleSpeechApiDemo', 'googleSpeechApi'].includes(speechService)) {
let apiUrl;
if (speechService === 'googleSpeechApiDemo') {
apiUrl =
'https://cxl-services.appspot.com/proxy?url=https://speech.googleapis.com/v1/speech:recognize';
} else {
const {googleSpeechApiKey: apiKey} = await storage.get(
'googleSpeechApiKey',
'sync'
);
if (!apiKey) {
browser.runtime.sendMessage({
id: 'notification',
messageId: 'error_missingApiKey'
});
return;
}
apiUrl = `https://speech.googleapis.com/v1/speech:recognize?key=${apiKey}`;
}
const data = {
audio: {
content: audioContent
},
config: {
encoding: 'LINEAR16',
languageCode:
captchaGoogleSpeechApiLangCodes[document.documentElement.lang] ||
'en-US',
model: 'default',
sampleRateHertz: 16000
}
};
const rsp = await fetch(apiUrl, {
referrer: '',
mode: 'cors',
method: 'POST',
body: JSON.stringify(data)
});
const results = (await rsp.json()).results;
if (results) {
solution = results[0].alternatives[0].transcript;
}
}
if (!solution) {
browser.runtime.sendMessage({
id: 'notification',
messageId: 'error_captchaNotSolved'
});
return;
}
document.querySelector('#audio-response').value = solution;
dispatchEnter(document.querySelector('#recaptcha-verify-button'));
browser.runtime.sendMessage({id: 'captchaSolved'});
}
function start(e) {
e.preventDefault();
e.stopImmediatePropagation();
if (solverRunning) {
return;
}
solverRunning = true;
solve()
.then(() => {
solverRunning = false;
})
.catch(err => {
solverRunning = false;
console.log(err.toString());
browser.runtime.sendMessage({
id: 'notification',
messageId: 'error_internalError'
});
});
}
function init() {
const observer = new MutationObserver(setButton);
observer.observe(document.body, {
childList: true,
subtree: true
});
setButton();
}
init();