diff --git a/.babelrc.json b/.babelrc.json deleted file mode 100644 index 19301bb..0000000 --- a/.babelrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - ["@babel/env", {"useBuiltIns": "usage", "corejs": {"version": 3}}] - ], - "env": { - "production": { - "plugins": ["lodash"] - } - } -} diff --git a/README.md b/README.md index 7ea1b90..5429a86 100644 --- a/README.md +++ b/README.md @@ -25,15 +25,17 @@ please consider contributing with ## Description -> Obviously, this blue part here is the land. -> -> — Byron "Buster" Bluth, reading a map - Buster is a browser extension which helps you to solve difficult captchas by completing reCAPTCHA audio challenges using speech recognition. Challenges are solved by clicking on the extension button at the bottom of the reCAPTCHA widget. +> Obviously, this blue part here is the land. +> +> — Byron "Buster" Bluth, reading a map + +## Motivation + reCAPTCHA challenges remain a considerable burden on the web, delaying and often blocking our access to services and information depending on our physical and cognitive abilities, our social @@ -42,8 +44,16 @@ and cultural background, and the devices or networks we connect from. The difficulty of captchas can be so out of balance, that sometimes they seem friendlier to bots than they are to humans. -The goal of this project is to improve our experience with captchas, -by giving us easy access to solutions already utilized by automated systems. +The goal of this project is to improve our experience on the web, +by giving us easy access to solutions utilized by automated systems. + +## Client App + +The client app enables you to simulate user interactions and improves +the success rate of the extension. Follow the instructions +from the extension's options to download and install the client app +on Windows, Linux and macOS, or get the app +from [this](https://github.com/dessant/buster-client#readme) repository. ## Screenshots diff --git a/secrets.json.example b/secrets.json.example index 1272511..019b68a 100644 --- a/secrets.json.example +++ b/secrets.json.example @@ -2,7 +2,6 @@ "witApiKeys": { "arabic": "", "bengali": "", - "catalan": "", "chinese": "", "dutch": "", "english": "", @@ -25,7 +24,6 @@ "spanish": "", "swedish": "", "tamil": "", - "telugu": "", "thai": "", "turkish": "", "urdu": "", diff --git a/src/assets/locales/en/messages.json b/src/assets/locales/en/messages.json index 740cb19..01e44af 100644 --- a/src/assets/locales/en/messages.json +++ b/src/assets/locales/en/messages.json @@ -44,158 +44,148 @@ "description": "Value of the option." }, - "optionTitle_ibmSpeechApiLoc": { + "optionTitle_microsoftSpeechApiLoc": { "message": "API location", "description": "Title of the option." }, - "optionValue_ibmSpeechApiLoc_seoul": { - "message": "Seoul", + "optionValue_microsoftSpeechApiLoc_southafricanorth": { + "message": "South Africa North", "description": "Value of the option." }, - "optionValue_ibmSpeechApiLoc_london": { - "message": "London", + "optionValue_microsoftSpeechApiLoc_eastasia": { + "message": "East Asia", "description": "Value of the option." }, - "optionValue_ibmSpeechApiLoc_frankfurt": { - "message": "Frankfurt", + "optionValue_microsoftSpeechApiLoc_southeastasia": { + "message": "Southeast Asia", "description": "Value of the option." }, - "optionValue_ibmSpeechApiLoc_dallas": { - "message": "Dallas", + "optionValue_microsoftSpeechApiLoc_australiaeast": { + "message": "Australia East", "description": "Value of the option." }, - "optionValue_ibmSpeechApiLoc_washington": { - "message": "Washington DC", + "optionValue_microsoftSpeechApiLoc_centralindia": { + "message": "Central India", "description": "Value of the option." }, - "optionValue_ibmSpeechApiLoc_sydney": { - "message": "Sydney", + "optionValue_microsoftSpeechApiLoc_japaneast": { + "message": "Japan East", "description": "Value of the option." }, - "optionValue_ibmSpeechApiLoc_tokyo": { - "message": "Tokyo", + "optionValue_microsoftSpeechApiLoc_japanwest": { + "message": "Japan West", "description": "Value of the option." }, - "optionTitle_microsoftSpeechApiLoc": { - "message": "API location", - "description": "Title of the option." - }, - - "optionValue_microsoftSpeechApiLoc_eastAu": { - "message": "Australia East", + "optionValue_microsoftSpeechApiLoc_koreacentral": { + "message": "Korea Central", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_centralCa": { + "optionValue_microsoftSpeechApiLoc_canadacentral": { "message": "Canada Central", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_centralUs": { - "message": "Central US", - "description": "Value of the option." - }, - - "optionValue_microsoftSpeechApiLoc_centralFr": { - "message": "France Central", + "optionValue_microsoftSpeechApiLoc_northeurope": { + "message": "North Europe", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_centralIn": { - "message": "Central India", + "optionValue_microsoftSpeechApiLoc_westeurope": { + "message": "West Europe", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_eastJp": { - "message": "Japan East", + "optionValue_microsoftSpeechApiLoc_francecentral": { + "message": "France Central", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_westJp": { - "message": "Japan West", + "optionValue_microsoftSpeechApiLoc_germanywestcentral": { + "message": "Germany West Central", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_southBr": { - "message": "Brazil South", + "optionValue_microsoftSpeechApiLoc_norwayeast": { + "message": "Norway East", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_centralKr": { - "message": "Korea Central", + "optionValue_microsoftSpeechApiLoc_switzerlandnorth": { + "message": "Switzerland North", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_northCh": { - "message": "Switzerland North", + "optionValue_microsoftSpeechApiLoc_switzerlandwest": { + "message": "Switzerland West", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_northCentralUs": { - "message": "North Central US", + "optionValue_microsoftSpeechApiLoc_uksouth": { + "message": "UK South", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_southCentralUs": { - "message": "South Central US", + "optionValue_microsoftSpeechApiLoc_uaenorth": { + "message": "UAE North", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_westCentralUs": { - "message": "West Central US", + "optionValue_microsoftSpeechApiLoc_brazilsouth": { + "message": "Brazil South", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_southUk": { - "message": "UK South", + "optionValue_microsoftSpeechApiLoc_centralus": { + "message": "Central US", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_eastUs": { + "optionValue_microsoftSpeechApiLoc_eastus": { "message": "East US", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_eastUs2": { + "optionValue_microsoftSpeechApiLoc_eastus2": { "message": "East US 2", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_westUs": { - "message": "West US", + "optionValue_microsoftSpeechApiLoc_northcentralus": { + "message": "North Central US", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_westUs2": { - "message": "West US 2", + "optionValue_microsoftSpeechApiLoc_southcentralus": { + "message": "South Central US", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_eastAsia": { - "message": "East Asia", + "optionValue_microsoftSpeechApiLoc_westcentralus": { + "message": "West Central US", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_southeastAsia": { - "message": "Southeast Asia", + "optionValue_microsoftSpeechApiLoc_westus": { + "message": "West US", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_westEu": { - "message": "West Europe", + "optionValue_microsoftSpeechApiLoc_westus2": { + "message": "West US 2", "description": "Value of the option." }, - "optionValue_microsoftSpeechApiLoc_northEu": { - "message": "North Europe", + "optionValue_microsoftSpeechApiLoc_westus3": { + "message": "West US 3", "description": "Value of the option." }, @@ -279,11 +269,6 @@ "description": "Value of the option." }, - "optionValue_witSpeechApiLang_catalan": { - "message": "Catalan", - "description": "Value of the option." - }, - "optionValue_witSpeechApiLang_chinese": { "message": "Chinese", "description": "Value of the option." @@ -394,11 +379,6 @@ "description": "Value of the option." }, - "optionValue_witSpeechApiLang_telugu": { - "message": "Telugu", - "description": "Value of the option." - }, - "optionValue_witSpeechApiLang_thai": { "message": "Thai", "description": "Value of the option." @@ -419,6 +399,11 @@ "description": "Value of the option." }, + "inputLabel_apiUrl": { + "message": "API endpoint", + "description": "Label of the input." + }, + "inputLabel_apiKey": { "message": "API key", "description": "Label of the input." @@ -576,6 +561,16 @@ "description": "Error message." }, + "error_captchaNotSolvedWitai": { + "message": "Wit.ai could not detect any speech. Try a new challenge, or switch to a more reliable service from the extension's options, such as IBM Watson.", + "description": "Error message." + }, + + "error_missingApiUrl": { + "message": "API endpoint missing. Visit the extension's options to configure the service.", + "description": "Error message." + }, + "error_missingApiKey": { "message": "API key missing. Visit the extension's options to configure the service.", "description": "Error message." diff --git a/src/background/main.js b/src/background/main.js index 7b32f3a..8bb50bd 100644 --- a/src/background/main.js +++ b/src/background/main.js @@ -27,9 +27,7 @@ import { captchaGoogleSpeechApiLangCodes, captchaIbmSpeechApiLangCodes, captchaMicrosoftSpeechApiLangCodes, - captchaWitSpeechApiLangCodes, - ibmSpeechApiUrls, - microsoftSpeechApiUrls + captchaWitSpeechApiLangCodes } from 'utils/data'; import {targetEnv, clientAppVersion} from 'utils/config'; @@ -298,8 +296,7 @@ async function getWitSpeechApiKey(speechService, language) { async function getWitSpeechApiResult(apiKey, audioContent) { const result = {}; - const rsp = await fetch('https://api.wit.ai/speech?v=20200513', { - referrer: '', + const rsp = await fetch('https://api.wit.ai/speech?v=20221114', { mode: 'cors', method: 'POST', headers: { @@ -316,7 +313,7 @@ async function getWitSpeechApiResult(apiKey, audioContent) { throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`); } } else { - const data = (await rsp.json()).text; + const data = JSON.parse((await rsp.text()).split('\r\n').at(-1)).text; if (data) { result.text = data.trim(); } @@ -325,15 +322,56 @@ async function getWitSpeechApiResult(apiKey, audioContent) { return result; } -async function getIbmSpeechApiResult(apiUrl, apiKey, audioContent, language) { +async function getGoogleSpeechApiResult( + apiKey, + audioContent, + language, + detectAltLanguages +) { + const data = { + audio: { + content: arrayBufferToBase64(audioContent) + }, + config: { + encoding: 'LINEAR16', + languageCode: language, + model: 'video', + sampleRateHertz: 16000 + } + }; + + if (!['en-US', 'en-GB'].includes(language) && detectAltLanguages) { + data.config.model = 'default'; + data.config.alternativeLanguageCodes = ['en-US']; + } + + const rsp = await fetch( + `https://speech.googleapis.com/v1p1beta1/speech:recognize?key=${apiKey}`, + { + mode: 'cors', + method: 'POST', + body: JSON.stringify(data) + } + ); + + if (rsp.status !== 200) { + throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`); + } + + const results = (await rsp.json()).results; + if (results) { + return results[0].alternatives[0].transcript.trim(); + } +} + +async function getIbmSpeechApiResult(apiUrl, apiKey, audioContent, model) { const rsp = await fetch( - `${apiUrl}?model=${language}&profanity_filter=false`, + `${apiUrl}/v1/recognize?model=${model}&profanity_filter=false`, { - referrer: '', mode: 'cors', method: 'POST', headers: { - Authorization: 'Basic ' + window.btoa('apiKey:' + apiKey), + Authorization: 'Basic ' + window.btoa('apikey:' + apiKey), 'X-Watson-Learning-Opt-Out': 'true' }, body: new Blob([audioContent], {type: 'audio/wav'}) @@ -351,15 +389,14 @@ async function getIbmSpeechApiResult(apiUrl, apiKey, audioContent, language) { } async function getMicrosoftSpeechApiResult( - apiUrl, + apiLocation, apiKey, audioContent, language ) { const rsp = await fetch( - `${apiUrl}?language=${language}&format=detailed&profanity=raw`, + `https://${apiLocation}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=${language}&format=detailed&profanity=raw`, { - referrer: '', mode: 'cors', method: 'POST', headers: { @@ -383,7 +420,7 @@ async function getMicrosoftSpeechApiResult( async function transcribeAudio(audioUrl, lang) { let solution; - const audioRsp = await fetch(audioUrl, {referrer: ''}); + const audioRsp = await fetch(audioUrl); const audioContent = await prepareAudio(await audioRsp.arrayBuffer()); const {speechService, tryEnglishSpeechModel} = await storage.get([ @@ -395,6 +432,7 @@ async function transcribeAudio(audioUrl, lang) { const language = captchaWitSpeechApiLangCodes[lang] || 'english'; const apiKey = await getWitSpeechApiKey(speechService, language); + if (!apiKey) { showNotification({messageId: 'error_missingApiKey'}); return; @@ -412,10 +450,12 @@ async function transcribeAudio(audioUrl, lang) { if (!solution && language !== 'english' && tryEnglishSpeechModel) { const apiKey = await getWitSpeechApiKey(speechService, 'english'); + if (!apiKey) { showNotification({messageId: 'error_missingApiKey'}); return; } + const result = await getWitSpeechApiResult(apiKey, audioContent); if (result.errorId) { showNotification({ @@ -430,86 +470,62 @@ async function transcribeAudio(audioUrl, lang) { const {googleSpeechApiKey: apiKey} = await storage.get( 'googleSpeechApiKey' ); + if (!apiKey) { showNotification({messageId: 'error_missingApiKey'}); return; } - const apiUrl = `https://speech.googleapis.com/v1p1beta1/speech:recognize?key=${apiKey}`; const language = captchaGoogleSpeechApiLangCodes[lang] || 'en-US'; - const data = { - audio: { - content: arrayBufferToBase64(audioContent) - }, - config: { - encoding: 'LINEAR16', - languageCode: language, - model: 'video', - sampleRateHertz: 16000 - } - }; - if (!['en-US', 'en-GB'].includes(language) && tryEnglishSpeechModel) { - data.config.model = 'default'; - data.config.alternativeLanguageCodes = ['en-US']; - } - - const rsp = await fetch(apiUrl, { - referrer: '', - mode: 'cors', - method: 'POST', - body: JSON.stringify(data) - }); - - if (rsp.status !== 200) { - throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`); - } + solution = await getGoogleSpeechApiResult( + apiKey, + audioContent, + language, + tryEnglishSpeechModel + ); + } else if (speechService === 'ibmSpeechApi') { + const {ibmSpeechApiUrl: apiUrl, ibmSpeechApiKey: apiKey} = + await storage.get(['ibmSpeechApiUrl', 'ibmSpeechApiKey']); - const results = (await rsp.json()).results; - if (results) { - solution = results[0].alternatives[0].transcript.trim(); + if (!apiUrl) { + showNotification({messageId: 'error_missingApiUrl'}); + return; } - } else if (speechService === 'ibmSpeechApi') { - const {ibmSpeechApiLoc: apiLoc, ibmSpeechApiKey: apiKey} = - await storage.get(['ibmSpeechApiLoc', 'ibmSpeechApiKey']); if (!apiKey) { showNotification({messageId: 'error_missingApiKey'}); return; } - const apiUrl = ibmSpeechApiUrls[apiLoc]; - const language = - captchaIbmSpeechApiLangCodes[lang] || 'en-US_BroadbandModel'; - solution = await getIbmSpeechApiResult( - apiUrl, - apiKey, - audioContent, - language - ); + const model = captchaIbmSpeechApiLangCodes[lang] || 'en-US_Multimedia'; + + solution = await getIbmSpeechApiResult(apiUrl, apiKey, audioContent, model); + if ( !solution && - !['en-US_BroadbandModel', 'en-GB_BroadbandModel'].includes(language) && + !['en-US_Multimedia', 'en-GB_Multimedia'].includes(model) && tryEnglishSpeechModel ) { solution = await getIbmSpeechApiResult( apiUrl, apiKey, audioContent, - 'en-US_BroadbandModel' + 'en-US_Multimedia' ); } } else if (speechService === 'microsoftSpeechApi') { - const {microsoftSpeechApiLoc: apiLoc, microsoftSpeechApiKey: apiKey} = + const {microsoftSpeechApiLoc: apiLocaction, microsoftSpeechApiKey: apiKey} = await storage.get(['microsoftSpeechApiLoc', 'microsoftSpeechApiKey']); + if (!apiKey) { showNotification({messageId: 'error_missingApiKey'}); return; } - const apiUrl = microsoftSpeechApiUrls[apiLoc]; + const language = captchaMicrosoftSpeechApiLangCodes[lang] || 'en-US'; solution = await getMicrosoftSpeechApiResult( - apiUrl, + apiLocaction, apiKey, audioContent, language @@ -520,7 +536,7 @@ async function transcribeAudio(audioUrl, lang) { tryEnglishSpeechModel ) { solution = await getMicrosoftSpeechApiResult( - apiUrl, + apiLocaction, apiKey, audioContent, 'en-US' @@ -529,7 +545,14 @@ async function transcribeAudio(audioUrl, lang) { } if (!solution) { - showNotification({messageId: 'error_captchaNotSolved', timeout: 6000}); + if (['witSpeechApiDemo', 'witSpeechApi'].includes(speechService)) { + showNotification({ + messageId: 'error_captchaNotSolvedWitai', + timeout: 60000 + }); + } else { + showNotification({messageId: 'error_captchaNotSolved', timeout: 6000}); + } } else { return solution; } diff --git a/src/options/App.vue b/src/options/App.vue index 5deb68c..138e251 100644 --- a/src/options/App.vue +++ b/src/options/App.vue @@ -36,15 +36,14 @@
- - +